System Verilog allows us to read and write into files in the disk.
How to open and close a file ?
A file can be opened for either read or write using the
$fopen() system task. This task will return a 32-bit integer handle called a file descriptor. This handle should be used to read and write into that file until it is closed. The file descriptor can be closed with the
$fclose() system task. No further reads or writes to the file descriptor is allowed once it is closed.
In the code shown below, we will declare a
int variable called fd to hold the file descriptor. fd is initially zero, and gets a valid value from
$fopen() and can be checked to see if the file opened successfully. The file is finally closed when
$fclose() is executed.
module tb; initial begin // 1. Declare an integer variable to hold the file descriptor int fd; // 2. Open a file called "note.txt" in the current folder with a "read" permission // If the file does not exist, then fd will be zero fd = $fopen ("./note.txt", "r"); if (fd) $display("File was opened successfully : %0d", fd); else $display("File was NOT opened successfully : %0d", fd); // 2. Open a file called "note.txt" in the current folder with a "write" permission // "fd" now points to the same file, but in write mode fd = $fopen ("./note.txt", "w"); if (fd) $display("File was opened successfully : %0d", fd); else $display("File was NOT opened successfully : %0d", fd); // 3. Close the file descriptor $fclose(fd); end endmodule
It is important to close all open files before end of simulation to completely write contents into the fileSimulation Log
ncsim> run Open failed on file "./note.txt". No such file or directory File was NOT opened successfully : 0 File was opened successfully : -2147483645 ncsim: *W,RNQUIE: Simulation is complete.
Every now and then you come across the need to avoid testbench recompilation, and instead be able to accept values from the command line just like any scripting language like bash or perl would do. In SystemVerilog, this information is provided to the simulation as an optional argument always starting with the
+ character. These arguments passed in from the command line are accessible in SV code via the following system functions called as plusargs.
$test$plusargs (user_string) $value$plusargs (user_string, variable)
$test$plusargs is typically used when a value for the argument is not required. It searches the list of plusargs for a user specified string. A variable can also be used to specify the string, and any null character will be ignored. If the prefix of one of the supplied plusargs matches all characters in the provided string, the function will return a non-zero integer, and otherwise zero.
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
Semaphore is just like a bucket with a fixed number of keys. Processes that use a semaphore must first get a key from the bucket before they can continue to execute. Other proceses must wait until keys are available in the bucket for them to use. In a sense, they are best used for mutual exclusion, access control to shared resources and basic synchronization.
semaphore is a built-in class and hence it should be used just like any other class object. It has a few methods with which we can allocate the number of keys for that
semaphore object, get and put keys into the bucket.
Sometimes we come across scenarios where we want the solver to randomly pick one out of the many statements. The keyword
randcase introduces a
case statement that randomly selects one of its branches. The case item expressions are positive integer values that represent the weights associated with each item. Probability of selecting an item is derived by the division of that item's weight divided by the sum of all weights.
randcase item : statement; ... endcase
The sum of all weights is 9, and hence the probability of taking the first branch is 1/9 or 11.11%, the probability of taking the second branch is 5/9 or 55.56% and the probability of taking the last branch is 3/9 or 33.33%.