Verification has typically been carried out using standardized Hardware Description Languages (HDLs), Hardware Verification Languages (HVLs), and a few procedural languages that are available as a part of the HDL category. Although it is possible to use a procedural language like 'C' for verification, it is nearly impossible to run such a language as an independent thread (for instance, moving simulation time forward without passing control back to Verilog is not possible).
There are occasions when the infrastructure available outside the simulation environment may be required to interface with the simulation environment. Also, there are occasions when one has to develop the verification infrastructure in languages like PERL. One example of such a testbench is a test layer built in a language like PERL interacting with the rest of the simulation infrastructure developed in the HDL/HVL domain.
Just keeping the test layer outside of the HDL/HVL domain yields many advantages:
- Test writing and execution can be moved outside of the traditional HDL/HVL domains. This enables testing to be carried out using procedural or scripting languages.
- With the right abstraction, a diagnostic team can use the simulation environment for code development.
- Test failures on actual hardware can be re-created in the simulation environment with little effort.
- It's possible to feed live control and data traffic into the environment.
One technique that can be used to connect an external process to the simulation environment is to use the inter-process communication available under UNIX platforms. This paper discusses how to connect an external/unrelated process to a simulator environment using such inter-process communication. The traditional way of designing a testbench is shown in Fig 1, where the entire infrastructure required for testing resides within the simulation environment using a combination of HDL, HVL and C.
1. The traditional verification setup uses hardware description languages.
By comparison, a case where the testbench infrastructure is supplemented by additional code that is present in an external process is shown in Fig 2. This process is running independent of the simulator thread. A couple of points worth noting here are as follows:
- The testbench architecture is highly generic in nature. There is no hard and fast rule as to what can exist within the simulation thread and what can exist outside of it.
- This approach comes at an additional cost because additional code is required to enable the two processes to communicate with each other.
2. An external process augments the verification infrastructure.
Using a UNIX pipe to connect simulation infrastructure to an external process
Inter-process communication (IPC) enables two (or more) processes to communicate. With IPC, there are different techniques that may be used. Although inter-process communication using a pipe is the most primitive, it is the simpler one to work with.
A very generic model of a process interacting with a simulator is presented in Fig 3. This shows the logical units that comprise a system when an external process consisting of the verification infrastructure interacts with the simulation infrastructure.
3. A process interacting with a simulator can be represented in this manner
In this case, the information flow is one way – from the external process to the simulation environment. Needless to say, an additional pipe is required if the information has to flow in the opposite direction. The core process contains the verification infrastructure. The IPC interface on the external process accepts new commands from the core process and puts them into the pipe. Similarly the IPC interface in the simulator monitors the presence of new commands and makes them available to the simulation environment.
Having understood a generic testbench which contains an external process interacting with the simulation environment, let us now consider what a simple test layer implemented in an external process looks like.
Test layer implemented in an external process
A simple hypothetical arbiter design is used in Fig 4 to explain the underlying mechanics involved when the test layer is implemented in an external process. The design chosen is deliberately kept simple as the focus is on methodology and not on the design functionality.
4. The test layer communicates with the testbench through a pipe
In the testbench above, the interface with the external process is implemented using a named pipe. The infrastructure in the simulation is almost similar to what one would expect in the traditional environment; however, it contains an additional IPC receive interface. It waits for commands in the pipe and makes these commands available to the rest of the simulation environment. The external process contains the core test logic and the IPC write interface.
Before the IPC transmit interface can write commands to the pipe and the IPC receive interface can start waiting for commands in the pipe, the pipe itself has to be created.
This is achieved on the Verilog side using a PLI call and using the IPC system call as follows.
The pipe is created by the C code called using the PLI call pipe_init(). The function has a file handle as the only parameter that is returned by this function. The code would look similar to the following:
// Open the pipe at the beginning of the simulation
$display ("@ %0d NS Pipe created successfully ", $time);
The following code first creates a named pipe using the IPC call mkfifo. The first parameter to this function is the path for the named pipe. If the pipe is created successfully then the function proceeds to open the pipe using the IPC call open () in read-only mode. Note that in this example, the simulation will only get the data from the external process, so the pipe has to be opened in read-only mode. The open function returns the handle to the named pipe. This handle is then returned back to the calling function which is in the Verilog domain.
void pipe_init ()
unsigned char test_data;
int fd, ret_val;
// create pipe
ret_val = mkfifo(TEST2ENV, 0666);
if((ret_val == -1) && (errno != EEXIST))
perror("Error creating the named pipe");
// pipe created successfully, now open the pipe for reading
fd = open (TEST2ENV, O_RDONLY);
Once the pipe is opened for reading, the Verilog side waits for any command available in the pipe (from the external process). The example code to perform this function is as follows:
// check if a new request is available from input PIPE
// this is a blocking call, it returns only when the external process
// writes commands to the pipe
$get_req_sts (fd, rqst_vld, rqst_data);
$display ("New request initiated by test thread %h", rqst_data);
$display ("@ %0d ns NS Initiating request1 to the arbiter", $time);
$display ("Simulation end detected ... \n");