Due: Mar. 20 Fri.
Problem A. (6 pts) This is a Verilog HDL assignment. Verilog HDL is a hardware description language. It has the same function as a state diagram or an ASM chart because it is used to describe digital circuits. Verilog HDL is similar to a programming language, and is similar to C in syntax. But it is not the same. C describes a process that is to be executed by a computer. A Verilog HDL program describes a hardware circuit which can be simulated and implemented using real circuits.
For this assignment, you will design a counter circuit. You will use the Veriwell simulator which can be found on the Macintosh computers in Holmes 483. You can also download your own Veriwell simulator (see our EE 361 Homepage for the link). The simulator can be used to write, edit, and simulate your programs.
This assignment is organized as follows
The output (q1,q0) is equal to the state of the counter, i.e., (q1,q0) is
the current count. The counter is positive edge triggered, and
has four possible functions: count-up, count-down, hold, and clear.
Counting-up means that the state will increment by one. Counting-down means
that the state will decrement by one. The function table for
the counter is
s1 s0 function
-----------------
0 0 hold
0 1 count-up
1 0 count-down
1 1 clear
There are many ways to implement the counter.
We could use a register (e.g., 2 D flip flops) and a ROM, or
we could use a register, 2-input NANDs, and voltage
inverters. For this example, we will use components such as
multiplexers, adders/subtracters, and a register. More specifically,
the counter will have the following block diagram
+-------------+
| +------+ |
V V | |
+-----------+ | |
s1,s0 ->| ALU | | |
+-----------+ | |
| | | |
V V | |
+-----------+ | |
clk -->| 2-bit Reg | | |
+-----------+ | |
| | | |
| +------+ |
+-------------+
where the ALU has the multiplexers and adders/subtractors.
d1 d0 | | V V +-----------+ clk -->| 2-bit Reg | +-----------+ | | q1 q0
The following is Verilog HDL code that implements the register
//
// Two-bit Register
//
module reg2(q1, q0, d1, d0, clk);
input d1, d0, clk; // Declaration of the input/output ports
output q1, q0;
reg Q1, Q0; // Q1, Q0 are variables that will be used
// to store the state.
initial Q1 = 0; // This initializes the state (Q1,Q0) to (0,0).
initial Q0 = 0; // Otherwise, the state will be initialized to "xx"
always @(posedge clk) // The state is updated at the positive clock
Q1 = d1; // edge
always @(posedge clk)
Q0 = d0;
assign q1 = Q1; // The output ports are connected to the regs.
assign q0 = Q0;
endmodule
Let's figure out what's going on with the code starting from the top.
The lines
//
// Two-bit Register
//
are comment lines. The double slash (//) means that everything to
the right is a comment. (This is the way comments are done in the
programming language C++).
The next line
module reg2(q1, q0, d1, d0, clk);
is a module declaration, which starts with "module" and ends with
a semicolon. Here, a hardware module is defined with input
and output ports, q1, q0, d1, d0, and clk, which are listed in the
parentheses. These ports are the connections into and out of
the module. For example, a NAND circuit has two inputs and one output.
If the NAND were a module, then these inputs and outputs would
correspond to input and output ports.
Note that the module is given the name "reg2". Thus, "reg2" is a name given to the module, just as "7400" is a name given to a chip with four 2-input NANDs. The general format of a module declaration is
module module_name(port_list);
where the port list is optional. Remember this format because if you deviate
from it, you will get a syntax error. Also, see page 8 of the Verilog HDL
handbook for a description of module structure.
The next set of lines are the port declarations
input d1, d0, clk; // Declaration of the input/output ports
output q1, q0;
This defines which ports are to be used as inputs and outputs. Notice that each line is terminated by a semicolon. Also note that a comment may be appended after a semicolon.
The next line declares variables
reg Q1, Q0;
Here, Q1 and Q0 are declared to be 1-bit registers (also note the
semicolon terminating the line, and the comma separating variables).
There are two kinds of variables: registers and wires
(wires are not shown in this example).
Registers are just like variables in C since they can store
values. Wires can't store anything, but are used to connect
things together (just like real wires).
Registers and wires can take on the following values: 0, 1, x (unknown),
and z (high impedence).
In the next couple of lines, we initialize the registers
initial Q1 = 0; // This initializes the state (Q1,Q0) to (0,0).
initial Q0 = 0; // Otherwise, the state will be initialized to "xx"
The "initial" statement is one of
two procedural statements, where the other is "always". Procedural
statements are used whenever registers are updated. The
statement "initial" is used to describe how a register should be
set initially (at time 0). In other words, it is used to initialize the
value of the register. The statement "always" is used to describe
how a register is updated while the module is running. We'll discuss
"always" a little bit later.
The lines
initial Q1 = 0; // This initializes the state (Q1,Q0) to (0,0).
initial Q0 = 0; // Otherwise, the state will be initialized to "xx"
say that Q1 and Q0 should be initialized to 0. The lines are to be
executed in parallel. Thus, Q1 and Q0 are cleared to zero simultaneously.
It is important to note that all procedure statements are excecuted
in parallel. You can think of each procedure statement as
being a piece of hardware, and that hardware components run in
parallel. This is different from C language where you
would expect "Q1 = 0" to be executed before
"Q0 = 0".
Another way to do the initialization is use a single initial statement
initial
begin
Q1 = 0;
Q0 = 0;
end
Note that the "begin-end" statements denote what is to be executed
for the "initial" statement. In this case, everything between
begin-end is executed in sequence. Thus, Q1 = 0 is executed
before Q0 = 0, so it's more like C.
In general, "initial" statements have the following form.
initial register = expression;
or
initial
begin
register1 = expression1;
register2 = expression2;
.
.
.
end
where the expressions are mathematical expressions composed
of variables (i.e., registers, wires, or ports), parentheses,
and the following operators
Okay, let's get back to the "reg2" Verilog HDL code. The next set of lines is
always @(posedge clk) // The state is updated at the positive clock
Q1 = d1; // edge
always @(posedge clk)
Q0 = d0;
These are examples of "always" procedure statements.
They update the registers Q1 and Q0 with the values d1 and d0,
respectively, when there is a positive
edge on the clock signal "clk".
To understand these statements, let's start from a simpler version.
Consider the statement
always Q1 = d1;
This means that the statement "Q1 = d1" is constantly (or always)
being evaluated. So this is like a wire.
For the reg2 application, this isn't good because
we'd like Q1 to be updated only at positive clock edges.
To accomplish this we can introduce an event into
the statement (see page 21 of Verilog HDL handbook for a description
of events):
always @clk Q1 = d1;
Here the event is "@clk". This means that the statement "Q1 = d1" is
executed whenever "clk" changes value. This is closer to what we
want but not exactly. The problem is that "clk" will change values
at both the positive and negative clock edges, but we
don't want a change at the negative edge. To have "Q1 = d1" executed
only at the positive clock edge, we use the statement
always @(posedge clk) Q1 = d1;
Note that "always" is a procedure statement just like "initial". So we have the following:
always @(posedge clk) Q1 = d1; always @(posedge clk) Q0 = d0;are executed in parallel.
always @(posedge clk) begin Q1 = d1; Q0 = d0; endwhere the statements between begin-end are executed in sequence (just like in C).
always register = expression;or
always event register = expression;or
always begin . . . endor
always event begin . . . endwhere event has the form
@variableor
@(posedge variable)and variable is either a register, wire, or port.
Let's get back to the "reg2" code. The next couple of lines are
assign q1 = Q1; // The output ports are connected to the regs.
assign q0 = Q0;
The keyword assign means that these are
continuous assignment statements. These are used to update
wires and output ports (which act like wires). So these statements
are constantly being evaluated, and are being evaluated in parallel.
The general form is
assign wire_or_port = expression;
You cannot use begin-end with a continous assignment. In other words,
assign
begin
.
.
end
is BAD. Begin-end can only be used with the procedure statements
"always" and "initial". Remember that to update wires/ports
use "assign", and to update registers use "initial" and "always".
The final line of the "reg2" code is
endmodule
which terminates the module. We're done with describing the reg2 module.
// // Version 2 of reg2 // module reg2(q1, q0, d1, d0, clk); input d1, d0, clk; output q1, q0; Dff dff1(q1,d1,clk); Dff dff0(q0,d0,clk); endmodule // // D flip flop module // module Dff(q,d,clk) input d, clk; output q; reg Q; // This is where the state is stored. initial Q = 0; always @(posedge clk) Q = d; // Update Q on pos. clock edge. assign q = Q; // Connect Q to the output port of D flip flop. endmoduleThis variation of "reg2" has the two lines
Dff dff1(q1,d1,clk); Dff dff0(q0,d0,clk);These are similar to function calls in C. However, they're more like macro calls which we'll illustrate a bit later. The general form of these calls are
Module_type name(list_of_inputs_and_outputs);The "Module_type" (e.g., the Dff module) has to be defined somewhere. The "name" (e.g., dff1) is some distinct label given to the instance of the module type. The list of inputs and outputs are registers, wires, or ports. Note that position is important. For example, in the case of the Dff module, the first position corresponds to the output of the flip flop, the second position corresponds to the D input, and the third position is the clock input. Thus, if the module were to have its variables renamed as
// // D flip flop module // module Dff(frank,clk,Dee) input clk, Dee; output frank; reg Q; // This is where the state is stored. initial Q = 0; always @(posedge Dee) Q = clk; assign frank = Q; endmodulethen it would still work (here, the Dff module was changed only by replacing "q" with "frank", "d" with "clk", and "clk" with "Dee").
To illustrate what the idea of a "macro" call, think of
having the lines
Dff dff1(q1,d1,clk);
Dff dff0(q0,d0,clk);
in "reg2" being replaced by lines of code in the Dff module. In
other words, the code for "reg2"
module reg2(q1, q0, d1, d0, clk);
input d1, d0, clk;
output q1, q0;
Dff dff1(q1,d1,clk);
Dff dff0(q0,d0,clk);
endmodule
after the macro expansion would look like
module reg2(q1, q0, d1, d0, clk);
input d1, d0, clk;
output q1, q0;
reg Q.1;
initial Q.1 = 0;
always @(posedge clk) Q.1 = d1;
assign q1 = Q.1;
reg Q.0;
initial Q.0 = 0;
always @(posedge clk) Q.0 = d0;
assign q0 = Q.0;
endmodule
//
// Version 3 of reg2
//
module reg2(q, d, clk);
input [1:0] d; // Declaration of input/output ports.
input clk;
output [1:0] q;
reg [1:0] Q; // Q1, Q0 are variables that will be used
// to store the state.
initial Q = 0; // This initializes the state (Q1,Q0) to (0,0).
always @(posedge clk) // The state is updated at the positive clock
Q = d; // edge
assign q = Q; // The output ports are connected to the regs.
endmodule
Here, register "Q" and ports "q" and "d" are 2-bit arrays with indices ranging
from 1 to 0. Not that all updates are bitwise updates.
Note that to call "reg2", the inputs and outputs have to be 2-bit
arrays for "q" and "d". See page 11 of Verilog HDL handbook for
more on arrays.
See page 7 on how to define constants. For example,
instead of
initial Q = 0;
we could have
initial Q = 2'b00;
which means set Q to a 2-bit binary number 00.
Whew! Let's take a break.
Now that we've got the register figured out, we should proceed with the ALU block, right? Wrong! To be on the safe side we should test the register on Veriwell. This not only tests our design but also tests our knowledge of the Verilog HDL language and the Veriwell simulator.
What happens if we type in module reg2 and let it run on Veriwell? Veriwell will compile the code and check for errors. If there are any, it'll point them out and we can correct them. Now suppose we have them corrected and then run reg2, what can we expect? Nothing because the code is not required to print anything out. In addition, reg2 requires a clock signal -- it doesn't have its own clock.
We need another module which will run reg2 and print out values that we can use to validate reg2. This is our main module. Typically, Verilog HDL programs will have a main module. These modules do not have any ports, since they are not called by any other module. They supply the clock signal, connect modules together, and display important data. This module is like a work bench in lab, where you have voltage supplies, clock generators, and probes.
We'd like our main module to supply a clock signal, input various
values into reg2, and display the values in reg2. The following is
a main module "test". We'll go through it shortly.
module test;
reg [1:0] d; // Inputs to reg2a
wire [1:0] q; // These wires connect to the output values of reg2.
reg clock; // Clock signal is stored in this register.
//
// Clock generator
//
initial clock = 0; // Initialize clock signal to 0
always #1 clock = ~clock; // Invert signal every time unit. Thus,
// clock period is 2 time units.
//
// Displaying data in the simulation
//
initial
begin
$display("Time clock d q"); // $display is like a printf
// statement
$monitor("%d %b %b %b",$time,clock,d,q);
// $monitor is also like printf
// but it will print when any
// of its variables changes
end // values.
//
// "Driver" for reg2 -- it inputs various types of values.
//
initial
begin
d = 0; // Initially set the input of reg2 to 00
#4 d = 1; // After a delay of 4 time units, set the
// input of reg2 to 01
#4 d = 2; // After another delay of 4 time units, set
// the input of reg2 to 10
#4 d = 3; // After yet another delay of 4 time units, set
// the input of reg2 to 11.
#4 $stop; // Halts simulation after 4 more time units.
end
//
// Datapath
// Here we assume version 3 of reg2 (bit array version).
reg2 reg2a(q, d, clock);
endmodule
Note that we have some new stuff in this module, so let's go through
it starting from the top. The first few lines are
reg [1:0] d; // Inputs to reg2a
wire [1:0] q; // These wires connect to the output values of reg2.
reg clock; // Clock signal is stored in this register.
These are related to the second to the last line
reg2 reg2a(q, d, clock);
which induces an instance of "reg2" that is labeled
"reg2a". reg2a needs to input a clock, input its parallel load,
and output its state q.
The register "clock" will be used as the clock input, and the 2-bit register
"d" will be used as the load inputs. By varying the values of "d" we can
load different values into "reg2a". The 2-bit wire variable "q" is
used to connect "reg2a"'s outputs to the "test" module.
The next couple of lines are used to manage the clock
initial clock = 0; always #1 clock = ~clock;The first statement initializes the clock signal to 0. The second statement toggles the clock every time unit. To better understand this statement, realize that Veriwell simulates the circuit according to some simulated time line. The exact simulated time is in some variable within the simulator. Its value can be accessed through the variable "$time". This simulated time is an integer value and indicates the number of time units that have elapsed. These time units could represent real time such as microseconds or nanoseconds.
The statement
#1 clock = ~clock;means that the statement "clock = ~clock" will be executed after 1 time unit delay. Thus, "#1" corresponds to a 1 time unit delay. The general form of a time delay is "#delay" where "delay" is a positive integer constant (DO NOT use variables). This is called Delay Control and is described on page 21 of the Verilog HDL handbook. Hence, the statement
always #1 clock = ~clock;will toggle the clock signal after every time unit leading to a period of 2 time units. Another way to accomplish the same clock period is
always begin #1 clock = 0; #1 clock = 1; endNotice that the two statements in the begin-end block are executed in sequence. Since the begin-end block is part of an "always" statement, it's executed over and over again.
Note that the delay is necessary for the simulation to make sense at all.
To see this, consider the statement
always clock = ~clock;
Now we have problems. The statement clock = ~clock is constantly updated
without any time elapsing, i.e., $time is never updated.
Thus, although the simulator is working, it never progresses in
simulated time. It's like having time stand still.
The next set of lines of the main module "test" displays the data
initial
begin
$display("Time clock d q"); // $display is like a printf
// statement
$monitor("%d %b %b %b",$time,clock,d,q);
// $monitor is also like printf
// but it will print when any
// of its variables changes
end // values.
The first line
$display("Time clock d q");
is just like the printf statement. It can also be used to output
variables, for example
$display("Time=%d clock=%b d=%b q=%b",$time,clock,d,q);
will print out the value of $time in integer, "clock" in binary, "d"
in binary, and "q" in binary. Notice that "%d" and "%b" means that
the values should be printed in "decimal" and "binary", respectively.
Page 27 of the Verilog HDL handbook has a description of $display.
Note the line feed "\n" is not typically specified in a quoted string.
The second line
$monitor("%d %b %b %b",$time,clock,d,q);
is like $display, but it displays whenever one of its values (except
$time) changes.
In the example, $monitor will display whenever one of the variables "clock",
"d", or "q" changes. Thus, $monitor can display more than once during
a simulation. This statement can appear only once in a program.
Two or more appearances of $monitor
will cause a compile error. This differs from $display which can appear
many times in a program. See page 28 of the Verilog HDL handbook
for a description of $monitor.
The next few lines
initial
begin
d = 0; // Initially set the input of reg2 to 00
#4 d = 1; // After a delay of 4 time units, set the
// input of reg2 to 01
#4 d = 2; // After another delay of 4 time units, set
// the input of reg2 to 10
#4 d = 3; // After yet another delay of 4 time units, set
// the input of reg2 to 11.
#4 $stop; // Halts simulation after 4 more time units.
end
are used to drive the program. The statements in the begin-end block are
executed in sequence starting from simulated time 0.
Note that instead of using decimal valued constants, we could use other
formats for constants. For example, an alternative to the initial
statement above is
initial
begin
d = 2'b00;
#4 d = 2'b01;
#4 d = 2'b10;
#4 d = 2'b11;
#4 $stop;
end
Here the constants are given as 2-bit numbers. See page 7 for a
description of other formats for constants. Yet another alternative
to the initial statement is the following set of initial statements
initial d = 0;
initial #4 d = 1;
initial #8 d = 2;
initial #12 d = 3;
initial #16 $stop;
Okay, that takes care of the "test" module. Let's do a little review.
Let's take another break.
Optional Task 1 (DO NOT turn in): Now let's enter the test program into Veriwell and see if reg2 works.
module test; reg [1:0] d; // Declarations wire [1:0] q; reg clock; // Clock initial clock = 0; always #1 clock = ~clock; // Displaying data in the simulation initial begin $display("Time clock d q"); $monitor("%d %b %b %b",$time,clock,d,q); end // "Driver" for reg2 -- it inputs various types of values. initial begin d = 0; #4 d = 1; #4 d = 2; #4 d = 3; #4 $stop; end // Datapath reg2 reg2a(q, d, clock); endmodule // Two-bit Register module reg2(q, d, clk); input [1:0] d; // Declaration of variables. input clk; output [1:0] q; reg [1:0] Q; initial Q = 0; always @(posedge clk) Q = d; assign q = Q; endmoduleNote that the above doesn't have many comments (to make it shorter) but good programming style will have a number of good comments. Comments make code understandable to others and to yourself in the future.
Take another break.
Now we're ready to tackle the ALU. First, let's discuss what the ALU
is supposed to do. Recall that the counter has the following
function table.
s1 s0 function
-----------------
0 0 hold
0 1 count-up
1 0 count-down
1 1 clear
The ALU is a combinational circuit that supplies the next state (q1+,q0+)
to the 2-bit register. The ALU has four inputs (q1,q0,s1,s0), two
outputs (q1+,q0+), and the following function table
s1 s0 function
-----------------
0 0 (q1+,q0+) = (q1,q0)
0 1 (q1+,q0+) = (q1,q0) + 1
1 0 (q1+,q0+) = (q1,q0) - 1
1 1 (q1+,q0+) = (0,0)
Next is a Verilog HDL implementation of the alu (read the comments
to figure out what's going on):
// The ALU module
module Alu2(qout, qin, s);
input [1:0] qin, s; // Port declarations
output [1:0] qout;
// The following wires are used to determine the values of qout when
// the counter is counting "up", counting "down", "holding",
// or "clearing".
wire [1:0] up, down, hold, clr;
assign up = qin + 1;
assign down = qin - 1;
assign hold = qin;
assign clr = 0;
// A 4:1 multiplexer is used to choose the output value.
Mux4 mux4a(qout, s, hold, up, down, clr);
endmodule
// 4:1 Multiplexer module
module Mux4(y, s, i0, i1, i2, i3);
input [1:0] s, i0, i1, i2, i3; // y = i0, if s = 0
output [1:0] y; // = i1, if s = 1
// = i2, if s = 2
// = i3, if s = 3
// The way we'll be implementing the multiplexer is
// by implementing the following equation:
//
// y = i0 & (~s[0]) & (~s[1])
// | i1 & (~s[0]) & s[1]
// | i2 & s[0] & (~s[1])
// | i3 & s[0] & s[1]
//
// But this equation won't work because i0, i1, i2, i3 are 2-bits
// a piece while s[0] and s[1] are 1 bit a piece. So we'll make
// two bit versions of s[0] and s[1], and then implement the equation
// using bit-wise operations
wire [1:0] s1, s0; // Our 2-bit versions of s[1] and s[0].
assign s1[1] = s[1];
assign s1[0] = s[1];
assign s0[1] = s[0];
assign s0[0] = s[0];
assign y = i0 & (~s0) & (~s1)
| i1 & (~s0) & s1
| i2 & s0 & (~s1)
| i3 & s0 & s1;
endmodule
Note that both the Alu2 and Mux4 modules use wire variables. Note
that the variables are used to define operations of combinational circuits.
For example in Alu2, the lines
assign up = qin + 1;
assign down = qin - 1;
define adder and subtractor operations. Another example in Mux4 is
assign y = i0 & (~s0) & (~s1)
| i1 & (~s0) & s1
| i2 & s0 & (~s1)
| i3 & s0 & s1;
which defines the 4:1 multiplexing operation.
For the heck of it, let's look at another way to implement the ALU.
We'll implement it using full adders. Here's a full adder in
Verilog HDL
// Full adder
module FA(cout, sum, a, b, cin);
input a, b, cin; // These are the bits to be added, where
// cin is the carry-in
output cout, sum; // The output is (cout,sum), where cout is
// the carry-out bit, and sum is the
// sum bit.
assign sum = a ^ b ^ cin; // The sum is the exclusive-ors of
// the input bits.
assign cout = a & b | a & cin | b & cin;
// cout = 1 if at least two of the
// input bits are 1.
endmodule
Now we can connect two full adders as follows
y -------+---------------+
| q1 | q0
| | | |
V V V V
+------+ +------+
cout1 <-| FA |<-------| FA |<-- cin0
+------+ +------+
| |
V V
q1+ q0+
Notice that this configuration has two control inputs: y and cin0.
It has the following function table
y cin0 | q1+ q0+
----------+----------
0 0 | q1 q0
0 1 | (q1 q0) + 1
1 0 | (q1 q0) - 1
1 1 | q1 q0, because we are basically adding 1 and
subtracting 1.
The only thing that the configuration doesn't do for us is to output
zeros. So we modify it by adding in ANDs (denoted by "&").
y0 -------+---------------+
| q1 | q0
| | | |
V V V V
+------+ +------+
cout1 <-| FA |<-------| FA |<-- cin0
+------+ +------+
y1 ------+--|------------+ |
| | | |
+----+ +----+
| & | | & |
+----+ +----+
| |
q1+ q0+
It now has the following function table.
y1 y0 cin0 | q1+ q0+
------------+----------
0 x x | 0 0 (clear)
1 0 0 | q1 q0 (hold)
0 1 | (q1 q0) + 1 (count-up)
1 0 | (q1 q0) - 1 (count-down)
1 1 | q1 q0, (hold)
We need a control circuit that will have as inputs (s1,s0) and
output the appropriate values for (y1,y0) and cin0. So it's
truth table should be
s1 s0 | y1 y0 cin0
--------+--------------
0 0 | 1 0 0 (hold)
0 1 | 1 0 1 (count-up)
1 0 | 1 1 0 (count-down)
1 1 | 0 x x (clear)
The MSOP (minimum sum of product) formulas for the outputs are
y1 = s1' + s0'
y0 = s1
cin0 = s0
This leads to the following Verilog HDL program for alu2
// Alu2
module Alu2(qout, qin, s);
input [1:0] qin, s; // Port declarations
output [1:0] qout;
wire [1:0] y, cin, cout, faout; // faout will store the sum bits
// of the full adders
// The control
assign y[1] = (~s[1]) | (~s[0]);
assign y[0] = s[1];
assign cin[0] = s[0];
// The full adder configuration
FA fa1(cout[1], faout[1], qin[1], y[0], cin[1]);
FA fa0(cout[0], faout[0], qin[0], y[0], cin[0]);
assign cin[1] = cout[0]; // The carry-out of full adder 0
// is connected to the carry-in of
// full adder 1.
assign qout[1] = faout[1] & y[1]; // The outputs of the full adders
assign qout[0] = faout[0] & y[1]; // are AND'd with y[1]
endmodule
Optional Task 2 (DO NOT Turn In): Now test either version of alu2 using Veriwell. Note that you need to come up with a main module that will "test" alu2. For example, your main module should try different values for "s" and "qin", to see if "qout" changes so that the counter can count, hold, and clear.