Skip to content

UVM

Timing Handling

The timing scenarios a driver must manage beyond the clocked drive — reset (abort and idle, resume after release), wait states, read latency, idle driving, and asynchronous events.

Drivers · Module 12 · Page 12.4

The Engineering Problem

The previous chapter handled the synchronous boundary — driving through a clocking block to avoid the clock-edge race (Module 12.3). But steady-state, edge-aligned driving is the easy part. A real driver faces timing scenarios the clocking block alone doesn't cover: a reset can assert mid-transaction and must abort what's in flight; the DUT may not be ready every cycle (wait states); read data may come back N cycles later (latency); and between transactions the interface must be driven to a defined idle. These are where drivers actually break — most notoriously, a driver that ignores reset corrupts the testbench the moment a reset hits during active traffic.

Timing handling is the driver's management of these scenarios. The centerpiece is reset: the driver must drive idle during reset, abort an in-flight transaction the instant reset asserts (not keep driving a now-meaningless transaction), and resume the normal loop only after reset releases. Beyond reset: wait states (wait for the DUT's handshake — ready — before advancing), read latency (sample the response at the protocol's defined cycle), idle driving (drive a defined idle state between transactions, not stale or X values), and asynchronous events (respond to events not aligned to the clock). This chapter is timing handling: the reset-aware loop, wait states, latency, idle, and why a driver that doesn't handle reset is broken the moment reset hits mid-transaction.

What timing scenarios must a driver handle beyond the clocked drive — reset (abort in-flight and idle, resume after release), wait states, read latency, idle driving, asynchronous events — and why is reset handling the one most drivers get wrong?

Motivation — why timing handling is more than the clocked drive

A driver needs explicit timing handling because real protocols and real simulations have timing that deviates from steady-state, and each deviation needs a response:

  • Reset can happen anytime, so the driver must abort and recover. Reset isn't only at time 0 — it can assert mid-transaction (a test reset, an error-recovery reset). A driver that keeps driving its in-flight transaction through a reset leaves the DUT and driver out of sync; it must abort and return to idle, then resume after release.
  • The DUT isn't always ready, so the driver must wait. A handshake protocol (valid/ready) means the DUT accepts an item only when ready; the driver must wait for ready before advancing each beat — back-pressure (Module 8.3) realized at the signal level.
  • Responses are delayed, so the driver must time the sample. Read data returns at a defined latency (fixed or variable); the driver must sample at the right cycle (Module 12.2's sampling, now with the latency made explicit), or it captures garbage.
  • Gaps between transactions need a defined state, so the driver drives idle. When no transaction is pending, the interface can't be left stale or undriven (X); the driver must drive a defined idle (de-assert valid, hold defaults) so the DUT sees a clean idle, not garbage.
  • Some events aren't clocked, so the driver handles them asynchronously. Reset assertion, async handshakes, interrupts — events not aligned to the clock — can't be handled by the clocking block alone; the driver must respond to them directly (e.g., watching reset on its own edge).

The motivation, in one line: real protocols deviate from steady-state — reset interrupts, the DUT stalls, responses lag, gaps need filling, events arrive async — so a correct driver must handle each deviation (abort-on-reset, wait-for-ready, sample-at-latency, drive-idle, respond-async), not just drive edge-aligned transactions back to back.

Mental Model

Hold timing handling as driving a car through real conditions, not just steady cruising:

Steady cruising (the clocked drive) is the easy part; real driving is handling the stops, the emergencies, the variable conditions, and the idling — and timing handling is all of that. Cruising at a constant speed on an open road is the steady clocked drive — you just keep going. But real driving has more: an emergency (reset) means pull over and reset to a safe stop immediately — you don't keep driving your planned route through it; you abort, idle safely, and resume only when it's clear. Traffic (wait states) means hold when the car ahead isn't moving — you wait for it to be ready before advancing. A delayed signal (read latency) means the green light comes some seconds after you arrive — you wait the prescribed time before proceeding. And at a red light (between transactions), you don't abandon the car (leave signals X) or creep (drive stale values) — you hold at a defined idle. The skill isn't the cruising; it's responding correctly to everything that isn't cruising — and the most dangerous mistake is ignoring the emergency: a driver who keeps cruising through a reset (ignores it) ends up out of position, out of sync with everyone else, exactly as a UVM driver that ignores reset ends up out of sync with the DUT.

So timing handling is driving through real conditions: abort and idle on the emergency (reset), wait in traffic (wait states), wait the delay (latency), hold cleanly at the light (idle) — the steady drive is trivial, and the correctness is entirely in handling the deviations, above all the reset.

Visual Explanation — the timing scenarios a driver handles

The defining picture is the set of timing scenarios a driver must handle — beyond the steady clocked drive — with reset as the centerpiece.

A driver handles reset, wait states, read latency, idle driving, and asynchronous events beyond the steady driveabort + idle +resumewait forreadysample at latencydrive idle between txnsdrive idlebetween…respond asyncDriver timing handlingbeyond the steady clocked driveReset (critical)abort in-flight, idle,resumeWait stateswait for ready(back-pressure)Read latencysample at the defined cycleIdle drivingdefined idle between txnsAsync eventsnon-clocked events12
Figure 1 — the timing scenarios a driver handles. Steady drive (the baseline, Module 12.3) is edge-aligned driving. Beyond it: reset (abort in-flight, drive idle, resume after release) — the most critical; wait states (wait for the DUT's ready before advancing); read latency (sample the response at the defined cycle); idle driving (a defined idle between transactions); asynchronous events (respond to non-clocked events). Timing handling is managing all of these, not just the steady drive — and reset is where most drivers fail.

The figure maps the timing scenarios a driver must handle, around the steady clocked drive (the baseline from Module 12.3). Reset (the critical one): the driver must abort an in-flight transaction, drive idle during reset, and resume after release — and this is the scenario most drivers get wrong, because in steady-state development reset only happens at time 0 (before traffic), so the mid-transaction reset case is untested until it bites. Wait states: the driver must wait for the DUT's ready before advancing each beat — the signal-level form of back-pressure (Module 8.3), so the driver paces to the DUT. Read latency: the driver must sample the response at the protocol's defined cycle (Module 12.2's sampling, with the latency now explicit) — sampling early or late captures garbage. Idle driving: between transactions, the driver must drive a defined idle state (de-assert valid, hold defaults) — not leave signals stale or X, which the DUT could misread as a transaction. Asynchronous events: events not aligned to the clock (reset assertion, async handshakes) must be handled directly — the clocking block (Module 12.3) handles the synchronous boundary, but async events need their own handling. The crucial reading is that timing handling is the deviations, not the steady drive: the steady edge-aligned drive is the trivial baseline, and the driver's real timing work — and its bugs — are in responding correctly to reset, stalls, latency, gaps, and async events. And reset is the highlighted scenario because it's both the most important (a mishandled reset corrupts everything after) and the most commonly missed.

RTL / Simulation Perspective — the reset-aware driver

The defining structure of timing handling is the reset-aware loop: drive idle during reset, fork the protocol drive against a reset watcher, and abort the drive when reset asserts. The code shows it.

a reset-aware driver: abort in-flight on reset, idle, resume
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
task my_driver::run_phase(uvm_phase phase);
  forever begin
    // ── during reset: drive IDLE and WAIT for reset to release ──
    if (!vif.rst_n) begin
      drive_idle();                          // de-assert valid, hold defaults — clean idle
      @(posedge vif.rst_n);                  // wait for reset to RELEASE
    end
 
    // ── run the protocol drive, but ABORT it if reset asserts ──
    fork
      begin : drive_proc
        forever begin
          seq_item_port.get_next_item(req);
          drive_transaction(req);            // wait-states + latency handled inside (below)
          seq_item_port.item_done();
        end
      end
      begin : reset_watch
        @(negedge vif.rst_n);                // reset ASSERTED → fall through
      end
    join_any
    disable fork;                            // ← ABORT the in-flight drive (reset hit) — back to idle
  end
endtask
 
task my_driver::drive_transaction(bus_item tr);
  @(vif.drv_cb);
  vif.drv_cb.valid <= 1;  vif.drv_cb.addr <= tr.addr;
  do @(vif.drv_cb); while (!vif.drv_cb.ready);          // WAIT STATES: hold until ready
  repeat (READ_LATENCY) @(vif.drv_cb);                  // READ LATENCY: wait the defined cycles
  if (tr.is_read) tr.rdata = vif.drv_cb.rdata;          // sample at the right cycle
  vif.drv_cb.valid <= 0;                                 // idle between transactions
endtask

The code shows timing handling's structure. Reset handling (the outer loop): during reset (!vif.rst_n), the driver drives idle (drive_idle() — clean, defined values) and waits for reset to release (@(posedge vif.rst_n)). Then it runs the protocol drive inside a fork against a reset watcher: drive_proc runs the normal pull-convert-complete loop, while reset_watch waits for reset to assert (@(negedge vif.rst_n)). The join_any returns when either finishes — and if reset asserts, reset_watch completes, join_any returns, and disable fork aborts the in-flight drive_proc (Module 10.5's race pattern, applied to reset) — killing the half-driven transaction and returning to the outer loop, which sees reset and drives idle again. This is the abort-on-reset discipline: the driver doesn't keep driving a meaningless transaction through the reset; it aborts the instant reset asserts. Wait states and latency (inside drive_transaction): do @(vif.drv_cb); while (!ready) holds until the DUT is ready (wait states), and repeat (READ_LATENCY) @(vif.drv_cb) waits the defined cycles before sampling (read latency). And valid <= 0 idles between transactions. The shape to carry: the reset-aware loop is drive-idle-during-reset + fork-drive-against-reset-watcher + disable-fork-on-reset, with wait states and latency handled inside the per-transaction drive — so the driver behaves correctly across reset, stalls, and delays, not just steady-state.

Verification Perspective — the reset-aware loop

Reset handling is the most consequential timing behavior, and its structure — drive idle, run-with-abort, resume — is the pattern to internalize. Getting it right is the difference between a driver that survives reset and one that corrupts on it.

The reset-aware loop: drive idle during reset, run the drive against a reset watcher, abort on reset assertion, resume after releasedrive idle (reset) → run drive + watch reset → reset asserts? abort → resumedrive idle (reset) → run drive + watch reset → reset asserts? abort → resume1During reset: drive idle, wait releasedrive defined idle values; block until reset releases (@(posedgerst_n)).2Run drive forked against reset watcherthe protocol drive loop runs concurrently with a watcher waitingfor reset to assert.3Reset asserts → abort in-flightthe watcher fires, join_any returns, disable fork kills thehalf-driven transaction.4Back to idle → resume after releasereturn to step 1: drive idle, wait release, then run again — resetalways wins.
Figure 2 — the reset-aware loop. During reset, drive idle and wait for release. After release, run the protocol drive forked against a reset watcher. If reset asserts mid-transaction, the watcher fires, join_any returns, and disable fork aborts the in-flight drive — back to drive-idle. If the drive completes normally, loop to the next transaction. So reset always wins: an in-flight transaction is aborted the instant reset asserts, and the driver returns to a clean idle, resuming only after release.

The reset-aware loop is a fixed pattern that guarantees reset always wins. During reset (step 1): the driver drives idle — defined, clean values (de-asserted valid, default signals) — so the DUT sees a clean idle, not a half-driven transaction or X; and it waits for reset to release (@(posedge rst_n)), so it doesn't try to run the protocol while held in reset. Run with abort (step 2): after release, the driver runs the protocol drive loop forked against a reset watcher — the drive does its normal work, while the watcher waits for reset to assert. Abort (step 3): if reset asserts mid-transaction, the watcher fires, join_any returns, and disable fork aborts the in-flight drive — killing the half-driven transaction immediately. Resume (step 4): control returns to the outer loop (step 1), which drives idle again and waits for release before running. The guarantee this provides is that reset always wins: no matter where in a transaction reset asserts, the driver aborts the transaction that instant and returns to a clean idle, resuming only after release. This is why the structure matters: a driver without it would keep driving the in-flight transaction through the reset (because nothing interrupts the drive), leaving the driver mid-protocol while the DUT resets — out of sync, corrupting everything after (the DebugLab). The reset-aware loop is the standard driver structure precisely because reset can happen anytime, and a driver must be correct when it does — which means abort, idle, resume, every time.

Runtime / Execution Flow — wait states and read latency

Beyond reset, the driver's per-transaction timing handles wait states (the DUT stalling) and read latency (the response delay). The flow shows how the driver paces to the DUT and times the sample.

Per-transaction timing: drive the request, wait for ready (wait states), wait the read latency, then sampledrive request → wait for ready (wait states) → wait read latency → sampledrive request → wait for ready (wait states) → wait read latency → sample1Drive the requestassert valid, drive the fields (through the clocking block, Module12.3).2Wait states: hold until readyloop on the clock while the DUT isn't ready — advance only when itaccepts (back-pressure).3Read latency: wait the defined cyclesfor a read, wait the protocol's read latency before the data isvalid.4Sample at the right cyclecapture the response when valid — too early/late gets garbage(Module 12.2).
Figure 3 — wait states and read latency within a transaction. The driver drives the request, then handles wait states: it holds (waiting on the clock) until the DUT asserts ready, so it advances only when the DUT accepts — back-pressure at the signal level. For a read, it then waits the protocol's read latency (the defined number of cycles) before sampling the response, so it captures the data at the cycle it's valid. Wait states pace the driver to the DUT; read latency times the sample. Both adapt the driver to the DUT's timing.

The per-transaction timing adapts the driver to the DUT's pace. Drive the request (step 1): the driver asserts valid and drives the fields through the clocking block (Module 12.3). Wait states (step 2): the DUT may not be ready every cycle — so the driver holds, looping on the clock while the DUT isn't ready, and advances only when ready asserts. This is back-pressure at the signal level (Module 8.3): the driver paces to the DUT, so a slow DUT stretches the transaction over more cycles, and the driver waits rather than driving ahead. Read latency (step 3): for a read, the data isn't available immediately — it returns at the protocol's defined latency (some cycles after the request) — so the driver waits the defined cycles before the data is valid. Sample (step 4): the driver captures the response when valid — at the right cycle — because sampling too early (before the data arrives) or too late (after it changes) captures garbage (Module 12.2's sampling, with the latency being what defines "the right cycle"). The runtime insight is that both wait states and read latency are forms of the driver waiting for the DUT's timing: wait states wait for the DUT to accept (variable, handshake-driven), read latency waits for the DUT to respond (fixed or variable, protocol-defined). A driver that doesn't handle them — driving every cycle regardless of ready, or sampling immediately without the latency — races ahead of the DUT and corrupts the transaction. So the per-transaction drive isn't a fixed sequence of cycles; it's an adaptive one that waits on the DUT's ready and times the sample to the protocol's latency — the driver following the DUT's pace, not forcing its own.

Waveform Perspective — reset abort and a wait state

The two most important timing behaviors are visible on a timeline: the driver holding through a wait state (DUT not ready), and aborting an in-flight transaction when reset asserts.

Timing handling: a wait state (hold for ready) and a reset abort (in-flight transaction killed)

12 cycles
Timing handling: a wait state (hold for ready) and a reset abort (in-flight transaction killed)wait state: valid high but ready low → driver HOLDS, transaction stretcheswait state: valid high…ready asserts → transaction advances/completesready asserts → transa…reset asserts (rst_n low) → driver ABORTS in-flight, drives idle (valid low)reset asserts (rst_n l…reset releases (rst_n high) → driver resumesreset releases (rst_n …clkrst_nvalidreadystateDRVWAITDRV----RSTRSTRSTIDLEDRVDRV--t0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — wait states and reset abort. Early, the driver drives a transaction but the DUT isn't ready (ready low), so the driver HOLDS valid high and waits — the transaction stretches until ready asserts and it completes. Later, reset asserts (rst_n falls) mid-transaction: the driver ABORTS the in-flight drive (disable fork) and drives idle (valid low) immediately, not continuing the transaction. After reset releases (rst_n rises), the driver resumes. Wait states pace to the DUT; reset aborts in-flight and idles.

The waveform shows the driver adapting to timing. Early on, a wait state: the driver drives a transaction (valid high) but the DUT isn't ready (ready low at cycle 1), so the driver holdsvalid stays high and the transaction stretches (the WAIT state) — until ready asserts (cycle 2), at which point the transaction advances/completes. This is the driver pacing to the DUT: it doesn't drive ahead; it waits for the DUT to accept. Later, a reset abort: rst_n falls (cycle 5) mid-traffic, and the driver immediately aborts the in-flight drive (disable fork) and drives idlevalid goes low and the driver enters the RST state, not continuing whatever transaction was in flight. When rst_n rises (cycle 8, reset releases), the driver resumesIDLE then back to DRV. The two behaviors are the core of timing handling: the wait state shows the driver holding for the DUT's ready (back-pressure), and the reset abort shows the driver killing the in-flight transaction the instant reset asserts and driving idle (reset always wins). The crucial visual is what the driver does when reset hits: valid drops to idle (the driver abandoned the transaction), not continuing to drive — which is exactly what a reset-aware driver does and what a reset-ignoring driver fails to do (it would keep valid high, driving a meaningless transaction through reset). Reading a waveform for timing handling — does the driver hold for ready? does it idle on reset? — is how you verify the driver handles the DUT's timing correctly, not just the steady drive.

DebugLab — the driver that corrupted on a mid-traffic reset

A driver that worked until a reset asserted during active traffic — no reset handling

Symptom

A driver worked perfectly in normal operation — and even with reset, as long as the reset happened at time 0, before any traffic. But when a test asserted reset during active traffic (an error-recovery reset, a reset-in-the-middle test), the testbench broke: after the reset, the driver and DUT were out of sync — the driver was mid-protocol on a transaction the DUT had forgotten, subsequent transactions were corrupted, and sometimes the driver hung waiting for a handshake the reset had cleared. The driver's steady-state behavior was flawless; it only broke when reset hit mid-transaction.

Root cause

The driver had no reset handling — it didn't watch for reset, so when reset asserted mid-transaction, the driver kept driving its in-flight transaction, ending up out of sync with the reset DUT:

why a mid-traffic reset corrupted the driver
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
driver (NO reset handling):
  forever begin
    get_next_item(req);
    drive_transaction(req);   // ✗ keeps driving even if reset asserts MID-transaction
    item_done();
  end
 
when reset asserts mid-transaction:
  the DUT resets (forgets the transaction, returns to initial state)
  BUT the driver keeps driving drive_transaction(req) — half-driven, now meaningless
  → driver is mid-protocol, DUT is reset → OUT OF SYNC
  → next transactions corrupted; or driver hangs waiting for a 'ready' the reset cleared
 
why it passed before: reset only happened at time 0 (before any traffic), so no in-flight
  transaction to corrupt — the mid-transaction case was simply never exercised
 
fix — make the driver RESET-AWARE: abort in-flight, idle, resume after release:
  forever begin
    if (!vif.rst_n) begin drive_idle(); @(posedge vif.rst_n); end   // idle during reset
    fork
      begin : drv  forever begin get_next_item(req); drive_transaction(req); item_done(); end end
      begin : rstw @(negedge vif.rst_n); end                        // watch for reset
    join_any
    disable fork;                                                    // ABORT in-flight on reset
  end

This is the no-reset-handling bug, the most common — and most deceptive — driver timing failure, because it passes until reset hits during traffic. The driver had no mechanism to detect reset: its run_phase was a plain pull-convert-complete loop with nothing watching for reset. So when reset asserted mid-transaction, the DUT reset (forgetting the in-flight transaction, returning to its initial state) but the driver kept driving drive_transaction(req) — a half-driven, now-meaningless transaction. The result is out-of-sync: the driver is mid-protocol (still asserting valid, waiting for ready) while the DUT is reset (not in the protocol at all) — so subsequent transactions are corrupted (the driver and DUT disagree on state), or the driver hangs waiting for a handshake (ready) that the reset cleared. The tell is precisely the symptom: it passed when reset was only at time 0 (no in-flight transaction to corrupt — the mid-transaction case never exercised), and broke when reset hit during traffic. The fix is to make the driver reset-aware: during reset, drive idle and wait for release; run the drive forked against a reset watcher; and on reset assertion, disable fork to abort the in-flight transaction and return to idle. Now reset always wins — the in-flight transaction is aborted the instant reset asserts, and the driver resyncs by idling and resuming after release. The general lesson, and the chapter's thesis: a driver must handle reset — abort the in-flight transaction, drive idle during reset, and resume after release — because reset can assert anytime (not just time 0), and a driver that ignores reset keeps driving through it, ending out of sync with the DUT and corrupting everything after. The latency of the bug — passing until a mid-traffic reset — is what makes it dangerous: it's not caught by steady-state or time-0-reset testing, only by resetting during active traffic.

Diagnosis

The tell is a driver that breaks only on a mid-traffic reset. Diagnose reset-handling bugs:

  1. Correlate the failure with a mid-transaction reset. A driver correct in steady-state and with time-0 reset, but broken when reset asserts during traffic, points at missing reset handling — the mid-transaction case.
  2. Check for a reset watcher. Look for a forked process (or equivalent) watching reset and aborting the drive; its absence is the bug — the driver keeps driving through reset.
  3. Check for idle driving during reset. Confirm the driver drives a defined idle (de-asserted) during reset; a driver still asserting valid through reset is out of sync.
  4. Look for a post-reset hang. A driver hanging on a handshake after a reset is waiting for a signal the reset cleared — a symptom of not aborting the in-flight transaction.
Prevention

Make the driver reset-aware:

  1. Drive idle during reset, wait for release. While reset is asserted, drive defined idle values and block until reset releases — don't run the protocol in reset.
  2. Fork the drive against a reset watcher. Run the drive loop concurrently with a process watching for reset assertion, and disable fork to abort the in-flight transaction when reset hits.
  3. Resume cleanly after release. Return to the idle/wait-for-release state and resume the loop only after reset releases — so the driver resyncs with the DUT.
  4. Test reset during active traffic. A reset only at time 0 doesn't exercise the mid-transaction abort; assert reset during traffic (and across seeds) to surface missing reset handling.

The one-sentence lesson: a driver must be reset-aware — drive idle during reset, abort an in-flight transaction the instant reset asserts (a forked reset watcher + disable fork), and resume after release — because reset can assert anytime (not just time 0), and a driver that ignores reset keeps driving its in-flight transaction through the reset, ending out of sync with the DUT and corrupting everything after (a bug that passes until a mid-traffic reset exercises it).

Common Mistakes

  • No reset handling. A driver that doesn't watch for reset keeps driving an in-flight transaction through a mid-traffic reset, ending out of sync; abort on reset, idle, and resume.
  • Not driving idle during reset. Leaving valid asserted (or signals stale/X) during reset confuses the DUT; drive a defined idle while reset is held.
  • Ignoring wait states. Driving every cycle regardless of ready races ahead of a stalling DUT; wait for ready before advancing (back-pressure).
  • Sampling reads at the wrong cycle. Not accounting for read latency captures garbage; wait the protocol's defined latency before sampling (Module 12.2).
  • Only testing reset at time 0. The mid-transaction reset abort is untested by time-0 reset; assert reset during active traffic.
  • Handling async events through the clocking block. Reset is asynchronous; watch it directly, not through the clocking block (which is for synchronous signals, Module 12.3).

Senior Design Review Notes

Interview Insights

A driver must be reset-aware: drive idle during reset, abort an in-flight transaction the instant reset asserts, and resume only after reset releases. Concretely, the driver's run_phase has an outer loop that first checks reset — while reset is asserted, it drives a defined idle state (de-asserted valid, default signals) and waits for reset to release. After release, it runs the protocol drive loop, but forked against a reset watcher: one forked process runs the normal pull-convert-complete loop, and another waits for reset to assert. A join_any returns when either finishes, and then a disable fork aborts the in-flight drive. So if reset asserts mid-transaction, the watcher fires, join_any returns, disable fork kills the half-driven transaction, and control returns to the outer loop, which drives idle and waits for release again. The guarantee is that reset always wins: no matter where in a transaction reset asserts, the driver aborts immediately and returns to a clean idle, resuming after release. This matters because reset can happen anytime, not just at time 0 — an error-recovery reset or a reset-in-the-middle test can assert during active traffic — and a driver that ignores reset keeps driving its in-flight transaction through the reset, so the driver ends up mid-protocol while the DUT is reset, leaving them out of sync and corrupting subsequent transactions, or hanging on a handshake the reset cleared. The deceptive part is that a driver with no reset handling passes when reset only happens at time 0, because there's no in-flight transaction to corrupt, and only breaks when reset hits mid-traffic. So the correct structure is idle-during-reset, fork-drive-against-reset-watcher, disable-fork-on-reset, resume-after-release, and you test it by asserting reset during active traffic.

Because the bug only manifests when reset asserts while a transaction is in flight, and time-0 reset never has an in-flight transaction. A driver with no reset handling is a plain pull-convert-complete loop with nothing watching for reset, so it keeps driving whatever transaction it's on regardless of reset. When reset happens at time 0, before any traffic, there's no transaction being driven, so there's nothing to corrupt — the driver just starts driving after reset releases, and everything works. So in normal development and in tests that reset only at the start, the driver looks completely correct. The problem appears only when reset asserts mid-transaction, which happens in error-recovery scenarios or reset-in-the-middle tests: now the DUT resets and forgets the in-flight transaction, but the driver, having no reset detection, keeps driving the half-driven transaction, so the driver is mid-protocol while the DUT is reset, and they're out of sync. Subsequent transactions are corrupted because the two disagree on state, or the driver hangs waiting for a ready the reset cleared. The reason this is dangerous is exactly that it's latent: steady-state testing and time-0-reset testing don't exercise the mid-transaction abort, so the bug passes all the obvious tests and surfaces later, when a test happens to reset during active traffic, often on a different simulator or seed. The fix is to make the driver reset-aware so it aborts the in-flight transaction the moment reset asserts, and the way to catch it is to deliberately assert reset during traffic. So the pass-until-mid-traffic-reset pattern is the signature of missing reset handling: correct in steady state, correct with time-0 reset, broken when reset interrupts a transaction.

Exercises

  1. Structure the reset loop. Sketch a reset-aware driver run_phase that drives idle during reset, forks the drive against a reset watcher, and aborts on reset.
  2. Diagnose the corruption. A driver works in steady state and with time-0 reset but breaks when reset asserts during traffic. Name the cause and the fix.
  3. Handle the wait state. Write the loop that holds a beat until the DUT asserts ready, and explain why driving ahead would corrupt the transaction.
  4. Sync vs async. Explain why reset is handled asynchronously (watched directly) while the data bus is driven synchronously (through the clocking block).

Summary

  • Timing handling is the driver's management of timing scenarios beyond the steady clocked drive — most critically reset, plus wait states, read latency, idle driving, and asynchronous events.
  • Reset is the centerpiece: the driver must drive idle during reset, abort an in-flight transaction the instant reset asserts (a forked reset watcher + disable fork), and resume after release — so reset always wins, no matter where in a transaction it hits.
  • Wait states make the driver wait for the DUT's ready before advancing (back-pressure at the signal level), and read latency makes it sample the response at the protocol's defined cycle — both adapt the driver to the DUT's pace.
  • Idle driving holds the interface in a defined idle (de-asserted) during reset and between transactions — not stale or X — so the DUT never misreads the bus; asynchronous events (reset) are watched directly, not through the clocking block.
  • The durable rule of thumb: make the driver reset-aware — idle during reset, abort the in-flight transaction on reset assertion, resume after release — and handle wait states (wait for ready), read latency (sample at the defined cycle), and idle driving, because a driver that ignores reset corrupts the moment reset hits mid-traffic (a latent bug that passes until a mid-transaction reset exercises it), and one that ignores wait states or latency races ahead of the DUT.

Next — Driver Architecture: the driver's responsibilities, conversion, signal driving, and timing all need organizing; the next chapter covers the driver's internal architecture — how to structure the run loop, separate the protocol logic, and build a clean, maintainable, reusable driver.