Intermediate SystemVerilog
Classes and OOP, constrained randomization, functional coverage, interfaces, and clocking blocks.
Classes, handles, and inheritance
SystemVerilog brings OOP to the verification layer. A class bundles data and the operations on that data. Unlike C++, SystemVerilog classes have no pointers — you work with handles, which are opaque references managed by garbage collection.
class axi_txn; rand bit[31:0] addr; rand bit[31:0] data; axi_resp_e resp; function void display(); $display("addr=%0h data=%0h resp=%s", addr, data, resp.name()); endfunction endclass // Usage axi_txn t = new(); // create the object t.addr = 32'h1000; t.display();
Inheritance
extends creates a subclass that inherits all members of the base. super.method() calls the base-class version. virtual marks a method for polymorphic dispatch — the runtime type decides which implementation runs.
virtual on a method that should be overridden is a classic bug.rand, randc, and constraint blocks
Mark class members rand (can repeat values) or randc (cyclic — visits every value in the range before repeating). Call obj.randomize() to generate a new random value set, respecting all active constraints.
Constraint blocks
Constraints restrict the random solution space. They're declarative: the order you write them doesn't matter.
class axi_txn; rand bit[31:0] addr; rand bit[7:0] length; constraint c_addr_align { addr[1:0] == 0; } // word-aligned constraint c_length_bounded { length inside {[1:16]}; } constraint c_addr_dist { addr dist { 'h0000:= 10, ['h1000:'hFFFF]:= 90 }; } endclass
Soft constraints and override
A soft constraint can be overridden in a derived class or an inline randomize() with { … } block. Use soft constraints for "default" policies that tests should be free to change. Use hard constraints only for protocol rules the randomizer must not violate.
Covergroups, bins, and crosses
Code coverage tells you what lines ran. Functional coverage tells you what scenarios you exercised. A covergroup samples values at a trigger and binds them to coverpoints. Each coverpoint has bins — value ranges you care about.
class axi_coverage; axi_txn t; covergroup cg; option.per_instance = 1; addr_cp : coverpoint t.addr { bins low = {[0:'hFFF]}; bins mid = {['h1000:'hFFFF]}; bins high = {['h10000:$]}; } resp_cp : coverpoint t.resp; cross_ar : cross addr_cp, resp_cp; endgroup function new(); cg = new(); endfunction function void sample(axi_txn tr); t = tr; cg.sample(); endfunction endclass
The bridge between testbench and DUT
An interface is a named bundle of signals. It's the connective tissue between your DUT and your testbench. Declared once, passed as a single port to both sides.
interface axi_lite_if(input logic clk, rst_n); logic [31:0] awaddr; logic awvalid, awready; logic [31:0] wdata; logic wvalid, wready; logic [1:0] bresp; logic bvalid, bready; clocking cb_m @(posedge clk); output awaddr, awvalid, wdata, wvalid, bready; input awready, wready, bvalid, bresp; endclocking modport master (clocking cb_m, input clk, rst_n); endinterface
Clocking blocks
A clocking block groups signals under a clock with a direction and a sampling/drive skew. Using it in your driver and monitor eliminates an entire class of race-condition bugs between testbench and DUT.
Modports
A modport gives each side of the interface a named, directional view. The master can't accidentally drive awready because the modport says it's an input.