187
Test Bench Overview TestBench must verify that the design does everything it is supposed to do and does not do anything it is not supposed to do. There are different styles of writing testbenchs. These styles are called methodologies. Methodologies states how to verify complex scenarios to what file name you should use also. LINEAR TB Linear Testbench: Linear TestBench approach to TestBench creation is especially bad for performance. As this is simplest, fastest and easiest way of writing testbenchs, this became novice verification engineer choice. Small models like simple state machine can be verified with this approach. The following code snippet shows linear testbench. Development time increases exponentially as the number of scenarios increases. It is not possible to list all possible input combinations if the number input vectors increases .Just imagine how many inputs are needed to test simple 32 bit adder. Usually Outputs are checked using waveform viewer. As the number of outputs increases, analysis of all the outputs is nightmare. There is no controllability in this method. To test another scenario like read operation, full test bench need to be coded. The simulator must evaluate and schedule a very large number of events. This reduces simulation performance in proportion to the size of the stimulus process. Linear test bench for a memory model. initial begin # 10 read_write = 1; address = 100 ; data = 10; # 10 read_write = 1; address = 101 ; data = 11; # 10 read_write = 1; address = 102 ; data = 12; # 10 read_write = 1; address = 103 ; data = 13; # 10 read_write = 1; address = 104 ; data = 14; end

Verification

Embed Size (px)

Citation preview

Page 1: Verification

Test Bench Overview

TestBench must verify that the design does everything it is supposed to do and  does not do anything it is not supposed to do. There are different styles of writing testbenchs. These styles are called methodologies. Methodologies states how to verify complex scenarios to what file name you should use also.

LINEAR TB

Linear Testbench:

Linear TestBench approach to TestBench creation is especially bad for performance. As this is simplest, fastest and easiest way of writing testbenchs, this became novice verification engineer choice. Small models like simple state machine can be verified with this approach. The following code snippet shows linear testbench. Development time increases exponentially as the number of scenarios increases. It is not possible to list all possible input combinations if the number input vectors increases  .Just imagine how many inputs are needed to test simple 32 bit adder. Usually Outputs are checked using waveform viewer. As the number of outputs increases, analysis of all the outputs is nightmare. There is no controllability in this method. To test another scenario like read operation, full test bench need to be coded. The simulator must evaluate and schedule a very large number of events. This reduces simulation performance in proportion to the size of the stimulus process.

Linear test bench for a memory model.

initial   begin   # 10 read_write = 1; address = 100 ; data = 10;   # 10 read_write = 1; address = 101 ; data = 11;   # 10 read_write = 1; address = 102 ; data = 12;   # 10 read_write = 1; address = 103 ; data = 13;   # 10 read_write = 1; address = 104 ; data = 14;   end 

FILE IO TB

File I/O Based Testbench

Another way of getting the Stimulus is get the vectors from an external file. The external vector file is generally formatted so that each value in the file represents either a specific input pattern .Verilog HDL contains the $readmemb or $readmemh system tasks to do the file read if the file data is formatted in a specific way using

Page 2: Verification

either binary or hexadecimal data. TestBench is like just an interface between external vector source and DUT. Sometimes outputs are also to written to external files. For example, to verify a a dsp algorithm implemented as  DUT, get the input vectors from matlab tool and send the outputs to a file and then compare the outputs of the matlab for the same algorithm.

Fallowing example illustrates how to initialize a memory array from data stored as hexadecimal values in a data file, Simulate this file directly to see the results. Note: The data file must reside in the same directory  as the .v file for the module in this example.

EXAMPLE: verilog file module readmemh_demo; 

   reg [31:0] Mem [0:11];       initial $readmemh("data.txt",Mem);       integer k;    initial begin       #10;       $display("Contents of Mem after reading data file:");       for (k=0; k<6; k=k+1) $display("%d:%h",k,Mem[k]);    end 

endmodule 

EXAMPLE: data.txt file    234ac    23ca5    b3c34    23a4a    234ca    b3234

RESULT:

   0:000234ac    1:00023ca5    2:000b3c34    3:00023a4a    4:000234ca    5:000b3234

Page 3: Verification

Reading or writing to files during simulation is costly to performance, because the simulator must halt and wait while the OS completes each transaction with the file system. One way to improve performance is to replace ASCII vector files with a constant table in HDL itself. Do this using Perl script.  

module readmemh_demo; 

   reg [31:0] Mem [0:11];       `include "data.v"       integer k;    initial begin       #10;       $display("Contents of Mem after reading data file:");       for (k=0; k<6; k=k+1) $display("%d:%h",k,Mem[k]);    end 

endmodule 

EXAMPLE: data.v file initial    begin    Mem[0] = 32'h234ac;     Mem[1] = 32'h23ca5;     Mem[2] = 32'hb3c34;     Mem[3] = 32'h23a4a;     Mem[4] = 32'h234ca;     Mem[5] = 32'hb3234;     end 

RESULT:

   0:000234ac    1:00023ca5    2:000b3c34    3:00023a4a    4:000234ca    5:000b3234

Page 4: Verification

STATE MACHINE BASED TB

By definition, a stat machine TestBench used state machine to generate input vector and drive it to the I/O ports of the design. One testbench can have multiple state machines each handling a different functionality. To achieve the quality of verification required by today's complex designs, testbench must be robust. State machine based verification cannot support todays verification needs. A state machine based testbench is hardly seen nowadays.

always@(posedge clk) case(state) READ  :  if(i < No_of_reads)         begin         read_write = 0;         address = $random;         i=i+1;         end         else         $finish WRITE : if(j < no_of_writes)         begin         read_write = 1;         address = $random;         data = $random;         j=j+1;         end         else         state = READ ; endcase 

Now lets see how to develop our scenarios: Only 10 write operations,

initial    begin    No_of_reads = 0;    No_of_writes = 10;    end 

Only 10 read operations,

Page 5: Verification

initial    begin    No_of_reads = 10;    No_of_writes = 0;    end 

With the above style of testbench, the controllability is less and hard to change the code to add new features like to convert the above code to alternate read and write operation, its very difficult.

TASK BASED TB

Task And Function Based Tb:

Task based verification is more flexible over all the above approaches. All the operations in are done using takes and functions. The task based BFM is extremely efficient if the device under test performs many calculations. Each task or function focuses on one single functionality. Verification of DUT using the task based testbench is faster. Using tasks makes it possible to describe structural testbenchs. These tasks can be ported without much effort.

EXAMPLE: task write(input integer data,input integer address); begin    @(posedge clock);    read_write = 1;    address = $random;    data = $random;  end endtask 

task read(input integer address,output integer data); begin    @(posedge clock);    read_write = 0;    address = $random;    // Do some operation to get dataend endtask 

Now lets see how to develop the senarious. 

1) 10 write operations.

Page 6: Verification

initial    repeat(10)    write($random,$random); 

2) 10 read operations

initial     repeat(10)    read($random,data); 

3) Alternate read and write operations.

initial     repeat(10)    begin       write($random,$random);       read($random,data);    end 

4) Do the write and read the same location.

initial     begin         write(10,20);         read (10,data);     end 

SELF CHECKING TESTBENCH

Two important aspects of todays functional verification are quality and re usability. Design engineers have made design reuse to reduce development time and effort in designing an ASIC. Significant design blocks are reused from one project to the next. The lack of flexible verification environments that allow verification components reuse across ASIC design projects keep the verification cost very high. Considering the fact that verification consumes more resources than design does , it would be of great value to build verification components that are modular and reusable. When a design is passing all the tests in the verification environment, it has not been possible to know whether the design under verification is correct, and may be safely taped-out, or whether the verification environment is just incapable of finding any bugs that may still remain in DUT.

ADVANTAGES:

Page 7: Verification

Speeds up verification and results in early tape out of the chip.Less man power is required, by which the over all cost of the project will be low.Environment can be reusable.Easy tracking of verification progress(functional coverage).Developing self checking testbench is very interesting.

Todays functional verification flow mainly contains following steps:

Generate the stimulus vectors. Send the Stimulus to the DUT. Monitor the response generated by the DUT. Verify the response generated. Generate report about the DUT performance. Some kind of feedback to show the quality of testbench.

A test-bench is built to functionally verify the design by providing meaningful scenarios to check that given certain input, the design performs to specification. Test bench provides the stimulus to exercise DUT code. A self checking testbench is a intelligent testbench which does some form of output sampling of DUT and compares the sampled output with the expected outputs. A simulation environment is typically composed of several types of components:

 

Stimulus Generator:

In order to test the model of some design, a verification engineer must apply test patterns to the input ports and observe the output ports over time to decide whether the inputs were transformed to the expected outputs. The generator component generates input vectors. For simple memory stimulus generator generates read, write operations, address and data to be stored in the address if its write operation. Modern generators generate random, biased, and valid stimuli. In

Page 8: Verification

verilog $random does this job. The randomness is important to achieve a high distribution over the huge space of the available input stimuli. To this end, users of these generators intentionally under-specify the requirements for the generated tests. It is the role of the generator to randomly fill this gap. This mechanism allows the generator to create inputs that reveal bugs not being searched for directly by the user. Generators also bias the stimuli toward design corner cases to further stress the logic. Biasing and randomness serve different goals and there are tradeoffs between them, hence different generators have a different mix of these characteristics. Since the input for the design must be valid and many targets should be maintained, many generators use the Constraint Satisfaction Problem technique to solve the complex testing requirements. SystemVerilog, Vera, SystemC and Specman have " constraints " to specify The legality of the design inputs. In verilog ,to constrain the memory address to be between 0 to 63, {$random} % 64 is used. The model-based generators use this model to produce the correct stimuli for the target design. The stimulus generator should be intelligent and easily controllable.

Bus Functional Models

The Bus Functional Model (BFM) for a device interacts with the DUT by both driving and sampling the DUT signals. A bus functional model is a model that provides a task or procedural interface to specify certain bus operations for a defined bus protocol. For a memory DUT, transactions usually take the form of read and write operations. Bus functional models are easy to use and provide good performance. It has to follow the timing protocol of the DUT interface. BFM describes the functionality and provides a cycle accurate interface to DUT. It models external behavior of the device. For re usability, the implementation of the BFM functionality should be kept as independent of the communication to the BFM as it can be.

Driver

Driver is a types of BFM. The drivers translate the stimuli produced by the generator into the actual inputs for the design under verification. Generators create inputs at a high level of abstraction; namely, as transactions like read write operation. The drivers convert this input into actual design inputs which is at a low level like bits ,as defined in the specification of the designs interface. If the generator generates read operation, then read task is called, in that, the DUT input pin "read_write" is asserted.

Reciver

Receiver is also a type of BFM. The output of the DUT is collected. The output of the DUT is available in a low level format. Let<92>s take a packet protocol. The

Page 9: Verification

interface has "start of the packet" and "end of packet" signal to indicate the packet arrival. The receiver starts collecting the packet looking at the signal "start of packet" and does this job until "end of the packet".

Protocol Monitor:

Protocol monitor do not drive any signals, monitor the DUT outputs, identifies all the transactions and report any protocol violations. The monitor converts the state of the design and its outputs to a transaction abstraction level so it can be stored in a 'score-boards' database to be checked later on. Again let<92>s take a packet protocol. The monitor gets the information from the packet like, length of the packet, address of the packet etc.

Scoreboard:

Scoreboard is sometimes referred as storage structure. The stimulus generator generated the random vectors. These are derived to the dut. These stimulus are stored in scoreboard until the output comes out of the DUT. When a write operation is done on a memory with address 101 and data 202,asfter some cycles, if a read is done at address 101,what should be the data?.The score board recorded the address and data when write operation is done. Get the data stored at address of 101 in scoreboard and compare with the output of the DUT in checker. Scoreboard also has expected logic if needed. Take an 2 input and gate. The expect logic does the " and " operation on the two inputs and stores the output.

Checker:

Checker is part of score board. The checker validates that the contents of the 'score-boards' are legal. There are cases where the generator creates expected results, in addition to the inputs. In these cases, the checker must validate that the actual results match the expected ones.

Coverage:

Coverages are of two types, Functional coverage and code coverage. Code coverage is not part of Testbench. Functional Coverage is part of test bench. Functional coverage cannot be done in Verilog.

Code Coverage:

Code coverage, in short, is all about how thoroughly your tests exercise your code base. The intent of tests, of course, is to verify that your code does what it's expected to, but also to document what the code is expected to do. Taken further, code coverage can be considered as an indirect measure of quality -- indirect

Page 10: Verification

because we're talking about the degree to what our tests cover our code, or simply, the quality of tests. In other words, code coverage is not about verifying the end product's quality.

Statement coverage: measures the number of statements executed . Branch coverage: measures the expressions and case statements that affect the control flow of the HDL execution Condition coverage: breaks down the condition on the branch into elements that make the result true or false Toggle coverage: counts low-to-high and high-to-low transitions Finite State Machine: state and state transition coverage

Functional Coverage:

Functional is the metric which shows how much we have verified. It shows how many possible scenarios are possible and how many are covered. Take a memory. If the memory address is 64 byte depth, and if the address is generated randomly, we are not sure that every location is covered. Functional coverages gives report how many address are possible and how may we have covered.

Verification of a design usually follows the flow synopsis below.

Planning:

After the preliminary design specification is completed, the first verification phase is started Verification planning.

Verification planning consists, following main tasks.

1) Feature extraction from design specification. 2) Listing out Testcases. 3) Verification Environment Architecture plan.

Feature Extraction:

Extract all the features of the DUT from the design specification. Mainly the features are configuration, Interface protocol, data processing protocol and status communication. Categorizing all this features according to where these features are verified. What are the features covered by random stimulus generation? What are the features verifiable by writing separate test cases? What features assertions can catch? What features the coverage module contains?

Page 11: Verification

Verification Environment Architecture Plan:

Verification plan contains the structure of the Verification environment. Based on the project requirements, following points are considered while Architecture is built. Reusability, Is it a verification IP. What blocks the verification language can support.

Controllability of the stimulus generation etc.

Next phase is to build the Verification environment. Final phase is to verify the DUT using the environment built.

CLOCK GENERATOR

Clocks are the main synchronizing events to which all other signals are referenced. If the RTL is in verilog, the Clock generator is written in Verilog even if the TestBench is written in other languages like Vera, Specman or SystemC. Clock can be generated many ways. Some testbenchs need more than one clock generator. So testbench need clock with different phases some other need clock generator with jitter. The very first transition of clock at time zero may be perceived as a transition because clock has an unknown value before time zero and gets assigned to a value at time zero. How this time zero clock transition is perceived is simulator dependent, and thus care must be taken. Fallowing examples show simple clock generators with 50% duty cycles.

EXAMPLE: initial clk = 0; always #10 clk = ~clk; 

EXAMPLE: always  begin     clk = 0;    #10;    clk = 1;    #10; end  

EXAMPLE: always  begin     clk = 0; 

Page 12: Verification

   forever #10 clk = ~clk; end  

Different testbenchs need different clock periods. It is beneficial to use parameters to represent the delays, instead of hard coding them. For example, to generate a clock starting with zero that has a 50% duty cycle, the following code can be used:

EXAMPLE: module Tb();    reg clock;    integer no_of_clocks;       parameter CLOCK_PERIOD = 5;    initial no_of_clocks = 0;    initial clock = 1'b0;       always #(CLOCK_PERIOD/2) clock = ~clock;       always@(posedge clock)    no_of_clocks = no_of_clocks +1 ;       initial    begin       #50000;       $display("End of simulation time is %d , total number of clocks seen is %d expected is %d",$time,no_of_clocks,($time/5));       $finish;    end endmodule RESULTS:

End of simulation time is 50000 , total number of clocks seen is 12500 expected is 10000

Total number of clocks are 12500 and the expected are 1000.There are 25 % of more clocks than expected. The reason is half clock period is 2 insted of 2.5. Make sure that CLOCK_PERIOD is evenly divided by two. If CLOCK_PERIOD is odd, the reminder is truncated the frequency of the clock generated in not what expected. If integer division is replaced by real division, the result is rounded off according to the specified resolution.

EXAMPLE: module Tb(); 

Page 13: Verification

   reg clock;    integer no_of_clocks;       parameter CLOCK_PERIOD = 5;       initial no_of_clocks = 0;    initial clock = 1'b0;       always #(CLOCK_PERIOD/2.0) clock = ~clock;       always@(posedge clock)       no_of_clocks = no_of_clocks +1 ;       initial    begin       #50000;       $display("End of simulation time is %d , total number of clocks seen is %d expected is %d",$time,no_of_clocks,($time/5));       $finish;    end endmodule 

RESULTS:

End of simulation time is 50000 , total number of clocks seen is 8333 expected is 10000

Look at the result, total number of clock seen are 8333, where the rest of the clocks have gone? There is some improvement than earlier example. But the results are not proper. Well that is because of `timeprecision. By default time precision is 1ns/1ns. Half of the clock period is 2.5 . It is rounded of to 3 . So total time period is 6 and resulted 8333 clocks( 50000/6) instead of (50000/5). 2.5 can be rounded to 3 or 2 . LRM is specific about this. So try out this example on your tool. You may see 12500.

Timescale And Precision Enlightment:

Delay unit is specified using 'timescale, which is declared as `timescale time_unit base / precision base --time_unit is the amount of time a delay of 1 represents. The time unit must be 1 10 or 100 --base is the time base for each unit, ranging from seconds to femtoseconds, and must be: s ms us ns ps or fs

Page 14: Verification

--precision and base represent how many decimal points of precision to use relative to the time units.

Time precision plays major role in clock generators. For example, to generate a clock with 30% duty cycle and time period 5 ns ,the following code has some error.

EXAMPLE: `timescale 1ns/100ps module Tb();    reg clock;    integer no_of_clocks; 

   parameter CLOCK_PERIOD = 5;    initial clock = 1'b0;    always    begin        #(CLOCK_PERIOD/3.0) clock = 1'b0;        #(CLOCK_PERIOD - CLOCK_PERIOD/3.0) clock = 1'b1;    end       initial no_of_clocks = 0;       always@(posedge clock)        no_of_clocks = no_of_clocks +1 ;       initial    begin        #50000;        $display(" End of simulation time is %d , total number of clocks seen is %d expected is %d",$time,no_of_clocks,($time/5));        $finish;    end endmodule RESULTS:

End of simulation time is 50000 , total number of clocks seen is 9999 expected is 10000

Now CLOCK_PERIOD/3.0 is 5/3 which is 1.666. As the time unit is 1.0ns, the delay is 1.666ns. But the precision is 100ps. So 1.666ns is rounded to 1.700ns only. and when (CLOCK_PERIOD - CLOCK_PERIOD/3.0) is done, the delay is 3.300ns

Page 15: Verification

instead of 3.333.The over all time period is 5.If the clock generated is implemented without taking proper care, this will be the biggest BUG in testbench.

All the above clock generators have hard coded duty cycle. The following example shows the clock generation with parameterizable duty cycle. By changing the duty_cycle parameter, different clocks can be generated. It is beneficial to use parameters to represent the delays, instead of hard coding them. In a single testbench, if more than one clock is needed with different duty cycle, passing duty cycle values to the instances of clock generators is easy than hard coding them.  

NOTE: Simulation with `timescale 1ns/1ns is faster than `timescale 1ns/10ps A simulation using a `timescale 10ns/10ns and with `timescale 1ns/1ns  will take same time.

EXAMPLE:    parameter CLK_PERIOD  = 10;     parameter DUTY_CYCLE  = 60; //60% duty cycle    parameter TCLK_HI = (CLK_PERIOD*DUTY_CYCLE/100);    parameter TCLK_LO = (CLK_PERIOD-TCLK_HI);       reg clk; 

   initial       clk = 0; 

   always    begin       #TCLK_LO;       clk = 1'b1;       #TCLK_HI;       clk = 1'b0;    end  

Make sure that parameter values are properly dividable. The following example demonstrates how the parameter calculations results. A is 3 and when it is divided by 2,the result is 1.If integer division is replaced by real division, the result is rounded off according to the specified resolution. In the following example is result of real number division.

EXAMPLE:     module Tb(); 

Page 16: Verification

    parameter A = 3;     parameter B = A/2;     parameter C = A/2.0; 

    initial     begin         $display(" A is %e ,B is %e ,C is %e ",A,B,C);     end 

    endmodule RESULTS:

A is 3.000000e+00 ,B is 1.000000e+00 ,C is 1.500000e+00  

Often clockgenerators are required to generate clock with jitter.The following is simple way to generate clock with jitter.

EXAMPLE:    initial clock = 1'b0;       always clock = #1 ~clock;    jitter = $random() % range;       assign jittered_clock = #(jitter) clock;   

With the above approace,over all clock period is increased. A better approach for clock divider is as follows

EXAMPLE:     parameter DELAY = TIMEPERIOD/2.0 - range/2.0;     initial clock = 1'b0;         always     begin        jitter = $dist_uniform(seed,0,jitter_range);        #(DELAY + jitter) clock = ~clock;     end 

Clock dividers and multipliers are needed when more than one clock is needed to be

Page 17: Verification

generated from base clock and it should be deterministic. Clock multipliers are simple to design. A simple counter does this job. Clock division is little bit tricky. TO design a lock divider i.e a frequency multiplier, first the time period has to be captured and then it is used to generate another clock. With the following approach, the jitter in the base clock is carried to derived clock.

EXAMPLE:Clock multipler with N times multiplication    initial i = 0;       always @( base_clock ) begin        i = i % N;        if (i == 0) derived_clock = ~derived_clock;        i = i + 1;    end 

EXAMPLE:Clock division with N times division     initial begin         derived_clock = 1'b0;         period = 10; // for initial clock          forever derived_clock = #(period/(2N)) ~ derived_clock;     end          always@(posedge base_clock)      begin         T2 = $realtime;         period = T2 - T1;         T1 = T2;     end 

Simulation Steps:

Simulation is defined as the process of creating a model (i.e., an abstract representation) of a system  in order to identify and understand those factors which control the system and/or to predict (forecast) the future behavior of the system.  The simulation model need not reflect any understanding of the underlying technology, and the simulator need not know that the design is intended for any specific technology.

The underlying purpose of simulation is to shed light on the underlying mechanisms that control the behavior of a system. More practically, simulation can be used to predict (forecast) the future behavior of a system, and determine what you can do to influence that future behavior. That is, simulation can be used to predict the way in which the system will evolve and respond to its surroundings, so that you can identify any necessary changes that will help make the system perform the way

Page 18: Verification

that you want it to.

Simulation Eliminates the time-consuming need for constant physical prototyping.  Simulation should be performed during ALL stages of ASIC design.

Macro Preprocessing:

The macro preprocessing step performs textual substitutions of macros defined with `define statements, textual inclusion with `include statements, and conditional compilation by `ifdef and `ifndef statements.

Compilation (Analyzer)

Checks source code to check  syntax and semantic rules. If a syntax or semantic error occurs, then the compiler gives error message. If there are no errors , compilation produces an internal representation for each HDL design unit.

Elaboration

The elaboration process constructs a design hierarchy based on the instantiation and configuration information in the design, establishes signal connectivity . Memory storage is allocated for the required signals. The elaboration process creates a hierarchy of module instances that ends with primitive gates and statements.

Optimization:

Some tools support optimization at this level. This is optional step.

Initialization :

Initial values preset in the declarations statement are assigned to signals / variables.

Execution

Every process is executed until it suspends. Signal values are updated only after the

Page 19: Verification

process suspends. Simulator accepts simulation commands like (run, assign, watch), which control the simulation of the system and specify the desired simulator output.  Simulation ends  when all signals have been updated and new values have been assigned to signals. This design hierarchy is stored in a simulation snapshot. The snapshot is the representation of your design that the simulator uses to run the simulation.

Simulation Process :

When Simulation time is incremented, On receiving Simulation commands, a signal is updated. All processes sensitive to that signal are placed on a ¿Process Execution¿ queue. Each resumed process is executed until it suspends. Effects of the logic changes that have occurred as a result of process execution are evaluated. Simulation time is set to the next event in queue, or halted if simulation time is exhausted.

INCREMENTAL COMPILATION

Incremental compilation means that the compiler inspects your code, determines which parts of the application are affected by your changes, and only recompiles the newer files. Incremental compilation can help reduce compile time on small applications, but you achieve the biggest gains on larger applications. The default value of the incremental compiler option is true for most tools.

While recompiling , Incremental compilation does less work than full recompile. The simulation doesn't show any difference , only the compilation time is reduced.  Imagine you are working with hundreds of files and U just changed one file, During full recompilation , all the files are recompiled, where in incremental compilation , only the file which is changed and the files which are dependent on changed files are compiled and linked to the already compiled database.

STORE AND RESTORE

Incremental compilation means that the compiler inspects your code, determines which parts of the application are affected by your changes, and only recompiles the newer files. Incremental compilation can help reduce compile time on small applications, but you achieve the biggest gains on larger applications. The default value of the incremental compiler option is true for most tools.

While recompiling , Incremental compilation does less work than full recompile. The

Page 20: Verification

simulation doesn't show any difference , only the compilation time is reduced.  Imagine you are working with hundreds of files and U just changed one file, During full recompilation , all the files are recompiled, where in incremental compilation , only the file which is changed and the files which are dependent on changed files are compiled and linked to the already compiled database.

Save and Restore saves the complete state of the simulator to a file that can be used to restart simulation at the point of the save.

If multiple simulation, have same property for several hours of simulation, then the simulation state can be shared across all the simulation. For example, In System Level verification, it takes more than a day for operating system to boot in RTL, then the testing scenarios starts. This boot operations in RTL can be saved to a state. Using this saved state, user can directly start simulation from the saved point. Typically, once a bug is discovered, perhaps hours or days into a simulation, the simulation would need to be repeated for hours or days to verify the bug fix. In Verilog, the simulation state can be saved at any time and restored, to skips hours or days into a simulation and validate a bug fix. This feature not only allows quick verification of bug fixes, but enables much longer simulations by not rerunning previously validated code.

Three system tasks $save, $restart, and $incsave work in conjunction with one another to save the complete state of simulation into a permanent file such that the simulation state can be reloaded at a later time and processing can continue where it left off.

EXAMPLE:

$save("file_name");$restart("file_name");$incsave("incremental_file_name");

All three system tasks take a file name as a parameter. The file name has to be supplied as a string enclosed in quotation marks.

The $save system task saves the complete state into the host operating system file specified as a parameter.

The $incsave system task saves only what has changed since the last invocation of $save. It is not possible to do an incremental save on any file other than the one

Page 21: Verification

produced by the last $save.

The $restart system task restores a previously saved state from a specified file.

Restarting from an incremental save is similar to restarting from a full save, except that the name of the incremental save file is specified in the restart command. The full save file that the incremental save file was based upon shall still be present, as it is required for a successful restart. If the full save file has been changed in any way since the incremental save was performed, errors will result.

Take care while using Pli application . Since PLI application may have some other form of simulation snapshot storage, the simulation tool doesn't have the control on them. $save system task, creates a checkpoint file and PLI tr routines are there to save the PLI snapshot.

Event Based Simulation

Event simulation typically traces every signal transition and continues this until stable state is reached. Simulation based on events in logic, which means, whenever there is change in a input event, the output is evaluated. Means both timing and functional information is available. With this glitches in signal changes can be observed. Event based simulators are slow when compared with cycle based simulators.

Cycle Based Simulation

Cycles based simulator takes the advantage of the fact that most digital circuits are synchronous in nature.  Cycle simulation typically re-evaluates the state of the circuit as a whole, once upon each external trigger, usually without evaluating any intermediate node states. Cycle based simulator is very fast compared to event based simulator. Disadvantage of cycle based simulator are it cannot detect glitches and setup and hold checks cannot be done. In cycle based simulators, delays cannot be specified.

Time Scale And Time Precision:

RECAP: 

Delay unit is specified using 'timescale, which is declared as `timescale time_unit base / precision base --time_unit is the amount of time a delay of #1 represents. The time unit must be 1 10 or 100 --base is the time base for each unit, ranging from seconds to femtoseconds, and must be: s ms us ns ps or fs

Page 22: Verification

--precision and base represent how many decimal points of precision to use relative to the time units.

For example : `timescale 1 ns / 100 ps means time values to be read as ns and to be rounded to the nearest 100 ps.

If timescale is omitted, there is a default time scale.

The following examples demonstrate how the time scale and time precision effect $stime, #delay and toggling in waveform.

EXAMPLE:    `timescale 10ns/10ns       module tim();       reg i; 

      initial       begin          i=0;          #7.7212;          i=1;          $display("STATEMENT 1 :: time is ",$stime);          #7.123;          $finish;       end          endmodule    module try;             time delay_time = 7.721; 

      initial begin       $display("STATEMENT 2 :: delay for %0t",delay_time     );       end    endmodule    RESULTS:

STATEMENT 1 :: time is 8 STATEMENT 2 :: delay for 8

reg i toggled at 80 in waveform debugger

EXAMPLE: `timescale 10ns/1ns module tim();     reg i;     initial     begin 

Page 23: Verification

        i=0;         #7.7212;         i=1;         $display("STATEMENT 1 :: time is ",$stime);         #7.123;         $finish;     end     endmodule 

module try; 

   time delay_time = 7.721;    initial begin       $display("STATEMENT 2 :: delay for %0t",delay_time     );    end endmodule RESULTS:

STATEMENT 1 :: time is 8 STATEMENT 2 :: delay for 80

reg i toggled at 77 waveform debugger

EXAMPLE: `timescale 10ns/1ps module tim();     reg i; 

    initial     begin         i=0;         #7.7212;         i=1;         $display("STATEMENT 1 :: time is ",$stime);         #7.123;         $finish;     end     endmodule module try; 

    time delay_time = 7.721;     initial begin         $display("STATEMENT 2 :: delay for %0t",delay_time     );     end endmodule 

RESULTS:

Page 24: Verification

STATEMENT 1 :: time is 8 STATEMENT 2 :: delay for 80000

reg i toggled at 77.212 in waveform debugger

In the timescale statement, the first value is the time unit and the second is the precision for the simulation. So with the time unit, when the simulator displays a value, you just have to multiply the value by this time unit to get the real time. With a 10ns time unit, when a delay of #7.7212, that means that it is 77.212ns delay.

Now the second one (time precision) specify with which precision time values are rounded. Asking for a 77.212ns delay is possible only with a 1ps precision. That's what you can see with reg i. It toggles at 77ns with a 1ns precision and 77.212 with a 1ps precision.

For the STATEMENT 1, $stime returns an integer scaled to timesale unit, that's why results are always 8 which is 7.7212 rounded up to 8.

Now for STATEMENT 2, the way %t is printed depends on $timeformat. It seems that in this case, 7.7212 is first rounded to an integer => 8 and then printed according to your time precision. Im not sure of this topic. If some one finds mistake in my understanding, please mail me at [email protected]

Each module can have separate time scale. The smallest time_precision argument of all the timescale compiler directives in the design determines the precision of the time unit of the simulation.

Lets take an example. There are two modules. Module_1 is instance od Module_2. Module_1 has timescale of 1 ns/1ps. Module_2 has time scale of 1ns / 10ps. The smallest resolution is 1ps. This is taken as simulator resolution but each module evaluates according to its precision mentioned.  

Lets take another example. There are two modules. Module_1 is instance of Module_2. Module_1 does not have any time scale. Module_2 is having time scale of 1ns/100 ps. As there is no time scale for Module_1 ,the simulator takes precision and time unit of 100 ps i.e `timescale 100 ps/100 ps.

$Time Vs $Realtime

$time round offs the time to nearby integer where as $realtime does not. So when you are using real valued delays, then use $realtime instead of $time , else there may be a misunderstanding during debugging.

Page 25: Verification

System Task Printtimescale

The $printtimescale system task displays the time unit and precision for a particular module. When no argument is specified, $printtimescale displays the time unit and precision of the module that is the current scope. When an argument is specified, $printtimescale displays the time unit and precision of the module passed to it.

EXAMPLE: `timescale 1 ms / 1 us module a_dat;    initial       $printtimescale(b_dat.c1); endmodule 

`timescale 10 fs / 1 fs module b_dat;    c_dat c1 (); endmodule 

`timescale 1 ns / 1 ns module c_dat; 

endmodule 

RESULTS:

Time scale of (b_dat.c1) is 1ns / 1ns

System Task Timeformat

The $timeformat system task performs the following two functions: 1)It specifies how the %t format specification reports time information for the $write, $display,$strobe, $monitor, $fwrite, $fdisplay, $fstrobe, and $fmonitor group of system tasks. 2)It specifies the time unit for delays entered interactively.

The units number argument shall be an integer in the range from 0 to -15. This argument represents the time unit as shown in table

Unit number Time unit Unit number Time unit     0        1 s        -8         10 ns    -1        100 ms     -9         1 ns    -2        10 ms      -10        100 ps    -3        1 ms       -11        10 ps    -4        100 us     -12        1 ps    -5        10 us      -13        100 fs

Page 26: Verification

   -6        1 us       -14        10 fs    -7        100 ns     -15        1 fs

Syntax : $timeformat(time unit, precision number, suffix string, and minimum field width);

EXAMPLE: `timescale 1 ms / 1 ns module cntrl;     initial     $timeformat(-9, 5, " ns", 10); endmodule 

`timescale 1 fs / 1 fs module a1_dat;     reg in1;     integer file; 

    buf #10000000 (o1,in1); 

    initial begin         file = $fopen("a1.dat");         #00000000 $fmonitor(file,"%m: %t in1=%d o1=%h", $realtime,in1,o1);         #10000000 in1 = 0;         #10000000 in1 = 1;     end endmodule 

RESULTS:

a1_dat: 0.00000 ns in1= x o1=x a1_dat: 10.00000 ns in1= 0 o1=x a1_dat: 20.00000 ns in1= 1 o1=0 a1_dat: 30.00000 ns in1= 1 o1=1

EXAMPLE: `timescale 1 ms / 1 ns module cntrl;    initial      $timeformat(-9, 5, " ns", 10); endmodule 

Page 27: Verification

`timescale 1 ps / 1 ps module a2_dat;     reg in2;     integer file2; 

    buf #10000 (o2,in2); 

    initial begin         file2=$fopen("a2.dat");         #00000 $fmonitor(file2,"%m: %t in2=%d o2=%h",$realtime,in2,o2);         #10000 in2 = 0;         #10000 in2 = 1;     end endmodule 

RESULTS:

a2_dat: 0.00000 ns in2=x o2=x a2_dat: 10.00000 ns in2=0 o2=x a2_dat: 20.00000 ns in2=1 o2=0 a2_dat: 30.00000 ns in2=1 o2=1

STIMULUS GENERATION

  Verilog test benches range from simple descriptions signal values to descriptions that test vector files and high level controllable descriptions that use functions or tasks .There are many ways to create input test vectors to test DUT. Hardcoded value is simplest way of creating a test vectors. This I used to do when I was in schooling. As the number of inputs are less, this is comfortable to use.  

EXAMPLE: module Tb_mem();     reg clock;     reg read_write;     reg [31:0] data;     reg [31:0] address;         initial     begin         clock = 0;         forever             #10 clock = ~clock; 

Page 28: Verification

    end         initial     begin         @(negedge clock)  read_write = 1 ; data = 4;address = 1;         @(negedge clock)  read_write = 1 ; data = 5;address = 2;         @(negedge clock)  read_write = 1 ; data = 6;address = 3;         @(negedge clock)  read_write = 1 ; data = 7;address = 4;         @(negedge clock)  read_write = 1 ; data = 8;address = 5;         $finish;     end         initial         $monitor($time,"read_write = %d ; data = %d ; address = %d;",read_write,data,address);     endmodule 

RESULT:

 20read_write = 1 ; data = 4 ; address = 1;  40read_write = 1 ; data = 5 ; address = 2;  60read_write = 1 ; data = 6 ; address = 3;  80read_write = 1 ; data = 7 ; address = 4;

Another way of getting the Stimulus is get the vectors from an external file. The external vector file is generally formatted so that each value in the file represents either a specific input pattern .Verilog HDL contains the $readmemb or $readmemh system tasks to do the file read if the file data is formatted in a specific way using either binary or hexadecimal data.

Fallowing example illustrates how to initialize a memory array from data stored as hexadecimal values in a data file, Simulate this file directly to see the results. Note: The data file must reside in the same directory  as the .v file for the module in this example.

EXAMPLE: verilog file module readmemh_demo; 

   reg [31:0] Mem [0:11];   

Page 29: Verification

   initial $readmemh("data.txt",Mem);       integer k; 

   initial begin        #10;        $display("Contents of Mem after reading data file:");        for (k=0; k<6; k=k+1) $display("%d:%h",k,Mem[k]);    end    endmodule 

EXAMPLE: data.txt file 234ac 23ca5 b3c34 23a4a 234ca b3234

RESULT:

0:000234ac 1:00023ca5 2:000b3c34 3:00023a4a 4:000234ca 5:000b3234

With the above approach,its not possible to list all the combinations manually if the number of vectors get increases.

SYSTEM FUNCTION RANDOM A MYTH

Verilog has system function $random ,which can be used to generate random input vectors. With this approach, we can generate values which we wouldn't have got, if listed manually. In this topic I would like to discuss what natural things happening behind $random and how we use it in different manners.

EXAMPLE:

Page 30: Verification

module Tb_mem();     reg clock;     reg read_write;     reg [31:0] data;     reg [31:0] address;         initial     begin         clock = 0;         forever             #10 clock = ~clock;     end         initial     begin         repeat(5)@(negedge clock)          begin read_write = $random ; data = $random;address = $random; end         $finish;     end         initial         $monitor($time,"read_write = %d ; data = %d ; address = %d;",read_write,data,address);     endmodule 

RESULT:

                 20read_write = 0 ; data = 3230228097 ; address = 2223298057;                  40read_write = 1 ; data = 112818957 ; address = 1189058957;                  60read_write = 1 ; data = 2302104082 ; address = 15983361;                  80read_write = 1 ; data = 992211318 ; address = 512609597;

$random()  system function returns a new 32-bit random number each time it is called. The random number is a signed integer; it can be positive or negative. The following example demonstrates random generation of signed numbers.

EXAMPLE: module Tb();     integer address;         initial 

Page 31: Verification

    begin         repeat(5)         #1 address = $random;     end         initial         $monitor("address = %d;",address);     endmodule 

RESULT:

address = 303379748; address = -1064739199; address = -2071669239; address = -1309649309; address = 112818957;

We have seen how to generate random numbers. But the numbers range from - (2**32 -1) to 2 **32. Most of the time, the requirement don't need this range. For example, take a memory. The address starts from 0 to some 1k or 1m.Generating a random address which DUT is not supporting is meaningless. In verilog there are no constructs to constraint randomization. Fallowing example demonstrated how to generate random number between 0 to 10.Using % operation, the remainder of any number is always between 0 to 10.

EXAMPLE: module Tb();     integer add_1; 

    initial     begin         repeat(5)         begin             #1;             add_1 = $random % 10;         end     end         initial         $monitor("add_1 = %d",add_1); 

Page 32: Verification

endmodule 

RESULT:

add_1 = 8; add_1 = 4294967287; add_1 = 4294967295; add_1 = 9; add_1 = 9;

OOPS!...... The results are not what is expected. The reason is $random generates negative numbers also. The following example demonstrates proper way of generating a random number between 0 to 10. Concatenation operator returns only bit vector. Bit vectors are unsigned, so the results are correct as we expected. Verilog also has $unsigned systemtask to convert signed numbers to signed number. This can also be used to meet the requirements. The following example shows the usage of concatenation operator and $unsigned.

EXAMPLE: module Tb();      integer add_2;      reg [31:0] add_1;      integer add_3;           initial      begin          repeat(5)          begin              #1;              add_1 = $random % 10;              add_2 = {$random} %10 ;              add_3 = $unsigned($random) %10 ;          end      end           initial      $monitor("add_3 = %d;add_2 = %d;add_1 = %d",add_3,add_2,add_1); 

endmodule 

RESULT:

Page 33: Verification

add_3 = 7;add_2 = 7;add_1 = 8 add_3 = 7;add_2 = 7;add_1 = 4294967287 add_3 = 1;add_2 = 2;add_1 = 4294967295 add_3 = 7;add_2 = 8;add_1 = 9 add_3 = 9;add_2 = 2;add_1 = 9

The above example shows the generation of numbers from 0 to N.Some specification require the range to start from non Zero number. MIN + {$random} % (MAX - MIN ) will generate random numbers between MIN and MAX.

EXAMPLE: module Tb();     integer add;         initial     begin         repeat(5)         begin             #1;             add = 40 + {$random} % (50 - 40) ;             $display("add = %d",add);         end     end endmodule 

RESULT:

add = 48 add = 47 add = 47 add = 47 add = 47

Now  how to generate a random number between two ranges? The number should be between MIN1 and MAX1 or MIN2 and MAX2.The following example show how to generate this specification.

EXAMPLE: module Tb(); 

Page 34: Verification

    integer add;         initial     begin         repeat(5)         begin             #1;             if($random % 2)                 add = 40 + {$random} % (50 - 40) ;             else                 add = 90 + {$random} % (100 - 90) ;             $display("add = %d",add);         end     end endmodule 

RESULT:

add = 97 add = 47 add = 47 add = 42 add = 49

All the random number generates above generate numbers of 32 vector. Not always the requirements are 32 bit .For example, to generate a 5 bit and 45 bit vector random number, the following method can be used.

EXAMPLE: module Tb();     reg [4:0]  add_1;     reg [44:0] add_2; 

    initial     begin         repeat(5)         begin             add_1 = $random ;             add_2 = {$random,$random};             $display("add_1 = %b,add_2 = %b ",add_1,add_2);         end 

Page 35: Verification

    end endmodule 

RESULTS:

add_1 = 00100,add_2 = 111101000000110000100100001001101011000001001  add_1 = 00011,add_2 = 110110000110101000110110111111001100110001101  add_1 = 00101,add_2 = 100100001001000000000111100111110001100000001  add_1 = 01101,add_2 = 100010111011000011110100011011100110100111101  add_1 = 01101,add_2 = 101111000110001111100111111011110100111111001  

Some protocols require a random number which is multiple some number. For example, Ethernet packet is always in multiples of 8bits,and PCIExpress packets are multiples of 4byts .Look at the following example. It generates a random number which is multiple of 3 and 5.

EXAMPLE: module Tb();     integer num_1,num_2,tmp; 

    initial     begin         repeat(5)         begin             #1;             tmp = {$random} / 3;             num_1 = (tmp) * 3;             tmp = {$random} / 3;             num_2 = (tmp) * 5;             $display("num_1 = %d,num_2 = %d",num_1,num_2);         end     end endmodule 

RESULT:

   num_1 = 303379746,num_2 = -1064739195    num_1 = -2071669239,num_2 = -1309649305    num_1 = 112818957,num_2 = 1189058955    num_1 = -1295874969,num_2 = -1992863210    num_1 = 15983361,num_2 = 114806025

Page 36: Verification

All the above example show that the random numbers are integers only. In verilog there is not special construct to generate a random real number. The following method shows the generation of random real number.

EXAMPLE: module Tb(); 

integer num_1,num_2,num_3; real r_num; 

   initial    begin        repeat(5)        begin            #1;            num_1 = $random;            num_2 = $random;            num_3 = $random;            r_num = num_1 + ((10)**(-(num_2)))*(num_3);            $display("r_num = %e",r_num);        end    end endmodule 

RESULT:

    r_num = -2.071669e+03     r_num = 2641.189059e+013     r_num = 976361.598336e+01     r_num = 57645.126096e+02     r_num = 24589.097015e+0

To generate random real number , system function $bitstoreal can also be used.

EXAMPLE: module Tb(); 

   real r_num; 

   initial 

Page 37: Verification

   begin        repeat(5)        begin           #1;           r_num = $bitstoreal({$random,$random});           $display("r_num = %e",r_num);        end    end endmodule 

RESULTS:

   r_num = 1.466745e-221    r_num = -6.841798e-287    r_num = 2.874848e-276    r_num = -3.516622e-64    r_num = 4.531144e-304

If you want more control over randomizing real numbers in terms of sign, exponential and mantissa, use $bitstoreal() as shown in example below. For positive numbers, use sgn = 0 etc.

EXAMPLE: module Tb();     reg  sgn;     reg [10:0] exp;     reg [51:0] man;      real r_num; 

    initial     begin         repeat(5)         begin             sgn = $random;             exp = $random;             man = $random;             r_num = $bitstoreal({sgn,exp,man});             $display("r_num = %e",r_num);         end     end endmodule RESULTS:

Page 38: Verification

   r_num = 3.649952e+193    r_num = -1.414950e-73    r_num = -3.910319e-149    r_num = -4.280878e-196    r_num = -4.327791e+273

Sometimes it is required to generate random numbers without repetition. The random numbers should be unique. For example, to generate 10 random numbers b/w 0 to 9 without repetition, the following logic can be used.

EXAMPLE: module Tb();    integer num,i,j,index;    integer arr[9:0];    reg ind[9:0];    reg got;       initial    begin       index=0;       for(i=0;i<10;i=i+1)       begin          arr[i] = i;          ind[i] = 1;       end 

      for(j = 0;j<10 ;j=j+1)       begin          got = 0;          while(got == 0)          begin              index = { $random() } % 10;              if(ind[index] == 1)              begin                  ind[index] = 0;                  got = 1;                  num = arr[index];              end 

Page 39: Verification

         end          $write("| num=%2d |",num);       end 

   end endmodule 

RESULT:

| num= 8 || num= 7 || num= 5 || num= 2 || num= 1 || num= 9 || num= 6 || num= 4 || num= 0 || num= 3 |

Random number system function has a argument called seed. The seed parameter controls the numbers that $random returns such that different seeds generate different random streams. The seed parameter shall be either a reg, an integer, or a time variable. The seed value should be assigned to this variable prior to calling $random. For each system function, the seed parameter is an in-out parameter; that is, a value is passed to the function and a different value is returned.  

EXAMPLE: module Tb();    integer num,seed,i,j; 

   initial    begin        for(j = 0;j<4 ;j=j+1)        begin            seed = j;            $display(" seed is %d",seed);            for(i = 0;i < 10; i=i+1)            begin                num = { $random(seed) } % 10;                $write("| num=%2d |",num);            end            $display(" ");        end    end endmodule 

RESULT:

Page 40: Verification

seed is 0 | num= 8 || num= 7 || num= 7 || num= 7 || num= 7 || num= 7 || num= 5 || num= 2 || num= 1 || num= 9 |  seed is 1 | num= 8 || num= 8 || num= 2 || num= 2 || num= 6 || num= 3 || num= 8 || num= 5 || num= 5 || num= 5 |  seed is 2 | num= 8 || num= 1 || num= 0 || num= 5 || num= 0 || num= 8 || num= 6 || num= 7 || num= 1 || num= 6 |  seed is 3 | num= 8 || num= 2 || num= 2 || num= 3 || num= 8 || num= 6 || num= 1 || num= 4 || num= 3 || num= 9 |

The $random function has its own implicit variable as seed when the used is not giving explicitly giving seed. The following example shows that seed = 0 and implicit seed are having same sequence. It means that the implicitly taken seed is also 0.

EXAMPLE: module Tb();      integer num,seed,i,j; 

     initial      begin          seed = 0; 

         for(j = 0;j<2 ;j=j+1)          begin              if(j ==0)                  $display(" seed is %d",seed);              else                  $display(" No seed is given "); 

             for(i = 0;i < 10; i=i+1)              begin                  if( j == 0)                      num = { $random(seed) } % 10;                  else                      num = { $random() } % 10;                  $write("| num=%2d |",num);              end 

Page 41: Verification

             $display(" ");          end      end endmodule 

RESULT:

seed is 0 | num= 8 || num= 7 || num= 7 || num= 7 || num= 7 || num= 7 || num= 5 || num= 2 || num= 1 || num= 9 |  No seed is given  | num= 8 || num= 7 || num= 7 || num= 7 || num= 7 || num= 7 || num= 5 || num= 2 || num= 1 || num= 9 |  

The system functions shall always return the same value given the same seed. This facilitates debugging by making the operation of the system repeatable. The argument for the seed parameter should be an integer variable that is initialized by the user and only updated by the system function. This ensures the desired distribution is achieved.

EXAMPLE: module Tb();     integer num,seed,i,j; 

    initial     begin         for(j = 0;j<4 ;j=j+1)         begin             seed = 2;             $display(" seed is %d",seed);             for(i = 0;i < 10; i=i+1)             begin                 num = { $random(seed) } % 10;                 $write("| num=%2d |",num);             end             $display(" ");         end     end endmodule 

RESULT:

seed is 2

Page 42: Verification

| num= 8 || num= 1 || num= 0 || num= 5 || num= 0 || num= 8 || num= 6 || num= 7 || num= 1 || num= 6 |  seed is 2 | num= 8 || num= 1 || num= 0 || num= 5 || num= 0 || num= 8 || num= 6 || num= 7 || num= 1 || num= 6 |  seed is 2 | num= 8 || num= 1 || num= 0 || num= 5 || num= 0 || num= 8 || num= 6 || num= 7 || num= 1 || num= 6 |  seed is 2 | num= 8 || num= 1 || num= 0 || num= 5 || num= 0 || num= 8 || num= 6 || num= 7 || num= 1 || num= 6 |

Seed is inout port. Random number system function  returns a random number and also returns a random number to seed inout argument also. The results of the following example demonstrates how the seed value is getting changed.

EXAMPLE: module Tb();     integer num,seed,i,j;         initial     begin         seed = 0;         for(j = 0;j<10 ;j=j+1)         begin             num = { $random(seed) } % 10;             $write("| num=%2d |",num);             $display(" seed is %d ",seed);         end     end endmodule 

RESULT:

| num= 8 | seed is -1844104698  | num= 7 | seed is 1082744015  | num= 7 | seed is 75814084  | num= 7 | seed is 837833973  | num= 7 | seed is -2034665166  | num= 7 | seed is -958425333  | num= 5 | seed is 851608272  | num= 2 | seed is 154620049  | num= 1 | seed is -2131500770  

Page 43: Verification

| num= 9 | seed is -2032678137

From the above results we can make a table of seed values and return values of $random. If a seed is taken from the table, then rest of the sequence has to follow sequence in table.  

Table is as falows for initial seed 0;

| num= 8 | seed is -1844104698  | num= 7 | seed is 1082744015  | num= 7 | seed is 75814084  | num= 7 | seed is 837833973  | num= 7 | seed is -2034665166  | num= 7 | seed is -958425333  | num= 5 | seed is 851608272  | num= 2 | seed is 154620049  | num= 1 | seed is -2131500770  | num= 9 | seed is -2032678137 . . . . . table goes on........

In the following example, the seed is 837833973, which is the 4 th seed from the above table.

EXAMPLE: module Tb();     integer num,seed,i,j;         initial     begin         seed = 837833973;         for(j = 0;j<10 ;j=j+1)         begin             num = { $random(seed) } % 10;             $write("| num=%2d |",num); 

Page 44: Verification

            $display(" seed is %d ",seed);         end     end endmodule 

RESULTS:

| num= 7 | seed is -2034665166  | num= 7 | seed is -958425333  | num= 5 | seed is 851608272  | num= 2 | seed is 154620049  | num= 1 | seed is -2131500770  | num= 9 | seed is -2032678137  | num= 8 | seed is -1155272804  | num= 7 | seed is -1634874387  | num= 9 | seed is -153856566  | num= 2 | seed is -970066749  

From the above example we can come to conclusion that $random is not giving a random number. It is randomizing seed and returning corresponding number for that seed.

Total possible seed values are 4294967295. Is it possible for $random to generate all the seeds? . Lets say ,if the seed gets repeated after 10 iterations, then after the 10 iterations, same values are repeated. So $random is circulating inside a chain of 10 numbers.  

The following example demonstrates how $random misses many seeds. I tried to display the seeds between 0 to 20 in the chain formed by initial seed of 0. Results show that total possible seeds are 4294967295 , and number of seeds possible in seed chain are 4030768279 , so we are missing some seeds. Look at the seeds between 0 to 20. Seed == 1 is missing.

EXAMPLE:  module Tb();      integer num,seed,j;      reg [0:31] i;           initial      begin          i = 0; 

Page 45: Verification

         seed = 1;          while (seed != 0)          begin              if(i == 0)                  seed = 0;              i = i + 1;              num = $random(seed);              if(seed < 20 && seed > 0)                  $display(" seed is %d after values %d ",seed,i);          end          $display(" seed is one after this number of random numbers %0d  total numbers available are %d",i,{32'hffff_ffff});      end endmodule 

RESULTS:

seed is 10 after values 93137101  seed is 17 after values 307298440  seed is 2 after values 410139893  seed is 12 after values 483530075  seed is 19 after values 592243262  seed is 3 after values 720224974  seed is 11 after values 1342230278  seed is 15 after values 2032553666  seed is 7 after values 2266624778  seed is 13 after values 2362534380  seed is 5 after values 2512466932  seed is 9 after values 2575033104  seed is 16 after values 2988686279  seed is 4 after values 3173376451  seed is 6 after values 3483433473  seed is 8 after values 3547878575  seed is 14 after values 3663208793  seed is 18 after values 3930700709  seed is zero after this number of random numbers 4030768279  total numbers available are 4294967295

Now I tried to simulate with seed== 1 . Its interesting to know that some how the sequence is able to enter this chain which is formed with seed==0 and there is no seed value 1 in this chain and my simulation hanged. So aborted the simulation and parter results show that the initial seed = 1 with enter the chain formed by seed 0.

Page 46: Verification

EXAMPLE: module Tb();     integer num,seed,j;     reg [0:31] i;         initial     begin         i = 0;         seed = 0;         while (seed != 1)         begin             if(i == 0)                 seed = 1;             i = i + 1;             num = $random(seed);             if(seed < 20 && seed > 0)                 $display(" seed is %d after values %d ",seed,i);         end         $display(" seed is one after this number of random numbers %0d  total numbers available are %d",i,{32'hffff_ffff});     end endmodule 

RESULTS:

seed is 10 after values 357336117  seed is 17 after values 571497456  seed is 2 after values 674338909  seed is 12 after values 747729091  seed is 19 after values 856442278  seed is 3 after values 984423990  seed is 11 after values 1606429294  seed is 15 after values 2296752682  seed is 7 after values 2530823794  seed is 13 after values 2626733396  seed is 5 after values 2776665948  seed is 9 after values 2839232120  seed is 16 after values 3252885295  seed is 4 after values 3437575467  seed is 6 after values 3747632489  seed is 8 after values 3812077591  seed is 14 after values 3927407809  

Page 47: Verification

seed is 18 after values 4194899725  seed is 10 after values 357336117  seed is 17 after values 571497456  seed is 2 after values 674338909  seed is 12 after values 747729091  seed is 19 after values 856442278  seed is 3 after values 984423990  

Verilog also has other system functions to generate random numbers. Each of these functions returns a pseudo-random number whose characteristics are described by the function name. Following are the Verilog random number generator system functions:

      $random       $dist_chi_square       $dist_erlang        $dist_exponential        $dist_normal       $dist_poisson        $dist_t       $dist_uniform  

All parameters to the system functions are integer values. For the exponential , Poisson , chi-square , t , and erlang  functions, the parameters mean, degree of freedom, and k_stage must be greater than 0 .

$dist_uniform(seed, min, max) is similar to min + {$random(seed)}%(max-min+1),the difference is that in $dist_uniform,the distribution is uniform. $dist_uniform returns a number between min and max. In the $dist_uniform function, the start and end parameters are integer inputs that bound the values returned. The start value should be smaller than the end value.

The mean parameter, used by $dist_normal, $dist_exponential, $dist_poisson, and $dist_erlang, is an integer input that causes the average value returned by the function to approach the value specified. The standard deviation parameter used with the $dist_normal function is an integer input that helps determine the shape of the density function. Larger numbers for standard deviation spread the returned values over a wider range.

The degree of freedom parameter used with the $dist_chi_square and $dist_t functions is an integer input that helps determine the shape of the density function. Larger numbers spread the returned values over a wider range.

Page 48: Verification

EXAMPLE: module Tb();     integer num_1,num_2,seed;         initial     begin         seed = 10;         repeat(5)         begin             #1;             num_1 = $dist_uniform(seed,20,25);             num_2 = $dist_uniform(seed,50,55);             $display("num_1 = %d,num_2 = %d",num_1,num_2);         end     end endmodule 

RESULTS:

num_1 = 20,num_2 = 50 num_1 = 23,num_2 = 55 num_1 = 22,num_2 = 54 num_1 = 25,num_2 = 51 num_1 = 23,num_2 = 55

As I discussed $random changes its seed , Lets see whether $dist_uniform is also doing the same.

EXAMPLE:

module Tb();     integer num_1,num_2,seedd,seedr;         initial     begin         seedd = 10;         seedr = 10;         repeat(5)         begin             #1; 

Page 49: Verification

            num_1 = $dist_uniform(seedd,20,25);             num_2 = 20 + ({$random(seedr)} % 6);             $display("num_1 = %d,num_2 = %d,seedd = %d seedr = %d",num_1,num_2,seedd,seedr);         end     end endmodule RESULTS:

num_1 = 20,num_2 = 22,seedd = 690691 seedr = 690691 num_1 = 20,num_2 = 20,seedd = 460696424 seedr = 460696424 num_1 = 23,num_2 = 22,seedd = -1571386807 seedr = -1571386807 num_1 = 25,num_2 = 21,seedd = -291802762 seedr = -291802762 num_1 = 22,num_2 = 23,seedd = 1756551551 seedr = 1756551551

Look at the results... Its interesting to note that $random and $dist_uniform have same seed sequence flow also.

As I mentioned ,$dist_uniform(seed, min, max) is similar to min + {$random(seed)}%(max-min+1). "similar" means they have some common functionality. $dist_uniform is having uniform distribution, $random for that range, is also uniformly distributed. Fallowing example ,demonstrates that $dist_uniform and $random are uniformly distributed.

EXAMPLE: module Tb();      integer num,seed;      integer num_20,num_21,num_22,num_23,num_24,num_25;           initial      begin          seed = 10;          num_20 = 0;num_21 = 0;num_22 = 0;num_23 = 0;num_24 = 0;num_25 =0;                   repeat(6000)          begin                       num = $dist_uniform(seed,20,25);              if(num == 20 ) 

Page 50: Verification

                 num_20 = num_20 + 1;              if(num == 21)                  num_21 = num_21 + 1;              if(num == 22)                  num_22 = num_22 + 1;              if(num == 23)                  num_23 = num_23 + 1;              if(num == 24)                  num_24 = num_24 + 1;              if(num == 25)                  num_25 = num_25 + 1;                   end          $display("num_20 = %0d;num_21 = %0d;num_22 = %0d;num_23 = %0d;num_24 = %0d;num_25 = %0d",num_20,num_21,num_22,num_23,num_24,num_25);      end endmodule 

RESULTS:

num_20 = 1014;num_21 = 983;num_22 = 946;num_23 = 1023;num_24 = 1014;num_25 = 1020

EXAMPLE: module Tb();     integer num;     integer num_20,num_21,num_22,num_23,num_24,num_25;         initial     begin         seed = 10;         num_20 = 0;num_21 = 0;num_22 = 0;num_23 = 0;num_24 = 0;num_25 =0;                 repeat(6000)         begin                      num = 20 +( {$random() } %6 );              if(num == 20 )                  num_20 = num_20 + 1;              if(num == 21)                  num_21 = num_21 + 1;              if(num == 22) 

Page 51: Verification

                 num_22 = num_22 + 1;              if(num == 23)                  num_23 = num_23 + 1;              if(num == 24)                  num_24 = num_24 + 1;              if(num == 25)                  num_25 = num_25 + 1;                      end         $display("num_20 = %0d;num_21 = %0d;num_22 = %0d;num_23 = %0d;num_24 = %0d;num_25 = %0d",num_20,num_21,num_22,num_23,num_24,num_25);     end endmodule RESULTS:

num_20 = 996;num_21 = 999;num_22 = 959;num_23 = 996;num_24 = 1002;num_25 = 1048

As I mentioned ,$dist_uniform(seed, min, max) is similar to min + {$random(seed)}%(max-min+1). "similar" means they have some difference. The difference is that they generate different sequence.  

EXAMPLE: module Tb();     integer num_1,num_2,seedd,seedr;         initial     begin         seedd = 10;         seedr = 10;         repeat(5)         begin             #1;             num_1 = $dist_uniform(seedd,20,25);             num_2 = 20 + ({$random(seedr)} % 6);             $display("num_1 = %d,num_2 = %d",num_1,num_2);         end     end endmodule RESULTS:

num_1 = 20,num_2 = 22

Page 52: Verification

num_1 = 20,num_2 = 20 num_1 = 23,num_2 = 22 num_1 = 25,num_2 = 21 num_1 = 22,num_2 = 23

Till now what we have seen is $random has uniform distribution over integer values. It means that distribution should be uniform across all the bits in 32 bit vector also. The following example shows that bits positions 2,3,4,11,12,13 have equal probability of getting 0. For demonstration I showed some indexes only. Try out rest of them and see that results is same for all the bis.

EXAMPLE: module Tb();     integer num;     integer num_2,num_3,num_4,num_11,num_12,num_13;         initial     begin         seed = 10;         num_2 = 0;num_3 = 0;num_4 = 0;num_11 = 0;num_12 = 0;num_13 =0;                 repeat(6000)         begin                     num = $random();             if(num[2] == 0 )                 num_2 = num_2 + 1;             if(num[3] == 0)                 num_3 = num_3 + 1;             if(num[4] == 0)                 num_4 = num_4 + 1;             if(num[11] == 0)                 num_11 = num_11 + 1;             if(num[12] == 0)                 num_12 = num_12 + 1;             if(num[13] == 1)                 num_13 = num_13 + 1;                 end         $display("num_2 = %0d;num_3 = %0d;num_4 = %0d;num_11 = %0d;num_12 = %0d;num_13 = %0d",num_2,num_3,num_4,num_11,num_12,num_13); 

Page 53: Verification

    end endmodule 

RESULTS:

num_2 = 3012;num_3 = 2964;num_4 = 3065;num_11 = 3001;num_12 = 2964;num_13 = 3025

The distribution is uniform for system function $random. Suppose if the requirement is to generate random numbers for more than one variable, and all the variables should have uniform distribution, then use different seeds for each variable. Otherwise distribution is distributed on all the variables as overall. But for lower bits, the distribution is same as shown in example.

EXAMPLE: module Tb();     integer seed;     reg [1:0] var_1,var_2,var3,var4;     integer num_2,num_3,num_1,num_0;     integer cou_2,cou_3,cou_1,cou_0;         initial     begin         seed = 10;         num_2 = 0;num_3= 0;num_1= 0;num_0= 0;         cou_2= 0;cou_3= 0;cou_1= 0;cou_0= 0;                         repeat(40000)         begin                     var_1 = $random();             var3 = $random();             var4 = $random();             var_2 = $random();              if(var_1 == 0 )                num_0 = num_0 + 1;             if(var_1 == 1 )                num_1 = num_1 + 1;             if(var_1 == 2 )                num_2 = num_2 + 1; 

Page 54: Verification

            if(var_1 == 3 )                num_3 = num_3 + 1;                         if(var_2 == 0 )                cou_0 = cou_0 + 1;             if(var_2 == 1 )                cou_1 = cou_1 + 1;             if(var_2 == 2 )                cou_2 = cou_2 + 1;             if(var_2 == 3 )                cou_3 = cou_3 + 1;         end         $display("num_2 = %0d;num_3= %0d;num_1= %0d;num_0= %0d;",num_2,num_3,num_1,num_0);         $display("cou_2= %0d;cou_3= %0d;cou_1= %0d;cou_0= %0d;",cou_2,cou_3,cou_1,cou_0);      end endmodule 

RESULTS:

num_2 = 9984;num_3= 10059;num_1= 10002;num_0= 9955; cou_2= 10060;cou_3= 9934;cou_1= 10072;cou_0= 9934;

Use system time as seed, so the same TB simulated at different times have different random sequences and there is more probability of finding bugs. The following is c code useful in PLI to get system time in to verilog.

    #include <stdio.h>     #include <time.h>     char *get_time_string(int mode24);     int get_systime() {     time_t seconds;     seconds = time (NULL);     return seconds;     }

Verilog 1995, every simulator has its own random number generation algorithm.

Page 55: Verification

Verilog 2001 , The standard made that every simulator has to follow same algorithm. So the same random number sequence can seen on different simulators for same seed.

Don't expect that the same sequence is generated on all the simulators. They are only following same algorithm. The reason is, race condition. Look at the following example, both the statements num_1 and num_2 are scheduled to execute at same simulation time. The order of execution is not known. Some simulators take num_1 as the first statement to execute and some other num_2 .If the TB is built without any race condition to $random function calls, then the same random sequence can be generated on different simulators.  

EXAMPLE:   initial   # 10  num_1 = $random; 

  initial   #10  num_2 = $random; 

RACE CONDITION

Verilog is easy to learn because its gives quick results. Although many users are telling that their work is free from race condition. But the fact is race condition is easy to create, to understand, to document but difficult to find. Here we will discuss regarding events which creates the race condition & solution for that. 

What Is Race Condition?

When two expressions are scheduled to execute at same time, and if the order of the execution is not determined, then race condition occurs.

EXAMPLE module race();     wire p;    reg q;     assign p = q; 

    initial begin         q = 1;         #1 q = 0;         $display(p);     end endmodule 

Page 56: Verification

The simulator is correct in displaying either a 1 or a 0. The assignment of 0 to q enables an update event for p. The simulator may either continue or execute the $display system task or execute the update for p, followed by the $display task. Then guess what can the value of p ? Simulate the above code in your simulator. Then simulate the following code . Statement "assign p = q;" is changed to end of the module.

EXAMPLE module race();     wire p;    reg q; 

   assign p = q; 

   initial begin       q = 1;       #1 q = 0;       $display(p);    end endmodule 

Analyze the effect if I change the order of the assign statement.

Why Race Condition?

To describe the behavior of electronics hardware at varying levels of abstraction, Verilog HDL has to be a parallel programming language and Verilog simulator and language itself are standard of IEEE, even though there are some nondeterministic events which is not mentioned in IEEE LRM and left it to the simulator algorithm, which causes the race condition. So it is impossible to avoid the race conditions from the language but we can avoid from coding styles.

Look at following code. Is there any race condition?

EXAMPLE: initial begin    in = 1; 

Page 57: Verification

   out <= in; end 

Now if you swap these two lines:

EXAMPLE initial begin    out <= in;    in = 1; end 

Think, is there any race condition created? Here first statement will schedule a non-blocking update for "out" to whatever "in" was set to previously, and then "in" will be set to 1 by the blocking assignment. Any statement whether it is blocking or nonblocking statements in a sequential block (i.e. begin-end block) are guaranteed to execute in the order they appear. So there is no race condition in the above code also. Since it is easy to make the "ordering mistake", one of Verilog coding guidelines is: "Do not mix blocking and nonblocking assignments in the same always block". This creates unnecessary doubt of race condition.

When Race Is Visible?

Sometimes unexpected output gives clue to search for race. Even if race condition is existing in code, and if the output is correct, then one may not realize that there exists race condition in their code. This type of hidden race conditions may come out during the following situation.

When different simulators are used to run the same code. Some times when the new release of the simulator is used. Adding more code to previous code might pop out the previously hidden race. If the order of the files is changed. When using some tool specific options. If the order of the concurrent blocks or concurrent statements is changed.(One example is already discussed in the previous topics)

Some simulators have special options which reports where exactly the race condition is exists. Linting tools can also catch race condition.

How To Prevent Race Condition?

Page 58: Verification

There are many details which is unspecified between simulators. The problem will be realized when you are using different simulators. If you are limited to design guidelines then there is less chance for race condition but if you are using Verilog with all features for Testbench, then it is impossible to avoid. Moreover the language which you are using is parallel but the processor is sequential. So you cant prevent race condition.

Types Of Race Condition

Here we will see race condition closely. Types of race condition

Write-Write Race:

it occurs when same register is written in both the blocks.

EXAMPLE: always @(posedge clk)     a = 1; always @(posedge clk)     a = 5; 

Here you are seeing that one block is updating value of a while another also. Now which always block should go first. This is nondeterministic in IEEE standard and left that work to the simulator algorithm.

Read-Write Race:

it occurs when same register is read in one block and writes in another.

EXAMPLE: always @(posedge clk)     a = 1; always @(posedge clk)     b = a; 

Here you are seeing that in one always block value is assign to a while simultaneously its value is assign to b means a is writing and read parallel. This

Page 59: Verification

type of race condition can easily solved by using nonblocking assignment.

EXAMPLE always @(posedge clk)     a <= 1; always @(posedge clk)     b <= a; 

More Race Example:

1) Function calls

EXAMPLE: function incri(); begin    pkt_num = pkt_num + 1; end endfunction 

always @(...)    sent_pkt_num = incri(); 

always @(...)    sent_pkt_num_onemore = incri(); 

2) Fork join

EXAMPLE: fork   a =0;   b = a; join 

3) $random

EXAMPLE: always @(...)    $display("first Random number is %d",$random()); always @(...)    $display("second Random number is %d",$random()); 

Page 60: Verification

4) Clock race

EXAMPLE initial     clk = 0; always     clk = #5 ~clk; 

If your clock generator is always showing "X" then there is a race condition. There is one more point to be noted in above example. Initial and always starts executes at time zero.

5) Declaration and initial

EXAMPLE: reg a = 0; initial    a = 1; 

6)Testbench DUT race condition.

In test bench , if driving is done at posedge and reading in DUT is done at the same time , then there is race. To avoid this, write from the Testbench at negedge or before the posedge of clock. This makes sure that the DUT samples the signal without any race.

EXAMPLE: module DUT();     input d;     input clock;     output q;         always @(posedge clock)     q = d; 

endmodule 

module testbench(); 

     DUT dut_i(d,clk,q); 

Page 61: Verification

     initial      begin         @(posedge clk)         d = 1;         @(posedge clock)         d = 0;      end endmodule 

The above example has write read race condition. 

Event Terminology:

Every change in value of a net or variable in the circuit being simulated, as well as the named event, is considered an update event. Processes are sensitive to update events. When an update event is executed, all the processes that are sensitive to that event are evaluated in an arbitrary order. The evaluation of a process is also an event, known as an evaluation event.

In addition to events, another key aspect of a simulator is time. The term simulation time is used to refer to the time value maintained by the simulator to model the actual time it would take for the circuit being simulated. The term time is used interchangeably with simulation time in this section. Events can occur at different times. In order to keep track of the events and to make sure they are processed in the correct order, the events are kept on an event queue, ordered by simulation time. Putting an event on the queue is called scheduling an event.

The Stratified Event Queue

The Verilog event queue is logically segmented into five different regions. Events are added to any of the five regions but are only removed from the active region.

1) Events that occur at the current simulation time and can be processed in any order. These are the active events. 1.1 evaluation of blocking assignment. 1.2 evaluation of RHS of nonblocking assignment. 1.3 evaluation of continuous assignment. 1.4 evaluation of primitives I/Os 1.5 evaluation of $display or $write

2) Events that occur at the current simulation time, but that shall be processed after all the active events are processed. These are the inactive events.

Page 62: Verification

#0 delay statement.

3) Events that have been evaluated during some previous simulation time, but that shall be assigned at this simulation time after all the active and inactive events are processed. These are the nonblocking assign update events.

4) Events that shall be processed after all the active, inactive, and non blocking assign update events are processed. These are the monitor events. $strobe and $monitor

5) Events that occur at some future simulation time. These are the future events. Future events are divided into future inactive events, and future non blocking assignment update events.

Example : PLI tasks

The processing of all the active events is called a simulation cycle.

Determinism

This standard guarantees a certain scheduling order.

1) Statements within a begin-end block shall be executed in the order in which they appear in that begin-end block. Execution of statements in a particular begin-end block can be suspended in favor of other processes in the model; however, in no case shall the statements in a begin-end block be executed in any order other than that in which they appear in the source.

2) Non blocking assignments shall be performed in the order the statements were executed.

Consider the following example:

initial begin     a <= 0;     a <= 1; end 

When this block is executed, there will be two events added to the non blocking assign update queue. The previous rule requires that they be entered on the queue in source order; this rule requires that they be taken from the queue and performed in source order as well. Hence, at the end of time step 1, the variable a will be assigned 0 and then 1.

Page 63: Verification

Nondeterminism

One source of nondeterminism is the fact that active events can be taken off the queue and processed in any order. Another source of nondeterminism is that statements without time-control constructs in behavioral blocks do not have to be executed as one event. Time control statements are the # expression and @ expression constructs. At any time while evaluating a behavioral statement, the simulator may suspend execution and place the partially completed event as a pending active event on the event queue. The effect of this is to allow the interleaving of process execution. Note that the order of interleaved execution is nondeterministic and not under control of the user.

Guideline To Avoid Race Condition

(A). Do not mix blocking and nonblocking statements in same block. (B). Do not read and write using blocking statement on same variable.( avoids read write race) (C). Do not initialize at time zero. (D). Do not assign a variable in more than one block.( avoids write-write race) (E). Use assign statement for inout types of ports & do not mix blocking and nonblocking styles of declaration in same block. It is disallow variables assigned in a blocking assignment of a clocked always block being used outside that block and disallow cyclical references that don't go through a non-blocking assignment. It is require all non-blocking assignments to be in a clocked always block.  (F). Use blocking statements for combinational design and nonblocking for sequential design. If you want gated outputs from the flops, you put them in continuous assignments or an always block with no clock.

Avoid Race Between Testbench And Dut

Race condition may occurs between DUT and testbench. Sometimes verification engineers are not allowed to see the DUT, Sometimes they don't even have DUT to verify. Consider the following example. Suppose a testbench is required to wait for a specific response from its DUT. Once it receives the response, at the same simulation time it needs to send a set of stimuli back to the DUT.

Most Synchronous DUT works on the posedge of clock. If the Testbench is also taking the same reference, then we may unconditionally end in race condition. So it<92>s better to choose some other event than exactly posedge of cock. Signals are stable after the some delay of posedge of clock. Sampling race condition would

Page 64: Verification

be proper if it is done after some delay of posedge of clock. Driving race condition can be avoided if the signal is driven before the posedge of clock, so at posedge of clock ,the DUT samples the stable signal.  So engineers prefer to sample and drive on negedge of clock, this is simple and easy to debug in waveform debugger also.

CHECKER

Protocol Checker

Protocol checking is the mechanism we use to verify IO buses functionality. Protocol checkers are created to validate whether or not the DUT is compliant with the bus protocol. It does this by checking the clock-by-clock state of the bus interface, verifying that the DUT drives the bus in accordance to the rules and specifications of the bus protocol. Protocol checker verifies that DUT adheres to the interface protocol and also verifies that the assumptions about the temporal behavior of your inputs is correct.

The  Protocol checker is responsible for extracting signal information from the DUT and translating it into meaningful events and status information. This information is available to other components. It also supplies information needed for functional coverage. The  Protocol checker should never rely on information collected by other components such as the BFM.

 Typically, the buses that are checked are external buses and may be industry standard buses such as PCI, DDR, I2C or proprietary buses . Protocol checking may occur at a transaction or wire level.  Protocol checker does not considered the data, as data has nothing to do with interface. The data processing protocol checking is done in data checker which is generally in scoreboard( or tracker) .

Protocol checks can be divided mainly into 3 categories: 1) Duration checks 2) Condition checks3) Temporal or Sequence Checks.

Duration checks are the simplest since they involve a single signal. For example, the "req signal should be high for at least 3 clocks".

Data_checker

Data checker verifies the correctness of the device output. Data checking is based on comparing the output with the input. The data processing protocol checking is done in data checker which is generally in scoreboard( or tracker) . To do that you must:

Page 65: Verification

--Collect the output data from the DUT and parse it.--Match the output to its corresponding input.--Forecast the DUT output by calculating the expected output.--Compare the output to the input and to the expected results.

Modularization

One of the ways to reduce the amount of work is the ability to leverage components from one environment to the next. The concept of modularization is to break up a complex problem into manageable pieces, which has many benefits including increasing the quality, maintainability, and reusability of the environment.

In order to reuse components of one environment to another, it is important to separate functionality into specific entities. This separation allows the work to be distributed to multiple people. Task separation is very similar to and goes hand in hand with modularization. Another benefit of task separation is having different people understanding the functionality.

TASK AND FUNCTION

Tasks and functions can bu used to in much the same manner but there are some important differences that must be noted.

Functions

A function is unable to enable a task however functions can enable other functions. A function will carry out its required duty in zero simulation time.  Within a function, no event, delay or timing control statements are permitted. In the invocation of a function there must be at least one argument to be passed. Functions will only return a single value and cannot use either output or inout statements. Functions are synthesysable. Disable statements cannot be used. Function cannot have nonblocking statements.

EXAMPLE:function

module  function_calling(a, b,c);                        input a, b ; 

Page 66: Verification

    output c;     wire c;             function  myfunction;     input a, b;     begin         myfunction = (a+b);     end     endfunction         assign c =  myfunction (a,b);           endmodule 

Task

Tasks are capable of enabling a function as well as enabling other versions of a Task

Tasks also run with a zero simulation however they can if required be executed in a non zero simulation time. Tasks are allowed to contain any of these statements. A task is allowed to use zero or more arguments which are of type output, input or inout. A Task is unable to return a value but has the facility to pass multiple values via the output and inout statements. Tasks are not synthesisable. Disable statements can be used.

EXAMPLE:task module traffic_lights;     reg clock, red, amber, green;     parameter on = 1, off = 0, red_tics = 350,     amber_tics = 30, green_tics = 200; 

    initial red = off;     initial amber = off;     initial green = off; 

    always begin // sequence to control the lights.         red = on; // turn red light on         light(red, red_tics); // and wait.         green = on; // turn green light on

Page 67: Verification

        light(green, green_tics); // and wait.         amber = on; // turn amber light on         light(amber, amber_tics); // and wait.     end     // task to wait for tics positive edge clocks     // before turning color light off.     task light;     output color;     input [31:0] tics;     begin         repeat (tics) @ (posedge clock);         color = off; // turn light off.     end     endtask 

    always begin // waveform for the clock.         #100 clock = 0;         #100 clock = 1;     end endmodule // traffic_lights.

Task And Function Queries:

Why a function cannot call a task? As functions does not consume time, it can do any operation which does not consume time. Mostly tasks are written which consumes time. So a task call inside a function blocks the further execution of function utile it finished. But it<92>s not true. A function can call task if the task call consumes zero time, but the IEEE LRM doesn't allow.

Why tasks are not synthesized? Wrong question! Tasks can be synthesized if it doesn't consume time.

Why a function should return a value? There is no strong reason for this in Verilog. This restriction is removed in SystemVerilog.

Why a function should have at least one input? There is no strong reason for this in verilog. I think this restriction is not removed fin SystemVerilog. Some requirements where the inputs are taken from the global signal, those functions don<92>t need any input. A work around is to use a dummy input. If you have a better reason, just mail me at [email protected]

Page 68: Verification

Why a task cannot return a value? If tasks can return values, then Lets take a look at the following example.

A=f1(B)+f2(C); and f1 and f2 had delays of say 5 and 10? When would B and C be sampled, or global inside f1 and f2 be sampled? How long does then entire statement block? This is going to put programmers in a bad situation. So languages gurus made that tasks can't return .

Why a function cannot have delays? The answer is same as above. But in Open Vera, delays are allowed in function. A function returns a value and therefore can be used as a part of any expression. This does not allow any delay in the function.  

Why disable statements are not allowed in functions? If disable statement is used in function, it invalids the function and its return value. So disable statements are not allowed in function.

Constant Function:

Constant function calls are used to support the building of complex calculations of values at elaboration time. A constant function call shall be a function invocation of a constant function local to the calling module where the arguments to the function are constant expressions.

EXAMPLE:constant function. module ram_model (address, write, chip_select, data);      parameter data_width = 8;      parameter ram_depth = 256;      localparam adder_width = clogb2(ram_depth);      input [adder_width - 1:0] address;      input write, chip_select;      inout [data_width - 1:0] data; 

     //define the clogb2 function      function integer clogb2;      input depth;      integer i,result;      begin         for (i = 0; 2 ** i < depth; i = i + 1)         result = i + 1; 

Page 69: Verification

        clogb2 = result;      end endfunction 

Reentrant Tasks And Functions:

Tasks and functions without the optional keyword automatic are static , with all declared items being statically allocated. These items shall be shared across all uses of the task and functions executing concurrently. Task and functions with the optional keyword automatic are automatic tasks and functions. All items declared inside automatic tasks and functions  are allocated dynamically for each invocation. Automatic task items and function items cannot be accessed by hierarchical references.

EXAMPLE:

module auto_task();  task automatic disp;    input integer a;    input integer d;    begin        #(d) $display("%t d is %d a is %d", $time,d,a);    end endtask  initial  #10 disp(10,14);    initial  #14 disp(23,18); 

initial  #4 disp(11,14);  

initial #100  $finish;  endmodule RESULTS:

18 d is 14 a is 11

Page 70: Verification

24 d is 14 a is 10 32 d is 18 a is 23

EXAMPLE: module tryfact;      // define the function      function automatic integer factorial;          input [31:0] operand;          integer i;          if (operand >= 2)              factorial = factorial (operand - 1) * operand;          else              factorial = 1;      endfunction      // test the function      integer result;      integer n;      initial begin          for (n = 0; n <= 7; n = n+1) begin              result = factorial(n);              $display("%0d factorial=%0d", n, result);          end      end endmodule // tryfact

RESULTS:

0 factorial=1 1 factorial=1 2 factorial=2 3 factorial=6 4 factorial=24 5 factorial=120 6 factorial=720 7 factorial=5040

PROCESS CONTROL

Nonblocking Task

If there is a delay in a task and when it is called, it blocks the execution flow. Many times in verification it requires to start a process and continue with the rest of the flow. The following example demonstrated how the task block the execution flow.

Page 71: Verification

EXAMPLE: module tb();         initial     begin         blocking_task();         #5 $display(" Statement after blocking_task at %t ",$time);     end         task blocking_task();     begin        #10;        $display(" statement inside blocking task at %t",$time);     end     endtask endmodule RESULTS:

statement inside blocking task at                   10 Statement after blocking_task at                   15  

To make the task call does not block the flow, use events as follows. The event triggers the always block and the task is started. This does not block the flow.

EXAMPLE: module tb();     event e;     initial     begin         #1 ->e;         #5 $display(" Statement after blocking_task at %t ",$time);         #20 $finish;     end         always@(e)     begin         blocking_task();     end         task blocking_task(); 

Page 72: Verification

    begin         #10;         $display(" statement inside blocking task at %t",$time);     end     endtask endmodule RESULTS

Statement after blocking_task at                   6  statement inside blocking task at                  11

Fork/Join Recap:

Fork/join is a parallel block. Statements shall execute concurrently. Delay values for each statement shall be considered relative to the simulation time of entering the block. Delay control can be used to provide time-ordering for assignments Control shall pass out of the block when the last time-ordered statement executes. The timing controls in a fork-join block do not have to be ordered sequentially in time.

EXAMPLE:

module fork_join();    integer r ;       initial    fork       #50 r = 35;       #100 r = 24;       #150 r = 00;       #200 r = 7;       #250 $finish;    join       initial       $monitor("%t , r is %d",$time,r);    endmodule 

RESULTS:

                 50 , r is 35                 100 , r is 24

Page 73: Verification

                150 , r is 0                 200 , r is 7

As the statements are parallel running, there is race condition between some statements. In the following example, first statement after delay of 50 + 100, r is 24 and in second statement at 150 r is 00. But only the statement which is executed last overrides previous value.

EXAMPLE: module fork_join();     integer r ;         initial     fork        begin            #50 r = 35;            #100 r = 24;        end        #150 r = 00;        #200 r = 7;        #250 $finish;     join         initial        $monitor("%t , r is %d",$time,r);     endmodule 

RESULTS:

                 50 , r is 35                 150 , r is 24                 200 , r is 7

Fork/Join None

In the fork join, the parent process continues to execute after all the fork/join processes are completed. To continue the parent process concurrently with all the processes spawned by the fork use this trick. This is as simple as above nonblocking task example. Just use fork/join the always block as shown below.

Page 74: Verification

EXAMPLE: module tb();     event e; 

    initial     begin         #1 ->e;         #5 $display(" Statement after blocking_task at %t ",$time);         #40 $finish;     end         always@(e)     begin         fork             blocking_task_1();             blocking_task_2();         join     end         task blocking_task_1();     begin         #10;         $display(" statement inside blocking task_1 at %t",$time);     end     endtask         task blocking_task_2();     begin         #20;         $display(" statement inside blocking task_2 at %t",$time);     end     endtask     

endmodule RESULTS

Statement after blocking_task at                   6  statement inside blocking task_1 at                  11 statement inside blocking task_2 at                  21

Page 75: Verification

Fork/Join Any

If you want to continue the parent process after finishing any of the child process, then block the parent process until an event if triggered by the forked threads.

EXAMPLE: module tb();     event e,ee; 

    initial     begin         #1 ->e;         @(ee);         $display(" Statement after blocking_task at %t ",$time);         #40 $finish;     end         always@(e)     begin         fork         begin blocking_task_1(); -> ee;end         begin blocking_task_2(); -> ee;end         join     end         task blocking_task_1();     begin         #10;         $display(" statement inside blocking task_1 at %t",$time);     end     endtask         task blocking_task_2();     begin         #20;         $display(" statement inside blocking task_2 at %t",$time);     end     endtask endmodule RESULTS

statement inside blocking task_1 at                  11

Page 76: Verification

Statement after blocking_task at                   11  statement inside blocking task_2 at                  21

DISABLEING THE BLOCK

Disable

The disable statement stops the execution of a labeled block and skips to the end of the block. Blocks can be named by adding : block_name after the keyword begin or fork. Named block can only be disabled using disable statement.

This example illustrates how a block disables itself.

EXAMPLE: begin : block_name    rega = regb; disable block_name;    regc = rega; // this assignment will never execute end 

This example shows the disable statement being used as an early return from a task. However, a task disabling itself using a disable statement is not a short-hand for the return statement found in programming languages.

EXAMPLE: task abc(); begin : name    :    :    :    if( something happened)    disable name;    :    :    : end endtask 

Goto

Verilog does not have a goto, but the effect of a forward goto can be acheived as

Page 77: Verification

shown:

EXAMPLE: begin: name     ...     if (a)     disable name;     ... end 

Execution will continue with the next statement after the end statement when the disable is executed.  

Break

The break statement as in C can be emulated with disable as shown in the following example:

EXAMPLE: begin: break     for (i=0; i<16; i=i+1) begin         ...         if (exit)          disable break;         ...     end end  

Continue

The continue statement in C causes the current iteration of a loop to be terminated, with execution continuing with the next iteration. To do the same thing in Verilog, you can do this:

EXAMPLE: for (i=0; i<16; i=i+1) begin: name     ...     if (abort)     disable name;     ... end 

WATCHDOG

Page 78: Verification

A watchdog timer is a piece of code, that can take appropriate action when it judges that a system is no longer executing the correct sequence of code. In this topic ,I will discuss exactly the sort of scenarios a watch dog can detect, and the decision that must be made by watchdog. Generally speaking, a watchdog timer is based on a counter that counts down from some initial value to zero. If the counter reaches, then the appropriate action is take. If the required functionality is archived, watchdog can be disabled.  

In software world, in watchdog articles you will see various terms like strobing, stroking etc. In this topic I will use more visual metaphor of man kicking the dog periodically-with apologies to animal lovers. If the man stops kicking the dog, the dog will take advantage of hesitation and bite the man. The man has to take a proper decision for the dog bite. The process of restarting the watchdog timer's counter is sometimes called "kicking the dog.".Bugs in DUT can cause the testbench to hang, if they lead to an infinite loop and creating a deadlock condition. A properly designed watchdog should catch events that hang the testbench.  

Once your watchdog has bitten ,you have to decide what action to be taken. The testbench will usually assert the error message, other actions are also possible like directly stop simulation or just give a warning in performance tests.

In the following example, I have taken a DUT model so its easy to understand than a RTL to demonstrate watchdog.

DUT PROTOCOL: DUT has 3 signals.Clock a,b; output b should be 1 within 4 clock cycles after output a became 1.

There are two scenarios I generated in DUT. one is following the above protocol and the other violated the above rule. The testbench watchdog shows how it caught there two scenarios.

Page 79: Verification

EXAMPLE: module DUT(clock,a,b);     output a;     output b;     input clock;     reg a,b;         initial     begin         repeat(10)@(posedge clock) a = 0;b = 0;         @(posedge clock) a = 1;b = 0;         @(posedge clock) a = 0;b = 0;         @(posedge clock) a = 0;b = 0;         @(posedge clock) a = 0;b = 1;         repeat(10)@(posedge clock) a = 0;b = 0;         @(posedge clock) a = 1;b = 0;         @(posedge clock) a = 0;b = 0;     end endmodule 

module TB();      wire aa,bb;      reg clk;           DUT dut(clk,aa,bb);           always       #5 clk = ~clk;           initial      #400 $finish;           initial      begin          clk = 0;          $display(" TESTBENCH STARTED");          wait(aa == 1) ;          watchdog();          wait( aa == 1);          watchdog();      end           task watchdog(); 

Page 80: Verification

     begin          $display(" WATCHDOG : started at %0d ",$time);          fork : watch_dog              begin                  wait( bb == 1);                  $display(" bb is asserted time:%0d",$time);                  $display(" KICKING THE WATCHDOG ");                  disable watch_dog;              end              begin                  repeat(4)@(negedge clk);                  $display(" bb is not asserted time:%0d",$time);                  $display(" WARNING::WATCHDOG BITED ");                  disable watch_dog;              end          join      end      endtask 

endmodule 

RESULTS:

TESTBENCH STARTED WATCHDOG : started at 105  bb is asserted time:135 KICKING THE WATCHDOG  WATCHDOG : started at 245  bb is not asserted time:280 WARNING::WATCHDOG BITED

Statement " disable watch_dog " is the trick hear. If that statement is not there, the statement " wait(b == 1) " is waiting and the simulation goes hang. This watchdog is just giving a warning about bite. You can also assert a ERROR message and call $finish to stop simulation.

COMPILATION N SIMULATION SWITCHS

Compilation And Simulation Directives:

Page 81: Verification

Conditional Compilation directive switches vs Simulation directive switches

Verilog has following conditional compiler directives.

`ifdef  `else  `elsif  `endif  `ifndef  

The `ifdef compiler directive checks for the definition of a text_macro_name. If the text_macro_name is defined, then the lines following the `ifdef directive are included. If the text_macro_name is not defined and an `else directive exists, then this source is compiled. The `ifndef compiler directive checks for the definition of a text_macro_name. If the text_macro_name is not defined, then the lines following the `ifndef directive are included. If the text_macro_name is defined and an `else directive exists, then this source is compiled. If the `elsif directive exists (instead of the `else) the compiler checks for the definition of the text_macro_name. If the name exists the lines following the `elsif directive are included. The `elsif directive is equivalent to the compiler directive sequence `else `ifdef ... `endif. This directive does not need a corresponding `endif directive. This directive must be preceded by an `ifdef or `ifndef directive.

EXAMPLE: module switches(); 

initial begin `ifdef TYPE_1 $display(" TYPE_1 message "); `else   `ifdef TYPE_2   $display(" TYPE_2 message ");    `endif `endif end endmodule 

Compile with    +define+TYPE_1  Then simulate,result is

Page 82: Verification

RESULT:

TYPE_1 message  

Compile with    +define+TYPE_2 Then simulate,result is

RESULT:

TYPE_2 message

TYPE_1 and TYPE_2 are called switches.

In the above example, When TYPE_1 switch is given, statement " $display(" TYPE_1 message "); " is only compile and statement " $display(" TYPE_2 message "); " is not compiled. Similarly for TYPE_2 switch. It wont take much time to compile this small example. Compilation time is not small for real time verification environment. Compiler takes time for each change of conditional compilation switches.  

Simulation directives are simple. This is archived by `define macros. The following example demonstrated the same functionality as the above example.

EXAMPLE:

module switches(); 

initial begin if($test$plusargs("TYPE_1")) $display(" TYPE_1 message "); else   if($test$plusargs("TYPE_2"))    $display(" TYPE_2 message "); end endmodule 

No need to give +define+TYPE_1  or   +define+TYPE_2 during compilation

Page 83: Verification

Simulate with +TYPE_1

RESULT:

TYPE_1 message  

Simulate with   +TYPE_2 Then simulate,result is

RESULT:

TYPE_2 message

With the above style of programing,we can save recompilation times.

This system function searches the list of plusargs (like the $test$plusargs system function) for a user specified plusarg string. The string is specified in the first argument to the system function as either a string or a register which is interpreted as a string. If the string is found, the remainder of the string is converted to the type specified in the user_string and the resulting value stored in the variable provided. If a string is found, the function returns a non-zero integer. If no string is found matching, the function returns the integer value zero and the variable provided is not modified.

%d decimal conversion%o octal conversion%h hexadecimal conversion%b binary conversion%e real exponential conversion%f real decimal conversion%g real decimal or exponential conversion%s string (no conversion)

The first string, from the list of plusargs provided to the simuator, which matches the plusarg_string portion of the user_string specified shall be the plusarg string available for conversion. The remainder string of the matching plusarg (the remainder is the part of the plusarg string after the portion which matches the users plusarg_string) shall be converted from a string into the format indicated by the format string and stored in the variable provided. If there is no remaining string, the value stored into the variable shall either be a zero (0) or an empty string value.

Page 84: Verification

Example

module valuetest();      integer i;   real r;   reg [11:0] v;   reg [128:0] s;      initial   begin       if($value$plusargs("STRING=%s",s))           $display(" GOT STRING ");       if($value$plusargs("INTG=%d",i))           $display(" GOT INTEGER ");       if($value$plusargs("REAL=%f",r))           $display(" GOT REAL ");       if($value$plusargs("VECTOR=%b",v))           $display(" GOT VECTOR ");              $display( " String is %s ",s);       $display(" Integer is %d ",i);       $display(" Realnum is %f ",r);       $display(" Vector  is %b ",v);   end   endmodule

Compilation : command filename.vSimulation :command  +STRING=rrf +INTG=123 +REAL=1.32 +VECTOR=10101

RESULTS:

 GOT STRING  GOT INTEGER  GOT REAL  GOT VECTOR  String is               rrf  Integer is 123

Page 85: Verification

 Realnum is 1.320000e+00  Vector  is 000000010101

DEBUGGING

Debugging is a methodical process of finding and reducing the number of bugs. When a the outputs are of the DUT are not what expected, then a bug may be in DUT or sometimes it may be in testbench. Debuggers are software tools which enable the verification and design engineers to monitor the execution of a program, stop it, re-start it, run it in interactive mode.

The basic steps in debugging are:

--- Recognize that a bug exists --- Isolate the source of the bug --- Identify the cause of the bug --- Determine a fix for the bug --- Apply the fix and test it

Pass Or Fail

At the end of simulation of every test, TEST FAILED or TEST PASSED report should be generated. This is called self checking. Log files and Waveform viewer can help for further debugging if test failed.  

An error count should be maintained to keep track of number of errors occurred. Simplest way to increment an error counter is using named event.  

EXAMPLE: module top();     integer error;     event err;     //ur testbench logic     initial      begin         #10;         if("There is error ")            -> error;         #10         if("There is error ") 

Page 86: Verification

           -> error;         #10         if("There is error ")            -> error;         // call final block to finish simulation             end         //Initilize error to 0     initial         error = 0;     // count number of errors     always@(err)         error = error +1 ;         // final block--to end simulation     task finish();     begin         #10; // so delay to make sure that counter increments for the last triggered error.         if( error == 0)             $dsplay("************ TEST PASSED ***************");         else             $dsplay("************ TEST FAILED ***************");             end     endtask     endmodule 

Waveform Viewer:

For post process debug, Waveform viewer needs VCD(value change dump) file. A value change dump (VCD) file contains information about value changes on selected variables in the design stored by value change dump system tasks. Two types of VCD files exist:

a) Four state: to represent variable changes in 0, 1, x, and z with no strength information. b) Extended: to represent variable changes in all states and strength information.

This clause describes how to generate both types of VCD files and their format.

Page 87: Verification

The steps involved in creating the four state VCD file are listed below . a) Insert the VCD system tasks in the Verilog source file to define the dump file name and to specify the variables to be dumped. b) Run the simulation.

A VCD file is an ASCII file which contains header information, variable definitions, and the value changes for all variables specified in the task calls. Several system tasks can be inserted in the source description to create and control the VCD file.

The $dumpfile task shall be used to specify the name of the VCD file.

EXAMPLE:

initial  $dumpfile ("my_dump_file"); 

$dumpvar //Dump all the variables // Alternately instead of $dumpvar, one could use $dumpvar(1, top) //Dump variables in the top module. $dumpvar(2, top) //Dumps all the variables in module top and 1 level below.

Executing the $dumpvars  task causes the value change dumping to start at the end of the current simulation time unit. To suspend the dump, the $dumpoff  task may be invoked. To resume the dump, the $dumpon  task may be invoked.  

Due to dumping the value changes to a file,there is simulation over head. Not all the time the dumping is required. So controlling mechanism to dump VCD files needs to be implemented.

EXAMPLE: `ifdef DUMP_ON   $dumpon; `endif 

Log File:

Log file keeps track of the operation in text format. Using Display system tasks, proper information can be sent to log files. The display group of system tasks are divided into three categories: the display and write tasks, strobed monitoring tasks, and continuous monitoring tasks.

Page 88: Verification

These are the main system task routines for displaying information. The two sets of tasks are identical except that $display automatically adds a newline character to the end of its output, whereas the $write task does not.

The system task $strobe provides the ability to display simulation data at a selected time. That time is the end of the current simulation time, when all the simulation events that have occurred for that simulation time, just before simulation time is advanced.

$monitor displays when any of the arguments values change.

Message Control System:

Sending message to log file is useful for debugging. But what messages are useful to send and not. Sometimes only few messages are required to send to log file, other times very detailed messages. If the number of messages are more, the simulation time is more. So messaging should be controllable.

EXAMPLE: always@(error) begin `ifdef DEBUG $display(" ERROR : at %d ",$time); `endif end 

With the above approach only one level of controlling is achieved. Messages can be conveyed with wide range of severity levels. Following is the message controlling system I used in my projects. This has 3 levels of controllability and 3 severity levels.

Message Severity Levels:

Following are the 4 severity levels of messaging:

INFO: The messages is used to convey simple information. WARNING: This message conveys that some this is bad but doesn't stop the simulation. ERROR: 

Page 89: Verification

This messages indicate that some error has occurred. Simulation can be terminated. DEBUG: These messages are for debugging purpose. NOTE: %m prints hierarchy path. EXAMPLE:

$display(" INFO : %t : UR MESSAGE GOES HEAR",$time); $display(" WARN : %t : UR MESSAGE GOES HEAR",$time); $display(" EROR : %t : UR MESSAGE GOES HEAR",$time); $display(" DBUG : %t : UR MESSAGE GOES HEAR",$time); 

Message Controlling Levels

By default ,messages INFO, WARN and EROR are logged. When a special switch is used, Debug messages are logged. This example also removes lot of manly coding.

EXAMPLE: `ifndef DEBUG `define SHOW 0 `else  `define SHOW 1 `endif 

`define INFO $write("INFO : %5t :%m:",$time); $display `define WARN $write("WARN : %5t :%m:",$time); $display `define EROR $write("EROR : %5t :%m:",$time); $display `define DBUG if(`SHOW == 1) $write("DBUG : %t :%m:",$time); if(`SHOW == 1) $display 

module msg(); 

initial begin #10; `INFO("UR MESSAGE GOES HEAR"); `WARN("UR MESSAGE GOES HEAR"); `EROR("UR MESSAGE GOES HEAR"); `DBUG("UR MESSAGE GOES HEAR"); end endmodule 

When compilation is done without +define+DEBUG 

Page 90: Verification

RESULTS:

INFO :    10 :msg:UR MESSAGE GOES HEAR WARN :    10 :msg:UR MESSAGE GOES HEAR EROR :    10 :msg:UR MESSAGE GOES HEAR

When compilation is done with +define+DEBUG RESULTS:

INFO :    10 :msg:UR MESSAGE GOES HEAR WARN :    10 :msg:UR MESSAGE GOES HEAR EROR :    10 :msg:UR MESSAGE GOES HEAR DBUG :    10 :msg:UR MESSAGE GOES HEAR

The above results show that DEBUG messages can be disable if not needed.

With the above approach, the controllability is at compilation level. If the controllability is at simulation level, compilation time can be saved. The following message controlling system has controllability at simulation level.

EXAMPLE:

`define INFO $write("INFO : %0t :%m:",$time); $display `define WARN $write("WARN : %0t :%m:",$time); $display `define EROR $write("EROR : %0t :%m:",$time); $display `define DBUG if(top.debug == 1) $write("DBUG : %0t :%m:",$time); if(top.debug == 1) $display 

module top();     reg debug = 0;         initial         if($test$plusargs("DEBUG"))             #0 debug = 1;         initial     begin         #10;         `INFO("UR MESSAGE GOES HEAR"); 

Page 91: Verification

        `WARN("UR MESSAGE GOES HEAR");         `EROR("UR MESSAGE GOES HEAR");         `DBUG("UR MESSAGE GOES HEAR");     end endmodule When simulation is done without +DEBUG RESULTS:

INFO :    10 :top:UR MESSAGE GOES HEAR WARN :    10 :top:UR MESSAGE GOES HEAR EROR :    10 :top:UR MESSAGE GOES HEAR

When simulation is done with +DEBUG RESULTS:

INFO :    10 :top:UR MESSAGE GOES HEAR WARN :    10 :top:UR MESSAGE GOES HEAR EROR :    10 :top:UR MESSAGE GOES HEAR DBUG :    10 :top:UR MESSAGE GOES HEAR

Passing Comments To Waveform Debugger

This is simple trick and very useful. By passing some comments to waveform, debugging becomes easy. Just declare a string and keep updating the comments. There is no slandered way to pass comments to waveform debugger but some tools have their own methods to do this job.

EXAMPLE: module pass_comments();     reg [79 : 0] Comment; // holds 10 characters.         reg [7:0] status;         initial     begin         #10 status = 8'b10101010;         comment = Preambel;         #10 status = 8'b10101011;         comment = Startofpkt;     end endmodule 

Page 92: Verification

The reg " Comment " holds string. This strings can be viewed in waveform debugger.

$Display N $Strobe

According to scheduling semantics of verilog, $display executes before the nonblocking statements update LHS. Therefore if $display contains LHS variable of nonblocking assignment, the results are not proper. The $strobe command shows updated values at the end of the time step after all other commands, including nonblocking assignments, have completed.

EXAMPLE: module disp_stro;     reg a;         initial begin         a = 0;         a <= 1;         $display(" $display a=%b", a);         $strobe (" $strobe a=%b", a);         #1 $display("#1 $display a=%b", a);         #1 $finish;     end endmodule RESULTS:

$display a=0 $strobe a=1 #1 $display a=1

Who Should Do The Rtl Debugging?

One of the important question in debugging is who should do the RTL debugging? Verification engineer or the RTL designer?I personally like to debug the RTL as verification engineer. This is a great opportunity to know RTL methodology. This also improves my understanding ability of RTL. Sometimes test fails because of the Verification environment, before I go and ask RTL designer, I am sure that there is no bug in my environment. By debugging RTL, the bug report is more isolated and designer can fix it sooner. Designer is fast enough to catch cause of the bug, as he knows more about the RTL

Page 93: Verification

then verification engineer. Verification and Designer should sit together and debug the issue, if the bug is in RTL, verification engineer can file the bug, if it is in Testbench, no need to file it.

ABOUT CODE COVERAGE

To check whether the Testbench has satisfactory exercised the design or not? Coverage is used. It will measure the efficiency of your verification implementation. Code coverage answers the questions like  Have all the lines of the DUT has been exercised? Have all the states in the FSM has been entered? Have all the paths within a block have been exercised? Have all the branches in Case have been entered? Have all the conditions in an if statement is simulated?

With the above information, verification engineer can plan for more test cases and excursive uncovered areas to find bugs.  

By default, every tool disables the code coverage. If user enables then only code coverage is done. By enabling the code coverage there is overhead on the simulation and the simulation takes more time. So it is recommended not to enable the code coverage always. Enabling the code coverage during the regression saves user time a lot.

Types Of Coverage

Implementation of Testbench can be separated by following types of coverage hierarchy.

Code Coverage:

It specifies that how much deep level the design is checked. There are sub parts of the code coverage that will be discussed bellow.

Statement Coverage /Line Coverage:

This is the easiest understandable type of coverage. This is required to be 100% for every project.  From N lines of code and according to the applied stimulus how many statements (lines) are covered in the simulation is measured by statement coverage. Lines like module, endmodule, comments, timescale etc are not

Page 94: Verification

covered.  

always @(posedge clk)  begin     if (a > b) statement 1     begin        y = a and b; statement 2        z = a or b; statement 3     end     if (a < b)     begin        y = a xor b;        z = a xnor b;     end     if (a == b)     begin         y = not b;         z = a % b;     end end 

As seen in example those statements only will execute whose condition is satisfied. Statement coverage will only consider those statements.

Block/Segment Coverage:

The nature of the statement and block coverage looks somewhat same. The difference is that block which is covered by begin-end, if-else or always, those group of statements which is called block counted by the block coverage.

Page 95: Verification

Branch / Decision / Conditional Coverage:

Branch coverage will report the true or false of the branch like if-else, case and the ternary operator (? :) statements. In bellow branch of casez, sequences of statements are given. Their execution is depending upon the implementation of stimulus. The default branch in case statement in RTL is not exercised mostly because the Design guidelines insist to mention all the branches of the case statement.

case (state) idle : casez (bus_req) 4'b0000 : next = idle; 4'b1??? : next = grant1; 4'b01?? : next = grant2; 4'b001? : next = grant3; 

Page 96: Verification

4'b0001 : next = grant4; default : next = idle; endcase 

As per the case selectivity list it will check all the statements are reached or not?

Path Coverage:

Due to conditional statements like if-else, case in the design different path is created which diverts the flow of stimulus to the specific path.

Page 97: Verification

Path coverage is considered to be more complete than branch coverage because it can detect the errors related to the sequence of operations. As mentioned in the above figure path will be decided according to the if-else statement According to the applied stimulus the condition which is satisfied only under those expressions will execute, the path will be diverted according to that. Path coverage is possible in always and function blocks only in RTL. Path created by more than one block is not covered.  Analysis of path coverage report is not so easy task.

Expression Coverage:

Page 98: Verification

It is the ratio of no. of cases checked to the total no. of cases present. Suppose one expression having Boolean expression like AND or OR, so entries which is given to that expression to the total possibilities is called expression coverage.

y = (a xor b) + (c xor d); 

In above example it analyzes the right and side of the expression and counts how many times it executed. The expression which involves the Boolean expression for that expression coverage will make its truth table with number of times it executed. If any expression is uncovered then table will come with plane line.

Toggle Coverage:

It makes assures that how many time reg, net and bus toggled? Toggle coverage could be as simple as the ratio of nodes toggled to the total number of nodes.

X or Z --> 1 or H X or Z --> 0 or L 1 or H --> X or Z 0 or L --> X or Z

Above example shows the signal changes from one level to another. Toggle coverage will show which signal did not change the state. Toggle coverage will not consider zero-delay glitches.  All types of transitions mentioned above are not interested. Only 1->0 and 0->1 are much important. This is very useful in gate level simulation.

Variable Coverage:

Page 99: Verification

After the one step of toggle coverage variable coverage comes. Both the coverage looks same but there is a minor different between them is toggle coverage works on gate level but it fail on large quantity. For entity like bus we use variable coverage.

Triggering / Event Coverage:

Events are typically associated with the change of a signal. Event coverage checks the process whenever the individual signal inside the sensitivity list changes.

EXAMPLE: always @(a or b or c) if ((a & b) | c) x = 1'b 1; else x = 1'b 0; 

As per the change in above sensitivity list whether the process is triggered or not.

Parameter Coverage:

It works on the specification which is defined in the design process. If you have implemented 30bit design instead of 32bit, here code coverage check for the functionality while if your design is parameterized then parameter coverage will give error which shows size mismatch.

Functional Coverage:

It works on the functional part of the stimuli's implementation. Functional coverage will check the overall functionality of the implementation. Verilog does not support functional coverage. To do functional coverage, Hardware verification languages like SystemVerilog, Specman E or Vera are needed.

Fsm Coverage :

It is the most complex type of coverage, because it works on the behavior of the design. In this coverage we look for how many times states are visited, transited

Page 100: Verification

and how many sequence are covered. Thats the duty of FSM coverage.  

State Coverage:

It gives the coverage of no. of states visited over the total no. of states. Suppose you have N number of states and state machines transecting is in between only N-2 states then coverage will give alert that some states are uncovered. It is advised that all the states must be covered.

Transition Coverage:

It will count the no. of transition  from one state to another and it will compare it with other total no. of transition. Total no. of transition is nothing but all possible no. of transition which is present in the finite state machine. Possible transition = no. of states * no. of inputs.

Sequence Coverage:

suppose your finite state machine detects the particular sequences. So there is more than 1 possibilities of sequences through which your desired output can be achieved. So here sequence coverage will check which sequence is covered and which is missed? This is a small and corner problem but stimulus should be such a way that all the possibilities must be covered.

Tool Support:

Page 101: Verification

Coverage tool should have following features: Capability to merge reports generated by different test cases. Capability to disable specified block,statement,module,signal. A GUI report for easy analysis. Capability to enable or disable any type of coverage. User options for default branch in case statement coverage.

Limitation Of Code Coverage:

Coverage does not know anything about what design supposed to do. There is no way to find what is missing in the code. It can only tell quality of the implementation. Sometime we get the bug because of the incorrectly written RTL code. If we found that all the lines of the code are used, it doesn't mean that we have tasted all the lines. Sometimes we want the 2nd input of the mux but due to mistake in stimulus if it has taken 1st during that cycle. So whether we got he correct data or not? This cannot tell by coverage. Thats depend on us weather we are feeding correct stimulus or not?

        so remember "VERIFICATION IS NOT COMPLETED EVEN AFTER 100% CODE COVERAGE"  

TESTING STRATIGIES

Function verification approaches can be divided into two categories. Bottom-up and flat approaches.

Bottom-Up

Bottom-up approach can be done at 4 levels. 1)Unit (Module-level) Level 2)Sub-ASIC (Functional Blocks) Level 3)ASIC Level 4)System Level

Unit Level

In unit level verification, a module is verified in its own test environment to prove that the logic, control, and data paths are functionally correct. The goal of module level verification is to ensure that the component/unit  being tested conforms to its

Page 102: Verification

specifications and is ready to be integrated with other subcomponents of the product. In unit level verification good coverage percentage is expected.

Sub-Asic Level

In sub-asic level ,the goal is to ensure that the interfaces among the units  are correct & the units work together to execute the functionality correctly. Sometimes this level can be skipped.

Asic Level

Asic level verification is the process of verifying the ASIC to see that it meets its specified requirements. ASIC level verification must concentrate on ensuring the use and interaction of ASIC rather than on checking the details of its implementations .  

System Level

Flat

In this ,verification approaches by combining interface models and transaction streams to test the Complete ASIC.

FILE HANDLING

The system tasks and functions for file-based operations are divided into three categories:

Functions and tasks that open and close filesTasks that output values into filesTasks that output values into variablesTasks and functions that read values from files and load into variables or

memories

Fopen And Fclose

$fopen and $fclose

Page 103: Verification

The function $fopen opens the file specified as the filename argument and returns either a 32 bit multi channel descriptor, or a 32 bit file descriptor, determined by the absence or presence of the type argument. Filename is a character string, or a reg containing a character string that names the file to be opened. The multi channel descriptor mcd is a 32 bit reg in which a single bit is set indicating which file is opened. The least significant bit (bit 0) of a mcd always refers to the standard output. Output is directed to two or more files opened with multi channel descriptors by bitwise oring together their mcds and writing to the resultant value. The most significant bit (bit 32) of a multi channel descriptor is reserved, and shall always be cleared, limiting an implementation to at most 31 files opened for output via multi channel descriptors. The file descriptor fd is a 32 bit value. The most significant bit (bit 32) of a fd is reserved, and shall always be set; this allows implementations of the file input and output functions to determine how the file was opened. The remaining bits hold a small number indicating what file is opened.

EXAMPLE// file open close examplemodule fopenclose();   integer mcd,number;   initial   begin       mcd = $fopen("xyz.txt"); // opening the file       repeat(7)       begin           number = $random ;           $fdisplay(mcd, " Number is ", number);       end       $fclose(mcd); // closing the file   endendmodule

After simulating the above code, file name called "xyz.txt" will be opened in the same directory. In above example you show that file is getting open and closing, so according to that there will be change in value of mcd.

EXAMPLE// Display mcd value before and after the opening the file.module fopenclose();    integer mcd,number;    initial    begin        $display("value of mcd before opening the file %b " , mcd);        mcd = $fopen("xyz.txt"); // opening the file

Page 104: Verification

        $display("value of mcd after opening the file %b " , mcd);        repeat(7)        begin            number = $random ;            $fdisplay(mcd, " Number is ", number);        end        $fclose(mcd); // closing the file    endendmoduleRESULT

value of mcd before opening the file xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxvalue of mcd after opening the file 00000000000000000000000000000010

Then how can we check that file is closed or not??In above example its clear that mcd value is changed as file is opened, if we dont close the file than it will be remain in stack. But how will we come toknow that file is closed?? That will come after following examples.

Fdisplay

$fdisplay, $fdisplayb, $fdisplayo, $fdisplayh

$display has its own counterparts. Those are $fdisplay, $fdisplayb, $fdisplayo, $fdisplayh. Instead of writing on screen they are writing on the specific file with is pointed by the mcd. $fdisplay write in decimal format, $fdisplay in binary, $fdisplay in octal and $fdisplayh in hex format. so no need to put %d-b-o-h.

EXAMPLE// file open close example with all $fdisplay

module fopenclose();    integer mcd,number;    initial    begin        mcd = $fopen("temp.txt"); // mcd = multi_channel_descriptor        repeat(7)        begin            number = $random;            $fdisplay(mcd, "Number is ", number);        end        $fclose(mcd);

Page 105: Verification

    endendmoduleRESULT

Number is 303379748Number is -1064739199Number is -2071669239Number is -1309649309Number is 112818957Number is 1189058957Number is -1295874971

EXAMPLE $displaybmodule fopenclose();    integer mcd,number;    initial    begin        mcd = $fopen("temp.txt"); // mcd = multi_channel_descriptor        repeat(7)        begin            number = $random;            $fdisplayb(mcd, "Number is ", number);        end        $fclose(mcd);    endendmoduleRESULT

Number is 00010010000101010011010100100100Number is 11000000100010010101111010000001Number is 10000100100001001101011000001001Number is 10110001111100000101011001100011Number is 00000110101110010111101100001101Number is 01000110110111111001100110001101Number is 10110010110000101000010001100101

EXAMPLE c. $displayomodule fopenclose();    integer mcd,number;    initial    begin        mcd = $fopen("temp.txt"); // mcd = multi_channel_descriptor

Page 106: Verification

        repeat(7)        begin            number = $random;            $fdisplayo(mcd, "Number is ", number);        end        $fclose(mcd);    endendmoduleRESULT

Number is 02205232444Number is 30042257201Number is 20441153011Number is 26174053143Number is 00656275415Number is 10667714615Number is 26260502145

EXAMPLE. $displayhmodule fopenclose();     integer mcd,number;     initial     begin         mcd = $fopen("temp.txt"); // mcd = multi_channel_descriptor         repeat(7)         begin             number = $random;             $fdisplayh(mcd, "Number is ", number);         end         $fclose(mcd);     endendmoduleRESULT

Number is 12153524Number is c0895e81Number is 8484d609Number is b1f05663Number is 06b97b0dNumber is 46df998dNumber is b2c28465

Page 107: Verification

In below example we will see that how we will come to know that file is closed or not?? so even after closing the file I will try to write in that file, for that it should give error.

EXAMPLEmodule fopenclose();     integer mcd,number;     initial     begin         $display("value of mcd before opening the file %b " , mcd);         mcd = $fopen("xyz.txt");         $display("value of mcd after opening the file %b " , mcd);         repeat(7)         begin             number = $random ;             $fdisplay(mcd, " Number is ", number);         end         $fclose(mcd);         $fdisplay("value of mcd after closing the file %b ",         mcd);     endendmoduleRESULT

Error during elaboration.

Fmonitor

$fmonitor, $fmonitorb, $fmonitoro, $fmonitorh, $fstrobe, $fstrobeb,$fstrobeo, $fstrobeh

Like $display; $monitor and $strobe also have counterparts. They also write in decimal, binary, octal and hexadecimal.

EXAMPLE// file open close example with $fmonitormodule monitortask();     integer mcd,number;     initial     begin

Page 108: Verification

        #0;        mcd = $fopen("abc.txt");        $monitoron;        repeat(7)        begin           #1 number = $random ;        end        $monitoroff;        $fclose(mcd);     end

     initial        $fmonitorh(mcd, " Number is ", number);endmoduleRESULT

Number is 12153524Number is c0895e81Number is 8484d609Number is b1f05663Number is 06b97b0dNumber is 46df998d

Due to initial-initial race condition we have to put the #0 delay in first initial block and $monitoron-$monitoroff system task, otherwise it is not able tocache the updated value of integer "number" because "number" is updated in active(1st) event while monitor in system task(3rd) event in the event queue.

Fwrite

$fwrite, $fwriteb, $fwriteo, $fwriteh

Like $display; $write also have counterparts. They also write in decimal,binary, octal and hexadecimal.

EXAMPLE// file open close example with $fwritemodule writetask();    integer mcd1,mcd2,number,pointer;    initial    begin        $display("value of mcd1 before opening the file %b " , mcd1);        $display("value of mcd2 before opening the file %b " , mcd2);

Page 109: Verification

        mcd1 = $fopen("xyz.txt");        mcd2 = $fopen("pqr.txt");        $display("value of mcd1 after opening the file %b " , mcd1);        $display("value of mcd2 after opening the file %b " , mcd2);        repeat(7)        begin             pointer = $random;             number = $random % 10;             $fwriteo(mcd1, " Number is ", number);             $fwriteh(mcd2, " Pointer is ", pointer);        end        $fclose(mcd1);        $fclose(mcd2);    endendmodule

One of the reasons behind writing this example is to show how the integers are getting different value as per the number of files are opened.

RESULT

value of mcd1 before opening the file xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxvalue of mcd2 before opening the file xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxvalue of mcd1 after opening the file 00000000000000000000000000000010value of mcd2 after opening the file 00000000000000000000000000000100in file pqr.txtPointer is 12153524 Pointer is 8484d609 Pointer is 06b97b0dPointer is b2c28465 Pointer is 00f3e301 Pointer is 3b23f176Pointer is 76d457ed

In file xyz.txt

Number is 37777777767 Number is 37777777767 Number is00000000007 Number is 37777777774 Number is 00000000011 Numberis 00000000007 Number is 00000000002

Mcd

Simultaneously writing same data to two different file. This example shows how to set up multi channel descriptors. In this example, two different channels are opened using the $fopen function. The two multi channel descriptors that are returned by the function are then combined in a bit-wise or operation and assigned to the

Page 110: Verification

integer variable "broadcast". The "broadcast" variable can then be used as the first parameter in a file output task to direct output to all two channels at once.

EXAMPLEmodule writetask();     integer mcd1,mcd2,broadcast,number;     initial     begin         mcd1 = $fopen("lsbbit1.txt");         mcd2 = $fopen("lsbbit2.txt");         broadcast = mcd1 |mcd2 ;         repeat(7)         begin             number = $random;             $fdisplayh(broadcast," Number is ", number);         end         $fclose(mcd1);         $fclose(mcd2);     endendmoduleRESULTIn lsbbit1.txt

Number is 12153524Number is c0895e81Number is 8484d609Number is b1f05663Number is 06b97b0dNumber is 46df998dNumber is b2c28465

In lsbbit2.txt

Number is 12153524Number is c0895e81Number is 8484d609Number is b1f05663Number is 06b97b0dNumber is 46df998dNumber is b2c28465

To create a descriptor that directs output to the standard output that is monitor screen as well as both the files, the "broadcast" variable is a bit-wise

Page 111: Verification

logical or with the constant 1, which effectively writes to both files as well as monitor screen.

EXAMPLEmodule writetask();     integer mcd1,mcd2,broadcast,number;     initial     begin         mcd1 = $fopen("lsbbit1.txt");         mcd2 = $fopen("lsbbit2.txt");         broadcast = 1 | mcd1 | mcd2 ;         repeat(7)         begin             number = $random;             $fdisplayh(broadcast," Number is ", number);         end         $fclose(mcd1);         $fclose(mcd2);     end     endmoduleendmoduleRESULT

Number is 12153524Number is c0895e81Number is 8484d609Number is b1f05663Number is 06b97b0dNumber is 46df998dNumber is b2c28465

Formating Data To String

The $swrite family of tasks are based on the $fwrite family of tasks, and accept the same type of arguments as the tasks upon which they are based, with one exception: The first parameter to $swrite shall be a reg variable to which the resulting string shall be written, instead of a variable specifying the file to which to write the resulting string.

The system task $sformat is similar to the system task $swrite, with a one major difference. Unlike the display and write family of output system tasks, $sformat always interprets its second argument, and only its second argument as a format string. This format argument can be a static string, such as "data is %d" , or can be

Page 112: Verification

a reg variable whose content is interpreted as the format string. No other arguments are interpreted as format strings. $sformat supports all the format specifies supported by $display.

EXAMPLE: $sformat(string, "Formatted %d %x", a, b); 

VERILOG SEMAPHORE

Semaphore In Verilog

A semaphore is a type of Interposes communication resource used for synchronization and mutual exclusion between any two asynchronous processes. A semaphore object is a synchronization object that maintains a count between zero and a specified maximum value. The count is decremented each time a thread completes a wait for the semaphore object and incremented each time a thread releases the semaphore. When the count reaches zero, no more threads can successfully wait for the semaphore object state to become signaled. The state of a semaphore is set to signaled when its count is greater than zero, and non-signaled when its count is zero. The semaphore object is useful in controlling a shared resource that can support a limited number of users. It acts as a gate that limits the number of threads sharing the resource to a specified maximum number.

Take an example, Many components in the testbench wants to access the dut memory. But memory has only one interface. So only one can do write and read operation at a time. Using semaphore, we can make sure that only one operation is done at a time.

Imagine a home with six persons living. They have only one car. Everyone wants to drive the car. But others plan to make a trip, when some other has gone out with car. The eldest person in home made a rule. Key will be with  him. Whoever wants, come to me and get the key. After finishing the job, return the key to him. This way, only one can plan for the trip.

EXAMPLE: module sema(); 

    integer keys;         initial        keys = 1;     

Page 113: Verification

    task get_key();     input integer i;     begin         if ( keys == 0)         begin         $display(" KEY IS NOT AVAILABLE : WAITING FOR KEYS : process %d",i);         wait(keys == 1);         end         $display(" GOT THE KEY : GET SET GO :process %d",i);         keys = 0;     end      endtask         task put_keys();     input integer i;     begin         keys = 1 ;         $display(" PROCESS %d gave the key back ",i);     end     endtask         initial     begin        # 10 ;        get_key(1);        repeat(4)        # 10 $display(" PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG ");        put_keys(1);     end         initial     begin         # 10 ;         get_key(2);         repeat(4)         # 10 $display(" PROCESS 2 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG ");         put_keys(2);     end     endmodule 

RESULTS:

Page 114: Verification

GOT THE KEY : GET SET GO :process 1 KEY IS NOT AVAILABLE : WAITING FOR KEYS : process 2 PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 1 gave the key back  GOT THE KEY : GET SET GO :process 2 PROCESS 2 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 2 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 2 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 2 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 2 gave the key back  

In this home, some of them are not interested to wait until they got the key. So they want tp progress to other works without waiting for keys. The following example shows, if keys are not available, the process don't wait.

EXAMPLE: module sema(); 

integer keys; 

initial keys = 1; 

task get_key(); input integer i; begin if ( keys == 0) begin $display(" KEY IS NOT AVAILABLE : WAITING FOR KEYS : process %d",i); wait(keys == 1); end $display(" GOT THE KEY : GET SET GO :process %d",i); keys = 0; end  endtask 

function get_key_dont_wait(); 

Page 115: Verification

input integer i; reg got; begin got =0; if ( keys == 0) $display(" KEY IS NOT AVAILABLE : LEAVING WITHOUT WAITING FOR KEYS : process %d",i); else begin $display(" GOT THE KEY : GET SET GO :process %d",i); keys = 0; got = 1; end get_key_dont_wait = got; end  endfunction 

task put_keys(); input integer i; begin keys = 1 ; $display(" PROCESS %d gave the key back ",i); end endtask 

initial begin # 10 ; get_key(1); repeat(4) # 10 $display(" PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG "); put_keys(1); end 

initial begin # 10 ; if(get_key_dont_wait(2)) begin repeat(4) # 10 $display(" PROCESS 2 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG "); put_keys(2); end else 

Page 116: Verification

$display(" IM not interested to wait "); end 

endmodule 

RESULTS:

GOT THE KEY : GET SET GO :process 1 KEY IS NOT AVAILABLE : LEAVING WITHOUT WAITING FOR KEYS : process 2 IM not interested to wait  PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG  PROCESS 1 gave the key back

After some days, they got new car to home. Now they have two cars, at once 2 members can go on drive. Looking at the following code. The keys are initialized to 2. Two processes are running at once.

EXAMPLE: module sema(); 

integer keys; 

initial keys = 2; 

task get_key(); input integer i; begin if ( keys == 0) begin $display(" KEY IS NOT AVAILABLE : WAITING FOR KEYS : process %d",i); wait(keys > 0); end $display(" GOT THE KEY : GET SET GO :process %d",i); keys = keys - 1; end  endtask 

Page 117: Verification

function get_key_dont_wait(); input integer i; reg got; begin got =0; if ( keys == 0) $display(" KEY IS NOT AVAILABLE : LEAVING WITHOUT WAITING FOR KEYS : process %d",i); else begin $display(" GOT THE KEY : GET SET GO :process %d",i); keys = keys - 1; got = 1; end get_key_dont_wait = got; end  endfunction 

task put_keys(); input integer i; begin keys = keys + 1 ; $display(" PROCESS %d gave the key back ",i); end endtask 

initial begin # 10 ; get_key(1); repeat(4) # 10 $display(" PROCESS 1 GOT KEYS : IM ALOS RUNNING "); put_keys(1); end 

initial begin # 10 ; if(get_key_dont_wait(2)) begin repeat(4) # 10 $display(" PROCESS 2 GOT KEYS : IM ALSO RUNNING "); put_keys(2); end 

Page 118: Verification

else $display(" IM not interested to wait "); end 

endmodule 

RESULTS:

GOT THE KEY : GET SET GO :process 1 GOT THE KEY : GET SET GO :process 2 PROCESS 1 GOT KEYS : IM ALOS RUNNING  PROCESS 2 GOT KEYS : IM ALSO RUNNING  PROCESS 1 GOT KEYS : IM ALOS RUNNING  PROCESS 2 GOT KEYS : IM ALSO RUNNING  PROCESS 1 GOT KEYS : IM ALOS RUNNING  PROCESS 2 GOT KEYS : IM ALSO RUNNING  PROCESS 1 GOT KEYS : IM ALOS RUNNING  PROCESS 1 gave the key back  PROCESS 2 GOT KEYS : IM ALSO RUNNING  PROCESS 2 gave the key back

FINDING TESTSENARIOUS

Test scenarios can be divided in to following category while implementing @bull test plan. @bull Register Tests @bull Interrupt Tests  @bull Interface Tests  @bull Protocol Tests  @bull Functional Tests  @bull Error Tests @bull Golden Tests @bull Performance Tests

Register Tests

This is complex to build efficiently. These tests requires more advanced planning and architecting . A poorly planned infrastructure is buggy, insufficient, and hard to use.

System Tests

These tests Verify whether ASIC interacts correctly with other ASICs / ICs correctly

Page 119: Verification

in the system.

Interrupt Tests

These tests Verify how the interrupt logic is working.

Interface Tests

These tests verify the Interface functionality.

Functional Tests

Contains scenarios related to specific features & combinations of these features.  

Error Tests

Error-oriented testing develops test data by focusing on the presence or absence of errors in DUT.

Golden Tests

Set of well defined test cases executed on a modified code to ensure that changes made to the code haven't adversely affected previously existing functions. These includes register tests, interrupt tests, interface tests, protocol tests, functional tests & error tests.

Performance Tests

These tests measures how well the product meets its specified performance objectives. Example: bandwidth monitoring.

HANDLING TESTCASE FILES

A test case is a file that describes an input, action, or event and an expected response, to determine if a feature of an application is working correctly. A test case should contain particulars such as test case identifier, test case name, objective, test conditions/setup, input data requirements, steps, and expected results.

Note that the process of developing test cases can help find problems in the requirements or design of an application, since it requires completely thinking through the operation of the application. For this reason, it's useful to prepare test cases early in the development cycle if possible.  

Page 120: Verification

The following example contains testbench environment and has 2 test cases.

EXAMPLE:  top.v

module top(); // DUT instance, clock generator and TB components

// some tasks

task write() begin // some logic end endtask 

task read() begin // some logic end endtask 

end 

EXAMPLE: testcase_1.v

// Do 10 write operations

EXAMPLE: testcase_2.v

// Do 10 read operations

To test first test cases, We have to simulate the contents of top.v file and testcase_1.v file.

1) Take an instance of module TEST in top.v file. Define the module definition in test cases.

During compilation just use the following commands

Page 121: Verification

for testcase_1.v file comile_command top.v testcase_1.v

for testcase_2.v file comile_command top.v testcase_2.v

EXAMPLE:  top.v

module top(); // DUT instance, clock generator and TB components

// some tasks

task write() begin // some logic end endtask 

task read() begin // some logic end endtask 

//  TEST case instance

TEST tst(); 

end 

EXAMPLE: testcase_1.v

// Do 10 write operations

module TEST(); 

initial repeat(10) top.write(); 

Page 122: Verification

endmodule EXAMPLE: testcase_2.v

// Do 10 read operations

module TEST(); 

initial repeat(10) top.read(); 

endmodule 

2) use `include test.v file. This needs a small script to copy the testcase file to test file. The compilation command is same. But copy command which copies the testcase to test.v file is different.

During compilation just use the following commands

for testcase_1.v file cp testcase_1 test.v comile_command top.v test.v

for testcase_2.v file cp testcase_2 test.v comile_command top.v test.v

EXAMPLE:  top.v

module top(); // DUT instance, clock generator and TB components

// some tasks

task write() begin // some logic end endtask 

task read() begin 

Page 123: Verification

// some logic end endtask 

//  incule test.v file

`include test.v

end 

EXAMPLE: testcase_1.v

// Do 10 write operations

initial repeat(10) top.write(); 

EXAMPLE: testcase_2.v

// Do 10 read operations

initial repeat(10) top.read(); 

2) With the above two approaches, for each test case, we have to do individual compilation. In  this method, compile once and use simulation command to test with individual test case.

This needs a small script to convert all the test cases to single intermediate file. compilation command is same. During simulation by giving the test case file name, we can include particular testcase.

During compilation just give following command

cat testcase_1.v > test.v cat testcase_2.v > test.v compile_command top.v test.v

During simulation , for each test case, use

Page 124: Verification

run_command +testcase_1 run_coomand +testcase_2

EXAMPLE:  top.v

module top(); // DUT instance, clock generator and TB components

// some tasks

task write() begin // some logic end endtask 

task read() begin // some logic end endtask 

//  incule test.v file

`include test.v

end 

EXAMPLE: testcase_1.v

// Do 10 write operations

repeat(10) top.write(); 

EXAMPLE: testcase_2.v

// Do 10 read operations

repeat(10) top.read(); 

Page 125: Verification

Intermediate file generated contains all the testcase contents with some extra logic as shown.

EXAMPLE: INTERMEDIATE FILE   test.v

initial begin if($test$plusargs("testcase_1")     begin             // testcase_1 contents // Do 10 write operations repeat(10) top.write(); end if($test$plusargs("testcase_2") begin            // testcase_2 contents // Do 10 read operations repeat(10) top.read(); end end 

TERIMINATION

Simulation should terminate after all the required operations are done. Recommended way to exit simulation is using a task. This termination task contains some messages about the activities done and $finish. This task should be called after collecting all the responses from DUT, then analyzing them only. If the simulation time is long and if there is bug in DUT, you can stop simulation at that time itself. This saves lot of time. Otherwise, even after the testbench found error, it will simulate till the end of the process or it may get hanged and waste your time and costly licenses.

 Sometimes, you are not just interested to terminate the simulation for known unfixed bugs. Then there should be a controllable way not to stop the simulation even after the error was found.

EXAMPLE:

task teriminate(); begin 

Page 126: Verification

if(no_of_errors == 0) $display(" *********TEST PASSED ***********"); else $display(" *********TEST FAILED ***********"); 

#10 $display(" SIMULATION TERMINATION at %d",$time); $finish; end endtask 

always@(error) begin no_of_errors = num_of_errors +1 ; 

`ifndef CONTINUE_ON_ERROR terminate(); `endif end 

If you know already a well known bug is there and it is giving 2 error counts. Its better to stop the simulation after 2 errors. From command line just give +define+NO_FO_ERR=2, simulation terminates after 3 errors.

EXAMPLE: always@(error) begin no_of_errors = num_of_errors +1 ; 

`ifndef CONTINUE_ON_ERROR `ifndef NO_OF_ERR `define NO_OF_ERR 0 `endif if(`NO_OF_ERR < no_of_erros) terminate(); `endif end 

ERROR INJUCTION

To verify error detection, reporting, and recovery features of the DUT, an error

Page 127: Verification

injection mechanism must be in place in testbench to generate error scenarios. The objective is to ensure that the errors are handled correctly. This is accomplished by introducing internal monitoring mechanisms. The simulation environment integrates a structure to randomly set the errors and verify that each error condition is handled properly.

Errors can be classified in to following categories:

Value Errors

The specification says that packet length should be greater than 64 and less than 1518. Testbench should be able to generate packets of length less than 64 and greater than 1518 and verify how the DUT is handling these. Testbench should also monitor that DUT is not generating any packets violating this rule.

Temporal Errors

Errors like acknowledgement should come after 4 cycles of request.  

Interface Error

Sometimes interfaces have invalid pins or error pins to inform to DUT that the some malfunction happened. Generate scenarios to test whether the DUT is properly responding to these signals.

Sequence Errors

To test protocols which define sequence of operations, generate sequence which violates the rule and check the DUT.

REGISTER VERIFICATION

Register Verification

Todays complex chips has thousands of register to configure the chip and to communicate the status to software. These registers  play a complex role in the chip operation So a test bench should verify these registers properly. Verification of these registers is tedious. As there are thousands of registers in a chip, the testbench should have a handy hooks to access these registers. Implementing testbench components for these registers is not one time job. Most designs change their register specification during the design development. So a very flexible testbench component should be available to satisfy these needs. When I was

Page 128: Verification

working for Ample, we had a script which generates testbench component for these registers. Register specification is input to these script. So when ever register file is changed, Just run the script, we don't need to change verilog module for these changes. These scripts can be used across modules, across projects and across companies also. There are some EDA tools just to do this job. I believe that a proper homemade script has better control then getting it from some EDA guy and you know home made scripts are life time free.

Register Classification:

Registers can be mainly classified in to these categories1) Configuration Registers. 2) Status Registers. 3) Mask Registers. 4) Interrupt Registers(makeable and nonmaskable).

Features:

What are the features that this testbench component should support?

1) It should have a data structure to store the values of config register .Testbench will write in to these register while it is writing to dut registers. These are called shadow registers. Shadow registers should have the same address and register name as DUT so it is easy to debug.

2) Back door access: There are two type of access to register in DUT. Front door and back door. Front door access uses physical bus . To write a value in to DUT registers, it takes some clock cycles in front door access. And writing for thousands of registers is resource consuming. Remember, only one register can be assigned at a time. One cannot make sure that only one method is called at one time. To make sure that only one method is assessing the bus, semaphore is used. In back door access, registers are access directly. In zero time. Accessing to these locations using back door will save simulation time. There should be a switch to control these feature. So after verifying the actual access path of these registers, we can start using back door access. In verilog, using Hierarchy reference to DUT register, we can by pass this path.

3) The Shadow registers by default should contain default values from register specification. A task should be provided to compare each register in shadow registers and DUT. After reset, just call this task before doing any changes to DUT registers. This will check the default values of the registers.

Page 129: Verification

4) Methods should be provided for read or write operation to dut registers using name and also address. Named methods are handy and readable mainly from test cases. While address based methods are good while writing to bulk locations( using for loop etc...).  5) Every register in Testbench may have these information.// Comments which describes the register information.Address of register.Offset of register.Width of register.Reset value. Access permissions.Register value.Register name as string.( Name should be self descriptive and same as in DUT. This string is used in printing while debugging.)

Some are methods which are used for register in functional verification .Read function.Write task.Update task.Print task.Check function.write_random task.

All the above methods should be accessible by name and also by address.

Write random task:Some registers values can be any constrained random value. Lets take an ether net. The unicast destination address can be any value which is not broadcast or multi cast. So the random values should be constraint. Some times, these random values depend on some other registers also. If this task is not provided,  while writing the test cases, one may forget the limitation of the register value and the DUT misbehaves and may spend hours in debugging. The best way to use random values is using this task.

Update task:Interrupt and status registers in Testbench should be updated by update task. These registers should contain the expected values. When check function is called, the check reads the register values in DUT and compares with the expected value in shadow registers.

Check task:Check task compares the DUT and shadow registers. Care should be taken while

Page 130: Verification

using back door access, as they are not cycle accurate. Config registers are compared for what is configured and what is in the register. Interrupt and status registers are compared with what is in DUT with the expected values.

Access permission:Each register in test bench should maintain the permissions. This permissions are used in write, read, check methods.

Fallowing are possible types of permissions:read/writeread onlywrite onlyread only,  write can be by the designclear on readautomatically set to 1 by design.Automatically set to 0 by design.Readable and settable by writing 1Readable and clearable by writing 1

By default the type of permission is read/write. If you are using any scripts, if you don't mention any permission, then it should be considered as read/write.

PARAMETERISED MACROS

How do we get rid of the typing repeating functionality which should be present at compilation level ?

You can Use generate block. But Generate will not help always.

Look at the example.There are four modules and_gate,or_gate,xor_gate,nand_gate. They are instantiated in top modules.

Each module has 2 inputs a,b and one output c. Instance port are connected wires which are prefixed with getname to signal name. Example   and_gate  a_g (.a(and_a), .b(and_b), .c(and_c)  );

and is prefexed to signal "a","b" and "c".

The following is what a novice engineer will do.

Page 131: Verification

CODE:

module top();wire and_a, or_a, xor_a, nand_a ;wire and_b, or_b, xor_b, nand_b ;wire and_c, or_c, xor_c, nand_c ;

and_gate  a_g (.a(and_a), .b(and_b), .c(and_c)  );or_gate   o_g (.a(or_a),  .b(or_b),  .c(or_c)   );xor_gate  x_g (.a(xor_a), .b(xor_b), .c(xor_c)  );nand_gate n_g (.a(nand_a),.b(nand_b),.c(nand_c) );

endmodule

module and_gate(a,b,c);input a,b;output c;endmodule

module or_gate(a,b,c);input a,b;output c;endmodule

module xor_gate(a,b,c);input a,b;output c;endmodule

module nand_gate(a,b,c);input a,b;output c;endmodule

This looks easy to do, as there are 3 inputs only. Real time projects doesnt have this much less. One may probable spend half day to connect all the ports. Sometime later if there is change in any of the ports, then all the instances needs to be changed.

Using parameterized macros, this job can be done easily. The directive <91>define

Page 132: Verification

creates a macro for text substitution. This directive can be used both inside and outside module definitions. After a text macro is defined, it can be used in the source description by using the (<91>) character, followed by the macro name. The compiler shall substitute the text of the macro for the string `macro_name. All compiler directives shall be considered predefined macro names; it shall be illegal to redefine a compiler directive as a macro name.

A text macro can be defined with arguments. This allows the macro to be customized for each use individually. If a one-line comment (that is, a comment specified with the characters //) is included in the text, then the comment shall not become part of the substituted text.

EXAMPLE:<91>define max(a,b)((a) > (b) ? (a) : (b))n = <91>max(p+q, r+s) ;

To use this for the above discussed example,

First step is declare a parameterized macro.Second step is just use macro instance.

CODE:`define GATE(M) M\_gate M\_g (.a(M\_a), .b(M\_b), .c(M\_c));

`define SIG and_\S, or_\S,xor_\S,nand_\S; \

module top();wire `SIG(a)wire `SIG(b)wire `SIG(c)

Page 133: Verification

`GATE(and)`GATE(or)`GATE(xor)`GATE(nand)

endmodule

WHITE GRAY BLACK BOX

Black Box Verification

Black Box verification refers to the technique of verification if  system with no knowledge of the internals of the DUT. Black Box testbench do not have access to the source code of DUT, and are oblivious of the DUT architecture. A Black Box testbench, typically, interacts with a system through a user interface by providing inputs and examining outputs, without knowing where and how the inputs were operated upon. In Black Box verification, the target DUT is exercised over a range of inputs, and the outputs are observed for correctness. How those outputs are generated or what is inside the box doesn't matter.

White Box Verification

In White box verification, testbench has access to internal structure of DUT. This makes the testbench environment reuse less. This is not much preferred in the industry.

Gray Box Verification

Gray box verification, the name itself Conway that testbench has access to some part of the DUT.

REGRESSION

Regression is re-running previously run tests and checking whether previously fixed faults have re-emerged. New bugs may come out due to new changes in RTL or DUT to unmasking of previously hidden bugs due to new changes. Each time time,when design is changed, regression is done. One more important aspect of regression is

Page 134: Verification

testing by generation new vectors. Usually the seed to generate stimulus is the system time. Whenever a regression is done, it will take the current system time and generate new vectors than earlier tested. This way testbench can reach corners of DUT.

TIPS

How To Avoid &Quot;Module Xxx Already Defined&Quot; Error

Sometimes compilation error "module xxx already defined" is tough to avoid when hundreds of files are there. Its hard to find where `include is including xxx file and how many times the file is given in compilation command.  

EXAMPLE: xxx.v file

module xxx(); 

initial $display(" MODULE "); 

endmodule  

EXAMPLE: yyy.v file `include "xxx.v" module yyy() 

endmodule 

Now compile with any of the comand.

compile_ur_command xxx.v yyy.v compile_ur_command xxx.v yyy.v yyy.v

To avoid this problem, Just use compilation switches. In the following example initial macros XXX and YYY are not defined. When the compiler comes the xxx.v file first times, macro XXX is defined. Nedtime when the comes across xxx.v, as already the macro XXX is defined, it will neglect the module definition.

EXAMPLE: xxx.v file

Page 135: Verification

`ifndef XXX `define XXX module xxx(); 

initial $display(" MODULE "); 

endmodule  `endif 

EXAMPLE: yyy.v file `include "xxx.v" `ifndef YYY `define YYY module yyy() 

endmodule `endif 

Now compile with any of the command.

compile_ur_command xxx.v yyy.v compile_ur_command xxx.v yyy.v yyy.v

You will not see any compilation error.

Colourful Messages:

Page 136: Verification

Look at the picture. Do you want to make your Linux terminal colorful like this, while you run your  verilog code?

Copy the following code and simulate in batch mode in Linux. What you can see is colorful messages from verilog.

CODE:

module colour();

initialbegin$write("%c[1;34m",27);$display("*********** This is in blue ***********");$write("%c[0m",27);

$display("%c[1;31m",27); $display("*********** This is in red   ***********");$display("%c[0m",27);

$display("%c[4;33m",27);$display("*********** This is in brown ***********"); $display("%c[0m",27);

$display("%c[5;34m",27);

Page 137: Verification

$display("*********** This is in green ***********");$display("%c[0m",27); 

$display("%c[7;34m",27); $display("*********** This is in Back ground color ***********");$display("%c[0m",27);

endendmodule

This works only in Linux or Unix terminals. To get required colors, ("%c[1;34m",27); should be used to print once. Ordinary messages following this messages continue to be the color specified.

Lets see how to get different colors and font format.The message to be printed is  ("%c[TYPE;COLOURm",27);.

TYPE specifies how the message should be?

1 set bold2 set half-bright (simulated with color on a color display)4 set underscore (simulated with color on a color display)5 set blink7 set reverse video

COLOR specifies the message color.

30 set black foreground31 set red foreground32 set green foreground33 set brown foreground34 set blue foreground35 set magenta foreground36 set cyan foreground37 set white foreground

If you really want to use in your environment, use macros.

`define display_blue  $write("%c[0m",27); $write("%c[1;34m",27); $display`define display_red   $write("%c[0m",27); $write("%c[1;31m",27); $display

Page 138: Verification

`define display_green $write("%c[0m",27); $write("%c[1;32m",27); $display

Use the macros instead of $display().

EXAMPLE:module color();

initialbegin`display_blue(" ******** this is blue ********** ");`display_red(" ******** this is red ********** ");`display_green(" ******** this is green ********** ");

end

endmodule

Debugging Macros

Most tools don't support Debugging Macros. The compilation error information is not enough to find the exactly line where the bug is. In simulation/Compilation steps ,   the first step is Macro preprocessing. The macro preprocessing step performs textual substitutions of macros defined with `define statements, textual inclusion with `include statements, and conditional compilation by `ifdef and `ifndef statements.

EXAMPLE:

`define SUM(A,B) A + B ;

module example();

integer a,b,c;

initial a = SUM(b,c);

endmodule

Run the above example and check where the error is.

Page 139: Verification

The find the exact cause of error, simply use the C pre-processor.

Just use command

cpp file_name.v

NOTE: cpp cannot understand `define. Before using cpp, covert all `define to #define.

Output of the above code using cpp preprocessor is

RESULTS

# 1 "fine_name.v"# 1 "<built-in>"# 1 "<command line>"# 1 "fine_name.v"

module example();

integer a,b,c;

initiala = b + c ;;

endmodule

Page 140: Verification

module bus_wr_rd_task();

reg clk,rd,wr,ce;reg [7:0] addr,data_wr,data_rd;reg [7:0] read_data;

initial begin clk = 0; read_data = 0; rd = 0; wr = 0; ce = 0; addr = 0; data_wr = 0; data_rd = 0; // Call the write and read tasks here #1 cpu_write(8'h11,8'hAA); #1 cpu_read(8'h11,read_data); #1 cpu_write(8'h12,8'hAB); #1 cpu_read(8'h12,read_data); #1 cpu_write(8'h13,8'h0A); #1 cpu_read(8'h13,read_data); #100 $finish;end// Clock Generatoralways #1 clk = ~clk;// CPU Read Tasktask cpu_read; input [7:0] address; output [7:0] data; begin $display ("%g CPU Read task with address : %h", $time, address); $display ("%g -> Driving CE, RD and ADDRESS on to bus", $time); @ (posedge clk); addr = address; ce = 1; rd = 1; @ (negedge clk); data = data_rd; @ (posedge clk); addr = 0; ce = 0;

Page 141: Verification

rd = 0; $display ("%g CPU Read data : %h", $time, data); $display ("======================"); endendtask// CU Write Tasktask cpu_write; input [7:0] address; input [7:0] data; begin $display ("%g CPU Write task with address : %h Data : %h", $time, address,data); $display ("%g -> Driving CE, WR, WR data and ADDRESS on to bus", $time); @ (posedge clk); addr = address; ce = 1; wr = 1; data_wr = data; @ (posedge clk); addr = 0; ce = 0; wr = 0; $display ("======================"); endendtask

// Memory model for checking tasksreg [7:0] mem [0:255];

always @ (addr or ce or rd or wr or data_wr)if (ce) begin if (wr) begin mem[addr] = data_wr; end if (rd) begin data_rd = mem[addr]; endend

endmodule

Page 142: Verification

System tasks and functions:

There are tasks and functions that are used to generate input and output during simulation. Their names begin with a dollar sign ($). The synthesis tools parse and ignore system functions, and hence can be included even in synthesizable models.

$display, $strobe, $monitor:

These commands have the same syntax, and display text on the screen during simulation. They are much less convenient than waveform display tools like GTKWave. or Undertow or Debussy. $display and $strobe display once every time they are executed, whereas $monitor displays every time one of its parameters changes. The difference between $display and $strobe is that $strobe displays the parameters at the very end of the current simulation time unit rather than exactly when it is executed. The format string is like that in C/C++, and may contain format characters. Format characters include %d (decimal), %h (hexadecimal), %b (binary), %c (character), %s (string) and %t (time), %m (hierarchy level). %5d, %5b etc. would give exactly 5 spaces for the number instead of the space needed. Append b, h, o to the task name to change default format to binary, octal or hexadecimal.

Syntax:

$display ("format_string", par_1, par_2, ... ); $strobe ("format_string", par_1, par_2, ... ); $monitor ("format_string", par_1, par_2, ... ); $displayb (as above but defaults to binary..); $strobeh (as above but defaults to hex..); $monitoro (as above but defaults to octal..);

$time, $stime, $realtime:

These return the current simulation time as a 64-bit integer, a 32-bit integer, and a real number, respectively.

$reset, $stop, $finish:

$reset resets the simulation back to time 0; $stop halts the simulator and puts it in interactive mode where the user can enter commands; $finish exits the simulator back to the operating system.

Page 143: Verification

$scope, $showscope:

$scope(hierarchy_name) sets the current hierarchical scope to hierarchy_name. $showscopes(n) lists all modules, tasks and block names in (and below, if n is set to 1) the current scope.

$random:

$random generates a random integer every time it is called. If the sequence is to be repeatable, the first time one invokes random giving it a numerical argument (a seed). Otherwise the seed is derived from the computer clock.

$dumpfile, $dumpvar, $dumpon, $dumpoff, $dumpall:

These can dump variable changes to a simulation viewer like Debussy. The dump files are capable of dumping all the variables in a simulation. This is convenient for debugging, but can be very slow.

Syntax

$dumpfile("filename.vcd") $dumpvar dumps all variables in the design. $dumpvar(1, top) dumps all the variables in module top and below, but not

modules instantiated in top. $dumpvar(2, top) dumps all the variables in module top and 1 level below. $dumpvar(n, top) dumps all the variables in module top and n-1 levels below. $dumpvar(0, top) dumps all the variables in module top and all level below. $dumpon initiates the dump. $dumpoff stop dumping.

$fopen, $fdisplay, $fstrobe $fmonitor and $fwrite

$fopen opens an output file and gives the open file a handle for use by the other commands.

$fclose closes the file and lets other programs access it. $fdisplay and $fwrite write formatted data to a file whenever they are executed.

They are the same except $fdisplay inserts a new line after every execution and $write does not.

$strobe also writes to a file when executed, but it waits until all other operations in the time step are complete before writing. Thus initial #1 a=1; b=0; $fstrobe(hand1, a,b); b=1; will write write 1 1 for a and b.

$monitor writes to a file whenever any of its arguments changes.

Syntax

handle1=$fopen("filenam1.suffix")

Page 144: Verification

handle2=$fopen("filenam2.suffix") $fstrobe(handle1, format, variable list) //strobe data into filenam1.suffix $fdisplay(handle2, format, variable list) //write data into filenam2.suffix $fwrite(handle2, format, variable list) //write data into filenam2.suffix all on one

line. Put in the format string where a new line is desired.

TESTBENCH:

Writing a testbench is as complex as writing the RTL code itself. These days ASICs are getting more and more complex and thus verifying these complex ASIC has become a challenge. Typically 60-70% of time needed for any ASIC is spent on verification/validation/testing. Even though the above facts are well known to most ASIC engineers, still engineers think that there is no glory in verification.

Before you Start

For writing testbenches it is important to have the design specification of "design under test" or simply DUT. Specs need to be understood clearly and a test plan, which basically documents the test bench architecture and the test scenarios (test cases) in detail, needs to be made.    Example - Counter

Let's assume that we have to verify a simple 4-bit up counter, which increments its count whenever enable is high, and resets to zero when reset is asserted high. Reset is synchronous to clock.

module counter (clk, reset, enable, count); 8 input clk, reset, enable; 9 output [3:0] count; 10 reg [3:0] count; 11 12 always @ (posedge clk) 13 if (reset == 1'b1) begin 14 count <= 0; 15 end else if ( enable == 1'b1) begin 16 count <= count + 1; 17 end 18 19 endmodule Test Plan

We will write a self-checking test bench, but we will do this in steps to help you understand the concept of writing automated test benches. Our testbench environment will look something like the figure below

Page 145: Verification

DUT is instantiated in the testbench, and the testbench will contain a clock generator, reset generator, enable logic generator and compare logic, which basically calculates the expected count value of counter and compares it with the output of counter.

Test Cases

Reset Test : We can start with reset de-asserted, followed by asserting reset for few clock ticks and deasserting the reset, See if counter sets its output to zero.

Enable Test : Assert/deassert enable after reset is applied. Random Assert/deassert of enable and reset.

Writing a TestBench:

First step of any testbench creation is building a dummy template which basically declares inputs to DUT as reg and outputs from DUT as wire, then instantiates the DUT as shown in the code below. Note that there is no port list for the test bench.

Test Bench

module counter_tb; 2 reg clk, reset, enable; 3 wire [3:0] count; 4 5 counter U0 ( 6 .clk (clk), 7 .reset (reset), 8 .enable (enable), 9 .count (count) 10 ); 11 12 endmodule

Next step would be to add clock generator logic: this is straight forward, as we know how to generate a clock. Before we add a clock generator we need to drive all the inputs to DUT to some known state as shown in the code below.

Test Bench with Clock generator

Page 146: Verification

module counter_tb; 2 reg clk, reset, enable; 3 wire [3:0] count; 4 5 counter U0 ( 6 .clk (clk), 7 .reset (reset), 8 .enable (enable), 9 .count (count) 10 ); 11 12 initial 13 begin 14 clk = 0; 15 reset = 0; 16 enable = 0; 17 end 18 19 always 20 #5 clk = ! clk; 21 22 endmodule

An initial block in Verilog is executed only once, thus simulator sets the value of clk, reset and enable to 0; by looking at the counter code (of course you will be referring to the DUT specs) could be found that driving 0 makes all these signals disabled.

There are many ways to generate a clock: one could use a forever loop inside an initial block as an alternative to the above code. You could a add parameter or use `define to control the clock frequency. You may write a complex clock generator, where we could introduce PPM (Parts per million, clock width drift), then control the duty cycle. All the above depends on the specs of the DUT and the creativity of a "Test Bench Designer".

At this point, you would like to test if the testbench is generating the clock correctly: well you can compile it with any Verilog simulator. You need to give command line options as shown below.

C:\www.asic-world.com\veridos counter.v counter_tb.v

Of course it is a very good idea to keep file names the same as the module name. Ok, coming back to compiling, you will see that the simulator does print anything on screen, or dump any waveform. Thus we need to add support for all the above as shown in the code below.

module counter_tb; 2 reg clk, reset, enable; 3 wire [3:0] count; 4 5 counter U0 ( 6 .clk (clk), 7 .reset (reset), 8 .enable (enable), 9 .count (count)

Page 147: Verification

10 ); 11 12 initial begin 13 clk = 0; 14 reset = 0; 15 enable = 0; 16 end 17 18 always 19 #5 clk = ! clk; 20 21 initial begin 22 $dumpfile ("counter.vcd"); 23 $dumpvars; 24 end 25 26 initial begin 27 $display("\t\ttime,\tclk,\treset,\tenable,\tcount"); 28 $monitor("%d,\t%b,\t%b,\t%b,\t%d",$time, clk,reset,enable,count); 29 end 30 31 initial 32 #100 $finish; 33 34 //Rest of testbench code after this line 35 36 endmodule

$dumpfile is used for specifying the file that the simulator will use to store the waveform, that can be used later using a waveform viewer. (Please refer to the tools section for freeware versions of viewers.) $dumpvars basically instructs the Verilog compiler to start dumping all the signals to "counter.vcd".

$display is used for printing text or variables to stdout (screen), \t is for inserting tabs. The syntax is the same as for printf C language. $monitor in the second line is a bit different: $monitor keeps track of changes to the variables that are in the list (clk, reset, enable, count). Whenever any of them changes, it prints their value, in the respective radix specified.

$finish is used for terminating the simulation after #100 time units (note: all the initial, always blocks start execution at time 0).

Now that we have written the basic skeleton, let's compile and see what we have just coded. Output of the simulator is shown below.

Page 148: Verification

Adding Reset Logic:

Once we have the basic logic to allow us to see what our testbench is doing, we can next add the reset logic. If we look at the testcases, we see that we had added a constraint that it should be possible to activate reset anytime during simulation. To achieve this we have many approaches, but I am going to teach something that will go long way. There is something called 'events' in Verilog: events can be triggered, and also monitored, to see if an event has occurred.

Let's code our reset logic in such a way that it waits for the trigger event "reset_trigger": when this event happens, reset logic asserts reset at negative edge of clock and de-asserts on next negative edge as shown in the code below. Also after de- asserting the reset, reset logic triggers another event called "reset_done_trigger". This trigger event can then be used somewhere else in the testbench to sync up.

Code of reset logic:event reset_trigger; 2 event reset_done_trigger; 3 4 initial begin 5 forever begin 6 @ (reset_trigger); 7 @ (negedge clk); 8 reset = 1; 9 @ (negedge clk); 10 reset = 0; 11 -> reset_done_trigger; 12 end 13 end

Adding test case logic:

Moving forward, let's add logic to generate the test cases, ok we have three testcases as in the first part of this tutorial. Let's list them again.

1 Reset Test : We can start with reset de-asserted, followed by asserting reset for few clock ticks and de-asserting the reset, See if counter sets its output to zero.

2 Enable Test : Assert/de-assert enable after reset is applied.

3 Random Assert/de-assert of enable and reset.

Repeating it again: "There are many ways" to code a test case, it all depends on the creativity of the Test bench designer. Let's take a simple approach and then slowly build upon it.

Test Case 1 - Asserting/ De-asserting reset:

In this test case, we will just trigger the event reset_trigger after 10 simulation units.

Page 149: Verification

initial 2 begin: TEST_CASE 3 #10 -> reset_trigger; 4 end

Test Case 2 - Assert/ De-assert enable after reset is applied.:

In this test case, we will trigger the reset logic and wait for the reset logic to complete its operation, before we start driving the enable signal to logic 1.

initial 2 begin: TEST_CASE 3 #10 -> reset_trigger; 4 @ (reset_done_trigger); 5 @ (negedge clk); 6 enable = 1; 7 repeat (10) begin 8 @ (negedge clk); 9 end 10 enable = 0; 11 end

Test Case 3 - Assert/De-assert enable and reset randomly.:

In this testcase we assert the reset, and then randomly drive values on to enable and reset signal.

initial 2 begin : TEST_CASE 3 #10 -> reset_trigger; 4 @ (reset_done_trigger); 5 fork 6 repeat (10) begin 7 @ (negedge clk); 8 enable = $random; 9 end 10 repeat (10) begin 11 @ (negedge clk); 12 reset = $random; 13 end 14 join 15 end

Well you might ask, do all this three test case exist in same file? Well, the answer is no. If we try to have all three test cases on one file, then we end up having race conditions due to three initial blocks driving reset and enable signal. So normally, once test bench coding is done, test cases are coded separately and included in testbench with `include

Page 150: Verification

directives as shown below. (There are better ways to do this, but you have to think how you want to do it).

If you look closely at all the three test cases, you will find that even though test case execution is not complete, simulation terminates. To have better control, what we can do is adding an event like "terminate_sim" and execute $finish only when this event is triggered. We can trigger this event at the end of test case execution. The code for $finish now could look as shown below.

event terminate_sim; 2 initial begin 3 @ (terminate_sim); 4 #5 $finish; 5 end

The modified test case #2 would be like:

initial

2 begin: TEST_CASE 3 #10 -> reset_trigger; 4 @ (reset_done_trigger); 5 @ (negedge clk); 6 enable = 1; 7 repeat (10) begin 8 @ (negedge clk); 9 end 10 enable = 0; 11 #5 -> terminate_sim; 12 end

Second problem with the approach that we have taken till now is that we need to manually check the waveform and also the simulator output on the screen to see if the DUT is working correctly. Part IV shows how to automate this.

Adding compare Logic:

To make any testbench self checking/automated, first we need to develop a model that mimics the DUT in functionality. In our example, it's going to be very easy, but at times if the DUT is complex, then to mimic it will be very complex and will require a lot of innovative techniques to make self-checking work.

reg [3:0] count_compare; 2 3 always @ (posedge clk) 4 if (reset == 1'b1) begin 5 count_compare <= 0; 6 end else if ( enable == 1'b1) begin

Page 151: Verification

7 count_compare <= count_compare + 1; 8 end

Once we have the logic to mimic the DUT functionality, we need to add the checker logic, which at any given point keeps checking the expected value with the actual value. Whenever there is any error, it prints out the expected and actual value, and also terminates the simulation by triggering the event "terminate_sim".

always @ (posedge clk) 2 if (count_compare ! = count) begin 3 $display ("DUT Error at time %d", $time); 4 $display (" Expected value %d, Got Value %d", count_compare, count); 5 #5 -> terminate_sim; 6 end

Now that we have the all the logic in place, we can remove $display and $monitor, as our testbench have become fully automatic, and we don't require to manually verify the DUT input and output. Try changing the count_compare = count_compare +2, and see how compare logic works. This is just another way to see if our testbench is stable.

module counter_tb;

reg clk, reset, enable;wire [3:0] count;reg dut_error;

counter U0 (.clk (clk),.reset (reset),.enable (enable),.count (count));

event reset_enable;event terminate_sim;

initialbegin $display ("###################################################"); clk = 0; reset = 0; enable = 0; dut_error = 0;end

always #5 clk = !clk;

initial begin $dumpfile ("counter.vcd"); $dumpvars;

Page 152: Verification

end

initial@ (terminate_sim) begin $display ("Terminating simulation"); if (dut_error == 0) begin $display ("Simulation Result : PASSED"); end else begin $display ("Simulation Result : FAILED"); end $display ("###################################################"); #1 $finish;end

event reset_done;

initialforever begin @ (reset_enable); @ (negedge clk) $display ("Applying reset"); reset = 1; @ (negedge clk) reset = 0; $display ("Came out of Reset"); -> reset_done;end

initial begin #10 -> reset_enable; @ (reset_done); @ (negedge clk); enable = 1; repeat (5) begin @ (negedge clk); end enable = 0; #5 -> terminate_sim;end

reg [3:0] count_compare;

always @ (posedge clk)if (reset == 1'b1) count_compare <= 0;else if ( enable == 1'b1) count_compare <= count_compare + 1;

always @ (negedge clk)

Page 153: Verification

if (count_compare != count) begin $display ("DUT ERROR AT TIME%d",$time); $display ("Expected value %d, Got Value %d", count_compare, count); dut_error = 1; #5 -> terminate_sim;end

endmodule