Skip to content

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.

 /fb
   FooBar_GL.v
   /test
     FooBar_GL-test.v
   /sim
     foo-bar-sim.v

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:

`include "fb/FooBar_GL.v"

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.

wire [7:0] x; // 8-bit wire
wire [3:0] y; // 4-bit wire
wire [5:0] z; // 6-bit wire

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:

wire        w;
wire  [7:0] x;
wire  [3:0] y;
wire [15:0] z;

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.

wire x, y;
and( x, in1_n, in2   );
or ( y, in1,   in2_n );

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

16'b0000_1010_0110_1111

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.

FooBar_GL foo_bar
(
  .val  (foo_val),
  .addr (foo_addr),
  .data (foo_data),
  .wait (foo_wait)
);

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.

FooBar_GL foo_bar(
.val (foo_val),
.addr (foo_addr),
.data (foo_data),
.wait (foo_wait)
);

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.

//without space, incorrect formatting
// with space, correct formatting

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 );