SystemVerilog Arrays: Packed, Dynamic and Associative

Arrays are how a verification environment holds groups of related values: a bus word, a slice of memory, a scoreboard of expected results, or a table keyed by address. Plain Verilog gave you only fixed-size arrays. SystemVerilog adds several distinct array kinds, and a good deal of day-to-day testbench debugging comes down to picking the right one and knowing its built-in operations. This guide builds up the family from first principles, with code you can run yourself.

Packed versus unpacked: the core distinction

Every SystemVerilog array is either packed or unpacked, and the difference is decided purely by where the dimension sits relative to the variable name. A dimension written to the left of the name is packed; a dimension written to the right is unpacked.

A packed dimension describes a single contiguous vector of bits. Because the bits are adjacent, you can do arithmetic on the whole thing, take bit-selects and part-selects, and copy it as one value. An unpacked dimension describes a collection of separate elements laid out like a C array or a hardware memory; you address one element at a time, and the elements need not be stored contiguously.

AspectPacked (left of name)Unpacked (right of name)
Declarationbit [31:0] w;byte b [4];
Stored asOne contiguous bit vectorIndependent elements
Bit / part selectsYes (w[15:8])No, element access only
Arithmetic on the wholeYesNo
Typical useRegisters, buses, flagsMemories, buffers, scoreboards

A worked example: packed, unpacked and multidimensional

You can also combine the two. bit [7:0] matrix [3][4] is an unpacked 3-by-4 grid whose every element is an 8-bit packed vector. The example below declares one packed word, one unpacked array of bytes, and a two-dimensional unpacked array, then shows the operations each one allows. Note the foreach (matrix[r, c]) form, which walks both unpacked dimensions at once.

module tb;
  bit [31:0] packed_word;
  byte unpacked_bytes [4];
  bit [7:0] matrix [3][4];
  initial begin
    packed_word = 32'hDEAD_BEEF;
    $display("packed_word      = %h", packed_word);
    $display("byte[1] (15:8)   = %h", packed_word[15:8]);
    unpacked_bytes = '{8'h11, 8'h22, 8'h33, 8'h44};
    $display("unpacked_bytes[2]= %h", unpacked_bytes[2]);
    foreach (matrix[r, c])
      matrix[r][c] = r*10 + c;
    $display("matrix[2][3]     = %0d", matrix[2][3]);
    $finish;
  end
endmodule
packed_word      = deadbeef
byte[1] (15:8)   = be
unpacked_bytes[2]= 33
matrix[2][3]     = 23
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmp7ysfj_go/packed_unpacked.sv:14: $finish called at 0 (1s)

The part-select packed_word[15:8] pulls out one byte of the vector, something you cannot do on an unpacked array. The '{...} braces are an array literal used to assign every unpacked element in one statement.

Dynamic arrays: sizing at run time

A dynamic array has an unpacked dimension whose size is not known until simulation runs. You declare it with empty square brackets and allocate storage later with new[n]. This is ideal when the element count depends on a packet length, a random value, or data read from a file.

Two details matter. new[6](old) resizes the array to six elements while copying the existing contents across, so you grow an array without losing data. delete() frees all elements and returns the size to zero.

module tb;
  int dyn [];
  initial begin
    dyn = new[4];
    foreach (dyn[i]) dyn[i] = i*i;
    $write("after new[4]  size=%0d :", dyn.size());
    foreach (dyn[i]) $write(" %0d", dyn[i]);
    $write("n");
    dyn = new[6](dyn);
    dyn[4] = 100; dyn[5] = 200;
    $write("after new[6]  size=%0d :", dyn.size());
    foreach (dyn[i]) $write(" %0d", dyn[i]);
    $write("n");
    dyn.delete();
    $display("after delete  size=%0d", dyn.size());
    $finish;
  end
endmodule
after new[4]  size=4 : 0 1 4 9
after new[6]  size=6 : 0 1 4 9 100 200
after delete  size=0
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpxvunlqdz/dynamic_array.sv:16: $finish called at 0 (1s)

Right after new[4] every element is the default value for its type (0 for int), which is why the loop fills them explicitly. The size() method reports the current length.

Associative arrays: sparse, keyed storage

When the index space is huge or not contiguous, an associative array is the right tool. Instead of a numeric position, it maps a key of almost any type to a value, storing only the entries you actually write. A model of a 4 GB address space, for instance, should never allocate four billion elements; an associative array indexed by address stores just the addresses that were touched.

Here the key is a string. Useful methods are num() for the entry count, exists(key) to test membership without creating an entry, and delete(key) to remove one. A foreach visits the keys in sorted order.

module tb;
  int age [string];
  initial begin
    age["arjun"]  = 21;
    age["bhavya"] = 23;
    age["chetan"] = 20;
    $display("entries      = %0d", age.num());
    $display("bhavya       = %0d", age["bhavya"]);
    $display("has 'dev'?   = %0d", age.exists("dev"));
    foreach (age[name])
      $display("  %s -> %0d", name, age[name]);
    age.delete("chetan");
    $display("after delete = %0d entries", age.num());
    $finish;
  end
endmodule
entries      = 3
bhavya       = 23
has 'dev'?   = 0
  arjun -> 21
  bhavya -> 23
  chetan -> 20
after delete = 2 entries

Because exists("dev") returns 0 and does not insert anything, it is the safe way to probe an associative array before reading, avoiding the accidental creation of empty entries.

Built-in array methods

SystemVerilog gives unpacked arrays a set of built-in methods that would otherwise be hand-written loops. They fall into three groups:

  • Reduction methods collapse the array to a single value: sum(), product(), and(), or(), xor().
  • Locator methods take a with expression and return a queue of results: find, find_index, find_first, find_last.
  • Ordering methods rearrange elements in place or return a filtered queue: sort, rsort, reverse, unique.

Inside a with clause the keyword item refers to the element under test. Locator results come back as a queue (declared with [$]), because the number of matches is not known in advance.

module tb;
  int data [] = '{5, 2, 8, 2, 9, 1};
  int res  [$];
  int idx  [$];
  initial begin
    $display("sum = %0d", data.sum());
    res = data.find with (item > 4);
    $write("find > 4   :"); foreach (res[i]) $write(" %0d", res[i]); $write("n");
    idx = data.find_index with (item == 2);
    $write("index of 2 :"); foreach (idx[i]) $write(" %0d", idx[i]); $write("n");
    data.sort();
    $write("sorted     :"); foreach (data[i]) $write(" %0d", data[i]); $write("n");
    data.rsort();
    $write("rsorted    :"); foreach (data[i]) $write(" %0d", data[i]); $write("n");
    res = data.unique();
    $write("unique     :"); foreach (res[i]) $write(" %0d", res[i]); $write("n");
    $finish;
  end
endmodule
sum = 27
find > 4   : 5 8 9
index of 2 : 1 3
sorted     : 1 2 2 5 8 9
rsorted    : 9 8 5 2 2 1
unique     : 9 8 5 2 1

Observe that find_index returns positions (1 and 3), not the values, while find returns the matching values themselves. After rsort leaves the array in descending order, unique keeps the first occurrence of each value, so the duplicate 2 is dropped.

Which array should you use?

Array kindSizeIndexReach for it when
PackedFixed, compile timeBit positionYou need a vector you can slice and do arithmetic on
Fixed unpackedFixed, compile timeIntegerThe element count is known and never changes
DynamicSet at run timeIntegerThe length depends on run-time data
AssociativeGrows per keyAny typeThe index space is large or sparse

Master these four and the matching method set, and most testbench data structures become a few lines instead of a page of loops.

Quick check

Where do packed and unpacked dimensions go relative to the variable name?

A packed dimension is written to the left of the name (for example bit [31:0] w); an unpacked dimension is written to the right (for example byte b [4]).

How do you enlarge a dynamic array without losing its current contents?

Use arr = new[n](arr). The argument in parentheses copies the old elements into the newly sized array.

What does exists("dev") return for an associative array that has no such key, and why is it useful?

It returns 0. It is useful because it tests for a key without creating an entry, unlike a plain read which can insert an empty element.

What data type do locator methods such as find and find_index return?

A queue (declared with [$]), because the number of matching elements is not known until the method runs.

Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.
▶ Try it in the ChipXpert Lab

Compile and simulate this in the browser — no install. Edit the code and re-run instantly.

Open in Verification Lab

Learn Verilog with mentors, labs and placement support

This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.

Explore VLSI Courses
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD