Skip to content

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 0x00REG_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, endian decides 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:

A 32-bit bus word with four packed 8-bit registers, one per byte laneOne bus word, four packed registers (byte lanes)One bus word, four packed registers (byte lanes)HIGHHIGHLOWLOWlane 3 [31:24] — REG_D @ 0x03byte-enable 3 writes it alone (if the bus has them)byte 3lane 2 [23:16] — REG_C @ 0x02byte-enable 2byte 2lane 1 [15:8] — REG_B @ 0x01byte-enable 1byte 1lane 0 [7:0] — REG_A @ 0x00byte-enable 0byte 0
Figure 1 — a single 32-bit bus word (four byte lanes) packing four independent 8-bit registers, one per lane. Byte offset selects the lane: REG_A at 0x00 is lane 0 (bits 7:0), REG_D at 0x03 is lane 3 (bits 31:24). Writing REG_B alone requires asserting ONLY byte-enable lane 1 — which needs a bus with byte enables. On a bus without them, any write drives all four lanes, so writing REG_B also writes A, C, and D. Packing is safe only when byte enables can write each lane independently.

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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

1

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
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.
Symptom

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.

Root Cause

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.

Fix

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_accessible on 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_accessible from 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_accessible and 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.