SystemVerilog arrays are far more powerful than anything Verilog offered. The language gives you five distinct array types — fixed-size, packed, unpacked, dynamic, queue, and associative — each designed for different verification patterns. Choosing the right one for your testbench makes the difference between clean, efficient code and a mess of manual size management.
This is the most confusing distinction in SystemVerilog arrays, and getting it wrong causes subtle bugs. The rule: packed dimensions come before the variable name (they describe the bits within a single element), while unpacked dimensions come after the variable name (they describe the number of elements).
A packed array is a contiguous sequence of bits that can be treated as a single integer value. You can index individual bits, do arithmetic on the whole thing, and assign it to/from integers directly. Packed arrays are synthesizable and commonly used in RTL.
An unpacked array is a collection of elements. Each element is a separate thing in simulation memory. You cannot directly assign an unpacked array to an integer. $size(arr) returns the number of elements. $dimensions(arr) returns the total number of dimensions.
logic [7:0] packed_byte — 8-bit packed vector. ONE element, 8 bits wide.
logic [7:0] mem [0:15] — 16 unpacked elements, each 8 bits wide. A memory array.
logic [7:0] matrix [4][8] — 4×8 unpacked array of bytes. 32 elements total.
A dynamic array (type[]) is an unpacked array whose size is not known at compile time. You allocate it at runtime with new[N], and you can resize it later with another new[N] (the old contents are preserved if the new size is larger — or copied into an existing array). Use dynamic arrays when you need indexed random access to all elements and the size is determined at runtime (e.g., read from a file, passed as a parameter).
| Operation | Syntax | Notes |
|---|---|---|
| Allocate | arr = new[N] | Creates N elements, zero-initialized |
| Allocate + copy | arr = new[N](old_arr) | Copy old_arr into first elements of new array |
| Get size | arr.size() | Returns current number of elements |
| Delete (free) | arr.delete() | Frees memory, size becomes 0 |
| Index | arr[i] | 0 to arr.size()-1 |
A queue (type[$]) is a dynamically-sized ordered list that supports efficient insertion and deletion from both ends. It is the natural choice for stimulus queues, scoreboards, FIFOs, and any testbench structure that needs to add/remove elements in order. Unlike dynamic arrays, queues do not require new[] — they auto-resize.
| Method | Description | Complexity |
|---|---|---|
| q.push_back(item) | Add item to the end (tail) | O(1) |
| q.push_front(item) | Add item to the front (head) | O(1) |
| q.pop_back() | Remove and return tail item | O(1) |
| q.pop_front() | Remove and return head item | O(1) |
| q.size() | Number of elements currently in queue | O(1) |
| q.delete() | Delete all elements | O(N) |
| q.delete(i) | Delete element at index i | O(N) |
| q.insert(i, item) | Insert item before index i | O(N) |
| q[i] | Random access by index | O(1) |
| q[$] | Last element (shorthand for q[q.size()-1]) | O(1) |
| q[0:3] | Slice: elements 0 through 3 as a queue | O(N) |
A SystemVerilog associative array (type[key_type]) is a hash map that stores sparse data indexed by any integral type or string. Unlike fixed arrays, no memory is allocated for unused keys — ideal for sparse memories, coverage maps, and per-ID scoreboards in testbenches.
| Method | Description |
|---|---|
| aa.exists(key) | Returns 1 if key exists, 0 otherwise — ALWAYS check before reading |
| aa.delete(key) | Delete the entry for key |
| aa.delete() | Delete all entries |
| aa.size() | Number of entries currently stored |
| aa.first(key) | Set key to the first (smallest) key; returns 1 if exists |
| aa.last(key) | Set key to the last (largest) key |
| aa.next(key) | Advance key to the next entry; returns 1 if a next exists |
| aa.prev(key) | Step key back to previous entry |
| foreach(aa[k]) | Iterate over all keys in ascending order |
SV provides a rich set of built-in array manipulation methods available on fixed, dynamic, and queue arrays. The with clause lets you filter or transform elements inline.
| Method | Returns | Description |
|---|---|---|
| .sum() | element type | Sum of all elements. Use with: .sum() with (item > 0 ? item : 0) |
| .product() | element type | Product of all elements |
| .min() | queue of type | Returns a queue containing the minimum value(s) |
| .max() | queue of type | Returns a queue containing the maximum value(s) |
| .find() | queue of type | Returns elements matching the with clause |
| .find_index() | queue of int | Returns indices of matching elements |
| .find_first() | queue of type | Returns first matching element |
| .find_last() | queue of type | Returns last matching element |
| .unique() | queue of type | Returns elements with unique values |
| .unique_index() | queue of int | Returns indices of unique elements |
| .sort() | void | Sort array in ascending order (in place) |
| .rsort() | void | Sort array in descending order |
| .reverse() | void | Reverse array order in place |
| .shuffle() | void | Randomly shuffle elements in place |
foreach(arr[i]) automatically iterates over every valid index of the array without needing to know its size. For multi-dimensional arrays, use foreach(matrix[i,j]). It works on all array types including queues and associative arrays. Use for when you need explicit index control (counting backwards, skipping elements). Use foreach for clean iteration over all elements.
// ============================================================
// arrays_demo.sv — All SV Array Types with TB Use Cases
// EcrioniX · SV Verification Course · Day 3
// ============================================================
module arrays_demo;
// ---- 1. FIXED-SIZE ARRAY ----
int fixed_arr [8]; // 8 ints, indices 0..7
logic [7:0] mem [0:255]; // 256-byte memory model (unpacked)
// ---- 2. PACKED ARRAY ----
logic [31:0] word; // 32-bit packed vector — one element, 32 bits
logic [3:0][7:0] quad_byte; // 4 bytes packed as one 32-bit value
// ---- 3. DYNAMIC ARRAY ----
int dyn[]; // dynamic array of int, size unknown at compile time
// ---- 4. QUEUE — stimulus and scoreboard ----
int stim_q [$]; // stimulus queue
logic [7:0] sb_q [$]; // scoreboard FIFO
// ---- 5. ASSOCIATIVE ARRAY — coverage map ----
int cov_map [string]; // hit count per scenario name
logic [31:0] sparse_mem [logic [31:0]]; // sparse address-mapped memory
initial begin
// ---- Fixed array: initialize and sum ----
foreach (fixed_arr[i]) fixed_arr[i] = i * 2;
$display("Fixed sum = %0d", fixed_arr.sum()); // 0+2+4+6+8+10+12+14 = 56
// ---- Packed array: bit-level access ----
word = 32'hDEAD_BEEF;
$display("Byte 3 of word: 8'h%0h", word[31:24]); // DE
$display("Byte 0 of word: 8'h%0h", word[7:0]); // EF
quad_byte = 32'h01020304;
$display("quad_byte[0] = %0h, [3] = %0h", quad_byte[0], quad_byte[3]); // 04, 01
// ---- Dynamic array: allocate, use, resize ----
dyn = new[4];
foreach (dyn[i]) dyn[i] = i + 100;
$display("dyn.size() = %0d", dyn.size()); // 4
dyn = new[8](dyn); // resize to 8, preserve existing 4 elements
$display("after resize, dyn.size() = %0d", dyn.size()); // 8
$display("dyn[0]=%0d dyn[4]=%0d", dyn[0], dyn[4]); // 100 0
dyn.delete();
$display("after delete, dyn.size() = %0d", dyn.size()); // 0
// ---- Queue: stimulus FIFO ----
// Build a stimulus queue of transactions to send
stim_q.push_back(8'hAA);
stim_q.push_back(8'hBB);
stim_q.push_front(8'h00); // priority: put 0x00 first
$display("Queue size = %0d", stim_q.size()); // 3
while (stim_q.size() > 0) begin
automatic int item = stim_q.pop_front();
$display("Sending: 8'h%0h", item); // 00, AA, BB
end
// Queue slice and unique
int vals[$] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
$display("min = %0d, max = %0d", vals.min()[0], vals.max()[0]); // 1, 9
$display("sum = %0d", vals.sum()); // 39
vals.sort();
$display("sorted[0..3] = %0d %0d %0d %0d", vals[0],vals[1],vals[2],vals[3]);
automatic int uniqs[$] = vals.unique();
$display("unique count = %0d", uniqs.size()); // 7
// find_with: filter elements > 4
automatic int big[$] = vals.find() with (item > 4);
$display("elements > 4: %0d entries", big.size());
// ---- Associative array: coverage hit counter ----
cov_map["reset_test"] = 0;
cov_map["burst_wr_test"] = 0;
cov_map["idle_test"] = 0;
cov_map["reset_test"]++;
cov_map["burst_wr_test"] += 3;
// Iterate over all entries
foreach (cov_map[scenario]) begin
$display("Coverage[%s] = %0d hits", scenario, cov_map[scenario]);
end
// Check before accessing to avoid phantom entries
if (cov_map.exists("unknown_test"))
$display("unknown_test hit count: %0d", cov_map["unknown_test"]);
else
$display("unknown_test not in map (size stays %0d)", cov_map.size());
// ---- Sparse memory model ----
sparse_mem[32'h4000_0000] = 32'hDEAD_BEEF;
sparse_mem[32'hFFFF_0000] = 32'hCAFE_BABE;
$display("sparse_mem.size() = %0d (only 2 entries allocated)", sparse_mem.size());
$display("\nAll array demos complete.");
$finish;
end
endmoduleint d[]): allocate with new[N], resize with new[N](old), free with .delete().int q[$]): use for all TB stimulus/scoreboard FIFOs — push_back/pop_front are O(1).int aa[string]): hash map — sparse data, coverage maps, address memories. Always .exists(key) before reading..sum(), .min()[0], .max()[0], .find() with (item > x), .unique(), .sort().foreach for clean iteration over any array type. Use for when you need explicit index control.Packed arrays have their dimensions before the variable name (logic [7:0] byte_val) — they are contiguous bits treated as a single value, and they are synthesizable. Unpacked arrays have their dimensions after the name (logic [7:0] mem [256]) — they are collections of separate elements. Think packed = "bits of a word", unpacked = "array of words".
Use a queue when you need to add/remove from either end (stimulus FIFO, scoreboard). push/pop are O(1). Use a dynamic array when you allocate once with a known size and need indexed random access. Dynamic arrays don't support efficient front insertion. In most TB code, queues are the right default.
Associative arrays are hash maps — perfect for sparse memories (logic[31:0] mem[logic[31:0]]), coverage hit counters (int cov[string]), and per-ID scoreboards (Packet sb[int]). Only allocated entries consume memory. Always use .exists(key) before reading, or you'll accidentally create entries.
arr.sum() sums all elements directly. arr.find() with (item > 5) returns a queue of elements where the with clause is true. arr.min()[0] and arr.max()[0] return a 1-element queue containing the min/max — take [0] to get the value. arr.sort() sorts in place. These work on fixed, dynamic, and queue arrays.