Skip to content

UVM RAL · Chapter 1 · Register Specification & CSR Thinking

Access Policies (RW, RO, W1C, RC, WO, …)

A field's access policy is the pair of rules that say what a read does to it and what a write does to it, and it is the single fact the mirror uses to predict the field's next value. This page is the working catalog of the common policies, grouped into four families so they are easy to remember: software-controlled like RW, RO, and WO, write-to-modify like W1C, read-to-modify like RC, and a few special cases. For each one you will see what it does on read and write, how RAL predicts it, and how to pick the right policy straight from the spec. It ends by breaking a read-to-clear field, the subtle bug where a stray debug or passive read clears the field before the test that cared about it ever looks.

Foundation12 min readUVM RALAccess PolicyW1CRCWOuvm_reg_field

Chapter 1 · Section 1.5 · Register Specification & CSR Thinking

1. Why Should I Learn This?

The access policy is the field property that most often decides whether a register test is real or fake. Nearly every confusing RAL failure — a mirror mismatch that is not a DUT bug, an interrupt test that passes when it should fail, a status register that reads stale — traces back to a field whose policy in the model does not match the field's behaviour in the hardware. And unlike a wrong width or offset, a wrong policy fails behaviourally: the field is the right size at the right address, it just responds to reads and writes differently than the spec says, so the mirror predicts the wrong next value.

Knowing the catalog cold — what each policy does and, crucially, which have side effects on read or write — lets you pick the right one from the spec in seconds, predict what RAL will do before you run it, and recognize an entire class of bugs (the read-side-effect and write-side-effect ones) that trip up everyone who thinks of a register as memory. It is the reference you will reach for constantly for the rest of the track.

2. Industry Story — the counter that read empty

A block has an EVENT_COUNT register, specified read-to-clear (RC): reading it returns the number of events since the last read and atomically clears the count to zero, so software gets a clean per-interval count. The verification engineer models it correctly as RC. Everything works — until someone adds a debug feature that periodically dumps all registers to a log for observability, including a plain read of EVENT_COUNT.

Now the counter is being read by two agents: the test that cares about the count, and the debug dumper that does not. Because RC clears on every read, the debug read empties the counter between the events and the test's read, so the test intermittently sees zero. The failure is maddening: it only happens when the debug dump lands in the wrong window, it disappears when you add print statements (which change timing), and it looks like a DUT bug even though the DUT is behaving exactly as an RC register must. The eventual fix is a rule the team writes in bold: for any field whose read has a side effect, every read is a state change — so nothing may read it 'just to look,' and the model must treat a passive or debug read as the mutation it is. The bug was not the model's policy; it was forgetting that an RC read is never free.

3. Concept — the catalog, as four families

Read every policy as a pair: what a read returns and does, and what a write does. Grouped into families, the common ones are:

  • Software-controlled — the field's value is what software makes it.
    • RW: read returns value; write sets value. The default for config.
    • RO: read returns value; write ignored. Status the field owner (hardware) drives.
    • WO: write sets value; read returns 0 (or undefined). Write-only strobes / keys.
  • Write-to-modify — a write changes the field by a rule, not by assignment. The classic interrupt/flag policies.
    • W1C: write 1 clears that bit; write 0 leaves it. Interrupt status.
    • W1S: write 1 sets that bit; write 0 leaves it. Interrupt set / doorbell.
    • W0C / W0S: the write-zero-to-clear / set duals.
  • Read-to-modify — the read itself has a side effect. The dangerous family.
    • RC: read returns value, then clears it. Self-clearing counters / status.
    • RS: read returns value, then sets it. Rare, but real.
  • Special — one-shot and combination policies.
    • Write-once (W1/WO1): writable until locked, then read-only. Lock / config-freeze bits.
    • Combinations like WRC, W1SRC, WC: read/write side effects layered together, per spec.

RAL ships built-in access strings for these, and the mirror's whole job is to apply the policy's rule to predict the field's next value. Here are the families as a map:

Access policy families: software-controlled, write-to-modify, read-to-modify, and specialSoftware-controlled — RW, RO, WOmemory-like: RW reads/writes the value, RO ignores writes, WO writes but does not read back; the default families for config and statusmemory-like: RW reads/writes the value, RO ignores writes, WO writes but does not read back; the default families for config and statusWrite-to-modify — W1C, W1S, W0C, W0Sa write changes the field by a rule, not assignment; the interrupt/flag family (write 1 to clear a pending bit, etc.)a write changes the field by a rule, not assignment; the interrupt/flag family (write 1 to clear a pending bit, etc.)Read-to-modify — RC, RSthe READ itself mutates the field (read-and-clear, read-and-set); the dangerous family — every read is a state changethe READ itself mutates the field (read-and-clear, read-and-set); the dangerous family — every read is a state changeSpecial — write-once, combinations (WRC, W1SRC, WC)one-shot lock/config-freeze bits and layered read+write side effects, per the spec's behaviour columnone-shot lock/config-freeze bits and layered read+write side effects, per the spec's behaviour column
Figure 1 — the access-policy families, from safest to trickiest. Software-controlled policies (RW/RO/WO) are memory-like: value in, value out or ignored. Write-to-modify policies (W1C/W1S/W0C) change the field by a rule on write — the interrupt/flag family. Read-to-modify policies (RC/RS) mutate on the READ itself — the dangerous family, because a read is never free. Special policies (write-once, combinations) layer these. The mirror predicts a field by applying its policy's (on_read, on_write) rule.

4. Mental Model — a policy is an (on_read, on_write) function

5. Working Example — one register, four policies

A realistic IRQ register mixes families: an RW enable, a W1C pending flag, an RC self-clearing count, and an RO raw status. Each field's configure() access string is its transition function:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class irq_reg extends uvm_reg;
  `uvm_object_utils(irq_reg)
  rand uvm_reg_field enable;   // [0]     RW   — software owns it
       uvm_reg_field pending;  // [1]     W1C  — HW sets, write 1 clears
       uvm_reg_field count;    // [9:2]   RC   — read returns count, then clears
       uvm_reg_field raw;      // [10]    RO   — live line state, HW-driven
 
  function new(string name = "irq_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");
    pending = uvm_reg_field::type_id::create("pending");
    count   = uvm_reg_field::type_id::create("count");
    raw     = uvm_reg_field::type_id::create("raw");
    //              parent size lsb access reset volatile rand indiv
    enable.configure (this, 1,  0, "RW",  0, 0, 1, 0);
    pending.configure(this, 1,  1, "W1C", 0, 1, 0, 0);   // volatile: HW can set it
    count.configure  (this, 8,  2, "RC",  0, 1, 0, 0);   // volatile + read-clears
    raw.configure    (this, 1, 10, "RO",  0, 1, 0, 0);   // volatile status
  endfunction
endclass

Now the mirror predicts each field by its own rule, and the difference is the whole point:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
uvm_status_e s;  logic [31:0] got;
 
irq.pending.set(1); irq.update(s);   // W1C: writing 1 -> mirror predicts pending = 0 (cleared)
irq.read(s, got);                    // RC on 'count': the read returns count AND clears it,
                                     //   so the mirror predicts count = 0 after this read
// Reading again immediately returns 0 for count — not because of a bug, but because
// the previous read cleared it. That is the RC transition function doing its job.

pending clears on the write; count clears on the read. Same register, different families, and the mirror stays honest only because each field's policy matches its spec behaviour.

6. Debugging Session — the passive read that cleared a counter

1

A read-to-clear field is emptied by a stray debug or passive read before the test that needed it looks

READ SIDE EFFECT
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// EVENT_COUNT is RC (read returns count, then clears). Two readers exist:
//   (a) the test that wants the count, (b) a debug register-dump task.
// Debug dump reads every register 'just to log it':
foreach (all_regs[i]) all_regs[i].read(s, tmp);   // includes a read of EVENT_COUNT -> CLEARS it
// ... later, the test finally reads the count it cares about ...
event_count.read(s, got);            // intermittently 0 — the debug read already cleared it
Symptom

An RC counter intermittently reads zero (or too low) in a test that should see a non-zero count. It is timing-dependent — it only fails when the debug dump's read lands between the events and the test's read — so it vanishes under a debugger, moves when you add prints, and reproduces only on some seeds. It looks exactly like a DUT bug ('the counter is not counting'), and a lot of time gets spent staring at RTL that is working perfectly: the counter is counting, but something is reading — and therefore clearing — it first.

Root Cause

The field's policy is read-to-clear, so every read is a state change, and a second reader (the debug dump) is consuming the count before the intended reader sees it. This is the defining hazard of the read-to-modify family: with RW/RO/W1C, a read is a pure observation and any number of readers coexist; with RC/RS, a read mutates, so a stray read — from debug tooling, a passive checker, or an over-eager scoreboard — silently changes the value everyone else depends on. The model's policy is correct and the DUT is correct; the bug is a design/usage error of reading a side-effecting field 'just to look.'

Fix

Treat a read of a side-effecting field as the mutation it is. Concretely: the debug dumper must not plain-read RC/RS (and other read-side-effect) registers — use a backdoor peek (which observes the HDL value without triggering the read side effect) for observability, or exclude side-effecting registers from the dump. Where a passive monitor or scoreboard needs the value, it too should peek, not read. And in review, flag every read of an RC/RS field and confirm there is exactly one intended reader. The general rule: for read-side-effect policies, a read is never free — audit who reads the field, and use backdoor peek for any read that must not perturb it.

7. Common Mistakes

  • Modelling a W1C field as RW. Writing 1 then expecting 1 — the interrupt-clear bug from 0.2; the mirror predicts the opposite of the hardware.
  • Confusing W1C with W1S. Write-1-to-clear and write-1-to-set are opposites; transcribe from the spec, not from muscle memory.
  • Forgetting read side effects on RC/RS. Every read mutates; a debug or passive read silently changes the field for everyone.
  • Modelling WO fields as RW. A write-only field does not read back its value; a read-back comparison against the written value is meaningless.
  • Ignoring volatile on hardware-touched policies. W1C pending, RC count, and RO status are hardware-updated; without volatile the mirror can go stale.

8. Industry Best Practices

  • Pick the policy from the spec's behaviour column, per field. Adjacent fields often differ; never default a whole register to RW.
  • Audit read-side-effect fields explicitly. List every RC/RS field and confirm a single intended reader; use backdoor peek for observability and passive checks.
  • Write a per-policy behaviour test for exotic fields. For each W1C/W1S/RC/WO, exercise the side effect (clear, set, read-clear, write-no-readback) so a wrong policy fails loudly.
  • Mark hardware-touched fields volatile. Any policy where hardware changes the field independently of software needs volatile so RAL does not trust a stale mirror.
  • Prefer generated models for large maps. IP-XACT/RALF carries the access policy per field, removing per-field transcription risk (Chapter 13).

9. Interview / Review Questions

10. Key Takeaways

  • A field's access policy is an (on_read, on_write) function, and the mirror predicts the field by applying that function — choosing a policy is identifying which function the spec's behaviour column describes.
  • The catalog groups into families: software-controlled (RW/RO/WO), write-to-modify (W1C/W1S/W0C), read-to-modify (RC/RS), and special (write-once, combinations).
  • Read-to-modify (RC/RS) is the dangerous family — every read is a state change, so a stray debug or passive read silently mutates the field; use backdoor peek for any read that must not perturb it.
  • A wrong policy is a wrong state machine: the mirror predicts confidently and wrongly, so match each field's policy to its spec behaviour and mark hardware-touched fields volatile.
  • Write a per-policy behaviour test for exotic fields so a mis-transcribed W1C/W1S/RC/WO fails loudly instead of passing by luck.