How to do Inter-Thread Communications (ITC) in SystemVerilog Testbench?

In SystemVerilog testbench, concurrent threads require communication to establish control of sequence of execution. There are 3 types of inter-thread communications (ITC):

  1. Event based ITC
  2. Resource sharing ITC
  3. Data passing ITC

Event-based ITC

Synchronized operation of concurrent threads is achieved via event variables: a thread waits for an event to be triggered, and an executing thread triggers the event to enable the waiting thread to be placed into the READY queue.

An example is shown below:

event A;
fork
    task0();
    task1();
join

task task0();
    $display(“task0 is still waiting for event A to be triggered!”);
    wait(A.triggered);
    $display(“task0 has seen event A triggered!”);
endtask

task task1();
    $display(“task1 has not yet triggered event A!”);
    -> A;
    $display(“task1 has triggered event A!”);
endtask

Resource Sharing ITC

Concurrent threads synchronization can be done via access to shared resources: A thread requests for a shared resource before executing a critical section of code; If the resource is unavailable, the requesting thread waits; When the requested resource becomes available, the thread resumes execution.

A concept called semaphore can be used to indicate resource availability. A semaphore is a bucket in which keys can be deposited and removed: a thread tries to acquire keys from a semaphore bucket, and its execution waits until keys requested are available in the semaphore bucket; When the keys become available, the thread execution resumes and the keys are removed from the semaphore bucket.

Semaphores are mainly used to prevent race conditions, where multiple threads attempt to access the same hardware signal or using the same software resource.

Semaphores are supported via built-in semaphore class:

class semaphore;
    function new(int keyCount = 0);
    task put(int keyCount = 1);

    // get() task is blocking
    task get(int keyCount = 1);

    // try_get() is non-blocking: 1 for success and 0 for failure
    task int try_get(int keyCount = 1);
endclass

We show a few example of semaphore class usage below:

// semaphore buckets are created using the constructor new()
semaphore sem;
sem = new(3);  // bucket with 3 keys is created

// Acquiring keys
sem.get(2); // bucket with 1 key left
if (!sem.try_get(2)) // does not block
    $display(“Not enough keys left!”);
sem.get(2); // blocks until 2 keys available

// Returning / Creating keys
sem.put (1); // return 1 key

The get() implementation is a greedy algorithm. If there are not enough keys to serve the get() call, the keys are left in the bucket without reservation. Use semaphore schemes which require a get() for multiple keys with caution. If implemented improperly, it may cause a starvation issue.

For put(), there is no limitation on the number of keys that can be put in a bucket. It is also not required to return the same number of keys as it originally takes. A thread can even deposit keys into a semaphore bucket without having taken any. This could be useful when resources do not exist at the start of simulation. During simulation, as the resources are created, keys are deposited into the semaphore buckets to represent the dynamically created resources.

Data Passing ITC

Data passing ITC can be achieved using mailbox: A thread sends data by putting messages in a mailbox; Another thread retrieves data by getting messages from the mailbox. If a message is not available, the thread can wait until there is a message to retrieve; the thread resumes execution once the message becomes available.

Mailboxes are supported via built-in SystemVerilog mailbox class:

class mailbox #(type T = dynamic_type); // message data type can be parameterized
    function new(int bound = 0);
    function int num(); // return number of messages in the mailbox

    // all functions below are blocking
    task put(T message); // block if exceeds bound
    task get(ref T message); // block if mailbox is empty
    // wait if no message; get a copy of content w/o removing the content from the mailbox
    task peek(ref T message);

    // all try_* functions are non-blocking
    // return 0 if mailbox is full
    function int try_put(T message);
    // if successful, return # of messages in mailbox before try_get; 0 if no message
    function int try_get(ref T message);
    // if successful, return # of messages in mailbox before try_peek; 0 if no message
    function int try_peek(ref T message); 
endclass

Subscribe

Enter your email to get updates from us. You might need to check the spam folder for the confirmation email.

Leave a comment