Skip to content
VLSI Mentor

Wishbone · Module 12

Memory Selection

A window reserves 1024 words and the RAM built 256. Measured: two global addresses a kilobyte apart reading and writing the same physical word.

Chapter 12.4 routed transfers to targets with two registers each. At that size the local offset is the register number and nothing interesting happens between them.

A memory window holds a thousand words, and the offset becomes an index into storage.

What has to be true for a global address to name exactly one physical word?

1. Window Size Is Not Storage Size

In the running SoC, RAM reserves 4 KiB and implements 1 KiB.

wordsbyte range
window the map reserves10240x0000_00000x0000_0FFF
storage the RAM implements2560x0000_00000x0000_03FF
holes inside the window7680x0000_04000x0000_0FFF

Three quarters of the window has no hardware behind it, and that is a normal configuration rather than a mistake. The window is sized for alignment and growth (Chapter 12.2); the storage is sized for what the design needed.

Which raises the question this chapter exists to answer. The decoder selects RAM for all 1024 words. What happens to the 768 that are not backed by anything?

There are exactly three possible designs, and only the first two are defensible:

designthe 768 holescost
refuse themthe RAM answers ERRa comparator against DEPTH
shrink the windowthey become unmappedthe map loses its alignment
let them wrapthey alias onto real storagenothing — and that is the problem

The third is not a design; it is the absence of one. It is what happens when the index is taken from the offset's low bits and nothing checks the rest.

2. Deriving the Index

The index width comes from the depth, not from the window.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  IW = $clog2(DEPTH)          256 words -> 8 bits
  index = offset[IW-1:0]
  in_range = (offset < DEPTH)

The truncation in the second line is safe only because of the third. offset[7:0] discards bits 8 and 9 of a 10-bit offset. Discarding them is fine when they are known to be zero, and in_range is what establishes that. Without it, offsets 0x000, 0x100, 0x200 and 0x300 all produce index 0x00.

This is the same width confusion Chapter 12.3 measured on the select, appearing one level down. There, a comparison sized to the offset could not distinguish targets. Here, an index sized to the depth cannot distinguish offsets — and in both cases the discarded bits were the ones carrying the distinction.

$clog2 is exact for powers of two and rounds up otherwise. For DEPTH = 256 it gives 8 and the index covers exactly 0–255. For DEPTH = 200 it gives 8 as well, and the index covers 0–255 while only 0–199 exist — so in_range is doing more work, and is not optional even in principle.

3. RTL — The Memory Target

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── RAM: DEPTH words of real storage ────────────────────────────────────
// The index is derived from the local offset and its width from DEPTH. The
// comparison against DEPTH is what makes the window and the storage the
// same size; Chapter 12.5 removes it and measures the alias.
module wb_ram_slave #(
  parameter int unsigned OFF_AW = 10,
  parameter int unsigned DW     = 32,
  parameter int unsigned DEPTH  = 256,
  // Derived in the parameter list, not the body: the index width is a
  // function of DEPTH and the port that carries it must already know it.
  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,
  output logic [DW-1:0]     dat_o,
  output logic              ack_o,
  output logic              err_o,
  output logic [IW-1:0]     index_o,        // observation
  output logic              in_range_o
);
  logic xfer, in_range;
  logic [IW-1:0] index;
  logic [DW-1:0] mem [0:DEPTH-1];

  assign xfer = cyc_i && stb_i;
  // The index is the low IW bits of the offset; in_range is what makes that
  // truncation safe. Without it, offset DEPTH and offset 0 would name the
  // same word — which is precisely the defect wb_ram_alias_slave contains.
  assign index    = adr_i[IW-1:0];
  assign in_range = (32'(adr_i) < 32'(DEPTH));

  assign ack_o = xfer &&  in_range;
  assign err_o = xfer && !in_range;

  always_comb begin
    dat_o = '0;
    if (xfer && 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;
    end else if (xfer && in_range && we_i) begin
      mem[index] <= dat_i;
    end
  end

  assign index_o    = index;
  assign in_range_o = in_range;
endmodule

Reading it

in_range gates the acknowledgement, the error and the write. One term, three consequences — the same single-condition discipline Chapter 11.1's FIFO used for its push. A design in which the acknowledgement and the write are computed separately can acknowledge a write it did not perform, or perform one it did not acknowledge.

The error for an out-of-range offset is the target's, not the interconnect's. Only the RAM knows its depth, so only the RAM can answer for a hole inside its own window. That is why the two ERRs in Section 5 come from different places and mean different things.

IW is declared in the parameter port list rather than in the body, because a port's width must be known where the port is declared. It is a localparam, so it cannot be overridden into disagreement with DEPTH.

The index is adr_i[IW-1:0] and the range test is on the full offset. Both are needed and they are not redundant: the index says which word, the test says whether that word is the one the address asked for.

4. One Address, Split Twice

A diagram showing a single global byte address being split twice. The byte address 0x00000200 is first shifted right by two to give the word address 0x00000080, because the Wishbone port does not carry the two lowest byte bits. The interconnect then compares the word address against the map and selects the RAM, producing a local offset of 0x080. The RAM checks the offset against its depth of 256, finds it in range, and uses the low eight bits as storage index 0x80. Each stage discards information that the next stage does not need.0x0000_0200software byte address0x0000_0080ADR — word addresssel = RAMthe interconnectdecidesoffset 0x080address minus base0x080 < 256the RAM decidesindex 0x80one physical wordshift by 2decoderange12

Three different numbers for one access, and each stage drops what the next does not need. The byte address loses two bits to granularity, the word address loses its high bits to the window, and the offset loses its high bits to the depth — but only after they have been checked.

0x0000_0200, 0x0000_0080 and 0x80 are all "the address" in casual speech. The first is what a C pointer holds, the second is what appears on ADR, the third is what indexes the memory array. Naming which one you mean is the difference between a five-minute debug and an afternoon.

5. Simulation — SIM F: Inside the Storage, Inside the Window, Outside Both

Five accesses across RAM's window, three into implemented storage and two past it in different ways.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM F - one global address, two decisions ===
    RAM window 4 KiB = 1024 words. RAM storage 256 words.
    The interconnect picks the window; the RAM decides whether
    anything is implemented at that offset.

    byte adr     offset  index  in_range  term   read back
    0x00000000   0x000   0x00     1       ACK    0x4a4a0000
    0x00000200   0x080   0x80     1       ACK    0x4a4a0080
    0x000003fc   0x0ff   0xff     1       ACK    0x4a4a00ff
    0x00000400   0x100   0x00     0       ERR    0x00000000
    0x00001000     -      -      -       ERR    0xdead0000

    rows 1-3 are implemented storage: offset equals index and
    the data returns. Row 4 is inside the window and outside the
    storage - the RAM refuses it. Row 5 never reached the RAM.
    Both refusals read ERR at the master and they are NOT the
    same event: row 4 was the target's decision, row 5 the
    interconnect's.

Reading it

In the first three rows offset and index are the same number. That identity is the anti-alias property — while the offset is in range, the index loses nothing — and Section 9 states it as P10.

Row four is the chapter's centre. Byte 0x0000_0400 is inside RAM's window, so the decoder selected RAM and produced offset 0x100. The RAM computed index 0x00, found in_range = 0, and refused. The index it computed was real and wrong, and the only thing that stopped it being used was the comparison.

Note the read-back on that row: 0x0000_0000, not the contents of word 0. The RAM does not drive stored data for a refused access — RULE 3.65 qualifies DAT_O() with the termination, and the termination here is ERR.

Row five never reached the RAM at all. Byte 0x0000_1000 is outside the window, so sel was empty and the default responder answered — hence 0xdead0000 rather than zero. Two ERRs from two different components, distinguishable only by the select vector.

That distinction is not academic. An ERR from the RAM means the map reserves this and I did not build it — the window may be over-sized, or the software has a bad pointer within a valid region. An ERR from the default means the map reserves nothing here. The first points at the target, the second at the map, and a trace that records only "ERR" cannot tell them apart.

6. Simulation — SIM G: The Same RAM Without Its Range Test

One term removed. in_range is gone; the acknowledgement is now cyc && stb, and the index is still offset[7:0]. The window, the map, the decoder and the router are unchanged.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 3. Truncated RAM index ──────────────────────────────────────────────
// wb_ram_alias_slave — wb_ram_slave with the range test removed and the
// window left at its original, larger size.
//
// The reasoning that produces this is ordinary: "the RAM is 256 words, so
// the index is 8 bits, so take the low 8 bits of the offset." Every step is
// true. The conclusion is still wrong, because it silently decides what
// happens to the offsets ABOVE 255 — and what happens is that they wrap
// onto storage that already belongs to another address.
module wb_ram_alias_slave #(
  parameter int unsigned OFF_AW = 10,
  parameter int unsigned DW     = 32,
  parameter int unsigned DEPTH  = 256,
  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,
  output logic [DW-1:0]     dat_o,
  output logic              ack_o,
  output logic              err_o,
  output logic [IW-1:0]     index_o,
  output logic              in_range_o
);
  logic xfer;
  logic [IW-1:0] index;
  logic [DW-1:0] mem [0:DEPTH-1];

  assign xfer  = cyc_i && stb_i;
  assign index = adr_i[IW-1:0];

  // ── THE DEFECT: no in_range test. Every offset in the window is
  //    acknowledged, and offsets 256..1023 alias onto 0..255. ──
  assign ack_o = xfer;
  assign err_o = 1'b0;

  always_comb begin
    dat_o = '0;
    if (xfer && !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;
    end else if (xfer && we_i) begin
      mem[index] <= dat_i;
    end
  end
  assign index_o    = index;
  assign in_range_o = 1'b1;    // it believes everything is in range
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM G - the same RAM without its range test ===
    index = offset[7:0] and nothing else. Every offset in the
    4 KiB window is accepted.

    wrote 0xAAAA0000 to byte 0x00000000
    wrote 0xBBBB1111 to byte 0x00000400

    byte adr     offset  index   correct RAM     truncating RAM
    0x00000000   0x000   0x00    ACK  0xaaaa0000  ACK  0xbbbb1111
    0x00000400   0x100   0x00    ERR  0x00000000  ACK  0xbbbb1111

    offsets 0x000 and 0x100 produce the SAME index 0x00. In the
    truncating RAM the second write overwrote the first, and a
    read of byte 0x00000000 returns data written to 0x00000400.

    window words whose index a lower word already claimed: 768
    the same 1024 words refused by the correct RAM:         768

    768 of the window's 1024 words are holes. The correct RAM
    answers every one of them with ERR; the truncating RAM
    answers all of them with someone else's data.

Reading it

Two writes, a kilobyte apart, and one of them is gone.

0xAAAA0000 was written to byte 0x0000_0000 and 0xBBBB1111 to byte 0x0000_0400. In the truncating RAM both produced index 0x00, so the second write overwrote the first. A read of byte 0x0000_0000 now returns 0xBBBB1111 — data written to an address 1024 bytes away.

The correct RAM's column is the control. Byte 0x0000_0000 returns 0xAAAA0000, and byte 0x0000_0400 returns ERRits write never happened, which is the honest outcome for an address with no storage behind it.

Then the sweep, which is the scale of it. Of the window's 1024 words, 768 land on an index some lower word already claimed. The same 768 are refused by the correct RAM. Every one of the 256 real storage words answers to four distinct global addresses in the broken design.

7. Failure Modes and Discriminating Evidence

Symptom: writing one buffer corrupts another at a fixed distance.

Candidate causes. A truncated index, so addresses separated by the storage size collide.

Discriminating evidence. The distance between the two addresses. If it is exactly the implemented storage size — or a multiple of it — the index is too narrow for the offset. The signature is that the stride is a power of two equal to DEPTH, not an arbitrary displacement.

Likely RTL location: the index assignment, and the absent range comparison.

Symptom: accesses near the end of a memory region return errors.

Candidate causes. The window is larger than the storage, and the target is correctly refusing the holes.

Discriminating evidence. The offset at which errors begin, against DEPTH. If they start exactly at DEPTH, this is correct behaviour and the map over-reserves. The bug, if there is one, is in whatever generated an address there — a size constant taken from the window rather than from the storage.

Symptom: an access reaches the right target at the wrong offset.

Candidate causes. The offset computed from a different base than the select predicate used.

Discriminating evidence. global - base, by hand, against the reported offset. A constant discrepancy across the whole window means one base with two values. Chapter 12.1's decoder derives both from wbase_of() so they cannot differ.

Symptom: a memory read returns data the software never wrote.

Candidate causes. An alias delivering another address's data, or a read of an uninitialised location.

Discriminating evidence. Write a distinctive pattern to the suspect address and read back every other address in the region. If the pattern appears anywhere else, it is an alias, and the distance between the two locations names the missing bits. This is SIM G's experiment, and it takes two writes and two reads.

8. Verification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_mem_props — the memory window, its index, and the absence of aliases.
// ─────────────────────────────────────────────────────────────────────────
module wb_mem_props #(
  parameter int unsigned DEPTH = 256
) (
  input logic        clk_i,
  input logic        rst_i,
  input logic        cyc_i,
  input logic        stb_i,
  input logic [29:0] adr_i,
  input logic [2:0]  sel_i,
  input logic [29:0] offset_i,
  input logic [7:0]  index_i,
  input logic        in_range_i,
  input logic        ram_ack_i
);
  default disable iff (rst_i);

  // P8 — LOCAL ADDRESS-MAP POLICY.
  // RAM is selected only for addresses inside RAM's window. The window is
  // words 0x00000000..0x000003FF, which is this map's choice.
  property p_ram_window;
    @(posedge clk_i) sel_i[0] |-> (adr_i <= 30'h0000_03FF);
  endproperty
  a_ram_window: assert property (p_ram_window);

  // P9 — LOCAL RTL POLICY.
  // A RAM access is acknowledged only when the offset names implemented
  // storage. This is the property wb_ram_alias_slave removes, and removing
  // it is what creates the alias.
  property p_index_in_range;
    @(posedge clk_i) (sel_i[0] && cyc_i && stb_i && ram_ack_i) |->
      (in_range_i && (32'(index_i) < 32'(DEPTH)));
  endproperty
  a_index_in_range: assert property (p_index_in_range);

  // P10 — LOCAL RTL POLICY, and the anti-alias property.
  // Within the acknowledged range the index IS the offset, so two distinct
  // offsets cannot name one storage word. Stated as an identity rather than
  // as "no two addresses collide", because the identity is checkable on a
  // single transfer and the collision statement is not.
  property p_no_alias;
    @(posedge clk_i) (sel_i[0] && ram_ack_i) |->
      (32'(index_i) == 32'(offset_i));
  endproperty
  a_no_alias: assert property (p_no_alias);
endmodule

P10 is the anti-alias property and its form is the interesting part. The natural statement — "no two distinct global addresses map to the same storage word" — quantifies over pairs of transfers and cannot be checked on one.

Stating it as index == offset fixes that. If the index equals the offset on every acknowledged access, then distinct offsets give distinct indices, so the pairwise property follows from a single-transfer one. That rewrite is the whole reason the property is checkable at all.

P9 and P10 fail together in SIM G — the broken RAM acknowledges with in_range false, and its index differs from its offset for 768 of 1024 words. Either property alone would have caught it.

9. Common Mistakes

"Connecting only the low RAM address bits is fine — the high bits are all zero anyway."

Wrong mental model: the decoder guarantees the offset is small.

What is true: the decoder guarantees the offset is inside the window, which is four times the storage here. The high bits are zero for 256 of 1024 offsets and non-zero for the rest. SIM G measures 768 aliases produced by exactly this reasoning.

"A 4 KiB memory region means 4 KiB of RAM."

Wrong mental model: the map is an inventory.

What is true: the window is a reservation, sized for alignment and growth. RAM implements a quarter of its window, and that is the ordinary case. The number that bounds the index is DEPTH, and it appears nowhere in the map.

"The interconnect should reject out-of-range memory offsets."

Wrong mental model: the decoder can validate everything.

What is true: the decoder does not know the target's depth and should not. Teaching it would put the RAM's size in two places — the map and the RAM — with nothing keeping them equal. Only the target can answer for its own holes, which is why SIM F's fourth row is an ERR from the RAM.

"An alias is a corner case; real software never touches those addresses."

Wrong mental model: unreachable in practice.

What is true: an allocator that believes the window size hands out those addresses immediately. And the failure is silent, so the first symptom is corruption somewhere unrelated. A hole that errors is discovered in bring-up; a hole that aliases is discovered in the field.

"$clog2(DEPTH) bits of index is sufficient."

Wrong mental model: the index width is the whole problem.

What is true: the width is necessary and not sufficient. For DEPTH = 200, $clog2 gives 8 bits, which addresses 256 words of which 56 do not exist. The range test is what makes any index width safe, and for non-power-of-two depths it is doing visible work.

10. Interview Reasoning

Discarding address bits that still carry information, and preventing it means checking them before discarding them.

The mechanism. A target receives an offset wider than its storage index. Taking the low bits and ignoring the rest makes every offset congruent modulo the storage size name the same word. In this SoC, a 10-bit offset and an 8-bit index make four global addresses share each storage location.

The prevention is one comparison. offset < DEPTH, gating the acknowledgement and the write. Offsets outside the storage are then refused rather than folded, and the truncation becomes safe because the discarded bits are known to be zero.

What makes it worth insisting on. The alias is silent — every access succeeds, and the symptom surfaces later as corruption somewhere else. A refusal is visible immediately.

The strongest close names the general rule. Truncation is safe exactly when the discarded bits are known to be zero, and a range check is how you know. The same rule explains Chapter 12.3's truncated decoder, one level up.

11. Understanding Check

They came from different components and point at different problems.

Row four, byte 0x0000_0400: the select vector named RAM. The address is inside RAM's window, and the RAM refused it because its offset, 0x100, is past the 256 words it implements.

Row five, byte 0x0000_1000: the select vector was empty. The address is outside every window, so no target was asked and the default responder answered.

At the master both look identicalERR, same clock, no data. The select vector is the only thing that separates them.

And they lead different places. The first says the map reserves space the target did not build, so look at the window size or at whatever produced that pointer. The second says the map reserves nothing there, so look at the address itself. A debug trace that records only the termination class cannot tell you which.

12. What's Next

Memory selection is complete: window, offset, bounded index, and a measured demonstration of what the bound prevents.

Two chapters have now ended with an access nobody owns, answered by something not yet built.

What is the thing that answers for an address no target claims, and what happens without it?

Chapter 12.6 — Default Slave measures the same unmapped address through two systems that differ in one parameter — one terminates in the clock it was presented, the other never terminates at all. The full path is on the Wishbone curriculum index.

Continue learning

Related tutorials

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.