A SystemVerilog queue is a First-In-First-Out data structure with a variable size, holding elements of the same data type. It behaves like a one-dimensional unpacked array that grows and shrinks automatically, and supports indexing, concatenation, and slicing operators. Queues can also be passed to tasks and functions as ref or non-ref arguments.
9 min read | Intermediate Level
What You'll Learn
- The difference between bounded and unbounded queues
- How to declare, index, and slice a queue
- The built-in queue methods for inserting, deleting, and popping elements
- How to build a queue of class objects or a queue of dynamic arrays
Types of Queues
A queue is recognized by the $ operator used in its size specification.
A bounded queue has a fixed maximum size and can hold a limited number of entries. The queue below has depth N and is already full — it cannot accept more items.
[data_type] [name_of_queue] [$:N];
int bounded_queue [$:10]; // Max size 11 (last valid index is 10)
An unbounded queue can grow to hold an unlimited number of entries. The queue below currently has 5 items but can keep accepting more.
[data_type] [name_of_queue] [$];
int unbounded_queue [$]; // Unlimited entries
Syntax
Queues support both array-style operators and dedicated methods for the same jobs — the operators below work directly on queue indices and slices, without calling a method.
string name_list [$]; // A queue of string elements
bit [3:0] data [$]; // A queue of 4-bit elements
logic [7:0] elements [$:127]; // A bounded queue of 8-bits with maximum size of 128 slots
int q1 [$] = '{ 1, 2, 3, 4, 5 }; // Integer queue, initialize elements
int q2 [$]; // Integer queue, empty
int tmp; // Temporary variable to store values
tmp = q1 [0]; // Get first item of q1 (index 0) and store in tmp
tmp = q1 [$]; // Get last item of q1 (index 4) and store in tmp
q2 = q1; // Copy all elements in q1 into q2
q1 = {}; // Empty the queue (delete all items)
q2[2] = 15; // Replace element at index 2 with 15
q2.insert (2, 15); // Inserts value 15 to index# 2
q2 = { q2, 22 }; // Append 22 to q2
q2 = { 99, q2 }; // Put 99 as the first element of q2
q2 = q2 [1:$]; // Delete first item
q2 = q2 [0:$-1]; // Delete last item
q2 = q2 [1:$-1]; // Delete first and last item
The $ symbol always means "the last index" when used inside brackets, so q1[$] reads as "the element at the last index of q1," and q2[1:$] reads as "everything from index 1 through the last index."
SystemVerilog Queue Example
module tb;
// Create a queue that can store "string" values
string fruits[$] = '{"orange", "apple", "kiwi"};
initial begin
// Iterate and access each queue element
foreach (fruits[i])
$display ("fruits[%0d] = %s", i, fruits[i]);
// Display elements in a queue
$display ("fruits = %p", fruits);
// Delete all elements in the queue
fruits = {};
$display ("After deletion, fruits = %p", fruits);
end
endmodule
Output
ncsim> run
fruits[0] = orange
fruits[1] = apple
fruits[2] = kiwi
fruits = '{"orange", "apple", "kiwi"}
After deletion, fruits = '{}
ncsim: *W,RNQUIE: Simulation is complete.
Quick Check
Q: After fruits = {};, what does fruits.size() return?
Show Answer
0. Assigning an empty concatenation {} to a queue removes every element, and a queue with no elements has a size of 0.
What Are Queue Slice Expressions?
A slice expression selects a subset of an existing queue using the [start:end] syntax, where either side can be a literal index or $ for the last index.
Some simulators produce different results for edge-case slice expressions, so using the dedicated queue methods covered further down is generally recommended over relying on slicing for insert/delete logic.
module tb;
// Create a queue that can store "string" values
string fruits[$] = '{ "orange", "apple", "lemon", "kiwi" };
initial begin
// Select a subset of the queue
$display ("citrus fruits = %p", fruits[1:2]);
// Get elements from index 1 to end of queue
$display ("fruits = %p", fruits[1:$]);
// Add element to the end of queue
fruits[$+1] = "pineapple";
$display ("fruits = %p", fruits);
// Delete first element
fruits = fruits[1:$];
$display ("Remove orange, fruits = %p", fruits);
end
endmodule
Output
Compiler version J-2014.12-SP1-1; Runtime version J-2014.12-SP1-1; May 15 16:21 2018
citrus fruits = '{"apple", "lemon"}
fruits = '{"apple", "lemon", "kiwi"}
fruits = '{"orange", "apple", "lemon", "kiwi", "pineapple"}
Remove orange, fruits = '{"apple", "lemon", "kiwi", "pineapple"}
V C S S i m u l a t i o n R e p o r t
Writing to fruits[$+1] is a common idiom for appending — since $ is the last valid index, $+1 is exactly one past the end, which extends the queue rather than going out of bounds.
Queue slicing is handy for scoreboard cleanup — for example, pending_txns = pending_txns[1:$]; to discard a transaction once it's been checked and move on to the next one.
SystemVerilog Queue Methods
In addition to the array operators above, queues expose dedicated methods for the same operations, which read more clearly in testbench code.
| Method | Description |
|---|---|
function int size (); | Returns the number of items in the queue, 0 if empty |
function void insert (input integer index, input element_t item); | Inserts the given item at the specified index position |
function void delete ( [input integer index] ); | Deletes the element at the specified index; if not provided, all elements are deleted |
function element_t pop_front (); | Removes and returns the first element of the queue |
function element_t pop_back (); | Removes and returns the last element of the queue |
function void push_front (input element_t item); | Inserts the given element at the front of the queue |
function void push_back (input element_t item); | Inserts the given element at the end of the queue |
SystemVerilog Queue Methods Example
module tb;
string fruits[$] = '{"apple", "pear", "mango", "banana"};
initial begin
// size() - Gets size of the given queue
$display ("Number of fruits=%0d fruits=%p", fruits.size(), fruits);
// insert() - Insert an element to the given index
fruits.insert (1, "peach");
$display ("Insert peach, size=%0d fruits=%p", fruits.size(), fruits);
// delete() - Delete element at given index
fruits.delete (3);
$display ("Delete mango, size=%0d fruits=%p", fruits.size(), fruits);
// pop_front() - Pop out element at the front
$display ("Pop %s, size=%0d fruits=%p", fruits.pop_front(), fruits.size(), fruits);
// push_front() - Push a new element to front of the queue
fruits.push_front("apricot");
$display ("Push apricot, size=%0d fruits=%p", fruits.size(), fruits);
// pop_back() - Pop out element from the back
$display ("Pop %s, size=%0d fruits=%p", fruits.pop_back(), fruits.size(), fruits);
// push_back() - Push element to the back
fruits.push_back("plum");
$display ("Push plum, size=%0d fruits=%p", fruits.size(), fruits);
end
endmodule
Output
ncsim> run
Number of fruits=4 fruits='{"apple", "pear", "mango", "banana"}
Insert peach, size=5 fruits='{"apple", "peach", "pear", "mango", "banana"}
Delete mango, size=4 fruits='{"apple", "peach", "pear", "banana"}
Pop apple, size=3 fruits='{"peach", "pear", "banana"}
Push apricot, size=4 fruits='{"apricot", "peach", "pear", "banana"}
Pop banana, size=3 fruits='{"apricot", "peach", "pear"}
Push plum, size=4 fruits='{"apricot", "peach", "pear", "plum"}
ncsim: *W,RNQUIE: Simulation is complete.
Notice that fruits.delete(3) deletes by current index, not original index — after the earlier insert, "mango" had shifted from index 2 to index 3, which is why delete(3) is the call that removes it.
This exact pattern — push_back() for arriving transactions and pop_front() for processing them in order — is the standard way to model an in-order scoreboard or a FIFO-style pipeline stage in a testbench.
Quick Check
Q: Which method would you use to remove and return the oldest item pushed into a FIFO-style queue?
Show Answer
pop_front(). If items are always added with push_back(), the oldest item is always at the front, so pop_front() retrieves items in the same order they were pushed — true FIFO behavior.
How to Create a Queue of Classes in SystemVerilog?
A queue can hold class handles just as easily as it holds simple types like int or string.
// Define a class with a single string member called "name"
class Fruit;
string name;
function new (string name="Unknown");
this.name = name;
endfunction
endclass
module tb;
// Create a queue that can hold values of data type "Fruit"
Fruit list [$];
initial begin
// Create a new class object and call it "Apple"
// and push into the queue
Fruit f = new ("Apple");
list.push_back (f);
// Create another class object and call it "Banana" and
// push into the queue
f = new ("Banana");
list.push_back (f);
// Iterate through queue and access each class object
foreach (list[i])
$display ("list[%0d] = %s", i, list[i].name);
// Simply print the whole queue, note that class handles are printed
// and not class object contents
$display ("list = %p", list);
end
endmodule
Output
ncsim> run
list[0] = Apple
list[1] = Banana
list = '{$unit_0x4ccdf83b::Fruit@2_1, $unit_0x4ccdf83b::Fruit@4_1}
ncsim: *W,RNQUIE: Simulation is complete.
Printing list directly with %p shows each element's class handle (an internal reference), not its field values — if you want to see name, you need to access it explicitly, as the foreach loop does above.
A queue of class handles is exactly how most UVM-style scoreboards store outstanding transactions — each entry is a handle to a transaction object, not a copy of raw data.
How to Create a Queue of Dynamic Arrays in SystemVerilog?
A queue's element type can itself be a dynamic array, giving you a queue whose entries are variable-length lists.
// Declare a dynamic array to store strings as a datatype
typedef string str_da [];
module tb;
// This is a queue of dynamic arrays
str_da list [$];
initial begin
// Initialize separate dynamic arrays with some values
str_da marvel = '{"Spiderman", "Hulk", "Captain America", "Iron Man"};
str_da dcWorld = '{"Batman", "Superman" };
// Push the previously created dynamic arrays to queue
list.push_back (marvel);
list.push_back (dcWorld);
// Iterate through the queue and access dynamic array elements
foreach (list[i])
foreach (list[i][j])
$display ("list[%0d][%0d] = %s", i, j, list[i][j]);
// Simply print the queue
$display ("list = %p", list);
end
endmodule
Output
ncsim> run
list[0][0] = Spiderman
list[0][1] = Hulk
list[0][2] = Captain America
list[0][3] = Iron Man
list[1][0] = Batman
list[1][1] = Superman
list = '{'{"Spiderman", "Hulk", "Captain America", "Iron Man"}, '{"Batman", "Superman"}}
ncsim: *W,RNQUIE: Simulation is complete.
The outer foreach (list[i]) walks each dynamic array in the queue, and the inner foreach (list[i][j]) walks the elements within that array — the same nested pattern used for any multidimensional unpacked structure.
Common Mistakes
Mistake #1: Popping from an empty queue
Calling pop_front() or pop_back() on a queue with no elements is a runtime warning in most simulators and can silently fail. Check queue.size() first if you're not certain an item is waiting.
Mistake #2: Deleting by the wrong index after a prior insert/delete
Every insert() or delete() shifts the indices of the elements after it. As shown in the methods example, delete(3) only removed "mango" because a prior insert() had already shifted it from index 2 to index 3 - always index against the queue's current state, not its original layout.
Mistake #3: Assuming a bounded queue silently drops excess pushes
Pushing beyond a bounded queue's declared depth ([$:N]) doesn't raise an error - any element that ends up beyond the bound is simply discarded, with only a warning issued. Check size() against the bound before pushing if silently losing an overflow element would be a real problem for your testbench.
Mistake #4: Expecting %p on a class queue to print field values
$display("%p", list) on a queue of class handles prints the handle representation, not the object's fields. Loop through the queue and call each object's own print method (or access its fields directly) if you need to see the actual data.
Practice Exercise
Challenge: Build a Simple FIFO Scoreboard
Declare an unbounded queue of int named expected_q. Push the values 10, 20, and 30 onto it using push_back(), then pop and print each value in order using pop_front() until the queue is empty.
Hint
Use a while (expected_q.size() > 0) loop so you don't call pop_front() on an empty queue.
Solution
module tb;
int expected_q [$];
initial begin
expected_q.push_back(10);
expected_q.push_back(20);
expected_q.push_back(30);
while (expected_q.size() > 0) begin
$display("Popped %0d, remaining size=%0d", expected_q.pop_front(), expected_q.size());
end
end
endmodule
Summary
In this tutorial, you learned:
- A queue is a variable-size, FIFO-ordered data structure recognized by the
$operator, and comes in bounded ([$:N]) and unbounded ([$]) forms - Queues support both array-style indexing/slicing and dedicated methods for the same operations
push_front()/push_back()add elements;pop_front()/pop_back()remove and return them;insert()/delete()work by index- Deleting or inserting by index shifts everything after it, so always index against the queue's current state
- A queue can hold class handles or dynamic arrays as its element type, not just simple data types