Reset in the middle of traffic

Derive reset handling from what the DUT preserves or discards, distinguish accepted work from an offer, and prove useful traffic recovers afterwards.

Lesson
U10.2
Time
25 min
Runs on
Verilator with UVM
Check
6 questions

A test applies reset halfway through, then sends one more value, 80. The design answers 81, correctly, and the scoreboard reports "y=81, expected 33". Before reset, the design had taken 32, and reset threw its result away. The scoreboard still had 33 waiting at the front of its queue.

The design forgets, the scoreboard remembers
0123456edge (ns)153545556585105rstinput16-32-80-result-17-81waiting17-3333 8181reset drops 3381 vs 33

Columns are selected rising edges. Reset dropped the result for 32, but the scoreboard kept predicting it.

Start from this design’s reset behavior

Accepted work and an offered item need different treatmentThis incrementer discards unfinished results on reset. The scoreboard cancels accepted work; the driver abandons an offer that never transferred.
Accepted work and an offered item need different treatmentThis incrementer discards unfinished results on reset. The scoreboard cancels accepted work; the driver abandons an offer that never transferred. accepted Accepted x = 32 45 ns: valid and readyare high prediction Expected y = 33 One result still owed ledger Canceled: 1 55 ns: removeexpected 33 offered Offered x = 48 No ready/validtransfer driver Drop the offer Lower valid; noitem_done after flush recovery New x = 80 Accepted at 85 ns;81 checked at 105 ns predictresetresetrestart
  1. Accept and predict45 ns (illustrative)

    The input monitor observes 32 being accepted. The model predicts 33, so accepted work has an entry in the scoreboard.

    More context

    The prediction comes from the observed accept, not from the driver's offer. The first pair, 16 → 17, was already checked at 35 ns.

    Follow the calls, values and waits
    1. data expected 33: Accepted x = 32 → Expected y = 33
    2. Waiting: Expected y = 33 — Waiting for result 33
  2. Distinguish the offer50 ns (illustrative)

    The driver is offering 48 when reset rises. No transfer accepted 48, so it creates no prediction and contributes nothing to the accepted count.

    Follow the calls, values and waits
    1. Waiting: Offered x = 48 — No acceptance yet
  3. Apply this reset rule55 ns (illustrative)

    Reset discards the pending DUT result. The scoreboard cancels 33 once; the driver abandons 48 while the test flushes the sequencer.

    More context

    The log prints both lines at 55 ns: "reset: 1 prediction(s) canceled" and "reset: drops x=48, no item_done". Reset turns off again at 70 ns.

    Follow the calls, values and waits
    1. data expected 33: Expected y = 33 → Canceled: 1
    2. data offer 48: Offered x = 48 → Drop the offer
    3. Released: Expected y = 33
    4. Released: Offered x = 48
  4. Demonstrate recovery85 ns (illustrative)

    After reset falls at 70 ns, a new item 80 is accepted.

    More context

    Restart creates fresh traffic. It does not silently retry the abandoned item.

    Follow the calls, values and waits
    1. data new item 80: Drop the offer → New x = 80
  5. Check the new result105 ns (illustrative)

    Its result 81 is checked at 105 ns. Neither cancellation nor dropping 48 counts as a successful comparison.

Read the diagram as text
  • Accepted x = 32: 45 ns: valid and ready are high
  • Expected y = 33: One result still owed
  • Canceled: 1: 55 ns: remove expected 33
  • Offered x = 48: No ready/valid transfer
  • Drop the offer: Lower valid; no item_done after flush
  • New x = 80: Accepted at 85 ns; 81 checked at 105 ns
  • Accepted x = 32 → Expected y = 33: predict
  • Expected y = 33 → Canceled: 1: reset
  • Offered x = 48 → Drop the offer: reset
  • Drop the offer → New x = 80: restart

Arrows show bookkeeping and restart actions. Together with 16 → 17 before reset, the final ledger is 3 accepted = 2 checked + 1 canceled. Offered 48 is outside that ledger.

Reset discards an unfinished result, so the bench must cancel the accepted work it removes, but not an offer that never transferred.

Other designs may finish accepted requests, return an abort response, or preserve work through a soft reset. A memory may retain its contents while its interface resets. Derive cancellation and model updates from that design behavior; UVM supplies no universal rule that all work vanishes.

Here reset changes on falling edges and is sampled on rising edges. At a sampled reset edge, the monitor gives reset priority and reports no transfer. An asynchronous reset or another clock domain needs its own specified sampling and ordering rule.

Coordinate every place that holds state

The test raises reset at 50 ns. At 55 ns the monitor reports reset, the sequencer is stopped, and the driver abandons its pending offer. Each action handles a different kind of state.

PartState before resetAction in this example
Test and sequencerThe first sequence and its pending requeststop_sequences() kills the sequence and flushes its request
Driver48 is offered but unacceptedLower valid, discard this request, wait for reset release
MonitorA transfer or reset at the sampled edgePublish reset; do not invent an acceptance for 48
Reference calculationy = x + 1 has no persistent stateNo calculation state to reset; a stateful model would apply the DUT’s reset values
ScoreboardExpected result 33 is pendingCount it as canceled, then remove it
Coverage and other consumersHistory and possibly local queued workApply their own reset rule; keep lifetime progress counts

A stateful predictor may preserve memory while clearing control state. Reset only the state the DUT specification resets.

U7.2’s analysis FIFOs can also contain old observations, and a consumer may already hold an item outside its FIFO. Flush, finish or cancel that work according to the same reset rule. Clearing the scoreboard queue alone cannot reset a separate model or consumer.

Organizing reset in a larger environment

A reset agent can drive the pin while a coordinator distributes the sampled event and waits for participants to acknowledge recovery. Define the cut before allowing new traffic, especially across clock domains.

UVM has pre_reset, reset and post_reset run-time phases; U1.2 shows the run-time schedule. They provide scheduling callbacks; their names do not drive reset pins or clear models. A domain can revisit phases through a jump, but the bench still supplies every recovery action.

Do not erase lifetime coverage merely because the DUT reset. Gate reset-cycle samples where appropriate, and keep separate coverage of reset during idle, an unaccepted offer, accepted work and repeated resets.

Release the driver after the sequencer flush

stop_sequences() stops sequences registered on that sequencer and removes their requests and reservations. The typed sequencer also clears its request FIFO and handshake flags. It does not reach into the driver to withdraw valid.

This driver uses try_next_item followed by item_done. After the flush, completing the old request would find no outstanding request and raise fatal SQRBADITMDN. The driver instead notices sampled reset, lowers valid, and abandons the request without item_done.

The reset branch belongs to the outer forever loop in the driver. Reset and stop_sequences are coordinated by this test.
do @(posedge vif.clk);
while (!vif.ready && !vif.rst);
if (vif.rst) begin
  vif.valid <= 0;
  continue;          // the test's stop_sequences() has flushed this request
end
seq_item_port.item_done();

The test must call stop_sequences() before the driver's next try_next_item; here both happen at the 55 ns edge, and the driver next asks at 80 ns. While reset remains high, the driver skips try_next_item. Omit item_done only for a request the sequencer has flushed.

For [U6.1](U6-1-uvm-pipelined-driver.html)’s pipelined get driver, request handoff already completed. Its reset logic instead handles local pending requests and any promised responses. Choose whether those responses complete, abort or disappear before changing its bookkeeping.

Count canceled work and demonstrate recovery

Before reset, 16 produced checked result 17. Accepted 32 added prediction 33, which reset cancels. Offered 48 added no prediction. After reset falls at 70 ns, fresh 80 is accepted at 85 ns and result 81 is checked at 105 ns.

Count and clear together. A second reset observation finds nothing left to cancel.
if (r.reset) begin
  canceled += expected.size();
  expected.delete();
  return;
end

The ledger is accepted = checked + canceled + waiting. Here it ends as 3 = 2 + 1 + 0. Canceled work was retired without comparison; recovery is demonstrated by new accepted work reaching a comparison after reset.

Gating the scoreboard around reset is cheaper. Suspend both input paths while reset is sampled, discard the stale predictions, and resume at the first edge after reset falls. The checks_enable knob of [U8.4](U8-4-uvm-metric-analyzer-ledger.html) is read at build time, so flipping it mid-run leaves stale predictions behind. What gating loses is the record of which accepted work reset removed.

The example requires exactly 3 accepted, 2 checked and 1 canceled in check_phase, so a run with no traffic, or with only cancellations, fails. A checked count also includes comparisons that found a mismatch, which are errors in their own right.

A late result needs an identity rule

A reused ID can hide an old responseConceptual tagged interface: a late response carries ID 3, but carries no reset generation. The untagged incrementer example has even less identity information.
A reused ID can hide an old responseConceptual tagged interface: a late response carries ID 3, but carries no reset generation. The untagged incrementer example has even less identity information. old work Old request: ID 3 Generation 4;canceled at reset response Late response: ID3 No generation on thepins new work New request: ID 3 Generation 5; waitingfor a reply ambiguity Which request? Current generation isnot proof of origin late replyexpects 3lookup ID 3
  1. Retire old bookkeeping

    The scoreboard cancels generation 4 and advances its own counter. That changes stored state, not the bits carried by a future response.

  2. Reuse the ID

    A new request uses ID 3. The scoreboard has a generation-5 prediction for it.

    More context

    The prediction is right for the new request. The trouble is that ID 3 alone no longer names one request across the reset.

    Follow the calls, values and waits
    1. data new ID 3: New request: ID 3 → Which request?
  3. Receive an ambiguous reply

    An old response arrives with ID 3. Stamping it with the current generation would misidentify it as new traffic.

    More context

    Reuse needs an observable generation, a proven flush, or a bounded period before the ID may be reused.

    Follow the calls, values and waits
    1. data old ID 3: Old request: ID 3 → Late response: ID 3
    2. data ID 3 only: Late response: ID 3 → Which request?
Read the diagram as text
  • Old request: ID 3: Generation 4; canceled at reset
  • Late response: ID 3: No generation on the pins
  • New request: ID 3: Generation 5; waiting for a reply
  • Which request?: Current generation is not proof of origin
  • Old request: ID 3 → Late response: ID 3: late reply
  • New request: ID 3 → Which request?: expects 3
  • Late response: ID 3 → Which request?: lookup ID 3

Arrows follow prediction and response information. This is an illustrative failure, not output from the runnable incrementer. A testbench counter cannot manufacture missing response identity.

The example’s DUT must not emit canceled 33 after reset. Before new work, that result has no prediction and is an error. After 80 is accepted, it mismatches expected 81. A coincidentally equal stale value could evade this untagged comparison.

A generation counter labels each stretch between resets. Pairing generation with an ID keeps old and new predictions distinct in storage. It only identifies a response if its generation is observable, or the protocol guarantees all old responses are gone before reuse.

Preserve response identity, or delay reuse until a specified drain or flush completes. Never silently discard all post-reset output.

Transfer: a soft reset preserves accepted reads

Change the design: soft reset preserves memory and completes accepted reads within 40 ns, while abandoning unaccepted offers. Decide what happens to the prediction for an accepted read, the offered request and an ID that the test wants to reuse.

Reason through the changed reset rule

Keep the accepted read’s prediction and compare its eventual response. It is waiting work, not a cancellation. Abandon the unaccepted offer only through the matching driver/sequencer procedure.

Preserve the model’s memory contents. Reserve the old ID until its response completes, or use protocol-visible identity that distinguishes the new request. Holding reset or waiting only 20 ns, as this incrementer allows, would be too short: allow the 40 ns completion bound before reusing the ID.

Test reset at idle, during a blocked offer, with accepted work pending and twice in succession. Check both the intended cancellation count and useful work completed after recovery.

Try it

Reset rises at 50 ns, with the result for 32 inside the design and 48 on offer. At 55 ns the scoreboard cancels one prediction and the driver drops 48. After reset, 80 goes in and 81 is checked.

Delete expected.delete(); from the scoreboard's reset branch and rerun: 81 is compared against the stale 33, and 81 is left waiting, so the run also ends at the 2,000 ns global timeout. Then change the driver's wait to while (!vif.ready);, ignoring reset, and read the fatal error.

RUNVerilator + UVM 2020.3.1 · simulator service
Edit the code, then run it
Expected among the UVM messages: 35 ns scoreboard y=17 checked 50 ns test reset on 55 ns scoreboard reset: 1 prediction(s) canceled 55 ns driver reset: drops x=48, no item_done 70 ns test reset off 105 ns scoreboard y=81 checked inputs 3 = checked 2 + canceled 1 UVM_ERROR : 0

Check yourself

Predict the outputThis test uses the example's bench, but reset rises at the eighth falling edge (80 ns) instead of the fifth. What line does the $display print?
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;

// The incrementer from U7 with a reset. It captures x at edge k (valid and ready both 1), is busy
// for two edges, and drives y = x + 1 with out_valid after edge k+1; monitors see it at edge k+2.
// Reset clears the busy count and drops a result still inside. STUCK_AFTER > 0 makes ready stay
// low for good after that many accepts (a hung design).
interface incr_if (input logic clk);
  logic       rst = 0;
  logic       valid = 0;
  logic [7:0] x = 0;
  logic       ready;
  logic       out_valid = 0;
  logic [7:0] y = 0;
endinterface

module incr_pipe #(parameter int STUCK_AFTER = 0) (incr_if bus);
  logic [1:0] busy = 0;
  logic       staged = 0;
  logic [7:0] stage = 0;
  int         taken = 0;
  assign bus.ready = (busy == 0) && !bus.rst && !(STUCK_AFTER > 0 && taken >= STUCK_AFTER);
  always @(posedge bus.clk) begin
    bus.out_valid <= staged && !bus.rst;
    bus.y         <= stage;
    staged        <= bus.valid && bus.ready;
    stage         <= bus.x + 1;
    if (bus.rst)                     busy <= 0;
    else if (bus.valid && bus.ready) begin busy <= 2; taken <= taken + 1; end
    else if (busy != 0)              busy <= busy - 1;
  end
endmodule

function automatic void log_event(string who, string what);
endfunction

class incr_item extends uvm_sequence_item;
  `uvm_object_utils(incr_item)
  bit [7:0] x;
  function new(string name = "incr_item"); super.new(name); endfunction
endclass

// What the monitor sees coming out; reset = 1 marks an edge with reset held.
class incr_result extends uvm_sequence_item;
  `uvm_object_utils(incr_result)
  bit [7:0] y;
  bit       reset;
  function new(string name = "incr_result"); super.new(name); endfunction
endclass

// Sends the values in the queue, one item each.
class incr_seq extends uvm_sequence #(incr_item);
  `uvm_object_utils(incr_seq)
  bit [7:0] values[$];
  string    lane = "";
  function new(string name = "incr_seq"); super.new(name); values = '{16, 32, 48}; endfunction
  task body();
    foreach (values[i]) begin
      incr_item it = incr_item::type_id::create("it");
      start_item(it);
      it.x = values[i];
      finish_item(it);
    end
  endtask
endclass

class incr_driver extends uvm_driver #(incr_item);
  `uvm_component_utils(incr_driver)
  virtual incr_if vif;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task run_phase(uvm_phase phase);
    forever begin
      @(negedge vif.clk);
      if (vif.rst) begin                     // no new item while reset is held
        vif.valid <= 0;
        continue;
      end
      seq_item_port.try_next_item(req);
      if (req == null) begin
        vif.valid <= 0;
        continue;
      end
      vif.valid <= 1;
      vif.x     <= req.x;
      do @(posedge vif.clk); while (!vif.ready && !vif.rst);
      if (vif.rst) begin                     // reset: the sequencer has forgotten this item
        vif.valid <= 0;
        log_event("driver", $sformatf("reset: drops x=%0d, no item_done", req.x));
        continue;
      end
      seq_item_port.item_done();
    end
  endtask
endclass

class incr_monitor extends uvm_monitor;
  `uvm_component_utils(incr_monitor)
  virtual incr_if vif;
  uvm_analysis_port #(incr_item)   in_ap;
  uvm_analysis_port #(incr_result) out_ap;
  function new(string name, uvm_component parent);
    super.new(name, parent);
    in_ap  = new("in_ap", this);
    out_ap = new("out_ap", this);
  endfunction
  task run_phase(uvm_phase phase);
    forever begin
      @(posedge vif.clk);
      if (vif.rst) begin                       // reset is part of what happened
        incr_result r = incr_result::type_id::create("r");
        r.reset = 1;
        out_ap.write(r);
        continue;
      end
      if (vif.valid && vif.ready) begin
        incr_item t = incr_item::type_id::create("t");
        t.x = vif.x;
        in_ap.write(t);
      end
      if (vif.out_valid) begin
        incr_result r = incr_result::type_id::create("r");
        r.y = vif.y;
        out_ap.write(r);
      end
    end
  endtask
endclass

`uvm_analysis_imp_decl(_in)
`uvm_analysis_imp_decl(_out)

class incr_scoreboard extends uvm_component;
  `uvm_component_utils(incr_scoreboard)
  uvm_analysis_imp_in  #(incr_item, incr_scoreboard)   in_imp;
  uvm_analysis_imp_out #(incr_result, incr_scoreboard) out_imp;
  bit [7:0] expected[$];
  int inputs, checked, canceled;
  function new(string name, uvm_component parent);
    super.new(name, parent);
    in_imp  = new("in_imp", this);
    out_imp = new("out_imp", this);
  endfunction
  function void write_in(incr_item t);
    inputs++;
    expected.push_back(t.x + 1);
  endfunction
  function void write_out(incr_result r);
    bit [7:0] want;
    if (r.reset) begin
      canceled += expected.size();
      expected.delete();
      return;
    end
    if (expected.size() == 0) begin
      `uvm_error("SB", $sformatf("result y=%0d with no input waiting", r.y))
      return;
    end
    want = expected.pop_front();
    if (r.y != want) `uvm_error("SB", $sformatf("y=%0d, expected %0d", r.y, want))
    checked++;
  endfunction
  function void check_phase(uvm_phase phase);
    if (expected.size() != 0)
      `uvm_error("SB", $sformatf("%0d expected results never arrived", expected.size()))
  endfunction
endclass

class predict_test extends uvm_test;
  `uvm_component_utils(predict_test)
  uvm_sequencer #(incr_item) sqr;
  incr_driver     drv;
  incr_monitor    mon;
  incr_scoreboard sb;
  virtual incr_if vif;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    sqr = uvm_sequencer #(incr_item)::type_id::create("sqr", this);
    drv = incr_driver::type_id::create("drv", this);
    mon = incr_monitor::type_id::create("mon", this);
    sb  = incr_scoreboard::type_id::create("sb", this);
    if (!uvm_config_db #(virtual incr_if)::get(this, "", "vif", vif))
      `uvm_fatal("NOVIF", "no virtual interface")
    drv.vif = vif;
    mon.vif = vif;
  endfunction
  function void connect_phase(uvm_phase phase);
    drv.seq_item_port.connect(sqr.seq_item_export);
    mon.in_ap.connect(sb.in_imp);
    mon.out_ap.connect(sb.out_imp);
  endfunction
  task run_phase(uvm_phase phase);
    incr_seq first  = incr_seq::type_id::create("first");
    incr_seq second = incr_seq::type_id::create("second");
    second.values = '{80};
    phase.raise_objection(this);
    fork first.start(sqr); join_none
    repeat (8) @(negedge vif.clk);
    vif.rst <= 1;                            // reset in the middle of traffic
    @(posedge vif.clk);
    sqr.stop_sequences();                    // kill the sequences, flush the sequencer
    repeat (2) @(negedge vif.clk);
    vif.rst <= 0;
    second.start(sqr);                       // fresh traffic after reset
    wait (sb.expected.size() == 0);
    phase.drop_objection(this);
  endtask
  function void report_phase(uvm_phase phase);
    $display("inputs %0d = checked %0d + canceled %0d", sb.inputs, sb.checked, sb.canceled);
  endfunction
endclass

module tb;
  bit clk;
  always #5 clk = ~clk;
  incr_if bus (clk);
  incr_pipe dut (bus);
  initial begin
    uvm_root::get().set_timeout(2000ns, 1);
    uvm_config_db #(virtual incr_if)::set(null, "uvm_test_top", "vif", bus);
    run_test("predict_test");
  end
endmodule
Spot the bugReset rises while 33 is waiting, and 80 is sent after reset. Each option is the scoreboard's reset branch. Which one causes a false failure?
ConceptIn this bench, reset discards unfinished work and the test calls sqr.stop_sequences() while the driver is offering an unaccepted item. What must this driver do?
ConceptWhy does the scoreboard count canceled predictions instead of just deleting them?
Read the waveformThe scoreboard uses the example's count-then-delete reset branch on each edge where the monitor reports reset. How many predictions does it cancel in total?
Inputs, results and reset at each rising edge
0123456789edge (ns)152535455565758595105rstinput10-20-30-result-11-

Every rising edge from 15 to 105 ns of a slower design than the example's: results can appear up to 50 ns after their inputs. Values are those just before each edge.

ConceptAfter reset, a scoreboard reuses ID 3 and stamps responses with its current generation. An old response can still arrive with ID 3 and no generation field. What prevents a false match?

Remember

  • Derive reset handling from the DUT: accepted work may be canceled, completed, aborted or preserved.
  • Coordinate sequencer, driver, monitor, model and every consumer; the same reset does different work in each.
  • After this try_next_item request is flushed, the driver drops its offer without item_done; a different handoff protocol needs its own cleanup.
  • Accepted equals checked plus canceled plus waiting. Require useful checked work, including after recovery.
  • A testbench generation counter cannot identify a stale response unless identity or the flush guarantee makes its origin known.

Practice