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:

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.

 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.

SystemVerilog Mailbox Functions and Methods

FunctionDescription
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
 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.

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.

 Simulation Log
 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).