Wishbone · Module 14
Sequential Transfers
Grouping four transfers under one CYC does not merge them. Measured: one bus cycle, four terminations, and the phase boundary that is an edge rather than a pulse.
Module 13 finished sub-word access. A transfer can name a word and name the bytes of it that matter.
Every transfer in this course has still stood alone — CYC_O rising before it and falling after it, once per word.
What changes when several transfers belong to one bus cycle?
1. Two Ways to Move Four Words
Case A — four separate cycles. CYC_O rises, one transfer happens, CYC_O falls. Four times.
Case B — one block cycle. CYC_O rises, four transfers happen, CYC_O falls.
The payload is identical. Same four addresses, same four data words, same final memory. What differs is the grouping, and Section 5 measures exactly what that grouping costs and buys.
What does not change is the per-transfer contract. In both cases each transfer has its own address, its own data, its own presentation and its own termination. Keeping CYC_O asserted does not merge them into anything.
2. What CYC_O Actually Communicates
Three verified facts, and the third is the one that surprises people.
RULE 3.25 — "MASTER interfaces MUST assert CYC_O for the duration of SINGLE READ / WRITE, BLOCK and RMW cycles. CYC_O MUST be asserted no later than the rising CLK_I edge that qualifies the assertion of STB_O. CYC_O MUST be negated no earlier than the rising CLK_I edge that qualifies the negation of STB_O." Retention across a block is normative, not a style.
PERMISSION 3.05 — "MASTER interfaces MAY assert CYC_O indefinitely." There is no maximum block length on the wires because there is no length on the wires at all.
And the specification's stated motivation is not speed. The BLOCK section says:
This function is most useful when multiple MASTERs are used on the interconnect. For example, if the SLAVE is a shared (dual port) memory, then an arbiter for that memory can determine when one MASTER is done with it so that another can gain access to the memory.
CYC_O is a statement of tenure, addressed to an arbiter. Throughput is a consequence of grouping, measured in Chapter 14.2 — it is not what the mechanism was described for.
3. Where the Phase Boundary Is
This is the structural detail most often drawn wrong, and the specification settles it in its own figure.
The intuitive picture pulses STB_O once per transfer — up, ACK, down, up, ACK, down. That is what a block cycle looks like when the master inserts wait states, and it is not what back-to-back phases look like.
The normative BLOCK READ figure holds STB_O asserted across consecutive phases. It drops only where the text says the master chose to: "MASTER negates STB_O to introduce a wait state (-WSM-)." ACK_I is held the same way, dropping where "SLAVE negates ACK_I to introduce a wait state."
So a phase ends at a clock edge where CYC_O, STB_O and a termination are all asserted:
phase boundary = rising CLK_I edge with CYC_O & STB_O & (ACK_I | ERR_I)The boundary is the edge, not a pulse. Four consecutive edges with everything asserted are four completed phases, and STB_O never moved.
Two consequences follow immediately.
Counting STB_O rising edges does not count transfers in a block cycle — it counts groups of back-to-back phases, which is a different number and sometimes 1.
And counting clocks where ACK_I is high does not count terminations either. PERMISSION 3.35 allows a slave to hold ACK_O asserted on a point-to-point interface with a single always-zero-wait slave, and RULE 3.55 requires masters to tolerate exactly that. Transfer-level accounting is the only kind that survives both permissions, which is why the probe in Section 4 counts boundaries.
4. RTL — One Master, Two Groupings
// ─────────────────────────────────────────────────────────────────────────
// wb_seq_master — moves LENGTH words, either as LENGTH separate bus cycles
// or as one BLOCK cycle containing LENGTH phases.
//
// ONE MASTER, ONE PARAMETER. Chapters 14.1 and 14.2 compare the two
// groupings, and the comparison is only worth anything if nothing else
// differs — so BLOCK selects the grouping and every other line is shared.
//
// WHAT THE SPECIFICATION CALLS THINGS. B3 Chapter 3's BLOCK section names
// the individual transfers PHASES: "these individual cycles (called phases)
// are combined together to form a single BLOCK cycle". It also states the
// per-phase obligation directly: "During each of the data transfer phases
// (within the block transfer), the normal handshaking protocol between
// [STB_O] and [ACK_I] is maintained."
//
// So a block cycle is NOT one large transfer. It is N transfers under one
// CYC_O, each presented and each terminated on its own.
//
// RULE 3.25 requires CYC_O for the duration of SINGLE, BLOCK and RMW
// cycles, asserted no later than the edge qualifying STB_O's assertion and
// negated no earlier than the edge qualifying its negation. PERMISSION 3.05
// allows CYC_O to be asserted indefinitely.
//
// STB_O IS HELD ACROSS BACK-TO-BACK PHASES, not pulsed. The normative BLOCK
// READ figure shows STB_O asserted across consecutive phases and dropping
// only where the master deliberately inserts a wait state ("MASTER negates
// [STB_O] to introduce a wait state"). A phase therefore ends at a clock
// edge where STB_O and ACK_I are both asserted — the boundary is the EDGE,
// not an STB_O pulse. GAP exists to produce the pulsed shape deliberately
// so Chapter 14.2 can measure what it costs.
//
// LENGTH SEMANTICS — LOCAL RTL POLICY, stated because it is the single most
// common source of off-by-one defects in block masters:
//
// LENGTH = NUMBER OF TRANSFERS, not a last index.
// LENGTH = 0 -> no cycle is begun at all
// LENGTH = 1 -> exactly one transfer
// LENGTH = 4 -> exactly four transfers
//
// The final-phase test is `beat_q == LENGTH - 1`, evaluated on a value that
// elaboration has already proved non-zero. wb_offbyone_master is this module
// with that one comparison changed.
//
// ADDRESS PROGRESSION IS THE MASTER'S, NOT THE PROTOCOL'S. Nothing in the
// Classic profile increments an address, and nothing requires block phases
// to be sequential — the specification says only that the master "presents
// new [ADR_O()]" for each phase. `adr_q + STRIDE` below is this master
// implementing a sequential stream, and USE_LIST replaces it with an
// arbitrary order to show the difference (Chapter 14.3).
//
// ADR IS A WORD ADDRESS. Established in Chapter 4.3 and used throughout
// Modules 12 and 13: on a 32-bit port with byte granularity the address
// array is ADR_O(n..2). STRIDE = 1 therefore advances one 32-bit word,
// which is four BYTE addresses. It is not 4.
// ─────────────────────────────────────────────────────────────────────────
module wb_seq_master #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32,
parameter int unsigned MAXLEN = 16,
parameter bit BLOCK = 1'b1, // 1 = one cycle, 0 = N cycles
parameter int unsigned GAP = 0, // idle clocks the master inserts
parameter int unsigned STRIDE = 1, // WORD stride
parameter bit USE_LIST = 1'b0, // take addresses from adr_list_i
localparam int unsigned CW = (MAXLEN <= 1) ? 1 : $clog2(MAXLEN + 1)
) (
input logic clk_i,
input logic rst_i,
// client request
input logic start_i,
input logic we_i,
input logic [AW-1:0] base_i,
input logic [CW-1:0] len_i, // NUMBER OF TRANSFERS
input logic [MAXLEN*32-1:0] adr_list_i, // used only when USE_LIST
output logic busy_o,
output logic done_o,
// Wishbone master port
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,
// observation only — not part of the Wishbone interface
output logic [CW-1:0] beat_o,
output logic [DW-1:0] rdat_o,
output logic err_seen_o
);
initial begin
if (MAXLEN == 0) $fatal(1, "wb_seq_master: MAXLEN must be non-zero");
if (STRIDE == 0) $fatal(1, "wb_seq_master: STRIDE must be non-zero");
end
typedef enum logic [1:0] { S_IDLE, S_XFER, S_GAP, S_DONE } state_e;
state_e state_q;
logic [CW-1:0] beat_q, len_q;
logic [AW-1:0] adr_q, base_q;
logic we_q;
logic [15:0] gap_q;
logic err_q;
logic [DW-1:0] rdat_q;
function automatic logic [AW-1:0] list_adr(input logic [CW-1:0] k);
return AW'(adr_list_i[32*int'(k) +: 32]);
endfunction
// A phase completes at a clock edge where the transfer is qualified and
// the slave has answered. This is the "boundary is the edge" statement in
// one line, and everything else in the state machine depends on it.
logic phase_done, last_phase;
assign phase_done = cyc_o && stb_o && (ack_i || err_i);
assign last_phase = (beat_q == len_q - CW'(1));
assign cyc_o = (state_q == S_XFER) || (BLOCK && (state_q == S_GAP));
assign stb_o = (state_q == S_XFER);
assign we_o = we_q;
assign adr_o = adr_q;
assign sel_o = '1; // whole-word transfers here
assign dat_o = we_q ? {16'hA500, 16'(beat_q)} : '0;
assign busy_o = (state_q != S_IDLE);
assign done_o = (state_q == S_DONE);
assign beat_o = beat_q;
assign rdat_o = rdat_q;
assign err_seen_o = err_q;
always_ff @(posedge clk_i) begin
if (rst_i) begin
state_q <= S_IDLE;
beat_q <= '0; len_q <= '0; adr_q <= '0; base_q <= '0;
we_q <= 1'b0; gap_q <= '0; err_q <= 1'b0; rdat_q <= '0;
end else begin
case (state_q)
S_IDLE: begin
err_q <= 1'b0;
if (start_i) begin
// ZERO-LENGTH POLICY: no transfers, no cycle, and done_o still
// pulses so a client is not left waiting for an answer that a
// bus cycle would have produced.
if (len_i == CW'(0)) begin
state_q <= S_DONE;
end else begin
len_q <= len_i;
we_q <= we_i;
base_q <= base_i;
beat_q <= '0;
adr_q <= USE_LIST ? list_adr(CW'(0)) : base_i;
state_q <= S_XFER;
end
end
end
S_XFER: begin
if (phase_done) begin
if (err_i) err_q <= 1'b1;
if (!we_q) rdat_q <= dat_i;
if (last_phase) begin
state_q <= S_DONE; // CYC_O and STB_O both drop
end else begin
beat_q <= beat_q + CW'(1);
adr_q <= USE_LIST ? list_adr(beat_q + CW'(1))
: adr_q + AW'(STRIDE);
if (BLOCK && (GAP == 0)) begin
// Back-to-back phases of one cycle: STB_O is never negated
// and the next phase begins on the very next edge.
state_q <= S_XFER;
end else begin
// Either the master is deliberately inserting wait states,
// or BLOCK is clear and this cycle must END so that the
// next transfer is a separate cycle. A separate cycle needs
// at least one clock with CYC_O low, so the gap is floored
// at 1 there.
gap_q <= BLOCK ? 16'(GAP)
: ((GAP == 0) ? 16'd1 : 16'(GAP));
state_q <= S_GAP;
end
end
end
end
// Master-inserted wait states. The specification's own mechanism:
// "MASTER negates [STB_O] to introduce a wait state (-WSM-)". With
// BLOCK set, CYC_O is retained here and only STB_O drops, which is
// what the normative figure shows. With BLOCK clear, both drop and
// the next transfer is a new cycle.
S_GAP: begin
if (gap_q <= 16'd1) state_q <= S_XFER;
else gap_q <= gap_q - 16'd1;
end
S_DONE: state_q <= S_IDLE;
default: state_q <= S_IDLE;
endcase
end
end
endmoduleReading it
phase_done is the phase boundary written once, and every other decision in the state machine depends on it. cyc_o && stb_o && (ack_i || err_i) — no STB_O edge detection anywhere, because the boundary is not an edge of STB_O.
BLOCK is the only parameter that separates the two cases, which is what makes Sections 5 and 6 a measurement rather than a comparison of two programs. With it clear, the master falls into S_GAP with a floor of one clock so that CYC_O genuinely drops and the next transfer is a new cycle.
LENGTH is a count, and the final test is beat_q == len_q - 1. The subtraction is safe because the zero case has already been diverted — len_i == 0 goes straight to S_DONE and begins no cycle. That is LOCAL RTL POLICY, chosen so a zero-length request cannot wrap a counter into a full-range block, and Section 7 sweeps it.
adr_q + STRIDE is the master implementing a stream. Nothing in the Classic profile increments an address; the specification says only that the master "presents new ADR_O()" for each phase. Chapter 14.3 runs a legal block where this line is replaced and the addresses go backwards.
STRIDE = 1 advances one word. ADR is a word address on this port — Chapter 4.3 and Module 12 — so one step here is four byte addresses. It is not + 4.
5. RTL — The Slave, and the Instrument
The slave is the part worth reading twice, because of what it does not contain.
// ─────────────────────────────────────────────────────────────────────────
// wb_block_ram — a small synchronous RAM that answers one phase at a time.
//
// THE POINT OF PUBLISHING IT IS THAT IT CONTAINS NOTHING ABOUT BLOCKS.
// There is no beat counter, no length input, no notion of a sequence and no
// use of CYC_I beyond qualification. It answers a qualified transfer and
// then answers the next one.
//
// That is not a simplification — it is what the Classic profile permits. A
// slave is never told how many phases are coming, and PERMISSION 3.55 even
// allows an interface not to support BLOCK cycles at all. A correct
// single-transfer slave already serves a block cycle, because from its side
// a block cycle is a sequence of qualified transfers.
//
// WAIT STATES. The specification's mechanism for a slave to throttle is to
// withhold its termination: "SLAVE negates [ACK_I] to introduce a wait
// state". WAITS sets a fixed number; VAR_WAITS instead takes the wait count
// from the low bits of the address, which lets Chapter 14.4 give different
// phases of one block different latencies without changing anything else.
//
// The wait counter is armed when a transfer is first seen and disarmed when
// it is answered, so a held STB_O across back-to-back phases produces one
// countdown per phase rather than one for the whole cycle.
// ─────────────────────────────────────────────────────────────────────────
module wb_block_ram #(
parameter int unsigned OFF_AW = 8, // local WORD offset width
parameter int unsigned DW = 32,
parameter int unsigned DEPTH = 64,
parameter int unsigned WAITS = 0, // fixed wait states per phase
parameter bit VAR_WAITS = 1'b0, // waits = adr_i[1:0] instead
localparam int unsigned IW = (DEPTH <= 1) ? 1 : $clog2(DEPTH)
) (
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 [IW-1:0] index_o,
output int unsigned writes_o,
output int unsigned reads_o
);
initial begin
if (DEPTH == 0) $fatal(1, "wb_block_ram: DEPTH must be non-zero");
end
logic [DW-1:0] mem [0:DEPTH-1];
logic [IW-1:0] index;
logic xfer, in_range, ready;
// clocks this phase has already been presented WITHOUT being answered.
// Zero on the first clock of a phase, so WAITS = 0 answers immediately.
logic [7:0] held_q;
int unsigned nw_q, nr_q;
assign xfer = cyc_i && stb_i;
assign index = adr_i[IW-1:0];
assign in_range = (32'(adr_i) < 32'(DEPTH));
// How many wait states this particular phase should take. With VAR_WAITS
// the count comes from the address, so consecutive phases of one block
// get different latencies without anything else changing.
logic [7:0] want_waits;
assign want_waits = VAR_WAITS ? 8'(adr_i[1:0]) : 8'(WAITS);
// The phase is answered once it has been presented for want_waits clocks
// beyond the first. This is the specification's slave-side mechanism:
// "SLAVE negates [ACK_I] to introduce a wait state" - the termination is
// simply withheld until the slave is ready.
assign ready = xfer && (held_q >= want_waits);
assign ack_o = ready && in_range;
assign err_o = ready && !in_range;
always_comb begin
dat_o = '0;
if (ready && in_range && !we_i) dat_o = mem[index];
end
always_ff @(posedge clk_i) begin
if (rst_i) begin
for (int unsigned k = 0; k < DEPTH; k++) mem[k] <= '0;
held_q <= 8'd0; nw_q <= 0; nr_q <= 0;
end else begin
// Count the clocks this phase has waited. Clearing on the answer is
// what gives the NEXT phase its own full latency even though STB_O
// never dropped between them - the boundary is the answered edge.
if (!xfer || ready) held_q <= 8'd0;
else held_q <= held_q + 8'd1;
if (ready && in_range) begin
if (we_i) begin mem[index] <= dat_i; nw_q <= nw_q + 1; end
else begin nr_q <= nr_q + 1; end
end
end
end
assign index_o = index;
assign writes_o = nw_q;
assign reads_o = nr_q;
endmoduleReading it
There is no beat counter, no length input, and no use of CYC_I beyond qualification. The slave answers a qualified transfer and then answers the next one.
That is not a simplification of a real slave — it is what the profile permits. PERMISSION 3.55 even allows an interface to be designed so that it does not support BLOCK cycles at all. A correct single-transfer slave already serves a block cycle, because from its side a block cycle is a sequence of qualified transfers.
held_q is what makes back-to-back phases work. It counts clocks this phase has gone unanswered, and clearing it on the answer gives the next phase its own full latency even though STB_O never dropped between them. A slave that armed on an STB_O edge would insert its wait states once for the whole cycle.
And the instrument counts boundaries, for the reason Section 3 gave:
// ─────────────────────────────────────────────────────────────────────────
// wb_block_probe — transfer-level accounting for a block cycle.
//
// TERMINATION IS COUNTED AT PHASE BOUNDARIES, NOT BY ACK-HIGH CLOCKS, and
// the distinction is not pedantry. PERMISSION 3.35 allows a slave to hold
// ACK_O asserted on a point-to-point interface with a single always-zero-
// wait slave, and RULE 3.55 requires masters to tolerate exactly that. A
// counter that incremented on "ACK is high" would report a number with no
// relationship to the number of transfers.
//
// What defines a phase boundary is a clock edge at which the transfer is
// qualified (CYC_I and STB_I) and the slave has answered. That is the same
// condition the master uses to advance, so the two agree by construction
// rather than by coincidence.
//
// Everything here is simulation instrumentation. `int unsigned` counters
// are not synthesizable constructs and none of this is a Wishbone feature —
// a system that exposes none of it can report only that a block took a
// while.
// ─────────────────────────────────────────────────────────────────────────
module wb_block_probe (
input logic clk_i,
input logic rst_i,
input logic cyc_i,
input logic stb_i,
input logic we_i,
input logic ack_i,
input logic err_i,
output int unsigned cycles_o, // CYC_O rising edges = bus cycles begun
output int unsigned phases_o, // phase boundaries = transfers
output int unsigned acks_o,
output int unsigned errs_o,
output int unsigned cyc_clocks_o, // clocks with CYC_O asserted
output int unsigned stb_clocks_o, // clocks presenting a transfer
output int unsigned wait_clocks_o, // presented, not yet answered
output int unsigned gap_clocks_o // CYC held, nothing presented
);
logic cyc_q;
always_ff @(posedge clk_i) begin
if (rst_i) begin
cyc_q <= 1'b0;
cycles_o <= 0; phases_o <= 0; acks_o <= 0; errs_o <= 0;
cyc_clocks_o <= 0; stb_clocks_o <= 0;
wait_clocks_o <= 0; gap_clocks_o <= 0;
end else begin
cyc_q <= cyc_i;
if (cyc_i && !cyc_q) cycles_o <= cycles_o + 1;
if (cyc_i) cyc_clocks_o <= cyc_clocks_o + 1;
if (cyc_i && stb_i) stb_clocks_o <= stb_clocks_o + 1;
if (cyc_i && !stb_i) gap_clocks_o <= gap_clocks_o + 1;
if (cyc_i && stb_i && !(ack_i || err_i))
wait_clocks_o <= wait_clocks_o + 1;
if (cyc_i && stb_i && (ack_i || err_i)) begin
phases_o <= phases_o + 1;
if (ack_i) acks_o <= acks_o + 1;
if (err_i) errs_o <= errs_o + 1;
end
end
end
endmodule6. Simulation — SIM A: Four Separate Cycles
Four words written from word address 8, each as its own bus cycle, against a zero-wait RAM.
=== SIM A - four transfers, four separate cycles ===
write 4 words from word address 8, zero-wait RAM.
bus cycles begun 4
transfers (phases) 4
ACK terminations 4
clocks with CYC high 4
clocks presenting 4
clocks CYC high, idle 0
elapsed clocks 8
RAM words written 47. Simulation — SIM B: The Same Four Words, One Cycle
Identical master, identical RAM, identical request. BLOCK is the only difference.
=== SIM B - the same four transfers, one block cycle ===
identical master, identical RAM. BLOCK is the only change.
bus cycles begun 1
transfers (phases) 4
ACK terminations 4
clocks with CYC high 4
clocks presenting 4
clocks CYC high, idle 0
elapsed clocks 5
RAM words written 4
ONE cycle, FOUR terminations. Grouping the transfers did
not merge them - each phase was still presented and still
answered on its own, which is what the specification
means by maintaining the STB/ACK handshake per phase.Four separate cycles, then four phases of one
10 cyclesRows A pulse once per transfer. Rows B do not pulse at all — CYC_O, STB_O and ACK_I are asserted once and held, and four phases complete underneath them.
Every clock in B where all three are high is one completed phase. That is the boundary rule from Section 3 drawn out: nothing transitions between phase 1 and phase 2, and the only signal that changes is the address.
This is why STB_O edges cannot count transfers. B has one rising edge and four phases.
Reading the two together
| SIM A | SIM B | |
|---|---|---|
| bus cycles begun | 4 | 1 |
| transfers (phases) | 4 | 4 |
| ACK terminations | 4 | 4 |
| clocks presenting | 4 | 4 |
| elapsed clocks | 8 | 5 |
| RAM words written | 4 | 4 |
One cycle and four terminations. That row is the chapter. Grouping the transfers did not merge them — each phase was presented and answered on its own, which is what the specification means by maintaining the STB_O/ACK_I handshake per phase.
The termination count did not change and could not have. There is no mechanism by which one ACK_I ends four transfers; the slave answered each qualified transfer it saw, and it saw four.
Both runs present for exactly four clocks. Each is the ordinary write of Chapter 8.2, repeated. The work is the same work. The three clocks SIM A spends that SIM B does not are the cycle boundaries — CYC_O falling and rising again between transfers, three times.
And the RAM is byte-identical after both. Same addresses, same data, same final state. The grouping is invisible in the result and visible only in the timing and the cycle count — which is exactly why Chapter 14.2 has to measure rather than assert.
8. Failure Modes and Discriminating Evidence
Symptom: a four-word block writes only three words.
Candidate causes. A final-phase test comparing against length - 2, or a counter that starts at 1, or CYC_O dropped a phase early.
Discriminating evidence. The termination count against the requested length. Three terminations for four requested means the master stopped early; the address of the missing word says where. Chapter 14.4 measures the opposite error, and the two are distinguished by which side of the requested count the observed one falls.
Likely RTL location: the final-phase comparison.
Symptom: the same address is written repeatedly.
Candidate causes. The phase index advances and the address does not.
Discriminating evidence. The phase index against ADR at each boundary. An index that climbs while the address stands still separates the two counters immediately — they are different registers, and only one of them is broken.
Symptom: one address in the sequence is skipped.
Candidate causes. The address advances on presentation and again on termination.
Discriminating evidence. ADR before and after each ACK_I. If it moves at both, it is advancing twice per phase. The signature is that exactly half the intended addresses appear, which is different from an off-by-one at the end.
Symptom: a block stalls on one phase and never completes.
Candidate causes. A slave withholding its termination, STB_O lost, or metadata changed mid-phase so the slave is answering a different transfer than the master thinks.
Discriminating evidence. CYC_O, STB_O, ADR and the phase index together during the stall. All stable with no termination is a slow or stuck slave — the extension Chapter 9.4 described, now happening inside a block — Module 9 and Chapter 10.4. Metadata moving during the stall is the master's fault, and P3 in Section 9 forbids it.
Symptom: a block-capable master works against one slave and hangs on another.
Candidate causes. The second slave does not support BLOCK cycles.
Discriminating evidence. Whether the slave answers the first phase and not the second. PERMISSION 3.55 makes BLOCK support optional, so this is a legitimate slave and an integration error. A datasheet should have said so under RULE 2.15.
9. Verification
// ─────────────────────────────────────────────────────────────────────────
// wb_block_props — what a block cycle promises, and who promises it.
//
// NOTE ON EXECUTION: Icarus Verilog does not support SVA. These properties
// were reviewed by inspection and are NOT claimed to have been executed.
// The numbers in this module come from procedural checks, which Icarus does
// run — the transfer-count audit is P5 and P8 in executable form, and
// SIM I's stability counter is P3.
//
// Each property is labelled. Only two of these are SPEC-DERIVED, and that
// ratio is the honest one: the Classic profile constrains CYC_O's lifetime
// and the qualification of the payload, and leaves almost everything else
// about a block to the master.
// ─────────────────────────────────────────────────────────────────────────
module wb_block_props #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32,
parameter int unsigned CW = 5
) (
input logic clk_i,
input logic rst_i,
input logic cyc_i,
input logic stb_i,
input logic ack_i,
input logic err_i,
input logic we_i,
input logic [AW-1:0] adr_i,
input logic [DW-1:0] dat_i,
input logic [DW/8-1:0] sel_i,
input logic [CW-1:0] beat_i,
input logic [CW-1:0] len_i,
input logic busy_i,
input logic start_i
);
default disable iff (rst_i);
// A phase boundary. Everything below is expressed against this rather
// than against an STB_O edge, because the normative BLOCK figure holds
// STB_O asserted across back-to-back phases — the boundary is the
// answered clock edge, not a pulse.
logic phase_end;
assign phase_end = cyc_i && stb_i && (ack_i || err_i);
// P1 — SPEC-DERIVED. RULE 3.25: "MASTER interfaces MUST assert [CYC_O]
// for the duration of SINGLE READ / WRITE, BLOCK and RMW cycles", with
// CYC_O negated no earlier than the edge qualifying STB_O's negation.
// Stated here as: a presented transfer always carries CYC_O.
property p_cyc_spans_stb;
@(posedge clk_i) stb_i |-> cyc_i;
endproperty
a_cyc_spans_stb: assert property (p_cyc_spans_stb);
// P2 — SPEC-DERIVED. RULE 3.60 qualifies ADR_O, DAT_O(), SEL_O(), WE_O
// and the tags with STB_O. A master that changed them while STB_O was
// asserted and unanswered would be re-aiming a transfer already in
// flight — which is the defect Chapter 8.3 measured as early address
// advance, and which P3 forbids directly.
property p_payload_qualified;
@(posedge clk_i) (cyc_i && stb_i) |-> !$isunknown({adr_i, sel_i, we_i});
endproperty
a_payload_qualified: assert property (p_payload_qualified);
// P3 — LOCAL MASTER POLICY. Metadata holds still for the whole of a
// phase. The specification obliges the master to qualify these with
// STB_O; holding them STABLE across a slave's wait states is what makes
// the transfer identifiable, and SIM I measures it.
property p_metadata_stable;
@(posedge clk_i) (cyc_i && stb_i && !(ack_i || err_i)) |=>
(cyc_i && stb_i) -> ($stable(adr_i) && $stable(we_i) &&
$stable(sel_i) && $stable(beat_i));
endproperty
a_metadata_stable: assert property (p_metadata_stable);
// P4 — LOCAL MASTER POLICY. The phase index advances only at a phase
// boundary. This is the "advance on termination" rule; a master that
// advanced on presentation would skip an address.
property p_advance_on_termination;
@(posedge clk_i) (busy_i && !phase_end) |=> $stable(beat_i);
endproperty
a_advance_on_termination: assert property (p_advance_on_termination);
// P5 — LOCAL MASTER POLICY, and the one wb_offbyone_master violates.
// No phase is presented once the final one has been answered. Expressed
// as: the last phase's boundary is followed by STB_O low.
property p_no_extra_phase;
@(posedge clk_i) (phase_end && (beat_i == len_i - CW'(1))) |=> !stb_i;
endproperty
a_no_extra_phase: assert property (p_no_extra_phase);
// P6 — LOCAL MASTER POLICY. CYC_O does not end before the final phase is
// answered. The mirror of P5: one forbids a transfer too many, this
// forbids ending one too early.
property p_cyc_until_final;
@(posedge clk_i) (cyc_i && stb_i && !(ack_i || err_i)) |=> cyc_i;
endproperty
a_cyc_until_final: assert property (p_cyc_until_final);
// P7 — LOCAL MASTER POLICY, sequential stream only.
// After a non-final phase, the next address is the previous plus the
// stride. THIS IS NOT A SPECIFICATION REQUIREMENT — nothing in the
// Classic profile requires block addresses to be sequential, and SIM F
// runs a legal block in which this property is deliberately false.
// A property true of one master and false of another must say so.
property p_sequential_progression;
@(posedge clk_i) (phase_end && (beat_i != len_i - CW'(1))) |=>
(adr_i == $past(adr_i) + AW'(1));
endproperty
a_sequential_progression: assert property (p_sequential_progression);
// P8 — LOCAL RTL POLICY. A zero-length request begins no cycle.
property p_zero_length_no_cycle;
@(posedge clk_i) (start_i && (len_i == CW'(0))) |=> !cyc_i;
endproperty
a_zero_length_no_cycle: assert property (p_zero_length_no_cycle);
endmoduleTwo of these are specification-derived and eight are not, and that ratio is the honest picture. The Classic profile constrains CYC_O's lifetime (P1, RULE 3.25) and the qualification of the payload (P2, RULE 3.60). Everything else about a block — how long, in what order, when to stop — is the master's.
P7 is written with its falsity attached. Sequential progression is true of this master and deliberately false of the one Chapter 14.3 runs, which is equally conformant. A property that holds for one design and not another has to say which.
P5 and P6 are a pair. One forbids a transfer too many, the other forbids ending one too early. Neither alone pins the end of the block, and Chapter 14.4 measures a master that fails the first.
10. Common Mistakes
"A block transfer is one transfer containing many words."
Wrong mental model: the block is the unit.
What is true: the phase is the unit. SIM B: one cycle, four terminations. The specification calls the parts phases and keeps the STB_O/ACK_I handshake on every one of them.
"One ACK completes the whole block."
Wrong mental model: termination scales with the cycle.
What is true: termination belongs to a transfer. Four transfers need four terminations, and there is no signal that says "all of it is done" — the cycle ends when the master negates STB_O and CYC_O.
"Keeping CYC_O high makes the bus faster automatically."
Wrong mental model: the grouping is a speed feature.
What is true: it removes cycle boundaries and nothing else. Measured here as three clocks over four transfers, and Chapter 14.2 shows how small that is next to slave latency. The spec's stated motivation is arbitration.
"A block cycle is atomic."
Wrong mental model: tenure is exclusivity.
What is true: LOCK_O provides uninterruptibility; CYC_O does not. The spec's own text: "To hold the access until the end of the cycle the LOCK_O signal must be asserted." Grouping alone guarantees nothing about other masters.
"Count STB_O rising edges to count transfers."
Wrong mental model: one transfer, one pulse.
What is true: STB_O is held across back-to-back phases in the normative figure. Four phases can occur with one rising edge. Count phase boundaries — qualified clocks with a termination.
"Count clocks where ACK_I is high to count terminations."
Wrong mental model: ACK is per-transfer by construction.
What is true: PERMISSION 3.35 lets a slave hold ACK_O asserted, and RULE 3.55 makes masters tolerate it. The count would be meaningless on exactly the interfaces where it is easiest to build.
11. Interview Reasoning
A cycle is a tenure; a transfer is a unit of data movement. One cycle may contain many transfers.
The cycle is delimited by CYC_O. RULE 3.25 requires it for the duration of SINGLE, BLOCK and RMW cycles, and PERMISSION 3.05 allows it to be asserted indefinitely. It is addressed to an arbiter — the specification's stated use is letting an arbiter for a shared memory know when one master is finished.
The transfer — the specification calls it a phase inside a block — is delimited by the handshake. Presented with STB_O, terminated by ACK_I, ERR_I or RTY_I — the same contract Chapter 5.8 set out for a single transfer. Address, data, SEL and WE belong to the transfer, not the cycle.
The measurement that makes it concrete: four words in one cycle produce one CYC_O assertion and four terminations. Grouping changed the first number and could not change the second.
A strong answer adds what the cycle does not provide: exclusivity is LOCK_O's job, and nothing about a block cycle makes it atomic.
12. Understanding Check
Because SIM A paid for three cycle boundaries that SIM B did not.
Both runs present for exactly four clocks. The transfers themselves cost the same — four presentations, four terminations, a zero-wait RAM answering each one immediately.
SIM A also drops CYC_O and raises it again between transfers, three times. Those clocks are not moving data; they are ending one tenure and beginning the next.
8 clocks against 5. The difference is three, and three is the number of boundaries between four transfers.
Which makes the saving predictable and bounded. It is one per avoided boundary, no matter how long the transfers take — and Chapter 14.2 shows what happens to that saving as a fraction when the slave gets slower.
13. What's Next
The model is in place: a cycle groups phases, each phase is presented and terminated on its own, and CYC_O is a statement of tenure rather than of length, order or exclusivity.
Grouping saved three clocks out of eight here. That is a specific number under specific conditions, and neither the number nor the conditions generalise on their own.
What does grouping actually buy, and what does it leave completely untouched?
Chapter 14.2 — Throughput Improvements measures the same four transfers across three slave latencies in both groupings, and finds the saving is a constant while the latency is a multiplier. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
CYC — Cycle
CYC frames the bus cycle a transfer lives inside. It must be asserted no later than the edge qualifying STB and must outlast the transfer — and a master that drops it early destroys a transaction every other signal says is fine.
- Related topic
CPU to Peripheral Communication
A CPU reaches hardware outside itself by reading and writing addressed locations, and a peripheral is hardware it cannot execute. Everything a driver does has to be expressed as a read or a write of a location the peripheral answers for — and once more than a couple of peripherals exist, wiring each one to the core separately stops scaling. That is the problem an on-chip bus is the answer to.
- Related topic
Need for Standardized Interconnects
An address map answers where a register lives. It says nothing about which wires carry the request, when they are valid, how the target reports completion, or what happens on an error. Three peripherals with three private interfaces produce three adapters, three verification efforts and three ways to be wrong — which is the argument for standardising the interface rather than the map.
- Related topic
FPGA Design Challenges
Six peripherals and two masters on one FPGA is where on-chip integration stops being theory. The decode that must be exhaustive and one-hot, the read multiplexer that grows with every target and carries the critical path, the latency and reset conventions that refuse to agree, and the point at which the fabric rather than the peripherals starts failing timing.
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.
