UVM RAL · Chapter 5 · Adapters
reg2bus() & bus2reg()
The adapter's two methods, reg2bus and bus2reg, form a codec: an encoder and a decoder over one common format, and getting the codec exactly right is where an adapter is correct or subtly wrong. That format is the generic bus operation, whose fields are worth naming: the kind of read or write, the address, which is a byte address by RAL convention, the data, the access width, and the byte-enable mask. reg2bus encodes this into a concrete bus item, and bus2reg decodes a bus item back into it. The rule is that they must be exact inverses, because bus2reg is also called by the predictor on every transaction it merely observes. If bus2reg does not reconstruct exactly what reg2bus sent, the mirror drifts. This lesson details the fields, the inverse contract, and the byte-versus-word address subtlety, then breaks an adapter that puts a byte address on a word-addressed bus so every access lands on the wrong register.
Foundation12 min readUVM RALreg2busbus2reguvm_reg_bus_opAddress GranularityPredictor
Chapter 5 · Section 5.3 · Adapters
1. Why Should I Learn This?
The codec is the adapter, and codec errors are the hardest adapter bugs to see because they are correct in one direction or for one case and wrong in another. An address conversion that works for offset 0x00 (which is 0 either way) fails for every other register; a data placement that is right for writes is wrong for reads; an encoding that reg2bus does one way and bus2reg decodes another passes a frontdoor round-trip but drifts the mirror through the predictor. Understanding the uvm_reg_bus_op fields and the exact-inverse contract is what lets you write a codec that is correct for every register, both directions, and both the frontdoor and the predictor.
Learning the methods in depth — especially the byte-versus-word address granularity that trips up nearly every first adapter — is what turns 'the adapter mostly works' into 'the adapter is correct.' It is the precise version of 5.2's overview.
2. Industry Story — the adapter that worked only at register zero
An engineer writes an adapter for a word-addressed bus — a bus where each successive address selects the next 32-bit word, so word 0 is register 0x00, word 1 is register 0x04, word 2 is 0x08. In reg2bus, they copy RAL's rw.addr straight into the bus item's address field. It passes their first smoke test, which happens to exercise the register at offset 0x00.
Every other register is wrong. RAL's rw.addr is a byte address, so the register at offset 0x08 arrives as rw.addr = 0x08, and putting that byte value onto a word-addressed bus selects word 8 — byte address 0x20 — an entirely different register. So a write to CTRL at 0x08 lands on the register at 0x20, a read of STATUS at 0x04 selects word 4 (0x10), and only register 0x00 (where byte and word address coincide at 0) is ever correct. Because the smoke test used 0x00, the adapter looked fine; the failures appeared as bizarre, register-specific corruption once real tests exercised the map, and looked like a decode bug in the DUT. The fix is one shift: on a word-addressed bus, item.addr = rw.addr >> 2. The lesson: RAL's addr is a byte address; the adapter must convert it to the bus's addressing granularity, and forgetting to means only register zero works while every other access lands on the wrong register.
3. Concept — the bus_op fields, the inverse contract, and granularity
The uvm_reg_bus_op is the codec's common format:
kind—UVM_READorUVM_WRITE. Selects the transaction type and which data field is meaningful.addr— the register's address, a byte address by RAL convention. The adapter converts it to the bus's granularity (byte-addressed: use as-is; word-addressed: shift).data— the value. On a write,reg2busreads it as the write data; on a read,bus2regfills it with the returned read data.n_bits— the width of this access. Relevant for buses that carry the access size, or for splitting a wide register (3.1).byte_en— the byte-enable mask for a sub-word write (mapped topstrb/wstrb), tied tosupports_byte_enable(5.2).status— filled bybus2regfrom the bus response (5.2).
The two methods are a codec, and the binding rule is that they are exact inverses: bus2reg(reg2bus(op)) must equal op. This matters because of where bus2reg runs — the predictor calls it on every transaction it passively observes to reconstruct the operation and update the mirror. So reg2bus (the encoder used to produce traffic) and bus2reg (the decoder the predictor uses to understand traffic) must agree perfectly, or the reconstructed operation differs from the one sent and the mirror drifts. Here are both uses of bus2reg, and why the two methods must agree:
4. Mental Model — a lossless, symmetric codec over one format
5. Working Example — the fields, read/write data, and granularity, both ways
An adapter for a word-addressed bus, with the codec symmetric on address and data:
// OUTBOUND: encode. RAL addr is a BYTE address; this bus is word-addressed -> shift.
virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
word_item item = word_item::type_id::create("item");
item.awrite = (rw.kind == UVM_WRITE);
item.aaddr = rw.addr >> 2; // byte address -> word address (4 bytes/word)
item.awdata = rw.data; // write data (meaningful on a write)
item.awstrb = rw.byte_en; // byte enables
return item;
endfunction
// INBOUND: decode. Must be the exact inverse — shift back, and read data on a read.
virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
word_item item;
if (!$cast(item, bus_item)) begin `uvm_fatal("ADPT", "bad item") return; end
rw.kind = item.awrite ? UVM_WRITE : UVM_READ;
rw.addr = item.aaddr << 2; // word address -> byte address (inverse)
rw.data = item.awrite ? item.awdata : item.ardata; // write data out, READ data in
rw.status = item.aerr ? UVM_NOT_OK : UVM_IS_OK;
endfunctionA round-trip test proves the two are inverses — the check every adapter should carry:
// Round-trip: decode(encode(op)) must equal op, for reads AND writes, every address.
uvm_reg_bus_op op, back;
op.kind = UVM_WRITE; op.addr = 'h08; op.data = 32'hDEAD; op.byte_en = 'hF;
uvm_sequence_item item = adapter.reg2bus(op);
adapter.bus2reg(item, back);
assert(back.kind == op.kind && back.addr == op.addr && back.data == op.data)
else `uvm_error("ADPT", $sformatf("codec not inverse: addr %0h -> %0h", op.addr, back.addr))
// back.addr == 0x08 only if reg2bus shifted >>2 and bus2reg shifted <<2 symmetrically.The address is shifted out in reg2bus and back in bus2reg, so the byte address survives the round trip; the data is placed by kind, so reads and writes both reconstruct correctly. Drop either symmetry and the round-trip test fails — which is exactly the next section, unshifted.
6. Debugging Session — the byte address on a word-addressed bus
Putting RAL's byte address straight onto a word-addressed bus makes every register except offset zero land on the wrong register
ADDRESS GRANULARITY// The bus is WORD-addressed (address N selects the Nth 32-bit word), but reg2bus copies
// RAL's BYTE address directly with no conversion:
virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
word_item item = word_item::type_id::create("item");
item.aaddr = rw.addr; // BUG: byte address 0x08 -> selects WORD 8 (byte 0x20)
item.awrite = (rw.kind == UVM_WRITE);
item.awdata = rw.data;
return item;
endfunction
// A write to CTRL at byte offset 0x08 lands on the register at byte 0x20.Register 0x00 works perfectly (byte and word address are both 0 there), so a smoke test passes and the adapter looks correct — but every other register is wrong in a register-specific way: CTRL at 0x08 reads/writes the register at 0x20, STATUS at 0x04 hits word 4 (0x10), and so on, each off by the byte-to-word factor. It looks like a bizarre address-decode bug in the DUT, and because register zero is fine, the pattern (only offset-zero correct) is easy to miss. A round-trip codec test, had one existed, would have failed immediately for any non-zero address.
RAL's rw.addr is a byte address, but the bus is word-addressed — each address increments select the next 32-bit word — so the byte address must be converted (>> 2 for 4-byte words) before it is placed on the bus. The adapter copied the byte address directly, so a byte offset of 0x08 was interpreted by the bus as word 0x08, i.e. byte address 0x20, selecting a completely different register. Only offset 0x00 survives, because zero is zero in either granularity. The register model, the map (whose byte offsets are correct), and the DUT decode are all right; the adapter failed to convert between RAL's byte-address convention and the bus's word-address granularity.
Convert the address to the bus's granularity in reg2bus, and invert it in bus2reg: item.aaddr = rw.addr >> 2; outbound and rw.addr = item.aaddr << 2; inbound (as in Section 5). Then add a round-trip codec test that exercises a non-zero address, so the asymmetry cannot pass a smoke test again. The rule the bug teaches: RAL's addr is a byte address, so the adapter must convert it to the bus's addressing granularity — symmetrically in both methods — and a codec that is only correct at offset zero is an unconverted-address bug that a round-trip test on any non-zero register catches instantly. When only register zero works, look at the address conversion.
7. Common Mistakes
- Not converting
addrto the bus's granularity. A byte address on a word-addressed bus lands on the wrong register for every non-zero offset. reg2busandbus2regnot symmetric. The predictor decodes differently thanreg2busencoded, drifting the mirror.- Reconstructing
datawithout regard tokind. Write data on a write, read data on a read; getting it backward corrupts reads. - Only smoke-testing at offset zero. Zero hides the address-granularity bug; test a non-zero register.
- Dropping
statusorn_bits. An incomplete decode misreports errors (5.2) or mishandles wide accesses.
8. Industry Best Practices
- Round-trip test the codec.
bus2reg(reg2bus(op)) == opfor reads, writes, a non-zero address, and success and error status. - Handle address granularity symmetrically. Convert byte to bus granularity in
reg2bus, invert inbus2reg. - Reconstruct
databykind. Keep write-data and read-data paths distinct inbus2reg. - Reconstruct the complete
uvm_reg_bus_op. Address, kind, data, status, andn_bitswhere the bus uses it. - Test non-zero registers first. They expose the granularity and symmetry bugs that offset zero conceals.
9. Interview / Review Questions
10. Key Takeaways
- The adapter is a codec over the
uvm_reg_bus_op(kind,addr,data,n_bits,byte_en,status):reg2busencodes,bus2regdecodes. - They must be exact inverses because the predictor runs
bus2regon observed traffic thatreg2busencoded; a mismatch reconstructs a phantom operation and drifts the mirror. Round-trip test:bus2reg(reg2bus(op)) == op. - RAL's
addris a byte address — convert it to the bus's granularity (shift for a word-addressed bus) inreg2bus, and invert inbus2reg. - Reconstruct
databykind(write data on a write, read data on a read) and reconstruct the complete operation, includingstatus(5.2). - The signature bug is a byte address on a word-addressed bus with no conversion: only offset zero works while every other access lands on the wrong register — test a non-zero register to catch it.