Ece4750 Tut4 Verilog
Ece4750 Tut4 Verilog
Introduction
3.1
Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2
3.3
Shift Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4
Arithmetic Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
3.5
Relational Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
3.6
Concatenation Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
3.7
14
3.8
16
3.9
Ternary Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
3.10 If Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
20
21
Registered Incrementer
21
4.1
22
4.2
24
4.3
26
4.4
28
4.5
30
Sort Unit
32
5.1
32
5.2
36
5.3
37
5.4
39
39
6.1
40
6.2
45
46
1. Introduction
In the lab assignments for this course, we will be using the PyMTL hardware modeling framework
for functional-level modeling, verification, and simulator harnesses. Students can choose to use
either PyMTL or Verilog to do their register-transfer-level (RTL) qmodeling. If you are planning to
use PyMTL, then you do not need to complete this tutorial. If you are planning to use Verilog, you
should still complete the PyMTL tutorial since we will always be using PyMTL for some aspects of
the lab assignment.
This tutorial briefly reviews the basics of the Verilog hardware description language, but primarily focuses on how we can integrate Verilog RTL modeling into our PyMTL framework. Although
we will be using the open-source tool Icarus Verilog (iverilog) for compiling some simple Verilog
models into simulators, we will primarily be using the open-source tool Verilator (verilator) as our
Verilog simulator. PyMTL has built-in support for testing Verilog simulators created using Verilator.
As in the PyMTL tutorial, we will also be using GTKWave (gtkwave) for viewing waveforms. All
tools are installed and available on amdpool. This tutorial assumes that students have completed the
Linux and Git tutorials.
To follow along with the tutorial, access the ECE computing resources, and type the commands
without the % character. In addition to working through the commands in the tutorial, you should
also try the more open-ended tasks marked with the H symbol.
Before you begin, make sure that you have sourced the ece4750-setup script or that you have added
it to your .bashrc script, which will then source the script every time you login. Sourcing the setup
script sets up the environment required for this class.
You should start by forking the tutorial repository on GitHub. Start by going to the GitHub page for
the tutorial repository located here:
https://github.com/cornell-ece4750/ece4750-tut4-verilog
Click on Fork in the upper right-hand corner. If asked where to fork this repository, choose your
personal GitHub account. After a few seconds, you should have a new repository in your account:
https://github.com/<githubid>/ece4750-tut4-verilog
Where <githubid> is your GitHub ID, not your NetID. Now access amdpool and clone your copy of
the tutorial repository as follows:
%
%
%
%
%
%
source setup-ece4750.sh
mkdir -p ${HOME}/ece4750
cd ${HOME}/ece4750
git clone https://github.com/<githubid>/ece4750-tut4-verilog.git tut4
cd tut4/sim
TUTROOT=${PWD}
test harnesses. So students must be very diligent in actively deciding whether or not they are
writing synthesizable register-transfer-level models or non-synthesizable code. Students must
always keep in mind what hardware they are modeling and how they are modeling it!
Students design work will almost exclusively use synthesizable register-transfer-level (RTL) models.
It is acceptable to include a limited amount of non-synthesizable code in students designs for the
sole purpose of debugging, assertions, or line tracing. If the student includes non-synthesizable code
in their actual design (i.e., not the test harness), they must explicitly demarcate this code by wrapping
it in ifndef SYNTHESIS and endif. This explicitly documents the code as non-synthesizable and
aids automated tools in removing this code before synthesizing the design. If at any time students
are unclear about whether a specific construct is allowed in a synthesizable concurrent block, they
should ask the instructors.
Appendix A includes a table that outlines which Verilog constructs are allowed in synthesizable
RTL, which constructs are allowed in synthesizable RTL with limitations, and which constructs are
explicitly not allowed in synthesizable RTL. There are no limits on using the Verilog preprocessor,
since the preprocessing step happens at compile time.
1
2
3
4
5
module top;
initial begin
$display( "Hello World!" );
end
endmodule
Code at https://github.com/cbatten/x/blob/master/ex-basics-hello-world.v
Figure 1: Verilog Basics: Display Statement The obligatory Hello, World! program to compiling
a basic Verilog program.
Logical Operators
&
|
^
^~
~
bitwise AND
bitwise OR
bitwise XOR
bitwise XNOR
bitwise NOT
&&
||
!
boolean AND
boolean OR
boolean NOT
Arithmetic Operators
+
-
addition
subtraction
Reduction Operators
&
~&
|
~|
^
^~
>>
<<
>>>
shift right
shift left
arithmetic shift right
Relational Operators
==
!=
>
>=
<
<=
equal
not equal
greater than
greater than or equals
less than
less than or equals
Other Operators
{}
concatenate
{N{}} replicate N times
Table 1: Table of Verilog Operators Not all Verilog operators are shown, just those operators that
are acceptable for use in the synthesizable RTL portion of students designs.
% cd ${TUTROOT}/build
% iverilog -g2012 -o hello-world hello-world.v
The -g2012 option tells iverilog to accept newer SystemVerilog syntax, and the -o specifies the
name of the simulator that iverilog will create. You can run this simulator as follows.
% cd ${TUTROOT}/build
% ./hello-world
As discussed in the Linux tutorial you can compile the Verilog and run the simulator in a single step.
% cd ${TUTROOT}/build
% iverilog -g2012 -o hello-world hello-world.v && ./hello-world
This makes it easy to edit the Verilog source file, quickly recompile, and test your changes by switching to your terminal, pressing the up-arrow key, and then pressing enter.
H
To-Do On Your Own: Edit the string that is displayed in this simple program, recompile, and rerun
the simulator.
3.2. Logic Data Types
To understand any new modeling language we usually start by exploring the primitive data types
for representing values in a model. Verilog uses a combination of the wire and reg keywords which
interact in subtle and confusing ways. SystemVerilog has simplified modeling by introducing the
logic data type. We will be exclusively using logic as the general-purpose, hardware-centric data
type for modeling a single bit or multiple bits. Each bit can take on one of four values: 0, 1, X, Z.
X is used to represent unknown values (e.g., the state of a register on reset). Z is used to represent
high-impedance values. Although we will use variables with X values in this course, we will not use
variables with Z values (although you may see Z values if you forget to connect an input port of a
module).
module top;
2
3
4
5
6
7
logic a;
logic b;
logic c;
8
9
initial begin
10
11
// Single-bit literals
12
13
14
15
16
a
a
a
a
=
=
=
=
1'b0;
1'b1;
1'bx;
1'bz;
$display(
$display(
$display(
$display(
"1'b0
"1'b1
"1'bx
"1'bz
=
=
=
=
%x
%x
%x
%x
",
",
",
",
a
a
a
a
);
);
);
);
17
18
// Bitwise logical operators for doing AND, OR, XOR, and NOT
19
20
21
a = 1'b0;
b = 1'b1;
22
23
24
25
26
c
c
c
c
=
=
=
=
a & b;
a | b;
a ^ b;
~b;
$display(
$display(
$display(
$display(
"0 & 1
"0 | 1
"0 ^ 1
"~1
=
=
=
=
%x
%x
%x
%x
",
",
",
",
c
c
c
c
);
);
);
);
27
28
// Bitwise logical operators for doing AND, OR, XOR, and NOT with X
29
30
31
a = 1'b0;
b = 1'bx;
32
33
34
35
36
c
c
c
c
=
=
=
=
a & b;
a | b;
a ^ b;
~b;
$display(
$display(
$display(
$display(
"0 & x
"0 | x
"0 ^ x
"~x
=
=
=
=
%x
%x
%x
%x
",
",
",
",
c
c
c
c
);
);
);
);
37
38
39
40
41
a = 1'b0;
b = 1'b1;
42
43
44
45
c = a && b;
c = a || b;
c = !b;
46
47
end
48
49
endmodule
Code at https://github.com/cbatten/x/blob/master/ex-basics-logic-sbit.v
Figure 2: Verilog Basics: Single-Bit Logic and Logical Operators Experimenting with single-bit
logic variables and literals, logical bitwise operators, and logical boolean operators.
Table 1 shows the operators that we will be primarily using in this course. Note that Verilog and
SystemVerilog support additional operators including * for multiplication, / for division, % for modulus, ** for exponent, and ===/!=== for special equality checks. There may occasionally be reasons
to use one of these operators in your assertion or line tracing logic. However, these operators are
either not synthesizable or synthesize poorly, so students are not allowed to use these operators in
the synthesizable portion of their designs.
Figure 2 shows an example program that illustrates single-bit logic types. Create a new Verilog
source file named logic-sbit.v and copy some or all of this code. Compile this source file and run
the resulting simulator.
Lines 1316 illustrate how to write single-bit literals to express constant values. Lines 2326 illustrate
basic bitwise logical operators (&, |, , ~). Whenever we consider an expression in Verilog, we should
always ask ourselves, What will happen if one of the inputs is an X? Lines 3336 illustrate what
happens if the second operand is an X for bitwise logical operators. Recall that X means unknown.
If we OR the value 0 with an unknown value we cannot know the result. If the unknown value is
0, then the result should be 0, but if the unknown value is 1, then the result should be 1. So Verilog
specifies that in this case the value of the expression is X. Notice what happens if we AND the value
0 with an unknown value. In this case, we can guarantee that for any value for the second operand
the result will always be 0, so Verilog specifies the value of the expression is 0.
In addition to the basic bitwise logical operators, Verilog also defines three boolean logical operators
(&&, ||, !). These operators are effectively the same as the basic logical operators (&, |, ~) when
operating on single-bit logic values. The difference is really in the designers intent. Using &&, ||, !
suggests that the designer is implementing a boolean logic expression as opposed to doing low-level
bit manipulation.
H
To-Do On Your Own: Experiment with more complicated multi-stage logic expressions by writing
the boolean logic equations for a one-bit full-adder. Use the display system task to output the
result to the console. Experiment with using X input values as inputs to these logic expressions.
Multi-bit logic types are used for modeling bit vectors. Figure 3 shows an example program that
illustrates multi-bit logic types. Create a new Verilog source file named logic-mbit.v and copy
some or all of this code. Compile this source file and run the resulting simulator.
Lines 58 declares multi-bit logic variables. The square brackets contain the index of the mostsignificant and the least-significant bit. In this course, you should always use zero as the index
of the least significant bit. Note that to declare a four-bit logic value, we use [3:0] not [4:0].
Lines 1417 illustrate multi-bit literals that can be used to declare constant values. These literals have
the following general syntax: <bitwidth><base><number> where <base> can be b for binary, h for
hexadecimal, or d for decimal. It is legal to include underscores in the literal, which can be helpful
for improving the readability of long literals.
Lines 2428 illustrate multi-bit versions of the basic bitwise logic operators. As before, we should
always ask ourselves, What will happen if one of the inputs is an X? Lines 3539 illustrate what
happens if two bits in the second value are Xs. Note that some bits in the result are X and some can
be guaranteed to be either a 0 or 1.
Lines 4550 illustrate the reduction operators. These operators take a multi-bit logic value and combine all of the bits into a single-bit value. For example, the OR reduction operator (|) will OR all of
the bits together.
To-Do On Your Own: We will use relational operators (e.g., ==) to compare two multi-bit logic
values, but see if you can achieve the same effect with the bitwise XOR/XNOR operators and
OR/NOR reduction operators.
module top;
2
3
4
5
6
7
8
logic
logic
logic
logic
[ 3:0]
[ 3:0]
[ 3:0]
[11:0]
A;
B;
C;
D;
//
//
//
//
4-bit
4-bit
4-bit
12-bit
logic
logic
logic
logic
variable
variable
variable
variable
9
10
initial begin
11
12
// Multi-bit literals
13
14
15
16
17
A
D
D
D
=
=
=
=
4'b0101;
12'b1100_1010_0101;
12'hca5;
12'd1058;
$display(
$display(
$display(
$display(
"4'b0101
"12'b1100_1010_0101
"12'hca5
"12'd1058
=
=
=
=
%x",
%x",
%x",
%x",
A
D
D
D
);
);
);
);
18
19
// Bitwise logical operators for doing AND, OR, XOR, and NOT
20
21
22
A = 4'b0101;
B = 4'b0011;
23
24
25
26
27
28
C
C
C
C
C
=
=
=
=
=
A & B;
A | B;
A ^ B;
A ^~ B;
~B;
$display(
$display(
$display(
$display(
$display(
"4'b0101 &
"4'b0101 |
"4'b0101 ^
"4'b0101 ^~
"~4'b0011
4'b0011 = %b", C );
4'b0011 = %b", C );
4'b0011 = %b", C );
4'b0011 = %b", C );
= %b", C );
29
30
31
32
33
A = 4'b0101;
B = 4'b00xx;
34
35
36
37
38
39
C
C
C
C
C
=
=
=
=
=
A & B;
A | B;
A ^ B;
A ^~ B;
~B;
$display(
$display(
$display(
$display(
$display(
"4'b0101 &
"4'b0101 |
"4'b0101 ^
"4'b0101 ^~
"~4'b00xx
4'b00xx
4'b00xx
4'b00xx
4'b00xx
" &
"~&
" |
"~|
"^
"^~
=
=
=
=
=
=
=
=
=
=
=
%b",
%b",
%b",
%b",
%b",
C
C
C
C
C
);
);
);
);
);
40
41
// Reduction operators
42
43
A = 4'b0101;
44
45
46
47
48
49
50
C
C
C
C
C
C
=
=
=
=
=
=
&A;
~&A;
|A;
~|A;
^A;
^~A;
$display(
$display(
$display(
$display(
$display(
$display(
4'b0101
4'b0101
4'b0101
4'b0101
4'b0101
4'b0101
%b",
%b",
%b",
%b",
%b",
%b",
C
C
C
C
C
C
);
);
);
);
);
);
51
52
end
53
54
endmodule
Code at https://github.com/cbatten/x/blob/master/ex-basics-logic-mbit.v
Figure 3: Verilog Basics: Multi-Bit Logic and Logical Operators Experimenting with multi-bit
logic variables and literals, bitwise logical operators, and reduction operators.
module top;
2
3
4
5
logic [7:0] A;
logic [7:0] B;
logic [7:0] C;
6
7
initial begin
8
9
10
11
A = 8'b1110_0101;
12
13
14
15
C = A << 1;
C = A << 2;
C = A << 3;
C = A >> 1;
C = A >> 2;
C = A >> 3;
16
17
18
19
20
21
22
23
A = 8'b0110_0100;
24
25
26
27
C = $signed(A) >>> 1;
C = $signed(A) >>> 2;
C = $signed(A) >>> 3;
28
29
A = 8'b1110_0101;
30
31
32
33
C = $signed(A) >>> 1;
C = $signed(A) >>> 2;
C = $signed(A) >>> 3;
34
35
36
37
38
A = 8'b1110_0101;
B = 8'd2;
39
40
41
C = A << B;
C = A >> B;
42
43
end
44
45
endmodule
Code at https://github.com/cbatten/x/blob/master/ex-basics-logic-shift.v
Figure 4: Verilog Basics: Shift Operators Experimenting with logical and arithmetic shift operators
and fixed/variable shift amounts.
9
module top;
2
3
4
5
logic [7:0] A;
logic [7:0] B;
logic [7:0] C;
6
7
initial begin
8
9
10
11
12
A = 8'd28;
B = 8'd15;
13
14
15
C = A + B;
C = A - B;
16
17
18
19
20
A = 8'd250;
B = 8'd15;
21
22
23
C = A + B;
C = B - A;
24
25
end
26
27
endmodule
Code at https://github.com/cbatten/x/blob/master/ex-basics-logic-arith.v
Figure 5: Verilog Basics: Arithmetic Operators Experimenting with arithmetic operators for addition and subtraction.
10
To-Do On Your Own: Try composing relational operators with the boolean logic operators we
learned about earlier in this section to create more complicated expressions.
11
module top;
2
3
4
5
6
7
logic
a; // 1-bit logic variable
logic [ 3:0] A; // 4-bit logic variable
logic [ 3:0] B; // 4-bit logic variable
8
9
initial begin
10
11
// Relational operators
12
13
A = 4'd15; B = 4'd09;
14
15
16
17
18
19
20
a
a
a
a
a
a
=
=
=
=
=
=
(
(
(
(
(
(
A
A
A
A
A
A
==
!=
>
>=
<
<=
B
B
B
B
B
B
);
);
);
);
);
);
$display(
$display(
$display(
$display(
$display(
$display(
"(15
"(15
"(15
"(15
"(15
"(15
==
!=
>
>=
<
<=
9)
9)
9)
9)
9)
9)
=
=
=
=
=
=
%x",
%x",
%x",
%x",
%x",
%x",
a
a
a
a
a
a
);
);
);
);
);
);
21
22
23
24
A = 4'b1100; B = 4'b10xx;
25
26
27
28
29
a
a
a
a
=
=
=
=
(
(
(
(
A
A
A
A
==
!=
>
<
B
B
B
B
);
);
);
);
$display(
$display(
$display(
$display(
"(4'b1100
"(4'b1100
"(4'b1100
"(4'b1100
==
!=
>
<
4'b10xx)
4'b10xx)
4'b10xx)
4'b10xx)
=
=
=
=
%x",
%x",
%x",
%x",
a
a
a
a
);
);
);
);
%x",
%x",
%x",
%x",
a
a
a
a
30
31
32
33
34
35
36
37
38
39
a
a
a
a
=
=
=
=
(
A >
B );
(
A <
B );
( $signed(A) > $signed(B) );
( $signed(A) < $signed(B) );
$display(
$display(
$display(
$display(
"(-1
"(-1
"(-1
"(-1
>
<
>
<
1)
1)
1)
1)
=
=
=
=
);
);
);
);
40
41
end
42
43
endmodule
Code at https://github.com/cbatten/x/blob/master/ex-basics-logic-relop.v
12
module top;
2
3
4
5
6
7
logic
logic
logic
logic
logic
[ 3:0]
[ 3:0]
[ 3:0]
[ 7:0]
[11:0]
A;
B;
C;
D;
E;
//
//
//
//
//
4-bit
4-bit
4-bit
18-bit
12-bit
logic
logic
logic
logic
logic
variable
variable
variable
variable
variable
8
9
initial begin
10
11
// Basic concatenation
12
13
14
15
16
A
B
C
D
=
=
=
=
4'ha;
4'hb;
4'hc;
8'hde;
17
18
19
20
E = { A, B, C };
E = { C, A, B };
E = { B, C, A };
E = { A, D };
E = { D, A };
E = { A, 8'hf0 };
E = { 8'hf0, A };
21
22
23
24
25
26
27
28
// Repeat operator
29
30
31
A = 4'ha;
B = 4'hb;
32
33
34
E = { 3{A} };
$display( "{ 4'ha, 4'ha, 4'ha } = %x", E );
E = { A, {2{B}} }; $display( "{ 4'ha, 4'hb, 4'hb } = %x", E );
35
36
end
37
38
endmodule
Code at https://github.com/cbatten/x/blob/master/ex-basics-logic-concat.v
Figure 7: Verilog Basics: Concatenation Operators Experimenting with the basic concatenation
operator and the repeat operator.
13
To-Do On Your Own: Create your own new enum type for the state variable we will use in the GCD
example later in this tutorial. The new enum type should be called state_t and it should support
three different labels: STATE_IDLE, STATE_CALC, STATE_DONE. Write some code to set and compare
the value of a corresponding state variable.
14
2
3
4
5
6
7
8
9
10
module top;
11
12
// Declare variables
13
14
15
state_t state;
logic
result;
16
17
initial begin
18
19
20
21
22
23
24
state
state
state
state
=
=
=
=
STATE_A;
STATE_B;
STATE_C;
STATE_D;
$display(
$display(
$display(
$display(
"STATE_A
"STATE_B
"STATE_C
"STATE_D
=
=
=
=
%d",
%d",
%d",
%d",
state
state
state
state
);
);
);
);
25
26
// Comparisons
27
28
state = STATE_A;
29
30
31
32
33
34
35
36
37
38
39
40
41
42
end
43
44
endmodule
Code at https://github.com/cbatten/x/blob/master/ex-basics-enum.v
Figure 8: Verilog Basics: Enum Data Types Experimenting with enum data types including setting
the value of an enum using a label and using the equality operator.
15
To-Do On Your Own: Create a new struct type for holding the an RGB color pixel. The struct
should include three fields named red, green, and blue. Each field should be eight bits. Experiment with reading and writing these named fields.
16
2
3
4
5
6
7
// Packed format:
//
11 8 7 4 3 0
// +----+----+----+
// | x | y | z |
// +----+----+----+
8
9
module top;
10
11
// Declare variables
12
13
14
point_t point_a;
point_t point_b;
15
16
17
18
19
20
initial begin
21
22
23
24
25
26
point_a.x = 4'h3;
point_a.y = 4'h4;
point_a.z = 4'h5;
27
28
29
30
31
32
// Assign structs
33
34
point_b = point_a;
35
36
37
38
39
40
41
42
point_bits = point_a;
43
44
45
46
47
48
49
50
51
52
53
54
55
end
56
57
endmodule
Code at https://github.com/cbatten/x/blob/master/ex-basics-struct.v
Figure 9: Verilog Basics: Struct Data Types Experimenting with struct data types including
read/writing fields and converting to/from logic bit vectors.
17
3
4
5
6
10
a;
b;
c;
sel;
initial begin
// ternary statement
11
12
13
a = 8'd30;
b = 8'd16;
14
15
16
c = ( a < b ) ? 15 : 14;
$display( "c = %d", c );
17
18
19
c = ( b < a ) ? 15 : 14;
$display( "c = %d", c );
20
21
22
23
sel = 2'b01;
24
25
26
27
28
29
c =
:
:
:
:
(
(
(
(
sel
sel
sel
sel
==
==
==
==
2'b00
2'b01
2'b10
2'b11
)
)
)
)
?
?
?
?
8'h0a
8'h0b
8'h0c
8'h0d
8'h0e;
30
31
32
33
34
35
sel = 2'bxx;
36
37
38
39
40
c =
:
:
:
:
(
(
(
(
sel
sel
sel
sel
==
==
==
==
2'b00
2'b01
2'b10
2'b11
)
)
)
)
?
?
?
?
8'h0a
8'h0b
8'h0c
8'h0d
8'h0e;
42
43
44
sel = 2'bx0;
46
47
48
49
50
51
c =
:
:
:
:
(
(
(
(
sel
sel
sel
sel
==
==
==
==
2'b00
2'b01
2'b10
2'b11
)
)
)
)
?
?
?
?
8'h0a
8'h0b
8'h0c
8'h0d
8'h0e;
52
53
54
55
a = ( cond_a );
b = !( cond_b );
H
[7:0]
[7:0]
[7:0]
[1:0]
45
logic
logic
logic
logic
41
module top;
end
56
57
18
endmodule
Code at https://github.com/cbatten/x/blob/
master/ex-basics-ternary.v
3.10. If Statements
Figure 11 illustrates using if statements. Create a
new Verilog source file named if.v and copy some
or all of this code. Compile this source file and run
the resulting simulator.
3
4
5
logic [7:0] a;
logic [7:0] b;
logic
sel;
initial begin
8
9
// if statement
10
11
12
a = 8'd30;
b = 8'd16;
13
14
15
16
if ( a == b ) begin
$display( "30 == 16" );
end
17
18
19
20
if ( a != b ) begin
$display( "30 != 16" );
end
21
22
// if else statement
23
24
sel = 1'b1;
25
26
27
28
29
30
31
32
33
34
35
// if else statement w/ X
36
37
sel = 1'bx;
38
39
40
41
42
43
44
module top;
45
46
47
48
end
49
50
endmodule
Code at https://github.com/cbatten/x/blob/
master/ex-basics-if.v
19
5
6
9
10
// case statement
11
12
sel = 2'b01;
13
14
15
16
17
18
19
20
case ( sel )
2'b00
: a
2'b01
: a
2'b10
: a
2'b11
: a
default : a
endcase
=
=
=
=
=
8'h0a;
8'h0b;
8'h0c;
8'h0d;
8'h0e;
21
22
23
24
// case statement w/ X
25
26
sel = 2'bxx;
27
28
29
30
31
32
33
34
case ( sel )
2'b00
: a
2'b01
: a
2'b10
: a
2'b11
: a
default : a
endcase
=
=
=
=
=
8'h0a;
8'h0b;
8'h0c;
8'h0d;
8'h0e;
35
36
37
38
39
40
41
sel = 2'bx0;
42
43
44
46
// Declaring Variables
45
module top;
47
48
49
50
51
case ( sel )
2'b00
: a
2'b01
: a
2'b10
: a
2'b11
: a
2'bx0
: a
2'bxx
: a
default : a
endcase
=
=
=
=
=
=
=
8'h0a;
8'h0b;
8'h0c;
8'h0d;
8'h0e;
8'h0f;
8'h00;
52
53
54
55
end
56
57
endmodule
Code at https://github.com/cbatten/x/blob/
master/ex-basics-case.v
module top;
2
3
4
logic [3:0] a;
logic [7:0] b;
5
6
initial begin
// casez statement
9
10
a = 4'b0100;
11
12
casez ( a )
13
4'b0000
4'b???1
4'b??10
4'b?100
4'b1000
14
15
16
17
18
:
:
:
:
:
b
b
b
b
b
=
=
=
=
=
8'd0;
8'd1;
8'd2;
8'd3;
8'd4;
19
20
21
default : b = 8'hxx;
endcase
22
23
24
25
// casez statement w/ Xs
26
27
a = 4'b01xx;
28
29
casez ( a )
30
4'b0000
4'b???1
4'b??10
4'b?100
4'b1000
31
32
33
34
35
:
:
:
:
:
b
b
b
b
b
=
=
=
=
=
8'd0;
8'd1;
8'd2;
8'd3;
8'd4;
36
37
38
default : b = 8'hxx;
endcase
39
40
41
42
end
43
44
endmodule
Code at https://github.com/cbatten/x/blob/
master/ex-basics-casez.v
4. Registered Incrementer
In this section, we will create our very first Verilog hardware model and learn how to test this module
using waveforms, ad-hoc testing, and a simple unit testing framework. As with PyMTL RTL design,
it is good design practice to usually draw some kind of diagram of the hardware we wish to model
before starting to develop the corresponding Verilog model. This diagram might be a block-level
21
in
8b
+1
8b
out
Figure 14: Block Diagram for Registered Incrementer An eight-bit registered incrementer with an
eight-bit input port, an eight-bit output port, and an
(implicit) clock input.
diagram, a datapath diagram, a finite-state-machine diagram, or even a control signal table; the
more we can structure our Verilog code to match this diagram the more confident we can be that
our model actually models what we think it does. In this section, we wish to model the eight-bit
registered incrementer shown in Figure 14.
4.1. RTL Model of Registered Incrementer
Figure 16 shows the Verilog code which corresponds to the diagram in Figure 14. Every Verilog
file should begin with a header comment as shown on lines 19 in Figure 16. The header comment
should identify the primary module in the file, and include a brief description of what the module
does. Reserve discussion of the actual implementation for later in the file. In general, you should
attempt to keep lines in your Verilog source code to less than 74 characters. This will make your code
easier to read, enable printing on standard sized paper, and facilitate viewing two source files sideby-side on a single monitor. Note that the code in Figure 16 is artificially narrow so we can display
two code listings side-by-side. Lines 1112 create an include guard using the Verilog pre-processor.
An include guard ensures that even if we include this Verilog file multiple times the modules within
the file will only be declared once. Without include guards, the Verilog compiler will likely complain
that the same module has been declared multiple times. Make sure that you have the corresponding
end of the include guard at the bottom of your Verilog source file as shown on line 43.
Unlike Python, Verilog does not have a clean way to manage namespaces for macros and module
names. This means that if you use the same macro or module name in two different files it will create
a namespace collision which can potentially be very difficult to debug. We will follow very specific
naming conventions to eliminate any possibility of a namespace collision. Our convention will to
use the subdirectory path as a prefix for all Verilog macro and module names. Since the registered
incrementer is in the directory tut4_verilog/regincr, we will use TUT4_VERILOG_REGINCR_ as a
prefix for all macro names and tut4_verilog_regincr_ as a prefix for all module names. You can
see this prefix being used for the macros on lines 1112 and for the module name on line 14. To
reiterate, Verilog macro and module name must use the subdirectory path as a prefix. While a bit tedious,
this is essential to avoiding namespace collisions.
As always, we begin by identifying the modules interface which in this case will include an eightbit input port, eight-bit output port, and a clock input. Lines 1520 in Figure 16 illustrate how we
represent this interface using Verilog. A common mistake is to forget the semicolon (;) on line 20. A
couple of comments about the coding conventions that we will be using in this course. All module
names should always include the subproject name as a prefix (e.g., ex_regincr_). The portion of
the name after this prefix should usually use CamelCaseNaming; each word begins with a capital
letter without any underscores (e.g., RegIncr). Port names (as well as variable and module instance
names) should use underscore_naming; all lowercase with underscores to separate words. As shown
on lines 1619, ports should be listed one per line with a two space initial indentation. The bitwidth
specifiers and port names should all line up vertically. As shown on lines 15 and 20, the opening and
closing parenthesis should be on their own separate lines. Carefully group ports to help the reader
understand how these ports are related. Use port names (as well as variable and module instance
22
1
2
3
4
5
6
7
8
9
//======================================
// Registered Incrementer
//======================================
// This is a simple example of a module
// for a registered incrementer which
// combines a positive edge triggered
// register with a combinational +1
// incrementer. We use flat register// transfer-level modeling.
1
2
3
4
5
6
7
8
9
10
11
12
10
`ifndef TUT4_VERILOG_REGINCR_REG_INCR_V
`define TUT4_VERILOG_REGINCR_REG_INCR_V
11
12
13
14
15
16
17
18
19
20
14
15
16
17
18
19
20
25
26
27
28
29
30
22
24
25
26
27
28
29
30
35
36
37
32
34
35
36
37
39
40
endmodule
41
42
43
38
40
41
// Combinational logic
33
38
39
31
// Combinational logic
33
34
// Sequential logic
23
31
32
module tut4_verilog_regincr_RegIncr
(
input
clk,
input
reset,
input [7:0] in,
output [7:0] out
);
21
// Sequential logic
23
24
`ifndef TUT4_VERILOG_REGINCR_REG_INCR_V
`define TUT4_VERILOG_REGINCR_REG_INCR_V
13
module tut4_verilog_regincr_RegIncr
(
input logic
clk,
input logic
reset,
input logic [7:0] in,
output logic [7:0] out
);
21
22
//======================================
// Registered Incrementer
//======================================
// This is a simple example of a module
// for a registered incrementer which
// combines a positive edge triggered
// register with a combinational +1
// incrementer. We use flat register// transfer-level modeling.
endmodule
42
`endif /* TUT4_VERILOG_REGINCR_REG_INCR_V */
43
`endif /* TUT4_VERILOG_REGINCR_REG_INCR_V */
names) that are descriptive; prefer longer descriptive names (e.g., write_en) over shorter confusing
names (e.g., wen).
Lines 2239 model the internal behavior of the module. We usually prefer using two spaces for each
level of indentation; larger indentation can quickly result in significantly wasted horizontal space.
You should always use spaces and never insert any real tab characters into your source code. You must limit
yourself to synthesizable RTL for modeling your design. We will exclusively use two kinds of always
blocks: always_ff concurrent blocks to model sequential logic (analogous to PyMTL @s.tick_rtl
concurrent blocks) and always_comb concurrent blocks to model combinational logic (analogous to
PyMTL @s.combinational concurrent blocks). We require students to clearly distinguishing between the portions of your code that are meant to model sequential logic from those portions meant
to model combinational logic. This simple guideline can save significant frustration by making it
23
easy to see subtle errors. For example, by convention we should only use non-blocking assignments
in sequential logic (e.g., the <= operator on line 27) and we should only use blocking assignments
in combinational logic (e.g., the = operator on line 36). We use the variable reg_out to hold the intermediate value between the register and the incrementer, and we use the variable temp_wire to
hold the intermediate value between the incrementer and the output port. reg_out is modeling a
register while temp_wire is modeling a wire. Notice that both of these variables use the logic data
type; what makes one model a register while the other models a wire is how these variables are used.
The sequential concurrent block update to reg_out means it models a register. The combinational
concurrent block update to temp_wire means it models a wire.
The register incrementer illustrates the two fundamental ways to model combinational logic. We
have used an always_comb concurrent block to model the actual incrementer logic and a continuous
assignment statement (i.e., assign) to model connecting the temporary wire to the output port. We
could just have easily written logic as part of the assign statement. For example, we could have used
assign out = reg_out + 1 and skipped the always_comb concurrent block. In general, we prefer continuous assignment statements over always @(*) concurrent blocks to model combinational
logic, since it is easier to model less-realistic hardware using always_comb concurrent blocks. There is
usually a more direct one-to-one mapping from continuous assignment statements to real hardware.
However, there are many cases where it is significantly more convenient to use always_comb concurrent blocks or just not possible to use continuous assignment statements. Students will need to
use their judgment to determine the most elegant way to represent the hardware they are modeling
while still ensuring there is a clear mapping from the model to the target hardware.
Figure 15 illustrates a few new SystemVerilog constructs. Figure 16 illustrates the exact same registered incrementer implemented using the older Verilog-2001 hardware description language. Verilog2001 uses reg and wire to specify variables instead of logic. All ports are of type wire by default.
Determining when to use reg and wire is subtle and error prone. Note that reg is a misnomer; it
does not model a register! On line 34, we must declare temp_wire to be of type reg even though it is
modeling a wire. Verilog-2001 requires using reg for any variable written by an always concurrent
block. Verilog-2001 uses a generic always block for both sequential and combinational concurrent
blocks. While the always @(*) syntax is an improvement over the need in Verilog-1995 to explicitly
define sensitivity lists, always_ff and always_comb more directly capture designer intent and allow
automated tools to catch common errors. For example, a Verilog simulator can catch errors where a
designer accidentally uses a non-blocking assignment in an always_comb concurrent block, or where
a designer accidentally writes the same logic variable from two different always_comb concurrent
blocks. SystemVerilog is growing in popularity and increasingly becoming the de facto replacement
for Verilog-2001, so it is worthwhile to carefully adopt new SystemVerilog features that can improve
designer productivity.
Edit the Verilog source file named RegIncr.v in the tut4_verilog/regincr subdirectory using your
favorite text editor. Add the combinational logic shown on lines 3439 in Figure 16 which models the
incrementer logic. We will be using iverilog to simulate this registered incrementer module, and
iverilog does not currently support always_ff and always_comb, which is why we are using the
Verilog-2001 construct for now.
4.2. Simulating a Model using iverilog
Now that we have developed a new hardware module, we can test its functionality using a simulation harness. Figure 17 shows an ad-hoc test using non-synthesizable Verilog. Note that we must
explicitly include any Verilog files which contain modules that we want to use; Line 5 includes the
Verilog source file that contains the registered incrementer. Lines 1112 setup a clock with a period
24
1
2
3
//========================================================================
// RegIncr Ad-Hoc Testing
//========================================================================
4
5
`include "../tut4_verilog/regincr/RegIncr.v"
6
7
module top;
8
9
// Clocking
10
11
12
logic clk = 1;
always #5 clk = ~clk;
13
14
15
16
17
18
logic
reset = 1;
logic [7:0] in;
logic [7:0] out;
19
20
21
22
23
24
25
26
tut4_verilog_regincr_RegIncr reg_incr
(
.clk
(clk),
.reset (reset),
.in
(in),
.out
(out)
);
27
28
// Verify functionality
29
30
initial begin
31
32
// Dump waveforms
33
34
35
$dumpfile("regincr-iverilog-sim.vcd");
$dumpvars;
36
37
// Reset
38
39
40
#11;
reset = 1'b0;
41
42
// Test cases
43
44
45
46
47
48
49
in = 8'h00;
#10;
if ( out != 8'h01 ) begin
$display( "ERROR: out, expected = %x, actual = %x", 8'h01, out );
$finish;
end
50
51
52
53
54
55
56
in = 8'h13;
#10;
if ( out != 8'h14 ) begin
$display( "ERROR: out, expected = %x, actual = %x", 8'h14, out );
$finish;
end
57
58
59
60
61
62
63
in = 8'h27;
#10;
if ( out != 8'h28 ) begin
$display( "ERROR: out, expected = %x, actual = %x", 8'h28, out );
$finish;
end
64
65
66
67
68
69
endmodule
25
Figure 17: Simulator for Registered Incrementer A Verilog simulator for the eight-bit registered
incrementer in Figure 16.
of 10 time steps. Notice that we are assigning an initial value to the clk net on line 11 and then
modifying this net every five timesteps; setting initial values such as this is not synthesizable and
should only be used in test harnesses. If you need to set an initial value in your design, you should
use properly constructed reset logic.
Lines 1924 instantiate the device under test. Notice that we use underscore_naming for the module
instance name (e.g., reg_incr). You should almost always use named port binding (as opposed to
positional port binding) to connect nets to the ports in a module instance. Lines 2123 illustrate the
correct coding convention with one port binding per line and the ports/nets vertically aligned. As
shown on lines 20 and 24 the opening and closing parenthesis should be on their own separate lines.
Although this may seem verbose, this coding style can significantly reduce errors by making it much
easier to quickly visualize how ports are connected.
Lines 2862 illustrate an initial block which executes at the very beginning of the simulation.
initial blocks are not synthesizable and should only be used in test harnesses. Lines 3233 instruct the simulator to dump waveforms for all nets. Line 35 is a delay statement that essentially
waits for one timestep. Delay statements are not synthesizable and should only be used in test harnesses. Lines 3944 implement a test by setting the inputs of the device under test, waiting for 10
time steps, and then checking that the output is as expected. If there is an error, we display an error
message and stop the simulation. We include two more tests, and if we make it to the bottom of the
initial block then the test has passed.
Edit the Verilog simulation harness named regincr-iverilog-sim.v in the tut4_verilog/regincr
subdirectory using your favorite text editor. Add the code on lines 2026 in Figure 17 to instantiate the registered incrementer model. Then use iverilog to compile this simulator and run the
simulation as follows:
% cd ${TUTROOT}/build
% iverilog -g2012 -o regincr-iverilog-sim ../tut4_verilog/regincr/regincr-iverilog-sim.v
% ./regincr-iverilog-sim
If everything goes as expected, then the ad-hoc test should display *** PASSED ***.
H
To-Do On Your Own: Edit the register incrementer so that it now increments by +2 instead of +1.
Use an assign statement instead of the always @(*) concurrent block to do the incrementer logic.
Recompile, rerun the ad-hoc test, and verify that the tests no longer pass. Modify the ad-hoc test
so that it includes the correct reference outputs for a +2 incrementer, recompile, rerun the ad-hoc
test, and verify that the test now pass. When you are finished, edit the register incrementer so
that it again increments by +1.
4.3. Verifying a Model with Unit Testing
Writing test and simulation harnesses in Verilog is very tedious. There are some industry standard
verification frameworks based on SystemVerilog, such as the Open Verification Methodology (OVM)
and the Universal Verification Methodology (UVM), but these frameworks are very heavyweight and
are not supported by open-source tools. In this course, we will be using PyMTL for FL modeling, but
also to productively write test and simulation harnesses for our Verilog RTL models. PyMTL includes
support for Verilog import by writing a special PyMTL wrapper model. Once we have created this
wrapper model, we can use all of the sophisticated techniques we learned in the PyMTL tutorial for
writing test and simulation harnesses.
26
1
2
3
#=========================================================================
# RegIncr
#=========================================================================
4
5
6
7
8
9
10
11
vprefix = "tut4_verilog_regincr"
12
13
# Constructor
14
15
def __init__( s ):
16
17
# Port-based interface
18
19
20
21
22
# Verilog ports
23
24
25
26
27
28
29
s.set_ports({
'clk'
: s.clk,
'reset' : s.reset,
'in'
: s.in_,
'out'
: s.out,
})
30
31
# Line Tracing
32
33
34
def line_trace( s ):
return "{} () {}".format( s.in_, s.out )
Figure 18: Registered Incrementer Wrapper PyMTL wrapper for the Verilog module shown in
Figure 14.
Figure 18 illustrates such a PyMTL wrapper. Note that on line 7, we inherit from VerilogModel not
Model. This is how we tell PyMTL that this is a special wrapper model. One line 11, we specify
the vprefix which is the prefixed discussed above for avoiding namespace collisions. The vprefix
must match the prefix used for the name of the Verilog model we are wrapping. The interface on
lines 1920 should be identical to what we would use with a standard PyMTL implementation. On
lines 2429, we must specify how the Verilog ports correspond to the PyMTL ports. The Verilog ports
are listed on the left, and the PyMTL ports are listed on the right. Notice that you must explicitly
include the clk and reset signals. Finally, you can include a line tracing function in the wrapper,
but keep in mind that you can only access the ports of the wrapper for line tracing. We will see later
in this tutorial how we can use line tracing within our Verilog modules.
Once we have a PyMTL wrapper, we can use the exact same test harness we used in the PyMTL
tutorial. The following commands will run the RegIncr_test.py test script.
% cd ${TUTROOT}/build
% py.test ../tut4_verilog/regincr/RegIncr_test.py -sv
% py.test ../tut4_verilog/regincr/RegIncr_extra_test.py -sv
Keep in mind that PyMTL uses the open-source verilator simulator instead of iverilog for simulating Verilog modules. The framework will output the specific verilog command being used to
27
create the simulator. You can us all of the py.test features we learned in the PyMTL tutorial including using the -s option to output line tracing and the --dump-vcd option to output VCD.
% cd ${TUTROOT}/build
% py.test ../tut4_verilog/regincr/RegIncr_test.py -s --dump-vcd
% gtkwave tut4_verilog.regincr.RegIncr_test.test_basic.verilator1.vcd
Note that the actual name of the VCD file will be a little different compared to when we use PyMTL
RTL models. In the lab assignments, we will mostly provide you with the appropriate top-level
PyMTL wrappers, but you may need to write your own wrappers for new child models you want to
test.
As we learned in the PyMTL tutorial, when testing an entire directory, we can use an iterative process
to zoom in on a failing test case. We will start by running all tests in the directory to see an overview
of which tests are passing and which tests are failing. We then explicitly run a single test script with
the -v command line option to see which specific test cases are failing. Finally, we will use the -k or
-x command line options with --tb, -s, and/or --dump-vcd command line option to generate error
output, line traces, and/or waveforms for the failing test case. Here is an example of this three-step
process to zoom in on a failing test case:
%
%
%
%
cd ${TUTROOT}/build
py.test ../tut4_verilog/regincr
py.test ../tut4_verilog/regincr/RegIncr2stage_test.py -v
py.test ../tut4_verilog/regincr/RegIncr2stage_test.py -v -x --tb=short
To-Do On Your Own: Edit the register incrementer so that it now increments by +2 instead of +1.
Recompile, rerun the unit test, and verify that the tests no longer pass. Modify the unit test so
that it includes the correct reference outputs for a +2 incrementer, recompile, rerun the unit test,
and verify that the test now pass. When you are finished, edit the register incrementer so that it
again increments by +1.
4.4. Reusing a Model with Structural Composition
As in PyMTL, we can use modularity and hierarchy to structurally compose small, simple models
into large, complex models. Figure 19 shows a two-stage registered incrementer that uses structural
composition to instantiate and connect two instances of a single-stage registered incrementer. Figure 20 shows the corresponding Verilog module. Line 11 uses a include to include the child model
that we will be reusing. Notice how we must use the full path (from the root of the project) to the
Verilog file we want to include.
Lines 2531 instantiate the first registered incrementer and lines 3541 instantiate the second registered incrementer. As mentioned above, we should almost always used named port binding to
in
8b
8b
RegIncr
RegIncr
out
Figure 19: Block Diagram for Two-Stage Registered Incrementer An eight-bit two-stage
registered incrementer that reuses the registered incrementer in Figure 14 through structural composition.
28
1
2
3
4
5
6
//========================================================================
// RegIncr2stage
//========================================================================
// Two-stage registered incrementer that uses structural composition to
// instantitate and connect two instances of the single-stage registered
// incrementer.
7
8
9
`ifndef TUT4_VERILOG_REGINCR_REG_INCR_2STAGE_V
`define TUT4_VERILOG_REGINCR_REG_INCR_2STAGE_V
10
11
`include "tut4_verilog/regincr/RegIncr.v"
12
13
14
15
16
17
18
19
module tut4_verilog_regincr_RegIncr2stage
(
input logic
clk,
input logic
reset,
input logic [7:0] in,
output logic [7:0] out
);
20
21
// First stage
22
23
24
25
26
27
28
29
30
31
tut4_verilog_regincr_RegIncr reg_incr_0
(
.clk
(clk),
.reset (reset),
.in
(in),
.out
(reg_incr_0_out)
);
32
33
// Second stage
34
35
36
37
38
39
40
41
tut4_verilog_regincr_RegIncr reg_incr_1
(
.clk
(clk),
.reset (reset),
.in
(reg_incr_0_out),
.out
(out)
);
42
43
endmodule
44
45
`endif /* TUT4_VERILOG_REGINCR_REG_INCR_2STAGE_V */
Figure 20: Two-Stage Registered Incrementer An eight-bit two-stage registered incrementer corresponding to Figure 19. This model is implemented using structural composition to instantiate and
connect two instances of the single-stage register incrementer.
connect nets to the ports in a module instance. Lines 2730 illustrate the correct coding convention
with one port binding per line and the ports/nets vertically aligned. As shown on lines 26 and 31 the
opening and closing parenthesis should be on their own separate lines. We usually declare signals
that will be connected to output ports immediately before instantiating the module.
We need to write a new PyMTL wrapper for our two-stage registered incrementer, although it will
be essentially the same as the wrapper shown in Figure 18 except with a different class name. This
illustrates a key point: the PyMTL wrapper simply captures the Verilog interface and is largely unconcerned with the implementation.
29
1
2
3
4
5
6
7
8
module vc_Incrementer
#(
parameter p_nbits
= 1,
parameter p_inc_value = 1
)(
input logic [p_nbits-1:0] in,
output logic [p_nbits-1:0] out
);
9
10
11
12
endmodule
Edit the Verilog source file named RegIncr2stage.v. Add lines 33-41 from Figure 20 to instantiate
and connect the second stage of the two-stage registered incrementer. Then reuse the test harness
from the PyMTL tutorial as follows:
% cd ${TUTROOT}/build
% py.test ../tut4_verilog/regincr/RegIncr2stage_test.py -sv
To-Do On Your Own: Create a three-stage registered incrementer similar in spirit to the two-stage
registered incrementer in Figure 19. Verify your design by writing a test script that uses test
vectors.
4.5. Parameterizing a Model with Static Elaboration
As we learned in the PyMTL tutorial, To facilitate model reuse and productive design-space exploration, we often want to implement parameterized models. A common example is to parameterize
models by the bitwidth of various input and output ports. The registered incrementer in Figure 16 is
designed for only for only eight-bit input values, but we may want to reuse this model in a different
context with four-bit input values or 16-bit input values. We can use Verilog parameters to parameterize the port bitwidth for the registered incrementer shown in Figure 16; we would replace references
to constant 7 with a reference to nbits-1. Now we can specify the port bitwidth for our register
incrementer when we construct the model. We have included a library of parameterized Verilog RTL
models in the vc subdirectory. Figure 21 shows a combinational incrementer from vc that is parameterized by both the port bitwidth and the incrementer amount. The parameters are specified using
the special syntax shown on lines 25. By convention, we use a p_ prefix when naming parameters.
Unfortunately, writing highly parameterized models in Verilog can be very tedious or even impossible, which is one key motivation for the PyMTL framework. Having said this, Verilog-2001 does
provide generate statements which are meant for static elaboration. Recall that static elaboration
happens at compile time, not runtime. We can use static elaboration to generate hardware which is
fundamentally different from modeling hardware. Figure 22 illustrates using generate statements
to create a multi-stage registered incrementer that is parameterized by the number of stages. The
number of stages is specified using the the p_nstages parameter shown on line 13. We create a array
of signals to hold the intermediate values between stages (line 25), and then we use a generate for
loop to instantiate and connect the stages. Using generate statements is one of the more advanced
parts of Verilog, so we will not go into more detail within this tutorial. We can reuse the test harness
from the PyMTL tutorial as follows:
% cd ${TUTROOT}/build
30
1
2
3
4
//========================================================================
// RegIncrNstage
//========================================================================
// Registered incrementer that is parameterized by the number of stages.
5
6
7
`ifndef TUT4_VERILOG_REGINCR_REG_INCR_NSTAGE_V
`define TUT4_VERILOG_REGINCR_REG_INCR_NSTAGE_V
8
9
`include "tut4_verilog/regincr/RegIncr.v"
10
11
12
13
14
15
16
17
18
19
module tut4_verilog_regincr_RegIncrNstage
#(
parameter p_nstages = 2
)(
input logic
clk,
input logic
reset,
input logic [7:0] in,
output logic [7:0] out
);
20
21
22
23
24
25
26
27
28
// Connect the input port of the module to the first signal in the
// reg_incr_out signal array.
29
30
31
32
33
34
35
36
37
genvar i;
generate
for ( i = 0; i < p_nstages; i = i + 1 ) begin: gen
38
39
40
41
42
43
44
45
tut4_verilog_regincr_RegIncr reg_incr
(
.clk
(clk),
.reset (reset),
.in
(reg_incr_out[i]),
.out
(reg_incr_out[i+1])
);
46
47
48
end
endgenerate
49
50
51
52
53
54
55
endmodule
56
57
`endif /* TUT4_VERILOG_REGINCR_REG_INCR_NSTAGE_V */
Figure 22: N-Stage Registered Incrementer A parameterized registered incrementer where the
number of stages is specified using a Verilog parameter.
31
in_val
in0
in1
in2
in3
Stage S1
Stage S2
Stage S3
n
n
min
max
min
max
min
max
n
n
min
max
min
max
n
n
out_val
out0
out1
out2
out3
Figure 23: Block Diagram for Four-Element Sorter An n-bit pipelined four-element sorter which
arranges the four elements in ascending order.
% py.test ../tut4_verilog/regincr/RegIncrNstage_test.py -sv
5. Sort Unit
The previous section introduces the key Verilog concepts and primitives that we will use to implement more complex RTL models including: declaring a port-based module interface; declaring internal state and wires using logic variables; declaring always @( posedge clk ) concurrent blocks
to model logic that executes on every rising clock edge; declaring always @(*) concurrent blocks to
model combinational logic that executes one or more times within a clock cycle; and creating PyMTL
wrappers.
In this section, we will explore how we can implement the same sorting unit previously seen in the
PyMTL tutorial except using the Verilog hardware description language. As a reminder, the simple
pipelined four-element sorter is shown in Figure 23. Each min/max unit compares its inputs and
sends the smaller value to the top output port and the larger value to the bottom output. This specific
implementation is pipelined into three stages, such that the critical path should be through a single
min/max unit. Input and output valid signals indicate when the input and output elements are
valid. Most of the code for this section is provided for you in the tut4_verilog/sort subdirectory.
5.1. Flat Sorter Implementation
We will begin by exploring a flat implementation of the sorter that does not instantiate any additional
child modules. This implementation is provided for you in the file named SortUnitFlatRTL.v. Figure 24 illustrates the Verilog code that describes the interface for the sorter. Notice how we have
parameterized the interface by the bitwidth of each element. Lines 24 declare a parameter named
p_nbits and give it a default value of one bit. We use this parameter when declaring the bitwidth of
the input and output ports, and we will also use this parameter in the implementation.
Figure 25 shows the first pipeline stage of the flat implementation of the sorter. Notice how we
use the parameter p_nbits to declare various internal registers and wires. We cleanly separate the
sequential logic from the combinational logic. We use comments and explicit suffixes to make it clear
what pipeline stage we are modeling.
The corresponding wrapper is in SortUnitFlatRTL.py and is shown in Figure 26. There are two
important differences from the PyMTL wrapper for the registered incrementer. On line 12, we set
32
1
2
3
4
5
6
module tut4_verilog_sort_SorterFlat
#(
parameter p_nbits = 1
)(
input logic
clk,
input logic
reset,
7
8
9
10
11
12
input
input
input
input
input
logic
logic
logic
logic
logic
[p_nbits-1:0]
[p_nbits-1:0]
[p_nbits-1:0]
[p_nbits-1:0]
in_val,
in0,
in1,
in2,
in3,
output
output
output
output
output
logic
logic
logic
logic
logic
[p_nbits-1:0]
[p_nbits-1:0]
[p_nbits-1:0]
[p_nbits-1:0]
out_val,
out0,
out1,
out2,
out3
13
14
15
16
17
18
19
);
vlinetrace to be True. This tells the PyMTL framework that the Verilog module includes its own
line tracing code. On lines 2830, we can specify how the PyMTL parameters (e.g., nbits) correspond
to the Verilog parameters (e.g., p_nbits). We can run the unit tests for this module as follows:
% cd ${TUTROOT}/build
% py.test ../tut4_verilog/sort/SortUnitFlatRTL_test.py
The design should pass all of the tests.
H
To-Do On Your Own: The sorter currently sorts the four input numbers from smallest to largest.
Change to the sorter implementation so it sorts the numbers from largest to smallest. Recompile
and rerun the unit test and verify that the tests are no longer passing. Modify the tests so that
they correctly capture the new expected behavior. Make a copy of the sorter implementation file
so you can put things back to the way they were when you are finished.
33
1
2
3
4
5
6
7
8
9
logic
logic
logic
logic
logic
[p_nbits-1:0]
[p_nbits-1:0]
[p_nbits-1:0]
[p_nbits-1:0]
val_S1;
elm0_S1;
elm1_S1;
elm2_S1;
elm3_S1;
10
11
12
13
14
15
16
17
always_ff
val_S1
elm0_S1
elm1_S1
elm2_S1
elm3_S1
end
@(
<=
<=
<=
<=
<=
18
19
20
21
22
23
24
25
26
27
28
29
30
logic
logic
logic
logic
[p_nbits-1:0]
[p_nbits-1:0]
[p_nbits-1:0]
[p_nbits-1:0]
elm0_next_S1;
elm1_next_S1;
elm2_next_S1;
elm3_next_S1;
31
32
always_comb begin
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
end
Figure 25: First Stage of the Flat Sorter Implementation First pipeline stage of the sorter using a
flat implementation corresponding to the diagram in Figure 23.
34
1
2
3
#=========================================================================
# SortUnitFlatRTL
#=========================================================================
4
5
6
7
8
9
10
11
12
vprefix = "tut4_verilog_sort"
vlinetrace = True
13
14
# Constructor
15
16
17
18
# Interface
19
20
21
s.in_val
s.in_
= InPort (1)
= [ InPort (nbits) for _ in range(4) ]
22
23
24
s.out_val = OutPort(1)
s.out
= [ OutPort (nbits) for _ in range(4) ]
25
26
# Verilog parameters
27
28
29
30
s.set_params({
'p_nbits' : nbits,
})
31
32
# Verilog ports
33
34
35
36
s.set_ports({
'clk'
: s.clk,
'reset'
: s.reset,
37
38
39
40
41
42
'in_val'
'in0'
'in1'
'in2'
'in3'
:
:
:
:
:
s.in_val,
s.in_[0],
s.in_[1],
s.in_[2],
s.in_[3],
'out_val'
'out0'
'out1'
'out2'
'out3'
:
:
:
:
:
s.out_val,
s.out[0],
s.out[1],
s.out[2],
s.out[3],
43
44
45
46
47
48
49
})
Figure 26: Sort Unit Wrapper PyMTL wrapper for the Verilog RTL implementation of the flat sort
unit.
35
1
2
3
4
5
6
7
8
Figure 27: Line Trace Output for Sort Unit RTL Model This line trace is for the test_basic test
case and is annotated to show what each column corresponds to in the model. Each line corresponds
to one (and only one!) cycle, and the fixed-width columns correspond to either the state at the
beginning of the corresponding cycle or the output of combinational logic during that cycle. If the
valid bit is not set, then the corresponding list of values is not shown.
1
`ifndef SYNTHESIS
2
3
4
5
6
7
// Inputs
8
9
10
11
12
13
...
14
15
16
end
`VC_TRACE_END
17
18
`endif /* SYNTHESIS */
Figure 28: Example of Line Tracing Code This code generates the first fixed-width column for the
line trace shown in Figure 27
36
As mentioned above, we provide a verilog component (VC) library with various useful Verilog modules, including one module in vc/trace.v which makes it easier to create line traces in your own
Verilog modules. Figure 28 shows a small snippet of code that is used in the sorter implementation
to trace the input ports. Notice how we have wrapped the line tracing code in ifndef SYNTHESIS
and endif to clearly indicate that this code is not synthesizable even though it is included in out
design. Line 3 declares a temporary string variable that we will use when converting nets into
strings. Lines 416 use helper macros to declare a task called trace which takes a special argument called trace_str that holds the current line trace string. The job of this task is to append
trace information to the trace_str that describes the current state and operation of this module.
The vc_trace.append_str and vc_trace.append_val_str helper tasks add strings to the line trace.
You can also build line traces hierarchically by explicitly calling the trace task on a child module.
Although we will provide significant line tracing code in each lab harness, you are also strongly
encouraged to augment this code with your own line tracing.
H
To-Do On Your Own: Modify the line tracing code to show the pipeline stage label (in, S1, S2, S3,
out) before each stage. After your modifications, the line trace might look something like this:
24: in:{05,07,06,08}|S1:{a5,a3,a2,a7}|S2:{03,04,01,02}|S3:
|out:
37
1
2
3
4
5
logic val_S1;
6
7
8
9
10
11
12
13
vc_ResetReg#(1) val_S0S1
(
.clk
(clk),
.reset (reset),
.d
(in_val),
.q
(val_S1)
);
14
15
16
17
18
19
20
21
22
logic
logic
logic
logic
[p_nbits-1:0]
[p_nbits-1:0]
[p_nbits-1:0]
[p_nbits-1:0]
elm0_S1;
elm1_S1;
elm2_S1;
elm3_S1;
23
24
25
26
27
vc_Reg#(p_nbits)
vc_Reg#(p_nbits)
vc_Reg#(p_nbits)
vc_Reg#(p_nbits)
elm0_S0S1(
elm1_S0S1(
elm2_S0S1(
elm3_S0S1(
clk,
clk,
clk,
clk,
elm0_S1,
elm1_S1,
elm2_S1,
elm3_S1,
in0
in1
in2
in3
);
);
);
);
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
tut4_verilog_sort_MinMaxUnit#(p_nbits) mmuA_S1
(
.in0
(elm0_S1),
.in1
(elm1_S1),
.out_min (mmuA_out_min_S1),
.out_max (mmuA_out_max_S1)
);
43
44
45
46
47
48
49
50
51
52
53
tut4_verilog_sort_MinMaxUnit#(p_nbits) mmuB_S1
(
.in0
(elm2_S1),
.in1
(elm3_S1),
.out_min (mmuB_out_min_S1),
.out_max (mmuB_out_max_S1)
);
Figure 29: First Stage of the Structural Sorter Implementation First pipeline stage of the sorter
using a structural implementation corresponding to the diagram in Figure 23.
38
To-Do On Your Own: The structural implementation is incomplete because the actual implementation of the min/max unit in MinMaxUnit.v is not finished. You should go ahead and implement
the min/max unit, and then as always you should write a unit test to verify the functionality of your
MinMax unit! Add some line tracing for the min/max unit. You should have enough experience based on the previous sections to be able to create a unit test from scratch and run it using
py.test. You should name the new test script MinMaxUnit_test.py. You can use the registered
incrementer model as an example for both implementing the min/max unit and for writing the
corresponding test script. Once your min/max unit is complete and tested, then test the structural sorter implementation like this:
% cd ${TUTROOT}/build
% py.test ../tut4_verilog/sort/SortUnitStructRTL_test.py -v
% py.test ../tut4_verilog/sort/SortUnitStructRTL_test.py -k test_basic -s
The line trace for the sort unit structural RTL model should be the same as in Figure 27, since
these are really just two different implementations of the sort unit RTL.
5.4. Evaluating the Sorter Using a Simulator
Just like we can use PyMTL test harnesses to verify Verilog modules, we can also use PyMTL simulation harnesses for evaluation. Since our PyMTL wrapper has the exact same interface as a PyMTL
RTL implementation, the exact same simulator from the PyMTL tutorial will work without change.
% cd ${TUTROOT}/build
% ../tut4_verilog/sort/sort-sim --stats --impl rtl-flat
% ../tut4_verilog/sort/sort-sim --stats --impl rtl-struct
To-Do On Your Own: Experiment with the algorithm using a Python interpreter. Try calculating
the GCD for different input values. Add additional debugging output to track what the algorithm
is doing each iteration.
39
req.msg
32
16
GCD
req.val
req.rdy
req_q
resp_q
resp.msg
resp.val
resp.rdy
7
8
req_msg.b
!req_val
is_b_zero
IDLE
zero?
req_val
b_reg
sub
req_msg.a
req_msg
less
than?
a_mux_sel
Figure 31: Euclids GCD Algorithm Iteratively subtract the smaller value from the
larger value until one of them is zero, at
which time the GCD is the non-zero value.
This is executable Python code.
resp_msg
is_a_lt_b
b_mux b_reg
_sel
_en
def gcd( a, b ):
while True:
if a < b:
a,b = b,a
elif b != 0:
a = a - b
else:
return a
a_reg
a<b
/swap
Figure 32: Datapath Diagram for GCD Datapath includes two state registers and required muxing and arithmetic units to iteratively implement Euclids algorithm.
40
b != 0
/sub
b == 0
!resp_rdy
a_reg_en
CALC
DONE
resp_rdy
from the VC library (e.g., vc_Mux2, vc_ZeroComparator, vc_Subtractor). You should use a similar
structural approach when building your own datapaths for this course. For a net that moves data
from left to right in the datapath diagram, we usually declare a dedicated wire right before the
module instance (e.g., a_mux_out and a_reg_out). For a net that moves data from right to left in
the datapath diagram, we need to declare a dedicated wire right before it is used as an input (e.g.,
b_reg_out and b_sub_out).
Take a look at the control unit and notice the stylized way we write FSMs. An FSM-based control
unit should have three parts: a sequential concurrent block for the state, a combinational concurrent
block for the state transitions, and a combinational concurrent block for the state outputs. We often
use case statements to compactly represent the state transitions and outputs. Figure 36 shows the
portion of the FSM responsible for setting the output signals. We use a task to set all of the control
signals in a single line; as long as the task does not include non-synthesizable constructs (e.g., delay
statements or system tasks) the task itself should be synthesizable. Essentially, we have created a
control signal table in our Verilog code which exactly matches what we might draw on a piece of
paper. There is one row for each state or Mealy transition and one column for each control signal.
These compact control signal tables simplify coding complicated FSMs (or indeed other kinds of
control logic) and can enable a designer to quickly catch bugs (e.g., are the enable signals always set
to either zero or one?).
Figure 37 shows a portion of the top-level module that connects the datapath and control unit together. Lines X and Y use the new implicit connection operator (.*) provided by SystemVerilog.
Using the implicit connection operator during module instantiation means to connect every port to a
signal with the same name. So these two lines take care of connecting all of the control and status signals. This is a powerful way to write more compact code which avoids connectivity bugs, especially
when connecting the datapath and control unit.
We can use the exact same source/sink testing that we used in the PyMTL tutorial to test our Verilog
implementation of the GCD unit. As a reminder, Figure 34 illustrates the overall connectivity in the
test harness. The test source includes the ability to randomly delay messages going into the DUT
and the test sink includes the ability to randomly apply back-pressure to the DUT. By using various
combinations of these random delays we can more robustly ensure that our flow-control logic is
working correctly. We can reuse the same test harnesses from the PyMTL tutorial as follows:
% cd ${TUTROOT}/build
% py.test ../tut4_verilog/gcd/GcdUnitRTL_test.py -v
% py.test ../tut4_verilog/gcd/GcdUnitRTL_test.py -sv -k basic_0x0
Figure 38 illustrates a portion of the line trace for the randomized testing. We use the line trace to
show the state of the A and B registers at the beginning of each cycle and use specific characters to
req_msg
Test Source
req_val
req_rdy
resp_msg
GCD Unit
resp_val
resp_rdy
Test Sink
Figure 34: Verifying GCD Using Test Sources and Sinks Parameterized test sources send a stream
of messages over a val/rdy interface, and parameterized test sinks receive a stream of messages over
a val/rdy interface and compare each message to a previously specified reference message. Clock
and reset signals are not shown.
41
1
2
3
4
module tut4_verilog_gcd_GcdUnitDpath
(
input logic
clk,
input logic
reset,
// Data signals
6
7
8
9
10
// Control signals
11
12
input
input
input
input
13
14
15
16
logic
logic
logic [1:0]
logic
a_reg_en,
b_reg_en,
a_mux_sel,
b_mux_sel,
//
//
//
//
is_b_zero,
is_a_lt_b
17
// Status signals
18
19
20
21
22
);
output logic
output logic
23
24
25
26
27
28
29
30
31
// A Mux
32
33
34
35
36
37
38
39
40
41
42
43
44
vc_Mux3#(c_nbits) a_mux
(
.sel
(a_mux_sel),
.in0
(req_msg_a),
.in1
(b_reg_out),
.in2
(sub_out),
.out
(a_mux_out)
);
45
46
// A register
47
48
49
50
51
52
53
54
55
56
57
vc_EnReg#(c_nbits) a_reg
(
.clk
(clk),
.reset (reset),
.en
(a_reg_en),
.d
(a_mux_out),
.q
(a_reg_out)
);
Figure 35: Portion of GCD Datapath Unit We use struct types to encapsulate both control and
status signals and we use a preprocessor macro from the GCD message struct to determine how to
size the datapath components.
42
1
2
3
4
5
6
7
8
localparam
localparam
localparam
localparam
a_x
a_ld
a_b
a_sub
=
=
=
=
2'dx;
2'd0;
2'd1;
2'd2;
9
10
11
12
localparam b_x
localparam b_ld
localparam b_a
= 1'dx;
= 1'd0;
= 1'd1;
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
task cs
(
input logic
input logic
input logic
input logic
input logic
input logic
);
begin
req_rdy
=
resp_val =
a_reg_en =
b_reg_en =
a_mux_sel =
b_mux_sel =
end
endtask
cs_req_rdy,
cs_resp_val,
[1:0] cs_a_mux_sel,
cs_a_reg_en,
cs_b_mux_sel,
cs_b_reg_en
cs_req_rdy;
cs_resp_val;
cs_a_reg_en;
cs_b_reg_en;
cs_a_mux_sel;
cs_b_mux_sel;
32
33
34
35
36
logic do_swap;
logic do_sub;
37
38
39
40
41
42
43
always_comb begin
44
45
46
47
48
49
50
51
52
53
a mux a
sel
en
a_ld, 1,
a_b,
1,
a_sub, 1,
a_x,
0,
a_x, 'x,
b mux b
sel
en
b_ld, 1 );
b_a, 1 );
b_x, 0 );
b_x, 0 );
b_x, 'x );
54
55
endcase
56
57
end
Figure 36: Portion of GCD FSM-based Control Unit for State Outputs We can use a task to create
a control signal table with one row per state or Mealy transition and one column per control signal.
Local parameters can help compactly encode various control signal values.
43
1
2
3
4
module tut4_verilog_gcd_GcdUnitRTL
(
input logic
clk,
input logic
reset,
5
6
7
8
input logic
output logic
input logic [31:0]
req_val,
req_rdy,
req_msg,
output logic
input logic
output logic [15:0]
resp_val,
resp_rdy,
resp_msg
9
10
11
12
13
);
14
15
16
17
18
19
// Control signals
20
21
22
23
24
logic
logic
logic [1:0]
logic
a_reg_en;
b_reg_en;
a_mux_sel;
b_mux_sel;
25
26
// Data signals
27
28
29
logic
logic
is_b_zero;
is_a_lt_b;
30
31
// Control unit
32
33
34
35
36
tut4_verilog_gcd_GcdUnitCtrl ctrl
(
.*
);
37
38
// Datapath
39
40
41
42
43
tut4_verilog_gcd_GcdUnitDpath dpath
(
.*
);
44
45
endmodule
Figure 37: Portion of GCD Top-Level Module We use the new implicit connection operator (.*)
to automatically connect all of the control and status signals to both the control unit and datapath.
indicate which state we are in (i.e., I = idle, Cs = calc with swap, C- = calc with subtract, D = done).
We can see that the test source sends a new message into the GCD unit on cycle 296. The GCD unit
is in the idle state and transitions into the calc state. It does five subtractions and a final swap before
transitioning into the done state on cycle 304. The result is valid but the test sink is not ready, so the
GCD unit waits in the done state until cycle 310 when it is able to send the result to the test sink. On
cycle 311 the GCD unit accepts a new input message to work on. This is a great example of how an
effective line trace can enable you to quickly visualize how a design is actually working.
44
cycle src
sink
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
296:
297:
298:
299:
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
002d00e1
#
#
#
#
#
#
#
#
#
#
#
#
#
#
002200cc
#
#
#
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
00e1:002d(0002
#
(00e1
#
(00b4
#
(0087
#
(005a
#
(002d
#
(0000
#
(002d
#
(002d
#
(002d
#
(002d
#
(002d
#
(002d
#
(002d
#
(002d
00cc:0022(002d
#
(00cc
#
(00aa
#
(0088
0000
002d
002d
002d
002d
002d
002d
0000
0000
0000
0000
0000
0000
0000
0000
0000
0022
0022
0022
I )
C-)
C-)
C-)
C-)
C-).
Cs)
C )
D )#
D )#
D )#
D )#
D )#
D )#
D )002d
I )
C-)
C-)
C-)
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
Figure 38: Line Trace for RTL Implementation of GCD State of A and B registers at the beginning of the cycle is shown,
along with the current state of the FSM.
I = idle, Cs = calc with swap, C- = calc with
subtract, D = done.
#
#
#
#
#
#
002d
To-Do On Your Own: Optimize the GCD implementation to improve the performance on the given
input datasets.
A first optimization would be to transition into the done state if either a or b are zero. If a is zero
and b is greater than zero, we will swap a and b and then end the calculation on the next cycle
anyways. You will need to carefully modify the datapath and control so that the response can
come from either the a or b register.
A second optimization would be to avoid the bubbles caused by the IDLE and DONE states.
First, add an edge from the CALC state directly back to the IDLE state when the calculation is
complete and the response interface is ready. You will need to carefully manage the response
valid bit. Second, add an edge from the CALC state back to the CALC state when the calculation
is complete, the response interface is ready, and the request interface is valid. These optimizations
should eliminate any bubbles and improve the performance of back-to-back GCD transactions.
A third optimization would be to perform a swap and subtraction in the same cycle. This will
require modifying both the datapath and the control unit, but should have a significant impact
on the overall performance.
6.2. Evaluating GCD Unit using a Simulator
As with the previous section, we have provided a simulator for evaluating the performance of the
GCD implementation. In this case, we are focusing on a single implementation with two different
input datasets. You can run the simulators and look at the average number of cycles to compute a
GCD for each input dataset like this:
% cd ${TUTROOT}/build
% ../tut4_verilog/gcd/gcd-sim --stats --impl cl --input random
% ../tut4_verilog/gcd/gcd-sim --stats --impl rtl --input random
45
46
Acknowledgments
This tutorial was developed for ECE 4750 Computer Architecture course at Cornell University by
Christopher Batten.
47
Always Allowed in
Synthesizable RTL
logic
logic [N-1:0]
& | ^ ^~ ~ (bitwise)
&& || !
& ~& | ~| ^ ^~ (reduction)
+ >> << >>>
== != > <= < <=
{}
{N{}} (repeat)
?:
always_ff, always_comb
if else
case, endcase
begin, end
module, endmodule
input, output
assign
parameter
localparam
genvar
generate, endgenerate
generate for
generate if else
generate case
$clog2()
$bits()
named port connections
named parameter passing
Allowed in
Synthesizable RTL
With Limitations
Explicitly
Not Allowed in
Synthesizable RTL
always1
enum2
struct3
casez, endcase4
task, endtask5
function, endfunction5
= (blocking assignment)6
<= (non-blocking assignment)7
typedef8
packed9
$signed()10
read-modify-write signal11
wire, reg12
integer, real, time, realtime
signed13
===, !==
* / % ** 14
#N (delay statements)
inout15
initial
variable initialization16
negedge17
casex, endcase
for, while, repeat, forever18
fork, join
deassign, force, release
specify, endspecify
nmos, pmos, cmos
rnmos, rpmos, rcmos
tran, tranif0, tranif1
rtran, rtranif0, rtranif1
supply0, supply1
strong0, strong1
weak0, weak1
primitive, endprimitive
defparam
unnamed port connections19
unnamed parameter passing20
all other keywords
all other system tasks
1 Students should prefer using always_ff and always_comb instead of always. If students insist on using
always, then it can only be used in one of the following two constructs: always @(posedge clk) for sequential logic, and always @(*) for combinational logic. Students are not allowed to trigger sequential
blocks off of the negative edge of the clock or create asynchronous resets, nor use explicit sensitivity lists.
2 enum can only be used with an explicit base type of logic and explicitly setting the bitwidth using the following syntax: typedef enum logic [$clog2(N)-1:0] { ... } type_t; where N is the number of labels
in the enum. Anonymous enums are not allowed.
3 struct can only be used with the packed qualifier (i.e., unpacked structs are not allowed) using the following syntax: typedef struct packed { ... } type_t; Anonymous structs are not allowed.
4 casez can only be used in very specific situations to compactly implement priority encoder style hardware
structures.
5 task and function blocks must themselves contain only synthesizable RTL.
48
Blocking assignments should only be used in always_comb blocks and are explicitly not allowed in
always_ff blocks.
Non-blocking assignments should only be used in always_ff blocks and are explicitly not allowed in
always_comb blocks.
10 Reading a signal, performing some arithmetic on the corresponding value, and then writing this value back
to the same signal (i.e., read-modify-write) is not allowed within an always_comb concurrent block. This is
a combinational loop and does not model valid hardware. Read-modify-write is allowed in an always_ff
concurrent block with a non-blocking assignment, although we urge students to consider separating the
sequential and combinational logic. Students can use an always_comb concurrent block to read the signal, perform some arithmetic on the corresponding value, and then write a temporary wire; and use an
always_ff concurrent block to flop the temporary wire into the destination signal.
11 $signed() can only be used around the operands to >>>, >, >=, <, <= to ensure that these operators perform
the signed equivalents.
12 wire and reg are perfectly valid, synthesizable constructs, but logic is much cleaner. So we would like
students to avoid using wire and reg.
13 signed types can sometimes be synthesized, but we do not allow this construct in the course.
14 Multipliers can be synthesized and small multipliers can even be synthesized efficiently, but we do not
allow this construct in the course. If you need to multiply or divide by a power of two, then you can use
the left and right shift operators.
15 Ports with inout can be used to create tri-state buses, but tools often have trouble synthesizing hardware
from these kinds of models.
16 Variable initialization means assigning an initial value to a logic variable when you declare the variable.
This is not synthesizable; it is not modeling real hardware. If you need to set some state to an initial
condition, you must explicitly use the reset signal.
17 Triggering a sequential block off of the negedge of a signal is certainly synthesizable, but we will be exclusively using a positive-edge-triggered flip-flop-based design style.
18 If you would like to generate hardware using loops, then you should use generate blocks.
19 In very specific, rare cases unnamed port connections might make sense, usually when there are just one or
two ports and there purpose is obvious from the context.
20 In very specific, rare cases unnamed parameter passing might make sense, usually when there are just one
or two parameters and their purpose is obvious from the context.
49