SystemVerilog mailbox provides a mechanism for communication between different processes, allowing them to exchange data efficiently. In this guide, we will explain how mailboxes work, their types, and how you can use them in your SystemVerilog code. We will also discuss the differences between generic and parameterized mailboxes, their key features, and how to implement them in a simulation.

What is a SystemVerilog Mailbox?

A SystemVerilog mailbox is similar to a physical postbox in which messages (or data) can be placed and retrieved by different processes. It acts as a communication tool between threads in your simulation, enabling them to pass data in a structured manner.

Key Features of a Mailbox:

  • Bounded Mailbox: It has a fixed capacity. If the mailbox is full and a process tries to add more messages, it will be blocked until space is available.
  • Unbounded Mailbox: It has no fixed size, meaning it can store an unlimited number of messages.
  • FIFO (First In, First Out): Messages are stored and retrieved in the order they are added. You cannot access specific indices within the mailbox like you would with a queue.

Types of SystemVerilog Mailboxes:

  1. Generic Mailbox: Can hold messages of any data type.
  2. Parameterized Mailbox: Designed to store only a specific data type. This helps avoid type mismatches during simulation.

Differences Between Mailboxes and Queues

Although a SystemVerilog mailbox behaves similarly to a queue, it differs significantly in how it operates. A queue only allows you to add or remove items from the front or back, whereas a mailbox is a built-in class that uses semaphores for atomic operations. This allows controlled access to the mailbox, ensuring that only one process can modify the mailbox at a time.

SystemVerilog Mailbox Example: Basic Operation

In the following example, we will demonstrate how two processes communicate using a bounded mailbox with a size limit of 2 items. One process will add messages to the mailbox, and another will retrieve them.

Example Code:

module tb;
    // Create a mailbox with a maximum capacity of 2 items
    mailbox mbx = new(2);

    // Block1: This block adds items to the mailbox
    initial begin
        for (int i = 0; i < 5; i++) begin
            #1 mbx.put(i);  // Put item into mailbox every 1ns
            $display ("[%0t] Thread0: Put item #%0d, size=%0d", $time, i, mbx.num());
        end
    end

    // Block2: This block retrieves items from the mailbox
    initial begin
        forever begin
            int idx;
            #2 mbx.get(idx);  // Get item from mailbox every 2ns
            $display ("[%0t] Thread1: Got item #%0d, size=%0d", $time, idx, mbx.num());
        end
    end
endmodule

Simulation Log:

ncsim> run
[1] Thread0: Put item #0, size=1
[2] Thread1: Got item #0, size=0
[2] Thread0: Put item #1, size=1
[3] Thread0: Put item #2, size=2
[4] Thread1: Got item #1, size=1
[4] Thread0: Put item #3, size=2
[6] Thread1: Got item #2, size=2
[6] Thread0: Put item #4, size=2
[8] Thread1: Got item #3, size=1
[10] Thread1: Got item #4, size=0
ncsim: *W,RNQUIE: Simulation is complete.

Explanation of Code:

  1. Mailbox Creation: A mailbox mbx is created with a capacity of 2 items.
  2. Thread 1 (Block1): This process adds 5 items to the mailbox. It uses the put() function and waits for 1ns between each addition.
  3. Thread 2 (Block2): This process continuously retrieves items from the mailbox using the get() function, waiting for 2ns between each retrieval.
  4. Race Condition: Notice that there is a potential race between the two threads where both can modify the mailbox at the same delta cycle. The number of items in the mailbox (num()) reflects the state during the current cycle but may change once the next operation occurs.

SystemVerilog Mailbox Functions and Tasks

SystemVerilog mailboxes provide several methods for interaction. Below is a list of commonly used functions and tasks:

Function/TaskDescription
new(int bound = 0)Creates a new mailbox. The optional bound argument defines its size.
num()Returns the number of messages currently in the mailbox.
put(singular message)Blocking method that adds a message to the mailbox in FIFO order.
try_put(singular message)Non-blocking method to add a message if the mailbox is not full. Returns 1 on success.
get(ref singular message)Blocking method that retrieves a message from the mailbox.
try_get(ref singular message)Non-blocking method that tries to get a message from the mailbox. Returns 0 if empty.
peek(ref singular message)Retrieves a message without removing it from the mailbox.
try_peek(ref singular message)Non-blocking method to peek a message without removing it.

Parameterized Mailbox for Specific Data Types

By default, a SystemVerilog mailbox can hold messages of any data type, which can sometimes lead to type mismatches. To avoid this, you can parameterize the mailbox to accept only a specific data type, such as string or byte.

Example: Parameterized Mailbox for Strings

// Create alias for parameterized "string" type mailbox
typedef mailbox #(string) s_mbox;

// Define a component to send messages
class comp1;
    s_mbox names;

    task send();
        for (int i = 0; i < 3; i++) begin
            string s = $sformatf("name_%0d", i);
            #1 $display ("[%0t] Comp1: Put %s", $time, s);
            names.put(s);
        end
    endtask
endclass

// Define a second component to receive messages
class comp2;
    s_mbox list;

    task receive();
        forever begin
            string s;
            list.get(s);
            $display ("[%0t] Comp2: Got %s", $time, s);
        end
    endtask
endclass

// Top-level module to connect both components
module tb;
    s_mbox m_mbx = new();
    comp1 m_comp1 = new();
    comp2 m_comp2 = new();

    initial begin
        m_comp1.names = m_mbx;
        m_comp2.list = m_mbx;

        fork
            m_comp1.send();
            m_comp2.receive();
        join
    end
endmodule

Simulation Log:

ncsim> run
[1] Comp1: Put name_0
[1] Comp2: Got name_0
[2] Comp1: Put name_1
[2] Comp2: Got name_1
[3] Comp1: Put name_2
[3] Comp2: Got name_2
ncsim: *W,RNQUIE: Simulation is complete.

Error Handling with Parameterized Mailboxes

If a mailbox is parameterized with a different type (e.g., a byte type in comp2), a compile-time error occurs if the types mismatch.

class comp2;
    mailbox #(byte) list;
endclass

module tb;
    s_mbox m_mbx;
    comp1 m_comp1;
    comp2 m_comp2;

    initial begin
        m_comp1.names = m_mbx;
        m_comp2.list = m_mbx; // Error: Type mismatch during assignment
    end
endmodule

This would result in a compilation error, helping you catch type mismatches early in the design process.

Conclusion

SystemVerilog mailboxes are essential for synchronizing and sharing data between different processes. Understanding the differences between bounded and unbounded mailboxes, as well as the concept of parameterized mailboxes, will help you use them effectively in your simulations. Whether you’re working with generic or type-constrained data, mailboxes offer a powerful way to handle inter-process communication in a SystemVerilog environment.

Scroll to Top