Data Types¶
4-state vs. 2-state¶
Verilog's reg/wire model every bit as one of four states: 0, 1, X (unknown), Z (high-impedance). SystemVerilog keeps this as logic, but adds pure 2-state types (bit, byte, int, shortint, longint) that only hold 0/1.
logic [7:0] rtl_data; // 4-state — can be X or Z, matches real hardware/simulation reality
bit [7:0] tb_counter; // 2-state — only 0/1, faster to simulate, no X-propagation
This isn't a stylistic choice — it's the single most important type decision in a testbench:
Why this matters
If you drive a DUT input from a 2-state variable, you can never send it an X, even deliberately. A huge class of real RTL bugs (uninitialized registers, unconnected muxes, reset-timing issues) only shows up as X-propagation. A testbench that can't drive or check for X is blind to that entire bug class.
Rule of thumb: anything that connects to or represents DUT signals (logic), anything internal to the testbench that's pure bookkeeping (bit/int — loop counters, transaction counts, scoreboard tallies).
logic replaces reg/wire — mostly¶
logic can be driven by exactly one continuous assignment or one procedural block — same restriction as reg, but it can be used anywhere reg or wire could, removing the old "is this a wire or a reg" decision when declaring a signal. It cannot be used where multiple drivers are genuinely needed (a real bus with multiple tri-state drivers still needs wire/tri).
Packed vs. unpacked arrays¶
bit [7:0] packed_bytes [0:3]; // packed dimension [7:0], unpacked dimension [0:3]
bit [31:0] word; // a packed 32-bit vector — contiguous bits, one "thing"
int unpacked_array [0:31]; // 32 separate 32-bit ints — not contiguous, no bit-level ops across elements
- Packed dimensions (
[7:0]) describe bits that are contiguous in memory and can be treated as a single vector — sliced, concatenated, cast as one value. - Unpacked dimensions (
[0:3]) are just an array of separate elements, each with its own storage.
A byte queue[$], an associative array int scores[string], and a fixed array bit data[0:63] are all unpacked-array variants — useful for testbench-side data structures (transaction queues, scoreboards) where you don't need bit-level packing.
Queues, dynamic arrays, associative arrays¶
These three cover almost every data-structure need in a testbench:
int q[$]; // queue — dynamic size, push/pop from either end, ordered
int dyn[]; // dynamic array — resizable, but no push/pop; use new[size]
int assoc[string]; // associative array — sparse, keyed by any type (string, int, enum...)
q.push_back(5);
q.push_front(1);
int x = q.pop_front();
dyn = new[10]; // allocate 10 elements
dyn.delete(); // free it
if (assoc.exists("dut_id_3")) $display(assoc["dut_id_3"]);
Queues are the default choice for scoreboards and transaction pipes (expected-vs-actual comparison queues); associative arrays are the default for anything keyed by an ID that isn't densely packed (e.g. tracking outstanding transactions by a sparse tag/address).
Common pitfall
pop_front()/pop_back() on an empty queue is a runtime error in most simulators (returns default value with a warning, or a fatal depending on tool/mode) — always check q.size() before popping in code that isn't guaranteed to have an entry, e.g. a scoreboard that might get a DUT response before the expected-value generator has produced anything.