Wishbone · Module 8
Performance Considerations
A block cycle saves exactly N-1 clocks over N single cycles, independent of slave latency — and that saving vanishes if the master throttles. Measured, including the latency it costs every other master.
Every chapter in this module has reported waits, gaps and clocks, and none has used them for anything.
Given a choice of cycle type, what does each one actually cost?
1. Where the Clocks Go
Before measuring anything, decompose the cost, because "how long did it take" has four independent answers and they are attributable to different owners.
A bus cycle occupies some number of clocks with CYC_O asserted. Every one of them is exactly one of:
| Clock kind | Condition | Whose cost |
|---|---|---|
| Transfer | STB_O high, terminated | irreducible — the data moved |
| Slave wait | STB_O high, not terminated | the slave's |
| Master gap | STB_O low, CYC_O high | the master's |
| Turnaround | CYC_O low between two cycles | the cycle boundary's |
The monitor from Chapter 8.1 reports the first three directly as transfers, waits and gaps. The fourth is invisible to it, because it happens when no cycle is in progress — and the fourth is what this chapter is about.
Why a turnaround clock is unavoidable between two single cycles. Two bus cycles are two rising edges of CYC_O. For a second rising edge to exist, CYC_O must be low at some rising clock edge in between. A minimum of one clock, and it is structural, not a design weakness.
So the hypothesis to test is narrow and falsifiable: a block of N transfers should cost N−1 turnaround clocks less than N single cycles, and nothing else should differ.
2. RTL — The Control
To measure the difference, one thing must vary. This master is Chapter 8.3's block master with the block removed.
// wb_single_repeat_master — move N words as N SEPARATE single cycles.
//
// The control against which Chapter 8.3's block master is measured. Same
// addresses, same transfers, same slave: the ONLY difference is that this
// master ends its bus cycle after every transfer and starts a new one.
//
// S_TURN is not padding. Two bus cycles are two rising edges of CYC_O, and
// CYC_O must be negated for at least one rising edge in between or there is
// only one cycle. This state IS the cost of a cycle boundary.
// ─────────────────────────────────────────────────────────────────────────
module wb_single_repeat_master #(
parameter int unsigned AW = 30,
parameter int unsigned DW = 32,
parameter int unsigned CW = 8
) (
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
);
typedef enum logic [1:0] { S_IDLE, S_XFER, S_TURN } state_e;
state_e state_q;
logic [AW-1:0] adr_q;
logic [CW-1:0] left_q;
logic [DW-1:0] dat_q;
logic we_q, terminated;
// One transfer per cycle, so PERMISSION 3.40's shortcut applies: this
// master inserts no wait states of its own and CYC_O could share a wire
// with STB_O. They are written separately for comparability.
assign cyc_o = (state_q == S_XFER);
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;
assign busy_o = (state_q != S_IDLE);
assign terminated = cyc_o && stb_o && (ack_i || err_i || rty_i);
always_ff @(posedge clk_i) begin
if (rst_i) begin
state_q <= S_IDLE; 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;
case (state_q)
S_IDLE: if (req_i && (req_len_i != '0)) begin
adr_q <= req_base_i; left_q <= req_len_i; we_q <= req_we_i;
dat_q <= req_dat_i; xfers_o <= '0; state_q <= S_XFER;
end
S_XFER: if (terminated) begin
xfers_o <= xfers_o + CW'(1);
if (left_q == CW'(1)) begin
state_q <= S_IDLE; done_o <= 1'b1;
end else begin
left_q <= left_q - CW'(1);
adr_q <= adr_q + AW'(1);
state_q <= S_TURN; // CYC_O negates here
end
end
S_TURN: state_q <= S_XFER; // one clock, then a NEW bus cycle
default: state_q <= S_IDLE;
endcase
end
end
endmoduleThe comparison is honest because the difference is one state. wb_block_master advances to S_XFER; this one advances to S_TURN and thence to S_XFER. Address sequence, transfer count, direction and slave are identical.
3. Waveform — The Clocks You Are Paying For
Four single cycles: three turnaround clocks
10 cyclesFour transfers, seven clocks. Cycles 1, 3, 5 and 7 each move a word. Cycles 2, 4 and 6 move nothing — CYC_O is low, no cycle is in progress, and the bus is idle.
Those three clocks are the entire subject of this chapter. A block cycle removes them and changes nothing else: same four addresses, same four acknowledges, same zero-wait-state slave.
Compare against Chapter 8.3's Figure 1, which moved four words under one cycle. CYC_O never fell. The block's cost was 4 clocks; this is 7.
4. Simulation — SIM K: The Measurement
Same N, same slave, same addresses. m-xfer/clk is transfers per clock × 1000, so 1000 means one transfer every clock.
=== SIM K - N transfers: N single cycles vs 1 block cycle ===
N wait singles block saved singles block
st clks cyc clks cyc clks m-xfer/clk
2 0 3 2 2 1 1 666 1000
4 0 7 4 4 1 3 571 1000
8 0 15 8 8 1 7 533 1000
16 0 31 16 16 1 15 516 1000
32 0 63 32 32 1 31 507 1000
2 1 5 2 4 1 1 400 500
4 1 11 4 8 1 3 363 500
8 1 23 8 16 1 7 347 500
16 1 47 16 32 1 15 340 500
32 1 95 32 64 1 31 336 500
2 3 9 2 8 1 1 222 250
4 3 19 4 16 1 3 210 250
8 3 39 8 32 1 7 205 250
16 3 79 16 64 1 15 202 250
32 3 159 32 128 1 31 201 250Read the saved column first. It is N - 1 on every row. Two transfers save 1 clock, thirty-two save 31 — and the slave's latency does not change it at all. Three wait states per transfer inflate both sides equally and leave the difference untouched.
That confirms the Section 1 hypothesis exactly. The block removes turnaround clocks and nothing else.
The formula, read off the measurement
singles = N x (L + 1) + (N - 1)
block = N x (L + 1)where L is the slave's wait states per transfer. Check it against a row: N = 8, L = 3 gives 8 x 4 + 7 = 39 and 8 x 4 = 32. Measured: 39 and 32.
So the per-transfer cost of using single cycles is exactly one extra clock, forever. It does not amortise with N — which is why the singles throughput column converges to 500, 333 and 200 rather than climbing toward the block's 1000, 500 and 250.
The ratio is largest when the slave is fastest
This is the result that reverses most people's intuition. Dividing the two throughput columns at large N:
| Slave wait states | Block throughput | Singles throughput | Block is |
|---|---|---|---|
| 0 | 1.000 xfer/clk | 0.507 | 1.97× |
| 1 | 0.500 | 0.336 | 1.49× |
| 3 | 0.250 | 0.201 | 1.24× |
The asymptotic ratio is (L+2)/(L+1) — 2.00, 1.50 and 1.25 for these three rows, which the measurements approach from below as N grows.
So a block cycle is worth most against a fast slave and least against a slow one. The fixed overhead it removes is one clock per transfer; the more clocks the slave already spends, the smaller a fraction that is.
The practical reading. Blocking transfers to an on-chip SRAM that answers in one clock nearly doubles throughput. Blocking transfers to a peripheral that takes four clocks per access buys about 20% — often not worth the state machine.
In absolute terms, at 100 MHz on a 32-bit port: a zero-wait block sustains 400 MB/s against 203 MB/s for singles; a three-wait block sustains 100 MB/s against 80 MB/s. The same protocol change is worth 197 MB/s in one case and 20 MB/s in the other.
5. SIM K(b) — When the Advantage Disappears Entirely
The block master has a gap_en_i input that makes it insert one master-side gap per transfer — a master that cannot supply or absorb a word every clock.
=== SIM K(b) - the master's own throttle, LAT=0 ===
N= 4 block, no gaps 4 clks block, 1 gap per transfer 7 clks (+3)
N= 8 block, no gaps 8 clks block, 1 gap per transfer 15 clks (+7)
N=16 block, no gaps 16 clks block, 1 gap per transfer 31 clks (+15)Compare those right-hand numbers against SIM K's singles column at the same N and latency: 7, 15, 31. They are identical.
A block cycle whose master inserts one gap per transfer costs exactly what N single cycles cost. The N−1 clocks saved at the cycle boundaries are spent again inside the cycle.
This is the condition nobody states. The block's advantage is not a property of the cycle type — it is a property of the master being able to accept or supply a word every clock. A master fed by a FIFO that is empty half the time gets nothing from blocking, and pays the cost in Section 6.
And it explains the two counters this module kept separate. waits and gaps both lengthen a cycle. Only gaps tells you the throughput gain you thought you were buying has been spent — which is why attributing a pause to the right side of the bus is a performance question and not only a debugging one.
6. SIM L — What It Costs Everyone Else
Throughput is not the only number. A block cycle holds the bus, and something else usually wants it.
Two masters share one slave through the honouring arbiter from Chapter 8.4. Master 0 moves N words. Master 1 wants a single transfer and requests it on the same clock.
=== SIM L - what master 0's cycle costs master 1 ===
master 0 moves N words; master 1 wants ONE transfer,
requested on the same clock. Arbiter honours CYC_I.Read the two columns as functions of N. Against single cycles, master 1's wait is a small constant — 1 to 3 clocks — and does not grow with N, because the arbiter gets a chance to switch at every cycle boundary. Against a block, master 1 waits for the whole block, and its wait grows linearly.
At N = 16 with one wait state: 2 clocks versus 35. The same work, the same total throughput for master 0, and a seventeen-fold difference in the other master's latency.
So blocking is a transfer of latency, not a free win. Master 0 finishes sooner; everyone else finishes later. If master 1 is a CPU servicing an interrupt, those 35 clocks are the interrupt latency, and no amount of master-0 throughput compensates.
The small irregularity in the singles column is real and worth not smoothing over. N = 2 at zero waits measures 3 clocks where N = 4, 8 and 16 measure 1 — an artifact of where the round-robin grant happened to be when both masters requested. The point the table supports is the scaling, not the constant: one column is bounded, the other is not.
7. The Specification's Own Tension
Wishbone does not resolve this tradeoff, and it is unusually direct about not resolving it. Two items, both about holding CYC_O, pointing opposite ways:
PERMISSION 3.05 — MASTER interfaces MAY assert
CYC_Oindefinitely.RECOMMENDATION 3.05 — arbitration logic often uses
CYC_Ito select between MASTER interfaces. KeepingCYC_Oasserted may lead to arbitration problems.
The permission says you may. The recommendation says be careful. Neither is a bound, and there is no rule anywhere that limits block length.
This is not an oversight. Wishbone specifies interfaces, not systems — and the right block length depends on the arbiter, the other masters and the latency budget, none of which an IP core can know. The specification pushes the decision to the integrator explicitly rather than picking a number that would be wrong for most systems.
Which makes it your number to choose and to write down. SIM L is what choosing it looks like.
8. A Decision Procedure
From the measurements, in the order that resolves fastest.
Step 1 — can the master sustain one transfer per clock? If not, stop: blocking buys nothing (SIM K(b)). Fix the data path first, or use single cycles and keep the latency profile.
Step 2 — what is the slave's latency? At 0 wait states blocking approaches 2× (SIM K). At 3 or more it is under 1.25×, and the extra state machine, the extra properties and the extra failure modes from Chapter 8.3 may not be worth it.
Step 3 — what is the worst-case latency the rest of the system tolerates? A block of N costs every other master up to the block's full length (SIM L). Divide the budget by the cycle time to get a maximum N, and treat it as a hard cap.
Step 4 — is the bus shared at all? If the master has a private port, step 3 is free and blocking is limited only by steps 1 and 2.
Step 5 — is this an RMW? Then the question does not arise. An RMW's cycle length is 2 transfers and its purpose is not throughput — Chapter 8.4's measurement was 2 clocks, and the reason to hold the cycle is ownership.
The general shape of the answer. Throughput is bought with worst-case latency, at a rate this module can now quantify on any specific bus. That is the tradeoff; there is no configuration in which blocking is free.
9. Failure Modes and Discriminating Evidence
Symptom: a block transfer delivers no measured speed-up.
Candidate causes. Two, and one counter separates them.
Discriminating evidence. Read gaps on the master's port. Non-zero means the master is throttling and the turnaround saving is being spent inside the cycle (SIM K(b)). Zero, with a high waits, means the slave is slow and the ratio is genuinely near 1 — the expected result, not a bug.
Likely location: the master's data path — the FIFO, the client interface — not its bus logic.
Symptom: adding block transfers made an unrelated subsystem miss deadlines.
Candidate causes. Blocking transferred latency to other masters (SIM L).
Discriminating evidence. The victim's wait tracks the block length. Halve N and its worst case should roughly halve. If it does not, the block is not the cause.
Likely location: the block length chosen, or the absence of one — an unbounded block is the usual version of this.
Symptom: throughput is far below 1/(L+1) even with gaps = 0 and one cycle.
Candidate causes. The transfer count is not what you think. A master re-presenting a transfer, or a slave terminating twice, inflates the clock count without moving data.
Discriminating evidence. Compare the monitor's transfers against the words the master's client actually received. They should be equal.
Symptom: a design is fast in unit test and slow in the system.
Candidate causes. The unit test had one master, so no arbitration delay was measured.
Discriminating evidence. clocks with CYC_O asserted versus wall-clock from request to completion. A gap between them is time spent waiting for the grant, which is invisible on the master's own port — the same blind spot Chapter 8.4 §9 identified for atomicity.
10. Common Mistakes
"Block transfers are faster."
Wrong mental model: the cycle type determines the speed.
What is true: a block saves exactly N−1 clocks, and only if the master sustains one transfer per clock.
Concrete bug: a block master fed by a FIFO that supplies a word every other clock. Measured: identical to N single cycles — 7, 15 and 31 clocks for N = 4, 8, 16.
Observable evidence: gaps greater than zero on the master's port.
Correct model: "a block saves the cycle-boundary turnarounds, if the master can keep up." State the condition.
"Blocking helps most when the slave is slow."
Wrong mental model: slow slaves need the help more, so blocking must help more.
What is true: the opposite. The saving is a fixed one clock per transfer; the slower the slave, the smaller a fraction that is. Measured asymptotic ratios: 1.97× at zero wait states, 1.24× at three.
Concrete bug: investing in a block-capable master for a peripheral with four-clock accesses, for about 20%.
Observable evidence: the two throughput columns of SIM K converging as L grows.
Correct model: the gain is (L+2)/(L+1). Compute it before building.
"Longer blocks are always better."
Wrong mental model: amortise the overhead over more transfers.
What is true: throughput saturates and latency does not. Throughput is already within 2% of its limit by N = 8, while another master's worst-case wait keeps growing linearly — measured at 35 clocks for N = 16.
Concrete bug: an unbounded DMA block that starves an interrupt handler.
Observable evidence: SIM K's m-xfer/clk column flattening while SIM L's block column does not.
Correct model: choose N from the latency budget, and note that the throughput argument for large N runs out early.
"The bus is idle between single cycles, so nothing is lost."
Wrong mental model: an idle clock is free because nothing was waiting.
What is true: the turnaround clock is capacity that cannot be recovered. Four words take 7 clocks instead of 4 — a 43% throughput loss at zero wait states.
Concrete bug: a design that meets its throughput target on paper using transfer counts and misses it by nearly half in silicon.
Observable evidence: Figure 1 — three of seven clocks move nothing.
Correct model: budget in clocks with CYC_O asserted plus the turnarounds between cycles, not in transfers.
"Wait states and gaps are both just latency."
Wrong mental model: a pause is a pause.
What is true: they have different owners and different fixes. waits is the slave; gaps is the master. A slave-side pause is addressed by a faster slave or a different cycle type; a master-side pause is addressed in the master's data path, and it is the one that silently cancels a block's benefit.
Concrete bug: optimising the slave for months when gaps was the dominant term.
Observable evidence: the two counters, reported separately by the Chapter 8.1 monitor.
Correct model: attribute every non-transfer clock to a side before optimising anything.
11. Interview Reasoning
Yes, by a specific and rather small amount, under a condition that usually goes unstated — and at a cost to the rest of the system.
The mechanism. Classic B3 has no pipelining and no address phase, so a transfer costs the same in either cycle type. What a block removes is the turnaround clock between bus cycles — CYC_O has to be low at some rising edge for a second cycle to exist at all.
So the saving is exactly N−1 clocks for N transfers, and I measured that it is independent of the slave's latency: three wait states per transfer inflate both sides equally. singles = N(L+1) + (N-1) against block = N(L+1).
The ratio, which is the number that matters, is (L+2)/(L+1) — so 1.97× measured against a zero-wait slave and 1.24× against a three-wait slave. Blocking helps most when the slave is fast, which is the opposite of what people expect.
The condition nobody states. The master must sustain one transfer per clock. I measured a block master inserting one gap per transfer and it cost exactly what N single cycles cost — 7, 15 and 31 clocks for N = 4, 8, 16, identical to the single-cycle column. The turnaround savings are simply spent inside the cycle.
And the cost. A block holds the bus. With two masters and an arbiter that honours CYC_I, a competing single transfer waited 1–3 clocks against single cycles regardless of N, and 35 clocks against a block of 16. Throughput was bought with somebody else's latency.
What I would conclude. For an SRAM-like slave with a master that keeps up, blocking is close to a 2× win and worth it. For a slow peripheral, or a master that throttles, it is not worth the extra state machine — and either way the block length is a latency-budget decision, not a throughput one.
12. Understanding Check
13. What Module 8 Established
| Chapter | What it added |
|---|---|
| 8.1 | the cycle monitor; cycles = transfers = 1 is a coincidence, not a law |
| 8.2 | the direction-agnostic cycle skeleton; SINGLE READ / WRITE is one cycle type |
| 8.3 | many transfers under one cycle; the master's own throttle; advance on completion |
| 8.4 | direction changing inside a cycle; what "atomic" does and does not mean |
| 8.5 | the complete taxonomy, mechanised; every cycle type is optional |
| 8.6 | what each one costs, measured — and what it costs everyone else (this chapter) |
The module set out to separate two things that single transfers keep welded together: the transfer, marked by STB_O, and the bus cycle, marked by CYC_O.
They are now separate in every way this module can measure. A cycle can hold one transfer or many (8.1, 8.3). It can change direction inside itself (8.4). It can be paused from either end, and the counters say which (8.3, 8.6). Its boundaries cost clocks, and holding it costs other masters latency (8.6). And what it guarantees is narrower than its name suggests (8.4).
What Module 8 deliberately did not do. It treated the slave as a source of configurable delay and never asked what the slave was doing. Module 9 owns wait-state generation from the slave's side, Module 10 errors, Module 11 retry, Module 12 address decoding, Module 13 byte selects. Arbitration appeared here only as far as measuring it required — the interconnect has its own module.
One of the four clock kinds in Section 1 has been a given throughout. Slave wait states arrived when wb_perf_slave was told to produce them, and the cycle simply got longer. Nothing has asked why a slave needs them, what it is doing during them, or how a slave that needs them is built without hanging the bus.
Why does a slave need to delay a transfer at all, and how does it do so safely?
Module 9 — Wait States takes the slave's side of the handshake. It has not shipped yet, which is why it appears in bold rather than as a link. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
Throughput Improvements
The saving from retaining CYC is a constant; slave latency is a multiplier. Measured: 1.6x decaying to 1.18x with nothing in either design changing.
- Related topic
Throughput Issues
Diagnosing a real bandwidth shortfall on a link whose accounting is already correct — every transfer opportunity classified into exactly one mutually exclusive cause so the classes sum to the total, the denominator discipline that makes a percentage mean something, the credit window that produces a sawtooth without a leak, and the four bottlenecks that are not inside UCIe at all.
- Related topic
Shared Resources
Two initiators wired to one target is not a wiring problem with a wiring solution. A single-port target has one address input and one completion output, so access must be serialised — and the rule that matters most is not who goes first but that ownership cannot change while a transaction is in flight.
- Related topic
CYC_O
STB_O presents one transfer; CYC_O frames the tenure it belongs to. Holding it across transfers is what makes a read-modify-write atomic — and what atomicity still does not guarantee.
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.
