Advanced

Advanced SystemVerilog

Threads, mailboxes, assertions, and the DPI.

Threads & Interprocess Communication

A real testbench has a generator, a driver, a monitor, a scoreboard, and a coverage collector — all running at once. SystemVerilog spawns them with fork, coordinates them with events, semaphores, and mailboxes.

The three joins

A fork…join block spawns its contained statements as concurrent threads and the parent blocks until all children complete. join_any returns to the parent as soon as the first child completes, leaving the others running. join_none returns immediately, allowing the parent to proceed while all children run in the background. The three variants map cleanly onto "barrier", "race", and "spawn-and-go".

Plate VII.1 forks.sv
// Run three transactors concurrently, block until all done fork driver.run(); monitor.run(); scoreboard.run(); join // Race: whichever finishes first wins; disable the rest fork begin #1000; $display("timeout"); end begin wait(done); $display("ok"); end join_any disable fork; // kill the survivor // Background: return immediately, keep children running fork logger.run(); join_none
Figure VII.1 — three idioms: barrier, race-with-cleanup, background task.
Three joins

join waits for all.
join_any waits for one.
join_none waits for none.

Advanced OOP.

Inheritance, polymorphism, callbacks, and the factory pattern — the four mechanisms that let you extend a testbench without editing the shared environment that dozens of other engineers depend on.

Inheritance & virtual methods

A derived class extends a base class, inheriting its members and adding or overriding as needed. For the override to take effect through a base-class handle, the base method must be declared virtual. Without it, a call through a base-class handle dispatches to the base implementation — defeating the entire point of the type hierarchy.

Plate VIII.1 polymorphism.sv
class BaseTr; virtual function void display(); $display("Base transaction"); endfunction endclass class BadTr extends BaseTr; virtual function void display(); // override $display("Bad (error-injecting) transaction"); endfunction endclass BaseTr t; t = new BadTr(); // base handle, derived object t.display(); // → "Bad (error-injecting) transaction"
Figure VIII.1 — a base-class handle calling a derived method. virtual is what makes it work.
Virtual, or nothing

A method must be declared virtual to dispatch polymorphically. Without it, the compiler binds the call at the declared-type of the handle.

Functional Coverage.

"We ran a million cycles" is not a verification deliverable. Functional coverage converts cycles into a map — what interesting combinations the stimulus actually exercised — and hands you the map with the blank spots circled.

covergroup & coverpoint

A covergroup is a sampling construct — a named collection of coverpoints that fire either automatically on a clock event or explicitly via .sample(). Each coverpoint is a variable or expression whose observed values are to be tracked. Without explicit bins, a coverpoint creates automatic bins: one per enum value for enum types; otherwise up to auto_bin_max (default 64) ranges spanning the type.

Plate IX.1 coverage.sv
class TxnCov; bit [7:0] len; bit [1:0] kind; bit err; covergroup cg; cp_len : coverpoint len { bins short = {[1:63]}; bins medium = {[64:511]}; bins large = {[512:1518]}; illegal_bins zero = {0}; } cp_kind : coverpoint kind; // auto-bins cp_err : coverpoint err; // Cross every length×kind combination, excluding error+large cx_lk : cross cp_len, cp_kind { ignore_bins big_err = binsof(cp_len.large) intersect {3}; } endgroup endclass
Figure IX.1 — named bins, illegal bins, cross coverage, and an ignore_bins pruning.
Coverage is the map

Random tests answer "what happened?" Coverage answers "was it interesting?"

Advanced Interfaces.

Classes live in packages; physical interfaces live in module hierarchy. The virtual interface is the connector.

The problem

A class cannot instantiate a module or an interface — those are elaboration-time constructs, not runtime objects. But a class-based driver absolutely must be able to drive signals into the DUT. The virtual interface resolves the impasse: it is a class-compatible variable that points at a physical interface instance. The test's top module constructs the real interface, then passes a handle to it into the class-based environment.

Plate X.1 virtual_interface.sv
class Driver; virtual bus_if.master vif; // virtual interface handle function new(virtual bus_if.master v); vif = v; endfunction task drive(Transaction tr); vif.addr <= tr.addr; // touch physical signals vif.data <= tr.data; vif.write <= 1; @(posedge vif.clk); endtask endclass // In the top module bus_if bus(clk); // real interface Driver drv = new(bus); // pass reference to class
Figure X.1 — the class holds a virtual interface; the top module provides the real one.
Virtual interface

A handle-like reference to a physical interface instance. The bridge between a class-based testbench and hierarchical RTL signals.

A Complete Testbench.

The manual's penultimate chapter stitches everything together into a production-shaped testbench — generator, driver, monitor, scoreboard, coverage — for an ATM router DUT.

The architecture

At the top: a test class that configures and launches the environment. The environment contains a generator (produces Transaction objects), a driver (converts them to signal activity via a virtual interface), a monitor (watches the DUT outputs and reconstructs transactions), a scoreboard (compares expected against actual), and a coverage collector (samples every observed transaction).

Test ENVIRONMENT Generator Driver Monitor Scoreboard Coverage [DUT signals]
Figure XI.1 — a canonical layered environment. Solid arrows are transactions; dashed are signals.
Assembly

Every component you have met now fits into a single, extendable structure.

Interfacing with C / C++.

Some things are already written in C. A reference model, a math library, a software driver. The DPI lets you call them from SystemVerilog — and lets C call back into SystemVerilog — without the ceremony of PLI or VPI.

Import & export

An imported DPI routine is a C/C++ function that SystemVerilog may call as if it were a native task or function. An exported routine is the reverse — a SystemVerilog task or function that the C side may call back. Together they form a bidirectional bridge.

Plate XII.1 dpi_example.sv
// Import a C function — call it like a native function import "DPI-C" function int sqrt_int(input int x); // Pure: no side effects, no state, no calls back into SV import "DPI-C" pure function real cosine(input real x); // Context: may call back into SystemVerilog import "DPI-C" context task cpp_model_step(chandle h); // Export a SV function for C to call back into export "DPI-C" function sv_log; function void sv_log(string msg); $display("[C]: %s", msg); endfunction
Figure XII.1 — import (pure and context variants) and export, with a chandle for C++ object pointers.
DPI

Direct Programming Interface — a foreign-function bridge that calls straight into native code. No PLI, no VPI.