SystemVerilog Series — SV-04: Arrays — VLSI Trainers
VLSI Trainers SV Series · 4 / 44
SystemVerilog Series · SV-04

Arrays

Fixed-size, dynamic, associative arrays and queues — packed vs unpacked, multi-dimensional indexing and slicing, new[]/delete()/size(), associative methods, queue operators, and the full suite of array manipulation methods: locator, ordering, and reduction.

📈 Array Types Overview

SystemVerilog provides four distinct array kinds. Fixed-size arrays come from Verilog-2001; the other three — dynamic, associative, and queue — are new in SystemVerilog.

Fixed-size
Size fixed at compile time. Stored contiguously. Used for RTL packed vectors and testbench structures with known bounds.

int arr[8];
Dynamic
One dimension resized at runtime with new[]. Can grow with value-preserving copy. Used for variable-length payloads.

int dyn[];
Associative
Sparse hash map. Storage allocated per entry. Index can be any type. Used for scoreboards, sparse memories.

int aa[string];
Queue
Variable-size ordered list. O(1) push/pop at either end. Used for FIFOs, result queues, mailbox backings.

int q[$];
New terminology in SystemVerilog: What Verilog-2001 called vector width (dimensions declared before the variable name) is now called a packed array. Dimensions declared after the name are an unpacked array. This distinction matters because packed arrays are treated as contiguous integer vectors; unpacked arrays are collections of individual elements.

🔁 Packed vs Unpacked Arrays

The position of dimension declarations relative to the variable name determines packed vs unpacked. This is not cosmetic — it controls how the array participates in expressions.

Packed — dimensions BEFORE the name

bit   [7:0]          byte_val;
logic [31:0]         word;
bit   [3:0][7:0]    bytes4; // 32-bit vector

// Usable as integer in expressions
int sum = bytes4 + 1;
word[7:0] = 8'hAA;    // part-select OK

Unpacked — dimensions AFTER the name

real u    [7:0];      // 8-element array of real
int  arr  [0:9];
int  arr2 [10];       // same as [0:9]

// Element access and whole-array copy
u[0] = 3.14;
int a[5], b[5];
a = b;               // whole-array copy
a[1:3] = b[1:3];    // slice assignment

Rules to remember

// Predefined-width types ARE already one packed dimension
byte    c;   // equivalent to bit [7:0] c
integer i;   // equivalent to logic signed [31:0] i

// If a packed array is declared signed, a part-select is still unsigned
bit signed [7:0] sv = 8'hFF; // -1 (signed)
int p = sv[3:0];               // p = 15 (unsigned part-select)

// Mixed: packed before name, unpacked after name
// 10 entries of 4 bytes (32-bit packed vector each)
bit [3:0][7:0] joe [1:10];
joe[9]        // 32-bit word (4-byte arithmetic on this)
joe[9][3]     // byte [3] within that word
joe[9][3][5:2] // 4-bit part-select

Multiple Dimensions

Arrays can have multiple packed dimensions, multiple unpacked dimensions, or both. The key rule is: rightmost dimension varies most rapidly. Packed dimensions always vary more rapidly than unpacked ones.

// C-style shorthand: [size] = [0:size-1]
int Array[8][32];   // same as int Array[0:7][0:31]

// Arithmetic on the packed dimension:
joe[9] = joe[8] + 1;       // 32-bit add
joe[7][3:2] = joe[6][1:0]; // 2-byte packed copy

// typedef stages for complex multi-dim types
typedef bit [1:5] bsix;        // 5-bit packed type
bsix [1:10] foo5;               // 10 elements of bsix (packed)

typedef bsix mem_t [0:3];       // unpacked array of 4 bsix
mem_t bar [0:7];                // 8×4 bsix elements

// Comma-separated declarations share the same packed dims
bit [7:0][31:0] foo7[1:5][1:10], foo8[0:255];
Invalid index behaviour: Reading from an out-of-bounds index (or one containing X/Z) returns the type’s default value — 'X for 4-state types, '0 for 2-state. Writing to an invalid index is silently ignored. Tools may issue a warning. This differs from C (where out-of-bounds is undefined behaviour), but can still silently mask bugs.

Indexing & Slicing

SystemVerilog adds the ability to select a contiguous range of elements from an unpacked array — called a slice. Part-selects of packed arrays were already in Verilog-2001. Variable-position part-selects with +: and -: are also supported.

// Part-select of packed array (Verilog-2001)
reg [63:0] data;
reg [7:0]  byte2 = data[23:16]; // 8-bit part-select

// Single element of a packed or unpacked array
bit [3:0][7:0] j;
byte k = j[2];              // one 8-bit element of packed j

// Slice of an unpacked array (SV only)
bit busA[7:0][31:0];        // 8 unpacked elements, each 32-bit
int busB[1:0] = busA[7:6];  // slice: 2 contiguous elements

// Variable part-select: position variable, width CONSTANT
int lo8 = data[j +: 8];    // bits j, j+1, … j+7  (8 bits, ascending)
int hi8 = data[j -: 8];    // bits j, j-1, … j-7  (8 bits, descending)

// Slices may only cover one dimension — other dims use single index
int mem[0:7][0:3];
mem[2:4]     // slice of dim 1 — 3 elements
mem[2:4][1]  // column 1 of rows 2-4

🔎 Array Querying System Functions

Seven built-in system functions return dimension and size metadata for any array. These are especially useful in parameterised code where the array bounds are not known statically.

Functions (dimension N, default=1)

$left(arr, N)       // left bound of dim N
$right(arr, N)      // right bound
$low(arr, N)        // min(left, right)
$high(arr, N)       // max(left, right)
$increment(arr, N) // 1 if left>=right, -1 otherwise
$size(arr, N)       // number of elements in dim N
$dimensions(arr)   // total number of dimensions

Example: generic loop over any array

int mem[0:7][0:15];
$size(mem,1)     // 8
$size(mem,2)     // 16
$dimensions(mem) // 2
$low(mem,1)      // 0
$high(mem,2)     // 15

for(int i=0; i<$size(mem,1); i++)
  for(int j=0; j<$size(mem,2); j++)
    mem[i][j] = 0;

🔧 Dynamic Arrays

A dynamic array has one unsized dimension declared with empty brackets. No storage exists until new[] is called. You can resize at any time with optional value-preservation.

// Declaration — empty brackets signal dynamic
bit  [3:0] nibble[];    // dynamic array of 4-bit vectors
integer    mem[];        // dynamic array of integers
string     names[];     // dynamic array of strings

// Allocation
integer addr[];
addr = new[100];           // 100 elements, initialised to 'X

// Grow preserving old values
addr = new[200](addr);     // 200 elements; first 100 copied from old addr

// Initialise with literal at declaration
int d[] = '{10, 20, 30, 40}; // 4 elements

Dynamic Array Methods

new[N]
operator
Allocate N elements, default-initialised. Optional (old) copies existing values first.
size()
function int
Current number of elements. Returns 0 if unallocated. Equivalent to $size(arr,1).
delete()
function void
Empties the array — size drops to 0. Releases all storage.
int ab[] = new[8];
$display(ab.size());          // 8

ab = new[ab.size()*4](ab);  // quadruple, preserving values
$display(ab.size());          // 32

ab.delete();
$display(ab.size());          // 0

// Pattern: allocate after randomisation
class Packet;
  rand int len;
  byte     payload[];
  function void post_randomize();
    payload = new[len];
  endfunction
endclass

Array Assignment Rules

Assignment compatibility depends on whether the arrays are fixed-size or dynamic. The rules are stricter for fixed-size (compile-time checking) and more flexible for dynamic (runtime checking).

int A[10:1], B[0:9], C[24:1];

A = B;   // OK — same element count (10), element-by-element copy
A = C;   // Error — different size (10 vs 24), compile-time error

// Wire arrays assignable to variable arrays of same shape
wire [31:0] W[9:0];
assign W = A;       // OK — continuous assignment to wire array
initial #10 B = W; // OK — procedural read of wire array

// Dynamic target: grows to match source size
int D[];
D = A;   // OK — D becomes 10 elements

// Slice + concat on RHS creates a new dynamic array
string src[1:5] = {"a","b","c","d","e"};
string dst[];
dst = {src[1:3], "hello", src[4:5]};
// dst = {"a","b","c","hello","d","e"}
Dynamic → fixed assignment requires a runtime size check. If a dynamic array is assigned to a fixed-size array and the sizes differ at runtime, a type-check error occurs. The assignment of a fixed-size to a dynamic array is always valid — the dynamic array simply resizes to match.

📋 Arrays as Arguments

By default arrays are passed by value (a copy). Use ref to pass by reference. The formal parameter type must be compatible with the actual argument.

// Fixed-size formal: actual must match dimension count and size
task fun(int a[3:1][3:1]);
endtask
int b1[3:1][3:1]; fun(b1); // OK
int b2[1:3][0:2]; fun(b2); // OK — same size, different range
reg b3[3:1][3:1]; fun(b3); // OK — assignment-compatible type
int b4[3:1][4:1]; fun(b4); // Error — size mismatch (3 vs 4)

// Dynamic formal: accepts any 1-D array of compatible type
task foo(string arr[]);
endtask
string fixed[4];       foo(fixed); // OK
string dyn[] = new[9];foo(dyn);   // OK

// Pass by reference — no copy, caller sees changes
task fill(ref int arr[], input int n);
  arr = new[n];
  foreach(arr[i]) arr[i] = i;
endtask

🔑 Associative Arrays

An associative array stores entries only when written — perfect for sparse data. The index type can be: wildcard (*), string, integer/int, a signed or unsigned packed type, a class handle, or any user-defined type with equality defined.

// Declarations
integer    i_arr   [*];          // wildcard — any integral index
bit [20:0] arr_b   [string];    // string-indexed
event      ev_arr  [myClass];   // class-handle-indexed
int        score   [string];    // typical scoreboard

// Write creates the entry on first use
score["Alice"] = 95;
score["Bob"]   = 82;

// Reading non-existent key returns type default (see table below)
$display(score["Dave"]);   // 'X (integer default for 4-state)

// Literal initialisation with default
integer table[string] = {"Peter":20, "Paul":22, "Mary":23, default:-1};
string  words[int]    = {default: "foo"};

// Assignment: clears target, copies all entries from source
int map2[string] = score; // deep copy

Default Values for Non-Existent Entries

Array element typeValue returned for missing entry
4-state integral (integer, logic)‘X
2-state integral (int, bit)‘0
EnumerationFirst member in the enumeration
string“” (empty string)
classnull
eventnull

Index Type Summary

Index typeExample declarationOrderingNotes
*int a[*]NumericalAny integral index; 4-state X/Z invalid; unsigned
stringint a[string]LexicographicEmpty string “” is a valid key
integerint a[integer]Signed numericalSmaller indices sign-extended to 32 bits
classint a[MyClass]Deterministic but arbitrarynull is a valid key; derived types allowed

🔨 Associative Array Methods

Seven built-in methods allow querying and traversing an associative array. The traversal methods (first, last, next, prev) use a ref parameter to return the index.

num()
function int
Returns number of entries. 0 if empty.
delete([index])
function void
Remove entry at index, or all entries if no arg.
exists(index)
function int
Returns 1 if entry exists at index, else 0.
first(ref idx)
function int
Sets idx to smallest index. Returns 0 if empty.
last(ref idx)
function int
Sets idx to largest index. Returns 0 if empty.
next(ref idx)
function int
Sets idx to next larger index. Returns 0 if none.
prev(ref idx)
function int
Sets idx to next smaller index. Returns 0 if none.
int map[string];
map["hello"] = 1;
map["sad"]   = 2;
map["world"] = 3;

$display(map.num());          // 3

// exists — safe read-before-write
if (map.exists("hello"))
  map["hello"] += 1;
else
  map["hello"] = 0;

// delete one entry
map.delete("sad");            // removes "sad" entry

// Iterate forward (smallest to largest key)
string s;
if (map.first(s))
  do
    $display("%s: %0d", s, map[s]);
  while (map.next(s));

// Iterate backward (largest to smallest)
if (map.last(s))
  do
    $display("%s: %0d", s, map[s]);
  while (map.prev(s));

// delete ALL entries at once
map.delete();                  // map now empty, num()=0
Traversal with truncated index: If the index variable passed to first/last/next/prev is narrower than the actual key (e.g. a byte for a wildcard array with large keys), the function returns -1 and copies only as many bits as fit. Always use an index variable of sufficient width.

🆕 Queues

A queue is a variable-size ordered collection with constant-time access to any element and constant-time insertion/removal at either end. It behaves like a 1-D unpacked array that grows and shrinks automatically. The index $ always refers to the last element.

// Declaration: $ in the brackets signals queue
byte    q1[$];                   // unbounded queue of bytes
string  names[$] = {"Bob"};     // initialised with one element
integer Q[$]    = {3, 2, 7};   // initialised with three elements
bit     q2[$:255];              // bounded: max 256 elements

// Reading
int e = Q[0];   // first element
e     = Q[$];   // last element

// Writing
Q[0] = 99;      // replace first element

Queue Operators — All via Concatenation / Slice Syntax

int Q[$] = {2, 4, 8};  // Q = {2,4,8}

// Append / prepend
Q = {Q, 6};             // {2,4,8,6}   — append
Q = {1, Q};             // {1,2,4,8,6} — prepend

// Delete first / last
Q = Q[1:$];             // {2,4,8,6}   — remove first
Q = Q[0:$-1];           // {2,4,8}     — remove last

// Insert at position pos
int pos=1, val=99;
Q = {Q[0:pos-1], val, Q[pos:$]}; // {2,99,4,8}

// Clear
Q = {};                  // empty queue

Queue Slice Rules

Queue Built-In Methods

size()
function int
Returns number of elements. 0 if empty.
insert(i, item)
= {Q[0:i-1], item, Q[i,$]}
Insert item at position i.
delete(i)
= {Q[0:i-1], Q[i+1,$]}
Delete element at position i.
pop_front()
e=Q[0]; Q=Q[1,$]
Remove and return first element.
pop_back()
e=Q[$]; Q=Q[0,$-1]
Remove and return last element.
push_front(item)
Q = {item, Q}
Insert item at the front.
push_back(item)
Q = {Q, item}
Insert item at the back.
int q[$] = {10, 20, 30};

q.push_back(40);          // {10,20,30,40}
q.push_front(5);          // {5,10,20,30,40}

int front = q.pop_front(); // front=5, q={10,20,30,40}
int back  = q.pop_back();  // back=40, q={10,20,30}

q.insert(1, 15);           // {10,15,20,30}
q.delete(2);               // {10,15,30}

for(int j=0; j<q.size(); j++)
  $display(q[j]);
Queue as a FIFO: The most common queue pattern in testbenches is a FIFO buffer between a producer and a consumer thread. The producer calls push_back(item); the consumer polls size() > 0 and calls pop_front(). For thread-safe producer-consumer, use a mailbox instead (which wraps a queue with semaphore protection).

🔨 Array Manipulation Methods

SystemVerilog provides a powerful set of built-in methods that operate on any unpacked array or queue: locator methods (find elements), ordering methods (sort/reverse/shuffle), and reduction methods (sum/product/xor). All use an optional with (expr) clause to specify a custom comparison or reduction expression.

General syntax
// array.method_name [ (iterator_name) ] [ with (expression) ]
// Default iterator name: item
arr.find()        with (item > 5)
arr.find(x)       with (x > 5)    // custom iterator name
arr.sort()        with (item.key)  // sort by struct field
arr.sum()                           // no with: sum all elements

Locator Methods — Return a Queue of Results

Locator methods traverse the array and return a queue of matching elements or their indexes. The with clause is mandatory for find/find_*; optional for min/max/unique if the element type already has relational operators.

find()
Returns all elements where with expression is true.
find_index()
Returns indexes of all matching elements (as queue of int).
find_first()
Returns the first element satisfying the expression.
find_first_index()
Returns index of the first matching element.
find_last()
Returns the last element satisfying the expression.
find_last_index()
Returns index of the last matching element.
min()
Returns element(s) with minimum value (or min of with expression).
max()
Returns element(s) with maximum value.
unique()
Returns all elements with unique values.
unique_index()
Returns indexes of elements with unique values.
string SA[10];
int    IA[*];
int    qi[$];
string qs[$];

// Find all elements greater than 5
qi = IA.find(x) with (x > 5);

// Indexes of elements equal to 3
qi = IA.find_index with (item == 3);

// First element equal to "Bob"
qs = SA.find_first with (item == "Bob");

// Last element greater than "Z"
qi = SA.find_last_index(s) with (s > "Z");

// Minimum and maximum
qi = IA.min;
qs = SA.max with (item.atoi);   // max by numeric value of string

// Unique values
qs = SA.unique;
qs = SA.unique(s) with (s.tolower); // unique case-insensitive

Ordering Methods — Reorder Elements In-Place

string s[] = {"hello", "sad", "world"};

s.reverse;              // {"world","sad","hello"}

logic [3:0] b = 4'bXZ01;
b.reverse;              // 4'b10ZX  (packed array OK for reverse)

int q[$] = {4, 5, 3, 1};
q.sort;                 // {1,3,4,5} ascending
q.rsort;                // {5,4,3,1} descending

// Sort by struct field
struct { byte red, green, blue; } c[512];
c.sort with (item.red);
c.sort(x) with (x.blue << 8 + x.green); // sort by blue then green

q.shuffle;               // randomise order
reverse and shuffle do not take a with clause. Specifying with on either reverse() or shuffle() is a compile-time error. sort() and rsort() accept an optional with clause; without it, the element type must have relational operators defined.

Reduction Methods — Reduce Array to a Single Value

byte b[] = {1, 2, 3, 4};
int  y;

y = b.sum;                        // 10  (1+2+3+4)
y = b.product;                    // 24  (1*2*3*4)
y = b.and;                        // bitwise AND of all
y = b.or;                         // bitwise OR of all
y = b.xor;                        // XOR of all (checksum!)
y = b.xor with (item + 4);        // 5^6^7^8 = 12

// sum with type coercion: keep result as int not byte
y = int'(b.sum); // avoids byte overflow

Iterator Index Querying

Inside a with clause you can call item.index(dim) to get the current element’s index in a given dimension. This is useful when you need to compare or filter based on position, not just value.

int arr[];
int q[$];

// Find elements equal to their own index position
q = arr.find with (item == item.index);

// Multi-dim: compare mem to another array by same index
int mem[9:0][9:0], mem2[9:0][9:0];
q = mem.find(x) with (x > mem2[x.index(1)][x.index(2)]);
Side effects inside with clauses: The array locator and ordering methods traverse elements in an unspecified order. If the with expression has side effects (increments a counter, writes to a variable), results are unpredictable. Always keep with expressions pure (no side effects).
Data Types☰ SV Series IndexPacked & Unpacked Arrays
Scroll to Top