Randomization

Random Variables


rand modifier
Variables declared with keyword rand are random variables. Their values are uniformly distributed over their range.

Syntax:



randc modifier
Variables declared with keyword randc are random-cyclic variables. Their random values are not repeated until every possible value has been assigned.
Random-cyclic can only be of type bit or enumerated types, and can be limited to a maximum size.

Syntax:



Contraint Blocks

  • The values of the random variables are determined using constraint expressions that are declared using constraint blocks.
  • Constraint blocks are class members, like tasks, functions and variables.
  • Constraint block names must be unique within a class
External Constraint Blocks
Constraint blocks can be declared outside a class declaration, just like external task and function bodies.








Inheritance
  • Constraints follow the same general rules for inheritance as class variables, tasks and functions.
  • A constraint in a derived class that uses the same name as a constraint in its parent class overrides the constraint in the base class.








Set Membership
  • You can create sets of values with the inside operator.
  • All values(either single value or values ranges), have an equal probability of being chosen.




  • If you want any value, as long as it is not inside a set, invert the constraint with NOT operator.




  • You can use $ as a shortcut for the minimum and maximum values for a range

Threads in SystemVerilog

SystemVerilog has following ways to create threads:

  1. fork...join
  2. fork...join_none
  3. fork...join_any
fork...join
















Output:
@0: Before fork...join
@0: parallel start
@10: parallel after #10
@30: sequential after #30
@40: sequential after #10
@50: parallel after #50
@50: after join

fork...join_none
A fork...join_none block schedules each statement in the block, but execution continues in the parent thread.

Output:
@0: Before fork...join
@0: parallel start
@0: after join_none
@10: parallel after #10
@30: sequential after #30
@40: sequential after #10
@50: parallel after #50


fork...join_any
A fork...join_any block schedules each statement in the block. Then, when the first statement completes, execution continues in the parent thread. All other remaining threads continue.


















Output:
@0: Before fork...join_any
@0: parallel start
@0: after join_any
@10: parallel after #10
@30: sequential after #30
@40: sequential after #10
@50: parallel after #10

Waiting for all Spawned Threads
In SystemVerilog, when all the initial blocks in the program are done, the simulation exits. You may need to spawn many threads, which might still be running. Use the wait fork statement to wait for all child threads












Disabling Threads

  • Disabling a Single Thread











  • Disabling Multiple Threads

Inter Process Communication: Events

In Verilog, events are static objects that can be triggered via -> operator, and processes can wait for an even to be triggered via the @ operator.

SystemVerilog enhances the Verilog event in several ways.

  • The triggered state of Verilog named events has no duration, whereas in SystemVerilog this state persists throughout the time-step in which the event triggered.
  • In SystemVerilog an event is a handle to a synchronization object that can be passes around to routines. This feature allows to share events across objects without having to make the events global.

Blocking on the Edge of an Event
































Output:
@0: 1: before trigger
@0: 2: before trigger
@0: 1: after trigger


In the above example, one initial block starts, triggers its event, and then blocks on the other event. The second block starts triggers its event(waking up the first), and then blocks on the first event. However, the second thread locks up because it missed the first event, as it is a zero-width pulse.


Instead of using an edge-sesitive block @event_1, use the level sensitive wait(eevent_1.triggerd( )). This does not block if the event has been triggered during this time step. Otherwise, it waits until the event is triggered.

















Output:
@0: 1: before trigger
@0: 2: before trigger
@0: 1: after trigger
@0: 2: after trigger

If you use wait(hndshk.triggered( )) in a loop, be sure to advance the time before waiting again. Otherwise, code will go in a zero delay loop as the wait continues over and over again on a single event trigger.








You need to put a delay in the above loop. The edge-sensitive delay statement continues once and only once every event trigger.








Passing Events
An event can be passes as an argument to a routine.


























Waiting for multiple events

  • Using wait fork


















  • Counting triggers





















  • Using thread count

Inter Process Communication: Semaphores

A semaphore allows you to control access to a resource. A semaphore is an equivalent of a key (or a set of keys) when a process tries to access a shared resource.

A semaphore is first associated with a resource that needs to be protected. Whenever a process wants to access this resource, it seeks a key from the semaphore. Depending on the availability of a key, a key is either allotted to the current process or not. If a key is alloted to a process, when this process is done with using the resource, it will give the key back to the semaphore and that key may be allotted to other processes who are waiting for a key.

Syntactically, semaphore is a built-in class that allows only certain pre-defined operations on the keys.

A semaphore declaration is shown below:




Built-in methods in Semaphore
There are 4 predefined methods inside a semaphore type class.


  1. new( ): Create a semaphore with specified number of keys.
  2. get( ): Obtain one or more keys from a sempahore and block until keys are available.
  3. try_get( ): Obtain one or more kets from a semaphore without blocking.
  4. put( ): Return one or more keys to a semaphore
new( )
Just as any other class, a semaphore needs a constructor.

The prototype declaration for new( ) is shown below:



  • The constructor new( ) has an integer argument key_count that can be used for creating a desired number of keys. 
  • The default value of key_count is 0. 
  • Upon success, the new( ) function returns the semaphore handle, otherwise, it returns null.

get( )
The get( ) task is used for obtaining one or multiple keys for a semaphore. 

The prototype declaration for get( ) is shown below:



  • The number of keys to procure is passed as the argument to get( ). 
  • The default value of this argument is 1.
  • If a process asks for certain number of kets and they are available, the call to get( ) returns and the execution continues.
  • If the required number of keys are not available, the call to get( ) blocks subsequent statements and waits for additional keys to be available. That is why get( ) is a task, not a function, and hence, can consume time.
  • All calls to get( ) are queued in a FIFO and keys are delivered in a first-come-first-serve basis.

try_get( )
If you do not want to block while trying to get keys for a semaphore, try_get( ) is your solution. Unlike get( ), try_get( ) is a function that checks for key availability and procures them if they are available (and returns 1). But, if they are not, try_get( ) does not block and returns 0.

The prototype for try_get( ) is shown below:



put( )
Now, suppose that a process is done with using a resource. According to the good code of conduct in the semaphore land, that process must return the keys (so that another process can use them). This is done by the put( ) task, where the number of returned kets are passes as an argument.

The prototype for put( ) is shown below:


Inter Process Communication: Mailboxes

A mailbox is a mechanism for Inter Process Communication(IPC).

Conceptually mailboxes operate like real mailboxes. A process may put a letter (which in this case is a data message) for another process in a mailbox. The message stays in the mailbox until the receiving process comes and retrieves it. If the receiving process checks the mailbox before the sending process puts the message in the mailbox, the receiving process either waits for the message or retrieves the message on subsequent trips to the mailbox.

A message in this case can be any singular type (i.e any variable other than an unpacked array, structure or union type). A non-parameterized mailbox can accept and retain messages of any singular type.

The only other restriction is that a message must be a valid left-hand side expression. For example, a net type, such as a wire, is not a valid message. So, you can use a single mailbox to handle various types of messages(from a single or multiple source).

Also, a mailbox can be bounded, meaning it can hold only a pre-specified maximum number of  messages. Or it can be unbounded, when it can hold all the messages that it receives.

Syntactically, mailbox is a built-in class that can be defined as






Non-parameterized mailboxes are typeless, that is, single mailbox can send and receive different types of data. Thus, in addition to the data being sent(i.e the message queue), a mailbox implementation must maintain the message data type placed by put( ). This is required in order to enable the run-time type checking.

Built-in methods in Mailbox


The class mailbox provides 8 methods that can be used for accessing a mailbox.
  1. new( ) : Create a mailbox with specified number of slots.
  2. num( ) : Find the number of messages in the mailbox
  3. get( ) : Retrieve a message, if available, from the mailbox. Block otherwise.
  4. try_get( ) : Retrieve a message from the mailbox without blocking.
  5. peek( ) : Copy a message, if available, from the mailbox. Block otherwise.
  6. try_peek( ) : Copy a message from the mailbox without blocking.
  7. put( ) : Put a message in the mailbox.
  8. try_put( ) : Put a message in the mailbox without blocking.

new( ):
The function new( ) is a constructor method for the class mailbox. 

The prototype declaration for new( ) is as shown



  • It takes one integer argument bound that describes the number of messages that this mailbox can hold.
  • If bound is 0, the mailbox can hold infinite number of messages. 
  • The default value of this argument is 0.


num( )
num ( ) returns the number of messages in a mailbox.

The prototype of num( ) is as follows:



  • This number changes whenever a process puts or retrieves a message in a mailbox.
  • num( ),  similar to new( ) is a function and hence non-blocking. 
  • It returns the number of messages in the mailbox and takes no argument.
  • The returned value should be used with care because it is valid only until the next get( ) or put( ) is executed on the mailbox.

get( )
The method get( ) retrieves a message, if available, from a mailbox.

The prototype declaration is as shown:



  • If a message is not available, a call to get( ) blocks. 
  • Also, since a message can be any singular type, if the actual type of the message retrieved does not match with the code that calls get( ), there will be a run-time error.
  • Note that a mailbox is a FIFO - the first message that's put in will be popped first.

try_get( )
This method try_get( ) is similar to get( ), except that it is non-blocking.

The prototype for try_get( ) is as shown:



  • If a message is available, it will retrieve the message (and will return 1).
  • If a message is not available, it will return 0 and, 
  • If a message of wrong type is available (indicating a possible programming error), it will return -1.

peek( )
Many times, it is useful to look into a message but without yanking it from the mailbox. The peek( ) method provides such a facility. It lets you copy a message from the mailbox, without actually deleting it from the mailbox.

The prototype of peek( )  is shown below:



  • If there is no message in the mailbox, the behavior of peek( )  is identical to get( ) - it blocks until a message is available in the mailbox.

try_peek( )
The try_peek( ) method is to peek( ), what try_get( ) is to get( ) - it copies a message from a mailbox, if one is available. However, if a message is not available, it does not block.

The prototype for try_peek( ) is shown below:



  • The try_peek( ) method returns 
    • 1, if a message is available, 
    • 0, if a message is not available and 
    • -1, if a message of proper type is not available.

put( ) 
A process uses the put( ) method to place a message in a mailbox.

The prototype for put( ) method is shown below:



  • This method is a task and if there are not enough places to put the message in a bounded mailbox, the call will block (i.e wait until a place is available).

try_put( )
This method try_put( ) is very similar to put( ), except that it does not block if there is no place available in the mailbox.

The prototype for try_put( ) method is shown below:



Following is an example for mailbox and the usage of its built-in methods.








































Parameterized Mailboxes
General mailboxes can hold any type of message. As long as a message matches with the expected type during retrieval, everything works seamlessly.

If it is known that a mailbox will be used with only one type of message, say string type, the mailbox definition can be parameterized. This is shown below.









The usefullness of paramterized mailboxes is that in a case of a type mismatch (as in the incorrect example above), it will be a compilation error, not a runtime error.




Thread Synchronization Using Mailboxes
Producer-Consumer without synchronization




































The producer runs to completion before the consumer even starts. This is because a thread continues running until there is a blocking statement, and the Producer has none. The Consumer thread blocks on the first call to mbx.get.

Output:
Producer: before put(1)
Producer: before put(2)
Producer: before put(3)
Consumer: after get(1)
Consumer: after get(2)
Consumer: after get(3)

Synchronization can be achieved as follows:
the Producer creates and puts a transaction into a mailbox, and then blocks until the Consumer finishes with it. This is done by having the Consumer remove the transaction from the mailbox only when it is finally done with it, not when the transaction is first detected.




Producer-Consumer synchronized with bounded mailbox
The Consumer uses the built-in mailbox method peek( ) to look at the data in the mailbox without removing. When the Consumer is done processing the data, it removed the data with get( ). This frees up the Producer to generate a new value.

If the Consumer loop started with a get( ) instead of the peek( ), the transaction would be immediately removed from the mailbox, and so the Producer could wake up before the Consumer finished with the transaction.






































Output:
Producer: before put(1)

Producer: before put(2)
Consumer: after get(1)
Consumer: after get(2)
Producer: before put(3)
Consumer: after get(3)

The Producer and Consumer are in lock-step, but the Producer is still one transaction ahead of the Consumer. This is because a bounded mailbox with size=1 only blocks when you try to do a put of the second transaction.



Producer-Consumer synchronized with a mailbox and event
The two threads use a handshake so that the Producer does not get ahead of the Consumer. The Consumer already blocks, waiting for the Producer using a mailbox. The Producer needs to block, waiting for the Consumer to finish the transaction. This is done by adding a blocking statement to the Producer such as an event, a semaphore, or a second mailbox.







































Output:
Producer: before put(1)
Consumer: after get(1)
Producer: after put(1)
Producer: before put(2)
Consumer: after get(2)
Producer: after put(2)
Producer: before put(3)
Consumer: after get(3)
Producer: after put(3)



Producer-Consumer synchronized by using two mailboxes
To synchronize the two thread use a second mailbox that sends a completion message from the Consumer back to the Producer.

The return message in the rtn mailbox is just a negative version of the original integer. 








































Output:
Producer: before put(1)
Consumer: before get
Consumer: after get(1)
Consumer: before get
Producer: after get(-1)
Producer: before put(2)
Consumer: after get(2)
Consumer: before get
Producer: after get(-2)
Producer: before put(3)
Consumer: after get(3)
Producer: after get(-3)

Callbacks

Wikipedia

In computer programming, a callback is a reference to executable code, or a piece of executable code, that is passed as an argument to other code. This allows a lower-level software layer to call a subroutine (or function) defined in a higher-level layer.

The key requirement in a verification environment that can be re-used for all tests without any changes is the presence a "hook" where the test program can inject new code without modifying the original classes.