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.
// ─────────────────────────────────────────────────────────────────────────
// 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
endmoduleReading 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:
// 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
endThat 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:
| check | range decode | mask decode |
|---|---|---|
| zero size | yes | yes |
| smaller than one bus word | yes | yes |
BASE + SIZE overflows | yes | — |
| word-aligned base | yes | — |
| power-of-two size | — | yes |
| base aligned to its own size | — | yes |
| pairwise overlap | yes | yes |
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.
// ── 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
endmoduleIt 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.
=== 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_0000 … 0x4000_0BFF, and every statement about "the map" in that block refers to that window rather than to the running SoC's.
// ─────────────────────────────────────────────────────────────────────────
// 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);
endmoduleRead 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.
--- 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 all — SIZE - 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 cyclesThe 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
// ─────────────────────────────────────────────────────────────────────────
// 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);
endmoduleP14 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
- Related topic
CPU to Peripheral Communication
A CPU reaches hardware outside itself by reading and writing addressed locations, and a peripheral is hardware it cannot execute. Everything a driver does has to be expressed as a read or a write of a location the peripheral answers for — and once more than a couple of peripherals exist, wiring each one to the core separately stops scaling. That is the problem an on-chip bus is the answer to.
- Related topic
Need for Standardized Interconnects
An address map answers where a register lives. It says nothing about which wires carry the request, when they are valid, how the target reports completion, or what happens on an error. Three peripherals with three private interfaces produce three adapters, three verification efforts and three ways to be wrong — which is the argument for standardising the interface rather than the map.
- Related topic
FPGA Design Challenges
Six peripherals and two masters on one FPGA is where on-chip integration stops being theory. The decode that must be exhaustive and one-hot, the read multiplexer that grows with every target and carries the critical path, the latency and reset conventions that refuse to agree, and the point at which the fabric rather than the peripherals starts failing timing.
- Related topic
Masters and Slaves
Master and slave are transaction roles, not a statement about importance or hierarchy. The role determines exactly which information each side owns: the initiator supplies address, direction and write data; the target supplies read data, completion and any error. Getting that ownership wrong is the source of an entire family of integration bugs.
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.
