Wishbone · Module 13
Alignment
The two address bits the bus does not carry become the select pattern. Measured across every offset and size, including the misaligned operand that fits in one transfer.
Chapter 13.3 proved partial writes correct across every select pattern. Every pattern arrived ready-made.
Software does not have select patterns. It has a pointer and a type.
Where does
SELcome from, and what happens when the bytes software wants do not fit in one word?
1. The Split
One byte address, two destinations:
byte address 0x0000_1002
|
+-- bits 31..2 -> ADR = 0x0000_0400 which word
|
+-- bits 1..0 -> offset = 2 where in it
|
+-- with the size -> SEL = 0100No information is lost and none is duplicated. P9 and P10 in Section 6 state exactly that: the word address is the byte address shifted, and the offset is the bits the shift discarded.
The offset alone is not SEL. A byte at offset 2 gives 0100; a halfword at offset 2 gives 1100. The size decides how many lanes; the offset decides where the run starts.
2. Which Lane the Offset Picks Is an Endianness Question
Chapter 13.1 separated two layers and this is where the separation pays.
Layer one is fixed. RULE 3.100 binds SEL(n) to DAT(8n+7..8n). Wiring. It never moves.
Layer two is chosen. Which byte number sits on which lane is the endianness of the system, and the same normative figure gives both:
| byte offset in the word | little endian | big endian |
|---|---|---|
| 0 | SEL[0] | SEL[3] |
| 1 | SEL[1] | SEL[2] |
| 2 | SEL[2] | SEL[1] |
| 3 | SEL[3] | SEL[0] |
RULE 2.15 requires a core's datasheet to indicate the data transfer ordering as BIG ENDIAN or LITTLE ENDIAN. This course is little endian throughout — a SOC CONVENTION, stated rather than assumed, and Chapter 4.7 declared it.
A slave never converts. It receives lane numbers and applies them to its own storage. A mixed-endian system needs an explicit converter, placed deliberately — a slave that tries to compensate applies the correction twice.
3. RTL — The Adapter
// ─────────────────────────────────────────────────────────────────────────
// wb_byte_address_adapter — software's byte address and operand size become
// a Wishbone word address plus a select pattern.
//
// THREE NUMBERS, KEPT APART. Module 12 insisted on the first two; this
// module adds the third.
//
// byte_adr_i what a C pointer holds. Byte units.
// adr_o what appears on ADR. WORD units — the organization32
// figure lists ADR_I/ADR_O(63..02) for a 32-bit port with
// 8-bit granularity, so the low two bits are not on the
// wires at all (Chapter 4.3).
// sel_o which lanes of that one word carry the operand.
//
// The low two bits of the byte address do not vanish. They become sel_o.
// That is the whole content of this module: ADR says WHICH WORD, SEL says
// WHERE IN IT, and the bits that stopped being address became select.
//
// ENDIANNESS. The lane binding SEL[n] <-> DAT[8n+7:8n] is normative
// (RULE 3.100) and fixed. Which BYTE NUMBER of an operand sits on which
// lane is NOT fixed — the same organization32 figure gives BIG and LITTLE
// ENDIAN as two different rows. RULE 2.15 requires a core's datasheet to
// state which it uses.
//
// LITTLE ENDIAN: byte offset 0 -> lane 0 -> SEL[0]
// BIG ENDIAN: byte offset 0 -> lane 3 -> SEL[3]
//
// THIS ADAPTER IS LITTLE ENDIAN, matching the convention Chapter 4.7
// declared for the whole course. That is a SOC CONVENTION, not a Wishbone
// requirement, and LITTLE_ENDIAN can be cleared to get the other one.
//
// ALIGNMENT IS LOCAL POLICY. Nothing in Wishbone forbids a misaligned
// access. What the specification does say (RULE 2.15) is that a datasheet
// states the port's granularity and maximum operand size; whether a given
// operand at a given offset can be carried is then a property of THIS
// system. This adapter declares its policy in ok_o/reason_o and rejects
// nothing silently.
// ─────────────────────────────────────────────────────────────────────────
module wb_byte_address_adapter #(
parameter int unsigned BYTE_AW = 32,
parameter int unsigned DW = 32,
parameter int unsigned GRAN = 8,
parameter bit LITTLE_ENDIAN = 1'b1,
localparam int unsigned SELW = DW / GRAN,
localparam int unsigned SHIFT = $clog2(DW / 8),
localparam int unsigned WAW = BYTE_AW - SHIFT
) (
input logic [BYTE_AW-1:0] byte_adr_i,
input logic [2:0] size_i, // operand size in BYTES: 1, 2 or 4
output logic [WAW-1:0] adr_o, // WORD address
output logic [SELW-1:0] sel_o,
output logic [1:0] off_o, // byte offset within the word
output logic ok_o, // representable in ONE transfer
output logic [1:0] reason_o // 0 ok, 1 bad size, 2 crosses word
);
initial begin
if (DW % GRAN != 0)
$fatal(1, "wb_byte_address_adapter: DW not a multiple of GRAN");
if (GRAN != 8)
$fatal(1, "wb_byte_address_adapter: assumes byte granularity");
end
localparam int unsigned BPW = DW / 8; // bytes per bus word
logic [SHIFT-1:0] off;
assign off = byte_adr_i[SHIFT-1:0];
assign off_o = 2'(off);
assign adr_o = byte_adr_i[BYTE_AW-1 -: WAW]; // == byte_adr_i >> SHIFT
// A contiguous run of `size` lanes starting at the byte offset. Built as
// a run over LANE indices, then flipped if the system is big endian —
// because the flip is about byte NUMBERING, and doing it in one named
// place is what keeps it from leaking into the mask expansion.
logic [SELW-1:0] run_lo;
always_comb begin
run_lo = '0;
for (int unsigned n = 0; n < SELW; n++)
if ((32'(n) >= 32'(off)) &&
(32'(n) < 32'(off) + 32'(size_i)))
run_lo[n] = 1'b1;
end
logic [SELW-1:0] run_be;
always_comb begin
run_be = '0;
for (int unsigned n = 0; n < SELW; n++)
run_be[SELW-1-n] = run_lo[n];
end
// Policy, stated once. Size must be a supported operand, and the operand
// must lie inside one bus word — a byte set that straddles the boundary
// is not one word's worth of lanes, so no single SEL pattern names it.
logic size_ok, fits;
assign size_ok = (size_i == 3'd1) || (size_i == 3'd2) || (size_i == 3'd4);
assign fits = (32'(off) + 32'(size_i)) <= 32'(BPW);
assign ok_o = size_ok && fits;
assign reason_o = !size_ok ? 2'd1 : (!fits ? 2'd2 : 2'd0);
// A rejected request emits no selects. Driving a partial pattern for a
// request the adapter has refused would invite a caller to use it.
assign sel_o = !ok_o ? '0 : (LITTLE_ENDIAN ? run_lo : run_be);
endmoduleReading it
adr_o and off_o are the two halves of one number, taken from the same input with no arithmetic between them. There is no opportunity for them to disagree.
run_lo is built over lane indices, then mirrored once for big endian. The flip lives in one named place. Putting it anywhere else — in the mask expansion, or in the target — is how a system ends up correcting twice.
ok_o and reason_o are the policy, and the policy is declared rather than enforced silently. A caller is told whether the request is representable and why not when it is not.
A refused request emits sel_o = '0'. Driving a partial pattern for an operand the adapter has already refused would invite a caller to use it — and the partial pattern would name the wrong bytes.
4. Simulation — SIM E: A Byte Address Becomes ADR Plus SEL
Nine translations, walking one byte at a time across a word boundary and then taking halfwords and words at their offsets.
=== SIM E - a byte address becomes ADR plus SEL ===
32-bit port, byte granularity. ADR is a WORD address, so
the two low bits of the byte address are not on the wires
- they become the byte offset, and the offset picks lanes.
Little endian: byte offset 0 is lane 0.
byte adr size ADR (word) off SEL classification
0x00001000 byte 0x00000400 0 0001 one transfer
0x00001001 byte 0x00000400 1 0010 one transfer
0x00001002 byte 0x00000400 2 0100 one transfer
0x00001003 byte 0x00000400 3 1000 one transfer
0x00001004 byte 0x00000401 0 0001 one transfer
0x00001000 halfword 0x00000400 0 0011 one transfer
0x00001002 halfword 0x00000400 2 1100 one transfer
0x00001000 word 0x00000400 0 1111 one transfer
0x00001004 word 0x00000401 0 1111 one transfer
The first five rows walk one byte at a time. The ADR column
changes only at 0x1004, where the byte address crossed into
the next word - four byte addresses share one ADR, and SEL
is what tells them apart.
--- the same byte addresses, big endian ---
byte adr off SEL little SEL big
0x00001000 0 0001 1000
0x00001001 1 0010 0100
0x00001002 2 0100 0010
0x00001003 3 1000 0001
Same address, same lane wiring, different lane chosen.
RULE 3.100 fixes SEL(n) to DAT(8n+7..8n) in both columns;
what moved is which byte NUMBER sits on which lane.
RULE 2.15 requires a datasheet to say which ordering a
core uses. This course is little endian throughout.Reading it
The first four rows share one ADR and differ only in SEL. 0x1000, 0x1001, 0x1002, 0x1003 all give ADR = 0x00000400, with select 0001, 0010, 0100, 1000. Four distinct byte addresses, one word, four lanes — which is the split of Section 1, measured.
Row five is the boundary. 0x1004 is the first byte of the next word: ADR steps to 0x00000401 and SEL returns to 0001. The ADR column changes exactly once in five rows, and that is the only place it can change.
Then the halfwords. Offset 0 gives 0011 — lanes 1 and 0. Offset 2 gives 1100 — lanes 3 and 2. The size sets the run length and the offset sets where it starts.
The word rows give 1111 at both word-aligned addresses, which is the configuration everything before this module used implicitly.
The big-endian block is the same four byte addresses through an adapter differing in one parameter. Byte offset 0 selects lane 0 in one column and lane 3 in the other; the columns are mirror images. Neither is more correct, and the lane wiring is identical in both — RULE 3.100 is not what changed.
5. Simulation — SIM F: What One Transfer Cannot Represent
A halfword at offset 3 needs byte 3 of this word and byte 0 of the next. One transfer presents one ADR, so no select pattern names both.
=== SIM F - operands one bus word cannot carry ===
a halfword at offset 3 needs bytes 3 and 4. Byte 4 is in
the NEXT word, and one transfer presents one ADR.
byte adr size ADR (word) off SEL classification
0x00001003 halfword 0x00000400 3 0000 crosses word
0x00001001 word 0x00000400 1 0000 crosses word
0x00001002 word 0x00000400 2 0000 crosses word
0x00001003 word 0x00000400 3 0000 crosses word
0x00001000 unsupported 0x00000400 0 0000 bad size
SEL reads 0000 on every rejected row. The adapter emits no
lanes for a request it refused, so a caller cannot use a
partial pattern for an operand that was never representable.
MISALIGNED AND CROSS-WORD ARE NOT THE SAME TEST:
0x00001001 halfword 0x00000400 1 0110 one transfer
A halfword at offset 1 is misaligned by the usual software
rule - its address is not a multiple of its size - and it
still fits inside one bus word, so one transfer carries it
with SEL = 0110. What one transfer cannot represent is a
byte set spanning TWO words, because a transfer presents
one ADR. Cross-word is the constraint; alignment is a
separate convention that this adapter does not enforce.
--- every offset and size, classified ---
size off 0 off 1 off 2 off 3
byte single single single single
halfword single single single cross-word
word single cross-word cross-word cross-word
8 of the 12 combinations fit in one transfer; 4 span two.
NOTHING IN WISHBONE FORBIDS THE 4 THAT SPAN. The spec has
no alignment rule; it requires a datasheet to state the
port's granularity and maximum operand size (RULE 2.15), and
leaves what a core accepts to the core. Rejecting them is
THIS SoC'S POLICY. Splitting them into two transfers would
be equally conformant, and RECOMMENDATION 3.20 advises the
low-address half first if a design chooses to split.
cases probed 15 single transfer 10 crosses word 4 bad size 1Reading it
Four rejected rows, all with SEL = 0000. The adapter refuses and emits no lanes, so a caller cannot take a partial pattern and use it for an operand that was never representable.
Then the row that separates two ideas people routinely fuse.
A halfword at offset 1 is misaligned — its address is not a multiple of its size, which is the usual software definition and what a processor with alignment requirements would fault on. And it fits in one bus word, so this adapter carries it with SEL = 0110.
What one transfer cannot represent is a byte set spanning two words. That is the constraint, and it is structural: one transfer, one ADR.
| misaligned | cross-word | |
|---|---|---|
| halfword at offset 1 | yes | no — SEL = 0110 |
| halfword at offset 3 | yes | yes — refused |
| word at offset 0 | no | no |
| word at offset 1 | yes | yes — refused |
The two tests coincide often enough to look like one test. They are not, and the halfword at offset 1 is the counterexample.
The matrix then classifies the whole space. Twelve combinations of three sizes and four offsets; eight fit in one transfer and four span two. Every byte fits — a single byte can never cross a boundary. Halfwords fail only at offset 3. Words fail everywhere but offset 0.
And the count comes from the design, not from the prose. The testbench classifies each cell by asking the adapter and tallies the result, so the sentence below the matrix cannot drift from the table above it.
6. Verification
// ─────────────────────────────────────────────────────────────────────────
// wb_adapter_props — the address/lane translation and the alignment policy.
// ─────────────────────────────────────────────────────────────────────────
module wb_adapter_props #(
parameter int unsigned DW = 32,
parameter int unsigned GRAN = 8,
localparam int unsigned SELW = DW / GRAN
) (
input logic clk_i,
input logic [31:0] byte_adr_i,
input logic [2:0] size_i,
input logic [29:0] adr_i,
input logic [SELW-1:0] sel_i,
input logic [1:0] off_i,
input logic ok_i
);
// P9 — SPEC-DERIVED (DESC ADR_O, and the organization32 figure).
// The word address is the byte address with the granularity bits removed.
// The figure lists ADR_I/ADR_O(63..02) for a 32-bit byte-granular port,
// so those two bits are not carried and cannot be recovered from ADR.
property p_word_address;
@(posedge clk_i) adr_i == byte_adr_i[31:2];
endproperty
a_word_address: assert property (p_word_address);
// P10 — SPEC-DERIVED, same source.
// The bits ADR does not carry are exactly the byte offset. Together with
// P9 this says no information was lost: the address split in two.
property p_offset_is_low_bits;
@(posedge clk_i) off_i == byte_adr_i[1:0];
endproperty
a_offset_is_low_bits: assert property (p_offset_is_low_bits);
// P11 — LOCAL SOC POLICY (little endian, declared by this course).
// For an accepted operand the selected lanes are exactly the run of
// `size` lanes starting at the byte offset. Under BIG ENDIAN the same
// adapter produces the mirrored run, which is why this property names
// its endianness instead of assuming one.
property p_lane_run;
@(posedge clk_i) (ok_i) |->
(sel_i == (SELW'((1 << size_i) - 1) << off_i));
endproperty
a_lane_run: assert property (p_lane_run);
// P12 — LOCAL SOC POLICY.
// A refused request selects nothing. Wishbone forbids neither misaligned
// nor cross-word operands; this adapter declines to represent the ones it
// cannot carry in a single transfer, and emits no partial pattern that a
// caller might mistake for a usable one.
property p_rejected_selects_nothing;
@(posedge clk_i) (!ok_i) |-> (sel_i == '0);
endproperty
a_rejected_selects_nothing: assert property (p_rejected_selects_nothing);
endmoduleP9 and P10 together say the address split loses nothing. One takes the high bits, the other takes the low bits, and between them they account for every bit of the input. Either alone would be satisfied by an implementation that dropped information.
P11 names its endianness in the property text, because the property is false under the other one. A conditional property that does not state its condition is worse than no property — it reads as a proof of something it does not prove, which is the same discipline Chapter 12.3's P14 needed.
P12 is the policy, and it is the only one here that another conformant SoC could legitimately fail. A design that splits cross-word operands would produce a partial pattern and a second transfer. That design is not wrong; it has a different policy, and P12 belongs to this one.
7. Failure Modes and Discriminating Evidence
Symptom: every access is off by a factor of four.
Candidate causes. A byte address wired directly to ADR, or a word address used as a byte address.
Discriminating evidence. The factor itself. Exactly 4, or exactly 1/4 — not an arbitrary displacement. Compare the byte address shifted right by two against the observed ADR. This is Chapter 12.1's units audit at a different layer.
Likely RTL location: the adapter's adr_o assignment, or a caller bypassing the adapter.
Symptom: a byte write works at offset 0 and fails at offset 3.
Candidate causes. A lane run that does not shift with the offset, or an offset taken from the wrong bits.
Discriminating evidence. SEL at each of the four offsets. If it is 0001 regardless of offset, the shift is missing. If it is right at 0 and 1 but wrong at 2 and 3, the offset field is too narrow. SIM E's first four rows are this test.
Symptom: byte accesses land on the wrong byte, consistently mirrored.
Candidate causes. An endianness disagreement between software and the adapter.
Discriminating evidence. Whether offset 0 selects lane 0 or lane 3. Mirroring across the word is endianness; an arbitrary permutation is a wiring bug. RULE 2.15 says the datasheet should have settled which the core uses, and the fix belongs in a converter placed deliberately, not inside a slave.
Symptom: an operand at a legal-looking address is refused.
Candidate causes. It crosses a word boundary.
Discriminating evidence. offset + size against the bytes per word. Greater than four means no single transfer can carry it. This is correct behaviour under this SoC's policy — the bug, if any, is in whatever generated an unaligned operand for a system that does not split them.
Symptom: a cross-word access silently reads or writes the wrong bytes.
Candidate causes. An adapter that truncates the lane run at the word boundary instead of refusing.
Discriminating evidence. A non-zero SEL on a request whose offset plus size exceeds the word. That pattern names a subset of the intended bytes — a partial operand presented as a whole one, which is worse than a refusal because nothing reports it.
8. Common Mistakes
"A byte address can be connected to ADR directly."
Wrong mental model: the address is the address.
What is true: ADR is a word address on this port. The specification's example for a 32-bit byte-granular port is ADR_O(n..2), and the normative organization figure confirms it. The low bits are not dropped — they become SEL.
"Misaligned means illegal Wishbone."
Wrong mental model: the protocol has alignment rules.
What is true: it has none. RULE 2.15 requires a datasheet to state granularity and maximum operand size; what a core accepts follows from that. This SoC refuses cross-word operands as LOCAL POLICY, and another could split them.
"Misaligned and cross-word are the same test."
Wrong mental model: one condition.
What is true: a halfword at offset 1 is misaligned and fits in one transfer, with SEL = 0110. SIM F measures it beside the ones that do not fit. Alignment is a software convention; fitting in one word is a structural constraint.
"SEL[0] is always the lowest-addressed byte."
Wrong mental model: lane number is byte number.
What is true: only under little endian, which SIM E shows by running both adapters on the same four addresses. The lane wiring is identical in both columns.
"A cross-word access just needs a wider SEL."
Wrong mental model: more lanes would fix it.
What is true: one transfer presents one ADR. The bytes are in two different words, so no select pattern of any width names them. Two transfers, or a refusal — and which one is the architecture's choice.
9. Interview Reasoning
The address splits; it does not shrink.
The high bits become ADR. On a 32-bit port with byte granularity the address array is ADR_O(n..2) — the specification's own example — so ADR = byte_address >> 2. Four byte addresses share one ADR.
The low bits become the offset, which with the operand size determines the lane run: size consecutive lanes starting at the offset. A byte at offset 2 gives 0100; a halfword at offset 2 gives 1100.
Nothing is lost. The two bits the bus does not carry are exactly the two bits SEL needs.
The part worth volunteering is the endianness layer. Which lane the offset picks is a system convention — little endian puts offset 0 on lane 0, big endian on lane 3 — and RULE 2.15 requires the datasheet to say which. The lane-to-wire binding underneath is fixed by RULE 3.100 and does not move.
10. Understanding Check
No — it moved to SEL.
ADR is a word address on this port, so 0x1000, 0x1001, 0x1002 and 0x1003 all name word 0x00000400. That much is genuinely the same.
What distinguishes them is the select pattern: 0001, 0010, 0100, 1000. The two bits that stopped being address became four bits of lane selection.
Count it. Two bits of byte address can express four positions; four lanes of select can express those four positions and eleven more combinations besides. Nothing was dropped.
Where information would genuinely be lost is SEL = 0000 — a transfer naming a word and no lanes within it. Chapter 13.1 covers that; it is permitted, and it accomplishes nothing.
11. What's Next
The translation is complete: a byte address splits into ADR and SEL, the endianness layer is named rather than assumed, and every offset and size in the configuration is classified.
Every register in this module so far has done the same thing with a selected lane: taken the data. Real peripherals do not all agree about that.
What does "this lane participates" mean to a status register, or to a command port?
Chapter 13.5 — Data Masking applies one mask to three different register semantics, and measures a command that fires from a lane the transfer never delivered. The full path is on the Wishbone curriculum index.
Continue learning
Related tutorials
- Related topic
Data Transfer
Selection routes a request; it does not move a value. Write data fans out, read data fans in through a multiplexer that every target widens, byte lanes decide which parts of a word participate, and a register narrower than the bus has to be placed on a lane rather than merely connected.
- Related topic
SEL_O
How a 32-bit bus writes one byte without disturbing the other three: byte lanes, the per-lane conditional write, where endianness actually lives, and why ignoring the mask destroys data the transfer never named.
- Related topic
Address Maps
An address map is a contract with five signatories and only one of them is checked by a compiler. Measured at the four addresses per region where decode bugs live.
- Related topic
SEL Signals
The address names a word; SEL names which bytes of it take part. The lane binding is normative, the byte numbering is not, and confusing the two is the expensive mistake.
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.
