SystemVerilog Queues: Push, Pop, Sort & Find Examples
A SystemVerilog queue is a variable-size, ordered collection of elements that all share one type. A fixed-size array freezes its length at compile time; a queue grows and shrinks while the simulation runs, as you add and remove items, and it keeps everything in order so that index 0 is always the front and index $ is always the back. That ordering is exactly what a testbench needs: stimulus waiting to be driven, expected results waiting to be checked, addresses observed on a bus. Think of a queue as sitting between a dynamic array and an associative array: like a dynamic array it is densely indexed from zero, but instead of resizing it with new[] you reshape it with small built-in calls such as push_back, pop_front, insert and delete.
Declaring a queue
You declare a queue by writing [$] after the variable name. The element type comes first and can be anything — an int, a bit vector, a string, a struct, or a class handle.
int q[$];— an unbounded queue of integers (it can hold any number of elements).bit [7:0] fifo[$:15];— a bounded queue; the$:15caps it at 16 elements, modelling a hardware FIFO depth.string names[$] = {"arjun", "bhavya"};— you can initialise a queue with a{}list, and assign a whole queue in one statement later.
The $ symbol does double duty: in the declaration it means “no upper bound”, and as an index q[$] it always refers to the last element currently in the queue.
Adding and removing items: push, pop and the $ index
Four methods do the everyday work. push_back(x) appends x at the tail and push_front(x) prepends it at the head. pop_front() removes and returns the head element, while pop_back() removes and returns the tail. size() tells you how many elements are present right now. Because the queue is ordered, pairing push_back with pop_front gives you a first-in-first-out FIFO, and pairing push_back with pop_back gives you a last-in-first-out stack.
module tb;
// A queue of ints: the $ means "unbounded"
int job_queue[$];
int head, tail;
initial begin
job_queue.push_back(10); // add at the tail
job_queue.push_back(20);
job_queue.push_front(5); // add at the head
// Queue now reads: {5, 10, 20}
$display("size = %0d", job_queue.size());
$display("front = %0d", job_queue[0]);
$display("back = %0d", job_queue[$]); // $ indexes the last element
head = job_queue.pop_front(); // removes and returns 5
tail = job_queue.pop_back(); // removes and returns 20
$display("popped head = %0d, tail = %0d", head, tail);
$display("remaining size = %0d", job_queue.size());
$finish;
end
endmodulesize = 3 front = 5 back = 20 popped head = 5, tail = 20 remaining size = 1 /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmp_x2wm3rt/queue_basics.sv:20: $finish called at 0 (1s)
Notice that declarations such as int head, tail; sit at the top of the block. Some simulators are strict about this, so declaring variables before the first statement keeps your code portable.
insert, delete and foreach
When you need to touch the middle of a queue, use insert(index, value) to place a value at a specific position and delete(index) to remove the element at that position; everything after the point shifts to make room or close the gap. Calling delete() with no argument empties the whole queue at once. To walk a queue, the foreach loop gives you each index in order without you having to manage bounds by hand.
module tb;
string pkts[$]; // a queue of strings
int i;
initial begin
pkts = {"A", "B", "C"}; // assign a whole queue at once
pkts.insert(1, "X"); // insert "X" at index 1 -> {A, X, B, C}
$display("after insert:");
foreach (pkts[i])
$display(" pkts[%0d] = %s", i, pkts[i]);
pkts.delete(2); // delete index 2 -> {A, X, C}
$display("after delete(2):");
foreach (pkts[i])
$display(" pkts[%0d] = %s", i, pkts[i]);
pkts.delete(); // delete the whole queue
$display("after delete() : size = %0d", pkts.size());
$finish;
end
endmoduleafter insert: pkts[0] = A pkts[1] = X pkts[2] = B pkts[3] = C after delete(2): pkts[0] = A pkts[1] = X pkts[2] = C after delete() : size = 0 /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpfic8zjh0/queue_edit.sv:19: $finish called at 0 (1s)
A detail worth remembering: pop_front() both removes the head and returns it, whereas delete(0) only removes index 0 and returns nothing. Reach for pop_front when you want the value, and delete when you simply want it gone.
Queue methods: size, sort, sum and find
Queues inherit the array manipulation methods, and they fall into two groups that behave very differently. Reduction methods like sum(), product(), and(), or() and xor() collapse the whole queue into a single scalar value. Locator methods like min(), max(), unique(), find() and find_index() return a queue — even when only one element matches. This is the single most common beginner trap: q.max() is not an int, it is a one-element queue, so you read the value with q.max()[0].
The find() and find_index() methods take a with clause — a boolean expression evaluated against every element (named here s). find() returns the matching elements; find_index() returns their positions. Combined with size(), this is how you count how many elements satisfy a condition without writing a loop. sort() reorders the queue ascending in place (rsort() for descending).
module tb;
int scores[$] = {40, 90, 15, 72, 90};
int top[$]; // max() and find() return queues, not scalars
int low[$];
int passed[$];
int idx[$];
initial begin
// Reduction method: sum() collapses the queue to one value
$display("sum of all scores = %0d", scores.sum());
// Locator methods max()/min() return a 1-element queue
top = scores.max();
low = scores.min();
$display("highest = %0d, lowest = %0d", top[0], low[0]);
// sort() reorders the queue ascending, in place
scores.sort();
$display("after sort: lowest index 0 = %0d, highest index $ = %0d",
scores[0], scores[$]);
// find() with a condition returns every matching element
passed = scores.find(s) with (s >= 70);
$display("scores >= 70 -> %0d of them", passed.size());
// find_index() returns the positions of matches
idx = scores.find_index(s) with (s == 90);
$display("value 90 appears at %0d position(s)", idx.size());
$finish;
end
endmodulesum of all scores = 307 highest = 90, lowest = 15 after sort: lowest index 0 = 15, highest index $ = 90 scores >= 70 -> 3 of them value 90 appears at 2 position(s)
Other locator variants are handy to know: find_first, find_last, find_first_index and find_last_index each still return a queue, but at most one element long. unique() gives you the distinct values, which is useful for sanity-checking that a stream of IDs has no duplicates.
Putting it to work: a queue as a scoreboard FIFO
In real verification a queue most often becomes the memory of a scoreboard. The monitor pushes expected transactions at the back; as the DUT produces outputs, the checker pops the oldest expected value from the front and compares. The queue enforces ordering for free, so you never manage read and write pointers yourself.
class Scoreboard;
// A queue acts as an ordered FIFO of expected values
int expected[$];
function void push_expected(int value);
expected.push_back(value); // producer adds at the tail
endfunction
function void check(int actual);
int exp;
if (expected.size() == 0) begin
$error("got %0d but scoreboard is empty", actual);
return;
end
exp = expected.pop_front(); // consumer removes from the head
if (exp != actual)
$error("mismatch: expected %0d, got %0d", exp, actual);
else
$display("match: %0d", actual);
endfunction
endclassThis snippet is shown for structure rather than as a standalone run — drop the class into a UVM or plain-SystemVerilog environment and connect it to your monitor and driver.
Quick reference
| Method | What it does | Returns |
|---|---|---|
push_back(x) / push_front(x) | Add x at tail / head | void |
pop_front() / pop_back() | Remove and return head / tail | element |
insert(i, x) | Place x at index i | void |
delete(i) / delete() | Remove index i / whole queue | void |
size() | Current element count | int |
sort() / rsort() | Reorder ascending / descending in place | void |
sum() / product() | Reduce to a single value | scalar |
min() / max() / unique() | Locate values | queue |
find() with / find_index() with | Elements / indices meeting a condition | queue |
Master these handful of methods and the SystemVerilog queue becomes the workhorse container of your testbenches — compact to declare, ordered by construction, and expressive enough to replace most hand-written loops.
Quick check
What do the two uses of $ mean in int q[$]; and q[$]?
In the declaration int q[$] the $ marks the queue as unbounded (no maximum size). As an index, q[$] refers to the last element currently in the queue, so it tracks the back as the queue grows or shrinks.
Does q.max() give you an integer you can compare directly?
No. Locator methods such as max(), min() and find() return a queue, not a scalar — even when only one element qualifies. Read the actual value with q.max()[0]. Only reduction methods like sum() and product() return a plain scalar.
What is the difference between pop_front() and delete(0)?
Both remove the element at the head. pop_front() also returns that element so you can use its value, whereas delete(0) just removes it and returns nothing. Use pop_front when you need the value, delete when you only need it gone.
How would you count how many elements of a queue are greater than or equal to 70?
Use find() with a condition and then take its size: q.find(s) with (s >= 70) returns a queue of the matching elements, and calling .size() on that result gives the count. find_index() would instead give you their positions.
Share your question in comments or talk to our mentor team for batch guidance.
Compile and simulate this in the browser — no install. Edit the code and re-run instantly.
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