SystemVerilog · Module 3
Dynamic Arrays
Runtime sizing with new[], delete(), resize, copy semantics, constraints.
Module 3 · Page 3.2
Dynamic arrays are the one array type the constraint solver can size, which is why they carry randomized payloads that queues cannot. This page is the allocation and resize semantics; the other three types are fixed-size, queues, and associative arrays.
When the Size Is Known Only at Runtime
Static arrays are great until you need to model an AXI burst that can be 1 to 256 beats long, or collect an unknown number of error events during a test run, or build a scoreboard that tracks exactly as many transactions as the stimulus generates. At that point you either pre-allocate the maximum size and waste memory, or you reach for a dynamic array.
Dynamic arrays in SystemVerilog behave like unpacked static arrays in almost every way — you index them with [], iterate with foreach, copy them with =, compare with == — but the size is set at runtime with new[N] and can be changed at any point. Before you call new[], the array is empty and any access produces a fatal error. After delete(), it is empty again.
The critical behavior that trips experienced engineers: when you assign one dynamic array to another with b = a, you get a deep copy — both arrays have independent storage. That sounds safe, but inside class objects the story changes. A class handle holding a dynamic array, when the class handle itself is copied with =, creates a shallow copy of the handle — both variables point to the same underlying data. Modify one and you silently corrupt the other.
How Dynamic Arrays Actually Work
A dynamic array starts as a null reference — no memory is allocated until you call new[N]. That call allocates N elements, each initialized to the default value of the element type (0 for int/bit, X for logic). You can then call new[M] again to resize — if M is larger than the current size, new elements are default-initialized; if smaller, the excess elements are discarded.
The resize operation also supports copying: arr.new[M](arr) allocates M elements and copies as many existing elements as possible — the standard pattern for growing a dynamic array while preserving its contents.
new[N] — Allocate
Creates N elements, all default-initialized. Replaces any previous allocation. The element type determines the initial value (X for logic, 0 for int/bit).
delete() — Free
Releases all memory. Array size becomes 0. Any subsequent access without a new new[] call is a fatal error. Equivalent to new[0].
new[M](src) — Resize + Copy
Allocates M elements and copies from src. Elements beyond src's size are default-initialized. The most common way to grow an array without losing existing data.
$size() — Current Count
Returns the current number of allocated elements. Returns 0 for an unallocated or deleted array. Use this instead of hardcoded sizes to make your code size-independent.
Syntax — Every Operation You'll Use
// ── Declaration (no allocation yet — size = 0) ───────────────────
logic [7:0] bytes []; // unallocated byte array
int scores []; // unallocated int array
string names []; // unallocated string array
// ── Allocation ────────────────────────────────────────────────────
bytes = new[16]; // allocate 16 elements (logic → X-initialized)
scores = new[8]; // allocate 8 elements (int → 0-initialized)
bytes = new[8]('{default:8'hFF}); // allocate + initialize via literal
// ── Resize — keep existing data ───────────────────────────────────
bytes = new[32](bytes); // grow from 16 to 32; old data preserved [0..15]
bytes = new[8](bytes); // shrink to 8; elements [8..15] dropped
// ── Element access — identical to static array ────────────────────
bytes[0] = 8'hAB;
logic [7:0] val = bytes[3];
// ── Size query ────────────────────────────────────────────────────
$size(bytes) // current number of elements
bytes.size() // equivalent method call form
// ── Iteration ─────────────────────────────────────────────────────
foreach (bytes[i])
bytes[i] = i;
// ── Whole-array copy (deep copy) ──────────────────────────────────
logic [7:0] copy [];
copy = bytes; // deep copy — independent storage
copy[0] = 8'hFF; // does NOT affect bytes[0]
// ── Whole-array comparison ────────────────────────────────────────
if (bytes == copy) … // element-wise equal (never X even if elements are X)
if (bytes !== copy) … // case inequality — catches X differences too
// ── Delete ────────────────────────────────────────────────────────
bytes.delete(); // free memory, size becomes 0
// bytes.size() == 0 after delete| Operation | Syntax | Result | Notes |
|---|---|---|---|
| Declare | type name [] | Size = 0, unallocated | No memory reserved |
| Allocate | name = new[N] | N elements, default-init | Previous data discarded |
| Allocate + init | name = new[N](src) | N elements, copied from src | src can be same array (resize) |
| Get size | $size(name) or name.size() | int — current element count | Returns 0 if deleted/unallocated |
| Delete | name.delete() | Size = 0, memory freed | Equivalent to new[0] |
| Copy | dst = src | Deep copy of all elements | Independent storage after copy |
Visual — Lifecycle and Memory Layout
Allocation Lifecycle
| Step | Code | Array state | $size() | Access result |
|---|---|---|---|---|
| 1. Declared | int arr [] | Null / empty | 0 | Fatal error |
| 2. Allocated | arr = new[4] | [0, 0, 0, 0] | 4 | Returns element |
| 3. Written | foreach fill | [10, 20, 30, 40] | 4 | Returns written value |
| 4. Grown | arr = new[6](arr) | [10, 20, 30, 40, 0, 0] | 6 | Old data preserved at [0..3] |
| 5. Shrunk | arr = new[2](arr) | [10, 20] | 2 | [2..5] gone permanently |
| 6. Deleted | arr.delete() | Null / empty | 0 | Fatal error |
Deep Copy vs Class Handle Copy
| Scenario | Code | What happens | Are they independent? |
|---|---|---|---|
| Standalone dynamic array copy | b = a (both are int arr[]) | Full deep copy — each element of a copied into new storage for b | Yes — modifying b[0] does NOT change a[0] |
| Class handle copy (contains dynamic array) | obj2 = obj1 (class handles) | Shallow copy — both handles point to the same object and its array field | No — obj2.arr[0] IS obj1.arr[0] |
| Proper class deep copy | obj2 = new obj1 or custom copy() | Creates new object, copies all fields including re-allocating the dynamic array | Yes — independent after explicit deep copy |
Resize With Data Preservation
arr = new[6](arr) — growing from 4 to 6 elements:
| Index | [0] | [1] | [2] | [3] | [4] | [5] |
|---|---|---|---|---|---|---|
| Before (size 4) | 10 | 20 | 30 | 40 | — | — |
| After (size 6) | 10 ✓ | 20 ✓ | 30 ✓ | 40 ✓ | 0 (new) | 0 (new) |
Green = preserved from original. Orange = new elements, default-initialized to 0 (int type).
Code Examples — Runtime Sizing to Verification Patterns
Example 1 — Beginner: Allocate, Fill, Resize, Delete
module tb_dyn_basics;
int arr [];
initial begin
// Every declaration in a begin-end block must precede the first statement
// (IEEE 1800 §9.3.1), so `copy` is declared here rather than beside its use.
int copy [];
// ── Allocate ──────────────────────────────────────────────────
arr = new[4];
$display("After new[4]: size = %0d", $size(arr)); // 4
// ── Fill ──────────────────────────────────────────────────────
foreach (arr[i]) arr[i] = (i + 1) * 10;
$display("Filled: %p", arr); // '{10, 20, 30, 40}
// ── Grow and preserve ─────────────────────────────────────────
arr = new[6](arr);
$display("After grow to 6: %p", arr); // '{10, 20, 30, 40, 0, 0}
// ── Shrink ────────────────────────────────────────────────────
arr = new[2](arr);
$display("After shrink to 2: %p", arr); // '{10, 20}
// ── Deep copy: b = a → independent ─────────────────────────
copy = arr;
copy[0] = 999;
$display("arr[0]=%0d copy[0]=%0d", arr[0], copy[0]); // 10 999 — independent
// ── Delete ────────────────────────────────────────────────────
arr.delete();
$display("After delete: size = %0d", $size(arr)); // 0
$finish;
end
endmoduleExpected output:
After new[4]: size = 4
Filled: '{10, 20, 30, 40}
After grow to 6: '{10, 20, 30, 40, 0, 0}
After shrink to 2: '{10, 20}
arr[0]=10 copy[0]=999
After delete: size = 0Example 2 — Intermediate: Variable-Length AXI Burst Driver
class axi_write_txn;
rand logic [31:0] addr;
rand int unsigned burst_len; // 1 to 16 beats
rand logic [31:0] data []; // size unknown until constrained
constraint len_range { burst_len inside {[1:16]}; }
constraint data_size { data.size() == burst_len; } // dynamic size constraint
constraint addr_align { addr[1:0] == 2'b00; }
// Solver calls new[] automatically when a dynamic array size is constrained
function void print();
$display("AXI WRITE addr=0x%08h len=%0d", addr, burst_len);
foreach (data[i])
$display(" beat[%0d] = 0x%08h", i, data[i]);
endfunction
endclass
module tb_axi_dyn;
initial begin
axi_write_txn txn = new();
repeat (3) begin
if (!txn.randomize())
$fatal(1, "Randomize failed");
txn.print();
end
$finish;
end
endmoduleExample 3 — Verification: Dynamic Scoreboard Event Log
module tb_event_log;
typedef struct {
int timestamp;
logic [7:0] opcode;
logic [31:0] addr;
} event_t;
event_t log []; // starts empty
int log_count = 0;
// Append one event — resizes the array by +1 each call
task automatic log_event(input int ts, input logic [7:0] op, input logic [31:0] a);
log = new[log_count + 1](log); // grow by 1, preserve existing entries
log[log_count] = '{ts, op, a};
log_count++;
endtask
// Print all logged events
task automatic dump_log();
$display("=== Event Log (%0d entries) ===", log_count);
foreach (log[i])
$display(" [%0d] t=%0d op=0x%02h addr=0x%08h",
i, log[i].timestamp, log[i].opcode, log[i].addr);
endtask
initial begin
log_event(100, 8'h10, 32'h0000_1000);
log_event(200, 8'h20, 32'h0000_2000);
log_event(300, 8'hFF, 32'hFFFF_FFFF);
dump_log();
$display("Final log size: %0d", $size(log));
$finish;
end
endmoduleExpected output:
=== Event Log (3 entries) ===
[0] t=100 op=0x10 addr=0x00001000
[1] t=200 op=0x20 addr=0x00002000
[2] t=300 op=0xFF addr=0xFFFFFFFF
Final log size: 3Example 4 — Corner Case: Class Handle vs Array Deep Copy Trap
class packet;
int payload [];
function new(int sz); payload = new[sz]; endfunction
endclass
module tb_copy_trap;
initial begin
// ── Case 1: Standalone dynamic arrays — DEEP copy ─────────────
int a [] = new[3]('{1, 2, 3});
int b [];
b = a; // deep copy of array data
b[0] = 99;
$display("Standalone: a[0]=%0d b[0]=%0d", a[0], b[0]); // 1 99 — independent ✓
// ── Case 2: Class handle copy — SHALLOW (DANGEROUS!) ─────────
packet p1 = new(3);
p1.payload = '{10, 20, 30};
packet p2;
p2 = p1; // SHALLOW: p2 points to same object as p1
p2.payload[0] = 99;
$display("Class handle: p1.payload[0]=%0d p2.payload[0]=%0d",
p1.payload[0], p2.payload[0]); // 99 99 — SAME! Bug!
// ── FIX: use new to create an independent copy ────────────────
packet p3 = new p1; // copy constructor — shallow copy of fields
// But p3.payload still shares storage with p1.payload!
// True deep copy requires explicitly re-allocating the array:
p3.payload = new[p1.payload.size()](p1.payload);
p3.payload[0] = 999;
$display("Deep copy: p1.payload[0]=%0d p3.payload[0]=%0d",
p1.payload[0], p3.payload[0]); // 99 999 — independent ✓
$finish;
end
endmoduleExpected output:
Standalone: a[0]=1 b[0]=99
Class handle: p1.payload[0]=99 p2.payload[0]=99
Deep copy: p1.payload[0]=99 p3.payload[0]=999Runnable Proof — What Resizing Actually Does to Your Data
new[N] and new[N](arr) differ by six characters and by whether your data survives. That is worth proving rather than asserting, because the silently-destructive form is the shorter one — so the failure mode is that the easier thing to type is the one that loses data.
// ─────────────────────────────────────────────────────────────────────────────
// Proves four dynamic-array semantics that are easy to state and easy to get
// backwards:
// 1. new[N] discards the old contents (default-initialised)
// 2. new[N](arr) preserves them, default-fills (the grow idiom)
// 3. new[N](arr) with N < size truncates (keeps the first N)
// 4. b = a is a DEEP copy for standalone arrays (not aliasing)
//
// vcs -sverilog dyn_resize_proof.sv && ./simv | xrun -sv dyn_resize_proof.sv
// ─────────────────────────────────────────────────────────────────────────────
module dyn_resize_proof;
int errors = 0;
// Compare a dynamic array against an expected list; report and count.
function automatic void check(string label, const ref int got [], input int exp []);
if (got.size() != exp.size()) begin
errors++;
$display(" ** FAIL %-22s size %0d, expected %0d", label, got.size(), exp.size());
return;
end
foreach (exp[i])
if (got[i] !== exp[i]) begin
errors++;
$display(" ** FAIL %-22s [%0d] = %0d, expected %0d", label, i, got[i], exp[i]);
return;
end
$display(" ok %-22s %p", label, got);
endfunction
initial begin
int arr [];
int copy [];
arr = new[4];
foreach (arr[i]) arr[i] = (i + 1) * 10; // 10,20,30,40
check("initial", arr, '{10,20,30,40});
// ── 1. new[N] without the argument: reallocation, not resize ──────────
arr = new[6];
check("new[6] discards", arr, '{0,0,0,0,0,0});
// ── 2. new[N](arr): the grow idiom ───────────────────────────────────
arr = new[4];
foreach (arr[i]) arr[i] = (i + 1) * 10;
arr = new[6](arr);
check("new[6](arr) preserves", arr, '{10,20,30,40,0,0});
// ── 3. Shrinking truncates from the TAIL, keeping the head ───────────
arr = new[2](arr);
check("new[2](arr) truncates", arr, '{10,20});
// ── 4. Standalone array assignment is a DEEP copy ─────────────────────
// (Contrast Example 4: a dynamic array INSIDE a class object is shared
// when the class HANDLE is copied - the array is not what aliases.)
copy = arr;
copy[0] = 999;
check("source after deep copy", arr, '{10,20});
check("copy after mutation", copy, '{999,20});
// ── 5. delete() empties it; size() is then 0 and any index is fatal ──
arr.delete();
if (arr.size() != 0) begin
errors++;
$display(" ** FAIL delete() left size %0d", arr.size());
end else
$display(" ok delete() size=0");
if (errors == 0) $display("\n [PASS] all dynamic-array resize semantics held");
else $display("\n [FAIL] %0d check(s) failed", errors);
$finish;
end
endmoduleExpected output.
ok initial '{10, 20, 30, 40}
ok new[6] discards '{0, 0, 0, 0, 0, 0}
ok new[6](arr) preserves '{10, 20, 30, 40, 0, 0}
ok new[2](arr) truncates '{10, 20}
ok source after deep copy '{10, 20}
ok copy after mutation '{999, 20}
ok delete() size=0
[PASS] all dynamic-array resize semantics heldThe line to take away is the second one. new[6] did not fail, warn, or complain — it returned a perfectly valid six-element array of zeros, and any code downstream sees a correctly-sized array containing wrong data. There is no runtime signal at all, which is what separates this from the out-of-bounds crash that dominates the rest of this page: bounds errors are loud, resize errors are silent.
Simulation Behavior — What Really Happens
Access Before Allocation: Fatal, Not X
Accessing an unallocated or deleted dynamic array is a fatal simulation error, not a silent X return. This is the key difference from static arrays: static arrays return X on out-of-bounds; dynamic arrays crash the simulation entirely. The simulator throws something like "Fatal: index out of range for dynamic array of size 0". The practical implication: always check $size(arr) > 0 before accessing, especially in reusable task/function code that might be called before the test has allocated the array.
Constraint Solver and Dynamic Array Sizing
When you constrain data.size() inside a class, the constraint solver automatically allocates the array to the solved size before randomizing the elements. You do not need to call new[] manually before randomize(). However, if you access data before the first randomize() call — say, in the class constructor or a pre-randomize callback — the array is still unallocated and any access is fatal.
| Event | Dynamic array behavior | Static array behavior |
|---|---|---|
| Access before allocation | Fatal error — simulation stops | Returns X (warning only) |
| Access after delete() | Fatal error | N/A — no delete on static |
| Out-of-bounds read | Fatal error | Returns X (warning) |
| Out-of-bounds write | Fatal error | Silently ignored |
| Uninitialized elements (logic) | X (same as static) | X |
| Uninitialized elements (int) | 0 (same as static) | 0 |
Where Dynamic Arrays Belong in Real Verification
// ── 1. TRANSACTION CLASS: variable-length burst payload ───────────
class axi_burst;
rand int unsigned len;
rand logic [31:0] data [];
constraint c { len inside {[1:256]}; data.size() == len; }
endclass
// ── 2. SCOREBOARD: collect all received transactions, then compare ─
logic [31:0] rcv_data []; // grows as beats arrive
int rcv_count = 0;
// In monitor task — called per beat:
// rcv_data = new[rcv_count+1](rcv_data);
// rcv_data[rcv_count++] = captured_beat;
// End of burst — compare against expected:
// if (rcv_data !== exp_data) $error("burst mismatch");
// ── 3. CONSTRAINT: size-dependent payload pattern ─────────────────
class pkt_with_crc;
rand int unsigned payload_len;
rand logic [7:0] payload [];
rand logic [15:0] crc;
constraint len_c { payload_len inside {[4:64]}; }
constraint size_c { payload.size() == payload_len; }
// post-randomize: compute real CRC from payload
function void post_randomize();
crc = 16'h0;
foreach (payload[i]) crc ^= {8'h0, payload[i]};
endfunction
endclass
// ── 4. UVM sequence item: standard pattern ────────────────────────
// class my_seq_item extends uvm_sequence_item;
// rand logic [7:0] data [];
// rand int unsigned len;
// constraint data_sz { data.size() == len; }
// endclass
// ── 5. COVERAGE: collect unique values seen ───────────────────────
int seen_values []; // grows as new values are observed
function void record_value(int v);
foreach (seen_values[i])
if (seen_values[i] == v) return; // already recorded
seen_values = new[seen_values.size()+1](seen_values);
seen_values[seen_values.size()-1] = v;
endfunctionBugs Engineers Hit With Dynamic Arrays
Bug 1 — Access Before Allocation: Fatal Crash
int buf []; // declared but not allocated — size = 0
// BUGGY: accessing index 0 on a size-0 array
buf[0] = 42; // FATAL: "dynamic array index out of range" — simulation crashes
// Also fatal:
foreach (buf[i]) $display(buf[i]); // safe only if size>0; empty array = no iterations
// foreach is safe — it just loops 0 times on a size-0 array
// But buf[0] directly when size=0 is always fatal
// FIXED: always allocate before accessing by index
buf = new[4]; // allocate first
buf[0] = 42; // now safe
// DEFENSIVE: check before access
if ($size(buf) > 0) buf[0] = 42;Bug 2 — Resize Without Preserving: Data Silently Lost
A scoreboard's expected data becomes all zeros after the queue grows past its initial size
SILENT-REALLOCATIONA scoreboard accumulates expected transactions in a dynamic array, growing it as traffic arrives. Short tests pass. Any test that pushes past the initial allocation reports every comparison as a mismatch, and the expected side always reads as 0 — never garbage, never stale, always exactly zero.
Expected. Growing the array preserves what was already recorded and extends it.
Actual. The array is the right size and contains nothing.
// The growth step, as written:
if (count == data.size())
data = new[data.size() * 2]; // ← no (data) argument
data[count++] = txn.payload;The value pattern is the diagnosis, and it is unusually specific:
- All zeros, not garbage. Reallocated storage is default-initialised, so a two-state type reads exactly
0and a four-state type reads exactly'x. Corrupted or stale memory would give arbitrary values. Uniform defaults point at reallocation, not at a pointer bug. - Correlates with size, not with data. The failure begins precisely when the array first grows. Tests that never cross the initial allocation pass completely — which is why this survives short regressions.
- The size is right.
data.size()returns exactly what you expect, so every length check andforeachbehaves normally. Nothing downstream can detect the loss.
Together those three rule out aliasing, out-of-bounds, and race conditions, and leave only "the storage was replaced."
new[N] allocates a new array; it does not resize the existing one. The old storage is released and every element of the new array is set to the type's default. The optional argument — new[N](data) — is what copies the old contents across.
The reason this reaches production is that the destructive form is not an error in any sense the language can see. It is a legal, meaningful operation (allocate a fresh array of N), it produces a correctly-sized array, and it is shorter to type than the preserving form. Nothing warns, because nothing is wrong.
// Pass the existing array as the argument - the contents are copied in, and
// any elements beyond the old size are default-initialised.
if (count == data.size())
data = new[data.size() * 2](data); // ← the (data) IS the fix
data[count++] = txn.payload;If growth is frequent and one-at-a-time, reconsider the container rather than the call: every new[N](data) copies the whole array, so repeated growth is O(N²). A queue gives O(1) push_back and is the better structure for accumulate-then-process. Dynamic arrays earn their place where the size is decided once — or where the constraint solver must control it, which queues cannot do.
Treat a bare new[N] on a non-empty array as a code-review finding. It is correct only when discarding the contents is intended, and that intent deserves a comment saying so.
Then make the loss detectable: after any growth, assert that a known-populated element still holds its value (assert(data[0] === first_seen)). It costs one line and converts a silent, size-dependent corruption into an immediate failure at the moment of the reallocation — which is the only point where the information still exists to debug it.
Bug 3 — Class Handle Aliasing: Modifying the "Copy" Corrupts the Original
class txn;
logic [31:0] data [];
endclass
txn sent_txn = new();
sent_txn.data = new[4]('{1,2,3,4});
// BUGGY: scoreboard saves a "copy" to compare later
txn saved = sent_txn; // SHALLOW COPY — both point to same object!
// Driver modifies the transaction for the next beat
sent_txn.data[0] = 99; // also silently modifies saved.data[0]!
$display("saved.data[0] = %0d", saved.data[0]); // 99 — WRONG, expected 1
// FIXED: allocate new object + copy array explicitly
txn saved_fixed = new();
saved_fixed.data = new[sent_txn.data.size()](sent_txn.data);
sent_txn.data[0] = 99;
$display("saved_fixed.data[0] = %0d", saved_fixed.data[0]); // 1 — correctBug 4 — Constraining data.size() Without Understanding Solver Allocation
class bad_txn;
rand logic [7:0] data [];
rand int unsigned len;
// BUGGY: no constraint linking len to data.size()
// Solver picks random len and random data size independently
// len=5 but data might have size=3 — they are unrelated!
constraint len_c { len inside {[1:16]}; }
// Missing: constraint size_c { data.size() == len; }
endclass
// CORRECT: link them explicitly
class good_txn;
rand logic [7:0] data [];
rand int unsigned len;
constraint len_c { len inside {[1:16]}; }
constraint size_c { data.size() == len; } // solver now allocates correctly
endclassInterview Questions
int arr []; arr = new[10]; — int is a two-state type, so new[N] default-initialises every element to 0 automatically. For a four-state type such as logic, the default is 'x, so an array you intend to read before writing must be initialised explicitly.
The general rule worth carrying: new[N] always default-initialises, and the default depends on the type, not on the array. That is why int arrays look "already zeroed" and logic arrays appear to contain garbage — both are behaving identically.
Best Practices & Coding Guidelines
Always allocate before access
Dynamic arrays start at size 0. Any indexed access before new[] is fatal. Add a size guard or allocate in the constructor. Never assume allocation happened elsewhere.
Use new[N](arr) for resize
When growing or shrinking, always pass the existing array as the copy source unless you explicitly want to discard all data. new[N] alone is a destructive operation.
Implement clone() in transaction classes
Any class with a dynamic array field needs an explicit deep copy method. Never rely on obj2 = obj1 for a scoreboard — it shares the handle, not the data.
Never use in synthesizable RTL
Dynamic arrays require runtime memory allocation which has no hardware equivalent. Keep them strictly in testbench, UVM components, and simulation-only utility classes.
| Task | Correct approach | Common mistake |
|---|---|---|
| Grow array, keep data | arr = new[N](arr) | arr = new[N] — destroys existing data |
| Store transaction in scoreboard | Clone the object, re-allocate its array field | Store the handle directly — aliasing bug |
| Check if allocated | if ($size(arr) > 0) | No check — fatal crash on empty array |
| Constrained random with variable size | constraint { data.size() == len; } | No size constraint — solver picks arbitrary size |
| Iterate all elements | foreach (arr[i]) — safe on empty array | for (int i=0; i<N; i++) with hardcoded N |
Where This Is Specified
Dynamic arrays are defined in IEEE Std 1800 (SystemVerilog), clause 7 — Aggregate data types: the type and the new[] constructor in §7.5, the size() and delete() methods in §7.5.1-§7.5.3, and array assignment/copy semantics in §7.6. The IEEE Standards Association listing is the primary source.
The clause worth reading rather than paraphrasing is the new[] definition, because it states plainly what this page's most expensive bug depends on: new[N] creates a new array and the optional argument specifies an array whose contents are copied into it. Read that way, new[6] losing your data is not surprising behaviour — it is the documented behaviour, and the surprise only exists if you read new[] as "resize" rather than "allocate."
Two supporting rules: array assignment for standalone dynamic arrays copies contents (§7.6), which is why b = a does not alias — while copying a class handle copies only the reference, an object rule from clause 8. And the declaration placement corrected in Example 1 is §9.3.1: declarations in a begin-end block precede all statements.
Related lessons. The other three array types are fixed-size arrays, queues, and associative arrays; shared operations are array methods. The solver interaction that makes dynamic arrays the payload type of choice is randomising arrays and constraint blocks. For the handle-versus-array distinction behind Example 4, see classes and handles.
Summary
Dynamic arrays solve the "I don't know the size at compile time" problem cleanly. The new[N] / delete() lifecycle is simple; the element access syntax is identical to static arrays; the constraint solver handles size constraints natively. The two things that produce real bugs: forgetting to allocate before access (which crashes, loudly), and the class-handle shallow-copy trap (which corrupts silently).
- Allocate with
new[N], release withdelete(). Any access on an unallocated array is a fatal error. - Resize with
new[M](arr)to preserve existing data.new[M]alone discards everything. - Standalone array assignment is a deep copy. Class handle assignment is shallow. Know which one you have.
- Constraint
data.size() == lenlets the solver control the size. Without it, the array size and your length variable are unrelated. - Verification-only. No synthesis tool supports dynamic arrays — they belong exclusively in the testbench.
Part of SystemVerilog Fundamentals·Arrays·Lesson 15 of 53
View program