SRAM-based FPGA designers don’t simulate their designs enough. It is easy to burn a part and try it, so that is our tendency. We can argue some advantage to this method; after all, the end result is a part that works, right? Still, any tool that improves the quality of our design must be used. ASIC designers and designers using antifuse technology don’t have the luxury (or crutch, depending on your point of view) of trying a part that is not virtually guaranteed to work. Instead of downloading a configuration file, these designers either program an expensive antifuse device (which is thrown away if it doesn’t work) or go through a full ASIC fabrication turn (which can cost tens and hundreds of thousands of dollars). This is why ASICs (and ASIClike devices) have long simulation processes that drive management nuts. The designers are fooling around with their computers all day instead of delivering product!
Verilog was designed as a simulation and test language. It has excellent features and has been thoroughly thought out and developed. Except for the cost of the simulator software and the danger of falling into the endless ‘paralysis of analysis’ loop, there is no reason the FPGA designer shouldn’t regularly use a simulator. There are some excellent books that cover Verilog simulation in detail (see the bibliography at the end of this book); we’ll just do a quick and dirty overview in this chapter.
Most simulators have a waveform viewer, and a lot of effort is put into making this viewer attractive to the eye. The problem is that a human brain is required to analyze and interpret the waveforms. Waveforms are great and we’ve used them throughout this text to show input and output signals. However, Verilog supports automated testing. This is a great way to test and validate a design and later design changes. You can make a design change and carefully evaluate the effect on the area of interest, but how do you know you didn’t break something in another part of the design that used to work?
This doesn’t mean the automated test fixtures are a panacea. They are often a pain in the rear. You’ll spend a lot of time revising the test fixture to ‘fix’ tests where signals that don’t matter were improperly or too strictly tested.
Many powerful compiler directives are available in Verilog. Note the use of the ` (back tick or accent grave) as part of these compiler directives.
`define, `ifdef, `else, `endif, `undef Verilog supports conditional compilation and execution. Code may support simulation and not be synthesizable, or may be conditionally synthesized to support optional features. A macro variable can be defined to control compilation and might have a form like that shown in Listing 5-1
// Conditional Compilation Example.
// Comment out the next line for synthesis.
‘define test_mode; // Define test_mode macro.
…
‘ifdef test_mode
// Insert test_mode code here. Could be a test module
// definition or simulation directives, not just inline// code.
data_bus <= test_points;
‘else
// Insert non-test_mode code here.
// The ‘else portion is optional.
data_bus <= internal_data;
‘endif
// Continue with unconditional code here.
A macro definition can be ‘undefined’ by an `undef occurring later in the code.
`include filename The directive is similar to the C language #include directive. The file pointed to by filename (which may be a file in the current path or a full path description) will be inserted in the Verilog code during compile time. Includes can be nested; in other words an included file may also have an included file.
`timescale unit/precision The time unit used by the simulator is programmable. For example, in the code of Listing 5-3, there is a line that reads:
#75 reset = 0;
The number 75 represents a delay in units of the timescale unit, in this case 75 nsec. The delay tells the simulator to wait until simulation time has advanced by the delay value before executing the next directive.
There is no magic hardware construct that will create a delay for you. The default, if no timescale directive is executed, is 1 nsec. The precision determines how delay values are rounded off and determine the simulation resolution. The precision must be equal to or less than the timescale unit. The timescale argument units are in s (seconds), ms (milliseconds), us (microseconds), ns (nanoseconds), ps (picoseconds), or fs (femtoseconds). Mostly, you’ll see 1 ns / 1 ns, for delay units of 1 nsec with rounding of delay values to the nearest nsec. There can only be one timescale in a design.
Verilog system tasks start with a $.
$finish; When encountered in the code, $finish ends the simulation. Without some termination point, the simulation will continue forever (or until your PC runs out of memory and crashes). $finish returns control of the computer back to the operating system.
$stop; This system task halts simulation but does not return control to the operating system. Simulation can be continued from the stopping point, or other system commands can be executed at the current simulation time.
$display(list element 1, list element 2); This system task is similar to C’s printf command. Verilog runs just fine in a nonwaveform output mode. If there are no waveforms, how can we tell what our design is doing? Stick some $display commands in your design to view variables and other information (such as simulation time) as illustrated in Listings 5-2 and 5-3.
module display1 (clock, reset);
input clock, reset;
reg [7:0] count_val;
always @ (posedge clock or posedge reset)
if (reset)
count_val <= 0;
else begin
count_val <= count_val + 1;
$display (count_val);
end
endmodule
// Dis play Test.
module dis p1_tf;
`timescale 1ns / 1ns
reg clock, reset;
parameter clk_period = 20;
display1 u1 (clock, reset);
always begin
#(clk_period / 2) clock = ~clock;
end
initial begin
clock = 0;
reset = 1; // Assert the system reset.
#75 reset = 0;
#1000 $finish;
end
endmodule
Listing 5-4 shows the result of a Silos III simulation run. The first few zeros are the count_val register content during the reset period. The $display defaults to a decimal number format and includes a carriage return (newline) after each execution. If the newline is not desired, the $write system task can be used instead.
S I L O S I I I Version 99.100
DEMO COPY LIMITED TO 100 to 200 DEVICES
Copyright (c) 1999 by SIMUCAD Inc. All rights reserved.
No part of this program may be reproduced, transmitted,
transcribed, or stored in a retrieval system, in any
form or by any means without the prior written consent of
SIMUCAD Inc., 32970 Alvarado-Niles Road, Union City,
California, 94587, U.S.A.
(510)-487-9700 Fax: (510)-487-9721
Electronic Mail Address: Ü[email protected]Ü
!file .sav=Üdisplay1Ü
!control .sav=3
!control .savcell=0
!control .disk=1000M
Reading Üc:verilogsourcecodedisp1_tf.vÜ
Reading Üc:verilogsourcecodedisplay1.vÜ
sim to 0
Highest level modules (that have been auto-instantiated):
(disp1_tf disp1_tf
3 total devices.
Linking …
3 nets total: 11 saved and 0 monitored.
74 registers total: 74 saved.
Done.
0 State changes on observable nets.
Simulation stopped at the end of time 0.000000000s.
Ready: sim
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
313 State changes on observable nets in 0.33 seconds.
948 Events/second.
Simulation stopped at the end of time 0.000001075s.
Ready:
Text and numbers can be formatted with escape string. An escape string is a string following a backslash () or %. Here are a few examples:
%h | |
%d | Display numbers in decimal (the default) format |
%o | Display numbers in octal format |
%b | Display numbers in binary format |
%c | Display numbers as ASCII characters |
%t | Display in current time format |
“string” | Display text string |
| newline |
| tab |
literal | Literal could be another (to print a ‘’), a “(quote), or % (print %). |
To get some experience with this formatting, take a look at Listings 5-5, 5-6, and 5-7.
// Display Test with formatting.
module time_setup;
initial
begin
// This timeformat is nsec (-9), 2 digits after decimal
// place (2), ns text, and a minimum of 3 spaces for
// time to be displayed in.
$timeformat (-9, 2, Ü nsÜ, 3);
end
endmodule
module disp2_tf;
‘timescale 1ns / 1ns
reg clock, reset;
parameter clk_period = 20;
display2 u1 (clock, reset);
always begin
#(clk_period / 2) clock = ~clock;
end
initial
begin
clock = 0;
reset = 1; // Assert the system reset.
#75 reset = 0;
#1000 $finish;
end
endmodule
module display2 (clock, reset);
input clock, reset;
reg [7:0] count_val;
// Comment out the next line for terse printout.
‘define verbose
always @ (posedge clock or posedge reset)
begin
if (reset) count_val <= 0;
else begin
count_val <= count_val + 1;
‘ifdef verbose
$write (Ücount_val = %hÜ, count_val);
$display (Ü Current time = %tÜ, $time);
‘else
$write (Ü:Ü, count_val);
‘endif
end
end
endmodule
S I L O S I I I Version 99.100
DEMO COPY LIMITED TO 100 to 200 DEVICES
Copyright (c) 1999 by SIMUCAD Inc. All rights reserved.
No part of this program may be reproduced, transmitted,
transcribed, or stored in a retrieval system, in any
form or by any means without the prior written consent of
SIMUCAD Inc., 32970 Alvarado-Niles Road, Union City,
California, 94587, U.S.A.
(510)-487-9700 Fax: (510)-487-9721
Electronic Mail Address: Ü[email protected]Ü
!file .sav=Üdisplay2Ü
!control .sav=3
!control .savcell=0
!control .disk=1000M
Reading Üc:verilogsourcecode ime_setup.vÜ
Reading Üc:verilogsourcecodedisplay2.vÜ
sim to 0
Highest level modules (that have been auto-instantiated):
(time_setup time_setup
(disp2_tf disp2_tf
4 total devices.
Linking …
3 nets total: 11 saved and 0 monitored.
74 registers total: 74 saved.
Done.
0 State changes on observable nets.
Simulation stopped at the end of time 0.000000000s.
Ready: sim
count_val = 00 Current time = 90.00 ns
count_val = 01 Current time = 110.00 ns
count_val = 02 Current time = 130.00 ns
count_val = 03 Current time = 150.00 ns
count_val = 04 Current time = 170.00 ns
count_val = 05 Current time = 190.00 ns
count_val = 06 Current time = 210.00 ns
count_val = 07 Current time = 230.00 ns
count_val = 08 Current time = 250.00 ns
count_val = 09 Current time = 270.00 ns
count_val = 0a Current time = 290.00 ns
count_val = 0b Current time = 310.00 ns
count_val = 0c Current time = 330.00 ns
count_val = 0d Current time = 350.00 ns
count_val = 0e Current time = 370.00 ns
count_val = 0f Current time = 390.00 ns
count_val = 10 Current time = 410.00 ns
count_val = 11 Current time = 430.00 ns
count_val = 12 Current time = 450.00 ns
count_val = 13 Current time = 470.00 ns
count_val = 14 Current time = 490.00 ns
count_val = 15 Current time = 510.00 ns
count_val = 16 Current time = 530.00 ns
count_val = 17 Current time = 550.00 ns
count_val = 18 Current time = 570.00 ns
count_val = 19 Current time = 590.00 ns
count_val = 1a Current time = 610.00 ns
count_val = 1b Current time = 630.00 ns
count_val = 1c Current time = 650.00 ns
count_val = 1d Current time = 670.00 ns
count_val = 1e Current time = 690.00 ns
count_val = 1f Current time = 710.00 ns
count_val = 20 Current time = 730.00 ns
count_val = 21 Current time = 750.00 ns
count_val = 22 Current time = 770.00 ns
count_val = 23 Current time = 790.00 ns
count_val = 24 Current time = 810.00 ns
count_val = 25 Current time = 830.00 ns
count_val = 26 Current time = 850.00 ns
count_val = 27 Current time = 870.00 ns
count_val = 28 Current time = 890.00 ns
count_val = 29 Current time = 910.00 ns
count_val = 2a Current time = 930.00 ns
count_val = 2b Current time = 950.00 ns
count_val = 2c Current time = 970.00 ns
count_val = 2d Current time = 990.00 ns
count_val = 2e Current time = 1010.00 ns
count_val = 2f Current time = 1030.00 ns
count_val = 30 Current time = 1050.00 ns
count_val = 31 Current time = 1070.00 ns
313 State changes on observable nets in 0.76 seconds.
411 Events/second.
Simulation stopped at the end of time 0.000001075s.
Ready:
Simulation stopped at the end of time 0.000001075s.
Ready:
A list of variables can be displayed when they change by using the $monitor directive. The syntax of the $monitor signal list and formatting controls is very similar to those used for the $display directive. Some examples of the using the $monitor directive are presented in Listing 5-8 and Listing 5-9 with the corresponding output shown in Listing 5-10.
$monitor (signal list and formatting);
$monitoron/$monitoroff;
module display3 (clock, reset);
input clock, reset;
reg [7:0] count_val;
always @ (posedge clock or posedge reset)
begin
if (reset) count_val <= 0;
else
begin count_val <= count_val + 1;
end
end
endmodule
// $monitor used in a test fixture.
module time_setup2;
initial
begin
‘timescale 1ns / 1ns
$timeformat (-9, 2, “ ns”, 3);
end
endmodule
module disp3_tf;
reg clock, reset;
wire [7:0] count_val;
parameter clk_period = 20;
display3 u1 (clock, reset);
always
begin
#(clk_period / 2) clock = ~clock;
end
initial
begin
$monitor ($time, “ Counter value: %h”, u1.count_val);
clock = 0;
reset = 1; // Assert the system reset.
#75 reset = 0;
#1000 $finish
end
endmodule
90000000000.00 ns Counter value: 01
110000000000.00 ns Counter value: 02
130000000000.00 ns Counter value: 03
150000000000.00 ns Counter value: 04
170000000000.00 ns Counter value: 05
190000000000.00 ns Counter value: 06
210000000000.00 ns Counter value: 07
230000000000.00 ns Counter value: 08
250000000000.00 ns Counter value: 09
270000000000.00 ns Counter value: 0a
290000000000.00 ns Counter value: 0b
310000000000.00 ns Counter value: 0c
330000000000.00 ns Counter value: 0d
350000000000.00 ns Counter value: 0e
370000000000.00 ns Counter value: 0f
390000000000.00 ns Counter value: 10
410000000000.00 ns Counter value: 11
430000000000.00 ns Counter value: 12
450000000000.00 ns Counter value: 13
470000000000.00 ns Counter value: 14
490000000000.00 ns Counter value: 15
510000000000.00 ns Counter value: 16
530000000000.00 ns Counter value: 17
550000000000.00 ns Counter value: 18
570000000000.00 ns Counter value: 19
590000000000.00 ns Counter value: 1a
610000000000.00 ns Counter value: 1b
630000000000.00 ns Counter value: 1c
650000000000.00 ns Counter value: 1d
670000000000.00 ns Counter value: 1e
690000000000.00 ns Counter value: 1f
710000000000.00 ns Counter value: 20
730000000000.00 ns Counter value: 21
750000000000.00 ns Counter value: 22
770000000000.00 ns Counter value: 23
790000000000.00 ns Counter value: 24
810000000000.00 ns Counter value: 25
830000000000.00 ns Counter value: 26
850000000000.00 ns Counter value: 27
870000000000.00 ns Counter value: 28
890000000000.00 ns Counter value: 29
910000000000.00 ns Counter value: 2a
930000000000.00 ns Counter value: 2b
950000000000.00 ns Counter value: 2c
970000000000.00 ns Counter value: 2d
990000000000.00 ns Counter value: 2e
1010000000000.00 ns Counter value: 2f
1030000000000.00 ns Counter value: 30
1050000000000.00 ns Counter value: 31
1070000000000.00 ns Counter value: 32
There are many file commands, we’ll touch on the highlights.
$dumpfile (“filename”);
$dumpvars(levels of hierarchy, module variables extracted from);
$dumpvars(0, module the variables extracted from);
$dumpvars
$dumpon;
$dumpoff;
$dumpall;
$dumplimit(filesize in bytes);
Verilog can save simulation results in an ASCII file. The format of this file is called Value Change Dump or VCD. An entry in the file occurs only when a variable value changes. The $dumpvars directive without an argument list will dump all the variables in the design. The $dumpvars (0, module name) directive will dump variables from the listed module and all modules instantiated by the listed module. Variables can be identified hierarchically in the file list (module1.module2.variable_name).
The VCD file can get very large. Setting a $dumplimit will stop the dump when the VCD file reaches the specified limit. Some examples of the $dump directive is shown in Listing 5-11 and Listing 5-12, with a partial output listing shown in Listing 5-13.
#100 $dumpon; // Dump all variables after 100 time units.
#100 $dumpoff; // Stop dump after 100 time units.
#100 $dumpall; // Dump a snapshot of all variables.
// Value Change Dump Example.
module time_setup3;
initial begin
‘timescale 10ns / 1ns
$timeformat (-9, 2, “ ns”, 3);
$dumpfile (“bigdump.dmp”); // Open file.
end
endmodule
module disp4_tf;
reg clock, reset;
wire [7:0] count_val;
parameter clk_period = 20;
display4 u1 (clock, reset);
always begin
#(clk_period / 2) clock = ~clock;
end
initial begin
$timeformat (-9, 2, “ ns”, 3);
$dumpvars;
clock = 0;
reset = 1; // Assert the system reset.
#75 reset = 0;
#1000 $finish;
end
endmodule
module display4 (clock, reset);
input clock, reset;
reg [7:0] count_val;
always @ (posedge clock or posedge reset)
begin
if (reset)
count_val <= 0;
else
begin
count_val <= count_val + 1;
end
end endmodule
$scope module disp4_tf $end
$var reg 1 ! reset $end
$var reg 1 “ clock $end
$scope module u1 $end
$var wire 1 # clock $end
$var wire 1 $ reset $end
$var reg 8 % count_val [7:0] $end
$upscope $end
$upscope $end
$enddefinitions $end
#0
$dumpvars
1!
0“
0#
1$
b00000000 %
$end
Listing 5-13 is a small part of the bigdump.dmp. Note that each signal in the scope of $dumpvars (because $dumpvars was not limited in scope, all signals in the design are dumped) is assigned a key character (! = reset, for example) and this shorthand is used in the dumpfile. The VCD is not human-friendly, but is a format that can be read by other tools.
$readmemh (“filename”, memory_name); Read hex values from a file.
$readmemb (“filename”, memory_name); Read binary values from a file.
Optional starting and ending addresses can be added to place limits on the data pulled from the file. The address is an index into the array, the nth data element. It is acceptable to have a start address, but no end address, in which case the file is read to the end of the memory array.
$readmemh (“filename”, memory_name, start_addr, end_address);
The only way to assure thorough testing of a design is to automate the task. A check-off list can be created. When a new revision of code is being released, all the automated tests should be run again. The process is maddening, because most of the effort to resolve problems will be in test-fixture errors, not design errors. Still, there is no better way to test a design.
As an example, let’s design and test a simple digital filter as shown in Listing 5-14. The source code for this design is shown in Listing 5-15. The output values are shown in Figure 5-1. This design implements a one-dimensional low-pass pyramidal filter that uses five samples with coefficients of 0.0625, 0.125, 0.625, 0.125, and 0.0625 (note the sum of these coefficients is 1). This filter is crude and suffers from truncation errors but will serve as an example of automated testing.
// Pyramidal Filter Example.
module time_setup4;
initial
begin
‘timescale 1ns / 1ns
$timeformat (-9, 2, “ ns”, 3);
end
endmodule
module pf1_tf;
reg clock, reset;
reg [7:0] tap_unfilt;
// Define array where test values will come from.
reg [7:0] test_pattern[0:31];
// Define array for testing filter output.
reg [7:0] verify_pattern[0:31];
reg [4:0] mem_index;
wire [7:0] tap_filt;
reg [7:0] tap_test, filt_test;
reg flag;
parameter clk_period = 20;
pfilt1 u1 (clock, reset, tap_unfilt, tap_filt);
always begin
#(clk_period / 2)
clock = ~clock;
tap_unfilt = test_pattern [mem_index];
// filt_test = tap_filt [mem_index];
tap_test = verify_pattern [mem_index];
mem_index = mem_index + 1;
if (mem_index == 0) $finish;
end
always begin
#(clk_period)
if (!reset & (tap_filt != tap_test))
begin
$display ($time, ERROR! tap_filt = %h tap_test = %h′′, tap_filt, tap_test);
flag <= 1;
end
// else $display (“All is okay.”);
end
initial
begin
clock = 0;
mem_index = 0;
tap_test = 0;
filt_test = 0;
tap_unfilt = 0;
flag = 0;
reset = 1; // Assert the system reset.
// Read test pattern data from file into
// test_pattern array.
$readmemh (“pfilt1.tst”, test_pattern);
$readmemh (“verify1.tst”, verify_pattern);
#(clk_period * 2) reset = 0;
end
endmodule
// Pyramidal Filter Example.
// This implements a low-pass filter with coefficients:
// .0625 .125 .625 .125 .0625
// Gain = 1.
module pfilt1 (clock, reset, tap4, tap_out[8:1]);
input clock, reset;
input [7:0] tap4;
output tap_out;
reg [8:0] tap_out;
reg [7:0] tap0, tap1, tap2, tap3;
// Intermediate summation (pipeline) registers.
reg [4:0] sum1;
reg [5:0] sum2;
reg [7:0] sum3;
always @ (posedge clock or posedge reset)
begin
if (reset)
begin
tap0 <= 0;
tap1 <= 0;
tap2 <= 0;
tap3 <= 0;
tap_out <= 0;
sum1 <= 0;
sum2 <= 0;
sum3 <= 0;
end
else begin
tap0 <= tap1;
tap1 <= tap2;
tap2 <= tap3;
tap3 <= tap4;
// To multiply by 0.0625 (same as division by 16):
// shift left 2 places.
// Result register must be 1 larger than input
// registers to hold carry.
sum1 <= tap0[7:4] + tap4[7:4];
// To multiply by 0.125 (same as division by 8):
sum2 <= tap1[7:3] + tap3[7:3];
// To multiply by 0.625 (5/8) is the same as:
// (division by 2) + (division by 8).
sum3 <= tap2[7:1] + {2′b0, tap2[7:3]};
// Final sum adds sum1 + sum2 + sum3.
// If the design needs to be faster, it can be
// further pipelined to spread out the summing logic.
// The LSB is truncated to give an 8-bit result. Logic
// can be added, if necessary, to round-off to 8 bits
// instead.
tap_out[8:0] <= {3′b0, sum1} + {2′b0, sum2} + sum3;
end
end
endmodule
The pyramidal filter design reads data from two external files. Data used to stimulate the filter is extracted from pfilt1.tst (shown in Listing 5-16) and identical data (except for an intentional error put in for test) used to test the filter output extracted from the file verify1.tst shown in Listing 5-17. The error is displayed as so:
140000000000.00 ns ERROR! tap_filt = 04 tap_test = 03
Three types of entries are allowed in files read by Verilog: numbers (either hex or binary), white space, and comments.
0 // Each data value is entered twice because the
0 // value is loaded on each clock edge; two values
5 // are required to sustain a value through
5 // a complete clock period.
9
9
b
b
f
f
5
5
c
c
5
5
c
c
5
5
c
c
5
5
c
c
5
5
c
c
5
5
0
0
0
0
0
0
0
0
0
0
3
3
3 // Error added. Should be 4.
3 // Error added. Should be 4.
4
4
2
2
3
3
2
2
3
3
2
2
3
3
2
2
3.140.186.201