Skip to content
VLSI Mentor

Wishbone · Module 2

Masters and Slaves

Master and slave are transaction roles, not a statement about importance or hierarchy. The role determines exactly which information each side owns: the initiator supplies address, direction and write data; the target supplies read data, completion and any error. Getting that ownership wrong is the source of an entire family of integration bugs.

Module 1 closed by arguing that independently written blocks need a published interface contract. It did not say what such a contract contains.

Module 2 builds that vocabulary — the concepts every on-chip bus shares, before any one bus's version of them. It uses generic names throughout, and says so every time, because the whole point of Module 2 is that these ideas are not Wishbone's.

The first of them is the most basic and the most often stated carelessly.

Which side of a bus connection starts an operation, and which side answers — and what, precisely, does each side get to decide?

1. The Roles, and What They Are Not

A master — equivalently an initiator — is the side that starts a transfer. It decides that an access should happen, when it should happen, what address it targets, and whether it is a read or a write.

A slave — equivalently a target — is the side that answers. It never starts anything. It waits to be addressed, and when it is, it supplies whatever the transfer requires from its side and indicates that the transfer is finished.

A note on terminology, kept short because it is not the lesson. This curriculum uses master and slave where the Wishbone specification does, because reading the specification and the ecosystem's RTL requires those words. Much of the industry now writes initiator and target, or manager and subordinate — Arm's AMBA specifications made that change, and APB's manager/subordinate model uses the newer terms for the same idea. The concepts are identical. This chapter uses both vocabularies interchangeably on purpose, so that neither one becomes the only one you can read.

Three things the role does not mean, each of which is a real misconception:

It is not a statement about importance or complexity. A DMA engine is an initiator and a 512 KiB SRAM controller is a target; the SRAM is by far the larger and more complex block. Initiating is a job, not a rank.

It is not a property of a block. A block is a master on a particular interface. Chapter 1.5's DMA engine is an initiator on the memory bus and simultaneously a target on the register bus the CPU configures it through. Same block, two interfaces, opposite roles, and both are active at once.

It is not a hierarchy. Nothing about being a master implies authority over a slave, or that a slave is "inside" a master. They are two ends of one interface, and the interface has no notion of above and below.

A DMA engine sits in the middle of the picture and holds two opposite roles at the same time. On its register interface it is a target: the CPU acts as initiator and writes the DMA engine's configuration registers, so the CPU starts those transfers and the DMA engine answers them. On its memory interface the same DMA engine is an initiator: it starts transfers of its own towards the SRAM, which acts as target and answers them. The role therefore belongs to the interface, not to the block, and the same block is a master on one port and a slave on another simultaneously.CPUmaster hereDMA register portSLAVE — it answersDMA memory portMASTER — it startsSRAMslave hereOne DMA engineboth roles, at onceThe role is theinterface'snot the block's12
Figure 1 — one DMA engine holding both roles at once, on two different interfaces.

2. Ownership — the Part That Matters

Here is the definition worth carrying out of this chapter. For each piece of information in a transfer, exactly one side decides its value, and the role determines which.

InformationOwnerWhy that side and not the other
AddressinitiatorOnly the initiator knows what it wants to access
Direction (read or write)initiatorThe target cannot infer intent from an address
Write datainitiatorIt is the value being delivered
Byte granularityinitiatorWhich bytes matter is a property of the request
Request qualificationinitiatorOnly the initiator knows when its request is real
Read datatargetThe value exists only inside the target
CompletiontargetOnly the target knows when it has finished
ErrortargetOnly the target can judge whether the access was legal

Read the split, not the rows. Everything describing what is being asked for flows one way. Everything describing what happened flows the other. No item is owned by both, and none is unowned.

Two consequences follow immediately, and both are load-bearing for the rest of Module 2.

Each signal has exactly one driver. If two blocks can drive read data, the bus has two owners for one fact and the result is whatever the electrical or logical merge produces. Chapter 1.5 showed an AND-OR return path producing the bitwise OR of two targets' values — a number belonging to neither. That is an ownership violation before it is an electrical problem.

Ownership tells you where to look when something is wrong. A transfer that returns wrong read data is a target-side or return-path problem; the initiator did not produce that value and cannot have corrupted it. A transfer that reaches the wrong target is an initiator-side or decode problem; the target only ever sees what it was handed. One glance at the ownership table cuts a debug search in half, and Section 6 makes that concrete.

3. The Teaching Interface

Module 2 needs a concrete interface to reason about. Here it is, defined once and reused in every chapter.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// THE MODULE 2 TEACHING INTERFACE.
//
// These are GENERIC educational signal names. They are NOT Wishbone signal
// names and they are not any other bus's signal names either. Wishbone's
// actual signals — their names, directions and rules — are Module 4's
// subject, and meeting them before the concepts are in place is exactly the
// mistake Module 1 exists to prevent.
//
// Every signal below is owned by exactly one side, per Section 2.
// ─────────────────────────────────────────────────────────────────────────

//  Driven by the INITIATOR (master):
//    valid    1 = a request is being presented this cycle
//    write    1 = write, 0 = read
//    addr     [31:0] byte address of the access
//    wdata    [31:0] data to be written
//    byte_en  [3:0]  which byte lanes of wdata participate

//  Driven by the TARGET (slave):
//    ready    1 = this request is complete on this clock edge
//    rdata    [31:0] read data, valid in the cycle `ready` is high
//    error    1 = the access completed unsuccessfully

The one temporal rule Module 2 assumes, stated here and relied on throughout:

A transfer is accepted on a rising clock edge where valid and ready are both high. Until that edge, the initiator holds valid, write, addr, wdata and byte_en stable.

That is a teaching contract, not a standard. It is deliberately the simplest rule that makes the rest of the module expressible, and Chapter 2.5 is where its consequences are worked out properly. Real buses differ in the details — how many transfers may be outstanding, whether a request may be withdrawn, what happens on a reset mid-transfer — and those differences are exactly what a protocol specification exists to pin down.

4. RTL 1 — A Target That Answers

The simplest useful target: a GPIO block that owns three registers.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// gpio_target — a slave on the Module 2 teaching interface.
//
// PURPOSE. Demonstrate what a target owns and what it must never assume.
// It answers requests addressed to it; it never starts one; it drives only
// `ready`, `rdata` and `error`, and reads everything else.
//
// The address it sees is a LOCAL OFFSET, not a system address. The block has
// no idea where it lives — the separation Chapter 1.2 argued for, now a port.
// ─────────────────────────────────────────────────────────────────────────
module gpio_target #(
  parameter int unsigned AW   = 8,     // local offset width
  parameter int unsigned DW   = 32,
  parameter int unsigned PINS = 8
) (
  input  logic            clk,
  input  logic            rst_n,       // asynchronous assert, active low

  // ── Initiator-driven (this block only reads these) ───────────────────
  input  logic            sel,         // this target is the decoded one
  input  logic            valid,
  input  logic            write,
  input  logic [AW-1:0]   addr,        // LOCAL offset
  input  logic [DW-1:0]   wdata,
  input  logic [3:0]      byte_en,

  // ── Target-driven (this block owns these) ────────────────────────────
  output logic            ready,
  output logic [DW-1:0]   rdata,
  output logic            error,

  // ── The hardware this block exists to control ────────────────────────
  output logic [PINS-1:0] pin_out,
  input  logic [PINS-1:0] pin_in
);
  localparam logic [AW-1:0] REG_DIR = 'h00;   // 1 = output
  localparam logic [AW-1:0] REG_OUT = 'h04;   // value driven when enabled
  localparam logic [AW-1:0] REG_IN  = 'h08;   // read-only, samples the pins

  logic [PINS-1:0] dir_q, out_q, pin_sync_q, pin_meta_q;

  // An access is happening to THIS target only when it is selected AND the
  // initiator says its request is real. Either alone is not enough, and
  // Section 6 is the bug that results from using only one of them.
  logic access;
  assign access = sel & valid;

  logic addr_legal;
  always_comb begin
    unique case (addr)
      REG_DIR, REG_OUT, REG_IN: addr_legal = 1'b1;
      default:                  addr_legal = 1'b0;
    endcase
  end

  // ── Combinational: what this cycle decides ───────────────────────────
  // This block is never busy, so it completes in the cycle it is asked.
  // `ready` is gated by `access` because a target must never complete a
  // transfer it was not given — see assertion P2 in Section 5.
  assign ready = access;
  assign error = access & ~addr_legal;

  always_comb begin
    rdata = '0;                                   // default: no latch
    if (access && !write && addr_legal) begin
      unique case (addr)
        REG_DIR: rdata = {{(DW-PINS){1'b0}}, dir_q};
        REG_OUT: rdata = {{(DW-PINS){1'b0}}, out_q};
        REG_IN:  rdata = {{(DW-PINS){1'b0}}, pin_sync_q};
        default: rdata = '0;
      endcase
    end
  end

  // ── Sequential: what survives the cycle ──────────────────────────────
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      dir_q <= '0;
      out_q <= '0;
    end else if (access && write && addr_legal && byte_en[0]) begin
      // Only lane 0 carries these 8-bit registers. Honouring byte_en is what
      // lets a byte store to a neighbouring register leave these alone.
      unique case (addr)
        REG_DIR: dir_q <= wdata[PINS-1:0];
        REG_OUT: out_q <= wdata[PINS-1:0];
        default: ;                                 // REG_IN is read-only
      endcase
    end
  end

  // Two-flop synchroniser: pin_in is asynchronous to clk.
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      pin_meta_q <= '0;
      pin_sync_q <= '0;
    end else begin
      pin_meta_q <= pin_in;
      pin_sync_q <= pin_meta_q;
    end
  end

  assign pin_out = out_q & dir_q;
endmodule

Reading this module

Purpose. It is the smallest block that exhibits every target-side behaviour: it answers only when selected, it owns exactly three outputs, and it distinguishes a legal access from an illegal one.

Interface contract. sel says the decoder chose you; valid says the initiator means it. Both are required. addr is a local offset — the block never sees a system address, which is what lets it be instantiated twice at two different bases.

Combinational behaviour. Three decisions are made in the current cycle: whether this is an access at all, whether the offset is legal, and — on a read — which register's value to present. rdata is assigned '0 before the conditional, so every path assigns it and no latch can be inferred.

Sequential behaviour. Exactly two registers survive across cycles, plus the synchroniser. The write branch requires access && write && addr_legal && byte_en[0], which is four independent conditions, each of which is a real requirement rather than defensive clutter.

Timing. On any rising edge where access is high, the transfer is complete — ready is already high combinationally, and a write has landed. There are no wait states. That is a deliberate simplification, and Chapter 2.5 removes it.

Deliberate simplifications. No wait states, no interrupt output, no byte-granular access to a wider register, and a single-cycle ready that is a combinational function of the inputs. That last one is worth flagging: a combinational path from valid to ready becomes a loop the moment an initiator makes valid depend on ready — a hazard Chapter 1.3 §6 raised and that real protocols address with explicit rules about which side may depend on the other.

How it could fail. Drop sel from access and the block answers every transfer in the system. Drop valid and it answers on stale bus values between transfers. Drop addr_legal from the write branch and a write to an unimplemented offset silently lands in whichever register the unique case happens to fall through to. Forget byte_en and a byte-granular store corrupts a neighbour.

What to verify. Section 5.

5. Verification — Properties That Encode Ownership

Ownership is a structural claim, so it is assertable.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Ownership properties for a target on the Module 2 teaching interface.
// These check the ARCHITECTURAL claims of this chapter. They are not any
// bus's protocol rules — Wishbone's own properties are Module 26's subject.
// ─────────────────────────────────────────────────────────────────────────
module target_role_checker #(
  parameter int unsigned DW = 32
) (
  input logic          clk,
  input logic          rst_n,
  input logic          sel,
  input logic          valid,
  input logic          ready,
  input logic          error,
  input logic [DW-1:0] rdata
);
  default disable iff (!rst_n);

  // P1 — a target never completes a transfer it was not given. This is the
  //      ownership rule stated as a property: `ready` belongs to the target,
  //      but the target may only assert it inside an access aimed at it.
  property p_ready_requires_access;
    @(posedge clk) ready |-> (sel && valid);
  endproperty
  a_ready_requires_access : assert property (p_ready_requires_access)
    else $error("target asserted ready with sel=%b valid=%b", sel, valid);

  // P2 — the same rule for the error output. Split from P1 so a failure
  //      names which output escaped, rather than reporting "something did".
  property p_error_requires_access;
    @(posedge clk) error |-> (sel && valid);
  endproperty
  a_error_requires_access : assert property (p_error_requires_access)
    else $error("target asserted error outside an access");

  // P3 — an unselected target drives no read data. On a shared return path
  //      built as an OR reduction, a non-zero rdata from an unselected
  //      target corrupts the selected one's value. This catches it at the
  //      source instead of at the multiplexer.
  property p_quiet_when_unselected;
    @(posedge clk) (!sel) |-> (rdata == '0);
  endproperty
  a_quiet_when_unselected : assert property (p_quiet_when_unselected)
    else $error("unselected target is driving rdata = %h", rdata);
endmodule

Why these three. Each corresponds to a failure that actually happens and that produces no error anywhere in a working system. P1 and P2 catch a target that answers transfers belonging to someone else. P3 catches the quieter version — a target that stays out of the completion path but still contributes bits to a shared return path.

What is deliberately not asserted. Nothing about how long the target may take, because this chapter's target is never busy and the general rule is Chapter 2.5's. Nothing about the initiator's obligations, which need the transaction model first.

6. Failure Modes and the Evidence That Separates Them

Role and ownership violations have a characteristic property: they produce plausible behaviour rather than errors. These are worth knowing by symptom.

Symptom: writing one peripheral also changes another.

Candidate causes. Two targets are selected for one address — an ownership violation in the decoder. Or one target ignores sel and answers everything. Or two regions overlap.

Discriminating evidence. Probe both targets' sel inputs on the offending access. If both assert, the fault is decode and Chapter 2.2 owns it. If only one asserts and the other target still changed state, that target is not gating on sel — the fault is inside it, and assertion P1 fires at the exact cycle.

Likely RTL location. Either the decoder's region comparison, or the target's access term.

Symptom: a read returns a value that looks like two values merged.

Candidate causes. An unselected target driving non-zero read data into an OR-based return path.

Discriminating evidence. Compare the returned value against each target's rdata in the same cycle. A bitwise OR of two of them is conclusive, and it is a pattern you can recognise by eye once you have seen it. Assertion P3 catches it at the source.

Likely RTL location. The unselected target's read mux — usually a missing sel in its default assignment.

Symptom: transfers succeed but data is occasionally from the previous access.

Candidate causes. The initiator changed addr or wdata before the accepting edge, violating the stability rule in Section 3. Or it sampled rdata outside the cycle ready was high.

Discriminating evidence. Put valid, ready and addr on one waveform. If addr changes while valid is high and ready is low, the initiator broke the contract — the target is blameless. If both were stable, look at when the initiator captured rdata.

Likely RTL location. The initiator's request register, not the target.

Symptom: the bus works with one target and breaks when a second is added.

Candidate causes. Almost always a target that was answering unconditionally and got away with it while it was the only one.

Discriminating evidence. Add P1 to every target and run the existing tests. The one that fires was broken the whole time and was invisible because nothing contradicted it.

The general strategy: use the ownership table to pick a side before picking a block. Read data and completion are the target's; address, direction and write data are the initiator's. A wrong value in one of those columns is a bug on that column's side, and that single question halves the search before any waveform is opened.

7. Common Mistakes

"The master controls the slave."

Wrong mental model: master implies authority, so a slave does what it is told.

Concrete failure: an engineer expects a write to complete the action, and designs an initiator that assumes the peripheral has finished the physical work when the transfer ends. The transfer completed; the UART is still shifting bits.

Observable evidence: software that overwrites a transmit register while the previous byte is still going out, and loses data at high rates only.

Correct model: the roles describe who starts and who answers. A completed transfer means the register was written, not the hardware has finished acting on it.

"A block is either a master or a slave."

Wrong mental model: the role is a property of the block.

Concrete failure: a DMA engine designed with a single port, on the assumption it must pick one role — so its configuration registers end up reachable only through a side channel, or not at all.

Observable evidence: an integration where the CPU has no way to configure the DMA engine it is supposed to control.

Correct model: the role belongs to the interface. Figure 1's DMA engine is a target on its register port and a master on its memory port, simultaneously.

"A target can answer whenever it recognises its own address."

Wrong mental model: sel is redundant because the target could decode the address itself.

Concrete failure: the target answers on bus values left over between transfers, and answers transfers aimed at other targets whose addresses happen to alias.

Observable evidence: spurious completions with no request, and — on a shared return path — corruption of other targets' reads. Assertion P1 fires immediately.

Correct model: the decoder owns target selection, and the target owns its local offset. Splitting it that way is what lets the same block be instantiated twice at different bases.

"Read data just needs to be correct eventually."

Wrong mental model: the target puts the value on the bus and the initiator picks it up.

Concrete failure: a target that drives valid read data one cycle and drops it, against an initiator that captures a cycle later. The value is right in a waveform and wrong in a register.

Observable evidence: reads that return the previous access's data, consistently, which looks like an off-by-one in software.

Correct model: read data has a defined validity window — here, the cycle ready is high — and both sides must agree on it. That agreement is precisely what a protocol specification fixes.

8. Interview Reasoning

It means that on a particular interface, that block starts transfers: it decides an access should happen, when, to what address, and in which direction.

What it does not mean, and each of these is a real confusion:

  • It is not importance or complexity. A DMA engine initiates against a memory controller that is far larger and more complex.
  • It is not a property of the block. A DMA engine is a target on its register port and a master on its memory port, at the same time. The role belongs to the interface.
  • It is not authority. A completed write means the register was written, not that the peripheral has finished acting on it.

The part a strong answer reaches: the role's real content is ownership. The initiator owns address, direction, write data and request qualification; the target owns read data, completion and error. That assignment is what makes each signal single-driver, and it is what tells you which side to investigate when a transfer goes wrong.

9. Understanding Check

10. What's Next

This chapter fixed the roles and, more usefully, the ownership that follows from them. The initiator supplies address, direction, write data and qualification; the target supplies read data, completion and error; every signal has one driver; and that table is a debugging tool before it is a definition.

It also assumed something without examining it. gpio_target has a sel input, and the chapter simply said the decoder chose you. Nothing so far says how.

Given a 32-bit address and a system of several targets, what decides which one is selected — and what makes that decision correct rather than merely working?

Chapter 2.2 — Address Space answers it at the bus level: how a region is expressed in hardware, why range comparison and mask comparison are different engineering choices, how a global address becomes a local offset, and what makes a decode exhaustive and mutually exclusive rather than accidentally either. The full path is on the Wishbone curriculum index.

Continue learning

Related tutorials

Standards & specifications

Governing standard
Wishbone SoC Interconnection Architecture (OpenCores)(opens OpenCores in a new tab)

Defines the Wishbone signal set, the bus cycles built from it and the interface rules a portable IP core must follow. It deliberately leaves interconnect topology, address map and arbitration policy to the integrator, so those are system decisions rather than requirements of the specification.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the Wishbone curriculum.