Test Bench Overview

132
Page | 1 TESTBENCH OVERVIEW INDEX ............INTRODUCTION ..................... Test Bench Overview ............LINEAR TB ..................... Linear Testbench ............FILE IO TB ..................... File I/O Based Testbench ............STATE MACHINE BASED TB ............TASK BASED TB ..................... Task And Function Based Tb ............SELF CHECKING TESTBENCH ..................... Stimulus Generator ..................... Bus Functional Models ..................... Driver ..................... Reciver ..................... Protocol Monitor ..................... Scoreboard ..................... Checker ..................... Coverage ..................... Code Coverage ..................... Functional Coverage ............VERIFICATION FLOW ..................... Planning ..................... Feature Extraction ..................... Verification Environment Architecture Plan ............CLOCK GENERATOR ..................... Timescale And Precision Enlightment ............SIMULATION ..................... Simulation Steps ..................... Macro Preprocessing ..................... Compilation (Analyzer) ..................... Elaboration ..................... Optimization ..................... Initialization ..................... Execution ..................... Simulation Process ............INCREMENTAL COMPILATION ............STORE AND RESTORE

Transcript of Test Bench Overview

Page 1: Test Bench Overview

Page | 1

TESTBENCH OVERVIEW

INDEX

............INTRODUCTION ..................... Test Bench Overview ............LINEAR TB ..................... Linear Testbench ............FILE IO TB ..................... File I/O Based Testbench ............STATE MACHINE BASED TB ............TASK BASED TB ..................... Task And Function Based Tb ............SELF CHECKING TESTBENCH ..................... Stimulus Generator ..................... Bus Functional Models ..................... Driver ..................... Reciver ..................... Protocol Monitor ..................... Scoreboard ..................... Checker ..................... Coverage ..................... Code Coverage ..................... Functional Coverage ............VERIFICATION FLOW ..................... Planning ..................... Feature Extraction ..................... Verification Environment Architecture Plan ............CLOCK GENERATOR ..................... Timescale And Precision Enlightment ............SIMULATION ..................... Simulation Steps ..................... Macro Preprocessing ..................... Compilation (Analyzer) ..................... Elaboration ..................... Optimization ..................... Initialization ..................... Execution ..................... Simulation Process ............INCREMENTAL COMPILATION ............STORE AND RESTORE

Page 2: Test Bench Overview

Page | 2

............EVENT CYCLE SIMULATION ..................... Event Based Simulation ..................... Cycle Based Simulation ............TIME SCALE AND PRECISION ..................... Time Scale And Time Precision ..................... $Time Vs $Realtime ..................... System Task Printtimescale ..................... System Task Timeformat ............STIMULUS GENERATION ............SYSTEM FUNCTION RANDOM A MYTH ............RACE CONDITION ..................... What Is Race Condition? ..................... Why Race Condition? ..................... When Race Is Visible? ..................... How To Prevent Race Condition? ..................... Types Of Race Condition ..................... Write-Write Race ..................... Read-Write Race ..................... More Race Example ..................... Event Terminology ..................... The Stratified Event Queue ..................... Determinism ..................... Nondeterminism ..................... Guideline To Avoid Race Condition ..................... Avoid Race Between Testbench And Dut ............CHECKER ..................... Protocol Checker ..................... Data_checker ..................... Modularization ............TASK AND FUNCTION ..................... Functions ..................... Task ..................... Task And Function Queries ..................... Constant Function ..................... Reentrant Tasks And Functions ............PROCESS CONTROL ..................... Nonblocking Task ..................... Fork/Join Recap ..................... Fork/Join None ..................... Fork/Join Any ............DISABLEING THE BLOCK ..................... Disable ..................... Goto ..................... Break ..................... Continue

Page 3: Test Bench Overview

Page | 3

............WATCHDOG ............COMPILATION N SIMULATION SWITCHS ..................... Compilation And Simulation Directives ..................... Example ............DEBUGGING ..................... Pass Or Fail ..................... Waveform Viewer ..................... Log File ..................... Message Control System ..................... Message Severity Levels ..................... Message Controlling Levels ..................... Passing Comments To Waveform Debugger ..................... $Display N $Strobe ..................... Who Should Do The Rtl Debugging? ............ABOUT CODE COVERAGE ..................... Types Of Coverage ..................... Code Coverage ..................... Statement Coverage /Line Coverage ..................... Block/Segment Coverage ..................... Branch / Decision / Conditional Coverage ..................... Path Coverage ..................... Expression Coverage ..................... Toggle Coverage ..................... Variable Coverage ..................... Triggering / Event Coverage ..................... Parameter Coverage ..................... Functional Coverage ..................... Fsm Coverage ..................... State Coverage ..................... Transition Coverage ..................... Sequence Coverage ..................... Tool Support ..................... Limitation Of Code Coverage ............TESTING STRATIGIES ..................... Bottom-Up ..................... Unit Level ..................... Sub-Asic Level ..................... Asic Level ..................... System Level ..................... Flat ............FILE HANDLING ..................... Fopen And Fclose ..................... Fdisplay ..................... Fmonitor ..................... Fwrite ..................... Mcd ..................... Formating Data To String

Page 4: Test Bench Overview

Page | 4

............VERILOG SEMAPHORE

..................... Semaphore In Verilog ............FINDING TESTSENARIOUS ..................... Register Tests ..................... System Tests ..................... Interrupt Tests ..................... Interface Tests ..................... Functional Tests ..................... Error Tests ..................... Golden Tests ..................... Performance Tests ............HANDLING TESTCASE FILES ............TERIMINATION ............ERROR INJUCTION ..................... Value Errors ..................... Temporal Errors ..................... Interface Error ..................... Sequence Errors ............REGISTER VERIFICATION ..................... Register Verification ..................... Register Classification ..................... Features ............PARAMETERISED MACROS ............WHITE GRAY BLACK BOX ..................... Black Box Verification ..................... White Box Verification ..................... Gray Box Verification ............REGRESSION ............TIPS ..................... How To Avoid "Module Xxx Already Defined" Error ..................... Colourful Messages ..................... Debugging Macros

Page 5: Test Bench Overview

Page | 5

INTRODUCTION 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 either binary or hexadecimal data. TestBench is like just an interface between external vector source and DUT.

Page 6: Test Bench Overview

Page | 6

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

Page 7: Test Bench Overview

Page | 7

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] = 234ac; Mem[1] = 23ca5; Mem[2] = b3c34; Mem[3] = 23a4a; Mem[4] = 234ca; Mem[5] = b3234; end RESULT: 0:000234ac 1:00023ca5 2:000b3c34 3:00023a4a 4:000234ca 5:000b3234

Page 8: Test Bench Overview

Page | 8

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, 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.

Page 9: Test Bench Overview

Page | 9

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 data end endtask Now lets see how to develop the senarious. 1) 10 write operations. 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.

Page 10: Test Bench Overview

Page | 10

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: 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:

Page 11: Test Bench Overview

Page | 11

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

Page 12: Test Bench Overview

Page | 12

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 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 because we're talking about the degree to what our tests cover our code, or simply, the quality of tests. In other words,

Page 13: Test Bench Overview

Page | 13

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 FLOW 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? Verification Environment Architecture Plan:

Page 14: Test Bench Overview

Page | 14

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

Page 15: Test Bench Overview

Page | 15

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

Page 16: Test Bench Overview

Page | 16

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 --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;

Page 17: Test Bench Overview

Page | 17

#(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 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;

Page 18: Test Bench Overview

Page | 18

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

Page 19: Test Bench Overview

Page | 19

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

Page 20: Test Bench Overview

Page | 20

SIMULATION

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 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:

Page 21: Test Bench Overview

Page | 21

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 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.

Page 22: Test Bench Overview

Page | 22

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 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.

Page 23: Test Bench Overview

Page | 23

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 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.

TIME SCALE AND PRECISION 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 --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

Page 24: Test Bench Overview

Page | 24

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 i=0; #7.7212; i=1; $display("STATEMENT 1 :: time is ",$stime); #7.123; $finish; end endmodule

Page 25: Test Bench Overview

Page | 25

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: STATEMENT 1 :: time is 8 STATEMENT 2 :: delay for 80000 reg i toggled at 77.212 in waveform debugger

Page 26: Test Bench Overview

Page | 26

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. 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,

Page 27: Test Bench Overview

Page | 27

$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 -6 1 us -14 10 fs -7 100 ns -15 1 fs

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

Page 28: Test Bench Overview

Page | 28

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 `timescale 1 ps / 1 ps module a2_dat; reg in2; integer file2; buf #10000 (o2,in2);

Page 29: Test Bench Overview

Page | 29

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; 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;

Page 30: Test Bench Overview

Page | 30

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]; 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

Page 31: Test Bench Overview

Page | 31

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

Page 32: Test Bench Overview

Page | 32

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 begin repeat(5) #1 address = $random; end initial $monitor("address = %d;",address); endmodule RESULT: address = 303379748; address = -1064739199; address = -2071669239; address = -1309649309; address = 112818957;

Page 33: Test Bench Overview

Page | 33

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); 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:

Page 34: Test Bench Overview

Page | 34

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: 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

Page 35: Test Bench Overview

Page | 35

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

Page 36: Test Bench Overview

Page | 36

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 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;

Page 37: Test Bench Overview

Page | 37

$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 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

Page 38: Test Bench Overview

Page | 38

To generate random real number , system function $bitstoreal can also be used. EXAMPLE: module Tb(); real r_num; initial 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;

Page 39: Test Bench Overview

Page | 39

man = $random; r_num = $bitstoreal({sgn,exp,man}); $display("r_num = %e",r_num); end end endmodule RESULTS: 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

Page 40: Test Bench Overview

Page | 40

ind[index] = 0; got = 1; num = arr[index]; end 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

Page 41: Test Bench Overview

Page | 41

RESULT: 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 $display(" "); end end

Page 42: Test Bench Overview

Page | 42

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

Page 43: Test Bench Overview

Page | 43

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 | 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.

Page 44: Test Bench Overview

Page | 44

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); $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

Page 45: Test Bench Overview

Page | 45

| 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; 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:

Page 46: Test Bench Overview

Page | 46

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. 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

Page 47: Test Bench Overview

Page | 47

$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 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

Page 48: Test Bench Overview

Page | 48

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. 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

Page 49: Test Bench Overview

Page | 49

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; 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:

Page 50: Test Bench Overview

Page | 50

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

Page 51: Test Bench Overview

Page | 51

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

Page 52: Test Bench Overview

Page | 52

end endmodule RESULTS: num_1 = 20,num_2 = 22 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

Page 53: Test Bench Overview

Page | 53

$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); 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; if(var_1 == 3 ) num_3 = num_3 + 1;

Page 54: Test Bench Overview

Page | 54

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. 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,

Page 55: Test Bench Overview

Page | 55

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 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.

Page 56: Test Bench Overview

Page | 56

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; out <= in; end Now if you swap these two lines: EXAMPLE initial begin out <= in; in = 1; end

Page 57: Test Bench Overview

Page | 57

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? 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:

Page 58: Test Bench Overview

Page | 58

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

Page 59: Test Bench Overview

Page | 59

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

Page 60: Test Bench Overview

Page | 60

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); 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.

Page 61: Test Bench Overview

Page | 61

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. #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.

Page 62: Test Bench Overview

Page | 62

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. 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

Page 63: Test Bench Overview

Page | 63

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 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 checks 3) 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

Page 64: Test Bench Overview

Page | 64

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: --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 ; output c;

Page 65: Test Bench Overview

Page | 65

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 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.

Page 66: Test Bench Overview

Page | 66

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] 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

Page 67: Test Bench Overview

Page | 67

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; 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:

Page 68: Test Bench Overview

Page | 68

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

Page 69: Test Bench Overview

Page | 69

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. 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

Page 70: Test Bench Overview

Page | 70

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

Page 71: Test Bench Overview

Page | 71

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

Page 72: Test Bench Overview

Page | 72

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. 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;

Page 73: Test Bench Overview

Page | 73

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

Page 74: Test Bench Overview

Page | 74

begin #20; $display(" statement inside blocking task_2 at %t",$time); end endtask endmodule RESULTS statement inside blocking task_1 at 11 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; : : :

Page 75: Test Bench Overview

Page | 75

end endtask Goto Verilog does not have a goto, but the effect of a forward goto can be acheived as 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

Page 76: Test Bench Overview

Page | 76

WATCHDOG

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 77: Test Bench Overview

Page | 77

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(); begin $display(" WATCHDOG : started at %0d ",$time); fork : watch_dog begin

Page 78: Test Bench Overview

Page | 78

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: Conditional Compilation directive switches vs Simulation directive switches Verilog has following conditional compiler directives. `ifdef `else `elsif `endif `ifndef

Page 79: Test Bench Overview

Page | 79

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 RESULT: TYPE_1 message Compile with +define+TYPE_2 Then simulate,result is RESULT: TYPE_2 message

Page 80: Test Bench Overview

Page | 80

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 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.

Page 81: Test Bench Overview

Page | 81

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

Page 82: Test Bench Overview

Page | 82

$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.v Simulation : 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 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

Page 83: Test Bench Overview

Page | 83

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

Page 84: Test Bench Overview

Page | 84

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. 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.

Page 85: Test Bench Overview

Page | 85

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. 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:

Page 86: Test Bench Overview

Page | 86

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: 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

Page 87: Test Bench Overview

Page | 87

endmodule When compilation is done without +define+DEBUG 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 88: Test Bench Overview

Page | 88

`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 The reg " Comment " holds string. This strings can be viewed in waveform debugger.

Page 89: Test Bench Overview

Page | 89

$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 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

Page 90: Test Bench Overview

Page | 90

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 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;

Page 91: Test Bench Overview

Page | 91

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.

Branch / Decision / Conditional Coverage:

Page 92: Test Bench Overview

Page | 92

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; 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 93: Test Bench Overview

Page | 93

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: 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.

Page 94: Test Bench Overview

Page | 94

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: 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.

Page 95: Test Bench Overview

Page | 95

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 and how many sequence are covered. Thats the duty of FSM coverage.

Page 96: Test Bench Overview

Page | 96

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: 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.

Page 97: Test Bench Overview

Page | 97

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

Page 98: Test Bench Overview

Page | 98

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 files Tasks that output values into files Tasks that output values into variables Tasks and functions that read values from files and load into variables or memories

Fopen And Fclose $fopen and $fclose 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 example

Page 99: Test Bench Overview

Page | 99

module 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 end endmodule 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 $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 end endmodule RESULT value of mcd before opening the file xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx value 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 to know that file is closed?? That will come after following examples. Fdisplay

Page 100: Test Bench Overview

Page | 100

$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); end endmodule RESULT Number is 303379748 Number is -1064739199 Number is -2071669239 Number is -1309649309 Number is 112818957 Number is 1189058957 Number is -1295874971 EXAMPLE $displayb module 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

Page 101: Test Bench Overview

Page | 101

$fclose(mcd); end endmodule RESULT Number is 00010010000101010011010100100100 Number is 11000000100010010101111010000001 Number is 10000100100001001101011000001001 Number is 10110001111100000101011001100011 Number is 00000110101110010111101100001101 Number is 01000110110111111001100110001101 Number is 10110010110000101000010001100101 EXAMPLE c. $displayo module fopenclose(); integer mcd,number; initial begin mcd = $fopen("temp.txt"); // mcd = multi_channel_descriptor repeat(7) begin number = $random; $fdisplayo(mcd, "Number is ", number); end $fclose(mcd); end endmodule RESULT Number is 02205232444 Number is 30042257201 Number is 20441153011 Number is 26174053143 Number is 00656275415 Number is 10667714615 Number is 26260502145 EXAMPLE. $displayh module fopenclose(); integer mcd,number; initial begin mcd = $fopen("temp.txt"); // mcd = multi_channel_descriptor repeat(7) begin

Page 102: Test Bench Overview

Page | 102

number = $random; $fdisplayh(mcd, "Number is ", number); end $fclose(mcd); end endmodule RESULT Number is 12153524 Number is c0895e81 Number is 8484d609 Number is b1f05663 Number is 06b97b0d Number is 46df998d Number is b2c28465 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. EXAMPLE module 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); end endmodule RESULT Error during elaboration. Fmonitor

Page 103: Test Bench Overview

Page | 103

$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 $fmonitor module monitortask(); integer mcd,number; initial begin #0; mcd = $fopen("abc.txt"); $monitoron; repeat(7) begin #1 number = $random ; end $monitoroff; $fclose(mcd); end initial $fmonitorh(mcd, " Number is ", number); endmodule RESULT Number is 12153524 Number is c0895e81 Number is 8484d609 Number is b1f05663 Number is 06b97b0d Number 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 to cache 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.

Page 104: Test Bench Overview

Page | 104

EXAMPLE // file open close example with $fwrite module 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); 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); end endmodule 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 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx value of mcd2 before opening the file xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx value of mcd1 after opening the file 00000000000000000000000000000010 value of mcd2 after opening the file 00000000000000000000000000000100 in file pqr.txt Pointer is 12153524 Pointer is 8484d609 Pointer is 06b97b0d Pointer is b2c28465 Pointer is 00f3e301 Pointer is 3b23f176 Pointer is 76d457ed In file xyz.txt Number is 37777777767 Number is 37777777767 Number is 00000000007 Number is 37777777774 Number is 00000000011 Number is 00000000007 Number is 00000000002

Page 105: Test Bench Overview

Page | 105

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 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. EXAMPLE module 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); end endmodule RESULT In lsbbit1.txt Number is 12153524 Number is c0895e81 Number is 8484d609 Number is b1f05663 Number is 06b97b0d Number is 46df998d Number is b2c28465 In lsbbit2.txt Number is 12153524 Number is c0895e81 Number is 8484d609 Number is b1f05663 Number is 06b97b0d Number is 46df998d Number is b2c28465

Page 106: Test Bench Overview

Page | 106

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 logical or with the constant 1, which effectively writes to both files as well as monitor screen. EXAMPLE module 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 endmodule endmodule RESULT Number is 12153524 Number is c0895e81 Number is 8484d609 Number is b1f05663 Number is 06b97b0d Number is 46df998d Number 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 a reg variable whose content is interpreted as the format string. No other arguments are interpreted as

Page 107: Test Bench Overview

Page | 107

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; task get_key(); input integer i; begin if ( keys == 0) begin

Page 108: Test Bench Overview

Page | 108

$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: 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

Page 109: Test Bench Overview

Page | 109

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

Page 110: Test Bench Overview

Page | 110

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 $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

Page 111: Test Bench Overview

Page | 111

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

Page 112: Test Bench Overview

Page | 112

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

Page 113: Test Bench Overview

Page | 113

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

Page 114: Test Bench Overview

Page | 114

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. 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

Page 115: Test Bench Overview

Page | 115

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

Page 116: Test Bench Overview

Page | 116

EXAMPLE: testcase_1.v // Do 10 write operations module TEST(); initial repeat(10) top.write(); 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

Page 117: Test Bench Overview

Page | 117

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

Page 118: Test Bench Overview

Page | 118

cat testcase_2.v > test.v compile_command top.v test.v During simulation , for each test case, use 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)

Page 119: Test Bench Overview

Page | 119

top.read(); 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 if(no_of_errors == 0) $display(" *********TEST PASSED ***********");

Page 120: Test Bench Overview

Page | 120

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 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.

Page 121: Test Bench Overview

Page | 121

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 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.

Page 122: Test Bench Overview

Page | 122

Register Classification: Registers can be mainly classified in to these categories 1) 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. 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.

Page 123: Test Bench Overview

Page | 123

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 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/write read only write only read only, write can be by the design clear on read automatically set to 1 by design. Automatically set to 0 by design. Readable and settable by writing 1 Readable and clearable by writing 1

Page 124: Test Bench Overview

Page | 124

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. 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

Page 125: Test Bench Overview

Page | 125

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 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.

Page 126: Test Bench Overview

Page | 126

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

Page 127: Test Bench Overview

Page | 127

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

Page 128: Test Bench Overview

Page | 128

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 `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 129: Test Bench Overview

Page | 129

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(); initial begin $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); $display("*********** This is in green ***********"); $display("%c[0m",27);

Page 130: Test Bench Overview

Page | 130

$display("%c[7;34m",27); $display("*********** This is in Back ground color ***********"); $display("%c[0m",27); end endmodule 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 bold 2 set half-bright (simulated with color on a color display) 4 set underscore (simulated with color on a color display) 5 set blink 7 set reverse video COLOR specifies the message color. 30 set black foreground 31 set red foreground 32 set green foreground 33 set brown foreground 34 set blue foreground 35 set magenta foreground 36 set cyan foreground 37 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 `define display_green $write("%c[0m",27); $write("%c[1;32m",27); $display Use the macros instead of $display(). EXAMPLE: module color();

Page 131: Test Bench Overview

Page | 131

initial begin `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. The find the exact cause of error, simply use the C pre-processor. Just use command cpp file_name.v

Page 132: Test Bench Overview

Page | 132

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; initial a = b + c ;; endmodule