SystemVerilog Foundations

Verification guidelines, data types, arrays, and procedural flow — the grammar of the language.

01 · Verification guidelines

What verification actually means

Verification is the art of proving that a design does what its specification says it does. The specification is a human-language document — ambiguous, incomplete, sometimes self-contradictory — and the RTL is one engineer's reading of it. Your job is to offer the second reading. Where the two interpretations disagree, there is a bug.

The goal is to find the bug before the customer does.

Precept
Bugs are good. Each one found before tape-out is one fewer that ships.

The verification plan

Verification begins with a plan derived from the hardware spec. The plan catalogs what features must be exercised and by which techniques — directed tests, constrained-random stimulus, assertions, formal, or emulation. A good plan names corners the designer likely missed.

Directed vs constrained-random

A directed test exercises a specific feature with hand-written stimulus. It finds the bugs you thought of. Constrained-random testing (CRT) uses a pseudo-random number generator with rand/randc and constraints to explore the stimulus space you didn't think of.

02 · Layered testbench

The anatomy of a serious testbench

A serious testbench is not a flat file of initial blocks. It is organized in layers, each with a single responsibility:

  • Signal layer — the physical wires of the DUT.
  • Command layer — drivers that turn transactions into signal wiggles; monitors that turn signal wiggles back into transactions.
  • Functional layer — generators, agents, self-checkers, scoreboards.
  • Scenario layer — sequences that orchestrate interesting traffic patterns.
  • Test layer — the top-level test and its coverage goals.

Each layer communicates with the next through transactions, not signals. This is the architecture the remainder of this reference is teaching you to build.

03 · Data types

The logic type and beyond

SystemVerilog introduced logic as an improvement on Verilog's reg. It's a 4-state type (0, 1, X, Z) usable anywhere a net is, but with one critical restriction: a single driver. This restriction catches netlist bugs at elaboration time, before you waste simulation cycles.

Use logic everywhere
For new code, logic is almost always the right choice. Reach for wire only when you genuinely have multiple drivers (e.g., a tri-state bus).

Integer types

SystemVerilog gives you precise, bounded integer types in addition to Verilog's integer:

TypeWidthStates
bit1 (configurable)2-state (0/1)
byte82-state
shortint162-state
int322-state
longint642-state
logicconfigurable4-state

2-state types simulate faster and are what you want for testbench counters, indices, and variables that should never see X or Z. 4-state types are what you want for DUT signals where X-propagation is a legitimate signal.

04 · Arrays

Packed, unpacked, and the collection types

SystemVerilog distinguishes packed from unpacked dimensions by where you put them relative to the variable name.

arrays.svDECLARATIONS
int lo_hi [0:15];      // Verilog-style bounds
int array [16];         // SystemVerilog C-style size
int grid [8][4];        // 2-D, 8 rows × 4 cols
foreach (grid[i,j]) grid[i][j] = i*4 + j;

Dimensions to the left of the name are packed: contiguous bits, sliceable across element boundaries, ideal for modeling hardware registers. Dimensions to the right are unpacked: separate storage per element, ideal for collections of records.

Dynamic collections

Three variable-length structures, each suited to a different shape of data:

TypeWhen to reach for itDeclaration
Dynamic arrayRuntime-sized, indexed by integer. Packet payloads.int d[];
QueueDeque with push/pop at both ends. Stimulus buffers, scoreboards.int q[$];
AssociativeSparse, any key type — 64-bit addresses, strings, enums.int mem[bit[63:0]];
05 · Procedural statements

Tasks, functions, and routine arguments

The distinction between a task and a function is the difference between a subroutine that may wait and one that may not. A function executes in zero simulation time; it may not contain # delays, @ event controls, wait statements, or calls to tasks. A task may contain all of them.

A void function is a function declared to return nothing. Use one when you want the zero-time discipline of a function but have no useful value to return — void function monitor_drive();.

Argument directions

Routine arguments may be declared input, output, inout, or ref. The first three copy values at call and return. ref passes by reference: the routine sees live updates to the caller's variable.

Pass big things by ref
Pass large arrays, mailboxes, and class handles by ref to avoid copy cost.
arguments.svTASK
task transfer(
  input  bit [31:0] addr,      // copied in
  output bit [31:0] data,      // copied out
  ref    mailbox mbx,             // live reference
  input  int retries = 3          // default value
);
  // …
endtask
06 · Enums and structs

Type-safe stimulus

An enum lets you name a set of values — states, commands, modes — and gives you type safety at compilation.

enum.svENUM
typedef enum bit[1:0] {
  RESP_OKAY   = 2'b00,
  RESP_EXOKAY = 2'b01,
  RESP_SLVERR = 2'b10,
  RESP_DECERR = 2'b11
} axi_resp_e;

axi_resp_e resp;
resp = RESP_OKAY;               // type-safe assignment
if (resp == RESP_SLVERR) // …

A struct groups related fields into a record. Use typedef struct packed for bit-level packing (registers, protocols) and plain typedef struct for testbench-side records.