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.

diagram
[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
diagram
[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 path
  • Callbacks 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.

systemverilog
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
endclass
systemverilog
class 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
diagram
[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 frame
  • Use 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.

systemverilog
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
endclass
systemverilog
class 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
diagram
[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
diagram
[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_HIGH
  • Delay 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.

systemverilog
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
endclass
systemverilog
class 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
diagram
[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
diagram
[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 spec
  • Protocol 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.

diagram
[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 validation
systemverilog
task 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);
endtask

Key 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.