Friday, 26 January 2018

Run VCS simulation in ucli mode

ucli mode is same as -do mode in questa simulation. commands used while running simulation can be. Commands that we are going to discuss below should be stored in .tcl file and run while simulation.

command to be executed after simv run:
 run 

Run simulation VCS simulation with dump:
vcs.fsdb dump file is created. $fsdbDumpvars & +all indicates it will dump all hierarchies. There are many features that ucli mode supports.

call {$fsdbDumpfile ("vcs.fsdb")};
call {$fsdbDumpvars ("+all")};

run 
exit

Run simulation for specific simulation time, following command will run simulation for 100 nano second:
run 100ns 
user can give ps - pico second, us - micro second , ms - milli second 

Run for signal or event:
  - run simulation till first posedge of the ack signal is detected.
run -posdege top.m1.ack

same way we can wait for negedge or change in ack signal.

Thursday, 25 January 2018

Bind a module in RTL

Sometimes Designer or Verification engineer do not want to touch design module while implementing assertions of RTL module.

In such cases we can use binding feature of System Verilog.
Binding a module or interface is like instantiating independently defined verification component in RTL.

Lets take an example of AXI Master RTL verification using assertion module.

Code for AXI module:

module AXI_master();
  output [31:0] AWADDR;
  output [2:0]   AWSIZE;
   ...
   ...
   ...
endmodule

Code for the AXI assertions:
module AXI_master_assertions();
  output [31:0] AWADDR;
  output [2:0]   AWSIZE;
   ...
   ...
   ...
  ASSRTION_1
endmodule

Now to bind assertions in AXI_master instance, use following syntax.
bind AXI_master AXI_master_assertions assertion_instance (.*);
.* should be used only if signal names are exactly same in both instance.

If signal names does not match use following syntax.(I would recommend below approach for all cases even if signal names are same)
  
     bind AXI_master AXI_master_assertions assertion_instance (
                          .AWADDR(awaddr),
                          .AWSIZE(awsize)
        );
 
Notes :
  • Binding module is equivalent to taking instance. So car must be taken while using parameter in module instantiation. 
  • Only static components like modules, interface or compilation unit scope can be used. Class or structure can not be bound.

Wednesday, 21 October 2015

Use of polymorphism for System Verilog testbench development

Polymorphism is most common topic asked during interview of ASIC Verification Engineer. It is little bit difficult to understand concept and if you know it properly it will be very much useful in development of verification environment.

Here I have tried to explain polymorphism by taken example of System Verilog verification environment generation. 

Let's assume that we are building System Verilog testbench for RTL module name DUT. 

We need to create environment and test case. Now assume for now that we are using only one testcase to verify our DUT (Ideally you will never have such DUT). 

So our testcase will be written as follows. Name of the test is Sample test.
Here I have given simple code for System Verilog testbench .


// Defining base test
class base_test;
  dut_environment env;
  // Define build function to create env
  function void build();
    env = new("env");
  endfunction : build

   // Define run task to call run method of environment object
   task run();
     env.run();
   endtask : run
endclass : base_test


// TOP module
module top();
  // Instance of base_Test
  base_test b_test;
  initial begin
    // new for base_test object
    b_test = new();
    b_test.build();
    b_test.run();
  end
endmodule : top


Above testbench is shows that in our top simulation module instance of base_test is taken. In this base_test class build method creates env and run method generates our testbench stimulus. 

If we have only one testcase for our verification purpose than above testbench will work but generally this is not the case. Let's assume that we have two different testcase and in each of them we have to create env. The code for both testcase is as follows.  

class testcase1 extends base_test;
   task run();
     fork
       super.run();
     join_none
     // Case scenario1
   endtask : run
endclass :  testcase1


class testcase2 extends base_test;
   task run();
     fork
       super.run();
     join_none
     // Test case scenario2
   endtask : run
endclass :  testcase2


Now If we do not understand use of polymorphism much than we would write code to run above two testcase as follows.

// TOP module
module top();
  testcase1 test1;
  testcase2 test2;
  initial begin
    // If test argument to run test is testcase1 than run that case
    if($plus$argv("testcase1"))begin
      test1 = new();
      test1.build();
      test1.run();
    end
    // If test argument to run test is testcase2 than run that case
    if($plus$argv("testcase1"))begin
      test2 = new();
      test2.build();
      test2.run();
    end
  end
endmodule : top

Here we have taken simple example. If suppose we have 20 test cases and we have to call different function of the testcase like new, build, connect, run, report, etc. We have to write so many lines in our top modules to run those testcases and it is really difficult to manage when one function is added in test case. And it will be really mesh up when you have 10 different function available and each testcase uses limited number of testcase from that 10 test cases. To avoid such situations we need to use polymorphism concept of system verilog.


To use advantage of polymorphism define every function and task in your base test as virtual as shown below.

// Defining base test
class base_test;
  dut_environment env;
  // Define build function to create env
  virtual function void build();
    env = new("env");
  endfunction : build

   // Define run task to call run method of environment object
   virtual task run();
     env.run();
   endtask : run
endclass : base_test

Now you can see that we have defined virtual function and task in base class and we have same function and task overridden in derived class. So as per polimorphism concept the pointer of base class which points to derived class instance memory will call extend class's function only when it is called. 

So we can write our new top.sv file as follows with polymorphism concept.


// TOP module
module top();
  base_test b_test;
  initial begin
    // If test argument to run test is testcase1 than run that case
    if($plus$argv("testcase1"))begin
      b_test = new testcase1::new();
    end
    // If test argument to run test is testcase2 than run that case
    if($plus$argv("testcase2"))begin
      b_test = new testcase2::new();
    end
     b_test.build();
     b_test.run();
  end
endmodule : top

You can see that when we are using polymorphism concept for testcase writing it is very simple to add new test case. For example you have to add new testcase testcase3 you need to add just following 3 lines only.

   if($plus$argv("testcase3"))begin
      b_test = new testcase3::new();
    end
And you are done. As we know that b_test points to memory location which is allocated for extended testcase. whenever I am calling b_test.run() task it will call method of extend class if it is overridden in extended class otherwise it is going to call method of base class itself. Thus it will be very much easy to add new function in our testcases. There wont be much change in your top.sv file.

Suppose you have added report function which should be called after run() task gets completed in testcase. What you need to do is just add report() function in base class and write down following line bellow b_test.run() task.

      b_test.report();


If you override report() function in extended class than it will call overridden function and if you do not specify anything about it in extend function than it will call base class report() function. This way it is really easy to develop System Verilog testbench using OOP and Polymorphism.


This was use of polymorphism , This blog is still under development I need to change lot of formatting and   I will work on blog about polymorphism once I am done with this.

Thanks for reading it !!! 

Wednesday, 17 September 2014

Timescale and Precision in Verilog & System Verilog

General description of timescale is "time allowed for or taken by a process or sequence of event." We can say System Verilog follows the same description of the timescale.

Let's see this from Verilog or System Verilog perspective. First of all `timescale is compiler directive. This compiler directive specifies the time unit and time precision of the design for particular module. Time unit is the unit of measurement for time values for simulation time as well as delay value.

Here we have used two words
simulation time : In one simulation time all event scheduling regions  specified in System verilog LRM file (IEEE std 1800-2012) is simulation time.
Delay value: Delay value specify the time that should be waited before moving forward at some point. For example you want to give 5ns delay before toggling clock signal value.

Time scale compiler directives is declared as follows :

   `timescale time_unit/time_precision

time_unit specifies the time unit of measurement during simulation for your design or test-bench.
time_precision specifies precision of time for time unit. Or in other way minimum time you are able to specify during simulation.

suppose you have specified timescale as follows.

                 `timescale 1 ns / 1 ps

This means in your System Verilog code whenever you specify #1 delay it will be considered as 1 ns delay.And one more thing you can specify the delay of minimum 1 ps = 0.001 ns. So your specified time delay in your design is rounded around 0.001 precision.

Following is some example that indicates how value of the delays are rounded.

         1.1           ns          is considered as    1.1       ns
         1.1234     ns          is considered as    1.123   ns
         1.1119     ns          is considered as    1.112    ns
         1.1123     ns          is considered as     1.112    ns

You can see that whatever the precision you specify your delay will get rounded around that value only. Neared time delay of 1.1113 in term of ps is 1.112 ns so it is rounded to that value.

Same way we have specified some timescale directives as follows.

`timescale 10ns / 1ns

In this timescale #2 will end up in 20 ns of delay. And you will be able to specify minimum 1ns of delay so #0.1 will give you 1ns of delay.

`timescale 0.001 ns / 1ps 

We can specify same timescale as  : `timescale 1ps / 1ps. 

Effect of Timescale

We have seen how to specify timescale. But what will be effect of the time scale ?? It is really important question as sometimes it happens that you spend 2 hours on debugging something event detection and after that you successfully find out that You have used wrong timescale for your module or package.

It is quite possible that in your top module (which is normally named as top ) you have specified timescale as 1ns / 1ns and in your design you are writing some delay like #25ps. In this case your compiler will count this as #0 ns only and you will not get this delay. That will end up in unspecified behavior of your design or System Verilog testbench.

It is also possible that you miss some pulse detection if it last for less time than your design or System Verilog testbench resolution. For example you have specified timescale as follows.

   `timescale 100ns / 10ns 

And you are getting some signal which is driven by other design which has different time scale with maximum value of resolution 1ns. This signal gives pulse signal which lasts for only one nano second only. So for our design it is quit possible that we miss that pulse and will end up in debugging everywhere in our design or System Verilog testbench.






Monday, 15 September 2014

Defining sequece for Assertion in System Verilog

Sequences works on clock ticks. This clock is define as events. In this post we will discuss something that would describe how to write sequential behaviour.

       a ##N b
- when a is true after exactly Nth clock ticks if b is true than sequence evaluates to true. Or we can say after delay of N clock b must be true.

Concatenate any sequence

 

(a ##1 b ##1 c) ##0 (d ##1 e ##1 f)
This seq indicates that once a is true after one clock cycle b is true and after one clock cycle c is true and at the same clock cycle second cycle starts and for that d should be true so ultimately  we can write the whole sequence as follows.
(a ##1 b ##1 c&d ##1 e ##1 f)


Up till now we have seen only linear sequence in which is written simply by specifying exact clock ticks between two events. But what if we have some cases when when delay is not fixed but finite clock ticks. Suppose we want to write sequence in which req is asserted at clock tick nth clock tick and after that in minimum 1 and maximum 5 clock ticks grant is being asserted. One way of doing this is write sequence as follows :
           (a ##1 b) or  (a ##2 b) or  (a ##3 b) or  (a ##4 b) or  (a ##5 b)
But what is there is delay of 5 to 100 clock ticks. If we write this 96 times that is completely absurd. In sequence we have [* repetition operator. The above sequence can be written as follows.
         a ##[1:5] b
Same way if you want to specify min_clk_ticks and max_clk_tiks it can be done as follows.
         a ##[min_clk_ticks:max_clk_ticks] b
If you want to specify min_clk_ticks to any clock tick before end of simulation it is specified by operator $. And expression for this as follows.
         a ##[min_clk_ticks:$] b
This expression indicates that when a is true b must be true any clock ticks after Nth clock ticks.


Goto repetition  [-> & Non-consecutive repetition  [=

Suppose we want to check that particular condition a should remain true for 10 consecutive clock cycle and after that b remains true for consecutive 2 clock cycle and that c is triggered to true. This sequence can be written as follows.
  a ##1 a ##1 a ##1 a ##1 a ##1 a ##1 a ##1 a ##1 a ##1 a ##1 a ##1 a ##1 a ##1 b ##1 b ##1 b ##1 c
But this is not comfortable if we are dealing with 100 clock ticks or 200 clock ticks. Right??
For this System Verilog provides us repetition operator for consecutive matches [* . The above sequence can be written as follows.
                  a ##[*10]  ##1 b ##[*3] ##1 c
We can specify the range of the repetition same way we have seen in previous post. For example :
                  b ##1 a[*0:2] ##2 c
This sequence indicates once b is true a should be true for either 0 times or 1 time  or  2 times and once a is true c should be true after two clock cycles. So, above sequence can be written as follows.
                  (b ##2 c) or (b ##1 a ##2 c) or (b ##2 a ##c)


Using repetition operator we can repeat some particular sequences also and combine it to trigger one seq. For example,
                              (a ##2 b) [*5]
This sequence is  same as
                     (a ##2 b ##1 a ##2 b ##1 a ##2 b ##1 a ##2 b ##1 a ##2 b)


To understand goto repetition we would consider simple example as follows. Let’s take an example of the following seq,
        a ##1 b [->4] ##1 c
This sequence indicates that after a is true b must be true four times with no gap between b and c. So this sequence says that when b is true 4th time it should be penultimate clock cycle of c is true.
         a ##1 ((!b[*0:$] ##1 b) [*4]) ##1 c


Non-consecutive repetition is same as goto repetition. The only difference is that it is not compulsory to be it in penultimate clock cycle.
             a ##1 b [=>4] ##1 c
This sequence indicates that after a is true b must be true four times with gap or no-gap between b and c. So this sequence says that when b is true 4th time it not necessary to be on penultimate clock cycle of c is true.
              a ##1 ((!b[*0:$] ##1 b) [*4]) ##1 !b[*0:$]  ##1 c

Saturday, 13 September 2014

Mailbox in System Verilog

Mailbox is the most common tool to for the communication between two class or processes while writing System Verilog test bench.
Conceptually maibox behaves like a real mailboxes that we use in our day to day life !! When latter is received in mailbox the person or owner of that mailbox can retrieve that mailbox if latter is not received person waits for latter. Same way mailbox works in System Verilog. System Verilog provides methods in mailbox to transfer controlled data between the process or different class.

Mailbox is nothing but a class. So there are some functions to manipulate queue of our object in mailbox like. put(), get(), new(), num(), try_put(), try_get() etc.
Mailboxes are created for either bounded or unbounded size of the queue. By default mailbox are created for unbounded size.  For bounded size when mailbox is full process can not put another data in mailbox. While for unbounded mailbox there is no such limits.

An example of creating a mailbox is as follows.
                  maibox mbx;
Mailbox is inbuilt class in system verilog that provides following methods for manipulation.

new()

Mailboxes are created using new() method.
                 prototype : function new(int bount = 0);
This new function returns handle of the mailbox. Input argument indicates the size of the mailbox user wants to keep in case if it is bound mailbox. Default value of this argument is 0. which indicated infinity size of the mailbox(Not practically !! Depends on RAM fo your machine). Bound value must be positive. Negative value of the bound will cause indefinite behavior of the mailbox.

num()

Number function of the mailbox gives total number of messages in mailbox.
  Prototype  : function int num();

put()

This method puts message in mailbox();
   Prototype : task put(singular message);
If mailbox is bounded size and if full than put() task is suspended till there is enough space in the mailbox. 

try_put();

This method puts message in mailbox(); but it is non-blocking method. 
  Prototype : function int try_put(singular message);
If mailbox is of bounded size and full than this function dose not put message mailbox and return without doing anything. If message has been put successfully in mailbox this method returns 0 otherwise it returns 1.

get()

This method fetches message from mailbox(). 
Prototype : task get(singular message);
If mailbox is empty this task suspends the process until some other process puts message in mailbox. This behavior is independent of size of the mailbox.

try_get()

This function fetches message from mailbox ; but it is not blocking process. 
Prototype : function int try_get(singular message);
If maibox is of bounded size and empty than this function does not retrieve any data from mailbox. If mailbox is empty this function returns 0 as it is not able to fetch any data. Other wise this function returns 1. 

peek()

This function does not remove data from mailbox queue it just copies the first data in mailbox queue.
 prototype : taskvtry_peek(ref singular message)
The peek() method copies one message from mailbox queue without removing message form mailbox. So it mailbox is empty than the current process blocks until the message gets into mailbox.

try_peek();

The try_peek() method attempts to copy message from mailbox without blocking.
Prototype :  function int try_peek(ref singular message);
Try peek method attempts to copy one message from the mailbox without removing the message from the mailbox queue. If mailbox is empty than it returns 0. 

Parametrize a Maibox

By default mailbox can be of any type. You can use mailbox to store int, string, user defined class etc. This means single mailbox can be used to send and receive any kind of data. This is really powerful mechanism but that also can lead us to run time error due to type mismatch between message in mailbox and type used to retrieve data as argument. For most of the cases mailbox is used to transfer some perticular type of message. In this case it is useful to detect type mismatch error during compile time. 

Prototype for parametrized mailbox is as follows :

     mailbox #(type = dynamic_type)

For example  we want to take mailbox of class ahb_data, this can be declare as follows.

    mailbox  #(ahb_data) mbx;

mbx is created mailbox of type ahb_data. So we can only add object of ahb_data only in this mbx mailbox non other than this type of data gives error.

The methods like get(), put(), try_get(), try_put(), num(), peek(), try_peek() same for normal mailbox as well as parametrized mailbox.



Semaphore in System Verilog

For verification purpose we need high level and easy to use synchronization and communication mechanism for our test bench.

Sytem verilog supports mailbox for communication between dynamic processes and semaphore for synchronization between dynamic processes.

Semaphore

Semaphore is like bucket of keys for processes. When a semaphore is created a bucket is created that has capability to contain fixed number of keys. The processes that uses semaphore must first get its keys and than only it is able to start its process. It is like token passing mechanism and as resources are always limited here number of token are also limited.

We will consider following example to understand Semaphore. 

Suppose we have two running processes and both of them access same interface. Both porcesses are independent of each other.  So it is possible that both the process starts driving the interface at the same time. If both processes are driving interface at the same time we will get wrong data on interface until and unless both processes are driving same data on same time !! But that is not going to happen anyway. This type of situations can be managed by keeping some flag that indicates that interface is free or not and our process starts if and only if interface is free. But what will happen if you have say m number of processes that is driving n (n < m) number of interfaces. For this kind of senariaos it will be dificult to syncronize between processes.
For such application semaphore is the best options. 

Lest assume that in some BFM two process write_data () & read_data() this both tasks are getting called randomly and they are using same interface to complete read or write operation. For such type of situation we can implement our read and write progreamn following way.
class semaphore_example;

  semaphore smphr = new(1);

    task read();
       smphr.get(1);       

       // Code to read data from interface

       smphr.put(1);
    endtask : read


task write();
       smphr.get(1);
    
       // Code to write data from interface

       smphr.put(1);
 endtask : write

      
endclass : semaphore_example

In the above example whenever the read or write task is getting called it first gets key form semaphore using smphr.get(1) task and than starts driving the interface and once done with the interface puts the key to the semaphore. This way whenever any task/process gets key from semaphore it is certain that interface is not driven by other process.

There are four methods are defined for semaphores which is listed down here. For more details you can use LRM for System Verilog.

New() ;

Prototype 
                    function new (int KeyCount = 0);
Then KeyCount indicates number of key that is initially allocated to the semaphore.

put ()

The semaphore method used put keys back into semaphore.
Prototype :   function void put(int KeyCount = 0);

get()

The method get() is used to procure specific number of key from semaphore.
Prototype : task get(int KeyCount = 0);
This task blocks the execution of the process until it gets the semaphore key.

Try_get();

The method is used to procure specific number of keys form semaphore but without blocking the process.
Prototype : function void try_get(int KeyCount = 0);