In a previous article, concepts and components of a simple testbench was discussed. Let us look at a practical SystemVerilog testbench example with all those verification components and how concepts in SystemVerilog has been used to create a reusable environment.
Design
// Note that in this protocol, write data is provided
// in a single clock along with the address while read
// data is received on the next clock, and no transactions
// can be started during that time indicated by "ready"
// signal.
module reg_ctrl
# (
parameter ADDR_WIDTH = 8,
parameter DATA_WIDTH = 16,
parameter DEPTH = 256,
parameter RESET_VAL = 16'h1234
)
( input clk,
input rstn,
input [ADDR_WIDTH-1:0] addr,
input sel,
input wr,
input [DATA_WIDTH-1:0] wdata,
output reg [DATA_WIDTH-1:0] rdata,
output reg ready);
// Some memory element to store data for each addr
reg [DATA_WIDTH-1:0] ctrl [DEPTH];
reg ready_dly;
wire ready_pe;
// If reset is asserted, clear the memory element
// Else store data to addr for valid writes
// For reads, provide read data back
always @ (posedge clk) begin
if (!rstn) begin
for (int i = 0; i < DEPTH; i += 1) begin
ctrl[i] <= RESET_VAL;
end
end else begin
if (sel & ready & wr) begin
ctrl[addr] <= wdata;
end
if (sel & ready & !wr) begin
rdata <= ctrl[addr];
end else begin
rdata <= 0;
end
end
end
// Ready is driven using this always block
// During reset, drive ready as 1
// Else drive ready low for a clock low
// for a read until the data is given back
always @ (posedge clk) begin
if (!rstn) begin
ready <= 1;
end else begin
if (sel & ready_pe) begin
ready <= 1;
end
if (sel & ready & !wr) begin
ready <= 0;
end
end
end
// Drive internal signal accordingly
always @ (posedge clk) begin
if (!rstn) ready_dly <= 1;
else ready_dly <= ready;
end
assign ready_pe = ~ready & ready_dly;
endmodule
Transaction Object
class reg_item;
// This is the base transaction object that will be used
// in the environment to initiate new transactions and
// capture transactions at DUT interface
rand bit [7:0] addr;
rand bit [15:0] wdata;
bit [15:0] rdata;
rand bit wr;
// This function allows us to print contents of the data packet
// so that it is easier to track in a logfile
function void print(string tag="");
$display ("T=%0t [%s] addr=0x%0h wr=%0d wdata=0x%0h rdata=0x%0h",
$time, tag, addr, wr, wdata, rdata);
endfunction
endclass
Driver
// The driver is responsible for driving transactions to the DUT
// All it does is to get a transaction from the mailbox if it is
// available and drive it out into the DUT interface.
class driver;
virtual reg_if vif;
event drv_done;
mailbox drv_mbx;
task run();
$display ("T=%0t [Driver] starting ...", $time);
@ (posedge vif.clk);
// Try to get a new transaction every time and then assign
// packet contents to the interface. But do this only if the
// design is ready to accept new transactions
forever begin
reg_item item;
$display ("T=%0t [Driver] waiting for item ...", $time);
drv_mbx.get(item);
item.print("Driver");
vif.sel <= 1;
vif.addr <= item.addr;
vif.wr <= item.wr;
vif.wdata <= item.wdata;
@ (posedge vif.clk);
while (!vif.ready) begin
$display ("T=%0t [Driver] wait until ready is high", $time);
@(posedge vif.clk);
end
// When transfer is over, raise the done event
vif.sel <= 0;
->drv_done;
end
endtask
endclass
Monitor
// The monitor has a virtual interface handle with which it can monitor
// the events happening on the interface. It sees new transactions and then
// captures information into a packet and sends it to the scoreboard
// using another mailbox.
class monitor;
virtual reg_if vif;
mailbox scb_mbx; // Mailbox connected to scoreboard
task run();
$display ("T=%0t [Monitor] starting ...", $time);
// Check forever at every clock edge to see if there is a
// valid transaction and if yes, capture info into a class
// object and send it to the scoreboard when the transaction
// is over.
forever begin
@ (posedge vif.clk);
if (vif.sel) begin
reg_item item = new;
item.addr = vif.addr;
item.wr = vif.wr;
item.wdata = vif.wdata;
if (!vif.wr) begin
@(posedge vif.clk);
item.rdata = vif.rdata;
end
item.print("Monitor");
scb_mbx.put(item);
end
end
endtask
endclass
Scoreboard
// The scoreboard is responsible to check data integrity. Since the design
// stores data it receives for each address, scoreboard helps to check if the
// same data is received when the same address is read at any later point
// in time. So the scoreboard has a "memory" element which updates it
// internally for every write operation.
class scoreboard;
mailbox scb_mbx;
reg_item refq[256];
task run();
forever begin
reg_item item;
scb_mbx.get(item);
item.print("Scoreboard");
if (item.wr) begin
if (refq[item.addr] == null)
refq[item.addr] = new;
refq[item.addr] = item;
$display ("T=%0t [Scoreboard] Store addr=0x%0h wr=0x%0h data=0x%0h", $time, item.addr, item.wr, item.wdata);
end
if (!item.wr) begin
if (refq[item.addr] == null)
if (item.rdata != 'h1234)
$display ("T=%0t [Scoreboard] ERROR! First time read, addr=0x%0h exp=1234 act=0x%0h",
$time, item.addr, item.rdata);
else
$display ("T=%0t [Scoreboard] PASS! First time read, addr=0x%0h exp=1234 act=0x%0h",
$time, item.addr, item.rdata);
else
if (item.rdata != refq[item.addr].wdata)
$display ("T=%0t [Scoreboard] ERROR! addr=0x%0h exp=0x%0h act=0x%0h",
$time, item.addr, refq[item.addr].wdata, item.rdata);
else
$display ("T=%0t [Scoreboard] PASS! addr=0x%0h exp=0x%0h act=0x%0h",
$time, item.addr, refq[item.addr].wdata, item.rdata);
end
end
endtask
endclass
Environment
// The environment is a container object simply to hold all verification
// components together. This environment can then be reused later and all
// components in it would be automatically connected and available for use
// This is an environment without a generator.
class env;
driver d0; // Driver to design
monitor m0; // Monitor from design
scoreboard s0; // Scoreboard connected to monitor
mailbox scb_mbx; // Top level mailbox for SCB <-> MON
virtual reg_if vif; // Virtual interface handle
// Instantiate all testbench components
function new();
d0 = new;
m0 = new;
s0 = new;
scb_mbx = new();
endfunction
// Assign handles and start all components so that
// they all become active and wait for transactions to be
// available
virtual task run();
d0.vif = vif;
m0.vif = vif;
m0.scb_mbx = scb_mbx;
s0.scb_mbx = scb_mbx;
fork
s0.run();
d0.run();
m0.run();
join_any
endtask
endclass
Test
// an environment without the generator and hence the stimulus should be
// written in the test.
class test;
env e0;
mailbox drv_mbx;
function new();
drv_mbx = new();
e0 = new();
endfunction
virtual task run();
e0.d0.drv_mbx = drv_mbx;
fork
e0.run();
join_none
apply_stim();
endtask
virtual task apply_stim();
reg_item item;
$display ("T=%0t [Test] Starting stimulus ...", $time);
item = new;
item.randomize() with { addr == 8'haa; wr == 1; };
drv_mbx.put(item);
item = new;
item.randomize() with { addr == 8'haa; wr == 0; };
drv_mbx.put(item);
endtask
endclass
Interface
// The interface allows verification components to access DUT signals
// using a virtual interface handle
interface reg_if (input bit clk);
logic rstn;
logic [7:0] addr;
logic [15:0] wdata;
logic [15:0] rdata;
logic wr;
logic sel;
logic ready;
endinterface
Testbench Top
// Top level testbench contains the interface, DUT and test handles which
// can be used to start test components once the DUT comes out of reset. Or
// the reset can also be a part of the test class in which case all you need
// to do is start the test's run method.
module tb;
reg clk;
always #10 clk = ~clk;
reg_if _if (clk);
reg_ctrl u0 ( .clk (clk),
.addr (_if.addr),
.rstn(_if.rstn),
.sel (_if.sel),
.wr (_if.wr),
.wdata (_if.wdata),
.rdata (_if.rdata),
.ready (_if.ready));
initial begin
new_test t0;
clk <= 0;
_if.rstn <= 0;
_if.sel <= 0;
#20 _if.rstn <= 1;
t0 = new;
t0.e0.vif = _if;
t0.run();
// Once the main stimulus is over, wait for some time
// until all transactions are finished and then end
// simulation. Note that $finish is required because
// there are components that are running forever in
// the background like clk, monitor, driver, etc
#200 $finish;
end
// Simulator dependent system tasks that can be used to
// dump simulation waves.
initial begin
$dumpvars;
$dumpfile("dump.vcd");
end
endmodule
There are two types of timing controls in Verilog - delay and event expressions. The delay control is just a way of adding a delay between the time the simulator encounters the statement and when it actually executes it. The event expression allows the statement to be delayed until the occurrence of some simulation event which can be a change of value on a net or variable (implicit event) or an explicitly named event that is triggered in another procedure.
Simulation time can be advanced by one of the following methods.
Gates and nets that have been modeled to have internal delays also advance simulation time.
Delay Control
If the delay expression evaluates to an unknown or high-impedance value it will be interpreted as zero delay. If it evaluates to a negative value, it will be interpreted as a 2's complement unsigned integer of the same size as a time variable.
`timescale 1ns/1ps
module tb;
reg [3:0] a, b;
initial begin
{a, b} <= 0;
$display ("T=%0t a=%0d b=%0d", $realtime, a, b);
#10;
a <= $random;
$display ("T=%0t a=%0d b=%0d", $realtime, a, b);
#10 b <= $random;
$display ("T=%0t a=%0d b=%0d", $realtime, a, b);
#(a) $display ("T=%0t After a delay of a=%0d units", $realtime, a);
#(a+b) $display ("T=%0t After a delay of a=%0d + b=%0d = %0d units", $realtime, a, b, a+b);
#((a+b)*10ps) $display ("T=%0t After a delay of %0d * 10ps", $realtime, a+b);
#(b-a) $display ("T=%0t Expr evaluates to a negative delay", $realtime);
#('h10) $display ("T=%0t Delay in hex", $realtime);
a = 'hX;
#(a) $display ("T=%0t Delay is unknown, taken as zero a=%h", $realtime, a);
a = 'hZ;
#(a) $display ("T=%0t Delay is in high impedance, taken as zero a=%h", $realtime, a);
#1ps $display ("T=%0t Delay of 10ps", $realtime);
end
endmodule
Note that the precision of timescale is in 1ps and hence $realtime
is required to display the precision value for the statement with a delay expression (a+b)*10ps.
xcelium> run T=0 a=x b=x T=10000 a=0 b=0 T=20000 a=4 b=0 T=24000 After a delay of a=4 units T=29000 After a delay of a=4 + b=1 = 5 units T=29050 After a delay of 5 * 10ps T=42050 Expr evaluates to a negative delay T=58050 Delay in hex T=58050 Delay is unknown, taken as zero a=x T=58050 Delay is in high impedance, taken as zero a=z T=58051 Delay of 10ps xmsim: *W,RNQUIE: Simulation is complete.
Event Control
Value changes on nets and variables can be used as a synchronization event to trigger execution other procedural statements and is an implicit event. The event can also be based on the direction of change like towards 0 which makes it a negedge
and a change towards 1 makes it a posedge
.
- A
negedge
is when there is a transition from 1 to X, Z or 0 and from X or Z to 0 - A
posedge
is when there is a transition from 0 to X, Z or 1 and from X or Z to 1
A transition from the same state to the same state is not considered as an edge. An edge event like posedge or negedge can be detected only on the LSB of a vector signal or variable. If an expression evaluates to the same result it cannot be considered as an event.
module tb;
reg a, b;
initial begin
a <= 0;
#10 a <= 1;
#10 b <= 1;
#10 a <= 0;
#15 a <= 1;
end
// Start another procedural block that waits for an update to
// signals made in the above procedural block
initial begin
@(posedge a);
$display ("T=%0t Posedge of a detected for 0->1", $time);
@(posedge b);
$display ("T=%0t Posedge of b detected for X->1", $time);
end
initial begin
@(posedge (a + b)) $display ("T=%0t Posedge of a+b", $time);
@(a) $display ("T=%0t Change in a found", $time);
end
endmodule
ncsim> run T=10 Posedge of a detected for 0->1 T=20 Posedge of b detected for X->1 T=30 Posedge of a+b T=45 Change in a found ncsim: *W,RNQUIE: Simulation is complete.
Named Events
The keyword event
can be used to declare a named event which can be triggered explicitly. An event
cannot hold any data, has no time duration and can be made to occur at any particular time. A named event is triggered by the ->
operator by prefixing it before the named event handle. A named event can be waited upon by using the @
operator described above.
module tb;
event a_event;
event b_event[5];
initial begin
#20 -> a_event;
#30;
->a_event;
#50 ->a_event;
#10 ->b_event[3];
end
always @ (a_event) $display ("T=%0t [always] a_event is triggered", $time);
initial begin
#25;
@(a_event) $display ("T=%0t [initial] a_event is triggered", $time);
#10 @(b_event[3]) $display ("T=%0t [initial] b_event is triggered", $time);
end
endmodule
Named events can be used to synchronize two or more concurrently running processes. For example, the always
block and the second initial
block are synchronized by a_event . Events can be declared as arrays like in the case of b_event which is an array of size 5 and the index 3 is used for trigger and wait purpose.
ncsim> run T=20 [always] a_event is triggered T=50 [always] a_event is triggered T=50 [initial] a_event is triggered T=100 [always] a_event is triggered T=110 [initial] b_event is triggered ncsim: *W,RNQUIE: Simulation is complete.
Event or operator
The or
operator can be used to wait on until any one of the listed events is triggered in an expression. The comma ,
can also be used instead of the or
operator.
module tb;
reg a, b;
initial begin
$monitor ("T=%0t a=%0d b=%0d", $time, a, b);
{a, b} <= 0;
#10 a <= 1;
#5 b <= 1;
#5 b <= 0;
end
// Use "or" between events
always @ (posedge a or posedge b)
$display ("T=%0t posedge of a or b found", $time);
// Use a comma between
always @ (posedge a, negedge b)
$display ("T=%0t posedge of a or negedge of b found", $time);
always @ (a, b)
$display ("T=%0t Any change on a or b", $time);
endmodule
ncsim> run T=0 posedge of a or negedge of b found T=0 Any change on a or b T=0 a=0 b=0 T=10 posedge of a or b found T=10 posedge of a or negedge of b found T=10 Any change on a or b T=10 a=1 b=0 T=15 posedge of a or b found T=15 Any change on a or b T=15 a=1 b=1 T=20 posedge of a or negedge of b found T=20 Any change on a or b T=20 a=1 b=0 ncsim: *W,RNQUIE: Simulation is complete.
Implicit Event Expression List
The sensitivity list or the event expression list is often a common cause for a lot of functional errors in the RTL. This is because the user may forget to update the sensitivity list after introducing a new signal in the procedural block.
module tb;
reg a, b, c, d;
reg x, y;
// Event expr/sensitivity list is formed by all the
// signals inside () after @ operator and in this case
// it is a, b, c or d
always @ (a, b, c, d) begin
x = a | b;
y = c ^ d;
end
initial begin
$monitor ("T=%0t a=%0b b=%0b c=%0b d=%0b x=%0b y=%0b", $time, a, b, c, d, x, y);
{a, b, c, d} <= 0;
#10 {a, b, c, d} <= $random;
#10 {a, b, c, d} <= $random;
#10 {a, b, c, d} <= $random;
end
endmodule
ncsim> run T=0 a=0 b=0 c=0 d=0 x=0 y=0 T=10 a=0 b=1 c=0 d=0 x=1 y=0 T=20 a=0 b=0 c=0 d=1 x=0 y=1 T=30 a=1 b=0 c=0 d=1 x=1 y=1 ncsim: *W,RNQUIE: Simulation is complete.
If the user decides to add new signal e and capture the inverse into z , special care must be taken to add e also into the sensitivity list.
module tb;
reg a, b, c, d, e;
reg x, y, z;
// Add "e" also into sensitivity list
always @ (a, b, c, d, e) begin
x = a | b;
y = c ^ d;
z = ~e;
end
initial begin
$monitor ("T=%0t a=%0b b=%0b c=%0b d=%0b e=%0b x=%0b y=%0b z=%0b",
$time, a, b, c, d, e, x, y, z);
{a, b, c, d, e} <= 0;
#10 {a, b, c, d, e} <= $random;
#10 {a, b, c, d, e} <= $random;
#10 {a, b, c, d, e} <= $random;
end
endmodule
ncsim> run T=0 a=0 b=0 c=0 d=0 e=0 x=0 y=0 z=1 T=10 a=0 b=0 c=1 d=0 e=0 x=0 y=1 z=1 T=20 a=0 b=0 c=0 d=0 e=1 x=0 y=0 z=0 T=30 a=0 b=1 c=0 d=0 e=1 x=1 y=0 z=0 ncsim: *W,RNQUIE: Simulation is complete.
Verilog now allows the sensitivity list to be replaced by *
which is a convenient shorthand that eliminates these problems by adding all nets and variables that are read by the statemnt like shown below.
module tb;
reg a, b, c, d, e;
reg x, y, z;
// Use @* or @(*)
always @ * begin
x = a | b;
y = c ^ d;
z = ~e;
end
initial begin
$monitor ("T=%0t a=%0b b=%0b c=%0b d=%0b e=%0b x=%0b y=%0b z=%0b",
$time, a, b, c, d, e, x, y, z);
{a, b, c, d, e} <= 0;
#10 {a, b, c, d, e} <= $random;
#10 {a, b, c, d, e} <= $random;
#10 {a, b, c, d, e} <= $random;
end
endmodule
ncsim> run T=0 a=0 b=0 c=0 d=0 e=0 x=0 y=0 z=1 T=10 a=0 b=0 c=1 d=0 e=0 x=0 y=1 z=1 T=20 a=0 b=0 c=0 d=0 e=1 x=0 y=0 z=0 T=30 a=0 b=1 c=0 d=0 e=1 x=1 y=0 z=0 ncsim: *W,RNQUIE: Simulation is complete.
Level Sensitive Event Control
Execution of a procedural statement can also be delayed until a condition becomes true and can be accomplished with the wait
keyword and is a level-sensitive control.
The wait statement shall evaluate a condition and if it is false, the procedural statements following it shall remain blocked until the condition becomes true.
module tb;
reg [3:0] ctr;
reg clk;
initial begin
{ctr, clk} <= 0;
wait (ctr);
$display ("T=%0t Counter reached non-zero value 0x%0h", $time, ctr);
wait (ctr == 4) $display ("T=%0t Counter reached 0x%0h", $time, ctr);
$finish;
end
always #10 clk = ~clk;
always @ (posedge clk)
ctr <= ctr + 1;
endmodule
ncsim> run T=10 Counter reached non-zero value 0x1 T=70 Counter reached 0x4 T=90 Counter reached 0x5 T=170 Counter reached 0x9 Simulation complete via $finish(1) at time 170 NS + 1
Verilog also provides support for transistor level modeling although it is rarely used by designers these days as the complexity of circuits have required them to move to higher levels of abstractions rather than use switch level modeling.
NMOS/PMOS
module des (input d, ctrl,
output outn, outp);
nmos (outn, d, ctrl);
pmos (outp, d, ctrl);
endmodule
module tb;
reg d, ctrl;
wire outn, outp;
des u0 (.d(d), .ctrl(ctrl), .outn(outn), .outp(outp));
initial begin
{d, ctrl} <= 0;
$monitor ("T=%0t d=%0b ctrl=%0b outn=%0b outp=%0b", $time, d, ctrl, outn, outp);
#10 d <= 1;
#10 ctrl <= 1;
#10 ctrl <= 0;
#10 d <= 0;
end
endmodule
ncsim> run T=0 d=0 ctrl=0 outn=z outp=0 T=10 d=1 ctrl=0 outn=z outp=1 T=20 d=1 ctrl=1 outn=1 outp=z T=30 d=1 ctrl=0 outn=z outp=1 T=40 d=0 ctrl=0 outn=z outp=0 ncsim: *W,RNQUIE: Simulation is complete.
CMOS Switches
module des (input d, nctrl, pctrl,
output out);
cmos (out, d, nctrl, pctrl);
endmodule
module tb;
reg d, nctrl, pctrl;
wire out;
des u0 (.d(d), .nctrl(nctrl), .pctrl(pctrl), .out(out));
initial begin
{d, nctrl, pctrl} <= 0;
$monitor ("T=%0t d=%0b nctrl=%0b pctrl=%0b out=%0b", $time, d, nctrl, pctrl, out);
#10 d <= 1;
#10 nctrl <= 1;
#10 pctrl <= 1;
#10 nctrl <= 0;
#10 pctrl <= 0;
#10 d <= 0;
#10;
end
endmodule
ncsim> run T=0 d=0 nctrl=0 pctrl=0 out=0 T=10 d=1 nctrl=0 pctrl=0 out=1 T=20 d=1 nctrl=1 pctrl=0 out=1 T=30 d=1 nctrl=1 pctrl=1 out=1 T=40 d=1 nctrl=0 pctrl=1 out=z T=50 d=1 nctrl=0 pctrl=0 out=1 T=60 d=0 nctrl=0 pctrl=0 out=0 ncsim: *W,RNQUIE: Simulation is complete.
Bidirectional Switches
tran
module des (input io1, ctrl,
output io2);
tran (io1, io2);
endmodule
module tb;
reg io1, ctrl;
wire io2;
des u0 (.io1(io1), .ctrl(ctrl), .io2(io2));
initial begin
{io1, ctrl} <= 0;
$monitor ("T=%0t io1=%0b ctrl=%0b io2=%0b", $time, io1, ctrl, io2);
#10 io1 <= 1;
#10 ctrl <= 1;
#10 ctrl <= 0;
#10 io1 <= 0;
end
endmodule
ncsim> run T=0 io1=0 ctrl=0 io2=0 T=10 io1=1 ctrl=0 io2=1 T=20 io1=1 ctrl=1 io2=1 T=30 io1=1 ctrl=0 io2=1 T=40 io1=0 ctrl=0 io2=0 ncsim: *W,RNQUIE: Simulation is complete.
tranif0
module des (input io1, ctrl,
output io2);
tranif0 (io1, io2, ctrl);
endmodule
ncsim> run T=0 io1=0 ctrl=0 io2=0 T=10 io1=1 ctrl=0 io2=1 T=20 io1=1 ctrl=1 io2=z T=30 io1=1 ctrl=0 io2=1 T=40 io1=0 ctrl=0 io2=0 ncsim: *W,RNQUIE: Simulation is complete.
tranif1
module des (input io1, ctrl,
output io2);
tranif1 (io1, io2, ctrl);
endmodule
ncsim> run T=0 io1=0 ctrl=0 io2=z T=10 io1=1 ctrl=0 io2=z T=20 io1=1 ctrl=1 io2=1 T=30 io1=1 ctrl=0 io2=z T=40 io1=0 ctrl=0 io2=z ncsim: *W,RNQUIE: Simulation is complete.
Power and Ground
module des (output vdd,
output gnd);
supply1 _vdd;
supply0 _gnd;
assign vdd = _vdd;
assign gnd = _gnd;
endmodule
module tb;
wire vdd, gnd;
des u0 (.vdd(vdd), .gnd(gnd));
initial begin
#10;
$display ("T=%0t vdd=%0d gnd=%0d", $time, vdd, gnd);
end
endmodule
ncsim> run T=10 vdd=1 gnd=0 ncsim: *W,RNQUIE: Simulation is complete.
Digital elements are binary entities and can only hold either of the two values - 0 and 1. However the transition from 0 to 1 and 1 to 0 have a transitional delay and so does each gate element to propagate the value from input to its output.
For example, a two input AND gate has to switch the output to 1 if both inputs become 1 and back to 0 when any of its inputs become 0. These gate and pin to pin delays can be specified in Verilog when instantiating logic primitives.
Rise, Fall and Turn-Off Delays
Delays | Description |
---|---|
Rise delay | The time taken for the output of a gate to change from some value to 1 from either 0, X or Z |
Fall delay | The time taken for the output of a gate to change fomr some value to 0 from either 1, X or Z |
Turn-off delay | The time taken for the output of a gate to change to Z, high impedance from either 0, 1, or X |

These delays are actually applicable to any signal as they all can rise or fall anytime in real circuits and are not restricted to only outputs of gates. There are three ways to represent gate delays and the two delay format can be applied to most primitives whose outputs do not transition to high impedance. Like a three delay format cannot be applied to an AND gate because the output will not go to Z for any input combination.
// Single delay specified - used for all three types of transition delays
or #(<delay>) o1 (out, a, b);
// Two delays specified - used for Rise and Fall transitions
or #(<rise>, <fall>) o1 (out, a, b);
// Three delays specified - used for Rise, Fall and Turn-off transitions
or #(<rise>, <fall>, <turn_off>) o1 (out, a, b);
Delay Specification Format
Specification | Usage | Format |
---|---|---|
One delay | Same value for Rise, Fall and Turn-off transitions | #(delay) |
Two delay | Rise, Fall transitions | #(rise, fall) |
Three delay | Rise, Fall and Turn-off transitions | #(rise, fall, turn-off) |
One Delay Format
module des ( input a, b,
output out1, out2);
// AND gate has 2 time unit gate delay
and #(2) o1 (out1, a, b);
// BUFIF0 gate has 3 time unit gate delay
bufif0 #(3) b1 (out2, a, b);
endmodule
module tb;
reg a, b;
wire out1, out2;
des d0 (.out1(out1), .out2(out2), .a(a), .b(b));
initial begin
{a, b} <= 0;
$monitor ("T=%0t a=%0b b=%0b and=%0b bufif0=%0b", $time, a, b, out1, out2);
#10 a <= 1;
#10 b <= 1;
#10 a <= 0;
#10 b <= 0;
end
endmodule
See that the output of AND gates change 2 time units after one of its inputs change. For example, b becomes 1 while a is already 1 at T=20. But the output becomes 1 only at T=22. Similarly, a goes back to zero at T=30 and the output gets the new value at T=32.
Gate delay is specified as 3 time units for BUFIF0 and hence when b changes from 0 to 1 while a is already at 1, output takes 3 time units to get updated to Z and finally does so at T=23.
ncsim> run T=0 a=0 b=0 and=x bufif0=x T=2 a=0 b=0 and=0 bufif0=x T=3 a=0 b=0 and=0 bufif0=0 T=10 a=1 b=0 and=0 bufif0=0 T=13 a=1 b=0 and=0 bufif0=1 T=20 a=1 b=1 and=0 bufif0=1 T=22 a=1 b=1 and=1 bufif0=1 T=23 a=1 b=1 and=1 bufif0=z T=30 a=0 b=1 and=1 bufif0=z T=32 a=0 b=1 and=0 bufif0=z T=40 a=0 b=0 and=0 bufif0=z T=43 a=0 b=0 and=0 bufif0=0 ncsim: *W,RNQUIE: Simulation is complete.
Two Delay Format
Let's apply the same testbench shown above to a different Verilog model shown below where rise and fall delays are explicitly mentioned.
module des ( input a, b,
output out1, out2);
and #(2, 3) o1 (out1, a, b);
bufif0 #(4, 5) b1 (out2, a, b);
endmodule
ncsim> run T=0 a=0 b=0 and=x bufif0=x T=3 a=0 b=0 and=0 bufif0=x T=5 a=0 b=0 and=0 bufif0=0 T=10 a=1 b=0 and=0 bufif0=0 T=14 a=1 b=0 and=0 bufif0=1 T=20 a=1 b=1 and=0 bufif0=1 T=22 a=1 b=1 and=1 bufif0=1 T=24 a=1 b=1 and=1 bufif0=z T=30 a=0 b=1 and=1 bufif0=z T=33 a=0 b=1 and=0 bufif0=z T=40 a=0 b=0 and=0 bufif0=z T=45 a=0 b=0 and=0 bufif0=0 ncsim: *W,RNQUIE: Simulation is complete.
Three Delay Format
module des ( input a, b,
output out1, out2);
and #(2, 3) o1 (out1, a, b);
bufif0 #(5, 6, 7) b1 (out2, a, b);
endmodule
ncsim> run T=0 a=0 b=0 and=x bufif0=x T=3 a=0 b=0 and=0 bufif0=x T=6 a=0 b=0 and=0 bufif0=0 T=10 a=1 b=0 and=0 bufif0=0 T=15 a=1 b=0 and=0 bufif0=1 T=20 a=1 b=1 and=0 bufif0=1 T=22 a=1 b=1 and=1 bufif0=1 T=27 a=1 b=1 and=1 bufif0=z T=30 a=0 b=1 and=1 bufif0=z T=33 a=0 b=1 and=0 bufif0=z T=40 a=0 b=0 and=0 bufif0=z T=46 a=0 b=0 and=0 bufif0=0 ncsim: *W,RNQUIE: Simulation is complete.
Min/Typ/Max Delays
Delays are not the same in different parts of the fabricated chip nor is it same for different temperatures and other variations. So Verilog also provides an extra level of control for each of the delay types mentioned above. Every digital gate and transistor cell has a minimum, typical and maximum delay specified based on process node and is typically provided by libraries from fabrication foundry.
For each type of delay - rise, fall, and turn-off - three values min, typ and max can be specified and stand for minimum, typical and maximum delays.
module des ( input a, b,
output out1, out2);
and #(2:3:4, 3:4:5) o1 (out1, a, b);
bufif0 #(5:6:7, 6:7:8, 7:8:9) b1 (out2, a, b);
endmodule
ncsim> run T=0 a=0 b=0 and=x bufif0=x T=4 a=0 b=0 and=0 bufif0=x T=7 a=0 b=0 and=0 bufif0=0 T=10 a=1 b=0 and=0 bufif0=0 T=16 a=1 b=0 and=0 bufif0=1 T=20 a=1 b=1 and=0 bufif0=1 T=23 a=1 b=1 and=1 bufif0=1 T=28 a=1 b=1 and=1 bufif0=z T=30 a=0 b=1 and=1 bufif0=z T=34 a=0 b=1 and=0 bufif0=z T=40 a=0 b=0 and=0 bufif0=z T=47 a=0 b=0 and=0 bufif0=0 ncsim: *W,RNQUIE: Simulation is complete.
A previous example explored a simple sequence detector. Here is another example for a pattern detector which detects a slightly longer pattern.
Design
module det_110101 ( input clk,
input rstn,
input in,
output out );
parameter IDLE = 0,
S1 = 1,
S11 = 2,
S110 = 3,
S1101 = 4,
S11010 = 5,
S110101 = 6;
reg [2:0] cur_state, next_state;
assign out = cur_state == S110101 ? 1 : 0;
always @ (posedge clk) begin
if (!rstn)
cur_state <= IDLE;
else
cur_state <= next_state;
end
always @ (cur_state or in) begin
case (cur_state)
IDLE : begin
if (in) next_state = S1;
else next_state = IDLE;
end
S1: begin
if (in) next_state = S11;
else next_state = IDLE;
end
S11: begin
if (!in) next_state = S110;
else next_state = S11;
end
S110 : begin
if (in) next_state = S1101;
else next_state = IDLE;
end
S1101 : begin
if (!in) next_state = S11010;
else next_state = IDLE;
end
S11010: begin
if (in) next_state = S110101;
else next_state = IDLE;
end
S110101: begin
if (in) next_state = S1;
else next_state = IDLE; // Bug 2
end
endcase
end
endmodule
Testbench
module tb;
reg clk, in, rstn;
wire out;
integer l_dly;
always #10 clk = ~clk;
det_110101 u0 ( .clk(clk), .rstn(rstn), .in(in), .out(out) );
initial begin
clk <= 0;
rstn <= 0;
in <= 0;
repeat (5) @ (posedge clk);
rstn <= 1;
@(posedge clk) in <= 1;
@(posedge clk) in <= 1;
@(posedge clk) in <= 0;
@(posedge clk) in <= 1;
@(posedge clk) in <= 0;
@(posedge clk) in <= 1;
@(posedge clk) in <= 1;
@(posedge clk) in <= 1;
@(posedge clk) in <= 0;
@(posedge clk) in <= 1;
@(posedge clk) in <= 0;
@(posedge clk) in <= 1;
#100 $finish;
end
endmodule
ncsim> run T=10 in=0 out=0 T=30 in=0 out=0 T=50 in=0 out=0 T=70 in=0 out=0 T=90 in=0 out=0 T=110 in=1 out=0 T=130 in=1 out=0 T=150 in=0 out=0 T=170 in=1 out=0 T=190 in=0 out=0 T=210 in=1 out=0 T=230 in=1 out=1 T=250 in=1 out=0 T=270 in=0 out=0 T=290 in=1 out=0 T=310 in=0 out=0 T=330 in=1 out=0 T=350 in=1 out=1 T=370 in=1 out=0 T=390 in=1 out=0 T=410 in=1 out=0 Simulation complete via $finish(1) at time 430 NS + 0