Skip to Content

Passing Queues as Arguments in System Verilog: A Comprehensive Guide

Passing Queues as Arguments in System Verilog: A Comprehensive Guide

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:

MethodSyntaxEffectPerformance
By Value (Copy)function int func(int q[$])Changes inside the function do not affect the original queueSlower (copies all elements)
By Reference (ref)function int func(ref int q[$])Changes inside the function affect the original queueFaster (no copy)
Read-Only (const ref)function int func(const ref int q[$])Prevents modification inside the functionFastest (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

  1. Use automatic for tasks/functions that handle queues (avoids static storage issues).
  2. Prefer ref for large queues (avoids unnecessary copying).
  3. Use const ref when the queue should not be modified.
  4. Avoid modifying queues while iterating (can cause unexpected behavior).
  5. 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.

Passing Queues as Arguments in System Verilog: A Comprehensive Guide
Prachi Goyal 15 May 2025
Share this post
Tags
Archive