A complete quick-reference for SystemVerilog. Covers every major language construct beyond Verilog — data types, always blocks, interfaces, classes, assertions, coverage, randomization, packages, and more. Designed for engineers who know Verilog and want to leverage the full power of SystemVerilog for both design and verification.
What SystemVerilog Adds to Verilog
SystemVerilog (IEEE 1800) is a superset of Verilog (IEEE 1364). Everything valid in Verilog is valid in SystemVerilog. This cheat sheet focuses on what SystemVerilog adds: improved data types, explicit always blocks, interfaces, packages, OOP for verification, constrained randomization, assertions, and functional coverage.
Data Types
Two-State vs Four-State
| Type | Width | Values | Signed? | Use |
|---|---|---|---|---|
logic | User-defined | 0, 1, x, z | Unsigned | Replaces both wire and reg |
reg | User-defined | 0, 1, x, z | Unsigned | Legacy — use logic instead |
integer | 32 | 0, 1, x, z | Signed | Legacy — use int instead |
bit | User-defined | 0, 1 | Unsigned | Two-state for testbench (faster simulation) |
byte | 8 | 0, 1 | Signed | Small signed integers |
shortint | 16 | 0, 1 | Signed | 16-bit signed |
int | 32 | 0, 1 | Signed | Loop variables, counters |
longint | 64 | 0, 1 | Signed | 64-bit signed |
real | 64 | float | — | Floating-point (not synthesizable) |
shortreal | 32 | float | — | 32-bit float (not synthesizable) |
string | Dynamic | chars | — | Text manipulation (not synthesizable) |
logic is the single most important SystemVerilog improvement. It replaces both wire and reg — you no longer need to think about which to use. Use logic everywhere except for signals with multiple drivers (which still need wire).
logic [7:0] data; // replaces both wire and reg
logic clk, rst_n;
bit [31:0] tb_counter; // two-state — faster in simulation
byte b = 8'hFF; // 8-bit signed
int i = -42; // 32-bit signed
longint l; // 64-bit signed
string s = "hello"; // dynamic string
Enumerations
// Basic enum (default type: int)
typedef enum {IDLE, RUN, DONE, ERROR} state_t;
state_t state, next_state;
// Explicit encoding
typedef enum logic [2:0] {
S_IDLE = 3'b001,
S_RUN = 3'b010,
S_DONE = 3'b100
} state_e;
// Enum methods
state.name(); // returns string: "IDLE"
state.next(); // next value in enumeration
state.prev(); // previous value
state.num(); // number of values in enum
state.first(); // first value
state.last(); // last value
For synthesizable FSMs, always specify the base type explicitly (e.g., enum logic [2:0]). This controls the encoding width and avoids synthesis warnings.
Structs & Unions
// Packed struct — maps to a contiguous bit vector (synthesizable)
typedef struct packed {
logic [3:0] opcode;
logic [11:0] address;
logic [7:0] data;
} instruction_t; // total: 24 bits
instruction_t instr;
assign instr.opcode = 4'hA;
assign instr.address = 12'h100;
logic [23:0] raw = instr; // can cast to plain vector
// Unpacked struct — fields may have gaps (for testbench modeling)
typedef struct {
int id;
string name;
real score;
} student_t;
// Packed union — all members share same bits
typedef union packed {
logic [31:0] word;
logic [3:0][7:0] bytes; // 4 bytes
struct packed {
logic [15:0] upper;
logic [15:0] lower;
} halves;
} word_u;
word_u w;
w.word = 32'hDEAD_BEEF;
// w.bytes[3] == 8'hDE, w.halves.lower == 16'hBEEF
Use packed structs/unions for synthesizable RTL. Unpacked structs are for verification only — they cannot map to a bit vector.
typedef
typedef logic [7:0] byte_t;
typedef logic [31:0] word_t;
typedef logic [15:0] addr_t;
// Parameterized types (via macros or packages)
typedef struct packed {
addr_t addr;
word_t data;
logic valid;
} bus_req_t;
Arrays — Fixed, Dynamic, Associative, Queues
// Packed array — contiguous bits (synthesizable)
logic [3:0][7:0] packed_arr; // 32 bits: 4 bytes packed together
// Unpacked fixed-size array (synthesizable)
logic [7:0] mem [0:255]; // 256 entries of 8 bits
logic [7:0] rom [256]; // same thing — shorthand
// Multidimensional
logic [7:0] matrix [4][4];
// Dynamic array (not synthesizable — verification)
int dyn[];
initial begin
dyn = new[10]; // allocate 10 elements
dyn = new[20](dyn); // resize, preserve contents
dyn.delete(); // free
end
// Associative array (not synthesizable — verification)
int lookup [string]; // string-indexed
int sparse [int]; // integer-indexed
initial begin
lookup["alpha"] = 1;
lookup["beta"] = 2;
if (lookup.exists("alpha")) $display("found");
lookup.delete("beta");
end
// Queue (not synthesizable — verification)
int q [$]; // unbounded queue
int bq [$:255]; // bounded queue (max 256 entries)
initial begin
int val, sz;
q.push_back(10); // append
q.push_front(5); // prepend
val = q.pop_front(); // remove & return first
q.insert(1, 7); // insert at index 1
q.delete(0); // delete index 0
sz = q.size(); // current size
end
Array Manipulation Methods
int arr[] = '{3, 1, 4, 1, 5, 9};
// Locator methods — return queues
int q[$];
q = arr.find with (item > 3); // {4, 5, 9}
q = arr.find_index with (item == 1); // {1, 3}
q = arr.find_first with (item > 4); // {5}
q = arr.find_last with (item > 4); // {9}
q = arr.min(); // {1}
q = arr.max(); // {9}
q = arr.unique(); // {3, 1, 4, 5, 9}
// Reduction methods
int s = arr.sum(); // 23
int p = arr.product(); // 540
// Ordering
arr.sort(); // ascending
arr.rsort(); // descending
arr.reverse(); // reverse order
arr.shuffle(); // randomize order
Strings
string s = "Hello";
int len = s.len(); // 5
s = {s, " World"}; // concatenation: "Hello World"
string up = s.toupper(); // "HELLO WORLD"
string lo = s.tolower(); // "hello world"
byte c = s.getc(0); // 'H' (ASCII 72)
s.putc(0, "h"); // "hello World"
string sub = s.substr(0, 4); // "hello"
int idx = s.atoi(); // string to integer
s.itoa(42); // integer to string
Casting & Type Conversions
// Static cast — compile-time check
int i = int'(3.14); // real → int: 3
logic [7:0] b = 8'(some_wider_signal); // truncate
logic signed [7:0] sb = signed'(b); // unsigned → signed
// Cast between enum and int
state_e s = state_e'(3); // int → enum
// $cast — dynamic cast with runtime check (returns 1 on success)
if (!$cast(child_handle, base_handle))
$error("Cast failed");
// Type query
$typename(variable) // returns type name as string (for debug)
$bits(type_or_expr) // number of bits
Packages
// Definition
package bus_pkg;
typedef enum logic [1:0] {READ, WRITE, IDLE} cmd_e;
typedef struct packed {
cmd_e cmd;
logic [15:0] addr;
logic [31:0] data;
} bus_req_t;
parameter int BUS_WIDTH = 32;
function automatic logic [3:0] encode(input logic [15:0] val);
return val[3:0] ^ val[7:4];
endfunction
endpackage
// Usage — wildcard import (most common)
import bus_pkg::*;
// Usage — explicit import
import bus_pkg::bus_req_t;
import bus_pkg::BUS_WIDTH;
// Usage — fully qualified (no import needed)
bus_pkg::cmd_e my_cmd;
Use packages for shared types, parameters, and utility functions. Wildcard import (::*) is convenient but explicit imports are safer in large projects to avoid name collisions.
Module Declaration — SystemVerilog Style
module my_module
import bus_pkg::*; // package import before ports
#(
parameter int WIDTH = 8,
parameter int DEPTH = 16,
parameter type DATA_T = logic [7:0] // parameterized type
)(
input logic clk,
input logic rst_n,
input DATA_T data_in,
output DATA_T data_out,
output logic valid
);
localparam int ADDR_W = $clog2(DEPTH);
// ...
endmodule
Implicit Port Connections
// .* — connect all ports by matching name (SystemVerilog)
my_module u_mod (
.clk, // .name is shorthand for .name(name)
.rst_n,
.data_in (fifo_data), // explicit when names differ
.data_out,
.valid
);
// Or even shorter with .*
my_module u_mod (
.*, // connect all matching names automatically
.data_in (fifo_data) // override specific ports
);
.* is powerful but dangerous — it silently connects signals you may not intend. Use with caution in large designs. Explicit connections are safer for RTL; .* is fine in testbenches.
Always Blocks — Explicit Intent
| Block | Purpose | Assignment | Sensitivity |
|---|---|---|---|
always_comb | Combinational logic | = (blocking) | Automatic (all reads) |
always_ff | Sequential logic (flip-flops) | <= (non-blocking) | Explicit edge list |
always_latch | Intentional latches | = (blocking) | Automatic |
// Combinational — replaces always @(*)
always_comb begin
y = a & b; // tool warns if latch is inferred
case (sel)
2'b00: out = in_a;
2'b01: out = in_b;
default: out = '0;
endcase
end
// Sequential — synchronous reset
always_ff @(posedge clk) begin
if (!rst_n) q <= '0;
else q <= d;
end
// Sequential — asynchronous reset
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= '0;
else q <= d;
end
// Intentional latch (rare — makes intent clear)
always_latch begin
if (enable) q = d;
// no else — intentionally latching
end
Always use always_comb, always_ff, and always_latch instead of the old always. Tools check the intent of each block: they should warn when an always_comb infers a latch or when an always_ff does not describe sequential logic. This catches bugs at compile time.
unique, priority, and Enhanced case/if
// unique — asserts all cases are mutually exclusive (no overlap)
// and that one case always matches (full)
always_comb begin
unique case (sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
2'b11: y = d;
endcase
end
// priority — asserts at least one case matches (full)
// but allows overlap (first match wins)
always_comb begin
priority case (1'b1)
req[3]: grant = 4'b1000;
req[2]: grant = 4'b0100;
req[1]: grant = 4'b0010;
req[0]: grant = 4'b0001;
default: grant = 4'b0000;
endcase
end
// unique if / priority if
always_comb begin
unique if (sel == 2'b00) y = a;
else if (sel == 2'b01) y = b;
else if (sel == 2'b10) y = c;
else y = d;
end
always_comb begin
priority if (req[3]) grant = 4'b1000;
else if (req[2]) grant = 4'b0100;
else if (req[1]) grant = 4'b0010;
else if (req[0]) grant = 4'b0001;
else grant = 4'b0000;
end
| Keyword | Full? | Parallel? | Simulation Check |
|---|---|---|---|
unique | Yes | Yes | Warns if no match or multiple matches |
unique0 | No | Yes | Warns only on multiple matches |
priority | Yes | No | Warns if no match |
| (none) | No | No | No runtime checks |
Use unique case for one-hot muxes and decoders. Use priority case or priority if for priority encoders. These replace the unreliable Verilog full_case/parallel_case pragmas with proper language semantics and runtime checks.
Interfaces
// Interface definition
interface axi_lite_if #(
parameter int ADDR_W = 32,
parameter int DATA_W = 32
)(
input logic clk,
input logic rst_n
);
logic [ADDR_W-1:0] awaddr;
logic awvalid;
logic awready;
logic [DATA_W-1:0] wdata;
logic wvalid;
logic wready;
logic [1:0] bresp;
logic bvalid;
logic bready;
// Modport — defines port directions from each perspective
modport master (
input clk, rst_n,
output awaddr, awvalid, wdata, wvalid, bready,
input awready, wready, bresp, bvalid
);
modport slave (
input clk, rst_n,
input awaddr, awvalid, wdata, wvalid, bready,
output awready, wready, bresp, bvalid
);
modport monitor (
input clk, rst_n,
input awaddr, awvalid, awready,
wdata, wvalid, wready,
bresp, bvalid, bready
);
endinterface
// Module using interface
module axi_slave (
axi_lite_if.slave bus // interface with modport
);
always_ff @(posedge bus.clk or negedge bus.rst_n) begin
if (!bus.rst_n) begin
bus.awready <= 1'b0;
end else begin
bus.awready <= bus.awvalid & ~bus.awready;
end
end
endmodule
// Top-level instantiation
module top;
logic clk, rst_n;
axi_lite_if #(.ADDR_W(16)) u_bus (.clk, .rst_n);
axi_master u_master (.bus(u_bus.master));
axi_slave u_slave (.bus(u_bus.slave));
endmodule
Interfaces bundle related signals into a single port, reducing port list verbosity and connection errors. Use modport to enforce correct signal directions for each connected module.
Clocking Blocks
// Clocking block — defines timing for testbench signal access
interface bus_if (input logic clk);
logic [7:0] data;
logic valid;
logic ready;
clocking driver_cb @(posedge clk);
default input #1step output #0; // sample before edge, drive at edge
output data;
output valid;
input ready;
endclocking
clocking monitor_cb @(posedge clk);
default input #1step;
input data;
input valid;
input ready;
endclocking
modport driver (clocking driver_cb);
modport monitor (clocking monitor_cb);
endinterface
// Usage in testbench
task automatic drive(virtual bus_if.driver drv);
drv.driver_cb.data <= 8'hAB;
drv.driver_cb.valid <= 1'b1;
@(drv.driver_cb); // wait one clock cycle
wait (drv.driver_cb.ready == 1);
drv.driver_cb.valid <= 1'b0;
endtask
Clocking blocks eliminate timing races in testbenches by defining exact sample/drive times relative to the clock edge. Always use them in UVM drivers and monitors.
Tasks & Functions — Enhanced
// Functions in a module are static by default - add "automatic" for per-call storage
function automatic int max(int a, int b);
return (a > b) ? a : b; // 'return' statement (new)
endfunction
// void function — no return value
function void print_hex(logic [31:0] val);
$display("0x%08h", val);
endfunction
// Default argument values
function automatic int clamp(int val, int lo = 0, int hi = 255);
if (val < lo) return lo;
if (val > hi) return hi;
return val;
endfunction
int x = clamp(300); // uses defaults: lo=0, hi=255 → returns 255
int y = clamp(50, .hi(100)); // named argument: lo=0, hi=100
// ref arguments — pass by reference (like C++ &)
function automatic void swap(ref int a, ref int b);
int temp = a;
a = b;
b = temp;
endfunction
// Tasks - no return value; results go through output, inout or ref arguments
task automatic drive_burst(
ref logic [7:0] bus,
input int count,
input logic [7:0] data[]
);
for (int i = 0; i < count; i++) begin
@(posedge clk);
bus = data[i]; // a ref argument can't be written with <=
end
endtask
Function vs Task (SystemVerilog Updates)
| Feature | Function | Task |
|---|---|---|
Timing controls (#, @) | No | Yes |
| Return value | Single value or void | None - results go through output, inout or ref arguments |
| Can call tasks | No | Yes |
ref arguments | Yes | Yes |
| Default arguments | Yes | Yes |
| Synthesizable | Yes (combinational) | Only if no timing |
Processes & Fork/Join
// fork...join — all threads must complete before continuing
fork
begin : thread_a
#10 $display("A done");
end
begin : thread_b
#20 $display("B done");
end
join // waits for both threads (resumes at t=20)
// fork...join_any — continue when ANY thread completes
fork
begin #10 $display("fast"); end
begin #50 $display("slow"); end
join_any // resumes at t=10 (slow thread still running)
// fork...join_none — launch threads and continue immediately
fork
begin #10 $display("background"); end
join_none // continues immediately, thread runs in background
// Controlling threads
disable fork; // kill all child threads of current process
// wait fork — wait for all forked threads to complete
fork
task_a();
task_b();
join_none
// ... do other work ...
wait fork; // block until task_a and task_b both finish
disable fork kills ALL child threads, including those from outer scopes. To limit scope, wrap the fork in a named begin...end block and disable that block instead.
Classes (OOP for Verification)
class Transaction;
// Properties
rand bit [7:0] addr; // randomizable
rand bit [31:0] data;
randc bit [3:0] id; // cyclic random — covers all values before repeating
bit is_write;
static int count = 0; // shared across all instances
// Constraints
constraint addr_range { addr inside {[8'h00:8'h7F]}; }
constraint data_align { data[1:0] == 2'b00; }
// Constructor
function new(bit is_write = 0);
this.is_write = is_write;
count++;
endfunction
// Methods
function void display();
$display("Txn id=%0d addr=0x%02h data=0x%08h wr=%0b",
id, addr, data, is_write);
endfunction
// Virtual method — can be overridden by subclass
virtual function bit is_valid();
return (addr != '0);
endfunction
// Copy
function Transaction copy();
Transaction t = new();
t.addr = this.addr;
t.data = this.data;
t.id = this.id;
t.is_write = this.is_write;
return t;
endfunction
endclass
// Inheritance
class WriteTransaction extends Transaction;
rand bit [3:0] byte_enable;
constraint be_valid { $countones(byte_enable) inside {1, 2, 4}; }
function new();
super.new(1); // call parent constructor
endfunction
// Override virtual method
function bit is_valid();
return (super.is_valid() && byte_enable != 0);
endfunction
endclass
// Usage
Transaction t = new();
t.randomize(); // randomize with constraints
t.display();
WriteTransaction wt = new();
wt.randomize() with { addr < 8'h40; }; // inline constraint
Polymorphism
// Virtual methods enable polymorphism
Transaction t;
WriteTransaction wt = new();
t = wt; // parent handle pointing to child object
t.is_valid(); // calls WriteTransaction::is_valid() (virtual dispatch)
// $cast for downcasting
WriteTransaction wt2;
if ($cast(wt2, t)) // runtime check: is t actually a WriteTransaction?
wt2.byte_enable = 4'hF;
Parameterized Classes
class Stack #(type T = int, int DEPTH = 32);
T data[$:DEPTH-1];
function void push(T val);
if (data.size() < DEPTH) data.push_back(val);
endfunction
function T pop();
return data.pop_front();
endfunction
function int size();
return data.size();
endfunction
endclass
Stack #(bit [7:0], 64) byte_stack = new();
Stack #(Transaction) txn_stack = new();
Constrained Randomization
class Packet;
rand bit [7:0] length;
rand bit [15:0] addr;
rand bit [7:0] payload[];
randc bit [2:0] channel; // cyclic — all values visited before repeat
// Simple constraints
constraint len_range { length inside {[1:64]}; }
constraint addr_align { addr[1:0] == 2'b00; } // 4-byte aligned
constraint payload_sz { payload.size() == length; }
// Weighted distribution
constraint len_dist {
length dist {
[1:8] := 50, // equal weight per value in range
[9:32] :/ 30, // weight divided among values in range
[33:64] := 20
};
}
// Implication
constraint wr_addr {
(channel == 0) -> addr inside {[16'h0000:16'h0FFF]};
}
// Conditional constraint
constraint cond_c {
if (length > 32) addr > 16'h1000;
else addr < 16'h1000;
}
// Foreach on arrays
constraint ordered {
foreach (payload[i])
if (i > 0) payload[i] >= payload[i-1];
}
// Solve order — control randomization order
constraint order { solve length before addr; }
endclass
// Randomization calls
Packet p = new();
assert(p.randomize()); // basic randomize
assert(p.randomize() with { // inline constraints (added on top)
length < 16;
addr == 16'h0100;
});
// Constraint control
p.constraint_mode(0); // disable ALL constraints
p.len_range.constraint_mode(0); // disable specific constraint
p.rand_mode(0); // disable ALL randomization
p.addr.rand_mode(0); // disable specific field randomization
// Pre/post randomize callbacks
class SmartPacket extends Packet;
function void pre_randomize();
// called before randomize()
endfunction
function void post_randomize();
// called after randomize() — compute CRC, etc.
endfunction
endclass
Assertions (SVA)
Immediate Assertions
// Simple immediate assertion — checked procedurally
always_comb begin
assert (state != ILLEGAL_STATE) else $error("Illegal state reached");
end
// With action blocks
always_ff @(posedge clk) begin
assert (fifo_count <= DEPTH)
$display("FIFO OK, count=%0d", fifo_count); // pass action
else
$fatal(1, "FIFO overflow! count=%0d", fifo_count); // fail action
end
// Deferred assertion — checked at end of time step (avoids glitches)
always_comb begin
assert #0 (onehot(grant)) else $error("Grant not one-hot");
end
Concurrent Assertions
// Property — a reusable temporal expression
property p_req_ack;
@(posedge clk) disable iff (!rst_n)
req |-> ##[1:3] ack; // if req, ack must come within 1-3 cycles
endproperty
assert property (p_req_ack)
else $error("Req not acknowledged within 3 cycles");
// Sequence — a pattern of events over time
sequence s_handshake;
req ##1 ack ##1 !req ##1 !ack; // four-cycle handshake
endsequence
property p_handshake;
@(posedge clk) disable iff (!rst_n)
req |-> s_handshake;
endproperty
assert property (p_handshake);
SVA Operators
| Operator | Meaning | Example |
|---|---|---|
##N | Delay N cycles | a ##2 b — a, then b 2 cycles later |
##[M:N] | Delay range | a ##[1:3] b — b within 1-3 cycles |
|-> | Overlapping implication | a |-> b — if a, b must hold same cycle |
|=> | Non-overlapping implication | a |=> b — if a, b must hold next cycle |
[*N] | Consecutive repetition | a[*3] — a for 3 consecutive cycles |
[*M:N] | Repetition range | a[*1:3] — a for 1 to 3 cycles |
[->N] | Goto repetition | a[->3] — 3rd occurrence of a |
[=N] | Non-consecutive repetition | a[=3] — a occurs 3 times (not necessarily consecutive) |
not | Property negation | not (a |-> b) |
$rose() | Signal rose this cycle | $rose(req) |
$fell() | Signal fell this cycle | $fell(ack) |
$stable() | Signal unchanged | $stable(data) |
$past() | Value in previous cycle | $past(data, 2) — value 2 cycles ago |
$onehot() | Exactly one bit set | $onehot(grant) |
$onehot0() | Zero or one bit set | $onehot0(req) |
$countones() | Count set bits | $countones(mask) |
Common Assertion Patterns
// Request must be acknowledged
assert property (@(posedge clk) disable iff (!rst_n)
req |-> ##[1:5] ack
);
// Signal must be stable while valid
assert property (@(posedge clk) disable iff (!rst_n)
valid |-> $stable(data)
);
// Two signals are mutually exclusive
assert property (@(posedge clk) disable iff (!rst_n)
not (signal_a && signal_b)
);
// Data must change after write
assert property (@(posedge clk) disable iff (!rst_n)
(wr_en && addr == TARGET) |=> (rdata == $past(wdata))
);
// No back-to-back requests
assert property (@(posedge clk) disable iff (!rst_n)
req |=> !req
);
// Cover — check that a scenario CAN occur (not that it must)
cover property (@(posedge clk) req ##1 ack);
// Assume — constrain inputs in formal verification
assume property (@(posedge clk) $onehot0(req));
Functional Coverage
// Covergroup — tracks value coverage during simulation
covergroup cg_bus @(posedge clk);
option.per_instance = 1;
// Coverpoint — which values were seen
cp_addr: coverpoint addr {
bins low = {[0:63]};
bins mid = {[64:191]};
bins high = {[192:255]};
bins zero = {0};
illegal_bins reserved = {[240:255]}; // error if hit
ignore_bins unused = {[200:239]}; // don't count
}
cp_cmd: coverpoint cmd {
bins read = {READ};
bins write = {WRITE};
}
cp_len: coverpoint length {
bins short_pkt = {[1:8]};
bins medium_pkt = {[9:32]};
bins long_pkt = {[33:64]};
bins powers[] = {1, 2, 4, 8, 16, 32, 64}; // one bin per value
}
// Cross coverage — combinations of coverpoints
addr_x_cmd: cross cp_addr, cp_cmd;
// Transition coverage
cp_state: coverpoint state {
bins idle_to_run = (IDLE => RUN);
bins run_to_done = (RUN => DONE);
bins any_to_idle = (RUN, DONE => IDLE);
}
endgroup
// Instantiate and sample
cg_bus bus_cov = new();
// Explicit sampling (alternative to automatic @(posedge clk))
covergroup cg_txn;
coverpoint txn.addr;
coverpoint txn.data;
endgroup
cg_txn txn_cov = new();
// ... later ...
txn_cov.sample(); // manually trigger sampling
// Query coverage
$display("Coverage: %.1f%%", bus_cov.get_coverage());
Processes & Synchronization
Events
event done;
event error;
// Trigger
-> done; // trigger event (non-blocking)
->> done; // non-blocking trigger (for use in always_ff)
// Wait
@(done); // wait for trigger
wait(done.triggered); // level-sensitive — doesn't miss same-timestep trigger
Mailboxes
// Typed mailbox — thread-safe FIFO
mailbox #(Transaction) mbx = new(16); // bounded: max 16 entries
mailbox #(Transaction) ubx = new(); // unbounded
// Producer
Transaction t = new();
t.randomize();
mbx.put(t); // blocking if full
mbx.try_put(t); // non-blocking, returns 0 if full
// Consumer
Transaction rx;
mbx.get(rx); // blocking if empty
mbx.try_get(rx); // non-blocking, returns 0 if empty
mbx.peek(rx); // get without removing
int n = mbx.num(); // number of entries
Semaphores
semaphore sem = new(1); // 1 key = mutex
// Acquire
sem.get(1); // blocking — wait for key
sem.try_get(1); // non-blocking, returns 0 if unavailable
// Release
sem.put(1); // return key
// Example: mutual exclusion
task automatic critical_section();
sem.get(1);
// ... exclusive access ...
sem.put(1);
endtask
Virtual Interfaces
// Virtual interface — a handle to an interface instance
// Allows class-based testbenches to drive/sample physical signals
class Driver;
virtual axi_lite_if vif; // virtual interface handle
function new(virtual axi_lite_if vif);
this.vif = vif;
endfunction
task run();
forever begin
@(posedge vif.clk);
vif.awaddr <= txn.addr;
vif.awvalid <= 1'b1;
@(posedge vif.clk iff vif.awready);
vif.awvalid <= 1'b0;
end
endtask
endclass
// In testbench top
module tb;
logic clk, rst_n;
axi_lite_if u_if (.clk, .rst_n);
axi_slave dut (.bus(u_if.slave));
Driver drv = new(u_if); // pass interface to class
initial drv.run();
endmodule
Classes cannot have ports, so virtual interface is the bridge between class-based verification (OOP) and the signal-level world of modules. Every UVM component uses virtual interfaces to access DUT signals.
Program Block
// program — separates testbench from DUT timing
// Executes in the "reactive" region (after all design updates)
program automatic test (
input logic clk,
bus_if.driver drv_if
);
initial begin
repeat (10) @(posedge clk);
drv_if.driver_cb.data <= 8'hAB;
drv_if.driver_cb.valid <= 1'b1;
@(drv_if.driver_cb);
drv_if.driver_cb.valid <= 1'b0;
$display("Test complete");
end
endprogram
The program block avoids race conditions by scheduling testbench code in the Reactive region, after all RTL always blocks have executed. In UVM-based flows, program blocks are less common since UVM handles phasing internally.
Generate Blocks — SystemVerilog Style
// generate/endgenerate keywords are optional in SystemVerilog
// Loop generate
for (genvar g = 0; g < N; g++) begin : stage
assign pipe[g+1] = pipe[g] & enable[g];
end
// Conditional generate
if (USE_PARITY) begin : gen_par
parity_checker u_par (.data(bus), .par(par_bit));
end else begin : gen_nopar
assign par_ok = 1'b1;
end
// Case generate
case (IMPL)
"FAST": fast_adder u_add (.*);
"SMALL": small_adder u_add (.*);
default: basic_adder u_add (.*);
endcase
Loops — Enhanced
// C-style for loop (declare loop variable inline)
always_comb begin
parity = 1'b0;
for (int i = 0; i < 8; i++) begin
parity ^= data[i];
end
end
// foreach — iterate over arrays
int arr[4] = '{10, 20, 30, 40};
foreach (arr[i]) begin
$display("arr[%0d] = %0d", i, arr[i]);
end
// foreach on multidimensional arrays
int matrix[3][4];
foreach (matrix[i, j]) begin
matrix[i][j] = i * 4 + j;
end
// do...while
int count = 0;
do begin
// body executes at least once
count++;
end while (count < 10);
// break and continue
for (int i = 0; i < 100; i++) begin
if (skip_condition) continue; // skip rest of this iteration
if (stop_condition) break; // exit loop entirely
// ...
end
System Tasks & Functions — SystemVerilog Additions
Severity System Tasks
$info("Informational message"); // severity: info
$warning("Something unusual: %0d", val); // severity: warning
$error("Protocol violation at %0t", $time); // severity: error
$fatal(1, "Unrecoverable: FIFO overflow"); // severity: fatal (stops simulation)
// $fatal first argument sets the diagnostic level (0 prints nothing, 1 time and location, 2 adds statistics); the simulation always finishes
Array Query Functions
int arr[4][8];
$dimensions(arr) // 2 (number of dimensions)
$left(arr, 1) // 0 (left bound of dimension 1)
$right(arr, 1) // 3 (right bound of dimension 1)
$low(arr, 1) // 0
$high(arr, 1) // 3
$size(arr, 1) // 4 (size of dimension 1)
$increment(arr, 1) // 1 or -1 depending on range direction
$bits(arr) // total bits
Type & Conversion
$typename(var) // returns type name as string
$itor(int_val) // integer to real
$rtoi(real_val) // real to integer
$signed(val) // cast to signed
$unsigned(val) // cast to unsigned
$bitstoreal(bits) // bit pattern to real
$realtobits(r) // real to bit pattern
Direct Programming Interface (DPI)
// Import C function into SystemVerilog
import "DPI-C" function int c_sqrt(input int val);
import "DPI-C" function void c_print(input string msg);
import "DPI-C" context function void c_callback();
// Export SystemVerilog function to C
export "DPI-C" function sv_get_data;
function int sv_get_data(input int addr);
return mem[addr];
endfunction
// Usage
initial begin
int result = c_sqrt(144); // calls C function
$display("sqrt(144) = %0d", result);
end
DPI enables calling C/C++ functions from SystemVerilog and vice versa. Use it for complex math, file parsing, or connecting to external models. The context keyword allows the C function to call back into SystemVerilog.
Compiler Directives — Additions
// Same as Verilog, plus:
`begin_keywords "1800-2012" // restrict to specific SV version keywords
`end_keywords
`__FILE__ // current filename (string)
`__LINE__ // current line number (integer)
`pragma protect begin // IP encryption
`pragma protect end
Common RTL Patterns — SystemVerilog Style
FSM with Enum
typedef enum logic [1:0] {
IDLE = 2'b00,
RUN = 2'b01,
DONE = 2'b10
} state_e;
state_e state, next_state;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) state <= IDLE;
else state <= next_state;
end
always_comb begin
next_state = state;
busy = 1'b0;
valid = 1'b0;
unique case (state)
IDLE: if (start) next_state = RUN;
RUN: begin
busy = 1'b1;
if (done) next_state = DONE;
end
DONE: begin
valid = 1'b1;
next_state = IDLE;
end
default: next_state = IDLE;
endcase
end
Parameterized FIFO
module sync_fifo #(
parameter type DATA_T = logic [7:0],
parameter int DEPTH = 16
)(
input logic clk,
input logic rst_n,
input logic wr_en,
input DATA_T wr_data,
input logic rd_en,
output DATA_T rd_data,
output logic full,
output logic empty
);
localparam int ADDR_W = $clog2(DEPTH);
DATA_T mem [DEPTH];
logic [ADDR_W:0] wr_ptr, rd_ptr; // extra bit for full/empty
assign full = (wr_ptr[ADDR_W] != rd_ptr[ADDR_W]) &&
(wr_ptr[ADDR_W-1:0] == rd_ptr[ADDR_W-1:0]);
assign empty = (wr_ptr == rd_ptr);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr <= '0;
rd_ptr <= '0;
end else begin
if (wr_en && !full) begin
mem[wr_ptr[ADDR_W-1:0]] <= wr_data;
wr_ptr <= wr_ptr + 1'b1;
end
if (rd_en && !empty) begin
rd_ptr <= rd_ptr + 1'b1;
end
end
end
assign rd_data = mem[rd_ptr[ADDR_W-1:0]];
endmodule
AXI-Style Handshake
// Valid/ready handshake — transfer occurs when both asserted
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
valid <= 1'b0;
data <= '0;
end else if (valid && ready) begin
// Transfer accepted — load next
valid <= next_valid;
data <= next_data;
end else if (!valid) begin
// Load new data when idle
valid <= next_valid;
data <= next_data;
end
end
// Assertion to verify protocol
assert property (@(posedge clk) disable iff (!rst_n)
valid && !ready |=> valid && $stable(data)
) else $error("Valid data changed without ready");
Packed Struct Bus
package bus_pkg;
typedef enum logic [1:0] {
CMD_READ = 2'b00,
CMD_WRITE = 2'b01,
CMD_NOP = 2'b10
} cmd_e;
typedef struct packed {
cmd_e cmd;
logic [15:0] addr;
logic [31:0] data;
logic [3:0] strobe;
} bus_req_t;
typedef struct packed {
logic [31:0] data;
logic [1:0] resp;
logic valid;
} bus_resp_t;
endpackage
module bus_master
import bus_pkg::*;
(
output bus_req_t req,
input bus_resp_t resp
);
always_comb begin
req.cmd = CMD_WRITE;
req.addr = 16'h1000;
req.data = 32'hDEAD_BEEF;
req.strobe = 4'hF;
end
endmodule
Edge Detector (SystemVerilog Style)
logic sig_q;
always_ff @(posedge clk) sig_q <= sig;
logic rising; assign rising = sig & ~sig_q;
logic falling; assign falling = ~sig & sig_q;
// Or use $rose/$fell in assertions and procedural code
always_ff @(posedge clk)
if ($rose(sig)) count <= count + 1;
UVM Essentials (Quick Reference)
SystemVerilog classes power the Universal Verification Methodology. Here are the key building blocks.
UVM Component Hierarchy
| Class | Purpose |
|---|---|
uvm_test | Top-level test — configures the environment |
uvm_env | Contains agents, scoreboards, coverage |
uvm_agent | Contains driver, monitor, sequencer |
uvm_driver | Drives stimulus onto DUT interface |
uvm_monitor | Observes DUT interface passively |
uvm_sequencer | Routes sequence items to driver |
uvm_sequence | Generates sequence items (stimulus) |
uvm_sequence_item | Transaction-level data object |
uvm_scoreboard | Checks DUT output against expected |
Minimal UVM Skeleton
// Sequence item
class my_txn extends uvm_sequence_item;
`uvm_object_utils(my_txn)
rand bit [7:0] addr;
rand bit [31:0] data;
function new(string name = "my_txn");
super.new(name);
endfunction
endclass
// Sequence
class my_seq extends uvm_sequence #(my_txn);
`uvm_object_utils(my_seq)
function new(string name = "my_seq");
super.new(name);
endfunction
task body();
my_txn txn;
repeat (10) begin
txn = my_txn::type_id::create("txn");
start_item(txn);
assert(txn.randomize());
finish_item(txn);
end
endtask
endclass
// Driver
class my_driver extends uvm_driver #(my_txn);
`uvm_component_utils(my_driver)
virtual bus_if vif;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
// drive signals via vif
@(posedge vif.clk);
vif.addr <= req.addr;
vif.data <= req.data;
@(posedge vif.clk);
seq_item_port.item_done();
end
endtask
endclass
Synthesis Golden Rules — SystemVerilog Edition
- Use
logicinstead ofwire/reg— except for multi-driver nets - Use
always_comb,always_ff,always_latch— never plainalways - Use
=inalways_comb,<=inalways_ff— the convention that lint tools check - Use
unique caseorpriority case— replacesfull_case/parallel_case - Use
typedef enum logic [N:0]for FSM states — explicit encoding width - Use packed structs for bus bundles — cleaner than raw vectors
- Use packages for shared types and parameters — avoid
`defines - Use interfaces for complex bus protocols — reduce port clutter
- Use
$clog2()for address widths - Use
'0,'1,'x,'zfor context-width fill `default_nettype noneat the top of every file- One module per file, filename matches module name
What Is Synthesizable?
| Feature | Synthesizable? | Notes |
|---|---|---|
logic, wire, packed structs/unions | Yes | Core RTL types |
enum, typedef | Yes | Use for FSMs, type clarity |
always_comb, always_ff, always_latch | Yes | Preferred over always |
unique/priority case/if | Yes | Synthesis directives + runtime checks |
| Interfaces & modports | Yes | Major synthesis tools support |
| Packages | Yes | Types, parameters, functions |
| Generate blocks | Yes | Parameterized hardware |
Inline variable declarations (for (int i...)) | Yes | C-style loops |
bit, int, byte, shortint | Partial | Some tools support; prefer logic in RTL |
Assertions (assert property) | Ignored | Stripped by synthesis, valuable for formal |
| Classes, dynamic arrays, queues, strings | No | Verification only |
| Randomization, coverage, mailboxes, semaphores | No | Verification only |
DPI, program block | No | Simulation/verification only |
Common Pitfalls
| Mistake | Result | Fix |
|---|---|---|
Using int/bit in RTL ports | Two-state: no x-propagation | Use logic for RTL signals |
| Unpacked struct on port | Tool-specific behavior | Use packed structs for RTL ports |
Missing unique/priority | No synthesis/sim checks | Annotate every case statement |
logic with multiple drivers | Compile error | Use wire for tri-state/multi-driver |
| Enum without explicit width | Defaults to int (32-bit) | Use enum logic [N:0] |
Blocking in always_ff | Legal, but flagged by lint and prone to race conditions | Use <= non-blocking |
Using always @(*) in SV | No latch checking | Use always_comb |
Forgetting disable iff in assertions | False failures during reset | Always add disable iff (!rst_n) |