Day 98: Virtual interfaces and clocking blocks
Virtual interfaces and clocking blocks
Testbench classes live in a dynamic, object world; the DUT lives in a static, pin world. An interface bundles the DUT's signals; a virtual interface is a handle to it that a class can hold, letting object-oriented code drive and sample real pins. A clocking block inside the interface defines *when* signals are driven and sampled relative to the clock — eliminating race conditions between the testbench and the DUT.
interface uart_if(input logic clk);
logic rx, tx;
logic [7:0] data;
clocking cb @(posedge clk);
default input #1step output #1; // sample just before, drive just after edge
output rx;
input tx;
endclocking
endinterface
class uart_driver;
virtual uart_if vif; // handle to the real interface
task drive(uart_txn t);
vif.cb.rx <= t.data[0]; // drive via clocking block -> race-free
endtask
endclassClocking blocks kill races
Without a clocking block, testbench drives and DUT updates can occur in the same delta cycle, giving order-dependent, flaky results. The clocking block's input/output skews (#1step/#1) sample just *before* the edge and drive just *after* — a clean, deterministic timing contract. This is the standard way to connect a class-based testbench to RTL.
Key terms
- Interface
- A named bundle of signals connecting a testbench to a DUT, with optional modports and clocking.
- Virtual interface
- A class-holdable handle to an interface instance, bridging OOP code and DUT pins.
- Clocking block
- A construct defining when signals are sampled/driven relative to a clock, removing races.
- Input/output skew
- The timing offsets a clocking block applies to sampling and driving (e.g. #1step / #1).
Before moving on, you should be able to
What problem does a clocking block solve when a class-based testbench drives an RTL DUT?