UVM RAL · Chapter 12 · Advanced RAL
Shadow Registers
A shadow register, also called a write-twice protected register, is a hardware protection mechanism rather than a modelling construct. To guard a safety-critical register against a spurious write from a glitch or a runaway pointer, the hardware requires the value to be written twice, commonly the value then its complement, and commits only if the two writes are consistent. A single stray write is rejected and the old value is kept. The consequence for RAL is direct. A plain single write will not update the DUT, but the model's mirror dutifully predicts the new value, so the two diverge. This page explains shadow registers as write-twice protection, how to drive the full protocol and update the mirror only on completion, and breaks the signature bug of driving one as an ordinary single-write register, which produces a mismatch whose real cause is an incomplete protocol.
Foundation12 min readUVM RALshadow registerwrite-twiceprotectionprotocol
Chapter 12 · Section 12.4 · Advanced RAL
1. Why Should I Learn This?
Safety-critical registers are often write-twice protected — they reject single writes by design — and if you model or drive one as an ordinary register, a plain write() is silently rejected while your mirror predicts success, producing a mismatch that looks like a DUT bug but is a protocol error. Knowing that a shadow register is write-twice hardware protection — and that you must drive the full protocol and update the mirror only on completion — is what lets you exercise these registers correctly instead of chasing phantom DUT bugs.
Learning shadow registers deepens the mirror-mismatch method (11.1): a shadow-register mismatch is the ahead case (model moved, DUT didn't) with a specific cause — a protection protocol not honored — and it is a common exotic-register case the debug chapter's method resolves.
2. Industry Story — the write that the hardware kept rejecting
A team writes a safety-critical watchdog-control register with a plain reg.write(new_value), then reads it back — and the read shows the old value, mismatching the mirror (which holds new_value). It happens every time, and it looks like the DUT is ignoring writes to that register — a serious-seeming DUT bug. A designer investigates the register's write logic for days.
The write logic was correct — it was protection logic. The watchdog-control register is a shadow register: it requires the value to be written twice (value, then complement) and rejects a single write to guard against spurious upsets. The team's single reg.write() was exactly the kind of stray write the protection exists to reject, so the hardware correctly discarded it and kept the old value — while the model's mirror predicted the new value from the single write, producing the mismatch. It was not a DUT bug (the hardware did precisely what it should); it was an incomplete protocol — a single write where the register demands two. Driving the full write-twice protocol (write X, then ~X) made the register commit and the mirror match. The post-mortem lesson: a shadow register is write-twice hardware protection that rejects single writes by design — so a plain single reg.write() is correctly discarded by the DUT while the model's mirror predicts success, producing a mismatch that looks like a DUT ignoring writes but is really an incomplete write-twice protocol; drive the full protocol and update the mirror only on completion.
3. Concept — write-twice protection; drive the full protocol, commit the mirror on completion
A shadow register protects a critical register by requiring a consistent pair of writes. The essentials:
- It is hardware protection, not a modelling artifact. The register guards against spurious single writes (upsets, glitches) by requiring the value written twice — commonly value then complement (
X,~X), sometimes the same value twice or behind an unlock. It commits only if the pair is consistent. - A single write is rejected — by design. One write cannot satisfy the two-write handshake, so the hardware keeps the old value. This is correct behaviour, the whole reason the protection exists.
- Model it as needing the full protocol. The RAL model must reflect that the register only changes on the completed protocol: driving must issue both writes in the right form, and the mirror must update only on completion, not on a single write.
- The mismatch is the ahead case with a protocol cause. A single
write()makes the mirror predict a change the DUT (correctly) did not make — mirror ahead of DUT (11.1) — but the cause is protocol not honored, not a modelling gap or a DUT bug.
Here is the protocol: two consistent writes commit; a single write is rejected and the mirror drifts:
4. Mental Model — a shadow register is a two-key launch switch; one key does nothing
5. Working Example — driving the full write-twice protocol
Drive both writes of the shadow protocol, and update the model only on completion:
// A shadow (write-twice) register commits only on value-then-complement. Drive the FULL protocol.
task write_shadow(uvm_reg r, uvm_reg_data_t value);
uvm_status_e s;
r.write(s, value); // first write: the value
r.write(s, ~value); // second write: its complement (as the HW protection requires)
// The HW sees a consistent pair -> COMMITS 'value'. A single write would be rejected.
endtask// Update the MODEL to reflect what the completed protocol committed (mirror updates on COMPLETION):
write_shadow(wdog_ctrl, 32'hDEAD_BEEF);
void'(wdog_ctrl.predict(32'hDEAD_BEEF)); // commit the mirror ONLY after the full protocol completes
// Now mirror == DUT (both hold DEAD_BEEF). A single write + predict would have run the mirror ahead.// The correctness hinge: shadow registers need the PROTOCOL, not a single write.
// full protocol (value + complement) -> DUT commits, mirror committed on completion -> MATCH.
// single write() -> DUT REJECTS (stray-write guard), mirror predicted change -> MISMATCH.Driving both writes makes the hardware commit; committing the mirror on completion keeps it in step. A single write() — the bug of the next section — is rejected by the protection while the mirror moves ahead.
6. Debugging Session — a single write silently rejected by the protection
Driving a shadow register with a single write is rejected by the write-twice protection, so the DUT keeps its old value while the model predicts the new one — a mismatch that is a protocol error, not a DUT bug
DRIVE THE WRITE-TWICE PROTOCOL// A shadow (write-twice) watchdog-control register driven with a plain SINGLE write:
wdog_ctrl.write(s, 32'hDEAD_BEEF); // BUG: one write to a register that requires value-THEN-complement
// The HW protection treats a lone write as a potential STRAY -> REJECTS it -> DUT keeps its OLD value.
// But the model's mirror predicts DEAD_BEEF from the single write -> mirror (ahead) != DUT (old).A write to the watchdog-control register 'has no effect': the subsequent read returns the old value, mismatching the mirror (which holds the written value). It happens every time, and it looks like the DUT is ignoring writes to this register — a serious-looking DUT bug that sends a designer into the register's write logic for days. The register's write logic is correct, though; what looks like 'ignoring writes' is the protection correctly rejecting a single write. The tell is that the register only misbehaves this way — other registers write fine — and that it is specifically the safety-critical one, which is exactly the kind that carries write-twice protection.
The register is a shadow (write-twice) register: to guard against spurious single-event-upset writes, the hardware requires the value written twice (value then complement) and commits only on a consistent pair, rejecting a lone write. The team's single reg.write() is precisely the stray write the protection exists to reject, so the hardware correctly discarded it and kept the old value — this is the protection working, not a DUT bug. Meanwhile the model's mirror, treating the register as ordinary, predicted the new value from the single write, so the mirror ran ahead of the (correctly unchanged) DUT. Diagnosed via the mirror-mismatch method (11.1): the mirror is ahead (model moved, DUT didn't), and a frontdoor/backdoor read agree that the DUT holds the old value — so it is not a bus-path or storage issue; the model predicted a change the hardware legitimately refused. The specific ahead cause here is a protection protocol not honored: the register demands two writes and got one. The write logic, the mirror mechanics, and the bus are all fine; the protocol was incomplete.
Drive the full write-twice protocol — write the value, then its complement (or the exact sequence the register specifies) — so the hardware sees a consistent pair and commits; then update the mirror on completion (predict the committed value after both writes). The register now changes and the mirror matches. Model shadow registers so the mirror commits only on the completed protocol, never on a single write. The rule the bug teaches: a shadow register is write-twice hardware protection that rejects single writes by design — so a plain reg.write() is correctly discarded while the model predicts success, producing a mirror-ahead mismatch (11.1) whose cause is an incomplete write-twice protocol, not a DUT ignoring writes; drive the full protocol and commit the mirror only on completion. The tell in the wild: a safety-critical register that appears to 'ignore' single writes is almost always a shadow/write-twice register, not a broken one — complete the protocol.
7. Common Mistakes
- Driving a shadow register with a single write. It is rejected by the write-twice protection by design — drive the full protocol (value then complement, as specified).
- Reading 'write had no effect' as a DUT bug. For a safety-critical register, a rejected single write is the protection working, not the hardware ignoring writes.
- Updating the mirror on a single write. The mirror must commit only on the completed protocol, or it runs ahead of a correctly-unchanged DUT (11.1).
- Not identifying which registers are shadow-protected. Safety-critical registers (watchdog, lock keys, critical config) often carry write-twice protection — know which ones.
- Using the wrong second-write form. Value-then-complement vs same-value-twice vs unlock-gated differ per design — drive the specified sequence.
8. Industry Best Practices
- Identify shadow-protected registers up front. Safety-critical registers (watchdog, lock/key, critical config) commonly require write-twice protection.
- Drive the full write-twice protocol. Value then complement (or the register's specified sequence) — a single write will be rejected.
- Commit the mirror only on completion. Model the register so the mirror updates on the completed protocol, not on a single write.
- Read a rejected single write as protection, not a bug. A safety-critical register 'ignoring' single writes is doing its job — complete the protocol.
- Diagnose via the mirror-mismatch method. Mirror ahead + DUT holds old value (fd==bd) = model predicted a change the protection refused — an incomplete protocol (11.1).
9. Interview / Review Questions
10. Key Takeaways
- A shadow register (write-twice / shadowed) is a hardware protection mechanism for safety-critical registers: it requires the value written twice — commonly value then complement — and commits only on a consistent pair, guarding against spurious single-event-upset writes.
- A single write is rejected by design — a lone write cannot satisfy the two-write handshake, so the hardware keeps the old value; this is the protection working, not a DUT bug.
- Modelling it means driving the full protocol (both writes, in the specified form) and committing the mirror only on completion — never updating the mirror on a single write.
- The signature bug is driving/modelling it as an ordinary single-write register: the write is silently rejected while the model predicts success, giving a mirror-ahead mismatch (11.1) whose cause is an incomplete write-twice protocol, not a DUT ignoring writes.
- A safety-critical register that appears to 'ignore' single writes is almost always a shadow/write-twice register — complete the protocol, read a rejected single write as protection, and additionally verify the protection (single write rejected, correct pair commits) as a feature.