Skip to content
VLSI Mentor

Wishbone · Module 12

Decoder Logic

Two decode forms from one map, agreeing on 108 addresses — and the window where the cheaper one silently rejects 256 addresses the map includes.

Chapter 12.2 settled the map. Three windows, non-overlapping, aligned, with their last addresses written down.

Turning it into logic has more than one correct answer, and the answers are not interchangeable.

Which decode form does this map allow, and what does each one demand in return?

1. Range Decode

The general form, and the one Chapter 12.1 already published. It works for any base and any size, which is exactly why it is the safe default.

The one thing it must not do is compute its upper bound at runtime. a < BASE + SIZE looks like the natural expression of a window and carries two separate hazards:

Off-by-one. If SIZE is a count, the last valid address is BASE + SIZE - 1. Writing <= against BASE + SIZE admits one address past the end — and in a packed map that address belongs to the next target.

Overflow. BASE + SIZE is evaluated in the address width. A window near the top of the space wraps, and the comparison silently becomes a test against a small number. Neither hazard produces a diagnostic.

The fix is not a more careful inequality. Precompute LAST at elaboration, check that it did not wrap, and compare against that — which is what wlast_of() and the initial block in Chapter 12.1's decoder do. LAST is also the number the map document prints, so the two can be read against each other.

2. Mask Decode

When a window is a power of two and naturally aligned, the range test collapses into a prefix comparison.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_mask_decode — the same map, decoded by prefix comparison.
//
//   sel[i] = (a & ~wmask[i]) == wbase[i]
//
// Cheaper than a range compare: it is a bitwise AND and an equality, with
// no magnitude comparator and no borrow chain. On the canonical map it
// produces results IDENTICAL to wb_range_decode — Chapter 12.3 sweeps the
// space and proves it rather than asserting it.
//
// IT IS NOT A GENERAL SUBSTITUTE, and that is the point of publishing both.
// The equivalence holds only when, for every window:
//
//   SIZE is a power of two            (so ~mask is a contiguous prefix)
//   BASE is naturally aligned to SIZE (so no in-window address escapes and
//                                      no out-of-window address is caught)
//
// Violate either and the mask decode silently means something else. It does
// not fail loudly; it decodes a DIFFERENT region than the map describes,
// which is why the elaboration checks below are $fatal rather than warnings.
// Chapter 12.7 shows what the silent version costs.
// ─────────────────────────────────────────────────────────────────────────
module wb_mask_decode #(
  parameter int unsigned BYTE_AW = 32,
  parameter int unsigned DW      = 32,
  parameter int unsigned NS      = 3,
  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,
  output logic [NS-1:0]                   sel_o,
  output logic [BYTE_AW-1-$clog2(DW/8):0] offset_o,
  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
  function automatic logic [WAW-1:0] wbase_of(input int unsigned i);
    return WAW'(base_of(i) >> SHIFT);
  endfunction
  // The in-window mask: SIZE-1 in word terms. Valid ONLY because the
  // elaboration check below has established SIZE is a power of two.
  function automatic logic [WAW-1:0] wmask_of(input int unsigned i);
    return WAW'((size_of(i) - 32'd1) >> SHIFT);
  endfunction

  initial begin
    for (int unsigned i = 0; i < NS; i++) begin
      if (size_of(i) == 32'd0)
        $fatal(1, "wb_mask_decode: region %0d has zero size", i);
      // The two preconditions, enforced rather than documented.
      if ((size_of(i) & (size_of(i) - 32'd1)) != 32'd0)
        $fatal(1, "wb_mask_decode: region %0d size %0h is not a power of two",
               i, size_of(i));
      if ((base_of(i) & (size_of(i) - 32'd1)) != 32'd0)
        $fatal(1, "wb_mask_decode: region %0d base %0h not aligned to size %0h",
               i, base_of(i), size_of(i));
      if (size_of(i) < 32'(DW / 8))
        $fatal(1, "wb_mask_decode: region %0d size %0h is under one bus word",
               i, size_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_mask_decode: regions %0d and %0d overlap", i, j);
    end
  end

  logic [NS-1:0] hit;
  always_comb begin
    hit = '0;
    for (int unsigned i = 0; i < NS; i++)
      hit[i] = ((adr_i & ~wmask_of(i)) == wbase_of(i));
  end

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

  // For an aligned power-of-two window the masked address IS the offset,
  // so no subtractor is needed. This equivalence is exactly as conditional
  // as the decode itself.
  always_comb begin
    offset_o = adr_i;
    for (int unsigned i = 0; i < NS; i++)
      if (hit[i]) offset_o = adr_i & wmask_of(i);
  end
endmodule

Reading it

Why the two preconditions are exactly the right two.

Power-of-two size makes SIZE - 1 a contiguous run of low-order ones. That is what turns it into a mask: ~MASK is then a contiguous prefix, and comparing the prefix is the same question as comparing the range. For a size that is not a power of two, SIZE - 1 has holes, and Section 6 measures what the resulting predicate actually selects.

Natural alignment makes the prefix of the base constant across the window. If BASE is a multiple of SIZE, every address in the window shares the same high bits. If it is not, some in-window addresses carry a different prefix and are excluded, while some out-of-window addresses carry the same one and are admitted.

Both checks are $fatal rather than warnings, and that is the design decision worth defending. A warning is something a build log scrolls past. This failure is silent at runtime, so it has to be loud at elaboration or it is not caught at all.

The offset comes free here. For an aligned power-of-two window the masked address is the distance from the base, so no subtractor is required — adr_i & wmask_of(i). That equivalence is exactly as conditional as the decode, which is why both live behind the same two checks.

3. Case Decode, and Why This Module Does Not Ship One

For a small fixed map, a case on the high bits reads well:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // Illustrative only. Not used in this module's SoC.
  always_comb begin
    sel = 3'b000;
    unique case (adr_i[29:20])      // the 4 KiB-window prefix
      10'h000: sel = 3'b001;        // RAM
      10'h100: sel = 3'b010;        // GPIO   word 0x1000_0000
      10'h100: sel = 3'b100;        // TIMER  -- same prefix!
      default: sel = 3'b000;
    endcase
  end

That fragment does not work, and the reason is instructive. GPIO and TIMER are 4 KiB apart, so they differ at bit 10 of the word address, not above bit 19. A prefix chosen for "the window size" picks the wrong bits when two windows share a larger aligned block.

Making it work means either a wider prefix — which is a mask decode written with different syntax — or casez with wildcards, which introduces a second problem: casez treats ? in the case item as a don't-care, and an X on the address as a match against any pattern containing wildcards. A decoder that matches on X is a decoder whose simulation is more optimistic than its silicon.

So the form is legitimate and this module does not use it. For a map of three windows it is the mask decode with extra ceremony; for a large map with a genuinely uniform prefix structure it can be clearer than a loop. What it is never worth is the X-semantics risk on a block whose entire job is deciding who owns a transfer.

4. Elaboration-Time Checks Are the Load-Bearing Part

Both published decoders check the map before any simulation runs. The checks differ, and the difference is the point:

checkrange decodemask decode
zero sizeyesyes
smaller than one bus wordyesyes
BASE + SIZE overflowsyes
word-aligned baseyes
power-of-two sizeyes
base aligned to its own sizeyes
pairwise overlapyesyes

The range decoder checks for overflow because it does the arithmetic. The mask decoder does not, because it never forms BASE + SIZE at all — a hazard that exists only in one implementation is checked only in that implementation.

The mask decoder checks alignment and power-of-two because its correctness depends on them. The range decoder does not care, and demanding them would reject maps it handles perfectly well. Neither check list is "more thorough"; each matches what its own form can get wrong.

Only the overlap check is shared, because overlap breaks the map rather than the implementation.

5. Simulation — SIM C: An Overlapping Map

The same three windows, with GPIO widened to 8 KiB "to leave room for expansion", and nothing comparing it against its neighbour. Every window in this map is individually defensible. TIMER is where the document put it; GPIO is larger than it needs to be, which is a decision someone could defend in review.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 1. Overlapping windows ──────────────────────────────────────────────
// wb_overlap_decode — wb_range_decode with the elaboration audit REMOVED.
//
// The map it is given is the plausible kind of wrong: someone sized the GPIO
// window at 8 KiB "to leave room for expansion" and left the TIMER where the
// document already placed it, 4 KiB above GPIO's base. The two windows now
// share their upper half. Nothing in the source of either window looks
// incorrect; the defect exists only in the relationship between them, which
// is exactly why the check that catches it has to compare PAIRS.
//
// The decoder still computes each predicate correctly. Both are simply true.
module wb_overlap_decode #(
  parameter int unsigned BYTE_AW = 32,
  parameter int unsigned DW      = 32,
  parameter int unsigned NS      = 3,
  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_2000, 32'h0000_1000 },   // GPIO is 8 KiB
  localparam int unsigned WAW = BYTE_AW - $clog2(DW / 8)
) (
  input  logic [WAW-1:0] adr_i,
  output logic [NS-1:0]  sel_o,
  output logic [WAW-1:0] offset_o,
  output logic           unmapped_o
);
  localparam int unsigned SHIFT = $clog2(DW / 8);
  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
  function automatic logic [WAW-1:0] wbase_of(input int unsigned i);
    return WAW'(base_of(i) >> SHIFT);
  endfunction
  function automatic logic [WAW-1:0] wlast_of(input int unsigned i);
    return WAW'((base_of(i) + size_of(i) - 32'd1) >> SHIFT);
  endfunction

  // ── THE DEFECT: no pairwise overlap check. Nothing else differs. ──

  logic [NS-1:0] hit;
  always_comb begin
    hit = '0;
    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);
  // With two hits the last one wins here — an arbitrary outcome that the
  // correct decoder's one-hot guarantee made impossible rather than
  // resolving by accident of loop order.
  always_comb begin
    offset_o = adr_i;
    for (int unsigned i = 0; i < NS; i++)
      if (hit[i]) offset_o = adr_i - wbase_of(i);
  end
endmodule

It is Chapter 12.1's decoder with the initial block deleted and one SIZE changed. Every comparison, every conversion function and every line of the offset logic is identical — the defect is entirely in what is absent.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  === SIM C - the same map with one window resized ===
    GPIO widened to 8 KiB 'for expansion'. TIMER left where the
    document put it. No pairwise check anywhere.

    byte adr     correct map      overlapping map
    0x40000004   010  GPIO         010  GPIO
    0x40000ffc   010  GPIO         010  GPIO
    0x40001004   100  TIMER        110  MULTIPLE
    0x40001ffc   100  TIMER        110  MULTIPLE

    words in 0x4000_0000..0x4000_1FFC owned by two targets: 1024
    the correct map's elaboration check rejects this configuration
    outright, which is why it cannot be measured on that decoder.

Reading it

The first two rows are identical in both maps, and that is the danger. 0x4000_0004 and 0x4000_0FFC are GPIO in the correct map and GPIO in the broken one. Everything an engineer would naturally test about GPIO passes.

The break is at 0x4000_1004 — a TIMER register. The correct map says TIMER. The broken map says 110: GPIO and TIMER, both.

1024 words are owned by two targets. That is the entire upper half of GPIO's enlarged window, which is the whole of TIMER's window. Every timer register in the system has two owners, and a bare decoder has no way to prefer one.

What "two owners" costs is not ambiguity in the select vector. It is that both targets receive a qualified strobe, both execute the transfer, and both side effects happen. A write intended for the timer also lands in whatever GPIO has at that offset. Chapter 12.4 shows the qualification path that makes this literal.

The last line is the one that matters for practice. The correct decoder cannot be measured on this map because it refuses to elaborate — the pairwise check fires and the build stops. That is not a limitation of the experiment; it is the experiment's result. The broken decoder is the correct one with the check deleted, and deleting the check is the entire difference between a build failure and 1024 silently shared addresses.

6. Three More Ways, Same Map

SIM C is one defect. Here are the other three. The first two are given the running SoC's map and differ from the correct decoder in one expression.

The third needs a different window, because the running map has no region that violates the mask form's preconditions — every window in it is a power of two on a natural boundary, which is why Chapter 12.2 measured the two forms agreeing. So the third experiment substitutes a hypothetical 3 KiB window at GPIO's base, 0x4000_00000x4000_0BFF, and every statement about "the map" in that block refers to that window rather than to the running SoC's.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// Three more decode forms, published together so the differences between
// them are visible rather than described. All four decoders in this module
// are given the SAME map parameters; what differs is how they turn those
// parameters into a predicate.
// ─────────────────────────────────────────────────────────────────────────

// ── The off-by-one that a map document cannot catch ─────────────────────
// wb_offbyone_decode — wb_range_decode with the upper bound written as
// "<= base + size" instead of "<= base + size - 1".
//
// Every window is now ONE WORD too large. On an isolated window that shows
// up as an address that should have been unmapped being accepted. On two
// ADJACENT windows it is worse: each window's extra word is the next
// window's FIRST word, so the two overlap by exactly one location and the
// decoder answers with whichever the loop wrote last.
module wb_offbyone_decode #(
  parameter int unsigned BYTE_AW = 32,
  parameter int unsigned DW      = 32,
  parameter int unsigned NS      = 3,
  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 },
  localparam int unsigned WAW = BYTE_AW - $clog2(DW / 8)
) (
  input  logic [WAW-1:0] adr_i,
  output logic [NS-1:0]  sel_o,
  output logic [WAW-1:0] offset_o,
  output logic           unmapped_o
);
  localparam int unsigned SHIFT = $clog2(DW / 8);
  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
  function automatic logic [WAW-1:0] wbase_of(input int unsigned i);
    return WAW'(base_of(i) >> SHIFT);
  endfunction
  // ── THE DEFECT: no "- 1". One word too many, in every window. ──
  function automatic logic [WAW-1:0] wend_of(input int unsigned i);
    return WAW'((base_of(i) + size_of(i)) >> SHIFT);
  endfunction

  logic [NS-1:0] hit;
  always_comb begin
    hit = '0;
    for (int unsigned i = 0; i < NS; i++)
      hit[i] = (adr_i >= wbase_of(i)) && (adr_i <= wend_of(i));
  end
  assign sel_o      = hit;
  assign unmapped_o = ~(|hit);
  always_comb begin
    offset_o = adr_i;
    for (int unsigned i = 0; i < NS; i++)
      if (hit[i]) offset_o = adr_i - wbase_of(i);
  end
endmodule

// ── A mask decode on a window that does not satisfy its preconditions ───
// wb_unchecked_mask_decode — wb_mask_decode with the elaboration checks
// REMOVED, so it can be given a window that is not a power of two.
//
// This is the module that shows why those checks are $fatal rather than
// warnings. Given a 3 KiB window it does not refuse, and it does not
// approximate: (size - 1) for 0x0C00 is 0x0BFF, whose bit pattern is not a
// contiguous low-order run, so ~mask has holes in it and the equality
// matches a set of addresses that is not an interval at all.
module wb_unchecked_mask_decode #(
  parameter int unsigned BYTE_AW = 32,
  parameter int unsigned DW      = 32,
  parameter logic [31:0] BASE    = 32'h4000_0000,
  parameter logic [31:0] SIZE    = 32'h0000_0C00,   // 3 KiB: not a power of 2
  localparam int unsigned WAW = BYTE_AW - $clog2(DW / 8)
) (
  input  logic [WAW-1:0] adr_i,
  output logic           sel_mask_o,     // what the mask form decides
  output logic           sel_range_o     // what the map actually says
);
  localparam int unsigned SHIFT = $clog2(DW / 8);
  localparam logic [WAW-1:0] WBASE = WAW'(BASE >> SHIFT);
  localparam logic [WAW-1:0] WMASK = WAW'((SIZE - 32'd1) >> SHIFT);
  localparam logic [WAW-1:0] WLAST = WAW'((BASE + SIZE - 32'd1) >> SHIFT);

  // ── no preconditions checked, and no complaint made ──
  assign sel_mask_o  = ((adr_i & ~WMASK) == WBASE);
  assign sel_range_o = (adr_i >= WBASE) && (adr_i <= WLAST);
endmodule

// ── A decoder whose comparison is sized to the offset, not the address ──
// wb_truncated_decode — identical to wb_range_decode except that the
// comparison uses the low CMP_W bits of the word address instead of all of
// them.
//
// This is not a typo bug. It is what happens when a width is chosen from
// the wrong quantity: the windows are 4 KiB, so an offset needs 10 word
// bits, and 10 bits therefore looks like the natural width for "the address
// the decoder cares about". The offset needs 10 bits. The SELECT needs all
// 30, because that is where the targets differ.
//
// With CMP_W = 20 the top ten bits vanish, and RAM at word 0x0000_0000 and
// GPIO at word 0x1000_0000 become indistinguishable — their low twenty bits
// are identical. Two windows that the map placed a gigabyte apart now
// occupy the same place.
module wb_truncated_decode #(
  parameter int unsigned BYTE_AW = 32,
  parameter int unsigned DW      = 32,
  parameter int unsigned NS      = 3,
  parameter int unsigned CMP_W   = 20,     // how much of the address is used
  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 },
  localparam int unsigned WAW = BYTE_AW - $clog2(DW / 8)
) (
  input  logic [WAW-1:0] adr_i,
  output logic [NS-1:0]  sel_o,
  output logic           unmapped_o
);
  localparam int unsigned SHIFT = $clog2(DW / 8);
  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 DEFECT: the comparison is CMP_W bits wide, not WAW. ──
  logic [CMP_W-1:0] a_cmp;
  assign a_cmp = adr_i[CMP_W-1:0];

  function automatic logic [CMP_W-1:0] cbase_of(input int unsigned i);
    return CMP_W'(base_of(i) >> SHIFT);
  endfunction
  function automatic logic [CMP_W-1:0] clast_of(input int unsigned i);
    return CMP_W'((base_of(i) + size_of(i) - 32'd1) >> SHIFT);
  endfunction

  logic [NS-1:0] hit;
  always_comb begin
    hit = '0;
    for (int unsigned i = 0; i < NS; i++)
      hit[i] = (a_cmp >= cbase_of(i)) && (a_cmp <= clast_of(i));
  end
  assign sel_o      = hit;
  assign unmapped_o = ~(|hit);
endmodule

Read the three THE DEFECT comments and nothing else changes. A missing - 1, a width taken from the wrong quantity, and a pair of elaboration checks deleted — each is a single editing decision that a reviewer would have to be looking for.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  --- decode forms, same map, four implementations ---

  1. upper bound written as <= base + size

     byte adr     correct      off-by-one
     0x40000ffc   010 GPIO      010 GPIO
     0x40001000   100 TIMER     110 MULTI
     0x00001000   000 -none-    001 RAM

     addresses owned by two targets, correct decoder:     0
     addresses owned by two targets, off-by-one decoder:  1
     each window gained one word, and that word was the
     next window's first.

  2. comparison sized to the offset (20 bits) not the address

     byte adr     correct      truncated
     0x00000000   001 RAM       011 RAM+GPIO
     0x40000000   010 GPIO      011 RAM+GPIO
     0x80000000   000 -none-    011 RAM+GPIO

     GPIO's window words seen as two targets at once: 1024
     RAM and GPIO differ only above bit 19, so a 20-bit
     comparison cannot tell them apart at all.

  3. mask decode on a 3 KiB window (not a power of two)

     the map says 0x40000000 .. 0x40000BFF. mask = 0x0BFF >> 2.

     byte adr     map says   mask says
     0x40000000   inside     inside
     0x40000800   inside     inside
     0x40000bfc   inside     inside
     0x40000c00   outside    outside
     0x40000400   inside     outside

     words the mask ACCEPTS that the map excludes: 0
     words the mask REJECTS that the map includes: 256
     the mask form did not fail. It decoded a different
     region, and said nothing.

Reading it

Defect 1 — the missing - 1. 0x4000_1000 is TIMER's first word, and the off-by-one decoder calls it GPIO and TIMER. Every window grew by one word, and in a packed map that word is the next window's first. The row below it, 0x0000_1000, shows the isolated case: one word past RAM, with no neighbour, so it is simply accepted as RAM. Same defect, two symptoms — a misroute where windows touch, an over-wide window where they do not.

Defect 2 — a comparison sized to the offset. The reasoning is ordinary: the windows are 4 KiB, an offset needs ten word bits, so ten bits is what the decoder cares about. The offset needs ten bits. The select needs all thirty, because that is where targets differ. With the top ten bits discarded, RAM at word 0x0000_0000 and GPIO at word 0x1000_0000 become the same address — 1024 words with two owners, and 0x8000_0000, which is mapped nowhere, decodes as both.

Defect 3 is the one worth the most. A 3 KiB window is not a power of two, so the mask decoder's precondition fails — and with its checks removed it did not error, did not approximate, and did not warn.

It rejected 256 addresses the hypothetical 3 KiB window includes, and accepted none it excludes. 0x4000_0400 is inside by that window and outside by the mask. The mask did not produce a slightly wrong interval; it produced something that is not an interval at allSIZE - 1 for 3 KiB is 0x0BFF, whose bit pattern is not a contiguous run, so the "prefix" it forms has a hole in the middle of the window.

7. Decode Timing

The decoder is combinational, so the select vector is valid whenever the address is — and Chapter 4.3 established that ADR_O is meaningful only while STB_O qualifies it (RULE 3.60).

Decode is combinational; qualification is not

8 cycles
Eight clock cycles. The master drives an address continuously from cycle one, and the decoder's select vector follows it combinationally with no clock delay, showing GPIO selected from cycle one onward. The cycle and strobe signals are asserted only in cycles three and four. The qualified strobe delivered to the GPIO target is asserted only in those two cycles, and the acknowledge returns in cycle four. Before cycle three the select vector is already correct but no target has been asked anything.address valid, decode already doneaddress valid, decodealready donestrobe: now GPIO has been askedstrobe: now GPIO has beenaskedGPIO answers; TIMER never saw itGPIO answers; TIMER neversaw itCLK_IADR_O0AAAAAAAsel[GPIO]CYC_O / STB_Ogpio STB_Itimer STB_IACK_It0t1t2t3t4t5t6t7

The select vector is correct two clocks before anything happens with it, and that is harmless because nothing consumes it until STB_O is asserted. Decoding an address the master is not presenting costs nothing and means nothing.

timer STB_I is flat across the whole figure. That is the property Chapter 12.4 formalises: an unselected target is not merely ignored, it is never asked, so by the STB_O description it owes no answer.

Nothing here is a Wishbone timing requirement. The specification does not say a decoder must be combinational — a registered decoder inserting a wait state would be equally conformant, and Module 9 covers what that costs. This is a LOCAL implementation choice, made because the decode fits comfortably in a cycle at this size.

8. Failure Modes and Discriminating Evidence

Symptom: a write to one peripheral also changes a register in another.

Candidate causes. Overlapping windows, so two targets were strobed and both executed.

Discriminating evidence. The select vector at the presenting clock — $countones(sel). Two is conclusive and needs no further investigation of either peripheral. Both targets are behaving correctly; they were both asked.

Likely RTL location: the map parameters, and the absent pairwise check.

Symptom: an address in the middle of a region is rejected, but both ends work.

Candidate causes. A mask decode on a window that is not an aligned power of two.

Discriminating evidence. Check SIZE for power-of-two and BASE % SIZE for zero. If either fails, the mask is not equivalent to the range and the set it selects is not an interval. Boundary probes will not find this — the failure is interior, which is what makes it distinctive.

Where the fault sits: the choice of decode form against the map, not the decoder's code.

Symptom: two regions a long way apart behave as one.

Candidate causes. A comparison narrower than the address, so the bits that distinguish them are discarded.

Discriminating evidence. Compare the declared width of the comparison against the address width. Then check whether the two colliding bases differ only above that width. The signature is that the collision is exact — every address in one window maps to one in the other, rather than a scattered overlap.

Symptom: the design fails to elaborate after a map edit.

Candidate causes. The map now overlaps, is misaligned, or has a non-power-of-two window under a mask decoder.

Discriminating evidence. Read the $fatal message; it names the regions. This is the success case, not a failure — every other symptom in this list is what happens when these checks are absent.

9. Verification

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────
// wb_form_props — properties about the DECODE FORM rather than the map.
//
// NOTE ON EXECUTION: Icarus Verilog does not support SVA. These were
// reviewed by inspection and are NOT claimed to have been executed. The
// numbers in Sections 5 and 6 come from procedural checks, which Icarus
// does run.
// ─────────────────────────────────────────────────────────────────────────
module wb_form_props #(
  parameter int unsigned NS = 3
) (
  input logic          clk_i,
  input logic          rst_i,
  input logic [NS-1:0] sel_range_i,     // the general decoder
  input logic [NS-1:0] sel_mask_i,      // the cheap decoder
  input logic [29:0]   offset_range_i,
  input logic [29:0]   offset_mask_i
);
  default disable iff (rst_i);

  // P14 — LOCAL, AND CONDITIONAL ON THE MAP.
  // The two forms agree. This is TRUE of the running map and FALSE in
  // general: it holds only while every window is a power of two and
  // naturally aligned. Section 6 measures a window where it fails, and the
  // property is stated here precisely so that the condition is recorded
  // alongside the claim rather than remembered separately.
  property p_forms_agree;
    @(posedge clk_i) sel_range_i == sel_mask_i;
  endproperty
  a_forms_agree: assert property (p_forms_agree);

  // P15 — LOCAL, AND CONDITIONAL ON THE SAME PRECONDITIONS.
  // Masking and subtracting give the same offset. The equivalence has
  // exactly the same preconditions as P14, because it is the same fact
  // about prefixes applied to the low bits instead of the high ones.
  property p_offsets_agree;
    @(posedge clk_i) (sel_range_i != '0) |->
      (offset_range_i == offset_mask_i);
  endproperty
  a_offsets_agree: assert property (p_offsets_agree);

  // P16 — LOCAL ADDRESS-MAP POLICY, and the one SIM C violates.
  // No two targets are ever selected together. Stated on the general
  // decoder because that is the one that can be given an overlapping map
  // without refusing to elaborate.
  property p_no_double_owner;
    @(posedge clk_i) $onehot0(sel_range_i);
  endproperty
  a_no_double_owner: assert property (p_no_double_owner);
endmodule

P14 is deliberately written as a claim with its conditions attached. "The two decoders agree" is true of this map and false as a general statement — Section 6 measures the counterexample. A property that is conditional and does not say so is worse than no property, because it reads as a proof of something it does not prove.

P16 is stated on the range decoder rather than the mask decoder for a practical reason: the mask decoder refuses to elaborate on an overlapping map, so the property could never fail on it. A property that cannot fail is not evidence.

10. Common Mistakes

"Masks and ranges are two ways of writing the same thing."

Wrong mental model: the forms are interchangeable.

What is true: a mask is a range only under two conditions. Section 6 shows a 3 KiB window where the mask rejects 256 addresses that window contains. The mask form is a specialisation, and using a specialisation outside its domain is not a style choice.

"addr <= BASE + SIZE is the normal range test."

Wrong mental model: SIZE is the last offset.

What is true: SIZE is a count, so the last valid address is BASE + SIZE - 1. The measured consequence is in Section 6's first block — 0x4000_1000 owned by GPIO and TIMER simultaneously. And the expression can overflow, which is the second, quieter reason not to write it at runtime at all.

"The decoder only needs as many bits as the window."

Wrong mental model: one width serves both jobs.

What is true: the offset needs the window's width; the select needs the whole address. Section 6's second block collapses RAM and GPIO into one target because the ten bits that distinguish them were discarded. The two jobs from Chapter 12.1 have different width requirements, and using one number for both is how they get confused.

"casez is a convenient way to express don't-care address bits."

Wrong mental model: wildcards are free.

What is true: casez also treats Z and ? in the expression as matching, so an X on the address can match a pattern it should not. A decoder that is optimistic about X is one whose simulation disagrees with its silicon — on the block that decides transfer ownership.

"Elaboration checks are belt-and-braces; the map is reviewed anyway."

Wrong mental model: review catches map errors.

What is true: overlap is not visible in any single window's declaration, and a mask precondition violation is not visible in the decoder at all. The one defect in this chapter that boundary testing also misses is the mask precondition — the checks are not redundant with review or with simulation.

11. Interview Reasoning

When the window's size is a power of two and its base is a multiple of that size.

Why power-of-two size. The mask is SIZE - 1. That is a contiguous run of low-order ones only when SIZE is a power of two, and only then is ~MASK a contiguous prefix. For 3 KiB, SIZE - 1 is 0x0BFF, which has a gap — and the resulting predicate selects a set that is not an interval.

Why natural alignment. The prefix comparison asks whether the address's high bits equal the base's high bits. That question is equivalent to the range question only if every address in the window shares the base's prefix, which requires the base to sit on a boundary of its own size.

What to add. The checks belong at elaboration and they belong fatal, because the failure is silent at runtime and interior rather than at the boundaries, so a normal boundary test does not find it.

And it is worth saying what is gained. An AND and an equality instead of two magnitude comparators, and the offset comes free from the same mask.

12. Understanding Check

Because both are GPIO addresses, and GPIO's window was widened rather than moved.

The broken map gave GPIO 8 KiB starting at the same base. Everything that was GPIO before is still GPIO. 0x4000_0004 and 0x4000_0FFC are inside the original 4 KiB and inside the enlarged 8 KiB, so both decoders agree.

The damage is entirely in the half that was added, which is where TIMER already lived. A test suite that exercises GPIO thoroughly finds nothing.

This is the general shape of an overlap bug. The enlarged region works; the region it swallowed is the casualty. So the test that finds it is not "does GPIO work" but "is any address owned twice" — which is $countones(sel), and which needs no knowledge of what the map was supposed to be.

13. What's Next

The decoder is finished: two forms, each with its preconditions enforced at elaboration, and four measured ways of getting it wrong.

It produces a select vector and an offset, and so far nothing consumes them.

How does a selected target receive the transfer — and only that target?

Chapter 12.4 — Peripheral Selection builds the request qualification and the response path, and measures a read that returns the wrong peripheral's data through an interconnect whose select logic is entirely correct. 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.