Interprocess synchronization and communication
We will discuss the following:
— Named events
High-level and easy-to-use synchronization and communication mechanisms are essential to control thekinds of interactions that occur between dynamic processes used to model a complex system or a highlyreactive testbench.
The basic synchronization mechanism is the named event type, along with the event trigger and eventcontrol constructs (i.e., -> and @). This type of control is limited to static objects. It is adequate forsynchronization at the hardware level and simple system level, but falls short of the needs of a highlydynamic, reactive testbench.
SystemVerilog also provides a powerful and easy-to-use set of synchronization and communicationmechanisms that can be created and reclaimed dynamically. This set comprises of a semaphore built-inclass, which can be used for synchronization and mutual exclusion to shared resources, and a mailbox builtinclass, which can be used as a communication channel between processes.
Semaphores and mailboxes are built-in types; nonetheless, they are classes and can be used as base classesfor deriving additional higher level classes. These built-in classes reside in the built-in stdpackage; thus, they can be redefined by user code in any other scope.
Conceptually, a semaphore is a bucket. When a semaphore is allocated, a bucket that contains a fixednumber of keys is created. Processes using semaphores must first procure a key from the bucket before theycan continue to execute. If a specific process requires a key, only a fixed number of occurrences of thatprocess can be in progress simultaneously. All others must wait until a sufficient number of keys is returnedto the bucket. Semaphores are typically used for mutual exclusion, access control to shared resources, andbasic synchronization.
An example of creating a semaphore is as follows:
Semaphore is a built-in class that provides the following methods:
— Create a semaphore with a specified number of keys: new()
— Obtain one or more keys from the bucket: get()
— Return one or more keys into the bucket: put()
— Try to obtain one or more keys without blocking: try_get()
Semaphores are created with the new() method.
The prototype for new() is as follows:
function new(intkeyCount = 0 );
The semaphore put() method is used to return keys to a semaphore.
The prototype for put() is as follows:
function void put(intkeyCount = 1);
The semaphore get() method is used to procure a specified number of keys from a semaphore.
The prototype for get() is as follows:
taskget(intkeyCount = 1);
The try_get() method attempts to retrieves a message from a mailbox without blocking.
The prototype for try_get() is as follows:
functioninttry_get( ref singular message );
The peek() method copies a message from a mailbox without removing the message from the queue.
The prototype for peek() is as follows:
taskpeek( ref singular message );
The message can be any singular expression, and it shall be a valid left-hand expression.
The try_peek() method attempts to copy a message from a mailbox without blocking.
The prototype for try_peek() is as follows:
functioninttry_peek( ref singular message );
The message can be any singular expression, and it shall be a valid left-hand expression.
The default mailbox is typeless, that is, a single mailbox can send and receive any type of data. This is a verypowerful mechanism, which, unfortunately, can also result in run-time errors due to type mismatches (typesnot equivalent) between a message and the type of the variable used to retrieve the message. Frequently, amailbox is used to transfer a particular message type, and, in that case, it is useful to detect type mismatchesat compile time.
Parameterized mailboxes use the same parameter mechanism as parameterized classes, modules,and interfaces:
mailbox#(type = dynamic_type)
wheredynamic_type represents a special type that enables run-time type checking (the default).
A parameterized mailbox of a specific type is declared by specifying the type:
typedef mailbox #(string) s_mbox;
s_mboxsm = new;
sm.put( “hello” );
sm.get( s ); // s <- “hello”
Parameterized mailboxes provide all the same standard methods as dynamic mailboxes: num(), new(),
get(), peek(), put(), try_get(), try_peek(), try_put().
The only difference between a generic (dynamic) mailbox and a parameterized mailbox is that for a
parameterized mailbox, the compiler verifies that the calls to put, try_put, peek, try_peek, get, andtry_get methods use argument types equivalent to the mailbox type so that all type mismatches are caughtby the compiler and not at run time.
An identifier declared as an event data type is called a named event. A named event can be triggeredexplicitly. It can be used in an event expression to control the execution of procedural statements in the samemanner as event controls described. A named event may also be used as a handle assigned fromanother named event.
A named event provides a handle to an underlying synchronization object. When a process waits for anevent to be triggered, the process is put on a queue maintained within the synchronization object. Processescan wait for a named event to be triggered either via the @ operator or by the use of the wait() construct toexamine their triggered state.
Triggering an event
Named events triggered via the -> operator unblock all processes currently waiting on that event. Whentriggered, named events behave like a one shot, i.e., the trigger state itself is not observable, only its effect.This is similar to the way in which an edge can trigger a flip-flop, but the state of the edge cannot beascertained, i.e., if (posedgeclock) is illegal.
Nonblocking events are triggered using the ->> operator. The effect of the ->> operator is that the statementexecutes without blocking, and it creates a nonblocking assign update event in the time in which the delaycontrol expires or the event control occurs. The effect of this update event shall be to trigger the referencedevent in the nonblocking assignment region of the simulation cycle.
Waiting for an event
The basic mechanism to wait for an event to be triggered is via the event control operator, @.
The @ operator blocks the calling process until the given event is triggered.
For a trigger to unblock a process waiting on an event, the waiting process shall execute the @ statementbefore the triggering process executes the trigger operator, ->. If the trigger executes first, then the waitingprocess remains blocked.
Persistent trigger: triggered property
SystemVerilog can distinguish the event trigger itself, which is instantaneous, from the event’s triggeredstate, which persists throughout the time step (i.e., until simulation time advances). The triggered eventproperty allows users to examine this state.
The triggered property is invoked using a method-like syntax:
The triggered event property evaluates to true (1’b1) if the given event has been triggered in the currenttime step and false (1’b0) otherwise. If event_identifier is null, then the triggered event propertyevaluates to false.
The triggered event property is most useful when used in the context of a wait construct:
wait( hierarchical_event_identifier.triggered )
Using this mechanism, an event trigger shall unblock the waiting process whether the wait executes beforeor at the same simulation time as the trigger operation. The triggered event property, thus, helps eliminatea common race condition that occurs when both the trigger and the wait happen at the same time. A processthat blocks waiting for an event might or might not unblock, depending on the execution order of the waitingand triggering processes. However, a process that waits on the triggered state always unblocks, regardless ofthe order of execution of the wait and trigger operations.
eventdone, blast; // declare two new events
eventdone_too = done; // declare done_too as alias to done
tasktrigger( event ev );
@ done_too; // wait for done through done_too
#1 trigger( done ); // trigger done through task trigger
wait( blast.triggered );
The first fork in the example shows how two event identifiers, done and done_too, refer to the samesynchronization object and also how an event can be passed to a generic task that triggers the event. In theexample, one process waits for the event via done_too, while the actual triggering is done via the triggertask that is passed done as an argument.
In the second fork, one process can trigger the event blast before the other process (if the processes in the
fork…join execute in source order) has a chance to execute, and wait for the event. Nonetheless, thesecond process unblocks and the fork terminates. This is because the process waits for the event’s triggeredstate, which remains in its triggered state for the duration of the time step.
Event sequencing: wait_order()
The wait_orderconstruct suspends the calling process until all of the specified events are triggered in thegiven order (left to right) or any of the untriggered events are triggered out of order and thus causes theoperation to fail.
Preceding events are not limited to occur only once. In other words, once an event occurs in the prescribedorder, it can be triggered again without causing the construct to fail.
Only the first event in the list can wait for the persistent triggered property.
The action taken when the construct fails depends on whether the optional action_blockelse statement (thefail statement) is specified. If it is specified, then the given statement is executed upon failure of theconstruct. If the fail statement is not specified, a failure generates a run-time error.
wait_order( a, b, c);
suspends the current process until events a, b, and c trigger in the order a –> b –> c. If the events triggerout of order, a run-time error is generated.
wait_order( a, b, c ) else $display( “Error: events out of order” );
In this example, the fail statement specifies that, upon failure of the construct, a user message be displayed,but without an error being generated.
wait_order( a, b, c ) success = 1; else success = 0;
Operations on named event variables
An event is a unique data type with several important properties. Named events can be assigned to oneanother. When one event is assigned to another, the synchronization queue of the source event is shared byboth the source and the destination event. In this sense, events act as full-fledged variables and not merely aslabels.
When one event variable is assigned to another, the two become merged. Thus, executing -> on either eventvariable affects processes waiting on either event variable.
eventa, b, c;
a = b;
->a; // also triggers b
->b; // also triggers a
a = c;
b = a;
->a; // also triggers b and c
->b; // also triggers a and c
->c; // also triggers a and b
When events are merged, the assignment only affects the execution of subsequent event control or waitoperations. If a process is blocked waiting for event1 when another event is assigned to event1, thecurrently waiting process shall never unblock. For example:
T1: forever @ E2;
T2: forever @ E1;
E2 = E1;
This example forks off three concurrent processes. Each process starts at the same time. Thus, at the sametime that processes T1 and T2 are blocked, process T3 assigns event E1 to E2. As a result, process T1 shallnever unblock because the event E2 is now E1. To unblock both threads T1 and T2, the merger of E2 and E1must take place before the fork.
When an event variable is assigned the special null value, the association between the event variable and
the underlying synchronization queue is broken. When no event variable is associated with an underlying
synchronization queue, the resources of the queue itself become available for reuse.
Triggering a null event shall have no effect. The outcome of waiting on a null event is undefined, and
implementations can issue a run-time warning.
eventE1 = null;
@ E1; // undefined: might block forever or not at all
wait( E1.triggered ); // undefined
-> E1; // no effect
Event variables can be compared against other event variables or the special value null. Only the followingoperators are allowed for comparing event variables:
— Equality (==) with another event or with null
— Inequality (!=) with another event or with null
— Case equality (===) with another event or with null (same semantics as ==)
— Case inequality (!==) with another event or with null (same semantics as !=)
— Test for a Boolean value that shall be 0 if the event is null and 1 otherwise
if( E1 ) // same as if ( E1 != null )
E1 = E2;
if( E1 == E2 )
$display( “E1 and E2 are the same event” );