This is a record of my attempts to learn enough Verilog to design and simulate a simple but complete CPU to include in my electronics textbook. To guide my steps I am relying the book Verilog Digital System Design by Zainalabedin Navabi. I will comment a little on the Verilog language at least at the beginning but will not attempt to cover the language in any real detail. For that Navabi's book is excellent.
I start with the need for a Verilog system that will work on a Mac. I have found a version called Icarus Verilog that is freely available at this website
The system consists of two programs; iverilog reads a set of text files (.v files) in the Verilog language and compiles them into an executable for a special virtual machine, vvp then executes the resulting program and generates the output. At its simplest the output is the results of text output commands in the test program. For more complex cases it generates compressed outputs that can be studied with a third program, gtkwave.
I followed the OSX instructions on the icarus wiki at
iverilog.wikia.com/wiki/Installation_Guide
except that I used the command
sudo port -v install iverilog
since the gtkwave install (which I tried first) did not include iverilog.
NOTE that this all worked easily because I already had MacPorts installed. Otherwise I would have had to go through the (easy) process of installing MacPorts first.
Once I waited through the installs I was able to run the "iverilog" and "vvd" commands at any command prompt.
To test the system I started with the simple 3-bit counter from Navabi's book. Here is the actual counter code.
module counter(out, clk, reset); parameter WIDTH = 8; output [WIDTH-1 : 0] out; input clk, reset; reg [WIDTH-1 : 0] out; wire clk, reset; always @(posedge clk) out <= out + 1; always @reset if (reset) assign out = 0; else deassign out; endmodule // counter |
The module is the basic structural element of Verilog. This is a "dataflow" description of an adjustable width, positive edge-triggered, binary up-counter. It has two inputs, a clock and an asynchronous reset, and a variable width output. It demonstrates various features of Verilog.
The module line does three things. It tells us that this is the start of a new self-contained Verilog object (a module) and that the module is generically called 'counter'. Then it tells us that a counter has three connections to the outside, named out, clk, and reset. As usual, the names are completely arbitrary but it is good practice to use names that will make sense to the reader. At this point we know nothing about these connections.
The first line after the module declaration introduces a named constant that can be used anywhere a number is needed. Here it is used to make it easy to alter the bit-width of the counter. With 8 bits, as shown, the counter can count from 0 to 255.
The next pair of lines start to explain the connections. The first tells us that the signal 'out' is an output from the module and that it is 'width' bits wide, in this case 8. Note that the width property comes BEFORE the signal name in Verilog. The second line tells us that 'clk' and 'reset' are inputs to the module and, by inference, that they are single-bit values.
The next pair add more information. 'out' is a 'registered' variable. That is, it holds its value even after the input that has caused it is removed. The inputs, however, are declared to be wires. These are tri-state objects that can be in any of four states, undefined (x), 0, 1, and high-impedance (z). A wire has the undefined state before anything has driven it. Once driven its output state will be determined by its inputs.
By itself, the Counter.v program is only a component of a larger program, like a subroutine in normal programming. A complete program will need some sort of driver, a main program to call the subroutine. Following the naming scheme in Navabi's book, I have the test program
module test; /* Make a reset that pulses twice. */ reg reset = 0; initial begin # 17 reset = 1; # 11 reset = 0; # 14 reset = 1; // Note that at cumulative time 17+11+14=42 # 11 reset = 0; # 200 $stop; end /* Make a regular pulsing clock. */ reg clk = 0; always #5 clk = !clk; wire [7:0] value; counter c1 (value, clk, reset); initial $monitor("At time %t, value = %h (%0d)", $time, value, value); endmodule // test |
This time we see a module without inputs or outputs. Instead we have three kinds of body. The two sections that begin with C-style block comments define signals (using names that are known only to this file--they have no intrinsic connection to the similarly named signals in counter.v) and specify their values for the life of the simulation. The lines that begin with a '#' specify times at which things happen. They are a little odd in that you specify not the actual time at which an event occurs but the time interval from the previous event. Thus the reset signal goes high for the second time in the line
# 14 reset = 1;
This event takes place 14 time units after the previous event and thus 42 intervals (see the comment) after the simulation starts.
The third section decares an 8-bit output named 'value' that it connects to the output of a counter (named 'c1') whose inputs are connected to the clk and reset signals. Note again that, although these files use the same names for the clk and reset signal in both the test program and the subroutine that is by no means required. The actual connection is all by position in the argument list to the counter. Indeed we use the completely different names 'out' and 'value' for the output signals in the two places.
The final section sets up some debugging output using the command $monitor. This is different from all the previous text. Up till now everything has been describing the hardware and its running. This is a request to the runtime system to log various values to standard output as the simulation runs. It tells the runtime what to output but does not alter the structure or running of the simulation.
We first compile these text sources into an executable program with the command
iverilog -o my_design counter_tb.v counter.v
which, if all is well, produces no output. We then execute the my_design program using vvp
vvp my_design
which produces the output
At time 0, value = xx (x) At time 17, value = 00 (0) At time 35, value = 01 (1) At time 42, value = 00 (0) At time 55, value = 01 (1) At time 65, value = 02 (2) At time 75, value = 03 (3) At time 85, value = 04 (4) At time 95, value = 05 (5) At time 105, value = 06 (6) At time 115, value = 07 (7) At time 125, value = 08 (8) At time 135, value = 09 (9) At time 145, value = 0a (10) At time 155, value = 0b (11) At time 165, value = 0c (12) At time 175, value = 0d (13) At time 185, value = 0e (14) At time 195, value = 0f (15) At time 205, value = 10 (16) At time 215, value = 11 (17) At time 225, value = 12 (18) At time 235, value = 13 (19) At time 245, value = 14 (20) ** VVP Stop(0) ** ** Flushing output streams. ** Current simulation time is 253 ticks. > |
We see that in the beginning value was undefined but then the reset signal activated at time 17 (see the line # 17 in the test) and the value went to 00. It started to count up but was interrupted by the second reset (at time 42, the sum of the time intervals preceeding the reset command). Then it ran happily incrementing every clock until we stopped the simulation at time 245. Note that the output is only recorded when something changes on the inputs and so almost all the events are recorded at the rising edges of the clock, every 10 time units.
Because I cribbed this example pretty much straight from the book it worked with only minor correction needed for silly typos.