Skip to content
VLSI Mentor

Wishbone · Module 8

Block Transfer Cycle

Holding CYC_O across several transfers separates cycle duration from transfer duration, gives the master its own throttle, and makes address advancement a correctness problem.

Chapters 8.1 and 8.2 counted 1 and 1, and both noted that the coincidence was conditional.

What changes when one bus cycle contains several transfers?

1. Not a Burst

Engineers arriving from AXI or AHB will map a block cycle onto a burst. The mapping fails in three places, and each failure produces a different wrong expectation.

AXI-style burstWishbone Classic BLOCK
Lengthdeclared up front (AWLEN)not declared — not on the bus at all
Address sequenceinferred from a declared modethe master drives every address
Last beatsignalled (WLAST)inferred from CYC_O negating
Per-transfer handshakeone per beatone per transfer, same as a single cycle
Slave knowledgeknows the block's shapeknows only "another qualified transfer"

The last row is the one with engineering consequences. A Classic slave cannot tell a block cycle from a sequence of single cycles by looking at any one transfer. It sees a qualified transfer, answers it, and sees another. CYC_O staying asserted is the only difference, and most slaves do not look at it for that purpose.

Which means a correct single-transfer slave already supports block cycles, with no changes — as long as it obeys RULE 3.30 and gates on CYC_I & STB_I. That is a real and slightly surprising benefit of the simplicity.

And it means the master carries all the structure. Transfer count, address sequence, when to stop — all master-side state, none of it on the wires.

2. The Master Can Throttle Too

Modules 6 and 7 showed one throttle: the slave withholds its termination and the master waits. A block cycle has a second one, and it is the reason CYC_O and STB_O must be separable signals.

The BLOCK sections describe wait states being inserted by the master — by negating STB_O — as well as by the slave.

ThrottleMechanismState on the bus
Slave-sidewithhold ACK_OCYC & STB asserted, no termination
Master-sidenegate STB_OCYC asserted, STB negated

That second state never occurred in Modules 6 or 7. Chapter 8.1's simulation measured gaps = 0 in every run and called it the PERMISSION 3.40 signature.

It is not an error and not a stall in the usual sense. It means: I still own this operation, and I am not asking for anything at this instant. A master uses it when it needs a cycle to compute the next address, or when its client has not supplied the next data word yet.

And it closes the loop on PERMISSION 3.40:

"If a MASTER doesn't generate wait states, then [STB_O] and [CYC_O] MAY be assigned the same signal."

A block master that ever pauses cannot tie them. The permission's precondition is exactly the thing a block master gives up.

3. The Running System Gains a Window

Block transfers need sequential addresses, and the peripheral from Modules 6 and 7 is a register map rather than a memory — its offsets are meaningful individually, not as a run.

So the device gains a small window, and nothing else changes:

ByteWordRegisterAccess
0x082CONTROLread/write
0x186OUTPUT_DATAread/write
0x300x3C12–15WINDOW[0..3]read/write, 4 sequential words

Why it exists, stated plainly: four consecutive addresses that mean something as a run, so a block cycle has a natural target. Everything Modules 6 and 7 named keeps its offset and its access rules, and word 7 stays unmapped exactly as Chapter 6.1 §8 and Chapter 6.6's Trace B rely on.

It is deliberately four words. A larger window would invite a memory-controller tutorial, which is not this module's job.

4. RTL — A Block Master and a Window

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_block_master — a block cycle controller.
//
// PURPOSE. Hold ONE bus cycle across N transfers. This is Chapter 8.2's
// wb_single_cycle_ctrl with exactly three additions:
//   * a transfer counter
//   * an address that advances
//   * a cycle that does NOT end when a transfer terminates
// Everything else is unchanged, which is the point: a block cycle is a
// small extension of the single-cycle skeleton, not a new protocol.
//
// ── THE CENTRAL CORRECTNESS RULE ─────────────────────────────────────
//   The address and the transfer index advance on TERMINATION, never on
//   elapsed clocks. A transfer may be presented for many cycles (slave
//   waits) and is still ONE transfer — the same counting rule Chapters
//   6.5 and 7.4 established inside slaves, here applied to sequencing.
//
// CYC/STB ARE SEPARATE. This master pauses between transfers (S_GAP), so
// PERMISSION 3.40's precondition does not hold and the two signals cannot
// share a register.
//
// DIRECTION. we_o is a passthrough, constant for the whole block. Nothing
// in the sequencing branches on it — BLOCK READ and BLOCK WRITE are the
// same controller, per Chapter 8.2 Section 1.
//
// Reset: SYNCHRONOUS, ACTIVE HIGH (RULES 2.30, 3.00, 3.20).
// ─────────────────────────────────────────────────────────────────────────
module wb_block_master #(
  parameter int unsigned AW = 30,
  parameter int unsigned DW = 32,
  parameter int unsigned CW = 4        // transfer-count width
) (
  input  logic            clk_i,
  input  logic            rst_i,

  // ── local client side ───────────────────────────────────────────────
  input  logic            req_i,
  input  logic            req_we_i,
  input  logic [AW-1:0]   req_base_i,  // first WORD address
  input  logic [CW-1:0]   req_len_i,   // number of transfers, >= 1
  input  logic [DW-1:0]   req_dat_i,   // write payload (see SIMPLIFICATIONS)
  input  logic            gap_en_i,    // insert a master wait between transfers
  output logic            busy_o,
  output logic            done_o,      // one pulse when the CYCLE completes
  output logic [CW-1:0]   xfers_o,     // transfers completed this cycle
  output logic [DW-1:0]   last_dat_o,  // last read value captured

  // ── 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 ─────────────────────────────────────────────────────────────
  // S_XFER : a transfer is presented          (CYC=1, STB=1)
  // S_GAP  : the cycle is held, nothing asked (CYC=1, STB=0)  <-- NEW
  //          This is the master-side throttle of Section 2, and the state
  //          that could not exist in Chapters 8.1 and 8.2.
  typedef enum logic [1:0] { S_IDLE, S_XFER, S_GAP } state_e;
  state_e state_q;

  logic [AW-1:0] adr_q;
  logic [CW-1:0] left_q;
  logic          we_q;
  logic [DW-1:0] dat_q;
  logic          gap_q;

  // ── THE SEPARATION ────────────────────────────────────────────────────
  // cyc_o covers BOTH active states; stb_o covers only S_XFER.
  assign cyc_o = (state_q == S_XFER) || (state_q == S_GAP);
  assign stb_o = (state_q == S_XFER);
  assign we_o  = we_q;
  assign adr_o = adr_q;
  assign dat_o = dat_q;
  assign sel_o = '1;                   // full-word transfers; see Module 13
  assign busy_o = (state_q != S_IDLE);

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

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      state_q    <= S_IDLE;            // RULE 3.20 via the encoding
      adr_q      <= '0;
      left_q     <= '0;
      we_q       <= 1'b0;
      dat_q      <= '0;
      gap_q      <= 1'b0;
      done_o     <= 1'b0;
      xfers_o    <= '0;
      last_dat_o <= '0;
    end else begin
      done_o <= 1'b0;

      unique case (state_q)
        S_IDLE: begin
          if (req_i && (req_len_i != '0)) begin
            // ── CYCLE START ─────────────────────────────────────────────
            // Once per block, not once per transfer.
            state_q <= S_XFER;
            adr_q   <= req_base_i;
            left_q  <= req_len_i;
            we_q    <= req_we_i;
            dat_q   <= req_dat_i;
            gap_q   <= gap_en_i;
            xfers_o <= '0;
          end
        end

        S_XFER: begin
          // ── TRANSFER END ────────────────────────────────────────────
          // Everything here is gated on `terminated`. Nothing advances on
          // elapsed clocks — that is the bug wb_block_master_fastadv has.
          if (terminated) begin
            if (ack_i && !we_q) last_dat_o <= dat_i;   // Chapter 6.3's window
            xfers_o <= xfers_o + CW'(1);

            if (left_q == CW'(1)) begin
              // ── CYCLE END ─────────────────────────────────────────────
              // The LAST transfer's termination is also the cycle's end,
              // so CYC_O and STB_O negate together — which is how a block
              // cycle ends in Classic, there being no last-transfer flag.
              state_q <= S_IDLE;
              done_o  <= 1'b1;
            end else begin
              // ── NEXT TRANSFER ─────────────────────────────────────────
              // Advance ONLY here. The address moves because a transfer
              // COMPLETED, not because a clock passed.
              left_q  <= left_q - CW'(1);
              adr_q   <= adr_q + AW'(1);
              // Written as if/else rather than a ternary: assigning an enum
              // from a conditional expression needs an explicit cast, and
              // Icarus rejects the bare form (the portability wart recorded
              // in Chapter 4.3).
              if (gap_q) state_q <= S_GAP;
              else       state_q <= S_XFER;
            end
          end
        end

        S_GAP: begin
          // The cycle is held with nothing presented. A slave sees CYC_I
          // asserted and STB_I negated: RULE 3.35 forbids it to terminate
          // and it simply waits. One cycle here; a real master might stay
          // until its client supplies the next word.
          state_q <= S_XFER;
        end

        default: state_q <= S_IDLE;
      endcase
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_window_slave — four sequential words, so a block has a target.
//
// PURPOSE. Give the running peripheral a small run of addresses. It is a
// register file, not a memory controller — four words, no latency model,
// no burst awareness.
//
// NOTE WHAT IS ABSENT: this slave has NO knowledge of block cycles. It
// answers each qualified transfer on its own terms. That is Section 1's
// claim in executable form — a correct single-transfer slave already
// supports blocks, provided it gates on CYC_I & STB_I per RULE 3.30.
//
// Reset: SYNCHRONOUS, ACTIVE HIGH (RULES 2.30, 3.00).
// ─────────────────────────────────────────────────────────────────────────
module wb_window_slave #(
  parameter int unsigned OFF_AW = 4,
  parameter int unsigned DW     = 32,
  parameter int unsigned LAT    = 0     // wait states, for SIM D
) (
  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,
  // observable for QA
  output logic [DW-1:0]     w0_o,
  output logic [DW-1:0]     w1_o,
  output logic [DW-1:0]     w2_o,
  output logic [DW-1:0]     w3_o
);
  localparam int unsigned NL = DW/8;
  localparam logic [OFF_AW-1:0] W_BASE = 4'd12;   // words 12..15

  logic [DW-1:0] win_q [4];
  assign w0_o = win_q[0];
  assign w1_o = win_q[1];
  assign w2_o = win_q[2];
  assign w3_o = win_q[3];

  logic xfer;
  assign xfer = cyc_i & stb_i;                    // RULES 3.30 & 3.35

  logic in_window;
  assign in_window = (adr_i >= W_BASE) && (adr_i <= (W_BASE + 4'd3));

  logic [1:0] idx;
  assign idx = adr_i[1:0];                        // words 12..15 -> 0..3

  // ── LATENCY, only so SIM D can delay one transfer ──────────────────────
  // `ready` is declared and assigned before the block that uses it.
  logic [7:0] waited_q;
  logic       ready;
  assign ready = (waited_q >= 8'(LAT));

  always_ff @(posedge clk_i) begin
    if (rst_i)       waited_q <= '0;
    else if (!xfer)  waited_q <= '0;
    else if (!ready) waited_q <= waited_q + 8'd1;
    else             waited_q <= '0;
  end

  assign err_o = xfer & ready & ~in_window;
  assign ack_o = xfer & ready &  in_window;

  logic commit;
  assign commit = xfer & we_i & ack_o;            // Module 7's commit policy

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      for (int unsigned i = 0; i < 4; i++) win_q[i] <= '0;
    end else if (commit) begin
      for (int unsigned n = 0; n < NL; n++)
        if (sel_i[n]) win_q[idx][n*8 +: 8] <= dat_i[n*8 +: 8];
    end
  end

  always_comb begin
    dat_o = '0;                                   // RULE 3.65
    if (xfer && !we_i && ack_o) dat_o = win_q[idx];
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_block_master_fastadv — THE EARLY-ADVANCE BUG, isolated.
//
// NOT A REFERENCE DESIGN. Identical to wb_block_master except that the
// address and the counter advance on every clock the transfer is
// presented, rather than on termination:
//
//     if (terminated) ...        becomes      unconditional advance
//
// Against a zero-wait-state slave every transfer terminates in the cycle
// it is presented, so the two masters are INDISTINGUISHABLE. Introduce one
// wait state and the address runs ahead of the transfers.
// ─────────────────────────────────────────────────────────────────────────
module wb_block_master_fastadv #(
  parameter int unsigned AW = 30,
  parameter int unsigned DW = 32,
  parameter int unsigned CW = 4
) (
  input  logic            clk_i,
  input  logic            rst_i,
  input  logic            req_i,
  input  logic            req_we_i,
  input  logic [AW-1:0]   req_base_i,
  input  logic [CW-1:0]   req_len_i,
  input  logic [DW-1:0]   req_dat_i,
  output logic            busy_o,
  output logic            done_o,
  output logic [CW-1:0]   xfers_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, we_q;
  logic [AW-1:0] adr_q;
  logic [CW-1:0] left_q;
  logic [DW-1:0] dat_q;

  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  = '1;
  assign busy_o = active_q;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      active_q <= 1'b0; adr_q <= '0; left_q <= '0; we_q <= 1'b0;
      dat_q <= '0; done_o <= 1'b0; xfers_o <= '0;
    end else begin
      done_o <= 1'b0;
      if (!active_q) begin
        if (req_i && (req_len_i != '0)) begin
          active_q <= 1'b1; adr_q <= req_base_i; left_q <= req_len_i;
          we_q <= req_we_i; dat_q <= req_dat_i; xfers_o <= '0;
        end
      end else begin
        if (ack_i || err_i || rty_i) xfers_o <= xfers_o + CW'(1);
        // ── THE BUG: advance every clock, regardless of termination ────
        if (left_q == CW'(1)) begin
          active_q <= 1'b0; done_o <= 1'b1;
        end else begin
          left_q <= left_q - CW'(1);
          adr_q  <= adr_q + AW'(1);
        end
      end
    end
  end
endmodule

Reading the group

Purpose. The block master holds one cycle across N transfers; the window gives it somewhere to go; the fast-advance master shows what happens when sequencing keys off clocks instead of terminations.

Interface. The client asks for a base address and a length. done_o pulses once per cycle, not once per transfer — that is the client-visible consequence of the whole chapter.

State. Three states, an address, a remaining count, a direction, a payload and a gap flag. state_q is no longer a single "active" bit, and that is precisely the change from Chapter 8.2's controller.

Combinational logic. The two qualifier expressions — which now differ — and the bus outputs from state.

Sequential logic. Capture at request; advance on termination; release after the last.

Cycle start. Once, at S_IDLE → S_XFER.

Transfer start. Repeatedly, at each entry to S_XFER.

Termination. Sampled as a level in S_XFER; everything that advances is inside that branch.

Next transfer. Via S_GAP if enabled, otherwise straight back to S_XFER — a back-to-back block with no gap.

Cycle end. When the last transfer terminates. CYC_O and STB_O negate together, which is how a Classic block ends.

Reset. Synchronous, active high; S_IDLE negates both qualifiers.

Failure modes. wb_block_master_fastadv is one; Section 7 covers the rest.

Simplifications. One write payload for the whole block rather than a per-transfer stream — a real block-write master takes a word per transfer from its client, and that plumbing would obscure the sequencing this chapter is about. SEL_O is tied to all-ones; Module 13 owns byte selects. The window has no realistic latency source; LAT exists only so SIM D can delay a transfer.

5. Waveform — One Cycle, Four Transfers

A block read cycle: CYC spans, STB repeats

10 cycles
Ten clock cycles showing a block read of four words under a single bus cycle. The cycle signal rises at cycle two and stays asserted continuously until the end of cycle seven. The strobe signal is asserted in cycles two and three for transfers zero and one, negated in cycle four which is a master inserted gap, and asserted again in cycles five through seven. Transfer two, presented at word fourteen, is delayed by one wait state before its acknowledge. The address advances from word twelve through word fifteen, changing only after each acknowledge and standing still through both the gap and the wait. Four acknowledges appear, one per transfer, and the transfer index advances from zero to four.cycle + transfer 0cycle + transfer 0master gap: CYC high, STB lowmaster gap: CYC high, STBlowlast transfer; cycle endslast transfer; cycle endsCLK_ICYC_OSTB_OWE_OADR_O----0xC0xD0xD0xE0xE0xF------------ACK_Ixfer0012223444t0t1t2t3t4t5t6t7t8t9
Figure 1 — four transfers under one bus cycle, with a master gap after transfer 1 and a slave wait on transfer 2.

Count it. Bus cycles = 1. Transfers = 4 — four acknowledges. CYC_O asserted for 6 clocks.

Cycle 4 is the state Modules 6 and 7 never produced. CYC_O asserted, STB_O negated: the master owns the cycle and is asking for nothing. The slave does nothing, correctly — RULE 3.35 requires a termination to be generated from the AND of CYC_I and STB_I.

The address advances only after an acknowledge. It reads 0xC for transfer 0, 0xD for transfer 1, and then stands still through the gap in cycle 4 before moving to 0xE for transfer 2 — which the slave delays, so 0xE holds across cycles 5 and 6 too. It is driven by completions, not by clocks, and Section 6 measures what happens when it is not.

The cycle ends at the last transfer's termination, with both qualifiers negating together. Nothing on the bus announced that transfer 3 was the last — the master simply stopped, which is how a Classic block ends.

6. Simulation — Three Block Runs

SIM C — four transfers, immediate response.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM C - block, immediate ACK ===
    bus cycles begun        1
    transfers completed     4
    reads / writes          4 / 0
    wait cycles             0
    gaps (CYC & !STB)       0
    clocks with CYC high    4
    addresses presented     0xc 0xd 0xe 0xf
    done pulses             1

One cycle, four transfers, four clocks. The address sequence is exactly the four window words, and done_o pulsed once — for the cycle, not per transfer.

SIM D — every transfer delayed by one wait state, plus a master gap.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM D - block with waits and a master gap ===
    bus cycles begun        1
    transfers completed     4
    wait cycles             4
    gaps (CYC & !STB)       3
    clocks with CYC high    11
    addresses presented     0xc 0xd 0xe 0xf
    address changes while a transfer was outstanding   0
    done pulses             1

Still one cycle and still four transfers. The cycle grew from 4 clocks to 11 — four slave wait states and three master gaps added to the four terminating clocks — and the content did not change.

gaps = 3 — the master-side throttle, and the number that was zero in every Module 6, 7, 8.1 and 8.2 run. That single counter is the clearest evidence a trace is a block cycle rather than a run of single cycles.

Note what the two throttle counters separate. waits = 4 is the slave holding four clocks; gaps = 3 is the master holding three. Both lengthen the same cycle, and only by reading STB_O can you tell which side spent the time — the distinction Section 2 argued is new in this chapter.

address changes while a transfer was outstanding = 0 is the correctness check: the address moved between transfers and never during one.

SIM E — the early-advance master, same stimulus.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM E - early address advance (1 wait state) ===
    correct master   addresses presented   0xc 0xd 0xe 0xf
                     transfers completed   4
                     addresses acknowledged 0xc 0xd 0xe 0xf
    fast-advance     addresses presented   0xc 0xd 0xe 0xf
                     transfers completed   2
                     address changes while outstanding   2
                     addresses acknowledged 0xd 0xf

Read the two address lines, not the transfer count. Both masters presented the same four window words. The broken one acknowledged only two of them0xd and 0xf.

WINDOW[0] and WINDOW[2] were never read at all. The fast-advance master put 0xc on the bus, the slave took its one wait state, and before that transfer could terminate the master had already moved to 0xd. The acknowledge that arrived on the next clock terminated a transfer whose address was 0xd. The same thing happened to 0xe. Two of the four requested words silently vanished, and the two that did return were the wrong ones for the slots the master was filling.

This is the failure shape worth remembering: the addresses look right on a bus trace. Every word 0xc0xf appears. Nothing is out of range, nothing is unmapped, no ERR_I is raised, and the cycle terminates normally. Only by correlating each address against its own termination does the loss become visible — which is exactly what address changes while outstanding = 2 counts, and it is a RULE 3.60 violation twice over.

The master's own bookkeeping records the damage: transfers completed = 2 against a request for four. A block master that reports fewer transfers than it asked for has usually failed this way — the counter is honest even when the bus trace looks clean.

At zero wait states the two masters are identical — SIM C's run is bit-for-bit the same for both. The bug needs latency to appear, which is the same structural property Chapter 6.2 and Chapter 7.4 both measured — and the reason a block master's unit test must include a waiting slave.

7. Failure Modes and Discriminating Evidence

Symptom: a block skips addresses, and the transfer count is short.

Candidate causes. The address advances on elapsed clocks rather than on terminations.

Discriminating evidence. Watch ADR_O across a presented transfer. Any change before its termination is a RULE 3.60 violation and is conclusive. Do not look for gaps in the presented address sequence — SIM E shows there may be none. Compare the addresses acknowledged against the addresses presented: under one wait state the acknowledged set is half the presented set, and it is the acknowledged set that says which words actually moved.

Likely RTL location. The advance's enable — outside the terminated branch.

Property. P3 in Section 8.

Symptom: a block repeats an address.

Candidate causes. The advance is inside the termination branch but the state machine re-enters S_XFER without it — or the termination is detected twice for one transfer.

Discriminating evidence. Count acknowledges against distinct addresses. More acknowledges than addresses means a transfer was counted twice; equal counts with a repeated address means the advance was skipped on one iteration.

Symptom: the block stops after the first transfer.

Candidate causes. The master released CYC_O at the first termination — the single-cycle behaviour it inherited.

Discriminating evidence. CYC_O negating at the first acknowledge. The monitor reports this as several bus cycles rather than one, which is the cleanest signature: cycles = 4, transfers = 4 is a run of single cycles, cycles = 1, transfers = 4 is a block.

Likely RTL location. The cycle-end condition — wb_block_master ends the cycle only when left_q == 1.

Symptom: a slave responds correctly to single cycles and misbehaves inside a block.

Candidate causes. The slave keys something off CYC_I rising, or holds internal state across what it assumes are cycle boundaries.

Discriminating evidence. CYC_I rises once for the whole block. A slave counting cycle starts, or resetting per-cycle state on that edge, sees one where it expected four.

Correct model: a slave should key on the qualified transfer, never on the cycle. wb_window_slave has no CYC_I edge logic at all.

Symptom: the master hangs mid-block with CYC_O high and STB_O low.

Candidate causes. The gap state has no exit — a master waiting for a client that never supplies the next word.

Discriminating evidence. gaps climbing without bound while transfers is static. Distinguishable from a slave-side hang, where STB_O would be high with no termination.

8. Verification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_block_checker — block-cycle properties.
//
// P1 and P2 are SPECIFICATION (RULES 3.25, 3.60). P3 and P4 are LOCAL
// DESIGN POLICY describing THIS master's sequencing — the specification
// says nothing about how a block master computes its addresses, so nothing
// here can be a Wishbone rule.
//
// NOTE what is ABSENT: Chapter 8.1's P3 ("the cycle ends with its
// transfer"). A block master violates it by design, and this checker
// REPLACES it with P4 rather than simply dropping it.
// ─────────────────────────────────────────────────────────────────────────
module wb_block_checker #(
  parameter int unsigned AW = 30,
  parameter int unsigned CW = 4
) (
  input logic          clk_i,
  input logic          rst_i,
  input logic          cyc_o,
  input logic          stb_o,
  input logic [AW-1:0] adr_o,
  input logic          ack_i,
  input logic          err_i,
  input logic          rty_i,
  // white-box
  input logic [CW-1:0] left_q,
  input logic [CW-1:0] xfers_q,
  input logic          done_o
);
  default disable iff (rst_i);

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

  // P1 — SPECIFICATION (RULE 3.25). A strobe never exists outside a cycle.
  //      Unchanged from Chapters 8.1 and 8.2: this rule does not care how
  //      many transfers the cycle contains.
  property p_stb_inside_cyc;
    @(posedge clk_i) stb_o |-> cyc_o;
  endproperty
  a_stb_inside_cyc : assert property (p_stb_inside_cyc)
    else $error("RULE 3.25: STB_O asserted outside a bus cycle");

  // P2 — SPECIFICATION (RULE 3.60). The address is stable while its
  //      transfer is outstanding. This is what the fast-advance master
  //      violates, and it fires on the FIRST early advance rather than
  //      when the address sequence is noticed to be wrong.
  property p_addr_stable_while_outstanding;
    @(posedge clk_i) (cyc_o && stb_o && !terminated) |=> $stable(adr_o);
  endproperty
  a_addr_stable_while_outstanding :
    assert property (p_addr_stable_while_outstanding)
    else $error("RULE 3.60: ADR_O moved while a transfer was outstanding");

  // P3 — LOCAL POLICY. The transfer index advances ONLY on a termination.
  //      The sequencing form of the counting rule Chapters 6.5 and 7.4
  //      established for side effects.
  property p_index_advances_on_termination;
    @(posedge clk_i) $changed(xfers_q) |-> $past(cyc_o && stb_o && terminated);
  endproperty
  a_index_advances_on_termination :
    assert property (p_index_advances_on_termination)
    else $error("LOCAL: transfer index advanced without a termination");

  // P4 — LOCAL POLICY, REPLACING Chapter 8.1's P3. The cycle ends only
  //      when the LAST transfer terminates — not when any transfer does.
  //      This is the block-cycle form of "the cycle ends with its work".
  property p_cycle_ends_only_at_last;
    @(posedge clk_i) ($fell(cyc_o)) |-> $past(terminated && (left_q == CW'(1)));
  endproperty
  a_cycle_ends_only_at_last : assert property (p_cycle_ends_only_at_last)
    else $error("LOCAL: cycle ended before the last transfer");
endmodule

P4 is the point of this checker. Chapter 8.1 §7 wrote a property saying the cycle ends with its transfer and labelled it LOCAL POLICY with a note that it would have to be deleted here. It is not deleted — it is replaced, and the replacement says the same thing at the right granularity.

That is what a policy property should do when a design grows. Dropping it would lose the invariant entirely; keeping it unchanged would fail on a correct design.

Tooling limitation. Icarus Verilog has no SVA support and cannot execute any of these. They are reviewed by inspection; all three modules are elaborated and the three simulations above were run.

9. Common Mistakes

"A block transfer is a Wishbone burst."

Wrong mental model: importing AXI/AHB burst structure.

Concrete bug: a slave written to expect a declared length or a last-beat flag, or a master that assumes the slave infers the address sequence.

Observable evidence: a slave that works with single cycles and mis-sequences inside a block; or RTL looking for CTI_O/BTE_O in a B3 Classic design, where they do not exist.

Correct model: Classic has no length, no last-beat signal and no address mode. The master drives every address and stops by negating both qualifiers. B4's pipelined mode adds tags; that is a different profile.

"The address should advance every clock during a block."

Wrong mental model: one transfer per clock.

Concrete bug: the fast-advance master. Measured: it presented 0xC 0xD 0xE 0xF — the correct addresses — but acknowledged only 0xD and 0xF, completing 2 of the 4 requested transfers.

Observable evidence: a short transfer count, and an acknowledged-address set smaller than the presented one. The presented sequence alone looks correct, which is why this bug is easy to miss.

Correct model: advance on termination. A transfer may occupy many clocks and is still one transfer.

"ACK ends the block."

Wrong mental model: the termination releases the cycle.

Concrete bug: a master that negates CYC_O at the first acknowledge, turning one block into four single cycles.

Observable evidence: the monitor reports cycles = 4, transfers = 4 instead of cycles = 1, transfers = 4.

Correct model: ACK terminates a transfer. The master's cycle logic decides when the cycle ends — per RULE 3.25, it must span from the first transfer to the last.

"CYC high with STB low means something is wrong."

Wrong mental model: the qualifiers always move together.

Concrete bug: none — but a reader who believes this will misdiagnose a normal master gap as a fault and look for a bug that is not there.

Observable evidence: time wasted investigating a correct trace.

Correct model: it is the master-side throttle, described in the BLOCK sections and counted by the monitor as gaps. It is the signature of a block cycle.

10. Interview Reasoning

Because a transfer may occupy many clocks and is still one transfer — the address belongs to the transfer, not to the clock.

The mechanism when it is wrong. With a slave inserting one wait state, each transfer is presented for two clocks. A master advancing every clock moves the address underneath its own outstanding transfer — a RULE 3.60 violation, since the address is qualified by STB_O and must be stable while the transfer is presented.

What I measured. The correct master presented 0xC 0xD 0xE 0xF and acknowledged all four. The fast-advance master presented the same four addresses and acknowledged only 0xD and 0xF — two address changes while a transfer was outstanding, and two of the four window words never read.

The part I would emphasise in an interview is that the presented addresses were identical. The broken master did not walk out of range, raise an error, or produce an obviously malformed trace. The damage is entirely in the pairing — which address was live when each acknowledge arrived. A defect that leaves the address sequence looking correct is the argument for checking address stability across a transfer rather than eyeballing the address list.

Two distinct consequences, which is worth separating. The slave serves whatever address happens to be present when it terminates, so the data is wrong. And the master's transfer count and the number of words it believes it moved come apart, so the sequence is wrong. Either alone would be a bug; together they make the trace hard to read.

Why it survives testing. At zero wait states every transfer terminates in the cycle it is presented, so the two masters are byte-identical. The bug needs latency to exist at all — the same structural property that hid the read-address drift in Chapter 6.2 and the write payload drift in Chapter 7.4.

The rule to state generally. Anything that advances a sequence — an address, an index, a pointer, a descriptor — advances on completion, never on elapsed time. That is the same counting rule as "a side effect fires once per accepted transfer", applied to sequencing instead of to state.

And the property that catches it is conformance rather than policy: (cyc && stb && !terminated) |=> $stable(adr). No white-box access needed, so it catches third-party masters too.

11. Understanding Check

12. What's Next

CYC_O and STB_O are now visibly different signals, a cycle's duration is visibly different from its content, and sequencing is visibly a function of completions rather than clocks.

Every transfer in that block was independent — four reads that happened to be adjacent. The next cycle type holds two transfers that are not independent at all: the second one's data is computed from the first one's result, and the whole point is that nothing intervenes between them.

How can one bus cycle contain a read followed by a related write — and what exactly does that guarantee?

Chapter 8.4 — Read-Modify-Write Cycle 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.