UVM RAL · Chapter 2 · Register Model Basics
uvm_reg — Modeling a Single Register
The register sits between the block and the field: one register, one offset, a handful of fields, and the operations a test actually calls. It is the level where addressing happens, since a register has an offset in a map and, once the model is locked, an address. It is also where most access happens, through methods like write, read, update, mirror, and predict. A register groups its fields in its build method so it becomes one addressable thing. The most important idea here is the difference between a register-level write, which drives a value to every field at once in a single transaction, and a field-level write, which changes only one field using a read-modify-write. Confuse them and a write meant to change one field silently overwrites all the others. This lesson builds a multi-field register, shows both kinds of access, and breaks it exactly that way.
Foundation12 min readUVM RALuvm_regRegisterread-modify-writeField Accessbuild
Chapter 2 · Section 2.2 · Register Model Basics
1. Why Should I Learn This?
The uvm_reg is the level you spend most of your time at: it is what you add to a map, what a sequence names, and what carries the write/read/update/mirror methods you call constantly. And it is where a subtle, expensive mistake lives — the difference between writing a register and writing a field. Because a register-level write sets all fields in one transaction, using it to change a single field means you are also asserting values for every other field, and if you did not read them first you are asserting whatever you happened to put there, quietly destroying configuration the rest of your test depends on.
Learning the register level — how it groups fields, how it acquires an address, and how register access differs from field access — is what lets you choose the right operation every time and avoid the clobber bug. It is also the anchor for the whole model: fields live inside it, the map addresses it, and adapters and sequences act on it.
2. Industry Story — the configuration that kept resetting itself
A test configures a block's CTRL register across a setup routine: it sets enable, picks a mode, sets a priority, and enables an interrupt — four fields in one 32-bit register. A helper task, meant to 'flip the mode later,' does ctrl.write(s, 32'h0000_0004) to set mode = 2 — because bits [3:2] are the mode and 0x4 puts 2 there.
The block starts misbehaving: every time the helper runs, enable drops to 0, priority resets, and the interrupt enable clears. The engineer chases a phantom hardware bug — 'the block keeps disabling itself' — for half a day. The truth is that ctrl.write(0x4) is a register write: it drives 0x0000_0004 to the whole register, which sets mode = 2 and simultaneously writes 0 to enable, priority, and the interrupt enable, because those bits are 0 in 0x4. The helper did not 'change the mode,' it rewrote the entire register and happened to leave one field non-zero. The fix is one word — ctrl.mode.write(s, 2) (a field write, which preserves the others via read-modify-write) — and the lesson is blunt: a register write is not 'set this field,' it is 'set every field'; to change one field, operate on the field, not the register.
3. Concept — what a uvm_reg is and how you operate on it
A uvm_reg models one register. It carries:
- Its fields, created and configured in
build(). The register callsfield.configure(this, ...)for each field, so the register owns them and knows the bit layout (from 2.3). - Its width (
n_bits) and its placement — a register has an offset in a map, and once the model is locked, an address (get_offset(),get_address()). - Register-level operations that act on the whole register in one transaction:
write(drive all fields),read(fetch all fields, auto-check),update(push fields whose desired differs from mirrored),mirror(read and update the mirror),predict(set the mirror explicitly), and the backdoorpeek/poke. - Field-level operations reached through the field handles —
reg.field.write,reg.field.read,reg.field.set,reg.field.get— that act on one field.
The pivotal distinction:
reg.write(value)is one transaction that sets every field fromvalue. Use it when you genuinely mean the whole register.reg.field.write(value)changes only that field; on a bus without sub-word access, RAL implements it as a read-modify-write — read the register, replace just that field's bits, write it back — so the other fields keep their values.
Here is a multi-field register — the register is the container whose fields tile its width:
4. Mental Model — the register is a row; write the cell, not the row
5. Working Example — a register, and both kinds of access
The register creates and configures its fields in build(), then a test uses register- and field-level access deliberately:
class ctrl_reg extends uvm_reg;
`uvm_object_utils(ctrl_reg)
rand uvm_reg_field enable; // [0]
rand uvm_reg_field mode; // [3:2]
rand uvm_reg_field priority; // [7:4]
rand uvm_reg_field irq_en; // [8]
function new(string name = "ctrl_reg");
super.new(name, .n_bits(32), .has_coverage(UVM_NO_COVERAGE));
endfunction
virtual function void build();
enable = uvm_reg_field::type_id::create("enable");
mode = uvm_reg_field::type_id::create("mode");
priority = uvm_reg_field::type_id::create("priority");
irq_en = uvm_reg_field::type_id::create("irq_en");
// parent size lsb access reset volatile rand indiv
enable.configure (this, 1, 0, "RW", 0, 0, 1, 0);
mode.configure (this, 2, 2, "RW", 0, 0, 1, 0);
priority.configure(this, 4, 4, "RW", 0, 0, 1, 0);
irq_en.configure (this, 1, 8, "RW", 0, 0, 1, 0);
endfunction
endclassuvm_status_e s; logic [31:0] got;
// Register-level write: program the WHOLE register in one shot from a complete value.
// enable=1, mode=2, priority=5, irq_en=1 -> bits: [8]=1 [7:4]=5 [3:2]=2 [0]=1
ctrl.write(s, (1<<8) | (5<<4) | (2<<2) | (1<<0)); // sets ALL four fields at once
// Field-level write: change ONLY mode; the other three are preserved (read-modify-write).
ctrl.mode.write(s, 2'd1); // enable, priority, irq_en keep their values
// Or the desired/update idiom for one field, same effect:
ctrl.mode.set(2'd3); // desired(mode)=3, nothing driven yet
ctrl.update(s); // pushes only the changed field, preserving the restThe register write is used correctly here — with a complete value that names every field on purpose. The field write is used when the intent is one field. The bug is using the first when you mean the second.
6. Debugging Session — the register write that wiped the config
Using a register-level write to change one field silently sets every other field to zero
REGISTER VS FIELD WRITE// Earlier setup programmed enable=1, mode=0, priority=5, irq_en=1.
// A helper 'changes the mode' with a REGISTER write:
ctrl.write(s, 32'h0000_0004); // BUG: register write -> drives 0x4 to the WHOLE register
// Result: mode <- 2, but enable <- 0, priority <- 0, irq_en <- 0 (all the zero bits of 0x4).
// The block just lost its enable, priority, and interrupt config.The block appears to reset parts of its own configuration whenever the helper runs: enable drops, priority reverts, the interrupt stops firing — all at once, and only after the 'change the mode' step. It looks like a hardware bug ('the block disables itself') or a spurious reset, and time is lost probing the RTL, which is doing exactly what it was told: it received a register write of 0x4 and set the register to 0x4. The tell is that multiple unrelated fields change together on a single access — a signature of a whole-register write, not a field write.
ctrl.write(0x4) is a register-level write: it drives 0x0000_0004 to the entire register in one transaction, so it sets mode = 2 and simultaneously writes 0 to enable, priority, and irq_en, because those bits are 0 in the value. The author intended 'set the mode field,' but a register write does not target a field — it targets the whole register, and every field not explicitly set in the value is set to that value's bits (here, zero). The register semantics are correct; the wrong operation was chosen for the intent. Nothing preserved the other fields because a register write, by definition, does not.
Change one field with a field operation: ctrl.mode.write(s, 2'd1), which does a read-modify-write and preserves enable, priority, and irq_en; or ctrl.mode.set(1); ctrl.update(s); for the desired/update idiom. Reserve ctrl.write(value) for when value is a complete register value you mean in full — programming the register in one shot. The rule the bug teaches, and the one to enforce in review: to change one field, operate on the field; use a register write only with a value that names every field on purpose. A single access that changes several unrelated fields is almost always a register write used where a field write was meant.
7. Common Mistakes
- Using
reg.writeto change one field. It sets every field; the un-named fields go to the value's bits (usually zero) — the clobber bug. - Passing a partial value to
reg.write. If the value is not a complete, intended register value, the missing fields are being reset silently. - Reaching into fields when you mean the whole register. The inverse: doing four field writes (four read-modify-writes) when one register write would program it in one transaction.
- Confusing
reg.predictwithreg.write.predictupdates the mirror without a bus access;writedrives the bus. Usingpredictwhen you meant to program the DUT leaves the hardware untouched. - Forgetting that a field write is a read-modify-write. On a register with a volatile neighbour, the RMW read-then-write can disturb it (a hazard explored in 2.3).
8. Industry Best Practices
- Match the operation to the intent. One field to change means a field operation; a whole register to program from a known value means a register write.
- Prefer
set+updatefor multi-field intent. Set several fields' desired values, then oneupdatepushes exactly the changed fields — expressive and clobber-safe. - Review any register write with a literal value. A magic constant into
reg.writedeserves a check that it is a complete intended value, not one field padded with zeros. - Use field access for read-modify-write semantics. When other fields must be preserved, the field API expresses it and does the RMW for you.
- Watch volatile neighbours when doing field writes. A field-level RMW touches the whole register on the bus; if a sibling is a hardware-updated W1C, the RMW can clear it (see 2.3).
9. Interview / Review Questions
10. Key Takeaways
- The
uvm_regis the addressable unit — one register at one offset, grouping fields (built inbuild()) and carrying the write/read/update/mirror/predict/peek/poke operations tests call. - A register write sets every field at once from the value passed; a field write changes only one field, via read-modify-write on buses without sub-word access.
- The signature bug is using
reg.writeto change one field — the un-named fields go to the value's bits (usually zero), silently clobbering configuration. - To change one field, operate on the field (
reg.field.write, orset+update); reservereg.write(value)for a complete, intended register value. - A field write is a read-modify-write of the whole register, so mixing a volatile W1C field with RMW-accessed software fields in one register is a layout hazard (revisited in 2.3).