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.
SystemVerilog provides four distinct array kinds. Fixed-size arrays come from Verilog-2001; the other three — dynamic, associative, and queue — are new in SystemVerilog.
int arr[8];new[]. Can grow with value-preserving copy. Used for variable-length payloads.int dyn[];int aa[string];int q[$];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.
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
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
bit, logic, reg, net types) or other packed arrays/structs.real, class handles, events, and other arrays.byte, shortint, int, longint, integer) already occupy one packed dimension; you cannot add extra packed dimensions on top.// 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
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];
'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.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
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.
$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
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;
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
(old) copies existing values first.$size(arr,1).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
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"}
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
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
| Array element type | Value returned for missing entry |
|---|---|
4-state integral (integer, logic) | ‘X |
2-state integral (int, bit) | ‘0 |
| Enumeration | First member in the enumeration |
| string | “” (empty string) |
| class | null |
| event | null |
| Index type | Example declaration | Ordering | Notes |
|---|---|---|---|
| * | int a[*] | Numerical | Any integral index; 4-state X/Z invalid; unsigned |
| string | int a[string] | Lexicographic | Empty string “” is a valid key |
| integer | int a[integer] | Signed numerical | Smaller indices sign-extended to 32 bits |
| class | int a[MyClass] | Deterministic but arbitrary | null is a valid key; derived types allowed |
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.
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
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.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
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
Q[a:b] yields b−a+1 elements. If a > b, yields empty queue {}.Q[n:n] is equivalent to {Q[n]} — one-element queue.Q[n:n] yields {}.Q[a:b] where a < 0 is treated as Q[0:b]; where b > $ is treated as Q[a:$].$+1 is legal (appends). Writing past any bounded limit is ignored with a warning.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]);
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).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.
// 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 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.
with expression is true.with expression).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
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
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.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
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)]);
with expression has side effects (increments a counter, writes to a variable), results are unpredictable. Always keep with expressions pure (no side effects).