UVM RAL · Chapter 9 · Callbacks
Field Callbacks
Callbacks attach at field granularity too, and fields bring a hook that is central to modelling real hardware: the post-predict hook. It fires whenever a field's mirrored value is updated in the model, whether from a frontdoor write's prediction, a backdoor poke, or an explicit predict call. That all-paths coverage makes it the right hook for modelling a hardware side-effect on a specific field, because the side-effect must occur however the field's value moved, not only when a bus write caused it. By contrast, the post-write hook fires only on a frontdoor write and is blind to the other paths. This lesson explains how to register a callback onto a field and use the post-predict hook, then breaks the bug where a field side-effect uses post-write instead, so backdoor pokes and explicit predictions silently skip it and the model drifts out of sync.
Foundation12 min readUVM RALfield callbackspost_predictpost_writemirror drift
Chapter 9 · Section 9.2 · Callbacks
1. Why Should I Learn This?
Modelling a hardware side-effect on a specific field — a bit the hardware sets, a field whose change must update a shadow — is a field-callback job, and it only works if the callback fires however the field's value changes. Knowing that post_predict catches all update paths (frontdoor, backdoor, explicit predict) while post_write catches only frontdoor writes is what lets you pick the hook that keeps the field model in sync, instead of one that silently drifts when the value moves by a route you did not anticipate.
Learning field callbacks and the all-paths reach of post_predict builds on the register-callback mechanism (9.1), sharpens against the bus-access hooks of 9.3, and sets up the advanced modelling of 9.4. It is the difference between a field model that tracks the hardware and one that quietly falls behind.
2. Industry Story — the field model that drifted on backdoor
A team models a status field whose change must update a shadow model, using a field callback. They chose post_write for the hook — reasonable-sounding, since 'the field changes when it is written.' Frontdoor tests all passed: a bus write to the field fired post_write, the shadow updated, everything tracked.
Then tests that used backdoor poke to set up state — and tests that used explicit predict() — began producing a stale shadow: the field's modelled value changed, but the shadow did not follow, because post_write fires only on a frontdoor write and is blind to backdoor and explicit-prediction updates. The field model drifted out of sync exactly on the paths post_write does not cover, and because backdoor seeding is common in setup (8.4), the drift was widespread but intermittent-looking — present only where backdoor/predict were used. The fix was a one-word hook change: post_predict instead of post_write, which fires on every field-value update regardless of path, so the shadow tracked the field however it moved. The post-mortem lesson: post_predict fires on all field-value updates (frontdoor prediction, backdoor poke, explicit predict), while post_write fires only on a frontdoor write — so a field side-effect that must occur however the value changes belongs in post_predict; using post_write makes the model silently drift on the backdoor and explicit-prediction paths it does not cover.
3. Concept — register on the field, use post_predict for all update paths
Field callbacks work like register callbacks (9.1) with two field-specific points:
- Register onto the field. You attach the callback to a field (via the field's callback registration /
uvm_callbackswith the field), so it fires for that field's updates — a finer scope than a whole register. post_predictis the all-update-paths field hook. It fires whenever the field's mirrored value is predicted/updated in the model, which covers every route the value can change by:- the prediction after a frontdoor write (the field's value updates in the model),
- the prediction after a backdoor
poke(8.4), - an explicit
predict()call (2.x/6.x). This makes it the correct hook for a hardware side-effect on a field — a change that must happen however the field moved.
post_writefires only on a frontdoor write. It is the right hook for 'react specifically to a bus write' (9.3), but it is blind to backdoor and explicit-prediction updates — so using it to model an all-paths side-effect causes drift.
Here is the difference: post_predict fires on all three update paths, post_write on only one:
4. Mental Model — post_predict watches the value; post_write watches the bus
5. Working Example — a field side-effect with post_predict
Attach a field callback that keeps a shadow in sync on every update path by using post_predict:
// A field callback whose side-effect must happen however the field's value changes -> post_predict.
class field_shadow_cb extends uvm_reg_cbs;
`uvm_object_utils(field_shadow_cb)
shadow_model shadow;
function new(string name = "field_shadow_cb"); super.new(name); endfunction
// post_predict fires on EVERY field-value update: frontdoor prediction, backdoor poke, explicit predict.
virtual function void post_predict(input uvm_reg_field fld,
input uvm_reg_data_t previous,
inout uvm_reg_data_t value,
input uvm_predict_e kind,
input uvm_path_e path,
input uvm_reg_map map);
shadow.set(fld.get_full_name(), value); // tracks the field no matter HOW it moved
endfunction
endclass// Register onto the FIELD (finer scope than a register), then it fires for that field's updates.
field_shadow_cb cb = field_shadow_cb::type_id::create("cb");
cb.shadow = env.shadow;
uvm_callbacks#(uvm_reg_field, uvm_reg_cbs)::add(status_reg.mode, cb); // attach to the FIELD
// Now a frontdoor write, a backdoor poke (8.4), AND an explicit predict all update the shadow.// Contrast: the SAME side-effect in post_write would fire ONLY on a frontdoor write:
// virtual task post_write(uvm_reg_item rw); shadow.set(...); endtask // frontdoor-only -> DRIFTS
// Backdoor pokes and explicit predicts would change the field's value but SKIP this -> stale shadow.post_predict makes the shadow follow the field on every update path; the same logic in post_write would silently miss the backdoor and explicit-predict paths — the bug of the next section.
6. Debugging Session — a field side-effect that drifts on backdoor and predict
A field side-effect placed in post_write fires only on frontdoor writes, so backdoor pokes and explicit predictions skip it and the field model drifts — the fix is post_predict, which fires on all update paths
post_predict, NOT post_write// A shadow that must track a field however it changes — but hooked on post_write (frontdoor-only):
class field_shadow_cb extends uvm_reg_cbs;
virtual task post_write(uvm_reg_item rw); // BUG: fires ONLY on a frontdoor write
shadow.set(rw.element.get_full_name(), rw.value[0]);
endtask
endclass
// ...
uvm_callbacks#(uvm_reg_field, uvm_reg_cbs)::add(status_reg.mode, cb);
// Backdoor pokes and explicit predict() change 'mode' but never fire post_write -> shadow goes stale.Frontdoor tests all pass — a bus write to the field fires post_write, the shadow updates, everything tracks. But tests that set the field via backdoor poke (common in setup, 8.4) or via explicit predict() produce a stale shadow: the field's modelled value changed, yet the shadow did not follow. The drift appears only on the backdoor/predict paths, so it looks intermittent — fine in pure-frontdoor tests, wrong wherever backdoor seeding or explicit prediction was used — and downstream checks that trust the shadow fail confusingly, with no obvious link to the callback.
The side-effect was a value-tracking one — the shadow must follow the field however its value changes — but it was hooked on post_write, which fires only on a frontdoor (bus) write. A field's mirrored value, though, can change by three routes: the prediction after a frontdoor write, the prediction after a backdoor poke, and an explicit predict() call. post_write covers only the first; the backdoor and explicit-prediction updates change the field's value in the model without a bus write, so they never fire post_write, and the shadow is not updated. It seemed to work because, in frontdoor-only tests, every value change was caused by a bus write — the one path post_write covers — so the gap only appears once a backdoor poke or explicit predict moves the value off the covered path. The right hook for an all-paths value-tracking side-effect is post_predict, which fires on every field-value update regardless of route; post_write was the wrong tool for a reaction that must track the value however it moves.
Move the side-effect from post_write to post_predict, which fires on all field-value updates (frontdoor prediction, backdoor poke, explicit predict), so the shadow tracks the field however it changes — the same shadow logic, correct hook. The rule the bug teaches: for a field side-effect that must occur however the value changes, use post_predict (all update paths), not post_write (frontdoor writes only); hooking a value-tracking reaction on post_write makes the model drift silently on the backdoor and explicit-prediction paths it does not cover. The general test for choosing the hook: if backdoor pokes and explicit predictions should also trigger your reaction, it is a post_predict reaction; if you specifically mean 'a bus write occurred,' it is post_write (9.3).
7. Common Mistakes
- Using
post_writefor a value-tracking field side-effect. It fires only on frontdoor writes; backdoor pokes and explicit predicts skip it and the model drifts — usepost_predict. - Forgetting that backdoor and explicit predict update the field's value. A field's value changes by three routes, not just bus writes; a value-tracking reaction must cover all of them (8.4).
- Testing a field callback only through the frontdoor. Frontdoor-only tests hide
post_write-vs-post_predictdrift entirely — exercise backdoor and explicit-predict paths too. - Registering on the register when you meant the field. Field callbacks attach to the field for field-scoped updates — match the registration scope to intent (9.1).
- Reaching for
post_predictwhen you truly mean a bus write. If the event is specifically a frontdoor write (protocol reaction, error injection on write),post_writeis correct (9.3).
8. Industry Best Practices
- Model value-tracking field side-effects in
post_predict. It fires on every update path (frontdoor, backdoor, explicit predict), so the model follows the field however it changes. - Use
post_write/post_readonly for genuine bus-access reactions. When the bus event itself is what you mean to catch, not any value change (9.3). - Exercise all update paths when testing a field callback. Frontdoor, backdoor
poke, and explicitpredict()— orpost_write-vs-post_predictdrift stays hidden. - Register field callbacks at field scope. Attach to the field whose updates you care about, keeping the callback's reach precise.
- Decide the hook by the event you react to. Value change ->
post_predict; bus write ->post_write. Naming the event picks the hook.
9. Interview / Review Questions
10. Key Takeaways
- Callbacks attach at field granularity too — register onto the field — and fields lean on
post_predict, the hook that fires whenever a field's mirrored value is updated in the model. post_predictcovers all update paths: the prediction after a frontdoor write, the prediction after a backdoorpoke(8.4), and an explicitpredict()— making it the right hook for a hardware side-effect on a field that must occur however the value moved.post_writefires only on a frontdoor write — correct for a genuine bus-write reaction (9.3), but blind to backdoor and explicit-prediction updates.- The signature bug: a value-tracking side-effect placed in
post_write— it works frontdoor but silently drifts on the backdoor and explicit-predict paths; the fix ispost_predict. - Choose the hook by the event you react to: field value change ->
post_predict, bus write ->post_write— and test all update paths (frontdoor, backdoor, explicit predict), or the drift stays hidden.