Skip to content
VLSI Mentor

Wishbone · Module 14

Memory Accesses

Six words checked against a reference model, four phases at four latencies, and a master performing five transfers when four were asked for — every one of them legal.

Chapter 14.3 settled the vocabulary. A block is a grouping; the master owns the sequence; nothing declares a length.

Which puts the entire burden of getting the count right on the master, with no help from the bus.

What does a block against real memory require, and what happens when the master counts its own transfers wrong?

1. The Memory Side of a Block

Chapter 12.5 established the address split; a block changes only how many times it happens.

Each phase carries a full address, which the interconnect decodes and the target turns into a local offset. The block does not shortcut that — there is no "same target as last time" fast path, because the slave was never told there was a last time.

And the target's own bounds still apply per phase. The RAM answers offsets below DEPTH and refuses the rest, exactly as in a single cycle. A block that runs off the end of a memory produces an ERR on the phase that does, not on the block.

What the master must supply, per phase: the address, the write data, SEL, and WE — the same payload Chapter 5.5 had it present to begin a single transfer, and terminated the way Chapter 5.6 described. What it must not do is change any of them while a phase is outstanding, which is Section 5's measurement.

2. Simulation — SIM G: A Block Write Into RAM

Six words from word address 8, one cycle, zero-wait RAM, checked against an independent reference array over the whole memory.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM G - a block write into RAM ===
    6 words from word address 8, one cycle, zero-wait RAM.

    phase   ADR (word)   DAT          SEL   RAM index
      0     0x00000008   0xa5000000   1111   8
      1     0x00000009   0xa5000001   1111   9
      2     0x0000000a   0xa5000002   1111   10
      3     0x0000000b   0xa5000003   1111   11
      4     0x0000000c   0xa5000004   1111   12
      5     0x0000000d   0xa5000005   1111   13

    bus cycles 1   phases 6   ACKs 6   elapsed 8 clocks
    RAM words written 6
    reference comparison over all 64 RAM words
    WRITE MISMATCHES: 0

Reading it

Six phases, six ACKs, six words written, and the reference comparison covers all 64 RAM words — not just the six that should have changed.

That scope is deliberate. Comparing only the touched words would pass a master that wrote the right values to the right places and also wrote somewhere else. The 58 words that should have stayed zero are as much a part of the result as the six that changed.

The data pattern is 0xA500_000k, carrying the phase index in its low half. A repeated address, a skipped address or a swapped pair is visible in the value itself rather than having to be inferred from which slot is wrong.

Eight clocks for six transfers — six presenting, plus the cycle's start and finish, consistent with Chapter 14.2's accounting.

3. Simulation — SIM H: Reading It Back

The same master, the same cycle structure, WE_O negated. Every returned word compared against the reference.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM H - reading the same block back ===

    phase   ADR (word)   returned     expected     match
      0     0x00000008   0xa5000000   0xa5000000   yes
      1     0x00000009   0xa5000001   0xa5000001   yes
      2     0x0000000a   0xa5000002   0xa5000002   yes
      3     0x0000000b   0xa5000003   0xa5000003   yes
      4     0x0000000c   0xa5000004   0xa5000004   yes
      5     0x0000000d   0xa5000005   0xa5000005   yes

    words checked 6   READ MISMATCHES: 0

Reading it

Six words checked, zero mismatches, and the read block has the same structure as the write — one cycle, six phases, six terminations.

The read direction is where a phase-boundary mistake becomes visible immediately. A master that latched DAT_I a clock late would return the next word each time and the last one would be junk; one that latched early would return the previous. Both produce a cleanly shifted sequence, which is why the expected column is worth printing beside the returned one rather than reporting a single pass.

SEL_O is all-ones throughout. These are whole-word transfers, so Module 13's lane machinery is present and idle — and every phase would carry its own operand classification under Chapter 13.4a block of partial writes is entirely possible, and each phase would carry its own select pattern exactly as a single transfer does. The two mechanisms compose without interacting.

4. Simulation — SIM I: Four Phases, Four Latencies

The RAM takes its wait count from the address, so each phase of one block is answered at a different speed — zero, one, two and three wait states, in that order.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM I - one block, four different latencies ===
    the RAM takes its wait count from the address, so each
    phase of this block is answered at a different speed.

    phase   ADR (word)   wait clocks   term
      0     0x00000008             0   ACK
      1     0x00000009             1   ACK
      2     0x0000000a             2   ACK
      3     0x0000000b             3   ACK

    bus cycles 1   phases 4   ACKs 4
    clocks presenting 10   of which waiting 6
    metadata changed while a phase was outstanding: no

    One cycle, four phases, four terminations, and the phase
    index advanced only after each answer. ADR, WE, SEL and
    DAT held still for the whole of every wait.

Reading it

Four phases, four different wait counts, four terminations, one cycle. The latencies are 0, 1, 2 and 3 and they land on the phases in order.

Ten clocks presenting, of which six were waiting. Six is 0+1+2+3 — the sum of the individual latencies, which is what Chapter 14.2 meant by latency being per-transfer. The block did not amortise any of it.

metadata changed while a phase was outstanding: no is the important line, and it is a measurement rather than a claim. The testbench watches ADR, WE, SEL, DAT and the phase index on every clock where a transfer is presented and unanswered, and counts any change.

Why that check and not a visual inspection of a waveform. A metadata change during a wait is the defect Chapter 8.3 measured as early address advance: the master moves on while the slave is still working, so the slave answers a transfer whose address has already been replaced. RULE 3.60 qualifies ADR_O, DAT_O(), SEL_O() and WE_O with STB_O, and holding them still for the whole of a phase is what makes that qualification mean anything.

The phase index is in the stability check for the same reason. It advances only at a boundary — P4 in Chapter 14.1 — and a master that advanced it on presentation would skip a word while the bus looked perfectly normal.

5. Transfer-Count and Address-Sequence Audits

Section 6's defect is a counting error, so the count is worth auditing across the whole supported range first — including zero, which is where a counter is most likely to wrap.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  --- transfer-count audit: LENGTH means NUMBER OF TRANSFERS ---

    requested  cycles  phases  ACKs  RAM writes  unique ADRs  CYC after
            0       0       0     0           0            0   low
            1       1       1     1           1            1   low
            2       1       2     2           2            2   low
            3       1       3     3           3            3   low
            4       1       4     4           4            4   low
            5       1       5     5           5            5   low
            6       1       6     6           6            6   low
            7       1       7     7           7            7   low
            8       1       8     8           8            8   low

    Every non-zero length produced exactly that many phases in
    exactly one cycle, and CYC_O was low afterwards in all of
    them. Length 0 began no cycle at all - LOCAL RTL POLICY,
    chosen so a zero-length request cannot wrap a counter into
    a full-range block.

    --- address-sequence audit, WORD units ---
    expected ADR[k] = base + k  for k = 0 .. length-1
    sequence violations across every length: 0

Reading it

Nine lengths, and the three count columns agree on every row. Phases, ACKs and RAM writes are the same number as the request, in exactly one cycle, with CYC_O low afterwards.

Length 0 begins no cycle at all. That is LOCAL RTL POLICY, and the reason for choosing it is visible in the master: the final-phase test is beat_q == len_q - 1. With len_q zero that subtraction underflows, and a counter compared against the wrap value runs for the full range instead of not at all. Diverting zero before the subtraction is what makes the rest safe.

The address audit is the second half of the same concern. ADR[k] = base + k for every k across every length — zero violations, in word units. A stride error would show here as a uniform multiple, and an advance-on-presentation error as half the addresses appearing.

Neither audit would catch the defect in Section 6, and that is worth noticing. Both check the correct master. The next section runs a different one.

6. Simulation — SIM J: One Comparison, One Extra Transfer

The same master with the final-phase test written against the count instead of the last index.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── THE DEFECT — a final-phase test written against the wrong bound ─────
// wb_offbyone_master. NOT A REFERENCE DESIGN.
//
// Identical to wb_seq_master except for one comparison:
//
//     correct:  last_phase = (beat_q == len_q - 1)
//     broken:   last_phase = (beat_q == len_q)
//
// The reasoning behind it is the ordinary confusion between a COUNT and a
// LAST INDEX. Four transfers are numbered 0..3, so the last one is index 3,
// not index 4. Comparing against the count runs one phase too many.
//
// What makes it worth measuring rather than describing is where the extra
// phase goes: it is a fully legal, fully acknowledged transfer to the next
// address. Nothing on the bus objects, the slave answers it, and on a write
// it lands in memory.
module wb_offbyone_master #(
  parameter int unsigned AW     = 30,
  parameter int unsigned DW     = 32,
  parameter int unsigned MAXLEN = 16,
  parameter bit          BLOCK  = 1'b1,
  parameter int unsigned STRIDE = 1,
  localparam int unsigned CW = (MAXLEN <= 1) ? 1 : $clog2(MAXLEN + 1)
) (
  input  logic            clk_i,
  input  logic            rst_i,
  input  logic            start_i,
  input  logic            we_i,
  input  logic [AW-1:0]   base_i,
  input  logic [CW-1:0]   len_i,
  output logic            busy_o,
  output logic            done_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,
  output logic [CW-1:0]   beat_o
);
  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;
  logic          we_q;
  logic [15:0]   gap_q;

  logic phase_done, last_phase;
  assign phase_done = cyc_o && stb_o && (ack_i || err_i);
  // ── THE DEFECT: compared against the COUNT, not the last index. ──
  assign last_phase = (beat_q == len_q);

  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;
  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;

  always_ff @(posedge clk_i) begin
    if (rst_i) begin
      state_q <= S_IDLE; beat_q <= '0; len_q <= '0;
      adr_q <= '0; we_q <= 1'b0; gap_q <= '0;
    end else begin
      case (state_q)
        S_IDLE: if (start_i) begin
          if (len_i == CW'(0)) state_q <= S_DONE;
          else begin
            len_q <= len_i; we_q <= we_i;
            beat_q <= '0; adr_q <= base_i; state_q <= S_XFER;
          end
        end
        S_XFER: if (phase_done) begin
          if (last_phase) state_q <= S_DONE;
          else begin
            beat_q <= beat_q + CW'(1);
            adr_q  <= adr_q + AW'(STRIDE);
            if (BLOCK) state_q <= S_XFER;
            else begin gap_q <= 16'd1; state_q <= S_GAP; end
          end
        end
        S_GAP: if (gap_q <= 16'd1) state_q <= S_XFER;
               else gap_q <= gap_q - 16'd1;
        S_DONE: state_q <= S_IDLE;
        default: state_q <= S_IDLE;
      endcase
    end
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM J - a final-phase test against the wrong bound ===
    the same master with one comparison changed:
      correct   last = (beat == len - 1)
      broken    last = (beat == len)

    requested transfers        4
    phases observed            5
    ACK terminations           5
    RAM words written          5

    addresses presented:
      phase 0   0x00000008
      phase 1   0x00000009
      phase 2   0x0000000a
      phase 3   0x0000000b
      phase 4   0x0000000c   <-- not requested

    word 12 was written by a transfer nobody asked for, and
    it is a completely legal transfer - presented, qualified,
    acknowledged. Nothing on the bus objects to it.

Reading it

Four requested, five performed. Five phases, five ACKs, five words written — and word 12 belongs to whatever was there before.

The extra transfer is completely legal. Presented with CYC_O and STB_O, carrying a valid address and valid data, acknowledged by the slave in the ordinary way. A protocol checker sees five conforming transfers, because five conforming transfers is what happened.

Nothing on the bus carries the number four. The request length lives in the master's len_q register and nowhere else, so there is no signal against which the fifth transfer could be judged wrong.

Which makes the evidence entirely count-based: phases observed against the length the client asked for. That comparison requires knowing the client's request, which a bus-level monitor does not.

The mirror-image defect is worth naming because it fails differently. Comparing against len_q - 2 performs N−1 transfers, and that one is caught immediately by a read-back: the last word is missing. One too many is silent; one too few is loud.

7. Failure Modes and Discriminating Evidence

Symptom: a block writes one word past its range.

Candidate causes. A final-phase test against the count rather than the last index; a counter initialised to 1; a length interpreted as a last index.

Discriminating evidence. Phases observed against the requested length, and the address of the last phase. Five against four, ending one stride past the intended end, is conclusive. Memory beyond the block is where to look, not within it.

Likely RTL location: the final-phase comparison.

Symptom: a block writes one word short.

Candidate causes. The same comparison, wrong in the other direction, or CYC_O dropped before the final termination.

Discriminating evidence. A read-back of the full range. The last word holds its previous value. This one announces itself, which is why it is the less dangerous of the pair.

Symptom: a block against a slow memory returns shifted data.

Candidate causes. Metadata advanced while a phase was outstanding, so the slave answered a transfer the master had already replaced.

Discriminating evidence. ADR and the phase index on every clock where a transfer is presented and unanswered. Any change is the defect. SIM I runs that check continuously and reports it as one line.

Symptom: a block completes but one word in the middle is wrong.

Candidate causes. A repeated or skipped address, or a data pattern that does not distinguish position.

Discriminating evidence. The value in the wrong slot. With 0xA500_000k the value names the phase that wrote it — a value of 0xA5000002 in slot 3 says phase 2 wrote twice, which is a different defect from slot 3 never being written.

Symptom: a block runs off the end of a memory.

Candidate causes. A length or base that puts later phases outside the target.

Discriminating evidence. Which phase returns ERR. The block does not fail as a unit — the phase that leaves the range fails, and the ones before it succeeded, so the memory is partially updated. Chapter 12.5's range test is what produces that ERR.

8. Verification

The properties in Chapter 14.1 Section 9 cover this chapter, and two of them are what SIM J violates.

P5 — no extra phase. After the final phase's boundary, STB_O is low. The broken master presents a fifth transfer instead, so P5 fails on exactly the clock where the defect happens rather than downstream where the symptom appears.

P4 — advance only on termination, and P3 — metadata stable, are what SIM I measures procedurally. Icarus cannot run the assertions; it can run the stability counter, and that counter is P3 in executable form.

And the RAM's own two properties apply per phase, not per block.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_ram_props — the slave's side. Both LOCAL: Wishbone does not reach
// inside a target, and nothing here is a block-specific obligation.
// ─────────────────────────────────────────────────────────────────────────
module wb_ram_props #(
  parameter int unsigned OFF_AW = 8,
  parameter int unsigned DEPTH  = 64
) (
  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 [OFF_AW-1:0] adr_i
);
  default disable iff (rst_i);

  // P9 — LOCAL RTL POLICY. An acknowledged access names implemented
  // storage. The same bound Chapter 12.5 measured, one module along.
  property p_ram_index_in_range;
    @(posedge clk_i) (cyc_i && stb_i && ack_i) |-> (32'(adr_i) < 32'(DEPTH));
  endproperty
  a_ram_index_in_range: assert property (p_ram_index_in_range);

  // P10 — LOCAL RTL POLICY, and the one that makes this slave block-
  // agnostic. It answers at most one of ACK/ERR, and only inside a
  // qualified transfer — with no reference anywhere to how many phases the
  // cycle contains, because it is never told.
  property p_one_answer_qualified;
    @(posedge clk_i) (ack_i || err_i) |-> (cyc_i && stb_i && !(ack_i && err_i));
  endproperty
  a_one_answer_qualified: assert property (p_one_answer_qualified);
endmodule

P10 is the property that makes the slave block-agnostic. It answers at most one of ACK/ERR, only inside a qualified transfer, with no reference anywhere to how many phases the cycle contains — because it is never told.

9. Common Mistakes

"length means the last index."

Wrong mental model: a bound rather than a count.

What is true: it is a count in this RTL, and the policy has to be written down. Four transfers are indexed 0..3, so the final-phase test is against length - 1. SIM J is what the other reading costs.

"A read-back proves the block was correct."

Wrong mental model: checking what you wrote is checking everything.

What is true: it cannot see a transfer that went somewhere else. SIM J's block reads back perfectly for all four requested words. The reference comparison has to cover the memory the block should not have touched.

"An extra transfer would show up as a protocol error."

Wrong mental model: the bus knows the length.

What is true: there is no length on the bus. The fifth transfer is presented, qualified and acknowledged like the other four. Nothing is available to object.

"A zero-length block is harmless."

Wrong mental model: nothing requested, nothing happens.

What is true: only if the RTL diverts it. With a final-phase test of beat == len - 1 and len zero, the subtraction wraps and the counter runs the full range. Section 5 sweeps zero explicitly for that reason.

"Block transfers to memory need a special memory."

Wrong mental model: the slave participates in the block.

What is true: the RAM here has no block awareness at all. It answers qualified transfers. PERMISSION 3.55 makes BLOCK support optional precisely because, for most slaves, there is nothing to support.

"A block that runs past the end of a memory fails as a block."

Wrong mental model: the cycle is the unit of failure.

What is true: the phase that leaves the range fails. Earlier phases already succeeded and their writes stand — the memory is partially updated, and the master's error policy decides what happens next.

10. Interview Reasoning

Define what the length parameter means, test against the last index, and divert zero before the subtraction.

The semantics come first because the bug is a semantic one. length is a count here: four means four transfers, indexed 0 through 3. So the final-phase test is beat == length - 1. Comparing against length runs one phase too many.

Zero needs diverting before that subtraction ever happens. length - 1 with length zero wraps to the maximum, and a counter compared against the wrap value runs the full range. This master sends zero straight to done and begins no cycle — local policy, and the audit sweeps it.

Then verify by counting, not by reading back. Phases observed against the requested length, across every supported length. A read-back of the requested words passes on a master that also wrote a fifth — measured: four words read back correctly while word 12 was corrupted.

And the reference comparison has to cover memory the block should not have touched. That is where the extra transfer is visible and the only place it is.

11. Understanding Check

None of them individually. The fifth one should not have existed.

Every transfer was legal. Presented with CYC_O and STB_O asserted, carrying a valid address and valid data, acknowledged by the slave in the ordinary way. The slave had no basis on which to refuse it — it was never told how many to expect.

The defect is in the count, which lives only in the master. beat_q == len_q instead of beat_q == len_q - 1, so the master kept going one phase past the end.

Word 12 is the damage — a location the client never asked about, overwritten with data belonging to a transfer that was never requested.

Which is why the evidence has to be a count comparison. Requested four, observed five. There is no signal on the bus that carries the number four, so nothing in the trace is marked wrong.

12. What Module 14 Established

A bus cycle groups transfers. It does not merge them, order them, bound them, or protect them.

14.1 — cycle and phase are different units. One CYC_O, four terminations, measured. The phase boundary is an answered clock edge, not an STB_O pulse — which is why STB_O edges and ACK_I-high clocks both count the wrong thing.

14.2 — the saving is a constant and the latency is a multiplier. Three clocks saved at every latency tested; a ratio that fell from 1.60× to 1.18× with nothing in either design changing.

14.3 — a block declares nothing. Four phases at words 8, 12, 9 and 15, all acknowledged. B3 does have burst metadata — CTI_O() and BTE_O() — in the Registered Feedback profile, which is not this one.

14.4 — the count lives only in the master. Six words written and read back against a reference; four phases at four latencies with nothing moving mid-phase; and one comparison changed producing a fifth transfer that no rule, checker or read-back objects to.

The thread is a single question asked four ways: what did the slave actually get told? It was told a transfer is qualified. Every expectation beyond that — length, order, exclusivity, overlap — is either the master's private state or somewhere else entirely, and every defect in this module is one of those expectations quietly assumed.

13. What's Next

Block transfers are complete. Many transfers, one tenure, measured throughput, verified counts.

And CYC_O turned out to be a statement about tenure that guarantees nothing about interference. The normative BLOCK figure has a second signal running alongside it that this module named and did not use.

What does it take to make a sequence of transfers genuinely indivisible — and why is that a different mechanism rather than a longer block?

Module 15 — Read-Modify-Write Cycles takes up LOCK_O and the third defined cycle type, where the guarantee is not grouping but exclusion. 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.