Skip to main content

Dynamic and Associative Arrays

The previous page's arrays all had a size fixed at compile time. A testbench, though, often doesn't know how many transactions a test will generate until it's actually running — a directed test might send exactly 10 packets, but a randomized test might send anywhere from 1 to 1,000. SystemVerilog adds three collection types built specifically for that uncertainty.

Dynamic arrays: fixed shape, runtime size​

A dynamic array has the same element type and indexing behavior as the fixed arrays from the previous page, but its size is set (and can be changed) at runtime rather than compile time:

int data []; // declared, but no storage yet — size 0
data = new[10]; // now holds 10 elements, indices 0..9
data[3] = 42;

data = new[20](data); // resize to 20, keeping the old 10 values

new[size] allocates storage; the optional (data) argument copies over as much of the old array's content as fits. .size() returns the current length, and .delete() frees it back to empty. Dynamic arrays fit best when the total count isn't known up front but the collection is still processed as one contiguous block once built (e.g., "generate N random transactions, then replay them all").

Queues: a dynamic array with FIFO/LIFO operations​

A queue is a dynamic-array-like collection with $ as its unpacked dimension, and adds the operations an actual queue (or stack) needs — push/pop from either end — without a manual shift loop:

int q [$]; // an empty queue of int

q.push_back(5); // {5}
q.push_back(7); // {5, 7}
q.push_front(1); // {1, 5, 7}

int first = q.pop_front(); // first = 1, q is now {5, 7}
int last = q.pop_back(); // last = 7, q is now {5}

q.insert(0, 99); // insert 99 at index 0
q.delete(0); // remove index 0

Queues are the natural fit for scoreboards and transaction pipelines — a driver's push_back()s expected values as it sends them, and a monitor's pop_front()s them off in the same order they arrive, exactly modeling a real in-order FIFO check without hand-managing indices or a fixed maximum size.

Associative arrays: index by anything, not just consecutive integers​

An associative array indexes by a key of any type (not just 0, 1, 2, ...) and only stores entries that actually exist — sparse, unordered, and unbounded in key range:

int coverage_count [string]; // indexed by string keys
coverage_count["opcode_add"] = 0;
coverage_count["opcode_add"]++;
coverage_count["opcode_sub"] = 1;

if (coverage_count.exists("opcode_mul"))
$display("mul seen %0d times", coverage_count["opcode_mul"]);

foreach (coverage_count[key])
$display("%s: %0d", key, coverage_count[key]);

.exists(key) checks membership without creating an entry as a side effect (unlike just indexing, which for some usages implicitly creates the key); .delete(key) removes one entry (or, with no argument, empties the whole array); .first()/.next()/.last()/.prev() iterate keys in order, .num() returns the current number of entries, and foreach (as shown) is the usual way to walk every entry. A byte-addressed sparse memory model (most addresses in a 32-bit address space are never actually touched by a test) is a classic associative-array use case — a real logic [7:0] mem [0:2**32-1] would be absurd to allocate, but logic [7:0] mem [int] only stores the addresses actually written.

Choosing between the three​

NeedReach for
Known max size, contiguous 0..N-1 indices, size changes rarelyDynamic array
FIFO/LIFO push/pop behavior, order mattersQueue
Sparse or non-integer keys, order doesn't matterAssociative array

What's next​

Section A's five pages cover every data type this curriculum needs from here on. Section B moves to how procedural code is written around these types — starting with always_comb/always_ff/always_latch, SystemVerilog's fix for one of Verilog's most common always @(...) sensitivity-list mistakes.