What Are Queues in SystemVerilog?

A queue in SystemVerilog is a dynamic array that allows elements to be added or removed from either end. It’s a flexible structure that automatically adjusts its size depending on how many elements it holds at a given time.

Queues are often used when the order of data needs to be maintained, making them ideal for FIFO (First In, First Out) operations. The queue grows as elements are added and shrinks when elements are removed, making them memory-efficient.

Key Characteristics of Queues:

  • Dynamic Sizing: The queue can expand and contract as elements are added or removed, without a need to declare its size upfront.
  • FIFO Behavior: By default, queues operate in a FIFO manner, but elements can also be added or removed from either end.
  • Efficient for Sequential Data: Queues are highly efficient when dealing with sequential data, as they allow insertion and removal in constant time.

Syntax for Declaring Queues

Declaring a queue in SystemVerilog is simple. You just need to specify the element type followed by [$] to indicate that it’s a queue.

Verilog
int queue_of_integers[$]; // Declare a queue of integers

Here, queue_of_integers is a queue that can hold integer values. You don’t need to specify the size when you declare a queue, as it dynamically adjusts based on the number of elements.

Operations on Queues

Queues in SystemVerilog support various operations, including adding elements, removing elements, and accessing them using indices. Below are some of the most common operations.

1. Inserting Elements

To insert elements into a queue, you can use the push_front() or push_back() methods. The difference between the two is where the element is inserted:

  • push_front(): Adds the element to the front of the queue.
  • push_back(): Adds the element to the end of the queue (default FIFO behavior).
Verilog
module queue_example;
  int q[$]; // Declare a queue of integers

  initial begin
    q.push_back(10); // Insert 10 at the end of the queue
    q.push_back(20); // Insert 20 at the end
    q.push_front(5); // Insert 5 at the front

    // Display the queue contents
    $display("Queue Contents: %p", q);
  end
endmodule

Output

Verilog
Queue Contents: '{5, 10, 20}

In this example, push_back() adds elements to the end of the queue, while push_front() adds an element at the front.

2. Removing Elements

You can remove elements from a queue using pop_front() or pop_back():

  • pop_front(): Removes the element from the front of the queue.
  • pop_back(): Removes the element from the end of the queue.
Verilog
module queue_example;
  int q[$] = {5, 10, 20}; // Initialize queue with elements

  initial begin
    q.pop_front(); // Removes 5 from the front
    q.pop_back();  // Removes 20 from the back

    // Display the queue contents
    $display("Queue after popping: %p", q);
  end
endmodule

Output

Verilog
Queue after popping: '{10}

In this example, pop_front() removes the first element, and pop_back() removes the last element.

3. Accessing Elements

You can access elements of a queue just like you would with an array, using an index. SystemVerilog also supports functions like size() to get the number of elements in a queue.

Verilog
module queue_example;
  int q[$] = {5, 10, 15, 20}; // Initialize queue

  initial begin
    // Access the first and last elements
    $display("First Element: %0d", q[0]);
    $display("Last Element: %0d", q[q.size()-1]);
  end
endmodule

Output:

Verilog
First Element: 5
Last Element: 20

In the example above, the first element is accessed with q[0] and the last element is accessed using q[q.size()-1].

4. Resizing a Queue

Since queues are dynamic, you can resize them easily by using the delete() method or by assigning a new value.

  • delete: Empties the queue or removes specific elements.
Verilog
module queue_example;
  int q[$] = {5, 10, 15, 20};

  initial begin
    q.delete(); // Delete all elements from the queue
    $display("Queue after delete: %0d", q.size());
  end
endmodule

Output

Verilog
Queue after delete: 0

In this case, the delete() method removes all elements from the queue, leaving it empty.

Queue Example in a FIFO System

A common application of queues is to implement a FIFO buffer. Here’s an example where a queue is used to simulate data being sent and received

Verilog
module fifo_example;
  int data_queue[$]; // Declare a queue for data

  initial begin
    // Producer: Add data to the queue
    data_queue.push_back(1);
    data_queue.push_back(2);
    data_queue.push_back(3);

    // Consumer: Process and remove data from the queue
    while (data_queue.size() > 0) begin
      int data = data_queue.pop_front(); // Fetch and remove front element
      $display("Processed Data: %0d", data);
    end
  end
endmodule

Output:

Verilog
Processed Data: 1
Processed Data: 2
Processed Data: 3

This example demonstrates how a queue can be used to implement a simple FIFO system where data is processed in the order it arrives.

Try out on EDA playground by clicking on the link below

Advantages of Queues in SystemVerilog

  1. Dynamic Sizing: Queues grow and shrink dynamically, making them perfect for scenarios where the size of the data is not known in advance.
  2. FIFO Behavior: By default, queues operate as FIFO buffers, making them ideal for scenarios where the order of data needs to be maintained.
  3. Efficient Operations: Queues allow efficient insertion and removal of elements from both ends (front and back).
  4. Memory Efficiency: Since queues dynamically allocate memory, they are more memory-efficient compared to fixed-size arrays, especially when the number of elements fluctuates during simulation.
  5. Useful for Buffers: Queues are commonly used to model FIFO buffers in verification environments and hardware design, where data needs to be processed in the order it is received.

Comparison with Other Array Types

Queues vs. Dynamic Arrays

  • Dynamic Arrays: Dynamic arrays allow resizing, but they can only grow or shrink as a whole. You can’t add or remove elements from specific positions without resizing the entire array.
  • Queues: Queues, on the other hand, allow you to add or remove elements from the front or back dynamically.

Queues vs. Associative Arrays

  • Associative Arrays: These are indexed by arbitrary keys, not necessarily numbers. They are ideal for sparse data and random access using keys.
  • Queues: These are used for sequential data where order matters, and they grow/shrink based on the number of elements.

Conclusion

Queues in SystemVerilog offer a powerful, flexible, and memory-efficient way to handle dynamic, sequential data. Their ability to grow and shrink as needed, along with easy insertion and removal from either end, makes them particularly suited for FIFO applications and scenarios where the amount of data isn’t known at compile time. By understanding how to work with queues, you can improve the efficiency and scalability of your SystemVerilog designs.

Queues are an essential tool in any SystemVerilog designer’s toolkit, and mastering their usage will make your design and verification tasks more efficient and robust.