Skip to content
VLSI Mentor

Wishbone · Module 9

Slave Delays

A delayed slave must hold a request while it works. Two capture styles measured: one costs a clock of latency it can never recover, the other couples you to the master's conformance.

Chapter 9.1 built a slave that counts and then answers. It has the value the whole time — the counter is pure theatre, standing in for work that a real target actually does.

A real target is busy during those clocks. It is registering an address, sequencing a state machine, waiting for a far side. And the moment a slave does real work across several clocks, it faces a question the zero-wait slaves of Modules 6 and 7 never had to answer.

Where does the request live while the slave is working on it?

1. Two Places to Keep the Request

While the slave works, the request metadata — address, direction, byte lanes, write payload — has to come from somewhere. There are exactly two answers.

STYLE A — work from the presented bus signals. Read adr_i live, every clock, for as long as the transfer is presented. This is what Chapter 9.1's wb_fixed_latency_slave does: its read multiplexer is combinational in adr_i, and its commit reads dat_i at the terminating edge.

STYLE B — capture the request into registers at acceptance. Latch address, data and direction once, then work from the copy and ignore the bus until it is time to respond. This chapter's wb_var_latency_slave does that.

Both are legal, and the reason is RULE 3.60.

RULE 3.60 — the MASTER MUST qualify ADR_O, DAT_O(), SEL_O(), WE_O and the tags with STB_O.

STB_O is asserted for the whole outstanding transfer, so every one of those signals is obliged to stand still for the whole outstanding transfer. Style A is therefore sound — the live signals cannot move under a conformant master.

What each one actually costs

Style A costs nothing in latency and buys nothing in isolation. It can answer in the presenting clock, because there is no register between the request and the response. But its correctness is contingent on the master's. If the master violates RULE 3.60 and moves the address mid-transfer, a Style A slave will happily serve whichever address is present when it terminates — the failure Chapter 8.3 measured for a block master, and Chapter 9.3 measures again here.

Style B costs exactly one clock, and that clock is a floor. Acceptance happens at an edge; the earliest the captured request can drive a response is the next edge. A Style B slave cannot do zero-wait, ever, by construction.

That is not a tuning parameter and it is worth being explicit about, because it is easy to write a Style B slave with a WAIT_CYCLES = 0 setting and be surprised that it still inserts one. Section 5 measures both styles on the same read.

What Style B buys is independence. Once captured, the operation runs against a snapshot. A master that misbehaves mid-transfer cannot corrupt an operation that is already under way — the slave is not looking.

Neither is universally right, and the honest summary is a trade between a clock of latency and a coupling to somebody else's conformance. A fast register file has no reason to pay the clock. A multi-cycle engine has every reason to want the snapshot.

2. The Running Peripheral Gains a Slow Target

Module 9 adds one register, at the last free word below the block window:

ByteWordRegisterAccessResponseIntroduced
0x000STATUSROfastest6.1
0x041COUNTRO, free-runningone clock of work6.1
0x082CONTROLRWfastest6.1
0x104IDROfastest6.1
0x249COMMANDWO, pulsesconfigurable7.4
0x2C11SENSORROSENSOR_WAIT clocks of work9.2

SENSOR is the point of the chapter. It is the register that genuinely cannot answer immediately — a stand-in for an ADC conversion, a filter pipeline, or a reading that must be fetched from somewhere else.

Word 7 remains unmapped, as Chapter 6.1 and Chapter 6.6 require for their ERR cases, and words 12–15 remain Chapter 8.3's window.

3. RTL — A Slave Whose Latency Depends on the Target

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// wb_var_latency_slave — latency that depends on WHICH register is addressed.
//
// This is the realistic case. A peripheral is not uniformly slow; it is a
// mixture of targets behind one Wishbone port, and the response time is a
// property of the target the address selected.
//
// STYLE B — CAPTURED REQUEST. Unlike wb_fixed_latency_slave (Chapter 9.1),
// which reads adr_i live on every presented clock, this slave LATCHES the
// request metadata at the first presented edge and works from the copy.
//
// Both styles are legitimate. RULE 3.60 obliges the master to hold ADR_O,
// DAT_O(), SEL_O and WE_O stable for the whole outstanding transfer, so
// reading them live is sound. Capturing them is a choice about coupling,
// not about legality, and Section 2 argues both sides.
//
// THE COST OF CAPTURING, WHICH IS MEASURED AND NOT ASSUMED:
//   Acceptance consumes a clock. The request is latched at the presenting
//   edge and the state machine can only be in S_RESPOND at the NEXT edge,
//   so this slave's minimum is ONE wait state. It cannot do zero-wait.
//
//   target_wait() below is the INTERNAL WORK, in clocks. The wait states
//   actually observed on the bus are  target_wait + 1:
//
//     word 0   STATUS   work 0  ->  1 wait state   2 clocks presented
//     word 1   COUNT    work 1  ->  2 wait states  3 clocks presented
//     word 4   ID       work 0  ->  1 wait state   2 clocks presented
//     word 11  SENSOR   work N  ->  N+1 waits      N+2 clocks presented
//
//   A STYLE A slave has no such floor: Chapter 9.1's answers combinationally
//   in the presenting clock when WAIT_CYCLES = 0. That one clock is the
//   price of decoupling, and Section 6 measures it.
// ─────────────────────────────────────────────────────────────────────────
module wb_var_latency_slave #(
  parameter int unsigned OFF_AW      = 4,
  parameter int unsigned DW          = 32,
  parameter int unsigned SENSOR_WAIT = 4
) (
  input  logic                clk_i,
  input  logic                rst_i,
  input  logic                cyc_i,
  input  logic                stb_i,
  input  logic                we_i,
  input  logic [OFF_AW-1:0]   adr_i,
  input  logic [DW-1:0]       dat_i,
  input  logic [DW/8-1:0]     sel_i,
  output logic [DW-1:0]       dat_o,
  output logic                ack_o,
  output logic                err_o,

  // observation only
  output logic [2:0]          state_o,
  output logic [7:0]          waited_o,
  output logic [DW-1:0]       sensor_o
);
  localparam logic [OFF_AW-1:0] O_STATUS = 4'd0;   // byte 0x00  RO, 0 waits
  localparam logic [OFF_AW-1:0] O_COUNT  = 4'd1;   // byte 0x04  RO, 1 wait
  localparam logic [OFF_AW-1:0] O_CTRL   = 4'd2;   // byte 0x08  RW, 0 waits
  localparam logic [OFF_AW-1:0] O_ID     = 4'd4;   // byte 0x10  RO, 0 waits
  localparam logic [OFF_AW-1:0] O_SENSOR = 4'd11;  // byte 0x2C  RO, SENSOR_WAIT
  localparam logic [DW-1:0] ID_VALUE = 32'h5742_0901;

  typedef enum logic [2:0] { S_IDLE, S_WORK, S_RESPOND } state_e;
  state_e state_q;

  // ── THE CAPTURED REQUEST. Written once, at acceptance. ──────────────────
  logic [OFF_AW-1:0] adr_q;
  logic [DW-1:0]     dat_q;
  logic              we_q;

  logic [7:0]    waited_q;
  logic [DW-1:0] ctrl_q, count_q, sensor_q;
  logic          xfer, mapped_live;

  assign xfer = cyc_i && stb_i;

  // Decode of the LIVE address, used only to decide whether to accept.
  assign mapped_live = (adr_i == O_STATUS) || (adr_i == O_COUNT)
                    || (adr_i == O_CTRL)   || (adr_i == O_ID)
                    || (adr_i == O_SENSOR);

  // How long the CAPTURED target takes. A function of the latched address,
  // so the answer cannot change under the slave mid-operation even if the
  // bus did something it should not.
  function automatic logic [7:0] target_wait(input logic [OFF_AW-1:0] a);
    case (a)
      O_SENSOR: target_wait = 8'(SENSOR_WAIT);
      O_COUNT:  target_wait = 8'd1;
      default:  target_wait = 8'd0;
    endcase
  endfunction

  assign state_o  = state_q;
  assign waited_o = waited_q;
  assign sensor_o = sensor_q;

  // Termination comes from the state machine, and is still gated on the
  // transfer being presented — RULE 3.35 does not relax because a slave
  // is busy.
  assign ack_o = xfer && (state_q == S_RESPOND);
  assign err_o = xfer && (state_q == S_IDLE) && !mapped_live;

  // Read data is driven from the CAPTURED address, so it is stable for the
  // whole response and cannot be disturbed by the bus.
  always_comb begin
    dat_o = '0;
    if ((state_q == S_RESPOND) && !we_q) begin
      case (adr_q)
        O_STATUS: dat_o = 32'h0000_0001;
        O_COUNT:  dat_o = count_q;
        O_CTRL:   dat_o = ctrl_q;
        O_ID:     dat_o = ID_VALUE;
        O_SENSOR: dat_o = sensor_q;
        default:  dat_o = '0;
      endcase
    end
  end

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      state_q <= S_IDLE; adr_q <= '0; dat_q <= '0; we_q <= 1'b0;
      waited_q <= '0; ctrl_q <= '0; count_q <= '0; sensor_q <= 32'h0000_5E01;
    end else begin
      count_q <= count_q + 1;

      case (state_q)
        S_IDLE: begin
          waited_q <= '0;
          if (xfer && mapped_live) begin
            // ── ACCEPTANCE: capture the request exactly once. ─────────────
            adr_q <= adr_i;
            dat_q <= dat_i;
            we_q  <= we_i;
            // A zero-latency target skips S_WORK entirely, so a fast
            // register is still answered in its presenting clock.
            if (target_wait(adr_i) == 8'd0) state_q <= S_RESPOND;
            else                            state_q <= S_WORK;
          end
        end

        S_WORK: begin
          // The internal operation. Nothing here reads the live bus.
          waited_q <= waited_q + 8'd1;
          if (waited_q + 8'd1 >= target_wait(adr_q)) state_q <= S_RESPOND;
        end

        S_RESPOND: begin
          // COMMIT exactly once, at the terminating edge — the edge where
          // the master is also sampling. One accepted request, one commit.
          if (xfer) begin
            if (we_q && (adr_q == O_CTRL)) ctrl_q <= dat_q;
            state_q  <= S_IDLE;
            waited_q <= '0;
          end
          // If the master has walked away (xfer low) the slave returns to
          // idle rather than holding a response nobody is collecting.
          if (!xfer) begin state_q <= S_IDLE; waited_q <= '0; end
        end

        default: state_q <= S_IDLE;
      endcase
    end
  end
endmodule

Reading it

Purpose. To show a slave whose response time is a property of the target, not of the port — which is what every real peripheral looks like.

The state machine is the request's home. S_IDLE observes and accepts, S_WORK runs the operation, S_RESPOND answers. Nothing in S_WORK reads the live bus, which is the whole of Style B.

target_wait() takes the captured address, not the live one. That is deliberate: once an operation is under way, how long it takes must not be able to change. Passing adr_i there would let a misbehaving master alter the remaining latency of an operation already running.

ack_o is still gated on xfer. A slave that has finished its work does not get to acknowledge into an empty bus — RULE 3.35 requires the termination to be generated in response to the AND of CYC_I and STB_I, and being busy does not relax it. assign ack_o = xfer && (state_q == S_RESPOND); is that rule, written out.

The S_RESPOND state has two exits, and the second one matters. If the master is still presenting, the transfer terminates and the slave commits. If the master has walked away — xfer low — the slave returns to idle rather than holding a response nobody is collecting. A slave that latched ACK_O high and waited would violate RULE 3.50, which requires the termination signals to be negated in response to the negation of STB_I. Chapter 9.5 measures a slave that gets this wrong.

Read data is driven from adr_q, and only in S_RESPOND. Two consequences. The value is stable for the whole response, and it does not exist before the response — which is the honest version of the early-DAT_O caveat Chapter 9.1 §4 raised. RULE 3.65 requires the slave to qualify DAT_O() with its termination, and this slave does so tightly rather than incidentally.

Timing. Acceptance is registered, so the response is registered, so this slave's minimum is one wait state. That floor is measured in Section 5, not assumed.

Reset. Active high, synchronous. sensor_q resets to a fixed reading; a real sensor would have a data path behind it, which is not what this chapter is about.

Simplifications. SEL_O is ignored — Module 13 owns byte lanes. The internal operation is a counter rather than real work. There is no RTY_O; Module 11 owns retry.

4. RTL — One Write, and the Side Effect That Should Fire Once

Chapter 7.4 established that a presented write is not a committed write. Module 9 makes the gap between them as wide as you like, which turns a subtle bug into an arithmetic one.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// wb_cmd_slave — a COMMAND register with a side effect, answered slowly.
//
// Word 9 (byte 0x24) is write-only and every accepted write PULSES an
// internal command strobe. The register introduced in Chapter 7.4; what is
// new here is that this slave takes WAIT_CYCLES clocks to answer, so the
// write is presented for several clocks before it terminates.
//
// THE RULE THIS SLAVE EXISTS TO ENFORCE:
//   a write presented for N clocks is ONE write.
//   The side effect fires once, at the terminating edge.
//
// Chapter 7.4 established this at the level of payload coherence. Module 9
// makes the window arbitrarily wide, which is what turns a subtle bug into
// a measurable one.
// ─────────────────────────────────────────────────────────────────────────
module wb_cmd_slave #(
  parameter int unsigned OFF_AW      = 4,
  parameter int unsigned DW          = 32,
  parameter int unsigned WAIT_CYCLES = 3
) (
  input  logic                clk_i,
  input  logic                rst_i,
  input  logic                cyc_i,
  input  logic                stb_i,
  input  logic                we_i,
  input  logic [OFF_AW-1:0]   adr_i,
  input  logic [DW-1:0]       dat_i,
  input  logic [DW/8-1:0]     sel_i,
  output logic [DW-1:0]       dat_o,
  output logic                ack_o,
  output logic                err_o,

  output logic                cmd_stb_o,     // one-clock pulse per command
  output int unsigned         cmd_count_o,   // side effects, cumulative
  output logic [DW-1:0]       last_cmd_o
);
  localparam logic [OFF_AW-1:0] O_CMD  = 4'd9;    // byte 0x24  WO, pulses
  localparam logic [OFF_AW-1:0] O_CTRL = 4'd2;    // byte 0x08  RW

  logic [7:0] waited_q;
  logic       xfer, mapped, ready;
  logic [DW-1:0] ctrl_q;

  assign xfer   = cyc_i && stb_i;
  assign mapped = (adr_i == O_CMD) || (adr_i == O_CTRL);
  assign ready  = (waited_q >= 8'(WAIT_CYCLES));

  assign ack_o = xfer &&  mapped && ready;
  assign err_o = xfer && !mapped && ready;
  assign dat_o = (xfer && !we_i && (adr_i == O_CTRL)) ? ctrl_q : '0;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      waited_q <= '0; cmd_stb_o <= 1'b0; cmd_count_o <= 0;
      last_cmd_o <= '0; ctrl_q <= '0;
    end else begin
      cmd_stb_o <= 1'b0;

      if (!xfer)       waited_q <= '0;
      else if (!ready) waited_q <= waited_q + 8'd1;
      else             waited_q <= '0;

      // ── THE COMMIT. Gated on `ready`, so it happens at the terminating
      //    edge and at no other. Holding the write longer changes how many
      //    clocks pass before this line runs; it does not change how many
      //    times it runs.
      if (xfer && ready && mapped && we_i) begin
        if (adr_i == O_CMD) begin
          cmd_stb_o   <= 1'b1;
          last_cmd_o  <= dat_i;
          cmd_count_o <= cmd_count_o + 1;
        end else if (adr_i == O_CTRL) begin
          ctrl_q <= dat_i;
        end
      end
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// wb_cmd_slave_repeating — THE BUG, isolated. NOT A REFERENCE DESIGN.
//
// Identical to wb_cmd_slave except for the commit condition:
//
//     if (xfer && ready && mapped && we_i)      becomes
//     if (xfer &&          mapped && we_i)
//
// The `ready` term is gone, so the side effect fires on EVERY clock the
// write is presented rather than on the terminating one.
//
// Against a zero-wait slave the two are indistinguishable, because the
// presenting clock IS the terminating clock. The bug needs latency to
// exist at all — the same structural property that hid the defects in
// Chapters 6.2, 7.4 and 8.3.
//
// The external Wishbone trace is IDENTICAL for both slaves: same ACK edge,
// same duration, same data. Only the internal side-effect count differs.
// ─────────────────────────────────────────────────────────────────────────
module wb_cmd_slave_repeating #(
  parameter int unsigned OFF_AW      = 4,
  parameter int unsigned DW          = 32,
  parameter int unsigned WAIT_CYCLES = 3
) (
  input  logic                clk_i,
  input  logic                rst_i,
  input  logic                cyc_i,
  input  logic                stb_i,
  input  logic                we_i,
  input  logic [OFF_AW-1:0]   adr_i,
  input  logic [DW-1:0]       dat_i,
  input  logic [DW/8-1:0]     sel_i,
  output logic [DW-1:0]       dat_o,
  output logic                ack_o,
  output logic                err_o,
  output logic                cmd_stb_o,
  output int unsigned         cmd_count_o,
  output logic [DW-1:0]       last_cmd_o
);
  localparam logic [OFF_AW-1:0] O_CMD  = 4'd9;
  localparam logic [OFF_AW-1:0] O_CTRL = 4'd2;

  logic [7:0] waited_q;
  logic       xfer, mapped, ready;
  logic [DW-1:0] ctrl_q;

  assign xfer   = cyc_i && stb_i;
  assign mapped = (adr_i == O_CMD) || (adr_i == O_CTRL);
  assign ready  = (waited_q >= 8'(WAIT_CYCLES));

  // The BUS interface is correct and unchanged. That is the point.
  assign ack_o = xfer &&  mapped && ready;
  assign err_o = xfer && !mapped && ready;
  assign dat_o = (xfer && !we_i && (adr_i == O_CTRL)) ? ctrl_q : '0;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      waited_q <= '0; cmd_stb_o <= 1'b0; cmd_count_o <= 0;
      last_cmd_o <= '0; ctrl_q <= '0;
    end else begin
      cmd_stb_o <= 1'b0;

      if (!xfer)       waited_q <= '0;
      else if (!ready) waited_q <= waited_q + 8'd1;
      else             waited_q <= '0;

      // ── THE BUG: no `ready` term. Every presented clock commits. ────────
      if (xfer && mapped && we_i) begin
        if (adr_i == O_CMD) begin
          cmd_stb_o   <= 1'b1;
          last_cmd_o  <= dat_i;
          cmd_count_o <= cmd_count_o + 1;
        end else if (adr_i == O_CTRL) begin
          ctrl_q <= dat_i;
        end
      end
    end
  end
endmodule

Reading the pair

The difference is one term. wb_cmd_slave commits when xfer && ready && mapped && we_i; wb_cmd_slave_repeating drops ready. Everything else — ports, decode, latency counter, acknowledge, error — is identical.

The bus interface of the broken slave is completely correct. It acknowledges at the right edge, after the right number of wait states, with the right decode. A conformance checker watching its pins passes it, which is why this bug reaches silicon.

ready is doing two jobs in the correct version, and that is the design idea worth taking away. It decides when to acknowledge and when to commit, from one expression. The two can then never disagree — a slave whose termination and whose side effect are computed separately can drift apart under exactly the conditions nobody tests.

Why cmd_stb_o is a registered one-clock pulse. Downstream logic counts pulses. If the strobe were combinational in xfer && mapped && we_i, it would be high for the whole presented window even in the correct slave, and the bug would be in the consumer instead of the producer.

5. Waveform — Identical Bus, Divergent Internals

Same write, one commit or four

10 cycles
Ten clock cycles showing a single write transfer to word nine held across four clocks. The cycle and strobe signals rise together at cycle two and stay asserted through cycle five, with write enable high and the address holding word nine. The acknowledge rises only at cycle five. The correct slave's command counter stays at zero until cycle six, where it becomes one and stays there. The repeating slave's command counter climbs to one, two, three and four at cycles three, four, five and six. The Wishbone signals above are identical for both slaves.one write presentedone write presentedtermination: the only commit edgetermination: the onlycommit edgebug has fired 4 timesbug has fired 4 timesCLK_ICYC_OSTB_OWE_OADR_O--------0x90x90x90x9----------------ACK_Icmd (ok)0000001111cmd (bug)0001234444t0t1t2t3t4t5t6t7t8t9
Figure 1 — one delayed write to COMMAND at WAIT_CYCLES = 3, shown against both slaves. The Wishbone signals are the same trace for both; only the two internal counters differ.

The top six rows are one trace, not two. CYC_O, STB_O, WE_O, ADR_O and ACK_I are identical for both slaves — the simulation checks this explicitly and reports zero differences across every clock.

The bottom two rows are where the designs part company. The correct slave's counter moves once, at cycle 6, reflecting the commit registered at the terminating edge 5. The broken slave's counter reads 1, 2, 3, 4 at cycles 3, 4, 5 and 6.

Four is not an arbitrary number. It is WAIT_CYCLES + 1 — the number of clocks the write was presented. The bug scales with the slave's own latency, so a target that gets slower gets proportionally more wrong, which is the opposite of how most bugs behave.

And the commit edge is single and identifiable. Cycle 5 is where ACK_I is asserted and where the correct slave latches. There is exactly one such edge in a transfer, however long the transfer is — the invariant Chapter 9.4 generalises.

What this figure proves about debugging. No amount of staring at the Wishbone signals distinguishes these two designs. The evidence is internal — a side-effect counter, a commit strobe, or the architectural state the register controls.

6. Simulation — Variable Latency, and the Cost of a Snapshot

SIM C — one slave, four registers, four different response times.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM C - variable latency: the target sets the wait ===
    one wb_var_latency_slave, SENSOR_WAIT=4, read four registers

    word 4  ID             waits=1  presented=2  value=0x57420901  transfers=1
    word 0  STATUS         waits=1  presented=2  value=0x00000001  transfers=2
    word 1  COUNT          waits=2  presented=3  value=0x00000011  transfers=3
    word 11 SENSOR         waits=5  presented=6  value=0x00005e01  transfers=4

    The same read of word 4, through each capture style:
      STYLE A  live bus metadata, WAIT_CYCLES=0   waits=0  presented=1
      STYLE B  captured request, work=0           waits=1  presented=2
    Capturing the request costs one clock. It is a floor, not a setting.

The latency is a property of the target, and the bus simply reflects it. ID and STATUS are registers behind a multiplexer and answer as fast as this slave can. COUNT has a clock of registered read path. SENSOR has four clocks of internal work.

One master, one port, one protocol — four different durations. Nothing was configured per transfer; the address selected the target and the target set the pace.

Now read the two style rows, which are the chapter's measured trade.

Style A reaches zero wait states. Style B cannot. The same read of word 4 costs 1 presented clock through Chapter 9.1's live-metadata slave and 2 through this one — and the difference is not the internal work, which is zero in both cases. It is the capture itself.

So SENSOR_WAIT = 4 produced 5 wait states, not 4. The parameter counts internal work clocks; the observed wait states are work + 1 because acceptance costs an edge. The RTL header states this and the simulation confirms it — and had it not, this is precisely where a module-wide off-by-one would have taken root, which is why Chapter 9.1 §2 insisted on defining the parameter before using it.

SIM D — a write held for four clocks against the correct slave.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM D - one delayed write, correct command slave ===
    WAIT_CYCLES=3, so the write is presented for 4 clocks
    side effects fired       1
    last command latched     0xc0de0001

One. The write was presented at four consecutive edges and the side effect fired at exactly one of them.

SIM E — the same stimulus against the repeating slave.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM E - the SAME bus trace, repeating command slave ===
    side effects fired       4
    last command latched     0xc0de0001
    bus-signal differences between the two rigs   0

Four side effects from one logical write, and zero differences in the bus trace.

That second number is the important one. The testbench compares CYC_O, STB_O, WE_O, ADR_O and ACK_I between the two rigs on every clock and counts disagreements. There are none. The two systems are indistinguishable from the bus and differ by a factor of four in what they actually did.

What the four side effects mean in a real device. COMMAND is a write-only pulse register — Chapter 7.4's model of "do the thing". One logical command became four. For a transmit trigger that is four packets; for a stepper, four steps; for a DMA kick, four transfers queued.

And the failure is latency-dependent, which is what makes it survive testing. At WAIT_CYCLES = 0 the presenting clock is the terminating clock, so ready is true immediately and the two slaves are identical. The bug needs a slow slave to exist at all — the same structural property that hid the read-address drift in Chapter 6.2, the payload drift in Chapter 7.4 and the early address advance in Chapter 8.3.

The general rule, stated once. A side effect fires once per accepted transfer, not once per presented clock. Everything with an architectural consequence — a pulse, a counter increment, a FIFO push, a register write — must be gated on the same condition that produces the termination.

7. Failure Modes and Discriminating Evidence

Symptom: a command executes more times than it was issued, and only on the slow path.

Candidate causes. The side effect is gated on CYC_I && STB_I && WE_I rather than on the termination condition.

Discriminating evidence. Count side effects against transfers, not against clocks. A ratio equal to WAIT_CYCLES + 1 is conclusive — measured here as 4 for a 3-wait slave. The bus trace will not help; it is identical either way.

Likely RTL location. The commit's if condition, missing the ready term.

Symptom: a slave inserts one more wait state than its parameter says.

Candidate causes. Usually not a bug — it is Style B's acceptance clock.

Discriminating evidence. Compare measured wait states against internal work clocks. A constant difference of exactly one, across every target and every parameter value, is the capture register rather than a counter error. A difference that varies is a counter error, and Chapter 9.5 measures those.

Symptom: a slave responds with data from the wrong register, but only sometimes.

Candidate causes. A Style A slave serving a master that moves the address mid-transfer.

Discriminating evidence. unstable on the wait monitor. A non-zero count means RULE 3.60 was violated and the fault is the master's; zero means look inside the slave. This is the discriminator that assigns blame, and it is why the monitor reports it.

Symptom: a slave works alone and hangs when a second master exists.

Candidate causes. ACK_O is driven from internal state without the xfer gate, so it fires into a bus the slave no longer owns.

Discriminating evidence. ACK_O asserted while STB_I is low. That is a direct RULE 3.35 violation and takes one clock to spot.

Likely RTL location. A state_q == S_RESPOND term without && xfer.

8. Verification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Properties for the delayed command slave. The labelling matters more here
// than anywhere else in the module: exactly one of these is a Wishbone rule
// and the other three are this slave's contract with its own designer.
//
// NOTE ON EXECUTION: Icarus Verilog does not support SVA. These were
// reviewed by inspection and are NOT claimed to have been executed. The
// numbers in Section 6 come from procedural checks, which Icarus does run.
// ─────────────────────────────────────────────────────────────────────────
module wb_cmd_slave_props (
  input logic clk_i, rst_i,
  input logic cyc_i, stb_i, we_i, ack_i,
  input logic [3:0] adr_i,
  input logic cmd_stb_i,
  input int unsigned cmd_count_i
);
  default clocking cb @(posedge clk_i); endclocking
  default disable iff (rst_i);

  logic xfer, commit_edge;
  assign xfer        = cyc_i && stb_i;
  assign commit_edge = xfer && ack_i && we_i && (adr_i == 4'd9);

  // P1 — SPECIFICATION (RULE 3.35). A termination only in response to the
  //      AND of CYC_I and STB_I. Latency does not relax this.
  P1_ack_qualified: assert property ( ack_i |-> xfer );

  // P2 — LOCAL POLICY, and the one this chapter exists for. The command
  //      strobe fires only in the clock following a terminating write to
  //      COMMAND. Not once per presented clock.
  P2_commit_once: assert property ( cmd_stb_i |-> $past(commit_edge) );

  // P3 — LOCAL POLICY. The side-effect counter advances only when the
  //      strobe fires, so "commands executed" and "commands acknowledged"
  //      cannot drift apart.
  P3_count_tracks_strobe: assert property (
    (cmd_count_i != $past(cmd_count_i)) |-> $past(cmd_stb_i)
  );

  // P4 — LOCAL POLICY. A write presented but not terminated produces no
  //      side effect at all. This is the direct negation of the bug, and
  //      it is the property wb_cmd_slave_repeating fails.
  P4_no_commit_while_waiting: assert property (
    (xfer && we_i && !ack_i) |=> !cmd_stb_i
  );
endmodule

P4 is the property worth writing even though P2 nearly implies it. P2 says every strobe follows a commit edge; P4 says every waiting clock produces no strobe. They fail differently, and P4 fails on the first wait clock rather than at the end of the transfer, which is a materially better debugging signal.

Only P1 is a Wishbone rule. P2, P3 and P4 describe a contract this slave's designer chose — that a command register pulses once per accepted write. A different peripheral could legitimately define different semantics, and it would then need different properties. What it could not do is leave the semantics unstated, which is how the repeating slave came to look reasonable.

9. Common Mistakes

"The slave can just read the bus whenever it needs the address."

Wrong mental model: the bus is a reliable store for the duration of the operation.

What is true: it is, if the master is conformant. RULE 3.60 obliges the master to hold the metadata stable for the whole outstanding transfer, so Style A is sound — but its correctness is now contingent on somebody else's.

Concrete bug: a Style A slave paired with a master that advances its address while waiting. The slave serves whichever address is present at termination.

Observable evidence: the wait monitor's unstable count, which assigns the fault to the master.

Correct model: choose the style deliberately. Capturing costs a clock and buys independence; reading live costs nothing and buys a dependency.

"Capturing the request is strictly safer, so always do it."

Wrong mental model: isolation is free.

What is true: acceptance consumes a clock, so a capturing slave can never reach zero wait states. Measured: the same read costs 1 presented clock through the live-metadata slave and 2 through the capturing one, with zero internal work in both.

Concrete bug: a register file rewritten "defensively" in Style B, doubling the cost of every fast access in the system for no benefit.

Observable evidence: the two style rows in SIM C.

Correct model: match the style to the target. A multiplexer does not need a snapshot.

"A write that is presented for four clocks is four writes."

Wrong mental model: each clock with CYC_I && STB_I && WE_I is a new request.

What is true: it is one transfer being presented for four clocks, and it has exactly one termination.

Concrete bug: wb_cmd_slave_repeating. Measured: 4 side effects from 1 logical write, with an identical bus trace.

Observable evidence: an internal side-effect count equal to WAIT_CYCLES + 1. Not visible on the bus at all.

Correct model: gate every architectural consequence on the same condition that produces the termination.

"If the bus trace is correct, the slave is correct."

Wrong mental model: conformance implies correctness.

What is true: the repeating slave's bus interface is fully conformant — right decode, right latency, right acknowledge edge. The defect is entirely internal.

Concrete bug: signing off a peripheral on protocol-checker results alone.

Observable evidence: zero bus-signal differences between a correct and a broken design.

Correct model: protocol conformance is necessary and not sufficient — the thread Chapter 5.8 §8 has been collecting since Module 4.

10. Interview Reasoning

There are two defensible answers and I would say which one I am choosing and why, because the trade is real.

Option one: work from the presented bus signals. RULE 3.60 requires the master to hold ADR_O, DAT_O(), SEL_O and WE_O stable for the whole outstanding transfer, and the transfer is outstanding for all four clocks. So the address genuinely is still there — reading it live is not a shortcut, it is relying on a stated obligation.

Option two: capture at acceptance and work from the copy. The operation then runs against a snapshot and cannot be disturbed by anything the bus does.

What decides it for me is usually the second-order cost. Capturing consumes a clock — acceptance happens at an edge, so the earliest a captured request can drive a response is the next edge. A capturing slave cannot do zero-wait, and I measured that: the same read costs one presented clock through a live-metadata slave and two through a capturing one, with no internal work in either.

So for a four-clock sensor, I capture. One clock on top of four is noise, and I get an operation that is immune to a master misbehaving mid-transfer.

For a register file, I would not. Paying a clock on every fast access to defend against a master that is required to be conformant anyway is a bad trade, and it slows down the accesses that dominate.

The part I would flag in review either way. If I work from the live bus, my correctness is now contingent on the master's conformance — and I would want a monitor that checks address stability across the transfer, so that when something does go wrong the evidence says whose fault it is rather than just that data was wrong.

11. Understanding Check

12. What's Next

The slave side is now covered: where the request lives, what capturing costs, and why a side effect belongs to the termination rather than to the clock.

Every experiment so far has used wb_wait_safe_master, which latches its client's request and then has nothing left that can move. That made the slave chapters clean — the metadata was stable because the master had no mechanism to disturb it.

Real masters have clients, and clients do not stop having ideas while a transfer is outstanding.

What is a master obliged to hold while it waits, and what happens if its client changes its mind?

Chapter 9.3 — ACK Delay takes the master's side, and measures a master that drives the bus straight from a client that moves. 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.