ECE 2300 Coding Conventions
Any significant hardware design project will usually require developers to use a standardized set of coding conventions. These conventions may be set by company leaders, owners of an open-source project, or simply through historical precedent. Standardized coding conventions allows the code we write to be consistent with others using the same convention and improves readability, maintainability, and extensibility. We have developed a simple set of coding conventions for ECE 2300 which students are required to use in all lab assignments. Keep in mind that these are just guidelines, and there may be situations where it is appropriate to defy a convention if this ultimately improves the overall code quality. These guidelines cover Verilog hardware designs implemented using gate-level (GL) modeling and register-transfer-level (RTL) modeling as well as Verilog test benches and Verilog interactive simulators.
1. Directories and Files
This section discusses the physical structure of how files should be organized in a project. In ECE 2300, we will provide you all of the files you need and they will already be organized into the appropriate directories. However, it can still be useful to understand the overall organization.
1.1 Directories
A project is comprised of multiple subprojects (e.g., each lab will be a
separate subproject). Each subproject is a subdirectory with two
additional subdirectories for test benches and interactive simulators. If
we have a subproject named fb
then it would be organized as follows.
Verilog files with hardware modules are in the fb
subdirectory. Verilog
files with test benches are in the fb/test
subdirectory. Verilog files
with interactive simulators are in the fb/sim
subdirectory.
1.2 File Names
All Verilog files should use the .v
filename extension.
In general, each Verilog hardware module should be in a separate file,
and the name of the file should match the name of the Verilog hardware
module. If a Verilog file contains a Verilog hardware module named
FooBar_GL
, then the name of the Verilog file should be FooBar_GL.v
.
In general, each Verilog hardware module should have its own Verilog test
bench file with the same name as the Verilog hardware module and a
-test
suffix. So the Verilog test bench file for FooBar_GL
would be
named FooBar_GL-test.v
. Sometimes we will factor out test cases so we
can share them across multiple test benches in which case the
corresponding Verilog file will have a -test-cases
suffix.
Verilog files for interactive simulators usually use all lowercase, a
dash (-
) as a separator, and a -sim
suffix. So the interactive
simulator for the FooBar
hardware module might be named
foo-bar-sim.v
.
1.3 Includes
To instantiate a Verilog module in a different Verilog module you must
explicitly include the appropriate Verilog hardware module file using the
Verilog include
preprocessor directive. You should use the complete
path starting at the root of the project. So for example, if we wanted to
instantiate the FooBar_GL
module in a different module we will use the
following:
1.4 Include Guards
All Verilog hardware module files should have include guards. These make
sure that the contents of a Verilog file are only included once in the
overall project, even if they are included multiple times from different
files. An include guard uses the Verilog ifndef
/endif
and define
preprocessor directives as follows.
`ifndef FOO_BAR_GL_V
`define FOO_BAR_GL_V
// ... FooBar_GL module definition ...
`endif /* FOO_BAR_GL_V */
We name the guard with the same name as the Verilog file but using all
caps, underscore (_
) as a separator, and a _V
suffix.
2. Formatting
This section discusses general formatting of files across all kinds of files.
2.1 Line Length
In general, you should attempt to keep the length of a line in your file to less than 74 characters. This amount of characters is ideal as it makes code easier to read, enables printing on standard sized paper, and allows the viewing of two files side-by-side on a modern laptop or four files on 24" to 27" inch monitors. Lines longer than 80 characters should be avoided unless there is a compelling reason to use longer lines to increase code quality.
2.2 Indentation
Absolutely no tabs are allowed. Only spaces are allowed for the purposes of indentation. The standard number of spaces per level of indentation is two. Note that in VS Code usually when you press the tab key it will actually insert spaces which is fine. We just want to avoid real tab characters inserted into Verilog files since this means the code formatting requires every reader to use the same tabstop settings.
2.3 Vertical Whitespace
Vertical whitespace can and should be used to separate conceptually distinct portions of your code. A blank line within a block of code serves like a paragraph break in prose: visually separating two thoughts. Vertical whitespace should be limited to a single blank line. Do not use two or more blank lines in a row.
2.4 Horizontal Whitespace
In general, whitespace should be used to separate distinct conceptual "tokens". Do not cram all of the characters in an expression together without any horizontal whitespace. Additional horizontal whitespace can be used to align visual columns when appropriate.
3. Naming
Proper naming is critical to enable the reader to quickly understand your code.
3.1 Port and Wire Names
All port and wire names should use snake
case which means we use
lower-case letters and an underscore (_
) to separate words. Do not
use camel case for port or wire names. Single letter port or wire names
should be used sparingly. Use port and wire names that clearly indicate
the purpose of the signal.
3.2 Module Names
All module names should use camel
case which means we use
upper-case letters and no other separator to separate words. Module names
might also include a suffix separated by an underscore (_
) to specify
the bitwidth and level of modeling. For example, a 32-bit ripple-carry
adder implemented using GL modeling would be named
AdderRippleCarry_32b_GL
and a 32-bit adder implemented using
register-transfer-level modeling would be named Adder_32b_RTL
. A
bitwidth suffix does not make sense for all kinds of hardware modules.
For example, a module which is parameterized by the bitwidth does not
have a bitwidth suffix. So an adder parameterized by the bitwidth
implemented using RTL modeling would be named Adder_RTL
. While a
bitwidth suffix does not make sense for all kinds of hardware modules,
all hardware modules must have a suffix indicating the level of
modeling.
3.3 Instance Names
Module instance names should use snake case which means we use lower-case
letters and an underscore (_
) to separate words. Do not use camel
case for module instance names. Use module instance names that clearly
indicate the purpose of the instantiated module.
Unlike modern programming languages like Python or C++, Verilog does not have a clean way to manage namespaces for module names. This means if you define modules with the same name in two different files, then it could cause a namespace collision which can be difficult to debug. Thus module names must be unique across the entire project.
4. Gate-Level (GL) Modeling
This section focuses on coding conventions suitable for gate-level (GL) modeling.
4.1 GL Allowable Constructs
In GL modeling, students can only use the following Verilog constructs:
wire
(single bit and multiple bit)not
,and
,or
,xor
,nand
,nor
,xnor
- literals (e.g.,
1'b0
,1'b1
) - wire slicing (e.g.,
x[0]
,x[1:0]
) assign
for connecting wires (e.g.,assign x = y;
);assign
for setting a wire to a constant value (e.g.,assign x = 1'b0;
)- module instantiation
4.2 GL Signal Declaration
You may only use wire
for GL modeling. Do not use any other signal
types such as logic
or reg
. Wires should be created close to where
they will be first used.
Declaring multi-bit wires is allowed using the following syntax.
Never use any other kind of indexing. So the following are all incorrect:
wire [0:7] x; // incorrect indexing
wire [4:1] y; // incorrect indexing
wire [7:2] y; // incorrect indexing
Vertically right align the closing square brackets and vertically left align the wire names. The following is correct:
The following is incorrect:
wire w; // incorrect vertical alignment
wire [7:0] x; // incorrect vertical alignment
wire [3:0] y; // incorrect vertical alignment
wire [15:0] z; // incorrect vertical alignment
4.3 Primitive Gate Instantiation
There should be spaces between wires when instantiating a logic gate. There should be a space after the opening parenthesis and before the closing parenthesis.
wire x;
and(x,in1,in2); // incorrect, no horizontal whitespace
and(x, in1,in2 ); // incorrect, inconsistent horizontal whitespace
and(x, in1, in2); // incorrect, no space after/before parenthesis
and( x, in1, in2 ); // correct
Additional horizontal whitespace can be used to align visual columns when appropriate. The following is correct if we want to align the visual columns to improve readability.
4.4 Literals
In hardware modeling, avoid using literals without specifying the
bitwidth. So prefer 1'b0
instead of 0
and 1'b1
instead of 1
. In
test benches, using 0
and 1
is acceptable. Use underscores (_
) to
help make long literals more readable as follows
4.5 Assign
Signal assignment using assign
is allowed to implement physical
connections. Concatenation is not allowed, because it obscures bit-level
behavior. Instead, write explicit bit-level assignments.
4.6 GL Module Definition
A GL module definition should specify each port on a separate line
(indented by two spaces) and place the opening and closing parenthesis on
their own lines. The definition should vertically align the wire
keywords, bitwidth declarations, and port names as follows.
module FooBar_GL
(
input wire val,
input wire [3:0] addr,
input wire [15:0] data,
output wire wait
);
// ... module implementation here ...
endmodule
4.7 GL Module Instantation
A GL module may only instantiate other GL modules. Module instantation should specify each port connection on a separate line (indented by two spaces) and place the opening and closing parenthesis on their own lines. The definition should vertically align the port connections as follows.
It is fine for ports and wires to have the same name as long as the
intent is clear. We recommend using a direct transformation of the module
name to create the module instance name (i.e., foo_bar
is a direct
transformation from FooBar_GL
), but this is not required. Using the
module instance name as a prefix for the wires used to connect to the
module can sometimes be useful.
The following is incorrect formatting since: (1) the opening parenthesis is not on its own line; (2) the port connections are not indented by two spaces; and (3) the port connections are not vertically aligned.
5. Comments
Though a pain to write, comments are absolutely vital to keeping our code readable. The following rules describe what you should comment and where. But remember: while comments are very important, the best code is self-documenting. Giving sensible names to wires and module instances is much better than using obscure names that you must then explain through comments. When writing your comments, write in a manner such that in a few years time, you can still look back and be able to understand your code and/or logic.
Do not state the obvious. In particular, don't literally describe what code does, unless the behavior is not obvious to a reader who generally understands Verilog. Instead, provide higher level comments that describe why the code does what it does, or make the code self describing. Over commenting is just as bad as under commenting.
5.1 ASCII Characters
Comments must only use ASCII characters. Do not use any kind of unicode characters in your comments. Including emoji's or foreign characters using unicode is not allowed.
5.2 Comment Style
Use //
comments. Do not use /* */
comments. Include a space after
//
before you start commenting.
5.3 Trailing Comments
Trailing comments are acceptable as long as they are short. For example, trailing comments to describe ports works well.
module FooBar_GL
(
input wire val, // valid
input wire [3:0] addr, // write address
input wire [15:0] data, // write data
output wire wait // module is busy, must wait
);
5.4 Test Case Comments
Every test case should start with a test case title block which at a
minimum gives the name of the test case. Often, it can also be helpful to
write a small description of what the test case is trying to achieve.
Within a directed test case using check
tasks always use a comment to
label the columns so it is clear what you are checking. An example is
shown below.
//----------------------------------------------------------------------
// test_case_1_basic
//----------------------------------------------------------------------
// This is a basic test case just for smoke testing. Passing this test
// case in no way guarantees any kind of functionality!
task test_case_1_basic();
t.test_case_begin( "test_case_1_basic" );
// in tens ones
check( 5'b00000, 4'b0000, 4'b0000 );
check( 5'b00001, 4'b0000, 4'b0001 );
check( 5'b01111, 4'b0001, 4'b0101 );
check( 5'b11111, 4'b0011, 4'b0001 );
t.test_case_end();
endtask
The horizontal lines used in the test case title block should extend
exactly 74 characters (i.e., two spaces, two '/' characters, and 70 -
characters).
5.5 File Comments
All files should include a "title block". This is a comment at the
beginning of the file which at minimum must give the name of the file.
Often, it can also be helpful to write a small description of the
interface in a comment right below the title block. So the title block
for Foo_GL.v
should look something like:
//=======================================================================
// FooBar_GL
//=======================================================================
// Memory module which enables writing data. If the memory module is busy
// then the wait signal will be one.
The horizontal lines used in the title block should extend exactly 74
characters (i.e., two '/' characters and 72 =
characters).
5.6 Instructor Comments
You must remove instruction comments provided in the released code. These
comments are no longer applicable once you have followed the instructions
and implemented your hardware design. You should also remove any
unnecessary ECE2300_UNUSED
and/or ECE2300_UNDRIVEN
macros that were
provided in the released code. So example, these should be removed:
//''' LAB ASSIGNMENT '''''''''''''''''''''''''''''''''''''''''''''''''''
// Implement the binary to binary coded decimal converter
//>'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
// remove these lines before starting your implementation
`ECE2300_UNUSED( in );
`ECE2300_UNDRIVEN( tens );
`ECE2300_UNDRIVEN( ones );