Part 10 · Advanced Topics · Intermediate
Error Injection Patterns
Reusable callback patterns for corruption, delay injection, and protocol perturbation without destabilizing base driver behavior.
Why callbacks fit error injection
Error injection is scenario-specific by nature. Most tests should run nominal traffic, while targeted tests need controlled perturbation. Callbacks are ideal because they can be attached only when needed, with parameters tuned per test.
A clean pattern is: host remains protocol-authoritative, callback requests perturbation within permitted hook contracts, and scoreboard/monitor validates resulting behavior. This keeps fault campaigns modular and reproducible.
[TEST] fault campaign architecture
fault test chooses policy
▼
[ADV] registers error callbacks on selected drv/mon
▼
[DRV] host invokes hooks at pre-defined points
▼
perturbation applied (data/timing/protocol)
▼
[UVM] monitor + scoreboard check DUT reaction[UVM][ADV] three major injection classes
A) data corruption -> bit flips, bad checksum, illegal opcode
B) timing disturbance -> deliberate wait/skew/backpressure bursts
C) protocol perturbation-> reorder, premature valid, malformed sequence
Choose minimal perturbation that tests the intended checker/recovery pathCallbacks make fault logic pluggable and scoped to specific tests or windows.
Injection should be explicit and parameterized for replayability.
Keep perturbation and observation separated to simplify debugging.
Pattern 1: field corruption callbacks
Field corruption modifies selected transaction fields before drive. Typical use cases: checksum mismatch, parity error, reserved bit set, invalid enum value, or malformed length.
class crc_error_cb extends eth_driver_cb;
rand int unsigned error_rate_pct = 5;
`uvm_object_utils(crc_error_cb)
virtual function void pre_drive(eth_driver drv, ref eth_item tr);
if ($urandom_range(0,99) < error_rate_pct) begin
tr.force_bad_crc = 1;
tr.inject_tag = "CRC_BAD";
end
endfunction
endclassclass length_corrupt_cb extends eth_driver_cb;
virtual function void pre_drive(eth_driver drv, ref eth_item tr);
if (tr.inject_len_fault) begin
tr.payload_len = tr.payload_len + 3; // declared length mismatch
tr.inject_tag = "LEN_MISMATCH";
end
endfunction
endclass[DRV][ADV] corruption flow
tr generated nominal
▼
pre_drive callbacks
├─ crc_error_cb -> maybe set force_bad_crc
└─ length_corrupt_cb -> maybe skew payload_len
▼
host drives resulting malformed frameUse tags/metadata fields to mark injected items for scoreboard correlation.
Prefer deterministic seed + rate controls for reproducible regressions.
Do not bypass host legality checks unless violation itself is the objective.
Pattern 2: delay injection callbacks
Delay injection stresses timeout logic, arbitration fairness, and retry handling. Task-based hooks let callbacks introduce cycle or time delays before key host actions.
class jitter_delay_cb extends apb_driver_cb;
rand int unsigned min_cycles = 0;
rand int unsigned max_cycles = 5;
`uvm_object_utils(jitter_delay_cb)
virtual task pre_drive(apb_driver drv, apb_item tr);
int unsigned d = $urandom_range(min_cycles, max_cycles);
repeat (d) @(posedge drv.vif.pclk);
tr.inject_delay_cycles = d;
endtask
endclassclass burst_pause_cb extends axi_driver_cb;
virtual task pre_beat(axi_driver drv, axi_item tr, int beat_idx);
if (tr.inject_pause && beat_idx == tr.pause_at_beat)
repeat (tr.pause_cycles) @(posedge drv.vif.aclk);
endtask
endclass[DRV] timing perturbation timeline
nominal:
beat0 beat1 beat2 beat3
with delay callback:
beat0 --pause-- beat1 beat2 ----pause---- beat3
[TEST] validates timeout/retry and latency counters[UVM][ADV] delay injection guardrails
Keep delay bounded
- avoid deadlock-like behavior
Annotate injected delay on txn
- helps scoreboards and coverage bins
Distinguish intentional pauses from DUT stalls
- add callback-side log at UVM_HIGHDelay hooks should be task-based and bounded by policy limits.
Store injected delay metadata for downstream diagnostics.
Validate that backpressure/timeouts trigger expected DUT behavior.
Pattern 3: protocol perturbation callbacks
Protocol perturbation pushes interfaces into unusual but controlled states: out-of-spec ordering, malformed handshake sequences, or edge-case legal sequences at stress boundaries.
class axi_id_reuse_cb extends axi_driver_cb;
virtual function void pre_send(axi_driver drv, ref axi_item tr);
if (tr.inject_id_reuse)
tr.awid = tr.target_reuse_id; // stress outstanding ID handling
endfunction
endclassclass valid_glitch_cb extends stream_driver_cb;
virtual task pre_cycle(stream_driver drv, ref stream_item tr);
if (tr.inject_valid_glitch) begin
drv.vif.valid <= 1'b1;
@(posedge drv.vif.clk);
drv.vif.valid <= 1'b0; // force early drop
end
endtask
endclass[DRV][ADV] perturbation examples
AXI:
- reuse ID under pressure
- long READY deassert bursts
- boundary-aligned burst with odd len
Stream:
- early VALID drop
- inserted bubble mid-packet
- malformed tlast placement[TEST] checker alignment
injection callback
▼
monitor tags observed anomaly
▼
scoreboard expects DUT recovery/reporting behavior
▼
pass criteria:
- DUT flags error OR retries OR drops safely per specProtocol perturbation should target explicit spec behaviors/checkers.
Prefer one perturbation axis per test for root-cause clarity.
Synchronize callback policy with scoreboard expectation model.
Walkthrough: staged fault campaign
A practical campaign often runs in phases: baseline nominal traffic, then isolated corruption, then timing stress, then combined stress to validate interaction handling.
[TEST][ADV] staged campaign plan
Phase 0: no callbacks
- establish clean baseline
Phase 1: corruption callbacks only
- CRC/length faults
Phase 2: delay callbacks only
- jitter and burst pauses
Phase 3: protocol perturbation only
- handshake/order stress
Phase 4: constrained combination
- limited blend for interaction validationtask run_fault_campaign(axi_driver drv);
crc_error_cb c0 = crc_error_cb::type_id::create("c0");
jitter_delay_cb c1 = jitter_delay_cb::type_id::create("c1");
axi_id_reuse_cb c2 = axi_id_reuse_cb::type_id::create("c2");
// Phase 1
uvm_callbacks#(axi_driver, axi_driver_cb)::add(drv, c0);
run_n_items(100);
uvm_callbacks#(axi_driver, axi_driver_cb)::delete(drv, c0);
// Phase 2
uvm_callbacks#(axi_driver, axi_driver_cb)::add(drv, c1);
run_n_items(100);
uvm_callbacks#(axi_driver, axi_driver_cb)::delete(drv, c1);
// Phase 3
uvm_callbacks#(axi_driver, axi_driver_cb)::add(drv, c2);
run_n_items(100);
uvm_callbacks#(axi_driver, axi_driver_cb)::delete(drv, c2);
endtaskKey takeaways
Callbacks are a natural fit for reusable, scoped error injection policies.
Use three primary injection families: data corruption, delay, protocol perturbation.
Tag injected transactions and align checker expectations with injection semantics.
Prefer staged campaigns to isolate and debug failure modes efficiently.
Common pitfalls
Always-on injection callbacks - contaminates unrelated tests.
Unbounded delays - can create deadlock-like simulation behavior.
Combining many perturbations at once - failure triage becomes ambiguous.
No metadata on injected items - scoreboard cannot separate intended vs unintended errors.