SystemVerilog offers a lot of flexibility for building data structures through its different array types. Picking the right one matters: a testbench scoreboard, a sparse lookup table, and a fixed-width register file all call for a different kind of array, and each one is recognized by a distinct declaration syntax.

This article gives a quick overview of the four array categories — static, dynamic, associative, and queue — so you can recognize each one and know where to dig deeper.

6 min read | Intermediate Level

What You'll Learn

  • The four array categories in SystemVerilog and how to recognize each one
  • How static arrays split further into packed and unpacked forms
  • When to reach for a dynamic array, an associative array, or a queue
  • Where to go for a deeper dive into each array type

Static Arrays

A static array is one whose size is known before compilation. The example below declares an 8-bit wide static array, assigns it a value, and iterates over it to print each bit.


module tb;
	bit [7:0] 	m_data; 	// A vector or 1D packed array

	initial begin
		// 1. Assign a value to the vector
		m_data = 8'hA2;

		// 2. Iterate through each bit of the vector and print value
		for (int i = 0; i < $size(m_data); i++) begin
			$display ("m_data[%0d] = %b", i, m_data[i]);
		end
	end
endmodule

Output

Output
ncsim> run
m_data[0] = 0
m_data[1] = 1
m_data[2] = 0
m_data[3] = 0
m_data[4] = 0
m_data[5] = 1
m_data[6] = 0
m_data[7] = 1
ncsim: *W,RNQUIE: Simulation is complete.

Static arrays are further categorized into packed and unpacked arrays, based on whether the dimensions are declared before or after the variable name.


bit [2:0][7:0] 	m_data; 			    // Packed
bit [15:0] 		  m_mem [10:0]; 		// Packed + Unpacked

Learn more about SystemVerilog Packed Arrays

Unpacked arrays may be fixed-size arrays, dynamic arrays, associative arrays, or queues — the rest of this article covers those non-fixed-size forms.

Learn more about SystemVerilog Unpacked Arrays

Static arrays (packed or unpacked-fixed-size) are the default choice for RTL and for any testbench structure whose size you already know, like a fixed-depth register file or a bus with a known number of lanes.

Quick Check

Q: Is bit [15:0] m_mem [10:0]; a packed array, an unpacked array, or both?

Show Answer

Both. [15:0] is written before the name, so it's a packed dimension (each element is a 16-bit vector). [10:0] is written after the name, so it's an unpacked dimension (11 separate elements).

Dynamic Arrays

A dynamic array is one whose size isn't known at compile time — it's allocated and resized during runtime instead. It's easily recognized by its empty square brackets [ ].


int 		m_mem []; 	// Dynamic array, size unknown but it holds integer values

Learn more about SystemVerilog Dynamic Arrays

Dynamic arrays are a good fit when you know you're storing a list of same-typed elements, but the count depends on something learned at runtime — for example, the number of packets to send for a given test scenario.

Associative Arrays

An associative array stores content against a key rather than a sequential index. It's recognized by a data type sitting inside the square brackets — that type is the key.


int 		m_data [int]; 			// Key is of type int, and data is also of type int
int 		m_name [string]; 		// Key is of type string, and data is of type int

m_name ["Rachel"] = 30;
m_name ["Orange"] = 2;

m_data [32'h123] = 3333;

Learn more about SystemVerilog Associative Arrays

Associative arrays are the natural fit for sparse data, like tracking scoreboard entries by transaction ID or counting coverage hits by an address that could be anywhere in a huge memory space, without allocating storage for every possible index.

Quick Check

Q: In int m_name [string];, what is string — the data type or the key type?

Show Answer

The key type. The type inside the square brackets always describes the key used to look up an entry; the type before the variable name (int here) is the data stored at each key.

Queues

A queue lets you push data in and pop data out from either end. It's recognized by the $ symbol inside the square brackets.


int 	m_queue [$]; 		// Unbound queue, no size


m_queue.push_back(23); 		// Push into the queue

int data = m_queue.pop_front(); // Pop from the queue

Learn more about SystemVerilog Queues

Queues are the go-to structure for scoreboards, transaction pipelines, and any place you need strict ordering with fast insertion and removal at either end — something a plain dynamic array doesn't give you as conveniently.

Common Mistakes

Mistake #1: Reaching for a static array when the size isn't actually known

Declaring int m_mem [100]; "just to be safe" wastes storage if you rarely need that many entries, and still fails if you occasionally need more than 100. If the count depends on runtime conditions, use a dynamic array or a queue instead of guessing a fixed size.

Mistake #2: Assuming an associative array preserves insertion order

Associative arrays are indexed by key, not by position, so iterating with foreach does not guarantee the order entries were added in. If order matters, use a queue instead.

Mistake #3: Confusing the key type with the data type

In int m_name [string];, it's easy to misread which side is the key. The type before the name (int) is what's stored; the type inside the brackets (string) is what you index with.

Mistake #4: Popping from an empty queue

Calling pop_front() or pop_back() on an empty queue is not a runtime error — it returns the type's default value, leaves the queue unchanged, and may just issue a warning. That's easy to rely on by accident, so it's still worth checking m_queue.size() (or tracking expected pushes/pops) before popping in code that isn't guaranteed to have an entry waiting, since a silently-returned default value can mask a real bug.

Practice Exercise

Challenge: Pick the Right Array Type

You're writing a testbench component that tracks outstanding read requests by their unique transaction ID (a random 32-bit value), so you can look up and remove a request once its response arrives. Which array type fits best, and how would you declare it?

Hint

Transaction IDs are random 32-bit values, not a small sequential range, and you need to look one up directly rather than scan through a list.

Solution

// Associative array: key = transaction ID, data = the outstanding request
my_transaction pending_reqs [bit [31:0]];

// Add an outstanding request
pending_reqs[tx_id] = req;

// Look up and remove it once the response arrives
if (pending_reqs.exists(resp_id)) begin
  my_transaction original_req = pending_reqs[resp_id];
  pending_reqs.delete(resp_id);
end

An associative array is the right choice here: transaction IDs are sparse (not a small, dense 0..N range), and you need direct key-based lookup rather than positional access — exactly what a static or dynamic array isn't built for.

Summary

In this tutorial, you learned:

  • Static arrays have a size known at compile time, and split further into packed and unpacked forms
  • Dynamic arrays use empty [ ] brackets and are sized at runtime
  • Associative arrays use a data type inside [ ] to represent the key, ideal for sparse or non-sequential data
  • Queues use [$] and support fast push_back()/pop_front()-style insertion and removal at either end
  • Choosing the right array type up front avoids wasted storage, missing-order bugs, and runtime errors later on
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5