In digital IC verification, arrays are everywhere — register models, TLM port lists, expected data queues in scoreboards, coverage bin definitions… pretty much every verification component depends on arrays.

SystemVerilog offers far richer array types and methods than traditional Verilog. Use them well and your productivity skyrockets. Use them poorly and performance tanks.

This guide covers every array type from the ground up.


1. Array Type Overview

SystemVerilog provides four main array categories:

TypeDeclarationSizeBest For
Fixed-Size[n]Compile-time fixedFixed-width signals, register banks
Dynamic[] + new[n]Runtime variableUnknown-count transactions, config tables
Associative[*] or [type]Key-indexedSparse storage, address maps
Queue[$]Grows/shrinks on demandScoreboards, FIFOs, ordered data streams

2. Packed vs Unpacked — The First Fork

Before diving into arrays, internalize one core concept: packed vs unpacked.

Packed Arrays

// Width on the left side of name → packed, physically contiguous
bit [7:0]  byte_vec;         // Single-dimension packed: an 8-bit vector
bit [3:0][7:0]  word_vec;    // 2-D packed: 4 bytes, 32 bits total
  • Essence: A contiguous chunk of bits, always treated as a whole
  • Operations: Can participate in arithmetic/logic ops as a unit; bit-slicing supported
  • Storage: Contiguous in memory, zero overhead

Unpacked Arrays

// Width on the right side → unpacked, each element is independent
bit  byte_mem [0:7];         // 8 unpacked elements, each 1 bit
int  data_buf [256];         // 256 unpacked ints
  • Essence: A collection of independent elements
  • Operations: Must operate element-by-element, no bulk arithmetic
  • Storage: Elements may not be contiguous in memory

⚠️ Common Gotcha

bit [7:0] a;        // packed: one 8-bit variable
bit b [7:0];        // unpacked: eight 1-bit variables

a = 8'hFF;          // ✅ Legal
b = 8'hFF;          // ❌ Compilation error! Can't bulk-assign unpacked

bit [7:0] c [0:3];  // Hybrid: 4 unpacked elements, each 8-bit packed

Rule of thumb: Prefer packed when possible — better simulation performance. But beyond 64 bits, synthesis results may degrade.


3. Fixed-Size Arrays

The most basic array type. All dimensions are known at compile time.

Declaration

// 1-D fixed
int  arr1 [0:7];          // 8 elements, indices 0~7
int  arr2 [8];            // Equivalent to [0:7]
int  arr3 [6:1];          // 6 elements, indices 6,5,4,3,2,1

// Multi-dimensional
int  matrix [0:3][0:7];   // 4×8 two-dimensional
int  cube   [0:1][0:3][0:7]; // 2×4×8 three-dimensional

Initialization

int arr [0:3] = '{0, 1, 2, 3};                // List init
int arr2[0:3] = '{default: -1};                // All to -1
int arr3[0:3] = '{0:10, 1:20, default: 0};     // Specific + defaults
int matrix[0:2][0:1] = '{ '{0,1}, '{2,3}, '{4,5} }; // Multi-dim

Iteration

// C-style for (discouraged)
for (int i = 0; i < $size(arr); i++)
    $display("arr[%0d] = %0d", i, arr[i]);

// foreach (recommended ✅)
foreach (arr[i])
    $display("arr[%0d] = %0d", i, arr[i]);

// Multi-dimensional foreach
foreach (matrix[i, j])
    $display("matrix[%0d][%0d] = %0d", i, j, matrix[i][j]);

Why foreach? No manual boundary calculation, works regardless of dimension ordering, cleaner code.

System Functions Quick Reference

FunctionMeaningExample
$size(arr)Size of first dimension$size(arr) → 8
$size(arr, 2)Size of specific dimension$size(matrix, 2) → 8
$dimensions(arr)Number of dimensions2
$left(arr) / $right(arr)Left/right bounds$left(arr[6:1]) → 6
$low(arr) / $high(arr)Min/max index$low(arr[6:1]) → 1

4. Dynamic Arrays

Size determined at runtime. Allocated via the new[] constructor.

Core Operations

int dyn[];                  // Declaration: empty, size 0
dyn = new[10];              // Allocate 10 elements (default 0)
dyn = new[20](dyn);         // Expand to 20, preserving old data ✅
dyn = new[5];               // Shrink to 5, old data lost ⚠️

dyn = '{1, 2, 3, 4, 5};    // Direct assignment (auto-reallocates)
dyn.delete();               // Clear, size → 0
$display("size = %0d", dyn.size());  // Get current size

Useful Trick: array ↔ queue

int dyn[];
dyn = new[5];
foreach (dyn[i]) dyn[i] = i * 10;

// Dynamic array → queue
int q[$] = dyn;                // Direct assignment ✅

// Queue → dynamic array
dyn = new[q.size()];
dyn = {>>{q}};                // Streaming operator

⚠️ Common Pitfalls

// ❌ Error: accessing without allocation
int dyn[];
dyn[0] = 5;     // Runtime fatal error!

// ❌ Error: resize loses data
int dyn[] = '{1,2,3,4,5};
dyn = new[10];  // First 5 elements lost! New elements are all 0

// ✅ Correct: expand and preserve
dyn = new[10](dyn);

Golden rule: Always check dyn.size() before accessing, or ensure new[] has been called.


5. Queues

Queues are SystemVerilog’s most flexible data structure — similar to C++ std::deque.

Declaration & Initialization

int q1[$];                       // Unbounded queue
int q2[$:255];                   // Bounded queue, max 256 elements
string names[$] = '{"alice", "bob", "charlie"};

Insertion & Deletion

int q[$] = '{10, 20, 30};

// Tail operations
q.push_back(40);                 // {10,20,30,40}
q.push_back(50);                 // {10,20,30,40,50}
q.pop_back();                    // {10,20,30,40}

// Head operations
q.push_front(0);                 // {0,10,20,30,40}
q.pop_front();                   // {10,20,30,40}

// Arbitrary position
q.insert(2, 25);                 // {10,20,25,30,40}  insert at index 2
q.delete(1);                     // {10,25,30,40}     delete index 1
q.delete();                      // Clear entire queue

Classic Verification Pattern: Scoreboard

class scoreboard;
    transaction exp_q[$];        // Expected queue
    
    function void add_expected(transaction tr);
        exp_q.push_back(tr);
    endfunction
    
    function void check_actual(transaction tr);
        if (exp_q.size() == 0) begin
            `uvm_error("SB", "unexpected transaction!")
        end
        else begin
            transaction exp = exp_q.pop_front();
            if (!exp.compare(tr))
                `uvm_error("SB", "mismatch!")
        end
    endfunction
endclass
// Sliding window: keep last N samples
int sample_window[$:15];         // Max 16 elements

function void add_sample(int val);
    if (sample_window.size() == 16)
        sample_window.pop_front();
    sample_window.push_back(val);
endfunction

6. Associative Arrays

Indexed by keys rather than sequential integers. Ideal for sparse data.

Declaration

int  aa_wild [*];                    // Wildcard index (any integral type)
int  aa_int  [int];                  // int key
int  aa_str  [string];               // string key
int  aa_cls  [some_class];           // class handle key (rare)
bit [63:0] aa_addr [bit [63:0]];    // Large-width key: address maps

Basic Operations

int aa [string];

aa["foo"]  = 10;
aa["bar"]  = 20;
aa["baz"]  = 30;

$display("aa[bar] = %0d", aa["bar"]);     // 20

// Check key existence
if (aa.exists("foo"))
    $display("foo exists");

// Deletion
aa.delete("bar");        // Delete single entry
aa.delete();             // Delete all

// Traversal
string key;
if (aa.first(key)) begin
    do
        $display("aa[%s] = %0d", key, aa[key]);
    while (aa.next(key));
end

// Size
$display("size = %0d", aa.num());

Typical Use Case: Register Address Map

class reg_model;
    uvm_reg   reg_map [bit [31:0]];
    
    function void build();
        // 200 registers in a 4GB address space
        // Associative array stores only what exists
        reg_map[32'h4000_1000] = reg_ctrl;
        reg_map[32'h4000_1004] = reg_status;
        reg_map[32'h4000_2000] = reg_data;
    endfunction
    
    function uvm_reg get_reg(bit [31:0] addr);
        if (reg_map.exists(addr))
            return reg_map[addr];
        else begin
            `uvm_error("REG", $sformatf("No register at addr 0x%h", addr))
            return null;
        end
    endfunction
endclass

⚠️ Watch Out

  • exists() on a missing key does not create an entry
  • Direct read on a missing key does create an entry with default value
  • [*] index keys must be integral (not class objects)
  • Associative arrays don’t support foreach — use first()/next()

7. Array Methods Reference

SystemVerilog provides a powerful set of methods for unpacked arrays (fixed, dynamic, queues).

7.1 Reduction Methods

Apply an accumulation operation across all elements, returning a single value.

int arr[] = '{1, 2, 3, 4, 5};

arr.sum();       // 15    Sum
arr.product();   // 120   Product
arr.and();       // Bitwise AND
arr.or();        // Bitwise OR
arr.xor();       // Bitwise XOR

7.2 Locator Methods — the find Family

int arr[] = '{1, 3, 5, 7, 2, 4, 6, 8};

// find            → queue of all matching elements
// find_first      → queue of first matching element (single)
// find_last       → queue of last matching element
// find_index      → queue of matching element indices
// find_first_index→ first matching index
// find_last_index → last matching index

int q[$];

q = arr.find(x) with (x > 4);            // {5, 7, 6, 8}
q = arr.find_first(x) with (x > 4);      // {5}
q = arr.find_index(x) with (x > 4);      // {2, 3, 5, 7}
q = arr.find_first_index(x) with (x > 4);// {2}

7.3 Advanced with Clauses

// 'item' is the default iterator variable — you can rename it
q = arr.find(item) with (item > 5 && item < 8);   // {7, 6}

// Multi-condition with objects
class packet;
    int  addr;
    bit  is_write;
endclass

packet pkt_q[$];
// Find addresses of all write transactions
int addr_q[$];
addr_q = pkt_q.find(p) with (p.is_write);
foreach (addr_q[i]) addr_q[i] = addr_q[i].addr;

7.4 Sorting Methods

int arr[] = '{5, 2, 8, 1, 3};

arr.reverse();       // {3, 1, 8, 2, 5}   In-place reverse
arr.sort();          // {1, 2, 3, 5, 8}   Ascending
arr.rsort();         // {8, 5, 3, 2, 1}   Descending
arr.shuffle();       // Random shuffle

// Custom sort with 'with' clause
class packet;
    int  id;
    int  priority;
endclass

packet pkt_q[$];
pkt_q.sort(p) with (p.priority);    // Ascending by priority
pkt_q.rsort(p) with (p.priority);   // Descending by priority

7.5 Other Useful Methods

int arr[] = '{1, 2, 3, 4, 5};

arr.min();                    // 1     Minimum
arr.max();                    // 5     Maximum
arr.unique();                 // Deduplicate, returns queue
arr.unique_index();           // Indices of unique elements

int idx = arr.min(item) with (item % 2);  // Min even number: 2

8. foreach Loop Advanced Usage

Multi-dimensional Traversal

int arr[3][4];  // 3 rows × 4 columns

// ✅ Recommended: foreach
foreach (arr[i, j])
    arr[i][j] = i * 4 + j;

// ❌ Discouraged: nested for
for (int i = 0; i < 3; i++)
    for (int j = 0; j < 4; j++)
        arr[i][j] = i * 4 + j;

foreach with Queues & Dynamic Arrays

int dyn[] = '{10, 20, 30, 40};
int q[$]  = dyn;

// All three are legal
foreach (dyn[i])  $display(dyn[i]);
foreach (q[i])    $display(q[i]);

// ⚠️ Warning: Don't resize during iteration! Behavior is undefined.
// ❌ foreach (q[i]) q.push_back(i);  // Don't do this

9. Synthesis vs Simulation — Key Differences

As a verification engineer, most of your code runs in simulation. But if it needs synthesis, know these limits:

FeatureSimulationSynthesis
Fixed-size packed array
Fixed-size unpacked array✅ (limited)
Dynamic array❌ Not synthesizable
Associative array❌ Not synthesizable
Queue❌ Not synthesizable
foreach✅ (SV 2017+)
find/sort methods❌ Not synthesizable
$size / $dimensions

Simulation advice: Use dynamic arrays and queues freely — performance is fine, expressiveness is great.
Synthesis advice: Stick to fixed-size packed/unpacked arrays; be cautious with 2+ dimensions.


10. Quiz Time — Common Interview Traps

Trap 1: new[] Without Argument

int arr[] = '{1, 2, 3, 4, 5};
arr = new[10];       // arr becomes {0,0,0,0,0,0,0,0,0,0} — data lost!
arr = new[10](arr);  // ✅ Expand and preserve

Trap 2: Reading a Missing Associative Array Key

int aa[string];
$display("aa[key] = %0d", aa["nonexistent"]);  // Prints 0, AND creates the entry!
// aa.num() is now 1, not 0!

Trap 3: find Returns a Queue

int arr[] = '{1, 2, 3, 4, 5};
int result = arr.find_first(x) with (x > 3);  // ❌ Type mismatch!
int result_q[$] = arr.find_first(x) with (x > 3); // ✅ Use a queue
int result = result_q[0];                          // ✅ Then extract

Trap 4: Packed Array Dimension Order

bit [3:0][7:0] a;       // 4 bytes, each 8 bits
// a[3] = most significant byte, a[0] = least significant byte

a = 32'hDEAD_BEEF;
$display("a[3] = %h", a[3]);  // DE
$display("a[0] = %h", a[0]);  // EF
$display("a[3][7] = %b", a[3][7]); // 1 (MSB of DE)

11. Summary

NeedPick
Fixed-count signals/DUT portsFixed-size unpacked
Concatenate into a busFixed-size packed
Size unknown until runtimeDynamic array
FIFO / Scoreboard orderingQueue [$]
Sparse address mappingAssociative array
Stats / search / sortArray methods (find, sort)
Array iterationforeach (always first choice)

SystemVerilog’s array system is well-designed and thorough. Master the right type for each scenario and your verification code will be cleaner by an order of magnitude. Don’t simulate C-style double for loops where you don’t need to — SV gives you better tools. Use them.


If you found this helpful, follow me on Bilibili for more digital IC verification content.