Skip to content
VLSI Mentor

Wishbone · Module 12

Address Decoding

A bus address answers two questions, not one. Measured across a three-target SoC, including the boundary where one window ends and the next begins.

Module 11 closed the last of the termination classes. Every chapter so far has had one master and one slave, wired directly together, and the address went to the only place it could go.

Real systems have several targets on one bus, and Chapter 3.5 named the block that sits between them without saying how it decides. Nothing in the Wishbone signal set says which target a transfer is for.

One address leaves the master. How does exactly one target answer it?

1. The Path, End to End

Five stages, and every one of them can be wrong independently.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  ADR_O            the master presents a global address
    |
    v
  DECODE           compare against every window in the map
    |
    v
  SELECT           a one-hot vector: which target owns it
    |
    +--> QUALIFY   assert CYC/STB to that target and no other
    |                 |
    |                 v
    |              the target sees a transfer and answers
    |                 |
    v                 v
  ROUTE            return that target's ACK/ERR and DAT upstream
    |
    v
  DAT_I, ACK_I     the master sees one answer

All of it happens inside one transfer. The address is presented when the master asserts CYC_O and STB_O (Chapter 5.5) and must stay valid until the strobe is negated — so the decode, the selection and the response all live inside that window.

The two arrows out of SELECT are the part most often half-built. Selecting correctly and then strobing everyone is a bug. Selecting correctly, strobing correctly, and then combining everyone's answer is also a bug — and it is the one that survives review, because the select logic it depends on is visibly correct. Chapter 12.4 measures it.

This chapter builds the DECODE and SELECT stages and proves the one property everything downstream rests on.

2. The Address Unit — Read This Before Any Number in This Module

The Wishbone address is not the address software uses, and the difference is a factor of four.

What the specification says. The ADR_O() description states that the array's higher boundary is set by the core's address width and the lower boundary is determined by the data port size and granularity — and gives the example directly:

the array size on a 32-bit data port with BYTE granularity is ADR_O(n..2)

Bits 1 and 0 are not on the wires. SEL_O carries byte selection instead, so encoding it again in the address would be redundant and, when the two disagreed, ambiguous. Chapter 4.3 works through why in full.

So this course's ADR is a word address, 30 bits wide for a 32-bit byte address space on a 32-bit port:

widthexample
software byte address320x4000_1004
Wishbone ADR (word)300x1000_0401

The conversion is word = byte >> 2, and it is stated once per design rather than at every use. Every address map in this module is written in bytes, because that is the unit of the document software engineers read, and converted to words in exactly one named place inside the decoder. A factor-of-four decode bug is a units bug, and units bugs are prevented by having one conversion point rather than twelve.

3. The Running SoC

One system, used by all seven chapters. Its peripheral bases are the ones Chapter 4.3 already placed, so the map is continuous with the course rather than invented for this module.

targetbyte windowsizeimplementedword window
RAM0x0000_00000x0000_0FFF4 KiB256 words0x0000_00000x0000_03FF
GPIO0x4000_00000x4000_0FFF4 KiB2 registers0x1000_00000x1000_03FF
TIMER0x4000_10000x4000_1FFF4 KiB2 registers0x1000_04000x1000_07FF
everything elsenothingunmapped

Notice the fourth column against the third. Every window reserves 4 KiB; not one target implements 4 KiB. A window is a reservation, not an inventoryChapter 12.7 is about what happens when a decoder forgets that, and Chapter 12.5 measures the memory version of it.

Each target drives a recognisable data signature — GPIO returns 0x1111_xxxx, TIMER 0x2222_xxxx, RAM whatever was written to it. A misrouted read is then visible in the data rather than having to be deduced from a waveform, which is the instrument Chapter 12.4 needs.

4. RTL — The Decoder

It answers one question and carries no state. Given an address it produces a one-hot select, a local offset, and a flag saying nobody matched.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_range_decode — the system address map, expressed as range predicates.
//
// ADDRESS UNITS. This is the whole reason the module looks the way it does.
// The course's Wishbone port is 32-bit with BYTE granularity, so the
// specification's own example applies: the address array on such a port is
// [ADR_O(n..2)] — bits 1 and 0 are NOT on the wires, because SEL_O carries
// byte selection instead (Chapter 4.3). ADR is therefore a WORD address.
//
// The address map, however, is written and read in BYTES, because that is
// what software sees and what the documentation says. So:
//
//   BASE_FLAT / SIZE_FLAT  are BYTE quantities  (the document's units)
//   adr_i                  is a WORD address    (the wire's units)
//
// The conversion happens once, in wbase_of()/wlast_of(), and nowhere else.
// Every comparison below has word-valued operands on both sides. A
// factor-of-four decode bug is a units bug, and units bugs are prevented by
// converting in exactly one named place.
//
// DECODE FORM. sel[i] = (a >= wbase[i]) && (a <= wlast[i])
//
//   Note the form: >= base AND <= LAST, where LAST is precomputed as
//   base + size - 1. It is NOT "< base + size" evaluated on the fly.
//   Writing the upper bound as a sum risks two distinct failures:
//     1. off-by-one, if someone writes <= base + size
//     2. arithmetic overflow, if base + size exceeds the address width,
//        which silently wraps the comparison to a range that matches
//        almost nothing (or almost everything).
//   Precomputing LAST at elaboration removes both. The LAST value is also
//   the number every address map document actually prints.
//
// This form works for ARBITRARY sizes and bases. It does not require the
// window to be a power of two or naturally aligned. wb_mask_decode is the
// cheaper alternative that does require both; Chapter 12.3 measures where
// they agree and proves where they cannot.
// ─────────────────────────────────────────────────────────────────────────
module wb_range_decode #(
  parameter int unsigned BYTE_AW = 32,
  parameter int unsigned DW      = 32,
  parameter int unsigned NS      = 3,
  // byte bases, target 0 first
  parameter logic [NS*32-1:0] BASE_FLAT =
      { 32'h4000_1000, 32'h4000_0000, 32'h0000_0000 },
  parameter logic [NS*32-1:0] SIZE_FLAT =
      { 32'h0000_1000, 32'h0000_1000, 32'h0000_1000 }
) (
  input  logic [BYTE_AW-1-$clog2(DW/8):0] adr_i,       // system WORD address
  output logic [NS-1:0]                   sel_o,       // one-hot or zero
  output logic [BYTE_AW-1-$clog2(DW/8):0] offset_o,    // local WORD offset
  output logic                            unmapped_o
);
  localparam int unsigned SHIFT = $clog2(DW / 8);
  localparam int unsigned WAW   = BYTE_AW - SHIFT;

  function automatic logic [31:0] base_of(input int unsigned i);
    return BASE_FLAT[i*32 +: 32];
  endfunction
  function automatic logic [31:0] size_of(input int unsigned i);
    return SIZE_FLAT[i*32 +: 32];
  endfunction

  // ── the single conversion point: byte document units -> word wire units ──
  function automatic logic [WAW-1:0] wbase_of(input int unsigned i);
    return WAW'(base_of(i) >> SHIFT);
  endfunction
  // LAST, not base+size. Computed from the byte values, then converted.
  function automatic logic [WAW-1:0] wlast_of(input int unsigned i);
    return WAW'((base_of(i) + size_of(i) - 32'd1) >> SHIFT);
  endfunction

  // ── elaboration-time map audit ──────────────────────────────────────────
  // These are LOCAL RTL POLICY, not Wishbone requirements. Wishbone says
  // nothing about address maps; it is the integrator who must guarantee that
  // at most one target owns an address. These checks are how that guarantee
  // is made mechanical instead of editorial.
  initial begin
    for (int unsigned i = 0; i < NS; i++) begin
      if (size_of(i) == 32'd0)
        $fatal(1, "wb_range_decode: region %0d has zero size", i);
      if (size_of(i) < 32'(DW / 8))
        $fatal(1, "wb_range_decode: region %0d size %0h is under one bus word",
               i, size_of(i));
      // A window must not run off the top of the byte address space. This is
      // the overflow the range form would otherwise hide.
      if ((base_of(i) + size_of(i) - 32'd1) < base_of(i))
        $fatal(1, "wb_range_decode: region %0d (base %0h size %0h) overflows",
               i, base_of(i), size_of(i));
      // A window that does not start on a bus-word boundary cannot be
      // addressed exactly, because the low bits are not on the wires at all.
      if ((base_of(i) & 32'(DW/8 - 1)) != 32'd0)
        $fatal(1, "wb_range_decode: region %0d base %0h is not word aligned",
               i, base_of(i));
      for (int unsigned j = i + 1; j < NS; j++)
        if ((base_of(i) <= base_of(j) + size_of(j) - 32'd1) &&
            (base_of(j) <= base_of(i) + size_of(i) - 32'd1))
          $fatal(1, "wb_range_decode: regions %0d and %0d overlap", i, j);
    end
  end

  logic [NS-1:0] hit;
  always_comb begin
    hit = '0;                                  // explicit default: no latch
    for (int unsigned i = 0; i < NS; i++)
      hit[i] = (adr_i >= wbase_of(i)) && (adr_i <= wlast_of(i));
  end

  assign sel_o      = hit;
  assign unmapped_o = ~(|hit);

  // Local offset = distance from the window's base, in words. Subtraction,
  // not masking: masking is only equivalent for aligned power-of-two windows,
  // and this decoder does not require either.
  always_comb begin
    offset_o = adr_i;                          // unmapped passes through
    for (int unsigned i = 0; i < NS; i++)
      if (hit[i]) offset_o = adr_i - wbase_of(i);
  end
endmodule

Reading it

Three design decisions carry the whole module.

The parameters are in bytes and the comparison is in words. wbase_of() and wlast_of() are the only places the conversion happens. A reviewer checking for a units bug has two functions to read, not every comparison in the file.

The upper bound is a precomputed LAST, never base + size. Writing a < base + size invites two separate failures: the off-by-one that comes from typing <= instead of <, and the silent wrap when base + size exceeds the address width. Precomputing LAST at elaboration removes both, and LAST is the number address-map documents actually print. Chapter 12.3 measures what the arithmetic form costs.

The offset is a subtraction, not a mask. Masking gives the same answer only for aligned power-of-two windows. This decoder does not require either, so it cannot use the shortcut — and Chapter 12.3 builds the version that can, along with the elaboration checks that make the shortcut safe.

The initial block is the map's audit and it is not decoration. Zero size, sub-word size, arithmetic overflow, misalignment and pairwise overlap all $fatal at elaboration. These are LOCAL RTL POLICY, not Wishbone requirements — the specification says nothing whatever about address maps. It is the integrator who must guarantee one owner per address, and these checks are how that guarantee stops being editorial.

5. The Decode Path

A block diagram of the decode path. On the left, the master's address output carries a thirty-bit word address. It feeds a decoder in the middle, which compares the address against three windows drawn from the system address map. The decoder produces a three-bit one-hot select vector and a local word offset. On the right, the select vector chooses exactly one of four owners: the RAM, the GPIO, the timer, or the default responder which owns every address the other three do not claim. Only the chosen owner receives a qualified transfer.ADR_O[29:0]one global word addressthe address map3 windows, byte unitswb_range_decodecompare against each windowRAMsel[0] — word 0x0000_0000GPIOsel[1] — word 0x1000_0000TIMERsel[2] — word 0x1000_0400default respondereverything elseword addressBASE, SIZEsel[0]sel[1]sel[2]unmapped12

The map enters the decoder as parameters, not as logic. That dashed edge is elaboration-time information: the windows are fixed when the SoC is built, and the decoder is the same combinational block whatever they are.

Four arrows leave the decoder and at most one is ever true. That is the property Section 7 states precisely and Section 8 measures.

6. Simulation — SIM A: One Address, One Owner

Six accesses through the decoder alone — no master, no targets, nothing sequential. The decoder is combinational, so every row is the answer to a single address.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM A - one address, one owner ===
    ADR carries a WORD address: 32-bit port, byte granularity,
    so the specification's ADR_O(n..2) form applies.

    access             byte adr    word adr    sel  target   offset
    RAM word 4         0x00000010  0x00000004  001  RAM     0x004
    RAM last word      0x000003fc  0x000000ff  001  RAM     0x0ff
    GPIO OUT           0x40000004  0x10000001  010  GPIO    0x001
    TIMER LOAD         0x40001004  0x10000401  100  TIMER   0x001
    nothing is here    0x50000000  0x14000000  000  -none-  0x000
    past RAM window    0x00001000  0x00000400  000  -none-  0x400

    the sel column is one-hot or zero. It is never anything else,
    and SIM B proves that over every boundary in the map.

Reading it

Read the two address columns against each other first. 0x4000_1004 becomes 0x1000_0401. That is the >> 2 and nothing else — no decode has happened yet at that point in the row.

Then read word adr against offset. TIMER's window starts at word 0x1000_0400, so a global word address of 0x1000_0401 is offset 0x001 inside it. The target will be handed 0x001 and will never see 0x1000_0401, which is what lets the same timer be instantiated anywhere in any map.

0x4000_0004 and 0x4000_1004 differ in one hex digit and land on different targets. Both come out as offset 0x001. Two different registers in two different blocks, with the same local name — this is exactly the ambiguity the vocabulary in the opening table exists to prevent.

The last two rows are the interesting ones. 0x5000_0000 matches nothing, and so does 0x0000_1000, which is one word past the end of RAM's window. Both produce sel = 000, and the offset column for them is the address passed through unchanged — a value with no meaning, which is why Chapter 12.7's sweep prints a dash there instead.

Nothing in this table required a clock. The decoder is a function of the address. That matters for Chapter 12.3, where its cost is the only thing that distinguishes two forms that compute the same answer.

7. One-Hot, and Why the Distinction Is Not Pedantry

Two similar-looking assertions mean genuinely different things, and picking the wrong one is how an overlap ships.

writtentrue whenfalse when
$onehot0(sel)zero or one bit settwo or more bits set
$onehot(sel)exactly one bit setzero bits, or two or more

The decoder's own property is $onehot0. An unmapped address legitimately selects nothing, so requiring exactly one bit would fail on every address outside the map — which is most of a 32-bit space.

The system's property is stronger, and it needs the default responder to state it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  for every PRESENTED transfer:
      $onehot({sel_default, sel_timer, sel_gpio, sel_ram})

Exactly one owner, counting the default as an owner. This is the invariant the module is built on, and Chapter 12.6 measures a system where it is false — not by accident, but because nothing was wired to be the fourth bit.

Both are LOCAL ADDRESS-MAP POLICY. Wishbone requires neither. The specification has no concept of an address map at all — it defines what a master presents and what a slave answers, and leaves the question of which slave entirely to the integrator. Every guarantee in this module is one the integrator makes.

8. Failure Modes and Discriminating Evidence

Symptom: a read returns data that belongs to a different peripheral.

Candidate causes. Overlapping windows, so two targets were selected. A response path that combines instead of selects. A correct select with a wrong base, so one target owns the other's addresses.

Discriminating evidence. The select vector at the presenting clock. Two bits set is an overlap and the map is wrong. One bit set, naming the wrong target, means a wrong base. One bit set, naming the right target, and the data still wrong, moves the investigation to the response path — the decoder is exonerated, and Chapter 12.4 takes it from there.

Likely RTL location: the BASE/SIZE parameters, or the upstream mux.

Symptom: an access is off by a factor of four.

Candidate causes. A byte-valued constant compared against a word address, or the reverse.

Discriminating evidence. Compute the global word address by hand and compare it with adr_i. If the master is right and the decode is wrong, look for a comparison whose two sides came from different units. The signature is the factor: exactly 4, or exactly 1/4 — not an arbitrary displacement.

Where the fault sits: any comparison whose operands did not both come from the conversion functions.

Symptom: everything works except the very last address in a region.

Candidate causes. a <= base + size where size is a count, which admits one address too many, or a < base + size - 1, which rejects the last valid one.

Discriminating evidence. Probe the four boundary addresses and nothing else — one below, first, last, one above. Chapter 12.2's SIM B is exactly that sweep, and it is the cheapest decode test that exists.

Symptom: an address that should be unmapped reaches a target.

Candidate causes. A mask decode on a window that is not an aligned power of two, so the prefix comparison covers more than the map describes.

Discriminating evidence. Check the window's size and base against the mask decode's two preconditions. If either fails, the decoder is decoding a different region than the document describes. Chapter 12.3 enforces both at elaboration for exactly this reason.

9. Verification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_decode_props — ownership. Bound to the decoder and the presented bus.
//
// 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 chapter come from procedural checks, which Icarus does
// run — SIM B's one-hot audit is P1 and P2 in executable form.
//
// Every property below is labelled. The distinction is not decoration: a
// SPECIFICATION property holds for any conformant Wishbone system, and a
// LOCAL property holds for THIS SoC and could be false of another that is
// equally conformant.
// ─────────────────────────────────────────────────────────────────────────
module wb_decode_props #(
  parameter int unsigned NS = 3
) (
  input logic           clk_i,
  input logic           rst_i,
  input logic           cyc_i,
  input logic           stb_i,
  input logic [NS-1:0]  sel_i,
  input logic           unmapped_i
);
  default disable iff (rst_i);

  // P1 — LOCAL ADDRESS-MAP POLICY.
  // At most one target owns any address. Wishbone does not require this;
  // it is what the map's non-overlap guarantee buys, and it is enforced at
  // elaboration rather than hoped for at runtime.
  property p_onehot0;
    @(posedge clk_i) $onehot0(sel_i);
  endproperty
  a_onehot0: assert property (p_onehot0);

  // P2 — LOCAL ADDRESS-MAP POLICY.
  // "Unmapped" is exactly the complement of "selected". Written as an
  // equivalence so that a decoder which sets both, or neither, fails.
  property p_unmapped_complement;
    @(posedge clk_i) unmapped_i == (sel_i == '0);
  endproperty
  a_unmapped_complement: assert property (p_unmapped_complement);

  // P3 — LOCAL ARCHITECTURE.
  // Every presented transfer has an owner: a real target, or the default.
  // This is the invariant the whole module is built on, and it is FALSE of
  // the no-default system in SIM H — deliberately, which is why the
  // property is labelled local rather than specification.
  property p_someone_owns_it;
    @(posedge clk_i) (cyc_i && stb_i) |-> ($onehot(sel_i) || unmapped_i);
  endproperty
  a_someone_owns_it: assert property (p_someone_owns_it);
endmodule

Three properties, and all three are local. P1 and P2 are what the elaboration-time overlap check buys at runtime; P3 is the system invariant, and it is deliberately false of the no-default system Chapter 12.6 builds. A property that is true of one architecture and false of another has to say so.

P2 is written as an equivalence rather than an implication. unmapped |-> sel == 0 would pass on a decoder that never asserted unmapped at all. The failure a checker is most likely to miss is the one where the flag simply never fires, and an equivalence catches it from both sides.

10. Common Mistakes

"The address is the register number."

Wrong mental model: one address, one meaning.

What is true: two meanings, and the target only ever sees the second. A global address of 0x4000_1004 and a local offset of 0x001 are both "the address" in casual speech, and they are different numbers with different owners. SIM A prints them in adjacent columns because that is the only reliable cure.

"Upper address bits select the peripheral, lower bits select the register."

Wrong mental model: a fixed bit split is what decoding is.

What is true: that is one implementation, valid for aligned power-of-two windows. In general the decoder is a predicate over the address — BASE <= a <= LAST — and Chapter 12.3 shows a window where no bit split expresses the same region. The predicate is the concept; the bit split is an optimisation of it.

"Every address maps somewhere."

Wrong mental model: the map covers the space.

What is true: the map covers 3072 words of a 4,294,967,296-word space in this SoC, and that is typical. The overwhelming majority of any address space is unmapped. What is required is not that everything is mapped, but that something answers — which is Chapter 12.6's entire subject.

"If two slaves match, OR their acknowledgements together."

Wrong mental model: two owners is a signalling problem.

What is true: it is an ownership problem, and ORing hides it rather than solving it. Both targets executed the transfer. If either had a side effect, both side effects happened. An OR of two acknowledgements is indistinguishable from one acknowledgement, so the evidence that anything went wrong is destroyed at the moment the fault occurs.

"A 4 KiB window means the peripheral has 4 KiB of registers."

Wrong mental model: reservation equals implementation.

What is true: GPIO implements two words inside a thousand-word window. The running map is built this way on purpose, and every target in it has more reserved space than hardware. Chapter 12.7 measures the ratio.

11. Interview Reasoning

Identify the owner, and produce the local offset.

The first is a classification. Given the map, which target's window contains this address — or none of them. The output is a one-hot vector, and its correctness depends entirely on the map being non-overlapping.

The second is an arithmetic translation. The owner's window has a base; the local offset is the distance from it. The target is then written as though it lived at zero, which is what makes the same GPIO block usable in a different SoC at a different base with no RTL change.

A good answer separates who decides each. The first is the integrator's decision, expressed in parameters. The second is derived, with no freedom in it at all.

12. Understanding Check

Because each is the second word of its own window, and the offset is measured from the window's base.

GPIO's window starts at byte 0x4000_0000, word 0x1000_0000. The access at byte 0x4000_0004 is word 0x1000_0001, which is one word in.

TIMER's window starts at byte 0x4000_1000, word 0x1000_0400. The access at byte 0x4000_1004 is word 0x1000_0401, also one word in.

The two global addresses differ; the two offsets do not. Each target numbers from zero, so "the second register" is offset 0x001 in both — and that is the point of the offset rather than a coincidence.

Which is why "offset 1" is never a sufficient description of an access. Without the select vector it does not identify a register. SIM A prints both columns together, and Chapter 12.5 measures the bug that comes from quoting one of them alone.

13. What's Next

The mechanism is in place: an address, a predicate per window, a one-hot answer, and a local offset that lets each target be written as though it lived at zero.

Every number in it came from the map, and the map arrived as a parameter without justification.

Where does an address map come from, and what makes one good?

Chapter 12.2 — Address Maps treats the map as an architectural contract rather than a table, and measures the four addresses per region where decode bugs actually live. 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.