Part 1 · Language Foundations · Intermediate

Dynamic Arrays

Runtime allocation with new[n], resize preserving contents, reference copy semantics, delete(), and sizing from plusargs.

Allocation and the new[] operator

A dynamic array is declared with empty square brackets — int data[] — and holds no storage at all until you call new[n]. Before allocation its size is 0 and any index access is an out-of-bounds error (simulators warn and return the default value; writes are discarded). This deferred sizing is the whole point: the testbench can decide packet length, memory depth, or iteration count at runtime instead of baking it into the type.

Calling new[n] again on an already-allocated array throws away the old contents and zero-fills. To grow while preserving the existing elements you pass the old array as a constructor argument: data = new[data.size() * 2](data). The simulator allocates fresh storage, copies the old elements into the low indices, and default-initializes the rest. This copy is O(n) — repeatedly growing by one element inside a loop is quadratic, which is exactly when you should have used a queue instead.

systemverilog
module dyn_array_demo;
  int data[];          // size 0, no storage yet
  int snapshot[];

  initial begin
    data = new[4];                 // allocate 4 elements, all 0
    foreach (data[i]) data[i] = i * 10;

    data = new[8](data);           // GROW to 8, old 4 preserved
    $display("size=%0d data[3]=%0d data[7]=%0d",
             data.size(), data[3], data[7]); // 8, 30, 0

    data = new[2](data);           // SHRINK: keeps first 2 only
    $display("after shrink size=%0d", data.size());

    snapshot = new[data.size()](data); // explicit deep copy
    data.delete();                 // size back to 0, storage freed
    $display("data=%0d snapshot=%0d", data.size(), snapshot.size());
  end
endmodule

Copy semantics — assignment vs new[](src)

Unlike class handles, dynamic-array assignment b = a performs a value copy : the simulator allocates new storage for b and copies every element, so later writes to one array never affect the other. This surprises engineers coming from class-based code where assignment copies a handle. The new[n](src) form is the variant you use when the destination size must differ from the source — copy and resize in one step.

diagram
DYNAMIC ARRAY RESIZE — new[8](data) preserves contents

  before:  data ──► ┌────┬────┬────┬────┐   size()==4
                    │ 0  │ 10 │ 20 │ 30 │
                    └────┴────┴────┴────┘
                            │ copy old elements
                            ▼
  after:   data ──► ┌────┬────┬────┬────┬────┬────┬────┬────┐
                    │ 0  │ 10 │ 20 │ 30 │ 0  │ 0  │ 0  │ 0  │
                    └────┴────┴────┴────┴────┴────┴────┴────┘
                      preserved (O(n) copy)    default-init

  b = a;         VALUE copy: independent storage afterwards
  data = new[8]  old contents DISCARDED (no src argument)
  data.delete()  size 0, storage released

Operations summary

  • size() — current element count; 0 before allocation and after delete().

  • new[n] — allocate n default-initialized elements, discarding any previous contents.

  • new[n](src) — allocate n elements, copying min(n, src.size()) elements from src.

  • delete() — release all storage; the variable itself remains usable for a later new[].

  • b = a — full value copy with automatic resize of b to a's size.


Sizing from plusargs and configuration

The classic testbench use is sizing a structure from a command-line plusarg or a configuration object, so one compiled image runs many test shapes. Because allocation happens at time zero in an initial block, no recompile is needed to change depth between regression runs — a practical lever interviewers expect you to mention when asked why dynamic arrays exist at all.

systemverilog
class mem_model;
  byte mem[];          // depth decided at runtime
  function new(int depth);
    mem = new[depth];
  endfunction
endclass

module tb;
  int depth;
  mem_model m;

  initial begin
    if (!$value$plusargs("DEPTH=%d", depth))
      depth = 1024;                 // default when plusarg absent
    m = new(depth);
    $display("memory model depth = %0d bytes", m.mem.size());
  end
endmodule
// run: simv +DEPTH=65536   → 64 KB model, same binary

Key takeaways

  • Dynamic arrays defer sizing to runtime — declare with [], allocate with new[n].

  • new[n](old) grows or shrinks while preserving contents; bare new[n] discards them.

  • Assignment between dynamic arrays is a value copy, not a handle copy.

  • Size from plusargs/config objects so one binary covers many test shapes.

Common pitfalls

  • Indexing before new[] — size is 0, reads return default, writes are silently dropped (with a warning).

  • Calling new[n] without the (old) argument when you meant to resize — contents vanish.

  • Growing by one element per loop iteration — O(n²); use a queue for incremental growth.

  • Assuming b = a aliases the same storage like class handles — it does not; it deep-copies.