Skip to content
VLSI Mentor

Wishbone · Module 5

Transaction Initiation

The boundary where a local client's request becomes a bus transaction, and why a master that does not latch its metadata produces transfers whose address changes mid-flight.

Every master so far has begun a transfer at "the edge following req_i", and that phrase has been doing a great deal of unexamined work.

Exactly what must be true for a Wishbone transfer to begin, and what must the master capture before it does?

1. The Acceptance Boundary

There is exactly one edge at which a client request becomes a bus transaction, and naming it precisely matters.

Before that edge: the client is asking. It may change its mind, change its address, or withdraw entirely. Nothing has happened on the bus.

At that edge: the master captures the request metadata into registers and sets its outstanding-transfer state. The transaction now exists.

After that edge: the client's inputs are irrelevant until completion. The bus is driven entirely from the captured copy.

Three obligations follow, and the RTL in Section 3 implements each one explicitly.

Capture everything the bus needs. Address, direction, write data, byte lanes — every signal RULE 3.60 qualifies with STB_O. Missing one leaves that signal hostage to the client.

Refuse a second request while busy. A single-outstanding master that accepts a new request mid-transfer either corrupts the outstanding one or silently drops the new one. Both are bugs; the master must pick one behaviour and expose it, which is what busy_o is for.

Do not present until the capture is complete. CYC_O and STB_O must not assert in a cycle where the metadata registers still hold the previous transfer's values.

2. Why the Client Will Change Its Inputs

It is tempting to treat "the client holds its request stable" as a reasonable assumption. It is not, and the reasons are ordinary rather than exotic.

A pipelined client moves on. A CPU that issues a load and continues executing has already computed the next address by the following cycle. Its request port reflects whatever it is doing now.

A request queue advances. A DMA engine that hands over a descriptor and pops the queue presents the next descriptor immediately.

A combinational client recomputes. A client whose request signals are derived from a state machine changes them when the state machine advances — which it does as soon as it sees busy_o rise.

Against a zero-wait-state slave none of this matters, because the transfer completes in the cycle it is presented and nothing has time to change. That is precisely what makes the bug dangerous: it is invisible in the configuration most designs are first tested in, and appears when latency is introduced by something that looks unrelated — a bus bridge, a clock-domain crossing, a slower peripheral.

3. RTL — The Robust Single-Outstanding Master

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_single_master — the reference initiation design for Module 5.
//
// PURPOSE. Convert a local client request, which may be transient and may
// change at any time, into a Wishbone transfer that satisfies RULE 3.60
// for as many cycles as the slave needs.
//
// This is Chapter 5.1's wb_hs_master with the initiation boundary made
// explicit and defended: an accept handshake toward the client, a full
// metadata capture, and an explicit refusal of overlapping requests.
//
// CLIENT CONTRACT (this is LOCAL POLICY, not Wishbone — and per RULE 2.15
// it is the kind of thing a datasheet must state):
//   * req_i is a request; it is ACCEPTED only in a cycle where acc_o is
//     also asserted. The client must hold its metadata valid in that cycle
//     and is free to change it from the next cycle onward.
//   * Exactly one transaction is outstanding at a time.
//   * done_o pulses for one cycle per accepted transaction.
//
// Reset: SYNCHRONOUS, ACTIVE HIGH (RULES 2.30, 3.00, 3.20).
// ─────────────────────────────────────────────────────────────────────────
module wb_single_master #(
  parameter int unsigned AW = 30,
  parameter int unsigned DW = 32
) (
  input  logic            clk_i,
  input  logic            rst_i,

  // ── local client side (NOT Wishbone) ────────────────────────────────
  input  logic            req_i,
  input  logic            req_we_i,
  input  logic [AW-1:0]   req_adr_i,
  input  logic [DW-1:0]   req_dat_i,
  input  logic [DW/8-1:0] req_sel_i,
  output logic            acc_o,        // request accepted THIS cycle
  output logic            busy_o,
  output logic            done_o,       // one pulse per accepted request
  output logic            err_o,        // terminated with ERR or RTY
  output logic [DW-1:0]   rdat_o,

  // ── Wishbone MASTER interface ───────────────────────────────────────
  output logic            cyc_o,
  output logic            stb_o,
  output logic            we_o,
  output logic [AW-1:0]   adr_o,
  output logic [DW-1:0]   dat_o,
  output logic [DW/8-1:0] sel_o,
  input  logic [DW-1:0]   dat_i,
  input  logic            ack_i,
  input  logic            err_i,
  input  logic            rty_i
);
  // ── STATE ─────────────────────────────────────────────────────────────
  // active_q says a transaction is outstanding. The four metadata
  // registers are the master's PRIVATE COPY of the request — the thing
  // that makes RULE 3.60 satisfiable across an unbounded wait.
  logic            active_q;
  logic            we_q;
  logic [AW-1:0]   adr_q;
  logic [DW-1:0]   dat_q;
  logic [DW/8-1:0] sel_q;

  // ── THE ACCEPTANCE BOUNDARY ───────────────────────────────────────────
  // acc_o is asserted only when a request is offered AND the master is
  // free. Exposing it lets the client know which cycle its metadata was
  // read in — the alternative, an implicit "we took it if busy_o rose",
  // forces the client to infer the boundary and is where ambiguity starts.
  assign acc_o  = req_i & ~active_q;
  assign busy_o = active_q;

  // ── BUS OUTPUTS: ENTIRELY FROM THE PRIVATE COPY ───────────────────────
  // Not one of these reads a req_* input. That is the whole defence: once
  // active_q is set, the client is disconnected from the bus.
  //
  // PERMISSION 3.40 lets both qualifiers share active_q, since this master
  // never negates STB_O mid-transfer.
  assign cyc_o = active_q;
  assign stb_o = active_q;
  assign we_o  = we_q;
  assign adr_o = adr_q;
  assign dat_o = dat_q;
  assign sel_o = sel_q;

  logic terminated;
  assign terminated = ack_i | err_i | rty_i;

  // ── SEQUENTIAL ────────────────────────────────────────────────────────
  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      active_q <= 1'b0;                  // RULE 3.20
      we_q     <= 1'b0;
      adr_q    <= '0;
      dat_q    <= '0;
      sel_q    <= '0;
      done_o   <= 1'b0;
      err_o    <= 1'b0;
      rdat_o   <= '0;
    end else begin
      done_o <= 1'b0;                    // pulses
      err_o  <= 1'b0;

      if (acc_o) begin
        // ── CAPTURE ─────────────────────────────────────────────────────
        // Every signal RULE 3.60 qualifies with STB_O is copied here, in
        // one edge. Omitting any one of them would leave that signal
        // driven by a client input for the life of the transfer.
        active_q <= 1'b1;
        we_q     <= req_we_i;
        adr_q    <= req_adr_i;
        dat_q    <= req_dat_i;
        sel_q    <= req_sel_i;
      end else if (active_q && terminated) begin
        // ── COMPLETION ──────────────────────────────────────────────────
        // Chapter 5.6 takes this edge apart. Note the guard: acc_o cannot
        // be true here because acc_o requires ~active_q, so a new request
        // can never be captured in the same edge that completes the old
        // one. That ordering is what keeps exactly one transaction live.
        active_q <= 1'b0;
        done_o   <= 1'b1;
        err_o    <= err_i | rty_i;
        if (ack_i && !we_q) rdat_o <= dat_i;
      end
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_unlatched_master — THE INITIATION BUG, isolated for simulation.
//
// NOT A REFERENCE DESIGN. Identical in structure to wb_single_master
// except that adr_o, we_o, dat_o and sel_o are driven DIRECTLY from the
// client inputs instead of from captured copies.
//
// Against a zero-wait-state slave this behaves identically to the correct
// master and passes every test. Against a slave that waits, the address on
// the bus follows the client and RULE 3.60 is violated from the second
// cycle of every transfer onward.
// ─────────────────────────────────────────────────────────────────────────
module wb_unlatched_master #(
  parameter int unsigned AW = 30,
  parameter int unsigned DW = 32
) (
  input  logic            clk_i,
  input  logic            rst_i,
  input  logic            req_i,
  input  logic            req_we_i,
  input  logic [AW-1:0]   req_adr_i,
  input  logic [DW-1:0]   req_dat_i,
  input  logic [DW/8-1:0] req_sel_i,
  output logic            acc_o,
  output logic            busy_o,
  output logic            done_o,
  output logic [DW-1:0]   rdat_o,

  output logic            cyc_o,
  output logic            stb_o,
  output logic            we_o,
  output logic [AW-1:0]   adr_o,
  output logic [DW-1:0]   dat_o,
  output logic [DW/8-1:0] sel_o,
  input  logic [DW-1:0]   dat_i,
  input  logic            ack_i,
  input  logic            err_i,
  input  logic            rty_i
);
  logic active_q;

  assign acc_o  = req_i & ~active_q;
  assign busy_o = active_q;
  assign cyc_o  = active_q;
  assign stb_o  = active_q;

  // ── THE BUG ───────────────────────────────────────────────────────────
  // Straight through from the client. There is no private copy, so the
  // bus reflects whatever the client is presenting RIGHT NOW — which,
  // after the first cycle, is usually the next request.
  assign we_o  = req_we_i;
  assign adr_o = req_adr_i;
  assign dat_o = req_dat_i;
  assign sel_o = req_sel_i;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      active_q <= 1'b0;
      done_o   <= 1'b0;
      rdat_o   <= '0;
    end else begin
      done_o <= 1'b0;
      if (acc_o) begin
        active_q <= 1'b1;
      end else if (active_q && (ack_i || err_i || rty_i)) begin
        active_q <= 1'b0;
        done_o   <= 1'b1;
        if (ack_i) rdat_o <= dat_i;
      end
    end
  end
endmodule

Reading the pair

Purpose. The first master closes the acceptance boundary; the second leaves it open so the failure can be measured.

Interface. The client side gains acc_o, which names the acceptance cycle explicitly. This is local policy, not Wishbone — but making the boundary a signal rather than an inference is what lets a client know exactly which cycle its metadata was read in.

State. wb_single_master holds the outstanding flag plus four metadata registers. The unlatched master holds only the flag — and the absent registers are the bug.

Combinational behaviour. acc_o is the accept term. All bus outputs come from state in the good master and from client inputs in the bad one.

Sequential behaviour. Capture on acc_o; release on termination.

Request start. At the edge where acc_o is asserted. CYC_O and STB_O rise together at the following edge, with metadata already valid — the capture and the assertion happen in the same edge, so there is no cycle in which the strobe is asserted over stale metadata.

Waiting. Every bus output is register-held. The client is disconnected.

Termination. Level-sampled while active_q is set, per Chapter 5.4.

Read data. Captured on ack_i && !we_q only.

Write data. dat_q is driven for the whole transfer including reads — legal under RULE 3.60, which qualifies DAT_O with STB_O and not with WE_O.

Reset. Synchronous, active high; active_q <= 0 negates both qualifiers per RULE 3.20.

Failure modes. Section 6.

Simplifications. One outstanding transaction. No retry policy — err_o merges ERR_I and RTY_I, which Chapter 4.12 showed is a policy choice a real master should separate. No arbitration.

4. Waveform — The Address That Moved

Initiation without capture: ADR_O follows the client

8 cycles
Eight clock cycles showing an unlatched master failing. In cycle two the client presents address A and the master accepts it, asserting both qualifiers with the address output showing A. The slave inserts wait states and does not acknowledge. In cycle three the client, having seen the busy signal rise, advances its request input to address B; because the master has no captured copy, the address output on the bus immediately changes to B while the strobe is still asserted and the transfer is still outstanding. The address remains B through cycles three, four and five. In cycle five the slave acknowledges, having seen the address change underneath it. The correct master's address output is shown below, holding A for the entire transfer.accepted at Aaccepted at Aclient moves on; bus followsclient moves on; busfollowsslave terminates — which address?slave terminates — whichaddress?CLK_Ireq_adr (cli)----0x0A0x0B0x0B0x0B0x0B0x0B0x0BCYC_OSTB_OADR_O (bad)----0x0A0x0B0x0B0x0B------------ADR_O (good)----0x0A0x0A0x0A0x0A------------ACK_It0t1t2t3t4t5t6t7
Figure 1 — the unlatched master. The client advances to B while the transfer at A is still outstanding.

Cycle 3 is the violation. STB_O is asserted, the transfer is outstanding, and ADR_O changes. RULE 3.60 qualifies the address with the strobe precisely so a slave can rely on it for the strobe's whole duration.

The ADR_O (good) trace holds 0x0A throughout, because it is driven from adr_q and adr_q was written once.

Cycle 5 is unanswerable. The slave terminates, but which address did it serve? A combinational decoder followed the change and served B. A decoder that registered the address at the first strobe cycle served A. Both slaves are conformant — the specification does not say which, because it forbids the master from putting them in this position.

And the master reports success either way.

5. Verification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_init_checker — initiation properties.
//
// P1 is SPECIFICATION (RULE 3.60 read across a multi-cycle presentation)
// and is the one that catches the unlatched master. P2, P3 and P4 are
// LOCAL POLICY encoding this master's single-outstanding client contract —
// exactly the material RULE 2.15 requires a datasheet to state.
// ─────────────────────────────────────────────────────────────────────────
module wb_init_checker #(
  parameter int unsigned AW = 30,
  parameter int unsigned DW = 32
) (
  input logic            clk_i,
  input logic            rst_i,
  input logic            cyc_o,
  input logic            stb_o,
  input logic            we_o,
  input logic [AW-1:0]   adr_o,
  input logic [DW-1:0]   dat_o,
  input logic [DW/8-1:0] sel_o,
  input logic            ack_i,
  input logic            err_i,
  input logic            rty_i,
  input logic            acc_o,        // white-box: acceptance boundary
  input logic            busy_o
);
  default disable iff (rst_i);

  logic terminated;
  assign terminated = ack_i | err_i | rty_i;

  // P1 — SPECIFICATION (RULE 3.60). Everything the strobe qualifies holds
  //      still while the transfer is outstanding. This is THE property of
  //      this chapter: it fires on the first cycle the unlatched master's
  //      address moves, rather than when a downstream symptom appears.
  //
  //      The end boundary is the termination. Writing the antecedent
  //      without `!terminated` would forbid a master from presenting a
  //      DIFFERENT transfer the cycle after one completes, which is legal
  //      and normal — the over-strong-property trap from Chapter 4.6.
  property p_request_stable_until_terminated;
    @(posedge clk_i) (cyc_o && stb_o && !terminated)
      |=> ($stable(adr_o) && $stable(we_o) &&
           $stable(dat_o) && $stable(sel_o) && stb_o);
  endproperty
  a_request_stable_until_terminated :
    assert property (p_request_stable_until_terminated)
    else $error("RULE 3.60: a qualified signal moved while outstanding");

  // P2 — LOCAL POLICY. A request is never accepted while one is
  //      outstanding. Catches an overlapping accept that would silently
  //      overwrite the live transaction's metadata.
  property p_no_accept_while_busy;
    @(posedge clk_i) acc_o |-> !busy_o;
  endproperty
  a_no_accept_while_busy : assert property (p_no_accept_while_busy)
    else $error("LOCAL: request accepted while a transaction was outstanding");

  // P3 — LOCAL POLICY. The bus goes active only because a request was
  //      accepted. Catches a master that asserts its qualifiers from some
  //      other condition — a reset artefact, or a spurious state entry.
  property p_active_only_after_accept;
    @(posedge clk_i) ($rose(stb_o)) |-> $past(acc_o);
  endproperty
  a_active_only_after_accept : assert property (p_active_only_after_accept)
    else $error("LOCAL: STB_O asserted without a preceding acceptance");

  // P4 — LOCAL POLICY. Metadata is valid in the cycle the strobe first
  //      asserts, i.e. capture and presentation happen at the same edge.
  //      Catches a master that asserts the strobe a cycle before its
  //      registers are loaded, presenting the PREVIOUS transfer's address.
  property p_metadata_valid_at_first_strobe;
    @(posedge clk_i) $rose(stb_o) |-> !$isunknown({adr_o, we_o, sel_o});
  endproperty
  a_metadata_valid_at_first_strobe :
    assert property (p_metadata_valid_at_first_strobe)
    else $error("LOCAL: metadata not valid in the first strobe cycle");
endmodule

P1 is the property that matters, and it is worth noting what it does not need: any knowledge of the client. It watches the bus, and a master that fails it is violating RULE 3.60 regardless of why. This is a conformance property, and a passive monitor catches the unlatched master with no white-box access at all — a pleasant contrast with Chapter 5.3's repeated-write bug, which no bus-level property could see.

P2 and P3 need acc_o, which exists because this master exposes its acceptance boundary. A master that left the boundary implicit could not be checked this way — which is an argument for exposing it.

Tooling limitation. Icarus Verilog has no SVA support. These are reviewed by inspection; both masters are elaborated and the failure is simulated in Section 7.

6. Failure Modes and Discriminating Evidence

Symptom: reads return data belonging to a different address.

Candidate causes. The master drives ADR_O from an unlatched client input, and the client advanced mid-transfer.

Discriminating evidence. Watch ADR_O across the whole strobe assertion. Any change before termination is a RULE 3.60 violation and is conclusive. The corrupted value will match the client's next request, which identifies the mechanism precisely.

Likely RTL location. The adr_o assignment — a direct connection where a register belongs.

Property. P1.

Symptom: the design works with one peripheral and corrupts with a slower one.

Candidate causes. The same bug. At zero wait states the transfer completes before the client can change anything, so the fault is latency-dependent.

Discriminating evidence. Re-run against a slave with WAITS > 0. If corruption only appears with wait states, the master's initiation boundary is open. The change that exposed it — a bridge, a CDC, a slower part — will look unrelated to the master.

Symptom: a write lands at the right address with the wrong data, or vice versa.

Candidate causes. A partial capture — the master latched the address but not the write data, or not SEL_O.

Discriminating evidence. Check which qualified signals are stable across the transfer and which move. The moving ones name exactly which registers are missing. This is more common than a fully unlatched master, because the address is the signal people remember to latch.

Likely RTL location. The capture block — an incomplete list.

Property. P1 covers all four signals for this reason.

Symptom: one client request produces two bus transfers, or two produce one.

Candidate causes. An ambiguous acceptance boundary — the client and master disagree about which cycle the request was taken.

Discriminating evidence. Count acc_o pulses against client requests and against STB_O rising edges. A mismatch at the first comparison is a client-contract bug; at the second it is a master bug.

Likely RTL location. The acc_o term, or a client that does not check it.

Property. P2 and P3.

Symptom: the first transfer after reset uses a stale address.

Candidate causes. The strobe asserts before the metadata registers are loaded.

Discriminating evidence. Compare the first STB_O rising edge against the first acc_o. Same edge is correct; strobe first is the bug.

Property. P4.

7. Simulation — Request Stability, Measured

Both masters were given one client request to address 0x0A against a slave inserting three wait states, under two client behaviours — because how aggressively the client moves on changes how the failure looks.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  Scenario 1 - client holds one cycle, then moves:
                                      good       bad
    cycles transfer presented          4          4
    ADR_O changes while presented      0          1
    cycles ADR_O != requested (0xa)    0          3
    ADR_O at termination edge        0xa        0xb

  Scenario 2 - client moves immediately on acceptance:
                                      good       bad
    cycles transfer presented          4          4
    ADR_O changes while presented      0          0
    cycles ADR_O != requested (0xa)    0          4
    ADR_O at termination edge        0xa        0xb

Scenario 1 is the failure Figure 1 draws. The bad master presents 0x0A for one cycle, the client advances, and the address changes underneath the outstanding transfer — one change, three wrong cycles out of four.

Scenario 2 is worse and less obvious. The client moves on in the very cycle acceptance occurs, so by the time the master first asserts STB_O its ADR_O is already 0x0B. The requested address never appears on the bus at all — and note the ADR_O changes while presented row reads 0, because nothing changed: it was wrong from the first cycle to the last.

That row is a warning about how to write the monitor. A checker that looks for the address changing catches scenario 1 and misses scenario 2 entirely. The property has to compare against what was requested, not against the previous cycle — which is why P1 in Section 5 asserts stability from the first presented cycle rather than detecting transitions.

The correct master held 0x0A through every cycle of both scenarios, and both masters reported success in both.

8. Common Mistakes

"The client holds its request until done_o, so latching is redundant."

Wrong mental model: the client contract guarantees stability.

Concrete bug: it does until a client changes — a pipelined CPU, an advancing queue, a state machine that moves on when busy_o rises.

Observable evidence: corruption that appears when the client is modified, in a master nobody touched.

Correct model: RULE 3.60 places the obligation on the master. A master that depends on a client promise has moved a bus requirement into a document, and documents do not synthesise. Latching costs four registers and removes the dependency entirely.

"Latching the address is enough."

Wrong mental model: the address is the request.

Concrete bug: WE_O, DAT_O or SEL_O left unlatched. A write whose direction flips mid-transfer, or whose byte lanes change, is as broken as one whose address moves — and Chapter 4.7 showed what a wrong lane mask destroys.

Observable evidence: right address, wrong data; or a write that partially lands.

Correct model: RULE 3.60 lists ADR_O, DAT_O(), SEL_O(), WE_O and the tag outputs together. The list is the capture list.

"busy_o tells the client everything it needs."

Wrong mental model: the client can infer acceptance from busy_o rising.

Concrete bug: an off-by-one at the boundary — the client releases its metadata a cycle early, or holds it a cycle too long and issues a duplicate.

Observable evidence: occasional duplicated or dropped transfers, correlated with client timing rather than bus timing.

Correct model: make acceptance explicit. acc_o names the cycle; the client does not have to infer it. That is local policy rather than Wishbone, and per RULE 2.15 it belongs in the datasheet.

9. Interview Reasoning

An open initiation boundary — the master drives one or more Wishbone outputs directly from client inputs instead of from captured copies.

Why latency exposes it. Against a zero-wait-state slave the transfer completes in the cycle it is presented. The client has no opportunity to change anything, so an unlatched master is indistinguishable from a correct one. Introduce wait states and the client's next request appears on the bus while the current transfer is still outstanding.

What changed if the master did not. Something added latency: a bus bridge, a clock-domain crossing, a slower peripheral, or an arbiter under load. None of those looks like a master change, which is why the investigation usually starts in the wrong place.

The confirming observation, in one capture. Watch ADR_O, WE_O, DAT_O and SEL_O across a full strobe assertion. Any of them changing before the termination is a RULE 3.60 violation and is conclusive — and the value they change to will match the client's next request, which names the mechanism exactly.

Why the master reports success. It presented a transfer and received a termination. Nothing in its own view indicates the address it was presenting changed underneath it — so this corruption is silent at both ends.

Which signals to check, not just the address. A partial capture is more common than none at all, because the address is the one people remember. RULE 3.60's list — ADR_O, DAT_O(), SEL_O(), WE_O, tags — is exactly the capture list, and the signals still moving name the missing registers.

The fix and the test that should have caught it. Latch all of them at the acceptance edge. Then add a wait-state configuration to the master's unit testbench: a master verified only against a zero-wait slave has never exercised the obligation this rule exists for.

10. Understanding Check

11. What's Next

The transaction now begins correctly: a named acceptance edge, a complete capture, and bus outputs that cannot follow the client. The transfer is presented and held.

It ends at a single edge, and that edge has been treated as one event throughout — observe the termination, release the bus. It carries more obligations than that, and getting their order wrong loses the read data or reports the completion twice.

What exactly happens at the edge where the slave terminates the outstanding transfer?

Chapter 5.6 — Transaction Completion answers it. The full path is on the Wishbone curriculum index.

Continue learning

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.