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.
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 answerAll 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:
| width | example | |
|---|---|---|
| software byte address | 32 | 0x4000_1004 |
Wishbone ADR (word) | 30 | 0x1000_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.
| target | byte window | size | implemented | word window |
|---|---|---|---|---|
| RAM | 0x0000_0000 … 0x0000_0FFF | 4 KiB | 256 words | 0x0000_0000 … 0x0000_03FF |
| GPIO | 0x4000_0000 … 0x4000_0FFF | 4 KiB | 2 registers | 0x1000_0000 … 0x1000_03FF |
| TIMER | 0x4000_1000 … 0x4000_1FFF | 4 KiB | 2 registers | 0x1000_0400 … 0x1000_07FF |
| everything else | — | — | nothing | unmapped |
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 inventory — Chapter 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.
// ─────────────────────────────────────────────────────────────────────────
// 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
endmoduleReading 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
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.
=== 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.
| written | true when | false when |
|---|---|---|
$onehot0(sel) | zero or one bit set | two or more bits set |
$onehot(sel) | exactly one bit set | zero 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:
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
// ─────────────────────────────────────────────────────────────────────────
// 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);
endmoduleThree 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
- 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
Memory-Mapped IO
Memory-mapped I/O does not turn a peripheral into memory. It gives the peripheral's registers addresses in the processor's address space, so an ordinary load or store selects them. The address then does two jobs — name the target, name the register inside it — and the map that assigns them is a contract between software and RTL.
- 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
Address Space
An address is a number until a decoder turns it into a target selection and a local offset. Range comparison and mask comparison are different engineering choices with different costs; exhaustive, mutually exclusive decode is a property to be proved rather than assumed; and a flat decoder's critical path is what eventually forces a hierarchy.
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.
