Test Bench Overview
Test Bench Overview
INDEX
Page | 1
............INTRODUCTION
..................... Test Bench Overview
............LINEAR TB
..................... Linear Testbench
............FILE IO TB
..................... File I/O Based Testbench
............TASK BASED TB
..................... Task And Function Based Tb
............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
............STIMULUS GENERATION
............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
............PROCESS CONTROL
..................... Nonblocking Task
..................... Fork/Join Recap
..................... Fork/Join None
..................... Fork/Join Any
............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
............VERILOG SEMAPHORE
..................... Semaphore In Verilog
............FINDING TESTSENARIOUS
..................... Register Tests
..................... System Tests
..................... Interrupt Tests Page | 4
..................... Interface Tests
..................... Functional Tests
..................... Error Tests
..................... Golden Tests
..................... Performance Tests
............TERIMINATION
............ERROR INJUCTION
..................... Value Errors
..................... Temporal Errors
..................... Interface Error
..................... Sequence Errors
............REGISTER VERIFICATION
..................... Register Verification
..................... Register Classification
..................... Features
............PARAMETERISED MACROS
............REGRESSION
............TIPS
..................... How To Avoid "Module Xxx Already Defined" Error
..................... Colourful Messages
..................... Debugging Macros
INTRODUCTION
TestBench must verify that the design does everything it is supposed to do and does not Page | 5
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:
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
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.
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. Page | 6
Note: The data file must reside in the same directory as the .v file for the module in this
example.
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
RESULT:
0:000234ac
1:00023ca5
2:000b3c34
3:00023a4a
4:000234ca
5:000b3234
module readmemh_demo;
Page | 7
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
RESULT:
0:000234ac
1:00023ca5
2:000b3c34
3:00023a4a
4:000234ca
5:000b3234
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 Page | 8
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
initial
begin
No_of_reads = 0;
No_of_writes = 10;
end
initial
begin
No_of_reads = 10;
No_of_writes = 0;
end
With the above style of testbench, the controllability is less and hard to change the code
to add new features like to convert the above code to alternate read and write operation,
its very difficult.
TASK BASED TB
Task 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 Page | 9
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
1) 10 write operations.
initial
repeat(10)
write($random,$random);
2) 10 read operations
initial
repeat(10)
read($random,data);
initial
repeat(10)
begin
write($random,$random);
read($random,data);
end
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.
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.
The Bus Functional Model (BFM) for a device interacts with the DUT by both driving and
sampling the DUT signals. A bus functional model is a model that provides a task or
procedural interface to specify certain bus operations for a defined bus protocol. For a
memory DUT, transactions usually take the form of read and write operations. Bus
functional models are easy to use and provide good performance. It has to follow the
timing protocol of the DUT interface. BFM describes the functionality and provides a
cycle accurate interface to DUT. It models external behavior of the device. For re
usability, the implementation of the BFM functionality should be kept as independent of
the communication to the BFM as it can be.
Driver
Driver is a types of BFM. The drivers translate the stimuli produced by the generator into
the actual inputs for the design under verification. Generators create inputs at a high
level of abstraction; namely, as transactions like read write operation. The drivers
convert this input into actual design inputs which is at a low level like bits ,as defined in
the specification of the designs interface. If the generator generates read operation,
then read task is called, in that, the DUT input pin "read_write" is asserted.
Reciver
Page | 12
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:
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,
code coverage is not about verifying the end product's quality.
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
Planning:
After the preliminary design specification is completed, the first verification phase is
started Verification planning.
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?
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
EXAMPLE:
module Tb();
reg clock;
integer no_of_clocks;
parameter CLOCK_PERIOD = 5;
initial no_of_clocks = 0;
initial clock = 1'b0; Page | 15
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@(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; Page | 16
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.
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;
#(CLOCK_PERIOD - CLOCK_PERIOD/3.0) clock = 1'b1;
end
initial no_of_clocks = 0;
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.
EXAMPLE:
parameter CLK_PERIOD = 10;
parameter DUTY_CYCLE = 60; //60% duty cycle
parameter TCLK_HI = (CLK_PERIOD*DUTY_CYCLE/100);
parameter TCLK_LO = (CLK_PERIOD-TCLK_HI);
reg clk;
initial
clk = 0;
always
begin Page | 18
#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:
Often clockgenerators are required to generate clock with jitter.The following is simple
way to generate clock with jitter.
EXAMPLE:
initial clock = 1'b0;
With the above approace,over all clock period is increased. A better approach for clock
divider is as follows
Page | 19
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.
always@(posedge base_clock)
begin
T2 = $realtime;
period = T2 - T1;
T1 = T2;
end
SIMULATION
Simulation Steps:
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.
Macro Preprocessing:
The macro preprocessing step performs textual substitutions of macros defined with
`define statements, textual inclusion with `include statements, and conditional
compilation by `ifdef and `ifndef statements.
Compilation (Analyzer)
Checks source code to check syntax and semantic rules. If a syntax or semantic error
occurs, then the compiler gives error message. If there are no errors , compilation
produces an internal representation for each HDL design unit.
Elaboration
The elaboration process constructs a design hierarchy based on the instantiation and
configuration information in the design, establishes signal connectivity . Memory storage
is allocated for the required signals. The elaboration process creates a hierarchy of
module instances that ends with primitive gates and statements.
Optimization:
Some tools support optimization at this level. This is optional step.
Initialization :
Initial values preset in the declarations statement are assigned to signals / variables. Page | 21
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 :
INCREMENTAL COMPILATION
Incremental compilation means that the compiler inspects your code, determines which
parts of the application are affected by your changes, and only recompiles the newer
files. Incremental compilation can help reduce compile time on small applications, but
you achieve the biggest gains on larger applications. The default value of the incremental
compiler option is true for most tools.
While recompiling , Incremental compilation does less work than full recompile. The
simulation doesn't show any difference , only the compilation time is reduced. Imagine
you are working with hundreds of files and U just changed one file, During full
recompilation , all the files are recompiled, where in incremental compilation , only the
file which is changed and the files which are dependent on changed files are compiled
and linked to the already compiled database.
STORE AND RESTORE
Incremental compilation means that the compiler inspects your code, determines which
parts of the application are affected by your changes, and only recompiles the newer
files. Incremental compilation can help reduce compile time on small applications, but
you achieve the biggest gains on larger applications. The default value of the incremental Page | 22
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.
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 Page | 23
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.
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.
The following examples demonstrate how the time scale and time precision effect $stime,
#delay and toggling in waveform.
EXAMPLE:
`timescale 10ns/10ns
module tim();
reg i;
initial
begin Page | 24
i=0;
#7.7212;
i=1;
$display("STATEMENT 1 :: time is ",$stime);
#7.123;
$finish;
end
endmodule
module try;
initial begin
$display("STATEMENT 2 :: delay for %0t",delay_time );
end
endmodule
RESULTS:
STATEMENT 1 :: time is 8
STATEMENT 2 :: delay for 8
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
module try;
STATEMENT 1 :: time is 8
STATEMENT 2 :: delay for 80
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;
RESULTS:
STATEMENT 1 :: time is 8
STATEMENT 2 :: delay for 80000
Now the second one (time precision) specify with which precision time values are rounded. Page | 26
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
gopi@testbench.in
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.
The $printtimescale system task displays the time unit and precision for a particular
module. When no argument is specified, $printtimescale displays the time unit and
precision of the module that is the current scope. When an argument is specified,
$printtimescale displays the time unit and precision of the module passed to it.
EXAMPLE:
`timescale 1 ms / 1 us
module a_dat; Page | 27
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:
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
Syntax : $timeformat(time unit, precision number, suffix string, and minimum field
width);
EXAMPLE:
`timescale 1 ms / 1 ns
module cntrl; Page | 28
initial
$timeformat(-9, 5, " ns", 10);
endmodule
`timescale 1 fs / 1 fs
module a1_dat;
reg in1;
integer file;
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:
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;
RESULTS:
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;
end
initial
$monitor($time,"read_write = %d ; data = %d ; address =
%d;",read_write,data,address);
Page | 30
endmodule
RESULT:
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.
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
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.
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)
begin read_write = $random ; data = $random;address = $random; end
$finish;
end
initial
$monitor($time,"read_write = %d ; data = %d ; address = Page | 32
%d;",read_write,data,address);
endmodule
RESULT:
$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;
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 Page | 33
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:
module Tb();
integer add_2;
reg [31:0] add_1;
integer add_3;
initial Page | 34
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:
The above example shows the generation of numbers from 0 to N.Some specification
require the range to start from non Zero number. MIN + {$random} % (MAX - MIN ) will
generate random numbers between MIN and MAX.
EXAMPLE:
module Tb();
integer add;
initial
begin
repeat(5)
begin
#1;
add = 40 + {$random} % (50 - 40) ;
$display("add = %d",add);
end
end
endmodule
RESULT:
add = 48 Page | 35
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
number, the following method can be used.
EXAMPLE:
module Tb();
reg [4:0] add_1; Page | 36
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:
Some protocols require a random number which is multiple some number. For example,
Ethernet packet is always in multiples of 8bits,and PCIExpress packets are multiples of
4byts .Look at the following example. It generates a random number which is multiple of 3
and 5.
EXAMPLE:
module Tb();
integer num_1,num_2,tmp;
initial
begin
repeat(5)
begin
#1;
tmp = {$random} / 3;
num_1 = (tmp) * 3;
tmp = {$random} / 3;
num_2 = (tmp) * 5;
$display("num_1 = %d,num_2 = %d",num_1,num_2);
end
end
endmodule
RESULT: Page | 37
All the above example show that the random numbers are integers only. In verilog there is
not special construct to generate a random real number. The following method shows the
generation of random real number.
EXAMPLE:
module Tb();
integer num_1,num_2,num_3;
real r_num;
initial
begin
repeat(5)
begin
#1;
num_1 = $random;
num_2 = $random;
num_3 = $random;
r_num = num_1 + ((10)**(-(num_2)))*(num_3);
$display("r_num = %e",r_num);
end
end
endmodule
RESULT:
r_num = -2.071669e+03
r_num = 2641.189059e+013
r_num = 976361.598336e+01
r_num = 57645.126096e+02
r_num = 24589.097015e+0
To generate random real number , system function $bitstoreal can also be used.
EXAMPLE:
module Tb(); Page | 38
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;
man = $random;
r_num = $bitstoreal({sgn,exp,man});
$display("r_num = %e",r_num);
end
end
endmodule Page | 39
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
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
end
endmodule
RESULT:
Random number system function has a argument called seed. The seed parameter controls
the numbers that $random returns such that different seeds generate different random
streams. The seed parameter shall be either a reg, an integer, or a time variable. The
seed value should be assigned to this variable prior to calling $random. For each system
function, the seed parameter is an in-out parameter; that is, a value is passed to the
function
and a different value is returned.
EXAMPLE:
module Tb();
integer num,seed,i,j;
initial
begin
for(j = 0;j<4 ;j=j+1)
begin
seed = j;
$display(" seed is %d",seed);
for(i = 0;i < 10; i=i+1)
begin
num = { $random(seed) } % 10;
$write("| num=%2d |",num);
end
$display(" ");
end
end
endmodule
RESULT:
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 Page | 41
| 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;
RESULT:
seed is 0
| num= 8 || num= 7 || num= 7 || num= 7 || num= 7 || num= 7 || num= 5 || num= 2 || Page | 42
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 ||
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 |
Page | 43
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:
From the above results we can make a table of seed values and return values of $random.
If a seed is taken from the table, then rest of the sequence has to follow sequence in
table.
Table is as falows for initial seed 0;
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:
Page | 45
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:
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 Page | 46
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
$display(" seed is one after this number of random numbers %0d total numbers
available are %d",i,{32'hffff_ffff});
end
endmodule
RESULTS: Page | 47
Verilog also has other system functions to generate random numbers. Each of these
functions returns a pseudo-random number whose characteristics are described by the
function name.
Following are the Verilog random number generator system functions:
$random
$dist_chi_square
$dist_erlang
$dist_exponential
$dist_normal
$dist_poisson
$dist_t
$dist_uniform
All parameters to the system functions are integer values. For the exponential , Poisson ,
chi-square , t , and erlang functions, the parameters mean, degree of freedom, and
k_stage must be greater than 0 .
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
As I discussed $random changes its seed , Lets see whether $dist_uniform is also doing the
same.
Page | 49
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:
Look at the results... Its interesting to note that $random and $dist_uniform have same
seed sequence flow also.
EXAMPLE:
module Tb();
integer num,seed;
integer num_20,num_21,num_22,num_23,num_24,num_25;
initial
begin Page | 50
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:
EXAMPLE:
module Tb();
integer num;
integer num_20,num_21,num_22,num_23,num_24,num_25;
initial
begin
seed = 10;
num_20 = 0;num_21 = 0;num_22 = 0;num_23 = 0;num_24 = 0;num_25 =0;
repeat(6000)
begin
num = 20 +( {$random() } %6 );
if(num == 20 ) Page | 51
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:
EXAMPLE:
module Tb();
integer num_1,num_2,seedd,seedr;
initial
begin
seedd = 10;
seedr = 10;
repeat(5)
begin
#1;
num_1 = $dist_uniform(seedd,20,25);
num_2 = 20 + ({$random(seedr)} % 6);
$display("num_1 = %d,num_2 = %d",num_1,num_2);
end
end
endmodule
RESULTS:
num_1 = 20,num_2 = 22
num_1 = 20,num_2 = 20 Page | 52
num_1 = 23,num_2 = 22
num_1 = 25,num_2 = 21
num_1 = 22,num_2 = 23
Till now what we have seen is $random has uniform distribution over integer values. It
means that distribution should be uniform across all the bits in 32 bit vector also. The
following example shows that bits positions 2,3,4,11,12,13 have equal probability of
getting 0. For demonstration I showed some indexes only. Try out rest of them and see
that results is same for all the bis.
EXAMPLE:
module Tb();
integer num;
integer num_2,num_3,num_4,num_11,num_12,num_13;
initial
begin
seed = 10;
num_2 = 0;num_3 = 0;num_4 = 0;num_11 = 0;num_12 = 0;num_13 =0;
repeat(6000)
begin
num = $random();
if(num[2] == 0 )
num_2 = num_2 + 1;
if(num[3] == 0)
num_3 = num_3 + 1;
if(num[4] == 0)
num_4 = num_4 + 1;
if(num[11] == 0)
num_11 = num_11 + 1;
if(num[12] == 0)
num_12 = num_12 + 1;
if(num[13] == 1)
num_13 = num_13 + 1;
end
$display("num_2 = %0d;num_3 = %0d;num_4 = %0d;num_11 = %0d;num_12 =
%0d;num_13 = %0d",num_2,num_3,num_4,num_11,num_12,num_13);
end
endmodule
RESULTS: Page | 53
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;
if(var_2 == 0 )
cou_0 = cou_0 + 1;
if(var_2 == 1 )
cou_1 = cou_1 + 1;
if(var_2 == 2 ) Page | 54
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:
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,
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.
Page | 55
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.
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
initial begin
q = 1;
#1 q = 0;
$display(p);
end
endmodule
EXAMPLE:
initial
begin
in = 1;
out <= in;
end
EXAMPLE
initial
begin
out <= in;
in = 1;
end
Think, is there any race condition created?
Here first statement will schedule a non-blocking update for "out" to whatever "in" was set
to previously, and then "in" will be set to 1 by the blocking assignment. Any statement
whether it is blocking or nonblocking statements in a sequential block (i.e. begin-end Page | 57
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.
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.
Some simulators have special options which reports where exactly the race condition is
exists. Linting tools can also catch 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.
Write-Write Race:
it occurs when same register is written in both the blocks.
EXAMPLE:
always @(posedge clk)
a = 1;
always @(posedge clk) Page | 58
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;
1) Function calls
EXAMPLE:
function incri();
begin
pkt_num = pkt_num + 1;
end
endfunction
always @(...)
sent_pkt_num = incri();
always @(...)
sent_pkt_num_onemore = incri();
Page | 59
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.
EXAMPLE:
reg a = 0;
initial
a = 1;
EXAMPLE:
module DUT(); Page | 60
input d;
input clock;
output q;
endmodule
module testbench();
DUT dut_i(d,clk,q);
initial
begin
@(posedge clk)
d = 1;
@(posedge clock)
d = 0;
end
endmodule
Event Terminology:
Every change in value of a net or variable in the circuit being simulated, as well as the
named event, is considered an update event. Processes are sensitive to update events.
When an update event is executed, all the processes that are sensitive to that event are
evaluated in an arbitrary order. The evaluation of a process is also an event, known as an
evaluation event.
In addition to events, another key aspect of a simulator is time. The term simulation time
is used to refer to the time value maintained by the simulator to model the actual time it
would take for the circuit being simulated. The term time is used interchangeably with
simulation time in this section. Events can occur at different times. In order to keep track
of the events and to make sure they are processed in the correct order, the events are
kept on an event queue, ordered by simulation time. Putting an event on the queue is
called scheduling an event.
The Stratified Event Queue
The Verilog event queue is logically segmented into five different regions. Events are
added to any of the five regions but are only removed from the active region.
1) Events that occur at the current simulation time and can be processed in any order. Page | 61
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.
Determinism
1) Statements within a begin-end block shall be executed in the order in which they
appear in that begin-end block. Execution of statements in a particular begin-end block
can be suspended in favor of other processes in the model; however, in no case shall the
statements in a begin-end block be executed in any order other than that in which they
appear in the source.
2) Non blocking assignments shall be performed in the order the statements were
executed.
Consider the following example:
initial begin
a <= 0;
a <= 1;
end Page | 62
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.
Race condition may occurs between DUT and testbench. Sometimes verification engineers
are not allowed to see the DUT, Sometimes they don't even have DUT to verify. Consider
the following example. Suppose a testbench is required to wait for a specific response
from its DUT. Once it receives the response, at the same simulation time it needs to send
a set of stimuli back to the DUT.
Most Synchronous DUT works on the posedge of clock. If the Testbench is also taking the
same reference, then we may unconditionally end in race condition. So it<92>s better to Page | 63
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
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) .
Duration checks are the simplest since they involve a single signal. For example, the "req
signal should be high for at least 3 clocks".
Data_checker
Data checker verifies the correctness of the device output. Data checking is based on
comparing the output with the input. The data processing protocol checking is done in
data checker which is generally in scoreboard( or tracker) . To do that you must:
--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. Page | 64
--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.
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
input a, b ;
output c;
wire c;
function myfunction;
input a, b;
begin
myfunction = (a+b); Page | 65
end
endfunction
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;
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 .
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;
Tasks and functions without the optional keyword automatic are static , with all declared
items being statically allocated. These items shall be shared across all uses of the task and
functions executing concurrently. Task and functions with the optional keyword automatic
are automatic tasks and functions. All items declared inside automatic tasks and
functions are allocated dynamically for each invocation. Automatic task items and
function items cannot be accessed by hierarchical references.
EXAMPLE:
module auto_task();
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
for (n = 0; n <= 7; n = n+1) begin
result = factorial(n);
$display("%0d factorial=%0d", n, result);
end
end
endmodule // tryfact Page | 69
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:
Page | 70
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
Fork/Join Recap:
Fork/join is a parallel block. Statements shall execute concurrently. Delay values for each
statement shall be considered relative to the simulation time of entering the block. Delay
control can be used to provide time-ordering for assignments Control shall pass out of the
block when the last time-ordered statement executes. The timing controls in a fork-join
block do not have to be ordered sequentially in time.
EXAMPLE:
module fork_join();
integer r ;
initial
fork
#50 r = 35;
#100 r = 24;
#150 r = 00; Page | 71
#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);
endmodule
RESULTS:
50 , r is 35 Page | 72
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;
$display(" statement inside blocking task_2 at %t",$time);
end
endtask
endmodule Page | 73
RESULTS
Fork/Join Any
If you want to continue the parent process after finishing any of the child process, then
block the parent process until an event if triggered by the forked threads.
EXAMPLE:
module tb();
event e,ee;
initial
begin
#1 ->e;
@(ee);
$display(" Statement after blocking_task at %t ",$time);
#40 $finish;
end
always@(e)
begin
fork
begin blocking_task_1(); -> ee;end
begin blocking_task_2(); -> ee;end
join
end
task blocking_task_1();
begin
#10;
$display(" statement inside blocking task_1 at %t",$time);
end
endtask
task blocking_task_2();
begin
#20;
$display(" statement inside blocking task_2 at %t",$time);
end
endtask
endmodule Page | 74
RESULTS
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.
EXAMPLE:
begin : block_name
rega = regb;
disable block_name;
regc = rega; // this assignment will never execute
end
This example shows the disable statement being used as an early return from a task.
However, a task disabling itself using a disable statement is not a short-hand for the
return statement found in programming languages.
EXAMPLE:
task abc();
begin : name
:
:
:
if( something happened)
disable name;
:
:
:
end
endtask
Goto
Verilog does not have a goto, but the effect of a forward goto can be acheived as shown: Page | 75
EXAMPLE:
begin: name
...
if (a)
disable name;
...
end
Execution will continue with the next statement after the end statement when the disable
is executed.
Break
The break statement as in C can be emulated with disable as shown in the following
example:
EXAMPLE:
begin: break
for (i=0; i<16; i=i+1) begin
...
if (exit)
disable break;
...
end
end
Continue
The continue statement in C causes the current iteration of a loop to be terminated, with
execution continuing with the next iteration. To do the same thing in Verilog, you can do
this:
EXAMPLE:
for (i=0; i<16; i=i+1) begin: name
...
if (abort)
disable name;
...
end
WATCHDOG
Page | 76
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.
EXAMPLE:
module DUT(clock,a,b);
output a;
output b;
input clock;
reg a,b; Page | 77
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
wait( bb == 1);
$display(" bb is asserted time:%0d",$time);
$display(" KICKING THE WATCHDOG ");
disable watch_dog;
end
begin Page | 78
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
`ifdef
`else
`elsif
`endif
`ifndef
The `ifdef compiler directive checks for the definition of a text_macro_name. If the
text_macro_name is defined, then the lines following the `ifdef directive are included. If
the text_macro_name is not defined and an `else directive exists, then this source is
compiled. The `ifndef compiler directive checks for the definition of a text_macro_name.
If the text_macro_name is not defined, then the lines following the `ifndef directive are Page | 79
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
RESULT:
TYPE_1 message
RESULT:
TYPE_2 message
TYPE_1 and TYPE_2 are called switches.
In the above example, When TYPE_1 switch is given, statement " $display(" TYPE_1
message "); " is only compile and statement " $display(" TYPE_2 message "); " is not
compiled.
Similarly for TYPE_2 switch. It wont take much time to compile this small example. Page | 80
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
RESULT:
TYPE_1 message
RESULT:
TYPE_2 message
%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))
$display(" GOT VECTOR ");
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.
Pass Or Fail
At the end of simulation of every test, TEST FAILED or TEST PASSED report should be
generated. This is called self checking. Log files and Waveform viewer can help for further
debugging if test failed.
An error count should be maintained to keep track of number of errors occurred. Simplest Page | 83
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 ;
end
endtask
endmodule
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:
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");
Executing the $dumpvars task causes the value change dumping to start at the end of the
current simulation time unit. To suspend the dump, the $dumpoff task may be invoked.
To resume the dump, the $dumpon task may be invoked.
Due to dumping the value changes to a file,there is simulation over head. Not all the time
the dumping is required. So controlling mechanism to dump VCD files needs to be
implemented.
EXAMPLE:
`ifdef DUMP_ON
$dumpon;
`endif
Page | 85
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.
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.
INFO:
The messages is used to convey simple information.
WARNING: Page | 86
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:
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
module msg();
initial
begin
#10;
`INFO("UR MESSAGE GOES HEAR");
`WARN("UR MESSAGE GOES HEAR");
`EROR("UR MESSAGE GOES HEAR");
`DBUG("UR MESSAGE GOES HEAR");
end
endmodule
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:
module top();
reg debug = 0;
initial
if($test$plusargs("DEBUG"))
#0 debug = 1;
initial
begin
#10;
`INFO("UR MESSAGE GOES HEAR");
`WARN("UR MESSAGE GOES HEAR");
`EROR("UR MESSAGE GOES HEAR");
`DBUG("UR MESSAGE GOES HEAR");
end
endmodule
When simulation is done without +DEBUG Page | 88
RESULTS:
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.
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.
$Display N $Strobe
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
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.
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
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.
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.
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.
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
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.
y = (a xor b) + (c xor d);
Page | 94
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.
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.
Page | 95
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
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 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 Page | 97
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?
TESTING STRATIGIES
Function verification approaches can be divided into two categories. Bottom-up and flat
approaches.
Bottom-Up
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
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 .
Flat
FILE HANDLING
The system tasks and functions for file-based operations are divided into three categories:
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
module fopenclose();
integer mcd,number;
initial
begin
mcd = $fopen("xyz.txt"); // opening the file
repeat(7) Page | 99
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
Fdisplay
$fdisplay, $fdisplayb, $fdisplayo, $fdisplayh
$display has its own counterparts. Those are $fdisplay, $fdisplayb, $fdisplayo, $fdisplayh.
Instead of writing on screen they are writing on the specific file with is pointed by the
mcd. $fdisplay write in decimal format, $fdisplay in binary, $fdisplay in octal and Page | 100
$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
$fclose(mcd);
end
endmodule
RESULT
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
number = $random;
$fdisplayh(mcd, "Number is ", number);
end
$fclose(mcd);
end
endmodule Page | 102
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
Fmonitor
$fmonitor, $fmonitorb, $fmonitoro, $fmonitorh, $fstrobe, $fstrobeb,$fstrobeo, $fstrobeh
Like $display; $monitor and $strobe also have counterparts. They also write in decimal,
binary, octal and hexadecimal.
Page | 103
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
Like $display; $write also have counterparts. They also write in decimal,binary, octal and
hexadecimal.
EXAMPLE
// file open close example with $fwrite
module writetask();
integer mcd1,mcd2,number,pointer;
initial Page | 104
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
In file xyz.txt
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". Page | 105
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
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.
Page | 106
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
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
format strings. $sformat supports all the format specifies supported by $display.
EXAMPLE:
$sformat(string, "Formatted %d %x", a, b);
Page | 107
VERILOG SEMAPHORE
Semaphore In Verilog
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
$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 Page | 108
endtask
task put_keys();
input integer i;
begin
keys = 1 ;
$display(" PROCESS %d gave the key back ",i);
end
endtask
initial
begin
# 10 ;
get_key(1);
repeat(4)
# 10 $display(" PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG ");
put_keys(1);
end
initial
begin
# 10 ;
get_key(2);
repeat(4)
# 10 $display(" PROCESS 2 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG ");
put_keys(2);
end
endmodule
RESULTS:
Page | 109
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;
end
endfunction
task put_keys();
input integer i;
begin Page | 110
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:
Page | 111
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
keys = keys + 1 ;
$display(" PROCESS %d gave the key back ",i);
end
endtask
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:
FINDING TESTSENARIOUS
Test scenarios can be divided in to following category while implementing @bull test
plan.
@bull Register Tests
@bull Interrupt Tests
@bull Interface Tests
@bull Protocol Tests Page | 113
@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
Interface Tests
Functional Tests
Error Tests
Error-oriented testing develops test data by focusing on the presence or absence of errors
in DUT.
Golden Tests
Set of well defined test cases executed on a modified code to ensure that changes made
to the code haven't adversely affected previously existing functions. These includes
register tests, interrupt tests, interface tests, protocol tests, functional tests & error
tests.
Performance Tests
These tests measures how well the product meets its specified performance objectives.
Example: bandwidth monitoring.
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
EXAMPLE: testcase_2.v
// Do 10 read operations
Page | 115
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.
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 tst();
end
EXAMPLE: testcase_1.v
// Do 10 write operations
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.
EXAMPLE: top.v
module top();
// DUT instance, clock generator and TB components
// some tasks
task write()
begin
// some logic
end
endtask
Page | 117
task read()
begin
// some logic
end
endtask
`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 simulation ,
for each test case, use
Page | 118
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
`include test.v
end
EXAMPLE: testcase_1.v
// Do 10 write operations
repeat(10)
top.write();
EXAMPLE: testcase_2.v
// Do 10 read operations
repeat(10)
top.read();
Intermediate file generated contains all the testcase contents with some extra logic as
shown.
Page | 119
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 ***********");
else
$display(" *********TEST FAILED ***********");
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.
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 Page | 121
and verify how the DUT is handling these. Testbench should also monitor that DUT is not
generating any packets violating this rule.
Temporal Errors
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.
Register Classification:
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...).
Some are methods which are used for register in functional verification .
Read function.
Write task. Page | 123
Update task.
Print task.
Check function.
write_random task.
All the above methods should be accessible by name and also by address.
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.
PARAMETERISED MACROS
Page | 124
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.
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
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 ;
endmodule
module and_gate(a,b,c);
input a,b;
output c;
endmodule
module or_gate(a,b,c);
input a,b;
output c;
endmodule
module 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) ;
module top();
wire `SIG(a)
wire `SIG(b)
wire `SIG(c)
`GATE(and)
`GATE(or)
`GATE(xor)
`GATE(nand)
endmodule
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
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.
module xxx();
initial
$display(" MODULE ");
endmodule
endmodule
Now compile with any of the comand.
initial
$display(" MODULE ");
endmodule
`endif
endmodule
`endif
Colourful Messages:
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);
$display("%c[7;34m",27);
$display("*********** This is in Back ground color ***********");
$display("%c[0m",27);
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.
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
EXAMPLE:
module color();
initial
begin
`display_blue(" ******** this is blue ********** ");
`display_red(" ******** this is red ********** ");
`display_green(" ******** this is green ********** "); Page | 131
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.
cpp file_name.v
NOTE: cpp cannot understand `define. Before using cpp, covert all `define to #define.
# 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