UVM RAL · Chapter 3 · Register Maps
Endianness, Bus Width & Byte Enables
Three properties of a register map decide how a register's bytes physically travel on the bus. Bus width sets how many byte lanes the data bus carries at once, which determines whether a register fits in one transfer, is split across several, or shares a transfer with its neighbours. Endianness sets the order in which the pieces of a register wider than the bus go out. Byte enables are a per-lane write mask that makes a true sub-word write possible, so you can update one field or one packed register without a read-modify-write of the whole word. This lesson draws the byte lanes and shows how offset and endianness assign bytes to them, then breaks a design that packs four narrow registers into one bus word with no byte enables, so writing one register clobbers the other three.
Foundation12 min readUVM RALEndiannessByte EnablesBus WidthByte LanesSub-word Access
Chapter 3 · Section 3.3 · Register Maps
1. Why Should I Learn This?
Byte lanes are where the abstract map meets the physical bus, and mistakes here fail in ways that look like data corruption rather than addressing: a register accessed on the wrong lane, a partial write that was not partial, a packed register whose neighbours get clobbered. They are also where a real design decision lives — whether to pack narrow registers into shared words, which is only safe if the bus can write individual lanes. Getting endianness, width, and byte enables right is what makes every wide and every packed register behave, and understanding byte enables is what lets you reason about when a sub-word or single-field write is actually possible.
Learning this closes the loop opened in 2.3: individually_accessible is a promise the bus keeps with byte enables, and this page shows the mechanism. It is the difference between assuming a field or a packed register can be written alone and knowing whether the bus can honour it.
2. Industry Story — the four registers that shared a word
A low-area block packs four unrelated 8-bit control registers into a single 32-bit word at offset 0x00 — REG_A in byte 0, REG_B in byte 1, REG_C in byte 2, REG_D in byte 3 — a common trick to save address space. The bus, however, is a plain APB with no byte enables: it can only write all 32 bits at once.
Firmware and the testbench both discover the problem the same way. Writing REG_B — byte 1 — requires driving a full 32-bit word, and with no byte enables the write drives all four bytes, so unless the other three bytes carry their current values, REG_A, REG_C, and REG_D are overwritten. The testbench's model, treating each as its own register, does a read-modify-write for a field-level write and mostly gets away with it; but any code that writes REG_B with a bare word — or any moment a neighbour is volatile and changes between the read and the write — corrupts a register nobody meant to touch. The block ships with a firmware rule ('always read-modify-write the packed word') that a later driver forgets, and one packed register starts resetting its neighbours. The post-mortem conclusion: packing several registers into one bus word is only safe if the bus has byte enables to write each lane independently; without them, every access to one register is an access to all of them, and independence is an illusion.
3. Concept — lanes, ordering, and the write mask
The three properties, made concrete:
- Bus width (
n_bytes) sets the byte lanes. A 4-byte bus has four byte lanes (lane 0 = bits[7:0], lane 1 =[15:8], and so on). A register's bytes are assigned to lanes by its offset within the aligned word and the endianness: a register at byte offset 2 occupies lane 2 on a little-endian bus. A register that fits in one word uses some or all lanes of one transfer; a register wider than the bus uses all lanes across multiple transfers. - Endianness orders multi-transfer words. For a register wider than
n_bytes,endiandecides whether the low word goes to the low address or the high address — the 3.1 word-swap concern. Within a single word, endianness also fixes which lane is the least-significant byte. - Byte enables are the per-lane write mask. A bus with byte enables can assert a subset of lanes on a write, so only those bytes are written and the rest are untouched. This is what makes a genuine sub-word write possible: writing one field, or one packed register, without a read-modify-write of the whole word. Without byte enables, every write is all-lanes, so the only way to preserve un-targeted bytes is to read them first and write them back (RMW).
Here is one 32-bit bus word with four packed registers, one per lane — the layout the story clobbers:
4. Mental Model — the bus is byte lanes, and byte enables are the only way to write one
5. Working Example — a byte-enabled sub-word write vs the RMW fallback
On a bus with byte enables, a field (or packed register) marked individually_accessible is written alone — one transfer, only its lanes enabled:
// Bus HAS byte enables. A packed 8-bit register in lane 1 can be written alone.
reg_b.configure(this, 8, 8, "RW", 0, 0, 1, /*individually_accessible*/ 1); // honest: bus has BEs
// ...
reg_b.write(s, 8'hA5);
// The adapter drives one bus transfer with data in lane 1 and byte-enable 1 asserted,
// byte-enables 0/2/3 deasserted -> only REG_B is written; A, C, D untouched. No RMW.On a bus without byte enables, the same intent must become a read-modify-write, and the model must know it:
// Bus has NO byte enables. individually_accessible must be 0 (2.3), and a single-register
// update is a read-modify-write of the whole shared word.
reg_b.configure(this, 8, 8, "RW", 0, 0, 1, /*individually_accessible*/ 0); // honest: no BEs
// A field-level write now RMWs the packed word so A, C, D are preserved:
reg_b.write(s, 8'hA5); // RAL: read the 32-bit word, splice A5 into lane 1, write it all back
// This preserves the neighbours ONLY because it read them first — and is not atomic vs hardware.The two differ in exactly one thing: whether the bus can enable a single lane. With byte enables the write is a clean, atomic single-lane transfer; without them it is a read-modify-write of the whole word, which preserves the neighbours only by reading and rewriting them — the seam the next section splits open.
6. Debugging Session — the packed registers with no byte enables
Narrow registers packed into one bus word on a bus without byte enables cannot be written independently, so writing one clobbers its word-mates
NO BYTE ENABLES// Four 8-bit registers packed into the 32-bit word at 0x00, on a bus with NO byte enables.
// A driver writes REG_B with a bare full-word write (no read-modify-write):
bus_write(32'h00, 32'h0000_A500); // intends REG_B (lane 1) = 0xA5
// With no byte enables, the DUT writes ALL four lanes:
// REG_A (lane 0) <- 0x00, REG_B (lane 1) <- 0xA5, REG_C (lane 2) <- 0x00, REG_D (lane 3) <- 0x00
// REG_A, REG_C, REG_D are clobbered to 0.Writing one packed register resets its word-mates: set REG_B and REG_A, REG_C, REG_D all drop to whatever bytes the write carried (usually zero). The registers behave as if they are secretly linked — touch one and the others move — which looks like an addressing or decode bug. It is intermittent in the full system because it depends on whether the writer happened to read-modify-write or drive a bare word, and whether a neighbour changed in between. The RTL decodes correctly; the 'link' is that the four registers physically share one write-enable because the bus cannot enable a single lane.
The bus has no byte enables, so it cannot write a single lane — every write drives all four byte lanes of the word. Four independent registers packed into one word are therefore not independently writable: a write intended for one lane writes all of them, and the un-targeted lanes take whatever the write drove there. Independence was assumed from the fact that each register has its own byte and offset, but a distinct offset does not grant a distinct write-enable; only byte enables do. The DUT and the packing are internally consistent — the mistake is expecting per-register writes on a bus that has no mechanism to enable one register's lane while masking the others.
Two real fixes, plus a modelling correction. If the packing must stay, either use a bus that has byte enables (so each lane can be written alone and individually_accessible = 1 is honest), or make every write to the shared word a read-modify-write — read the whole word, change only the target lane, write it back — which the RAL field API does automatically when individually_accessible = 0, so model the fields that way and never issue a bare full-word write to a packed register. The cleaner fix is at design time: do not pack independent registers into one word on a byte-enable-less bus — give each its own aligned word — and never pack a volatile register into a shared word, because the RMW cannot be atomic against hardware. The rule: a distinct offset is not a distinct write-enable; independent sub-word writes require byte enables, and without them a shared word is one atomic unit.
7. Common Mistakes
- Assuming a distinct offset means a distinct write-enable. Without byte enables, registers sharing a word share a write; you cannot update one alone.
- Claiming
individually_accessibleon a byte-enable-less bus. The sub-word write is impossible; it becomes an RMW (2.3), disturbing volatile neighbours. - Bare full-word writes to a packed register. Drives every lane; use read-modify-write (or byte enables) to preserve the word-mates.
- Packing a volatile register into a shared word. The RMW that preserves neighbours is not atomic against hardware; the volatile lane can be lost.
- Confusing endianness with byte enables. Endianness orders/places lanes; byte enables mask them — different mechanisms.
8. Industry Best Practices
- Pack registers only on a byte-enable bus. Per-lane writes make packing safe; without byte enables, give each register its own word.
- Set
individually_accessiblefrom the bus's byte-enable capability. True only when the bus can write the field's lanes alone. - Read-modify-write shared words on byte-enable-less buses. And never issue a bare full-word write to a packed register.
- Keep volatile registers out of shared words. So no field write has to RMW a hardware-owned lane.
- Test a wide register and a packed word. Wide registers expose endianness/splitting; packed words expose byte-enable dependence — a narrow-register-only suite hides both.
9. Interview / Review Questions
10. Key Takeaways
- Bus width (
n_bytes) sets the byte lanes, and a register's bytes are assigned to lanes by offset and endianness; the register fits one transfer, spans several, or shares one. - Endianness orders multi-transfer words (the wide-register concern) and fixes which lane is the least-significant byte.
- Byte enables are the per-lane write mask — the only way to write a single lane, and therefore the real mechanism behind
individually_accessibleand safe register packing. - Without byte enables, every write is all-lanes, so registers sharing a word are not independent — writing one clobbers its word-mates unless you read-modify-write.
- Pack registers only on a byte-enable bus, keep volatile registers out of shared words, and test a wide register and a packed word to expose endianness and byte-enable dependence.