Why we need smarter testbenches?
“Less is more” no longer holds true and everyone needs a faster, smarter, power efficient gadget in the blink of an eye (well almost!). There is always a pressure on engineering teams to churn out high quality, smart and sleek designs. But if you belong to the engineering side of the world, you know that anything that entails complex designs needs a lot more than just hard work. No wonder the chip designers and verification engineers are striving towards building easily customizable and reusable designs and testbenches respectively. By adopting such an approach, the designers can catalyse the process of feature addition. The verification engineering team, on the other hand, can benefit immensely as their testbenches can be tailored to verify new features without investing too much effort and time.
The layered testbench architecture has played a pivotal role in making the modern day testbenches reusable, scalable and customizable. This approach, unlike the legacy approach, considers each testbench component as an ‘object’ that can be replicated, modified/enhanced or even destroyed easily. The higher layers of this architecture are blissfully unaware of the lower level implementation details, thus making them more generic. Layered testbenches can be plugged and played with much ease too. SystemVerilog brings an assortment of in-built features that accelerate the testbench building process. For e.g., the ‘modport’ feature makes the job of signal connection a lot easier, similarly, the ‘queue’ makes the job of modelling a FIFO simple and error free. While there are a wide array of useful features offered by SystemVerilog, we shall discuss the ‘mailbox’ feature here.
The above figure serves as a pictorial representation of a ‘mailbox’. The generator and driver are marked for illustration purposes, they could be ‘any’ concurrent processes.
What is a ‘Mailbox’ in SV?
‘Mailbox’ is a built-in class in SystemVerilog just as a ‘queue’, ‘semaphore’ or ‘clocking block’. These built-in classes are equipped with unique and useful properties that make testbench construction simpler. As the name suggests, the ‘mailbox’ feature in SystemVerilog works just like a normal mailbox. A mail sent by the sender goes to a mailbox which the receiver must retrieve. It is possible that the mail wasn’t delivered when the receiver checked his mailbox. In that case, the receiver can either choose to wait for the mail or to check the mailbox on subsequent trips. Just as a mailbox works as a ‘controlled’ mode of communication of data/information between a sender and receiver, the SystemVerilog ‘mailbox’ feature serves as a mode of communication between processes/threads. Unlike the regular mailbox the SV ‘mailbox’ only carries references or handles of the objects. Hence one must be very careful in passing data via the ‘mailbox’.
The generator can generate multiple data packets in one go. In order to pass these packets to the driver, it would need a medium to communicate with the driver. This can be done by calling a method wading through the hierarchical path to the driver. But that would mean that the generator needs to know the path to the driver and its implementation details (such as the method in the driver that facilitates the data transfer). This is not a great idea as this would hinder code reusability as well as force the generator to work in synchronism with the driver. In case of a single generator feeding data to multiple drivers, this kind of a mechanism would be an utter failure. Thankfully the ‘mailbox’ feature offered by SV takes care of the interprocess communication like a pro.
SystemVerilog ‘mailbox’ works similar to a ‘queue’ with the exception that it offers a lot more than simply retrieving and pushing data from or into the queue. One can neither push or pop data to or from a random index into the ‘mailbox’ queue, it happens only in the FIFO fashion. Since the ‘mailbox’ is a shared means of data transfer, the sender cannot send data unless the receiver empties the ‘mailbox’. In order to ensure that the communication via ‘mailbox’ is smooth and error-free, the ‘mailbox’ must have a mechanism that prevents the sending or receiving of data unless it is free. ‘Semaphore’ is thus ingrained into a ‘mailbox’ to ensure that the none of the concurrent threads communicating via mailboxes can access the ‘mailbox’ unless it has the ‘key’ to send or retrieve data.
Semaphores hold the ‘key’ to interprocess synchronization, both literally and figuratively. A semaphore, when instantiated in the code, works like a bunch of keys or a single key. This controls as to ‘which’ process amongst the several parallel processes gets to access a memory location or drive a set of signals, for an instance. In the context of a ‘mailbox’, the key/keys determine as to ‘when’ is the receiver ready to take in data or ‘when’ can the sender transmit data. Thus the in-built semaphore in a ‘mailbox’ serves as a ‘handshake’ between two or more concurrent processes. Additionally, since the ‘mailbox’ is a shared means of communication, the handshaking would curb the chances of data contention.
Mailboxes can be ‘parameterized’ to carry only specific data types. By default, they are ‘typeless’, which implies that they can serve to carry any data type. Mailboxes can also be ‘bounded’, which means one can set the size of the internal FIFO of the ‘mailbox’. By default, it is ‘unbounded’, which means it is infinite in size. Parameterizing a ‘mailbox’ is often preferred to avoid data ‘‘type’ mismatches between the sender and receiver. Also, the ‘mailbox’ is most often bounded in size so that the sender does not generate more than the receiver can process. No point in eating more than your appetite. Here is the ‘mailbox’ in a nutshell for a quick reference!
Mailbox | YES/NO |
Is ‘mailbox’ a ‘built-in’ class in SystemVerilog | YES |
Is ‘mailbox’ a means for interprocess communication? | YES |
Can it transfer data between two concurrent processes | YES |
Do ‘mailboxes’ carry data handles and not the actual data objects? | YES |
Is it like a queue? | NO |
Does it work like a FIFO | YES |
Can the ‘sender’ put data into a ‘random’ location in the ‘mailbox’ queue | NO |
Can the ‘receiver’ pick data from a ‘random’ location of the ‘mailbox’ queue | NO |
Is it possible for the ‘mailbox’ to tell the sender ‘when’ it is OK to ‘send’ data? | YES |
Is it possible for the ‘mailbox’ to tell the ‘receiver’ when it is OK to receive data? | YES |
Do mailboxes have ‘semaphores’ in-built into them? | YES |
Can we set mailboxes to contain/carry only a ‘certain’ type of data? | YES |
Can we set the ‘mailbox’ FIFO size to be of a ‘defined’ value? | YES |
Does using a ‘mailbox’ free the higher layers from lower details in the code | YES |
Does ‘mailbox’ help in interprocess synchronization? | YES |
‘Mailbox’ and the in-built methods
‘Mailbox’ comes equipped with a bunch of methods that makes the task of deploying them a lot more easier into your testbench code. Let us take a quick look at these methods and their functions in brief
Method Name | Function |
new() | Creates a mailbox, or in other words, allocates memory to the mailbox |
put() | To put a message into the mailbox. This method is blocking in nature, which means that the sender cannot send/put data into the mailbox unless the mailbox is empty/ ‘free’. Semaphores at play! If the ‘mailbox’ is bounded to contain 8 data packets for an instance. Any attempt to put more than 8 packets into the mailbox leads to a block. The put() method unblocks only when the receiver creates room by removing data. |
get() | To retrieve a message from the mailbox. The message is extracted from the mailbox by this method. This can be compared to a person taking his letter away from the mailbox leaving no traces behind. This method is blocking in nature. It can be equated to a semaphore controlling two parallel processes that write to and read from the same memory location. If the data has not been written (put), the receiver cannot read (get). |
try_put() | This method ‘checks’ if the mailbox is free to send data. In case the mailbox is full it returns a 0, but if it is free it indicates by returning an integer value. This method is non-blocking in nature. |
try_get() | This method ‘checks’ if there is data in the mailbox to be processed. If the mailbox is empty, the method returns 0. While if the mailbox contains data, an integer is returned. This method is non-blocking in nature. |
num() | This method returns the size of the mailbox. |
peek() | This method peeks into the mailbox for data. It is blocking in nature. The difference between a get() and a peek() is that the latter does not remove the data from the mailbox. For e.g. the data that is sent out by the DUT is collected by the monitor. It is very likely that the monitor uses the same mailbox to send this data to the scoreboard and coverage. This is when peek()/try_peek() comes in handy as the receiving entities are more than 1 and removing the data from the mailbox unless it has been processed by the receivers, is not the right thing. |
try_peek() | This method ‘checks’ if there is data in the mailbox. If there is data it gets a copy of the data, it does not remove it. If there is no data in the mailbox, this method returns 0. It is a non-blocking method. |
Summary
A technological marvel takes in a lot more than just an idea. Plenty of research, innovation and the alacrity to find ways and means to slash time to market are some of the major reasons behind new technologies cropping up every other day. The insatiable hunger for upgrades is there to stay and so is the pressure on engineers to build designs better than the last. A major chunk of the efforts that go into a chip tapeout cycle goes into its verification. Hence verification engineers too must constantly come up with ways to get innovative at building better testbenches. The mass adoption of the layered testbench architecture, HVLs and their in-built features, methodologies and the built-in utilities are instrumental in building testbenches that can be easily modified and reused.
SystemVerilog offers a multitude of features that carry out a wide range of functions such as bunching the signals into a modport or grouping signals that run on the same clock into a clocking block or modelling a queue wherein data can be pushed or popped in random order or even ensuring the communication between concurrent processes is error free. The ‘mailbox’ is one of them and it works like a regular mailbox with the difference that in verification it works as a means of data transfer between two concurrent processes. A ‘mailbox’ works like a FIFO and is a shared resource between the sender and the receiver. The concept of ‘semaphore’ is ingrained into a ‘mailbox’ and thus it can also be controlled as to ‘when’ the data can be sent or received. It has built-in methods with which it is easy to create a ‘mailbox’ and send or receive data. Mailboxes can be configured to carry only a specific data type as by default they are ‘typeless’. They can be bounded to a specific size as by default they are ‘boundless’.Thus,‘mailbox’ is indeed a very significant feature as it single-handedly plays the medium of data transfer as well as imposes an ‘atomic’ control over the sender and receiver for error-free interprocess communication.