Tuesday 29 December 2015

Callback in SystemVerilog

One of the main guidelines of this book is to create a single verifi cation environment that you can use for all tests with no changes. The key requirement is that this testbench must provide a “hook” where the test program can inject new code without modifying the original classes.

Your driver may want to do the following:
  • Inject errors
  • Drop the transaction
  • Delay the transaction
  • Put the transaction in the scoreboard
  • Gather functional coverage data
Rather than try to anticipate every possible error, delay, or disturbance in the flow of transactions, the driver just needs to “call back” a method that is defined in the top-level test.
The beauty of this technique is that the callback method can be defined differently in every test. As a result, the test can add new functionality to the driver using callbacks, without editing the Driver class.
For some drastic behaviors such as dropping a transaction, you need to code this in the class ahead of time, but this is a known pattern. The reason why the transaction is dropped is left to the callback.

As shown in "Developer code" the Driver::run task loops forever with a call to a transmit task. Before sending the transaction, run calls the pre-transmit callback, if any. After sending the transaction, it calls the post-callback task, if any. By default, there are no callbacks, so run just calls transmit.



You could make Driver::run a virtual method and then override its behavior in an extended class, perhaps MyDriver::run. The drawback to this is that you might have to duplicate all the original method’s code in the new method if you are injecting new behavior. Now if you made a change in the base class, you would have to remember to propagate it to all the extended classes. Additionally, you can inject a callback without modifying the code that constructed the original object.

A callback task is created in the top-level test and called from the driver, the lowest level of the environment. However, the driver does not have to have any knowledge of the test – it just has to use a generic class that the test can extend.
 


Limitations of SystemVerilog callback (w.r.t. uvm_callback).
  • You cannot control particular nth number of transaction. Callback affects all the transaction or in random manner if you use randomization in extended callback as I had used in above both examples. For example you want initial some (n) transcation to drive without any modification, then for particular (n+1) transaction you want to modified its content. Then again for rest of all transaction you don't want any modification. This is not possible with SystemVerilog callback.
  • You cannot Add or Delete callback runtime.

Monday 28 December 2015

Difference between Variable and Signal in VHDL

Scope wise:
SIGNAL has scope to whole architecture. It can be access from any place in architecture of enitity.
VARIABLE is local t procedure defined in the architecture.

Behavior wise:
Variable assignment is evaluated and assigned in a single step:
1) Execution flow within the procedure is blocked until the Execution flow within the procedure is blocked until the
assignment is completed.
Variable assignment is same as Blocking assignment in Verilog.

Signal assignment is Evaluated and assigned in two steps:
1) The right The right-hand side is evaluated immediately.
2) The assignment to the left-hand side is postponed until other evaluations in the current time step are completed other evaluations in the current time step are completed.
Execution flow within the procedure continues until a timing control is encountered (flow is not blocked)
Signal assignment is same as Non-Blocking assignment in Verilog.
If several values are assigned to a given signal in one process, only the last assignment is effective.

Synthesis wise:
SIGNAL inter a FLOP during synthesis.
VARIABLE infer just a WIRE during synthesis.

Example:

Signal assignment:


Variable assignment:

UVM_ERROR demoter code


-------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------

Package in SystemVerilog

First Example:
--------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------------

Second Example:
--------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------------

Third Example:
--------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------------
 
Fourth Example:
--------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------------

Fifth Example:
--------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------------

Sixth Example:
--------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------------

Seventh Example:
--------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------------

Eighth Example: (package and module having variable with same name)
-------------------------------------------------------------------------------------------------------------------------------- 

-------------------------------------------------------------------------------------------------------------------------------- 

Ninth Example: (package and module having variable with same name)
--------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------------

Friday 18 December 2015

Semaphore in SystemVerilog

A semaphore allows you to control access to a resource.
Conceptually, a semaphore is a bucket. When a semaphore is allocated, a bucket that contains a fixed number of keys is created. Processes using semaphores must first procure a key from the bucket before they can continue to execute. If a specific process requires a key, only a fixed number of occurrences of that process can be in progress simultaneously. All others must wait until a sufficient number of keys are returned to the bucket.

Semaphores are typically used for mutual exclusion, access control to shared resources, and basic synchronization.

Imagine that you and your spouse share a car. Obviously, only one person can drive it at a time. You can manage this situation by agreeing that whoever has the key can drive it. When you are done with the car, you give up the car so that the other person can use it. The key is the semaphore that makes sure only one person has access to the car.

In operating system terminology, this is known as “mutually exclusive access,” so a semaphore is known as a “mutex” and is used to control access to a resource.

Semaphores can be used in a testbench when you have a resource, such as a bus, that may have multiple requestors from inside the testbench but, as part of the physical design, can only have one driver.

In SystemVerilog, a thread that requests a key when it is not available always blocks the execution of that particular thread. Multiple blocking threads are queued in FIFO order.

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()

The default value for keyCount is 0.
If the specified number of keys is not available, the get() method blocks process until the keys become available.
If the specified number of keys is not available, the try_get() method returns 0.


Semaphores with Multiple Keys:

You can put more keys back than you took out. Suddenly you may have two keys but only one car!

Be careful if your testbench needs to get and put multiple keys. Perhaps you have one key left, and a thread requests two, causing it to block. Now a second thread requests a single semaphore – what should happen? In SystemVerilog the second request, get(1) , sneaks ahead of the earlier get(2) , bypassing the FIFO ordering.

Thursday 17 December 2015

Mailbox in SystemVerilog

How do you pass information between two threads? Perhaps your generator needs to create many transactions and pass them to a driver. You might be tempted to just have the generator thread call a task in the driver. If you do that, the generator needs to know the hierarchical path to the driver task, making your code less reusable. Additionally, this style forces the generator to run at the same speed as the driver, which can cause synchronization problems if one generator needs to control multiple drivers. The channel must allow its driver and receiver to operate asynchronously. You may be tempted to just use a shared array or queue, but it can be difficult to create threads that read, write, and blocks safely.

The solution is a SystemVerilog mailbox. From a hardware point of view, the easiest way to think about a mailbox is that it is just a FIFO, with a source and sink. The source puts data into the mailbox, and the sink gets values from the mailbox.

A mailbox is a communication mechanism that allows messages to be exchanged between processes. Data can be sent to a mailbox by one process and retrieved by another.

Conceptually, mailboxes behave like real mailboxes. When a letter is delivered and put into the mailbox, a person can retrieve the letter (and any data stored within). However, if the letter has not been delivered when the mailbox is checked, the person must choose whether to wait for the letter or to retrieve the letter on a subsequent trip to the mailbox. Similarly, SystemVerilog’s mailboxes provide processes to transfer and retrieve data in a controlled manner. Mailboxes are created as having either a bounded or unbounded queue size. A bounded mailbox becomes full when it contains the bounded number of messages. A process that attempts to place a message into a full mailbox shall be suspended until enough room becomes available in the mailbox queue. Unbounded mailboxes never suspend a thread in a send operation.

Mailbox is a built-in class that provides the following methods:
— Create a mailbox: new()
— Place a message in a mailbox: put()
— Try to place a message in a mailbox without blocking: try_put()
— Retrieve a message from a mailbox: get() or peek()
— Try to retrieve a message from a mailbox without blocking: try_get() or try_peek()
— Retrieve the number of messages in the mailbox: num()

A put() blocks if the mailbox is full, and get() blocks if the mailbox is empty.
Use try_put() if you want to see if the mailbox is full. And try_get() to see if it is empty. Both are non-blocking methods.
If they are successful, they return a nonzero value; otherwise, they return 0. In other words,
If the mailbox is full, the method try_put() returns 0.
If the mailbox is empty, then the method try_get() or try_peek() returns 0.

These are more reliable than the num() function, as the number of entries can change between when you measure it and when you next access the mailbox.
The peek() task gets a copy of the data in the mailbox but does not remove it.

The data is a single value, such as an integer, or logic of any size or a handle. A mailbox never contains objects, only references to them.

The default mailbox is typeless, that is, a single mailbox can send and receive any type of data. This is a very powerful mechanism, which, unfortunately, can also result in run-time errors due to type mismatches (types not equivalent) between a message and the type of the variable used to retrieve the message. Frequently, a mailbox is used to transfer a particular message type, and, in that case, it is useful to detect type mismatches at compile time.


A classic mailbox bug:

A loop that randomizes objects and puts them in a mailbox, but the object is only constructed once, outside the loop. Since there is only one object, it is randomized over and over.
Below figure shows all the handles pointing to a single object. A mailbox only holds handles, not objects, so you end up with a mailbox containing multiple handles that all point to the single object. The code that gets the handles from the mailbox just sees the last set of random values.




The overcome this bug; make sure your loop has all three steps,
1) constructing the object,
2) randomizing it,
3) putting it in the mailbox
The result, shown in below figure, is that every handle points to a unique object. This type of generator is known as the Blueprint Pattern.




Bounded Mailboxes:

By default, mailboxes are similar to an unlimited FIFO — a producer can put any number of objects into a mailbox before the consumer gets the objects out. However, you may want the two threads to operate in lockstep so that the producer blocks until the consumer is done with the object.

You can specify a maximum size for the mailbox when you construct it. The default mailbox size is 0 which creates an unbounded mailbox. Any size greater than 0 creates a bounded mailbox. If you attempt to put more objects than this limit, put() blocks until you get an object from the mailbox, creating a vacancy.


Synchronized Threads Using a Bounded Mailbox and a Peek:

In many cases, two threads that are connected by a mailbox should run in lockstep, so that the producer does not get ahead of the consumer. The benefit of this approach is that your entire chain of stimulus generation now runs in lock step.

To synchronize two threads, the Producer creates and puts a transaction into a mailbox, 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.

Below example shows the first attempt to synchronize two threads, this time with a 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 removes 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, so the Producer could wake up before the Consumer finished with the transaction.


You can see that the Producer and Consumer are in lockstep, 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.

Reference:
1) SystemVerilog for Verification 3rd edition, by Chris Spear

Wednesday 16 December 2015

Randsequence in SystemVerilog

The random sequence generator is useful for randomly generating structured sequences of stimulus such as instructions or network traffic patterns.
By randomizing a packet, it will generate most unlikely scenarios which are not interested. These type of sequence of scenarios can be generated using randsequence.

randsequence is composed of one or more productions.
Each production contains a name and one or more production_list.

Production_list contains one or more production_item.
Production_items of production_list are further classified into terminals and non-terminals.
Non-terminals are defined in terms of terminals and other non-terminals.
A terminal is an indivisible item that needs no further definition than its associated code block.
Ultimately, every non-terminal is decomposed into its terminals.





A single production can contain multiple production lists.
multiple production lists are separated by the | symbol.
Production lists separated by a | imply a set of choices, which the generator will make at random.





By default procution_list is generated randomly, you can give probability for a production_list generation.
The probability that a production list is generated can be changed by assigning weights to production lists.
The probability that a particular production list is generated is proportional to its specified weight.
The := operator assigns the weight specified by the weight_specification to its production list.
A weight_specification must evaluate to an integral non-negative value.
Weight expressions are evaluated when their enclosing production is selected, thus allowing weights to change dynamically.





A production can be made conditional by means of an if..else production statement.
The expression can be any expression that evaluates to a boolean value. If the expression evaluates to true, the production following the expression is generated, otherwise the production following the optional else statement is generated.







A production can be selected from a set of alternatives using a case production statement.
The case expression is evaluated, and its value is compared against the value of each case-item expression, which are evaluated and compared in the order in which they are given.
The production associated with the first case-item expression that matches the case expression is generated.
If no matching case-item expression is found then the production associated with the optional default item is generated, or nothing if there no default item.
Case-item expressions separated by commas allow multiple expressions to share the production.





The repeat production statement is used to iterate a production over a specified number of times.

Randcase in SystemVerilog

randcase is a case statement that randomly selects one of its branches.
Randcase can be used in class or modules.
The randcase item expressions are non-negative integral values that constitute the branch weights.
An item weight divided by the sum of all weights gives the probability of taking that branch.

For example:

The sum of all weights is 8; therefore, the probability of taking the first branch is (3/8)0.375, the probability

of taking the second is (1/8)0.125, and the probability of taking the third is (4/8)0.5.

If a branch specifies a zero weight, then that branch is not taken.
If all randcase_items specify zero weights, then no branch is taken and a warning can be issued.

The randcase weights can be arbitrary expressions, not just constants.

For example:

In the preceding example, the first three weight expressions are computed using 8-bit precision, and the fourth

expression is computed using 12-bit precision.
The resulting weights are added as unsigned values using 12-bit precision. The weight selection then uses unsigned

12-bit comparison.

Each call to randcae statement will return a random number in the range from 0 to SUM.
$urandom_range(0,SUM) is used to generate a random number.