Playing with Verilog

This is the first programming post since I came to Japan. If you just want to know what I’m up to and are not interested in the technology, you can stop reading after the first paragraph, although you’re welcome to read on and enjoy the pictures. πŸ˜‰ The last few weeks I’ve been reading a lot about processor design, and now I’m starting to learn Verilog. Verilog is a hardware description language (HDL) specified in IEEE 1364. Simply put, a HDL is a programming language that can be used to describe the structure or behavior of logical circuits. Right now I’m learning how to design a simple processor, I already have a set of registers and an ALU (arithmetic logic unit) ready and have started implementing the instruction decode logic.

Gate structure of a full adder

The diagram on the right shows the structure of a 1-bit adder. It accepts a possible carry from a lower bit addition and output a carry bit as necessary. For more about adders please refer to Wikipedia or other sources, I’m just going to describe this in Verilog to show the principle.

A Verilog module is similar to a class in object oriented languages: It defines a list of inputs, outputs, internal variables and behavior or structure of a component. Instances of modules are created to actually use the module. I’ll be using the structural approach in this example, which means that I will define a module by describing its components and their connections, rather than using equations to calculate the output values based on the inputs. Modules for basic logic gates are built-in, with gate level logic, simple wires are sufficient to connect the components.

Because I’m used to splitting code up as much as possible, I first define a half-adder module for the AND/XOR pairs:

module halfadd(a, b, sum, carry);
   input a;
   input b;
   output sum;
   output carry;

   wire   a;
   wire   b;
   wire   sum;
   wire   carry;

   xor xsum(sum, a, b);
   and csum(carry, a, b);
endmodule // halfadd

Verilog’s built-in gates take the output as first argument and the inputs after that. The half-adder is defined in a file called halfadd.v, which I include into the full-adder file. I create two half-adder instances and an XOR gate to build the full-adder:

`include "halfadd.v"

module fulladd(a, b, cin, sum, cout);
   input a;
   input b;
   input cin;
   output sum;
   output cout;

   // inputs
   wire   a;
   wire   b;
   wire   cin;

   // internal wiring
   wire   s1;
   wire   c1;
   wire   c2;

   // outputs
   wire   sum;
   wire   cout;

   halfadd h1(a, b, s1, c1);
   halfadd h2(s1, cin, sum, c2);

   xor carry(cout, c1, c2);
endmodule // halfadd

The only thing left to do is test the fulladd module. A test-bench module can be used to simulate inputs. In this example you can see “behavioral” Verilog, which will seem familiar to most programmers. πŸ˜‰ Not everything that can be written in this style could be turned into hardware, however, for a simulation these restrictions can be completely disregarded.

`include "fulladd.v"

module fulladd_test();
   reg [2:0] in;
   wire      sum;
   wire      carry_out;

   initial begin
      $dumpfile ("test.vcd");
      $dumpvars (0, fulladd_test);
      $monitor ("a=%b, b=%b, carry_in=%b, sum=%b, carry_out=%b",
		in[0], in[1], in[2], sum, carry_out);
      in = 0;
      #20 $finish;

   always #2 in = in + 1;

   fulladd a1(in[0], in[1], in[2], sum, carry_out);
endmodule // fa_test

The most interesting thing here is probably the in variable. It is defined as a 3 bit register with indices from 2 to 0, so it’s a little endian value. Each bit is connected to one of the full-adder’s inputs, and I will increment this value during the simulation to simulate all possible input values. For bigger systems it would require an impossible amount of time to simulate all possible inputs, but here I can do it. Writing good tests is as important (and difficult) as writing a good design, and while working on my processor I’ve already spent a while searching for an error in my design to find that the test was wrong instead (that time).

initial marks a block that will be executed a simulation start, when the module is initalized. The $dumpfile and $dumpvars commands make the simulation write a trace of the variables to the file test.vcd, while $monitor prints the specified variables to the terminal every time one of them changes. Statements with #NUMBER in front of them are delayed that many units of simulated time. I use this both to stop the simulation after 20 units of simulated time and to increase the input value every two time units.

To actually run the simulation, I used the Icarus Verilog compiler and GTKWave to analyze the results (packages iverilog and gtkwave in Ubuntu):

$ iverilog -o fulladd_test fulladd_test.v
$ vvp fulladd_test
VCD info: dumpfile test.vcd opened for output.
a=0, b=0, carry_in=0, sum=0, carry_out=0
a=1, b=0, carry_in=0, sum=1, carry_out=0
a=0, b=1, carry_in=0, sum=1, carry_out=0
a=1, b=1, carry_in=0, sum=0, carry_out=1
a=0, b=0, carry_in=1, sum=1, carry_out=0
a=1, b=0, carry_in=1, sum=0, carry_out=1
a=0, b=1, carry_in=1, sum=0, carry_out=1
a=1, b=1, carry_in=1, sum=1, carry_out=1
a=0, b=0, carry_in=0, sum=0, carry_out=0
a=1, b=0, carry_in=0, sum=1, carry_out=0
a=0, b=1, carry_in=0, sum=1, carry_out=0
$ gtkwave test.vcd

A bunch of graphs and a GUI to select which ones to show

Two useful links:

If you have access to it (many universities have), I also recommend looking at the actual standard document.

Leave a Comment

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: