An Overview of SystemVerilog
for Design and V erification
Vighnesh Iyer, EECS 251B
Intention of this Lecture
● We use Chisel for all RTL written at Berkeley
○ Why bother with SystemVerilog?
● SystemVerilog is the de-facto industry standard
○ SV/UVM is used for (nearly) all industry verification
○ You will be asked about it in interviews
● Understand basic dynamic verification concepts
● Understand existing SystemVerilog code
● Inspire extensions to HDLs
SystemVerilog (SV) is an IEEE Standard 180 0
https://standards.ieee.org/project/180 0 .html
Universal Verification Methodology (UVM)
is a standard maintained by Accellera
https://www.accellera.org/downloads/standards/uvm
What is SystemVerilog
● IEEE 180 0 standard
● A massive extension of Verilog with new constructs for design and verification
○ New data types (for RTL and testbenches)
○ OOP support
○ Constrained random API
○ Specification language
○ Coverage specification API
● Fixing warts in Verilog
○ Synthesis - simulation mismatch
○ Verilog was initially developed as a simulation language; synthesis emerged later
SystemVerilog for Design
Ending the Wire vs. Reg Confusion
Verilog -2005 SystemVerilog
● wire for LHS of assign statements ● logic for LHS of assign statements
● reg for LHS of code inside always @ ● logic for LHS of code inside always @
blocks blocks
wire a; logic a, b, c;
reg b, c; assign a = ____;
assign a = ____; always @(* ) b = ____;
always @(* ) b = ____; always @(posedge clk) c <= ____;
always @(posedge clk) c <= ____;
Both: the containing statement determines if the net is the
direct output of a register or combinational logic
Signal Your Intent With Specific Always Blocks
Verilog -2005 SystemVerilog
always @(* ) begin always_comb begin
if (x) b = a if (x) b = a
else b = ! a; else b = ! a;
end end
always @(posedge clk) begin always_ff @(posedge clk) begin
if (x) c <= ! a; if (x) c <= ! a;
else c <= a; else c <= a;
end end
Coding style is used to verify that c infers New always_comb and always_ff
as a register and b as comb logic statements for safety
Autoconnect (Implicit Port Connections)
● How many times have you done this?
module mod ( input a, b, output c); endmodule
reg a, b; wire c;
mod x (.a(a), .b(b), .c(c));
● If the net names and their corresponding port names match, there’s a shortcut
mod x ( . a , . b, . c) ;
● In SystemVerilog, there’s a concise shortcut
mod x ( . * ) ;
● Implicit connections only work if port names and widths match
Use Enums Over localparams
Verilog -2005 SystemVerilog
localparam STATE_IDLE = 2’ b00; typedef enum logic [ 1: 0] {
localparam STATE_A = 2’ b01; STATE_IDLE, STATE_A, STATE_B
localparam STATE_B = 2’ b10; } state_t;
reg [ 1: 0] state; state_t state;
always @(posedge clk) begin always_ff @(posedge clk) begin
case (state) case (state)
STATE_IDLE: state <= STATE_A; STATE_IDLE: state <= STATE_A
STATE_A: state <= STATE_B; STATE_A: state <= STATE_B;
STATE_B: state <= STATE_IDLE; STATE_B: state <= STATE_IDLE;
endcase endcase
end end
Enums automatically check whether all values can fit. Can
be used as a net type. Add semantic meaning to constants.
More on Enums
● Common to use enums for attaching semantic strings to values
typedef enum logic {
READ, WRITE
} mem_op_t;
module memory (
input [ 4: 0] addr,
input mem_op_t op,
input [ 31: 0] din,
output logic [ 31: 0] dout
);
● Note that input/output net types are by default ‘wire’, you can override them as
logic
Even More on Enums
● You can force enum values to be associated with a specific value
○ To help match up literals for a port that doesn’t use enums
typedef enum logic [ 1: 0] { STATE_IDLE=3, STATE_A=2, STATE_B=1 } state_t
● You can generate N enum values without typing them out
typedef enum logic [ 1: 0] { STATE[ 3] } s t a t e_ t
/ / S T AT E 0 = 0, S T AT E 1 = 1, S T AT E 2 = 2
● You can generate N enum values in a particular range
typedef enum logic [ 1: 0] { STATE[ 3:5 ] } s t a t e_ t
/ / S T AT E 3 = 0, S T AT E 4 = 1, S T AT E 5 = 2
Even More on Enums
● Enums are a first-class datatype in SystemVerilog
○ Enum instances have native functions defined on them
● next(): next value from current value
● prev(): previous value from current value
● num(): number of elements in enum
● name(): returns a string with the enum’s name (useful for printing using $display )
● They are weakly typechecked
○ You can’t assign a binary literal to a enum type net
● They show up in waveforms
○ No more confusion trying to correlate literals to a semantic name
Multidimensional Packed Arrays
● Packed dimensions are to the left of the variable name
○ Packed dimensions are contiguous (e.g. logic [7:0 ] a)
● Unpacked dimensions are to the right of the variable name
○ Unpacked dimensions are non-contiguous (e.g. logic a [8])
logic [ 31: 0] memory [ 32];
// memory[0] is 32 bits wide
// cannot represent more than 1 dimension in memory[0]
// can’t easily byte address the memory
logic [ 3: 0][ 7: 0] memory [ 32];
// memory[0] is 32 bits wide
// memory[0][0] is 8 bits wide
// memory[0][1] is 8 bits wide
Structs
● Similar to Bundle in Chisel
○ Allows designer to group nets together, helps encapsulation of signals, easy declaration
○ Can be used within a module or in a module’s ports
○ Structs themselves can’t be parameterized
● but can be created inside a parameterized module/interface
typedef struct packed {
logic [ 31: 0] din,
ram_cmd a;
logic [ 7: 0] addr,
always_ff @(posedge clk) begin
logic [ 3: 0] wen,
din <= ____
mem_op op
addr <= ____
} ram_cmd;
wen <= ____
op <= ____
module ram ( end
ram_cmd cmd,
logic [ 31: 0] dout
);
Interfaces
● Interfaces allow designers to group together ports
○ Can be parameterized
○ Can contain structs, initial blocks with assertions, and other verification collateral
○ Simplify connections between parent and child modules
module ram (
interface ram_if #( int addr_bits, data_bits) ram_if intf
( input clk); );
logic [addr_bits - 1: 0] addr; always_ff @(posedge intf.clk)
logic [data_bits - 1: 0] din; intf.dout <= ram[intf.addr];
logic [data_bits - 1: 0] dout; endmodule
mem_op op;
endinterface module top();
ram_if #(.addr_bits( 8), .data_bits( 32)) intf();
ram r (.intf(intf));
assign intf.din = ____
endmodule
Modports
● But I didn’t specify the direction (input/output) of the interface ports!
○ This can cause multi-driver issues with improper connections
● Solution: use modports
interface ram_if #( int addr_bits, data_bits)
( input clk);
modport slave (
input addr, din, op, clk, module ram (
output dout ram_if.slave intf
); );
always_ff @(posedge intf.clk)
modport master ( intf.dout <= ram[intf.addr];
output addr, din, op, endmodule
input dout, clk
);
endinterface
Typedefs (Type Aliases)
● You probably saw ‘typedef’ everywhere
○ typedef is used to expose user-defined types
● J ust like with enums, they help attach semantic meaning to your design
● They are just type aliases
typedef signed logic [ 7: 0] sgn_byte;
typedef unsigned logic [ 3: 0] cache_idx;
Packages / Namespacing
● Verilog has a global namespace
○ Often naming conflicts in large projects
○ `include is hacky and requires `ifdef guards
● SystemVerilog allows you to encapsulate constructs in a package
○ modules, functions, structs, typedefs, classes
package my_pkg;
typedef enum logic [ 1: 0] { STATE[ 4] } state_t; import my_pkg::* ;
function show_vals();
state_t s = STATE0; module ex ( input clk);
for ( int i = 0; i < s.num; i = i + 1) begin state_t s;
$display (s.name()); always_ff @(posedge clk) begin
s = s.next(); s <= STATE0;
end end
endfunction endmodule
endpackage
SystemVerilog for Verification
Overview
● The SystemVerilog spec for verification is massive
○ We can’t cover everything in one lecture
● New data structures for writing testbenches
○ Parity with PHP
● OOP
● SystemVerilog Assertions
● Coverage API
● Constrained random
New Data Types
● bit, shortint, int, longint
○ 2-state types
● string
○ Now natively supported, some helper methods are defined on string (e.g. substr)
Dynamic Arrays
● Typical Verilog arrays are fixed length at compile time
bit [ 3: 0] arr [ 3]; // a 3 element array of 4 bit values
arr = ‘ { 12, 10, 3}; // a literal array assignment
● Dynamic arrays are sized at runtime
○ Useful for generating variable length stimulus
bit [ 3: 0] a r r [ ] ; / / a dy na mi c a r r a y of 4 bi t v a l ue s
initial begin
a r r = new[ 2] ; / / s i z e t he a r r a y f or 2 el ement s
a r r = ‘ { 12, 10} ; / / l i t e r a l a s s i gnme nt
a r r = new[ 10] ;
a r r [ 3] = 4;
end
Queues
● Similar to lists in Scala and Python
○ Useful for hardware modeling (FIFO, stack) - process transactions sequentially
bit [ 3: 0] data [ $]; // a queue of 4 - bit elements
bit [ 3: 0] data [ $] = ‘ { 1, 2, 3, 4}; // initialized queue
data[ 0] // first element
data[ $] // last element
data.insert( 1) // append element
data[ 1: $] // queue slice excluding first element
x = data.pop_front() // pops first element of queue and returns it
data = {} // clear the queue
Associative Arrays
● Similar to Python dicts or Scala Maps
○ Can be used to model a CAM or lookup testbench component settings
int fruits [ string ];
fruits = ‘ { “ apple ” : 4, “ orange ” : 10};
fruits[ “ apple ” ]
fruits.exists( “ lemon ” )
fruits.delete( “ orange ” )
Clocking Blocks
● There is often confusion when you should drive DUT inputs and sample DUT
outputs relative to the clock edge
○ Solution: encode the correct behavior in the interface by using clocking blocks
interface ram_if #( int addr_bits, data_bits)
( input clk);
logic [addr_bits - 1: 0] addr;
logic [data_bits - 1: 0] din;
logic [data_bits - 1: 0] dout;
mem_op op;
clocking ckb @( posedge clk)
default input #1step output negedge ;
input dout;
output din, dout, op;
endclocking
● Input/output is from the perspective of the testbench
endinterface ● Can use any delay value or edge event as skew
● intf.ckb.din = 32’d10 0 ; @(intf.ckb); x = intf.ckb.dout;
OOP in SystemVerilog
● SystemVerilog has your typical object-oriented programming (OOP) constructs
○ Classes, constructors, type generics, inheritance, virtual methods/classes, polymorphism
class Message;
bit [ 31: 0] addr; initial begin
msg = new Message( 32’ d4,
bit [ 3: 0] wr_strobe;
4’ b1111);
bit [ 3: 0] burst_mode; $display (msg.burst_mode);
bit [ 31: 0] data [ 4]; end
function new ( bit [ 31: 0] addr, bit [ 3: 0] wr_strobe =
4’ d0);
this .addr = addr;
this .wr_mode = wr_mode;
this .burst_mode = 4’ b1010;
this .data = ‘ { 0, 0, 0, 0};
endfunction
endclass
More OOP
● You can extend a class as usual
○ class ALUMessage extends Message
○ call .super() to access superclass functions
○ Polymorphic dynamic dispatch works as usual
● You can declare classes and functions ‘virtual’
○ Forces subclasses to provide an implementation
○ Prevents instantiation of abstract parent class
● Class members can be declared ‘static’
○ The member is shared among all class instances
● OOP constructs are used to:
○ Model transactions
○ Model hardware components (hierarchically and compositionally)
Type Generic Classes
● Classes can have parameters, just like modules
○ They can be ints, strings, or types
○ Parameters concretize the class prototype; constructor binds each class member
○ Can’t define type bounds on T
class FIFO #( type T = int , int entries = 8);
T items [entries];
int ptr;
function void push( T entry);
function T pull();
endclass
SystemVerilog Assertions (SVA)
SystemVerilog Assertions (SVA)
● The most complex component of SystemVerilog
○ Entire books written on just this topic
● SVA: a temporal property specification language
○ Allows you to formally specify expected behavior of RTL
● You are already familiar with ‘assert’ (so-called ‘immediate assertions’)
module testbench();
dut d (.addr, .dout);
● But how do I express properties that involve
initial begin the uArch of the RTL?
addr = ‘ h40; ● Can I express these properties (e.g. req-ack)
assert (dout == ‘ hDEADBEEF);
end
in a concise way?
endmodule
Concurrent Assertions
● Concurrent assertions are constantly monitored by the RTL simulator
○ Often embedded in the DUT RTL or an interface
module cpu();
assert property @(posedge clk) mem_addr[ 1: 0] != 2’ d0 && load_word | - > unaligned_load
assert property @(posedge clk) opcode == 0 | - > take_exception
assert property @(posedge clk) mem_stall |=> $stable(pc)
endmodule
● Properties are evaluated on a clock edge
● | - >: same-cycle implication
● | =>: next-cycle implication
● These properties can also be formally verified
System Functions
● You can call a system function in an SVA expression to simplify checking
historical properties
○ $stable(x) : indicates if x was unchanged from the previous clock cycle
○ $r os e( x )
○ $f el l ( x )
○ $pa s t ( x ) : gives you the value of x from 1 cycle ago
■ r s 1_ mem == $pa s t ( r s 1_ex )
Sequences
● Properties are made up of sequences + an implication
○ Many interfaces come with sequence libraries you can use to build complex properties
module cpu();
sequence stall
mem_stall;
endsequence
sequence unchanged_pc
##1 $stable(pc);
endsequence
property stall_holds_pc
@(posedge clk) stall | - > unchanged_pc;
endproperty
assert property (stall_holds_pc);
endmodule
Sequence Combinators
● Sequences are the core of SVA: they describe temporal RTL behavior
● Sequences can be combined with temporal operators
a ## 1 b / / a t hen b on t he nex t c y c l e
a # # N b / / a t hen b on t he Nt h c y c l e
a # # [ 1: 4] b / / a t hen b on t he 1- 4t h s ubs equent c y c l e
a # # [ 2: $] b / / a t hen b a f t er 2 or mor e c y c l es
s 1 and s 2 / / s equenc e s 1 a nd s 2 s uc c ee d
s 1 intersect s 2 / / s equenc e s 1 a nd s 2 s uc c ee d a nd end a t t he s a me t i me
s 1 or s 2 / / s equenc e s 1 or s 2 s uc c ee ds
● Sequences are combined with an implication to form a property
○ There’s a lot more to SVA
Coverage APIs
Coverage
● You’re probably familiar with software coverage tools
○ Track if a line of source code is hit by the unit tests
● Coverage is used to measure the thoroughness of the test suite
○ Are all the interesting cases in the code exercised?
● RTL coverage comes in two forms
○ Structural coverage: line, toggle, condition
○ Functional coverage: did a particular uArch feature specified by the DV engineer get
exercised?
● e.g. cache eviction, misaligned memory access, interrupt, all opcodes executed
Property Coverage
● Any SVA property can be tracked for coverage
○ Instead of ‘assert property’ use ‘cover property’
property req_ack;
req ##[ 1: 10] ack
endproperty
cover property (req_ack)
● Property covers are used in RTL to check that some multi -cycle uArch
behavior is exercised
○ e.g. did this req-ack handshake ever occur?
○ e.g. did a branch mispredict and predictor update happen?
Coverpoints and Covergroups
● Coverpoints track coverage of a single net
○ e.g. FSM state, control signals, data buses
● Covergroups group together coverpoints
○ Each coverpoint refers to a net whose value is tracked at every covergroup event
○ Can be used in RTL and in testbench code
module cpu ();
logic [ 5: 0] rs1, rs2; 15
logic [ 2: 0] funct3; 10 10 10
covergroup c @( posedge clk); 3 3 3
coverpoint rs1;
coverpoint funct3;
0 1 2 3 4 5 6 7 8
funct3 value
endgroup
endmodule
Coverpoint Bins
● Sometimes we don’t want to track each value a net can take on individually
○ Use the bins API to group some values together
module alu( input [ 31: 0] a, input [ 31: 0] b, input [ 3: 0] op, output [ 31: 0] out);
covergroup c();
coverpoint a{
bins zero = { 0};
bins max = { 32’ hffff_ffff};
// automatically allocate 100 uniformly sized bins for the remaining numbers
bins in_the_middle[ 100 ] = {[ 1: 32’ hffff_ffff - 1]};
}
endgroup
endmodule
Transaction-Level Modeling
Transactions
● Our testbenches are usually written at cycle-granularity
○ Leads to mixing of driving/monitoring protocols, timing details, golden modeling, and stimulus
○ Each of these concerns should be separated
● Model a single interaction with the DUT as a ‘transaction’
○ It can take multiple cycles
● We can build a stimulus generator and golden model at transaction-level
class MemReqTx();
bit [ 31: 0] addr;
bit [ 31: 0] wr_data; class Mem();
mem_op op; bit [ 31: 0] ram [];
endclass function MemRespTx processTx(MemReqTx tx);
endclass
class MemRespTx();
bit [ 31: 0] rd_data;
endclass
VIPs and Testbench Architecture
● Verification IPs consist of
tasks that encode Testbench
○ How to drive transactions into Mem VIP DUT (Mem)
Stimulus
an interface at cycle Driver Mem Interface
Transaction
Transaction
granularity MemReqTx
Monitor
○ How to translate cycle
granularity interface activity
into transactions Golden Model MemRespTx
● A testbench DUT Resps
○ Generates stimulus
○ Generates golden DUT MemRespTx Assert
Golden Resps Equals
behavior
○ Simulates actual DUT behavior
○ Checks correctness
Random Transaction Generation
● How do we generate transaction-level stimulus?
● SystemVerilog class members can be prefixed with the ‘rand’ keyword
○ These fields are marked as randomizable
class MemReqTx(); initial begin
rand bit [ 31: 0] addr; MemReqTx tx = new();
rand bit [ 31: 0] wr_data; tx.randomize();
rand mem_op op; end
endclass
Constrained Random
● You can constrain the random fields of a class inside or outside the class
○ You can add ad-hoc constraints when calling .randomize
class cls;
rand bit [ 7: 0] min, typ, max;
constraint range {
0 < min; typ < max; typ > min; max < 128 ;
}
extern constraint extra;
endclass
constraint cls :: extra { min > 5; };
initial begin
cls = new();
cls.randomize() with { min == 10; };
end
Randomization of Variable Length Data Structures
class Packet;
rand bit [ 3: 0] data [];
constraint size { data.size() > 5; data.size < 10; }
constraint values {
foreach (data[i]) {
data[i] == i + 1;
data[i] inside {[ 0: 8]};
}
}
endclass
● Many things I haven’t discussed
○ Biasing and distributions, soft constraints, disables, solve before, implications, dynamic
constraint on/off
Mailboxes for Safe Inter-Thread Communication
● Mailboxes are like golang channels
○ Bounded queues that allow one thread to send data to another
module example;
mailbox #( int ) m = new( 100 );
initial begin
for ( int i = 0; i < 200 ; i ++)
#1 m.put(i);
end
initial begin
for ( int i = 0; i < 200 ; i ++) begin
int i; # 2 m.get(i);
$display (i, m.num());
end
end
endmodule
Testbench Example
Register Bank
● Let’s test a simple register bank
○ Works like a memory
○ Multi-cycle (potentially variable) read/write latency
○ Uses a ready signal to indicate when a new operation (read/write) can begin
interface reg_if ( input clk); module regbank (reg_if.slave if );
logic rst; // implementation
logic [ 7: 0] addr; endmodule
logic [ 15: 0] wdata;
logic [ 15: 0] rdata; // Regbank transaction
mem_op op; class regbank_tx;
logic en; rand bit [ 7: 0] addr;
logic ready; rand bit [ 15: 0] wdata;
// primary/secondary modports bit [ 15: 0] rdata;
// drv_cb/mon_cb clocking blocks rand bit wr;
endinterface endclass
VIP Implementation
class driver; class monitor;
virtual reg_if vif; virtual reg_if vif;
mailbox drv_mbx; mailbox mon_mbx;
taskrun(); taskrun();
@(vif.drv_cb); @(vif.mon_cb);
forever begin if (vif.en) begin
regbank_tx tx; regbank_tx tx = new();
drv_mbx.get(tx); tx.addr = vif.mon_cb.addr;
vif.drv_cb.en <= 1; // assign op and wdata
vif.drv_cb.addr <= tx.addr; if (vif.mon_cb.op == READ) begin
// assign op and wdata @(vif.mon_cb);
@(vif.drv_cb); tx.rdata = vif.mon_cb.rdata;
while ( ! vif.drv_cb.ready) end
@(vif.drv_cb) mon_mbx.put(tx);
end end
endtask endtask
endclass endclass
Top-Level
● A rough sketch of the testbench top
module tb();
regbank dut (. * );
initial begin
// initialize driver/monitor classes
regbank_tx stim [ 100 ];
stim.randomize();
fork
drv.run(); mon.run();
join_none
drv.drv_mbx.put(stim);
while (mon.mon_mbx.size < 100 )
@(dut.drv_cb);
// Pull tx from mon_mbx and check correctness
end
endmodule
Conclusion
● SystemVerilog makes design easier and clearer than plain Verilog
● SystemVerilog has many useful verification features not found in open-source
environments
○ SVA, coverpoints, constrained random
● I’ve only scratched the surface
○ UVM
○ Hardware modeling
○ IPC
● Play around: https://www.edaplayground.com/x/CK
○ https://en.wikipedia.org/wiki/SystemVerilog
References
https://en.wikipedia.org/wiki/SystemV erilog
https://verificationguide.com/systemverilog/systemverilog-tutorial/
https://www.chipverify.com/systemverilog/systemverilog-tutorial
https://www.doulos.com/knowhow/systemverilog/systemverilog-tutorials/systemverilog-assertions-tutorial/
https://www.systemverilog.io/sva-basics
Advanced notes on SystemV erilog covergroups: https://staging.doulos.com/media/160 0/dvclub_austin.pdf