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.
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
endmoduleCopy 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.
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 releasedOperations 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.
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 binaryKey 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.