SystemVerilog queues are dynamic data structures that allow efficient insertion and deletion operations. Passing queues to tasks and functions enables modular testbench design, scoreboarding, and transaction processing. This article provides a detailed, line-by-line breakdown of how queues are passed as arguments in SystemVerilog.
1. Basic Queue Declaration and Initialization
Before passing a queue to a function or task, we must first declare and populate it.
Example: Declaring and Initializing a Queue
module queue_example;
    // Declare a queue of integers
    int int_queue[$] = '{10, 20, 30, 40};  // Initialize with values 10,20,30,40
    initial begin
        $display("Initial Queue: %p", int_queue);  // Prints: Initial Queue: '{10, 20, 30, 40}
    end
endmodule
Explanation:
- int int_queue[$]: Declares a dynamic queue of integers ($ denotes an unbounded queue).
- = '{10, 20, 30, 40}: Initializes the queue with four integer values.
- $display("%p", int_queue): Prints the entire queue using the %p format specifier.
2. Passing a Queue to a Function
Functions can take queues as arguments, either by value (copy) or by reference (ref).
Example: Passing a Queue to a Function
module queue_function_example;
    int numbers[$] = '{1, 2, 3, 4};
    // Function to calculate the sum of a queue (passed by reference)
    function automatic int sum_queue(ref int q[$]);
        int total = 0;
        foreach (q[i]) begin
            total += q[i];  // Accumulate sum
        end
        return total;
    endfunction
    initial begin
        int result = sum_queue(numbers);  // Pass the queue by reference
        $display("Sum of queue: %0d", result);  // Output: Sum of queue: 10
    end
endmodule
Explanation:
- function automatic int sum_queue(ref int q[$]):- automatic: Ensures the function uses dynamic memory (important for recursion and re-entrancy).
- ref int q[$]: The queue is passed by reference, meaning modifications inside the function affect the original queue.
 
- foreach (q[i]): Iterates through each element in the queue.
- sum_queue(numbers): The queue numbers is passed to the function.
3. Passing a Queue to a Task
Tasks can also take queues as arguments, useful for modifying queues in procedural blocks.
Example: Passing a Queue to a Task
module queue_task_example;
    string names[$] = '{"Alice", "Bob"};
    // Task to add a name to the queue (passed by reference)
    task automatic add_name(ref string q[$], input string name);
        q.push_back(name);  // Modifies the original queue
    endtask
    initial begin
        $display("Before: %p", names);  // Output: Before: '{"Alice", "Bob"}
        add_name(names, "Charlie");     // Pass queue by reference
        $display("After: %p", names);  // Output: After: '{"Alice", "Bob", "Charlie"}
    end
endmodule
Explanation:
- task automatic add_name(ref string q[$], input string name):- ref string q[$]: The queue is passed by reference, allowing modification.
- input string name: A new string is passed as input.
 
- q.push_back(name): Appends "Charlie" to the original queue.
- add_name(names, "Charlie"): The queue names is modified inside the task.
4. Passing Queues by Value vs. by Reference
Key Differences:
| Method | Syntax | Effect | Performance | 
|---|---|---|---|
| By Value (Copy) | function int func(int q[$]) | Changes inside the function do not affect the original queue | Slower (copies all elements) | 
| By Reference (ref) | function int func(ref int q[$]) | Changes inside the function affect the original queue | Faster (no copy) | 
| Read-Only (const ref) | function int func(const ref int q[$]) | Prevents modification inside the function | Fastest (no copy, read-only) | 
Example: Comparing ref vs. const ref
module ref_vs_const_ref;
    int data[$] = '{100, 200, 300};
    // Modifies the original queue (ref)
    task automatic modify_queue(ref int q[$]);
        q.pop_front();  // Removes first element (100)
    endtask
    // Only reads the queue (const ref)
    function automatic int get_size(const ref int q[$]);
        return q.size();  // Returns current size
    endfunction
    initial begin
        $display("Original size: %0d", get_size(data));  // Output: 3
        modify_queue(data);  // Removes 100
        $display("New size: %0d", get_size(data));       // Output: 2
    end
endmodule
Explanation:
- modify_queue(ref int q[$]): Modifies the original queue (data loses 100).
- get_size(const ref int q[$]): Only reads the queue (cannot modify it).
5. Using Queues with Mailboxes (Inter-Process Communication)
Mailboxes are often used with queues for passing transactions between processes.
Example: Passing a Mailbox (Queue-like) to a Task
module mailbox_example;
    mailbox #(int) int_mbox = new();  // Mailbox behaves like a queue
    // Producer task: Sends data to mailbox
    task automatic producer(ref mailbox #(int) mbox);
        for (int i = 1; i <= 5; i++) begin
            mbox.put(i);  // Sends 1,2,3,4,5 into the mailbox
        end
    endtask
    // Consumer task: Receives data from mailbox
    task automatic consumer(ref mailbox #(int) mbox);
        int received;
        repeat (5) begin
            mbox.get(received);  // Retrieves values 1,2,3,4,5
            $display("Received: %0d", received);
        end
    endtask
    initial begin
        fork
            producer(int_mbox);  // Pass mailbox by reference
            consumer(int_mbox);  // Pass mailbox by reference
        join
    end
endmodule
Explanation:
- mailbox #(int) int_mbox = new(): Creates a mailbox for integer data.
- producer(ref mailbox #(int) mbox): Puts data into the mailbox.
- consumer(ref mailbox #(int) mbox): Retrieves data from the mailbox.
6. Best Practices for Passing Queues
- Use automatic for tasks/functions that handle queues (avoids static storage issues).
- Prefer ref for large queues (avoids unnecessary copying).
- Use const ref when the queue should not be modified.
- Avoid modifying queues while iterating (can cause unexpected behavior).
- Initialize queues before passing them to prevent null reference errors.
Conclusion
Passing queues in System Verilog enables efficient data sharing between functions and tasks. Key takeaways:
- ref allows modifying the original queue.
- const ref is best for read-only access.
- Mailboxes (a type of queue) are useful for inter-process communication.