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 $:15 caps 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
endmodule
size      = 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
endmodule
after 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
endmodule
sum 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
endclass

This 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

MethodWhat it doesReturns
push_back(x) / push_front(x)Add x at tail / headvoid
pop_front() / pop_back()Remove and return head / tailelement
insert(i, x)Place x at index ivoid
delete(i) / delete()Remove index i / whole queuevoid
size()Current element countint
sort() / rsort()Reorder ascending / descending in placevoid
sum() / product()Reduce to a single valuescalar
min() / max() / unique()Locate valuesqueue
find() with / find_index() withElements / indices meeting a conditionqueue

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.

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