Skip to content

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

SystemVerilog — Dynamic Array Syntax
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
OperationSyntaxResultNotes
Declaretype name []Size = 0, unallocatedNo memory reserved
Allocatename = new[N]N elements, default-initPrevious data discarded
Allocate + initname = new[N](src)N elements, copied from srcsrc can be same array (resize)
Get size$size(name) or name.size()int — current element countReturns 0 if deleted/unallocated
Deletename.delete()Size = 0, memory freedEquivalent to new[0]
Copydst = srcDeep copy of all elementsIndependent storage after copy

Visual — Lifecycle and Memory Layout

Allocation Lifecycle

StepCodeArray state$size()Access result
1. Declaredint arr []Null / empty0Fatal error
2. Allocatedarr = new[4][0, 0, 0, 0]4Returns element
3. Writtenforeach fill[10, 20, 30, 40]4Returns written value
4. Grownarr = new[6](arr)[10, 20, 30, 40, 0, 0]6Old data preserved at [0..3]
5. Shrunkarr = new[2](arr)[10, 20]2[2..5] gone permanently
6. Deletedarr.delete()Null / empty0Fatal error

Deep Copy vs Class Handle Copy

ScenarioCodeWhat happensAre they independent?
Standalone dynamic array copyb = a (both are int arr[])Full deep copy — each element of a copied into new storage for bYes — 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 fieldNo — obj2.arr[0] IS obj1.arr[0]
Proper class deep copyobj2 = new obj1 or custom copy()Creates new object, copies all fields including re-allocating the dynamic arrayYes — 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)10203040
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

Example 1 — Dynamic Array Basics
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
 
endmodule

Expected output:

Simulation Output
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 = 0

Example 2 — Intermediate: Variable-Length AXI Burst Driver

Example 2 — AXI Burst with Dynamic Data Array
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endmodule

Example 3 — Verification: Dynamic Scoreboard Event Log

Example 3 — Growing Event Log Using Dynamic Array
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
 
endmodule

Expected output:

Simulation Output
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== 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: 3

Example 4 — Corner Case: Class Handle vs Array Deep Copy Trap

Example 4 — Shallow vs Deep Copy Trap
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
 
endmodule

Expected output:

Simulation Output
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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]=999

Runnable 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.

Starting from a four-element array, new of six discards all data giving six defaults, new of six with the old array preserves the four values and default-fills two, and new of two with the old array keeps the first two values.arr = 10,20,30,40size 4new[6]0,0,0,0,0,0 - all data lostnew[6](arr)10,20,30,40,0,0 - preservednew[2](arr)10,20 - tail discardedsilentno error, no warningintendedthe grow idiomintendedtruncation is explicit12
Figure — the two resize forms. new[6] allocates six fresh elements and the old storage is released, so every value is the type default. new[6](arr) allocates six and copies the old contents in, so indices 0-3 survive and 4-5 are default. Shrinking with new[2](arr) keeps the first two and discards the rest. The argument is the whole difference: without it the array is reallocated, not resized.
dyn_resize_proof.sv — self-checking; run it once and the semantics stop being a memory test
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ─────────────────────────────────────────────────────────────────────────────
//  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
 
endmodule

Expected output.

simulation log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
     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 held

The 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.

EventDynamic array behaviorStatic array behavior
Access before allocationFatal error — simulation stopsReturns X (warning only)
Access after delete()Fatal errorN/A — no delete on static
Out-of-bounds readFatal errorReturns X (warning)
Out-of-bounds writeFatal errorSilently ignored
Uninitialized elements (logic)X (same as static)X
Uninitialized elements (int)0 (same as static)0

Where Dynamic Arrays Belong in Real Verification

Verification Patterns Using Dynamic Arrays
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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;
endfunction

Bugs Engineers Hit With Dynamic Arrays

Bug 1 — Access Before Allocation: Fatal Crash

Bug 1 — Unallocated Array Access
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

2

A scoreboard's expected data becomes all zeros after the queue grows past its initial size

SILENT-REALLOCATION
Observed Symptom

A 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 vs Actual

Expected. Growing the array preserves what was already recorded and extends it.

Actual. The array is the right size and contains nothing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The growth step, as written:
if (count == data.size())
  data = new[data.size() * 2];      // ← no (data) argument
data[count++] = txn.payload;
Diagnostic

The value pattern is the diagnosis, and it is unusually specific:

  1. All zeros, not garbage. Reallocated storage is default-initialised, so a two-state type reads exactly 0 and 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.
  2. 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.
  3. The size is right. data.size() returns exactly what you expect, so every length check and foreach behaves 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."

Root Cause

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.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.

Prevention

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

Bug 3 — Shallow Copy Aliasing in Scoreboard
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 — correct

Bug 4 — Constraining data.size() Without Understanding Solver Allocation

Bug 4 — Dynamic Array Size Constraint Pitfall
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endclass

Interview 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.

TaskCorrect approachCommon mistake
Grow array, keep dataarr = new[N](arr)arr = new[N] — destroys existing data
Store transaction in scoreboardClone the object, re-allocate its array fieldStore the handle directly — aliasing bug
Check if allocatedif ($size(arr) > 0)No check — fatal crash on empty array
Constrained random with variable sizeconstraint { data.size() == len; }No size constraint — solver picks arbitrary size
Iterate all elementsforeach (arr[i]) — safe on empty arrayfor (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 with delete(). 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() == len lets 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

Continue learning