A SystemVerilog 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 ?
A SystemVerilog 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 get
or put
is executed on the mailbox and may depend on the start and finish times of the other methods.
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.
SystemVerilog Mailbox Functions and Methods
Function | Description |
---|---|
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 |
Parameterized mailboxes
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.
Example
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 [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.
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).