You are on page 1of 132

TESTBENCH OVERVIEW

INDEX

Page | 1
............INTRODUCTION
..................... Test Bench Overview

............LINEAR TB
..................... Linear Testbench

............FILE IO TB
..................... File I/O Based Testbench

............STATE MACHINE BASED TB

............TASK BASED TB
..................... Task And Function Based Tb

............SELF CHECKING TESTBENCH


..................... Stimulus Generator
..................... Bus Functional Models
..................... Driver
..................... Reciver
..................... Protocol Monitor
..................... Scoreboard
..................... Checker
..................... Coverage
..................... Code Coverage
..................... Functional Coverage

............VERIFICATION FLOW
..................... Planning
..................... Feature Extraction
..................... Verification Environment Architecture Plan

............CLOCK GENERATOR
..................... Timescale And Precision Enlightment

............SIMULATION
..................... Simulation Steps
..................... Macro Preprocessing
..................... Compilation (Analyzer)
..................... Elaboration
..................... Optimization
..................... Initialization
..................... Execution
..................... Simulation Process

............INCREMENTAL COMPILATION

............STORE AND RESTORE


............EVENT CYCLE SIMULATION
..................... Event Based Simulation
..................... Cycle Based Simulation

............TIME SCALE AND PRECISION


..................... Time Scale And Time Precision Page | 2
..................... $Time Vs $Realtime
..................... System Task Printtimescale
..................... System Task Timeformat

............STIMULUS GENERATION

............SYSTEM FUNCTION RANDOM A MYTH

............RACE CONDITION
..................... What Is Race Condition?
..................... Why Race Condition?
..................... When Race Is Visible?
..................... How To Prevent Race Condition?
..................... Types Of Race Condition
..................... Write-Write Race
..................... Read-Write Race
..................... More Race Example
..................... Event Terminology
..................... The Stratified Event Queue
..................... Determinism
..................... Nondeterminism
..................... Guideline To Avoid Race Condition
..................... Avoid Race Between Testbench And Dut

............CHECKER
..................... Protocol Checker
..................... Data_checker
..................... Modularization

............TASK AND FUNCTION


..................... Functions
..................... Task
..................... Task And Function Queries
..................... Constant Function
..................... Reentrant Tasks And Functions

............PROCESS CONTROL
..................... Nonblocking Task
..................... Fork/Join Recap
..................... Fork/Join None
..................... Fork/Join Any

............DISABLEING THE BLOCK


..................... Disable
..................... Goto
..................... Break
..................... Continue
............WATCHDOG

............COMPILATION N SIMULATION SWITCHS


..................... Compilation And Simulation Directives
..................... Example
Page | 3
............DEBUGGING
..................... Pass Or Fail
..................... Waveform Viewer
..................... Log File
..................... Message Control System
..................... Message Severity Levels
..................... Message Controlling Levels
..................... Passing Comments To Waveform Debugger
..................... $Display N $Strobe
..................... Who Should Do The Rtl Debugging?

............ABOUT CODE COVERAGE


..................... Types Of Coverage
..................... Code Coverage
..................... Statement Coverage /Line Coverage
..................... Block/Segment Coverage
..................... Branch / Decision / Conditional Coverage
..................... Path Coverage
..................... Expression Coverage
..................... Toggle Coverage
..................... Variable Coverage
..................... Triggering / Event Coverage
..................... Parameter Coverage
..................... Functional Coverage
..................... Fsm Coverage
..................... State Coverage
..................... Transition Coverage
..................... Sequence Coverage
..................... Tool Support
..................... Limitation Of Code Coverage

............TESTING STRATIGIES
..................... Bottom-Up
..................... Unit Level
..................... Sub-Asic Level
..................... Asic Level
..................... System Level
..................... Flat

............FILE HANDLING
..................... Fopen And Fclose
..................... Fdisplay
..................... Fmonitor
..................... Fwrite
..................... Mcd
..................... Formating Data To String
............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

............HANDLING TESTCASE FILES

............TERIMINATION

............ERROR INJUCTION
..................... Value Errors
..................... Temporal Errors
..................... Interface Error
..................... Sequence Errors

............REGISTER VERIFICATION
..................... Register Verification
..................... Register Classification
..................... Features

............PARAMETERISED MACROS

............WHITE GRAY BLACK BOX


..................... Black Box Verification
..................... White Box Verification
..................... Gray Box Verification

............REGRESSION

............TIPS
..................... How To Avoid "Module Xxx Already Defined" Error
..................... Colourful Messages
..................... Debugging Macros
INTRODUCTION

Test Bench Overview

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:

Linear TestBench approach to TestBench creation is especially bad for performance. As


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

Linear test bench for a memory model.

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

FILE IO TB

File I/O Based Testbench

Another way of getting the Stimulus is get the vectors from an external file. The external
vector file is generally formatted so that each value in the file represents either a specific
input pattern .Verilog HDL contains the $readmemb or $readmemh system tasks to do the
file read if the file data is formatted in a specific way using either binary or hexadecimal
data. TestBench is like just an interface between external vector source and DUT.
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.

EXAMPLE: verilog file


module readmemh_demo;

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

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

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

endmodule

EXAMPLE: data.txt file


234ac
23ca5
b3c34
23a4a
234ca
b3234

RESULT:

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

Reading or writing to files during simulation is costly to performance, because the


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

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

EXAMPLE: data.v file


initial
begin
Mem[0] = 234ac;
Mem[1] = 23ca5;
Mem[2] = b3c34;
Mem[3] = 23a4a;
Mem[4] = 234ca;
Mem[5] = b3234;
end

RESULT:

0:000234ac
1:00023ca5
2:000b3c34
3:00023a4a
4:000234ca
5:000b3234
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

Now lets see how to develop our scenarios:


Only 10 write operations,

initial
begin
No_of_reads = 0;
No_of_writes = 10;
end

Only 10 read operations,

initial
begin
No_of_reads = 10;
No_of_writes = 0;
end

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

Task And Function Based Tb:

Task based verification is more flexible over all the above approaches. All the operations
in are done using takes and functions. The task based BFM is extremely efficient if the
device under test performs many calculations. Each task or function focuses on one single 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

task read(input integer address,output integer data);


begin
@(posedge clock);
read_write = 0;
address = $random;
// Do some operation to get data
end
endtask

Now lets see how to develop the senarious.

1) 10 write operations.

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

2) 10 read operations

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

3) Alternate read and write operations.

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

4) Do the write and read the same location.


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

SELF CHECKING TESTBENCH

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

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

Todays functional verification flow mainly contains following steps:

Generate the stimulus vectors.


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

A test-bench is built to functionally verify the design by providing meaningful scenarios


to check that given certain input, the design performs to specification. Test bench
provides the stimulus to exercise DUT code. A self checking testbench is a intelligent
testbench which does some form of output sampling of DUT and compares the sampled
output with the expected outputs. A simulation environment is typically composed of
several types of components:
Page | 11

Stimulus Generator:

In order to test the model of some design, a verification engineer must apply test
patterns to the input ports and observe the output ports over time to decide whether the
inputs were transformed to the expected outputs. The generator component generates
input vectors. For simple memory stimulus generator generates read, write operations,
address and data to be stored in the address if its write operation. Modern generators
generate random, biased, and valid stimuli. In verilog $random does this job. The
randomness is important to achieve a high distribution over the huge space of the
available input stimuli. To this end, users of these generators intentionally under-specify
the requirements for the generated tests. It is the role of the generator to randomly fill
this gap. This mechanism allows the generator to create inputs that reveal bugs not being
searched for directly by the user. Generators also bias the stimuli toward design corner
cases to further stress the logic. Biasing and randomness serve different goals and there
are tradeoffs between them, hence different generators have a different mix of these
characteristics. Since the input for the design must be valid and many targets should be
maintained, many generators use the Constraint Satisfaction Problem technique to solve
the complex testing requirements. SystemVerilog, Vera, SystemC and Specman have "
constraints " to specify The legality of the design inputs. In verilog ,to constrain the
memory address to be between 0 to 63, {$random} % 64 is used. The model-based
generators use this model to produce the correct stimuli for the target design. The
stimulus generator should be intelligent and easily controllable.

Bus Functional Models

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

Driver

Driver is a types of BFM. The drivers translate the stimuli produced by the generator into
the actual inputs for the design under verification. Generators create inputs at a high
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:

Scoreboard is sometimes referred as storage structure. The stimulus generator generated


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

Checker:

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

Coverage:

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

Code Coverage:

Code coverage, in short, is all about how thoroughly your tests exercise your code base.
The intent of tests, of course, is to verify that your code does what it's expected to, but
also to document what the code is expected to do. Taken further, code coverage can be
considered as an indirect measure of quality -- indirect because we're talking about the
degree to what our tests cover our code, or simply, the quality of tests. In other words,
code coverage is not about verifying the end product's quality.

Statement coverage: measures the number of statements executed .


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

Functional Coverage:

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

VERIFICATION FLOW

Verification of a design usually follows the flow synopsis below.

Planning:

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

Verification planning consists, following main tasks.

1) Feature extraction from design specification.


2) Listing out Testcases.
3) Verification Environment Architecture plan.

Feature Extraction:

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

Verification Environment Architecture Plan:


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

Next phase is to build the Verification environment. Page | 14


Final phase is to verify the DUT using the environment built.

CLOCK GENERATOR

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

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

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

EXAMPLE:
always
begin
clk = 0;
forever #10 clk = ~clk;
end

Different testbenchs need different clock periods. It is beneficial to use parameters to


represent the delays, instead of hard coding them. For example, to generate a clock
starting with zero that has a 50% duty cycle, the following code can be used:

EXAMPLE:
module Tb();
reg clock;
integer no_of_clocks;

parameter CLOCK_PERIOD = 5;
initial no_of_clocks = 0;
initial clock = 1'b0; Page | 15

always #(CLOCK_PERIOD/2) clock = ~clock;

always@(posedge clock)
no_of_clocks = no_of_clocks +1 ;

initial
begin
#50000;
$display("End of simulation time is %d , total number of clocks seen is %d expected is
%d",$time,no_of_clocks,($time/5));
$finish;
end
endmodule
RESULTS:

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

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

EXAMPLE:
module Tb();
reg clock;
integer no_of_clocks;

parameter CLOCK_PERIOD = 5;

initial no_of_clocks = 0;
initial clock = 1'b0;

always #(CLOCK_PERIOD/2.0) clock = ~clock;

always@(posedge clock)
no_of_clocks = no_of_clocks +1 ;
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.

Timescale And Precision Enlightment:

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

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

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

parameter CLOCK_PERIOD = 5;
initial clock = 1'b0;
always
begin
#(CLOCK_PERIOD/3.0) clock = 1'b0;
#(CLOCK_PERIOD - CLOCK_PERIOD/3.0) clock = 1'b1;
end

initial no_of_clocks = 0;

always@(posedge clock) Page | 17


no_of_clocks = no_of_clocks +1 ;

initial
begin
#50000;
$display(" End of simulation time is %d , total number of clocks seen is %d expected is
%d",$time,no_of_clocks,($time/5));
$finish;
end
endmodule
RESULTS:

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

Now CLOCK_PERIOD/3.0 is 5/3 which is 1.666. As the time unit is 1.0ns, the delay is
1.666ns. But the precision is 100ps. So 1.666ns is rounded to 1.700ns only.
and when (CLOCK_PERIOD - CLOCK_PERIOD/3.0) is done, the delay is 3.300ns instead of
3.333.The over all time period is 5.If the clock generated is implemented without taking
proper care, this will be the biggest BUG in testbench.

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

NOTE: Simulation with `timescale 1ns/1ns is faster than `timescale 1ns/10ps


A simulation using a `timescale 10ns/10ns and with `timescale 1ns/1ns will take same
time.

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

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

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

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

EXAMPLE:
initial clock = 1'b0;

always clock = #1 ~clock;


jitter = $random() % range;
assign jittered_clock = #(jitter) clock;

With the above approace,over all clock period is increased. A better approach for clock
divider is as follows
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.

EXAMPLE:Clock multipler with N times multiplication


initial i = 0;

always @( base_clock ) begin


i = i % N;
if (i == 0) derived_clock = ~derived_clock;
i = i + 1;
end

EXAMPLE:Clock division with N times division


initial begin
derived_clock = 1'b0;
period = 10; // for initial clock
forever derived_clock = #(period/(2N)) ~ derived_clock;
end

always@(posedge base_clock)
begin
T2 = $realtime;
period = T2 - T1;
T1 = T2;
end
SIMULATION

Simulation Steps:

Simulation is defined as the process of creating a model (i.e., an abstract representation)


Page | 20
of a system in order to identify and understand those factors which control the system
and/or to predict (forecast) the future behavior of the system. The simulation model
need not reflect any understanding of the underlying technology, and the simulator need
not know that the design is intended for any specific technology.

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

Simulation Eliminates the time-consuming need for constant physical


prototyping. Simulation should be performed during ALL stages of ASIC design.

Macro Preprocessing:

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

Compilation (Analyzer)

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

Elaboration

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

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

When Simulation time is incremented, On receiving Simulation commands, a signal is


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

INCREMENTAL COMPILATION

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

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

Incremental compilation means that the compiler inspects your code, determines which
parts of the application are affected by your changes, and only recompiles the newer
files. Incremental compilation can help reduce compile time on small applications, but
you achieve the biggest gains on larger applications. The default value of the incremental 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.

TIME SCALE AND PRECISION

Time Scale And Time Precision:

RECAP:

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

For example : `timescale 1 ns / 100 ps means


time values to be read as ns and to be rounded to the nearest 100 ps.

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

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

EXAMPLE:
`timescale 10ns/10ns
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;

time delay_time = 7.721;

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

RESULTS:

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

reg i toggled at 80 in waveform debugger

EXAMPLE:
`timescale 10ns/1ns
module tim();
reg i;
initial
begin
i=0;
#7.7212;
i=1;
$display("STATEMENT 1 :: time is ",$stime);
#7.123;
$finish;
end

endmodule
module try;

time delay_time = 7.721;


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

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

reg i toggled at 77 waveform debugger

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

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

endmodule
module try;

time delay_time = 7.721;


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

RESULTS:

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

reg i toggled at 77.212 in waveform debugger


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

Now the second one (time precision) specify with which precision time values are rounded. 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.

System Task Printtimescale

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

EXAMPLE:
`timescale 1 ms / 1 us
module a_dat; 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:

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

System Task Timeformat

The $timeformat system task performs the following two functions:


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

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

Unit number Time unit Unit number Time unit


0 1 s -8 10 ns
-1 100 ms -9 1 ns
-2 10 ms -10 100 ps
-3 1 ms -11 10 ps
-4 100 us -12 1 ps
-5 10 us -13 100 fs
-6 1 us -14 10 fs
-7 100 ns -15 1 fs

Syntax : $timeformat(time unit, precision number, suffix string, and minimum field
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;

buf #10000000 (o1,in1);

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

RESULTS:

a1_dat: 0.00000 ns in1= x o1=x


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

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

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

buf #10000 (o2,in2);


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

RESULTS:

a2_dat: 0.00000 ns in2=x o2=x


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

STIMULUS GENERATION

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

EXAMPLE:
module Tb_mem();
reg clock;
reg read_write;
reg [31:0] data;
reg [31:0] address;

initial
begin
clock = 0;
forever
#10 clock = ~clock;
end

initial
begin
@(negedge clock) read_write = 1 ; data = 4;address = 1;
@(negedge clock) read_write = 1 ; data = 5;address = 2;
@(negedge clock) read_write = 1 ; data = 6;address = 3;
@(negedge clock) read_write = 1 ; data = 7;address = 4;
@(negedge clock) read_write = 1 ; data = 8;address = 5;
$finish;
end

initial
$monitor($time,"read_write = %d ; data = %d ; address =
%d;",read_write,data,address);
Page | 30
endmodule

RESULT:

20read_write =1 ; data = 4 ; address = 1;


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

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

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

EXAMPLE: verilog file


module readmemh_demo;

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

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

integer k;

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

endmodule

EXAMPLE: data.txt file


234ac
23ca5
b3c34
23a4a
234ca
b3234 Page | 31

RESULT:

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

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

SYSTEM FUNCTION RANDOM A MYTH

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

EXAMPLE:
module Tb_mem();
reg clock;
reg read_write;
reg [31:0] data;
reg [31:0] address;

initial
begin
clock = 0;
forever
#10 clock = ~clock;
end

initial
begin
repeat(5)@(negedge clock)
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:

20read_write = 0 ; data = 3230228097 ; address = 2223298057;


40read_write = 1 ; data = 112818957 ; address = 1189058957;
60read_write = 1 ; data = 2302104082 ; address = 15983361;
80read_write = 1 ; data = 992211318 ; address = 512609597;

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

EXAMPLE:
module Tb();
integer address;

initial
begin
repeat(5)
#1 address = $random;
end

initial
$monitor("address = %d;",address);

endmodule

RESULT:

address = 303379748;
address = -1064739199;
address = -2071669239;
address = -1309649309;
address = 112818957;
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:

add_3 = 7;add_2 = 7;add_1 = 8


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

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

EXAMPLE:
module Tb();
integer add;

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

RESULT:

add = 48 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:

add_1 = 00100,add_2 = 111101000000110000100100001001101011000001001


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

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

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

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

RESULT: Page | 37

num_1 = 303379746,num_2 = -1064739195


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

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

EXAMPLE:
module Tb();

integer num_1,num_2,num_3;
real r_num;

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

RESULT:

r_num = -2.071669e+03
r_num = 2641.189059e+013
r_num = 976361.598336e+01
r_num = 57645.126096e+02
r_num = 24589.097015e+0
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

Sometimes it is required to generate random numbers without repetition. The random


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

EXAMPLE:
module Tb();
integer num,i,j,index;
integer arr[9:0];
reg ind[9:0];
reg got;

initial
begin
index=0;
for(i=0;i<10;i=i+1)
begin
arr[i] = i;
ind[i] = 1;
end

for(j = 0;j<10 ;j=j+1)


begin
got = 0;
while(got == 0)
begin
index = { $random() } % 10;
if(ind[index] == 1)
begin
ind[index] = 0;
got = 1;
num = arr[index];
end
end
$write("| num=%2d |",num); Page | 40
end

end
endmodule

RESULT:

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


num= 0 || num= 3 |

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

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

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

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;

for(j = 0;j<2 ;j=j+1)


begin
if(j ==0)
$display(" seed is %d",seed);
else
$display(" No seed is given ");

for(i = 0;i < 10; i=i+1)


begin
if( j == 0)
num = { $random(seed) } % 10;
else
num = { $random() } % 10;
$write("| num=%2d |",num);
end
$display(" ");
end
end
endmodule

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:

| num= 8 | seed is -1844104698


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

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

| num= 8 | seed is -1844104698


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

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

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

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

RESULTS:

| num= 7 | seed is -2034665166


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

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

seed is 10 after values 357336117


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

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

$random
$dist_chi_square
$dist_erlang
$dist_exponential
$dist_normal
$dist_poisson
$dist_t
$dist_uniform
All parameters to the system functions are integer values. For the exponential , Poisson ,
chi-square , t , and erlang functions, the parameters mean, degree of freedom, and
k_stage must be greater than 0 .

$dist_uniform(seed, min, max) is similar to min + {$random(seed)}%(max-min+1),the Page | 48


difference is that in $dist_uniform,the distribution is uniform. $dist_uniform returns a
number between min and max. In the $dist_uniform function, the start and end
parameters are integer inputs that bound the values returned. The start value should be
smaller than the end value.

The mean parameter, used by $dist_normal, $dist_exponential, $dist_poisson, and


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

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

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

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

RESULTS:

num_1 = 20,num_2 = 50
num_1 = 23,num_2 = 55
num_1 = 22,num_2 = 54
num_1 = 25,num_2 = 51
num_1 = 23,num_2 = 55
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:

num_1 = 20,num_2 = 22,seedd = 690691 seedr = 690691


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

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

As I mentioned ,$dist_uniform(seed, min, max) is similar to min + {$random(seed)}%(max-


min+1). "similar" means they have some common functionality. $dist_uniform is having
uniform distribution, $random for that range, is also uniformly distributed. Fallowing
example ,demonstrates that $dist_uniform and $random are uniformly distributed.

EXAMPLE:
module Tb();
integer num,seed;
integer num_20,num_21,num_22,num_23,num_24,num_25;

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

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


1020

EXAMPLE:
module Tb();
integer num;
integer num_20,num_21,num_22,num_23,num_24,num_25;

initial
begin
seed = 10;
num_20 = 0;num_21 = 0;num_22 = 0;num_23 = 0;num_24 = 0;num_25 =0;
repeat(6000)
begin

num = 20 +( {$random() } %6 );
if(num == 20 ) 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:

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

As I mentioned ,$dist_uniform(seed, min, max) is similar to min + {$random(seed)}%(max-


min+1). "similar" means they have some difference. The difference is that they generate
different sequence.

EXAMPLE:
module Tb();
integer num_1,num_2,seedd,seedr;

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

num_1 = 20,num_2 = 22
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

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

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

EXAMPLE:
module Tb();
integer seed;
reg [1:0] var_1,var_2,var3,var4;
integer num_2,num_3,num_1,num_0;
integer cou_2,cou_3,cou_1,cou_0;

initial
begin
seed = 10;
num_2 = 0;num_3= 0;num_1= 0;num_0= 0;
cou_2= 0;cou_3= 0;cou_1= 0;cou_0= 0;

repeat(40000)
begin

var_1 = $random();
var3 = $random();
var4 = $random();
var_2 = $random();
if(var_1 == 0 )
num_0 = num_0 + 1;
if(var_1 == 1 )
num_1 = num_1 + 1;
if(var_1 == 2 )
num_2 = num_2 + 1;
if(var_1 == 3 )
num_3 = num_3 + 1;
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:

num_2 = 9984;num_3= 10059;num_1= 10002;num_0= 9955;


cou_2= 10060;cou_3= 9934;cou_1= 10072;cou_0= 9934;

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

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

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

Don't expect that the same sequence is generated on all the simulators. They are only
following same algorithm. The reason is, race condition. Look at the following example,
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.

What Is Race Condition?

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

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

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

The simulator is correct in displaying either a 1 or a 0. The assignment of 0 to q enables an


update event for p. The simulator may either continue or execute the $display system task
or execute the update for p, followed by the $display task.
Then guess what can the value of p ?
Simulate the above code in your simulator. Then simulate the following code . Statement
"assign p = q;" is changed to end of the module.
EXAMPLE
module race();
wire p;
reg q;
Page | 56
assign p = q;

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

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

Why Race Condition?

To describe the behavior of electronics hardware at varying levels of abstraction, Verilog


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

Look at following code. Is there any race condition?

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

Now if you swap these two lines:

EXAMPLE
initial
begin
out <= in;
in = 1;
end
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.

When Race Is Visible?

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

When different simulators are used to run the same code.


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

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

How To Prevent Race Condition?

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

Types Of Race Condition

Here we will see race condition closely.


Types of race condition

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

More Race Example:

1) Function calls

EXAMPLE:
function incri();
begin
pkt_num = pkt_num + 1;
end
endfunction
always @(...)
sent_pkt_num = incri();

always @(...)
sent_pkt_num_onemore = incri();
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.

5) Declaration and initial

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

6)Testbench DUT race condition.


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

EXAMPLE:
module DUT(); Page | 60
input d;
input clock;
output q;

always @(posedge clock)


q = d;

endmodule

module testbench();

DUT dut_i(d,clk,q);

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

The above example has write read race condition.

Event Terminology:

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

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

Example : PLI tasks

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

Determinism

This standard guarantees a certain scheduling order.

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

2) Non blocking assignments shall be performed in the order the statements were
executed.
Consider the following example:

initial begin
a <= 0;
a <= 1;
end 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.

Guideline To Avoid Race Condition

(A). Do not mix blocking and nonblocking statements in same block.


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

Avoid Race Between Testbench And Dut

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

Most Synchronous DUT works on the posedge of clock. If the Testbench is also taking the
same reference, then we may unconditionally end in race condition. So it<92>s better to 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

Protocol checking is the mechanism we use to verify IO buses functionality. Protocol


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

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

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

Protocol checks can be divided mainly into 3 categories:


1) Duration checks
2) Condition checks
3) Temporal or Sequence Checks.

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

Data_checker

Data checker verifies the correctness of the device output. Data checking is based on
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.

In order to reuse components of one environment to another, it is important to separate


functionality into specific entities. This separation allows the work to be distributed to
multiple people. Task separation is very similar to and goes hand in hand with
modularization. Another benefit of task separation is having different people
understanding the functionality.

TASK AND FUNCTION

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

Functions

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

EXAMPLE:function

module function_calling(a, b,c);

input a, b ;
output c;
wire c;

function myfunction;
input a, b;
begin
myfunction = (a+b); Page | 65
end
endfunction

assign c = myfunction (a,b);

endmodule

Task

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

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

initial red = off;


initial amber = off;
initial green = off;

always begin // sequence to control the lights.


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

always begin // waveform for the clock.


#100 clock = 0;
#100 clock = 1;
end
endmodule // traffic_lights.

Task And Function Queries:

Why a function cannot call a task?


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

Why tasks are not synthesized?


Wrong question! Tasks can be synthesized if it doesn't consume time.

Why a function should return a value?


There is no strong reason for this in Verilog. This restriction is removed in SystemVerilog.

Why a function should have at least one input?


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

Why a task cannot return a value?


If tasks can return values, then Lets take a look at the following example.

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

Why a function cannot have delays?


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

Why disable statements are not allowed in functions?


If disable statement is used in function, it invalids the function and its return value. So Page | 67
disable statements are not allowed in function.

Constant Function:

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

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

//define the clogb2 function


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

Reentrant Tasks And Functions:

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

EXAMPLE:
module auto_task();

task automatic disp;


input integer a;
input integer d; Page | 68
begin
#(d) $display("%t d is %d a is %d", $time,d,a);
end
endtask

initial
#10 disp(10,14);

initial
#14 disp(23,18);

initial
#4 disp(11,14);

initial
#100 $finish;

endmodule
RESULTS:

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

EXAMPLE:
module tryfact;
// define the function
function automatic integer factorial;
input [31:0] operand;
integer i;
if (operand >= 2)
factorial = factorial (operand - 1) * operand;
else
factorial = 1;
endfunction
// test the function
integer result;
integer n;
initial begin
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:

statement inside blocking task at 10


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

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

Statement after blocking_task at 6


statement inside blocking task at 11

Fork/Join Recap:

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

EXAMPLE:

module fork_join();
integer r ;
initial
fork
#50 r = 35;
#100 r = 24;
#150 r = 00; 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

Statement after blocking_task at 6


statement inside blocking task_1 at 11
statement inside blocking task_2 at 21

Fork/Join Any

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

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

initial
begin
#1 ->e;
@(ee);
$display(" Statement after blocking_task at %t ",$time);
#40 $finish;
end

always@(e)
begin
fork
begin blocking_task_1(); -> ee;end
begin blocking_task_2(); -> ee;end
join
end

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

task blocking_task_2();
begin
#20;
$display(" statement inside blocking task_2 at %t",$time);
end
endtask
endmodule Page | 74
RESULTS

statement inside blocking task_1 at 11


Statement after blocking_task at 11
statement inside blocking task_2 at 21

DISABLEING THE BLOCK

Disable

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

This example illustrates how a block disables itself.

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

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

EXAMPLE:
task abc();
begin : name
:
:
:
if( something happened)
disable name;
:
:
:
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

Compilation And Simulation Directives:

Conditional Compilation directive switches vs Simulation directive switches

Verilog has following conditional compiler directives.

`ifdef
`else
`elsif
`endif
`ifndef
The `ifdef compiler directive checks for the definition of a text_macro_name. If the
text_macro_name is defined, then the lines following the `ifdef directive are included. If
the text_macro_name is not defined and an `else directive exists, then this source is
compiled. The `ifndef compiler directive checks for the definition of a text_macro_name.
If the text_macro_name is not defined, then the lines following the `ifndef directive are 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

Compile with +define+TYPE_1


Then simulate,result is

RESULT:

TYPE_1 message

Compile with +define+TYPE_2


Then simulate,result is

RESULT:

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

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

Simulate with +TYPE_1

RESULT:

TYPE_1 message

Simulate with +TYPE_2


Then simulate,result is

RESULT:

TYPE_2 message

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


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

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

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

Example

module valuetest();

integer i;
real r;
reg [11:0] v;
reg [128:0] s;

initial
begin
if($value$plusargs("STRING=%s",s))
$display(" GOT STRING ");
if($value$plusargs("INTG=%d",i))
$display(" GOT INTEGER ");
if($value$plusargs("REAL=%f",r))
$display(" GOT REAL ");
if($value$plusargs("VECTOR=%b",v))
$display(" GOT VECTOR ");

$display( " String is %s ",s);


$display(" Integer is %d ",i);
$display(" Realnum is %f ",r);
$display(" Vector is %b ",v); Page | 82
end

endmodule

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

RESULTS:

GOT STRING
GOT INTEGER
GOT REAL
GOT VECTOR
String is rrf
Integer is 123
Realnum is 1.320000e+00
Vector is 000000010101

DEBUGGING

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

The basic steps in debugging are:

--- Recognize that a bug exists


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

Pass Or Fail
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 ;

// final block--to end simulation


task finish();
begin
#10; // so delay to make sure that counter increments for the last triggered error.
if( error == 0)
$dsplay("************ TEST PASSED ***************");
else
$dsplay("************ TEST FAILED ***************");

end
endtask
endmodule

Waveform Viewer: Page | 84

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

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


b) Extended: to represent variable changes in all states and strength information.

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

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

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

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

EXAMPLE:

initial
$dumpfile ("my_dump_file");

$dumpvar //Dump all the variables


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

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

Due to dumping the value changes to a file,there is simulation over head. Not all the time
the dumping is required. So controlling mechanism to dump VCD files needs to be
implemented.
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.

$monitor displays when any of the arguments values change.

Message Control System:

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

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

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

Message Severity Levels:


Following are the 4 severity levels of messaging:

INFO:
The messages is used to convey simple information.
WARNING: 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:

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


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

Message Controlling Levels

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

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

`define INFO $write("INFO : %5t :%m:",$time); $display


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

module msg();

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

When compilation is done without +define+DEBUG


RESULTS:

INFO : 10 :msg:UR MESSAGE GOES HEAR Page | 87


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

When compilation is done with +define+DEBUG


RESULTS:

INFO : 10 :msg:UR MESSAGE GOES HEAR


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

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

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

EXAMPLE:

`define INFO $write("INFO : %0t :%m:",$time); $display


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

module top();
reg debug = 0;

initial
if($test$plusargs("DEBUG"))
#0 debug = 1;

initial
begin
#10;
`INFO("UR MESSAGE GOES HEAR");
`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:

INFO : 10 :top:UR MESSAGE GOES HEAR


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

When simulation is done with +DEBUG


RESULTS:

INFO : 10 :top:UR MESSAGE GOES HEAR


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

Passing Comments To Waveform Debugger

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

EXAMPLE:
module pass_comments();
reg [79 : 0] Comment; // holds 10 characters.

reg [7:0] status;

initial
begin
#10 status = 8'b10101010;
comment = Preambel;
#10 status = 8'b10101011;
comment = Startofpkt;
end
endmodule

The reg " Comment " holds string. This strings can be viewed in waveform debugger.
$Display N $Strobe

According to scheduling semantics of verilog, $display executes before the nonblocking


statements update LHS. Therefore if $display contains LHS variable of nonblocking
assignment, the results are not proper. The $strobe command shows updated values at the Page | 89
end of the time step after all other commands, including nonblocking assignments, have
completed.

EXAMPLE:
module disp_stro;
reg a;

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

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

Who Should Do The Rtl Debugging?

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

ABOUT CODE COVERAGE


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

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

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

Types Of Coverage

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

Code Coverage:

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

Statement Coverage /Line Coverage:

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

always @(posedge clk)


begin
if (a > b) statement 1
begin
y = a and b; statement 2
z = a or b; statement 3
end
if (a < b)
begin
y = a xor b;
z = a xnor b;
end
if (a == b)
begin
y = not b;
z = a % b;
end Page | 91
end

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

Block/Segment Coverage:

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

Branch / Decision / Conditional Coverage:


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

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 tool should have following features:


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

Coverage does not know anything about what design supposed to do. There is no way to
find what is missing in the code. It can only tell quality of the implementation. Sometime 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?

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

TESTING STRATIGIES

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

Bottom-Up

Bottom-up approach can be done at 4 levels.


1)Unit (Module-level) Level
2)Sub-ASIC (Functional Blocks) Level
3)ASIC Level
4)System Level

Unit Level

In unit level verification, a module is verified in its own test environment to prove that
the logic, control, and data paths are functionally correct. The goal of module level
verification is to ensure that the component/unit being tested conforms to its
specifications and is ready to be integrated with other subcomponents of the product. In
unit level verification good coverage percentage is expected.

Sub-Asic Level

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

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

System Level Page | 98

Flat

In this ,verification approaches by combining interface models and transaction streams to


test the Complete ASIC.

FILE HANDLING

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

Functions and tasks that open and close files


Tasks that output values into files
Tasks that output values into variables
Tasks and functions that read values from files and load into variables or memories

Fopen And Fclose

$fopen and $fclose

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

EXAMPLE
// file open close example
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

value of mcd before opening the file xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


value of mcd after opening the file 00000000000000000000000000000010

Then how can we check that file is closed or not??


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

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

Number is 00010010000101010011010100100100 Page | 101


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

EXAMPLE c. $displayo
module fopenclose();
integer mcd,number;
initial
begin
mcd = $fopen("temp.txt"); // mcd = multi_channel_descriptor
repeat(7)
begin
number = $random;
$fdisplayo(mcd, "Number is ", number);
end
$fclose(mcd);
end
endmodule
RESULT

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

EXAMPLE. $displayh
module fopenclose();
integer mcd,number;
initial
begin
mcd = $fopen("temp.txt"); // mcd = multi_channel_descriptor
repeat(7)
begin
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

Error during elaboration.

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

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

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

$fwrite, $fwriteb, $fwriteo, $fwriteh

Like $display; $write also have counterparts. They also write in decimal,binary, octal and
hexadecimal.
EXAMPLE
// file open close example with $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

value of mcd1 before opening the file xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


value of mcd2 before opening the file xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
value of mcd1 after opening the file 00000000000000000000000000000010
value of mcd2 after opening the file 00000000000000000000000000000100
in file pqr.txt
Pointer is 12153524 Pointer is 8484d609 Pointer is 06b97b0d
Pointer is b2c28465 Pointer is 00f3e301 Pointer is 3b23f176
Pointer is 76d457ed

In file xyz.txt

Number is 37777777767 Number is 37777777767 Number is


00000000007 Number is 37777777774 Number is 00000000011 Number
is 00000000007 Number is 00000000002
Mcd

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

Formating Data To String

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

The system task $sformat is similar to the system task $swrite, with a one major
difference. Unlike the display and write family of output system tasks, $sformat always
interprets its second argument, and only its second argument as a format string. This
format argument can be a static string, such as "data is %d" , or can be a reg variable
whose content is interpreted as the format string. No other arguments are interpreted as
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

A semaphore is a type of Interposes communication resource used for synchronization and


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

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

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

EXAMPLE:
module sema();

integer keys;

initial
keys = 1;

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

GOT THE KEY : GET SET GO :process 1


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

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:

GOT THE KEY : GET SET GO :process 1


KEY IS NOT AVAILABLE : LEAVING WITHOUT WAITING FOR KEYS : process 2
IM not interested to wait
PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG
PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG
PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG
PROCESS 1 GOT KEYS : OTHERS CANT WRITE MESSAGE TO LOG
PROCESS 1 gave the key back
After some days, they got new car to home. Now they have two cars, at once 2 members
can go on drive. Looking at the following code. The keys are initialized to 2. Two
processes are running at once.

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


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

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

endmodule

RESULTS:

GOT THE KEY : GET SET GO :process 1


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

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

These tests Verify how the interrupt logic is working.

Interface Tests

These tests verify the Interface functionality.

Functional Tests

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

Error Tests

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

Golden Tests

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

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

HANDLING TESTCASE FILES


Page | 114

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.

During compilation just use the following commands

for testcase_1.v file


comile_command top.v testcase_1.v

for testcase_2.v file


comile_command top.v testcase_2.v

EXAMPLE: top.v

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

// some tasks

task write()
begin
// some logic
end
endtask

task read()
begin
// some logic
end
endtask

// TEST case instance

TEST tst();

end
EXAMPLE: testcase_1.v

// Do 10 write operations

module TEST(); Page | 116

initial
repeat(10)
top.write();

endmodule
EXAMPLE: testcase_2.v

// Do 10 read operations

module TEST();

initial
repeat(10)
top.read();

endmodule

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

During compilation just use the following commands

for testcase_1.v file


cp testcase_1 test.v
comile_command top.v test.v

for testcase_2.v file


cp testcase_2 test.v
comile_command top.v test.v

EXAMPLE: top.v

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

// some tasks
task write()
begin
// some logic
end
endtask
Page | 117
task read()
begin
// some logic
end
endtask

// incule test.v file

`include test.v

end

EXAMPLE: testcase_1.v

// Do 10 write operations

initial
repeat(10)
top.write();

EXAMPLE: testcase_2.v

// Do 10 read operations

initial
repeat(10)
top.read();

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

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

During compilation just give following command

cat testcase_1.v > test.v


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

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

// incule test.v file

`include test.v

end

EXAMPLE: testcase_1.v

// Do 10 write operations

repeat(10)
top.write();

EXAMPLE: testcase_2.v

// Do 10 read operations

repeat(10)
top.read();

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

EXAMPLE: INTERMEDIATE FILE test.v

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

TERIMINATION

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

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

EXAMPLE:

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

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


$finish;
end Page | 120
endtask

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

`ifndef CONTINUE_ON_ERROR
terminate();
`endif
end

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

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

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

ERROR INJUCTION

To verify error detection, reporting, and recovery features of the DUT, an error injection
mechanism must be in place in testbench to generate error scenarios. The objective is to
ensure that the errors are handled correctly. This is accomplished by introducing internal
monitoring mechanisms. The simulation environment integrates a structure to randomly
set the errors and verify that each error condition is handled properly.
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

Errors like acknowledgement should come after 4 cycles of request.

Interface Error

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

Sequence Errors

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

REGISTER VERIFICATION

Register Verification

Todays complex chips has thousands of register to configure the chip and to communicate
the status to software. These registers play a complex role in the chip operation So a test
bench should verify these registers properly. Verification of these registers is tedious. As
there are thousands of registers in a chip, the testbench should have a handy hooks to
access these registers. Implementing testbench components for these registers is not one
time job. Most designs change their register specification during the design development.
So a very flexible testbench component should be available to satisfy these needs. When I
was working for Ample, we had a script which generates testbench component for these
registers. Register specification is input to these script. So when ever register file is
changed, Just run the script, we don't need to change verilog module for these changes.
These scripts can be used across modules, across projects and across companies also.
There are some EDA tools just to do this job. I believe that a proper homemade script has
better control then getting it from some EDA guy and you know home made scripts are life
time free.
Register Classification:

Registers can be mainly classified in to these categories


1) Configuration Registers.
2) Status Registers.
3) Mask Registers. Page | 122
4) Interrupt Registers(makeable and nonmaskable).

Features:

What are the features that this testbench component should support?

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

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

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

4) Methods should be provided for read or write operation to dut registers using name and
also address. Named methods are handy and readable mainly from test cases. While
address based methods are good while writing to bulk locations( using for loop etc...).

5) Every register in Testbench may have these information.


// Comments which describes the register information.
Address of register.
Offset of register.
Width of register.
Reset value.
Access permissions.
Register value.
Register name as string.( Name should be self descriptive and same as in DUT. This string
is used in printing while debugging.)

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

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

Write random task:


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

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

Check task:
Check task compares the DUT and shadow registers. Care should be taken while using back
door access, as they are not cycle accurate. Config registers are compared for what is
configured and what is in the register. Interrupt and status registers are compared with
what is in DUT with the expected values.

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

Fallowing are possible types of permissions:


read/write
read only
write only
read only, write can be by the design
clear on read
automatically set to 1 by design.
Automatically set to 0 by design.
Readable and settable by writing 1
Readable and clearable by writing 1
By default the type of permission is read/write. If you are using any scripts, if you don't
mention any permission, then it should be considered as read/write.

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

Look at the example.


There are four modules and_gate,or_gate,xor_gate,nand_gate. They are instantiated in
top modules.

Each module has 2 inputs a,b and one output c. Instance port are connected wires which
are prefixed with getname to signal name.
Example

and_gate a_g (.a(and_a), .b(and_b), .c(and_c) );

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

The following is what a novice engineer will do.

CODE:

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

and_gate a_g (.a(and_a), .b(and_b), .c(and_c) );


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

endmodule

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

module xor_gate(a,b,c); Page | 125


input a,b;
output c;
endmodule

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

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

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

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

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

To use this for the above discussed example,

First step is declare a parameterized macro.


Second step is just use macro instance.
CODE:
`define GATE(M) M\
_gate M\
_g (.a(M\
_a), .b(M\
_b), .c(M\ Page | 126
_c));

`define SIG and_\


S, or_\
S,xor_\
S,nand_\
S; \

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

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

endmodule

WHITE GRAY BLACK BOX

Black Box Verification

Black Box verification refers to the technique of verification


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

White Box Verification


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

Gray Box Verification


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

REGRESSION

Regression is re-running previously run tests and checking whether previously fixed faults
have re-emerged. New bugs may come out due to new changes in RTL or DUT to
unmasking of previously hidden bugs due to new changes. Each time time,when design is
changed, regression is done. One more important aspect of regression is testing by
generation new vectors. Usually the seed to generate stimulus is the system time.
Whenever a regression is done, it will take the current system time and generate new
vectors than earlier tested. This way testbench can reach corners of DUT.

TIPS

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

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

EXAMPLE: xxx.v file

module xxx();

initial
$display(" MODULE ");

endmodule

EXAMPLE: yyy.v file


`include "xxx.v"
module yyy()

endmodule
Now compile with any of the comand.

compile_ur_command xxx.v yyy.v


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

EXAMPLE: xxx.v file


`ifndef XXX
`define XXX
module xxx();

initial
$display(" MODULE ");

endmodule
`endif

EXAMPLE: yyy.v file


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

endmodule
`endif

Now compile with any of the command.

compile_ur_command xxx.v yyy.v


compile_ur_command xxx.v yyy.v yyy.v

You will not see any compilation error.

Colourful Messages:
Page | 129

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

end Page | 130


endmodule

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

Lets see how to get different colors and font format.


The message to be printed is ("%c[TYPE;COLOURm",27);.

TYPE specifies how the message should be?

1 set bold
2 set half-bright (simulated with color on a color display)
4 set underscore (simulated with color on a color display)
5 set blink
7 set reverse video

COLOR specifies the message color.

30 set black foreground


31 set red foreground
32 set green foreground
33 set brown foreground
34 set blue foreground
35 set magenta foreground
36 set cyan foreground
37 set white foreground

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

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


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

Use the macros instead of $display().

EXAMPLE:
module color();
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.

Just use command

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

Output of the above code using cpp preprocessor is

RESULTS Page | 132

# 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

You might also like