mailbox is a way to allow different processes to exchange data between each other. It is similar to a real postbox where letters can be put into the box and a person can retrieve those letters later on.
SystemVerilog mailboxes are created as having either a bounded or unbounded queue size. A bounded
mailbox can only store a limited amount of data, and if a process attempts to store more messages into a full mailbox, it will be suspended until there's enough room in the mailbox. However, an unbounded
mailbox has unlimited size.
There are two types:
- Generic Mailbox that can accept items of any data type
- Parameterized Mailbox that can accept items of only a specific data type
SystemVerilog Mailbox vs Queue
Although a SystemVerilog mailbox essentially behaves like a queue, it is quite different from the
queue data type. A simple queue can only push and pop items from either the front or the back. However, a mailbox is a built-in class that uses semaphores to have atomic control the push and pop from the queue. Moreover, you cannot access a given index within the mailbox queue, but only retrieve items in FIFO order.
Where is a mailbox used ?
mailbox is typically used when there are multiple threads running in parallel and want to share data for which a certain level of determinism is required.
Generic Mailbox Example
Two processes are concurrently active in the example shown below, where one
initial block puts data into the mailbox and another
initial block gets data from the mailbox.
module tb; // Create a new mailbox that can hold utmost 2 items mailbox mbx = new(2); // Block1: This block keeps putting items into the mailbox // The rate of items being put into the mailbox is 1 every ns initial begin for (int i=0; i < 5; i++) begin #1 mbx.put (i); $display ("[%0t] Thread0: Put item #%0d, size=%0d", $time, i, mbx.num()); end end // Block2: This block keeps getting items from the mailbox // The rate of items received from the mailbox is 2 every ns initial begin forever begin int idx; #2 mbx.get (idx); $display ("[%0t] Thread1: Got item #%0d, size=%0d", $time, idx, mbx.num()); end end endmodule
Note that there is a race between the two threads where the first thread can push into the mailbox and the second thread can pop from the mailbox on the same delta cycle. Hence the value displayed using
num() is valid only until the next
put is executed on the mailbox and may depend on the start and finish times of the other methods.
ncsim> run  Thread0: Put item #0, size=1  Thread1: Got item #0, size=0  Thread0: Put item #1, size=1  Thread0: Put item #2, size=2  Thread1: Got item #1, size=1  Thread0: Put item #3, size=2  Thread1: Got item #2, size=2  Thread0: Put item #4, size=2  Thread1: Got item #3, size=1  Thread1: Got item #4, size=0 ncsim: *W,RNQUIE: Simulation is complete.
SystemVerilog Mailbox Functions and Methods
|function new (int bound = 0);||Returns a mailbox handle, bound > 0 represents size of mailbox queue|
|function int num ();||Returns the number of messages currently in the mailbox|
|task put (singular message);||Blocking method that stores a message in the mailbox in FIFO order; message is any singular expression|
|function int try_put (singular message);||Non-blocking method that stores a message if the mailbox is not full, returns a postive integer if successful else 0|
|task get (ref singular message);||Blocking method until it can retrieve one message from the mailbox, if empty blocks the process|
|function int try_get (ref singular message);||Non-blocking method which tries to get one message from the mailbox, returns 0 if empty|
|task peek (ref singular message);||Copies one message from the mailbox without removing the message from the mailbox queue.|
|function int try_peek (ref singular message);||Tries to copy one message from the mailbox without removing the message from queue|
By default, a SystemVerilog mailbox is typeless and hence can send and receive objects of mixed data-types. Although this is a good feature, it can result in type mismatches during simulation time and result in errors. To constrain the mailbox to accept and send objects of a fixed data-type, it can be parameterized to that particular data-type.
In the example shown below, we first create an alias for mailboxes that can send and receive strings using the
typedef construct. Although this step is optional, it is a good practice to avoid type mismatches between different components from coding errors. Consider that comp1 sends a few strings to comp2 via this mailbox. Naturally both classes need to have a mailbox handle which needs to be connected together and this is best done at the top level or the module where these two classes are instantiated.
// Create alias for parameterized "string" type mailbox typedef mailbox #(string) s_mbox; // Define a component to send messages class comp1; // Create a mailbox handle to put items s_mbox names; // Define a task to put items into the mailbox 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; // Create a mailbox handle to receive items s_mbox list; // Create a loop that continuously gets an item from // the mailbox task receive (); forever begin string s; list.get(s); $display ("[%0t] Comp2: Got %s", $time, s); end endtask endclass // Connect both mailbox handles at a higher level module tb; // Declare a global mailbox and create both components s_mbox m_mbx = new(); comp1 m_comp1 = new(); comp2 m_comp2 = new(); initial begin // Assign both mailbox handles in components with the // global mailbox m_comp1.names = m_mbx; m_comp2.list = m_mbx; // Start both components, where comp1 keeps sending // and comp2 keeps receiving fork m_comp1.send(); m_comp2.receive(); join end endmodule
ncsim> run  Comp1: Put name_0  Comp2: Got name_0  Comp1: Put name_1  Comp2: Got name_1  Comp1: Put name_2  Comp2: Got name_2 ncsim: *W,RNQUIE: Simulation is complete.
Matching different type mailboxes
Let's see what would have happened if the SystemVerilog mailboxes were parameterized to different data-types. Consider comp1 to have
string type and comp2 to have a
byte type mailbox.
class comp2; mailbox #(byte) list; ... endclass module tb; s_mbox m_mbx; ... initial begin m_comp1.names = m_mbx; m_comp2.list = m_mbx; end endmodule
This would result in a compile time error, and allow us to revisit the testbench code to correct the type mismatch.
m_comp2.list = m_mbx; | ncvlog: *E,TYCMPAT (testbench.sv,34|25): assignment operator type check failed (expecting datatype compatible with 'mailbox' but found '$unit::s_mbox (mailbox)' instead).