Google Style Guides

Google Verilog Style Guide

Basics

Summary

Verilog is the main logic design language at Google.

Verilog can be written in vastly different styles, which can lead to code conflicts and code review latency. This style guide aims to promote Verilog readability across groups. To quote the C++ style guide: “Creating common, required idioms and patterns makes code much easier to understand.”

This guide defines a Google style for Verilog. The goals are to:

This style guide defines style for SystemVerilog compliant code. Additionally, this style guide defines style for both synthesizable and testbench code.

Terminology Conventions

Unless otherwise noted, the following terminology conventions apply to this style guide:

Style Linter

The following Style Linter tools can be used to automatically check the code for violations to the mandatory requirements of this Style Guide:

Style Guide Exceptions

Justify all exceptions with a comment.

No style guide is perfect. There are times when the best path to a working design, or for working around a tool issue, is to simply cut the Gordian Knot and create code that is at variance with this style guide. It is always okay to deviate from the style guide by necessity, as long as that necessity is clearly justified by a brief comment, as well as a Style Linter waiver pragma where appropriate.

Inclusive Language

In all code, including naming and comments, use inclusive language and avoid terms that others might find disrespectful or offensive (such as “master” and “slave”, “blacklist” and “whitelist”, or “redline”), even if the terms also have an ostensibly neutral meaning. Similarly, use gender-neutral language unless you’re referring to a specific person (and using their pronouns). For example, use “they”, “them”, or “their” for people of unspecified gender (even when singular), and “it” or “its” for software, computers, and other things that aren’t people.

The bus protocols section has additional conventions for replacing non-inclusive terms in that common case.

Non-Inclusive Language in External IP

In some cases, non-inclusive terms are embedded in industry standards or third party IP (or similar) as names or keywords. Whenever possible, minimize your use of the term (hence avoid propagating it as a term of art), while still providing clear documentation to your readers.

Common Scenarios and Preferred Approaches:

Which Verilog to Use

Prefer SystemVerilog-2017.

All RTL and tests must be developed in SystemVerilog, following the SystemVerilog-2017 standard.

What about VHDL?

Google uses Verilog for logic design.

Precedent has already been established that Verilog is the choice for logic design at Google.

Reusable IP

Common RTL that is intended to be used across multiple projects is referred to as reusable IP.

File Conventions

Summary

This section covers file conventions, mostly how files are to be named.

File Extensions

Use .sv and .svh file extensions for SystemVerilog files.

File extensions have the following meanings:

File Names

File names must match the design element name and indicate the Verilog dialect.

If a source file declares a design element, the file name must begin with the name of that design element. For example, a file declaring a module named interconnect must be named interconnect.sv.

Declare only one design element per file. As per IEEE 1800-2017 section 3.2, a design element may be either a module, package, program, interface, checker, primitive, or configuration.

For variants of a design element, it is permissible to insert dot-delimited strings between the design element name and the file extension to indicate the particular variant contained in that file. For example, a file containing a gate-level model of an interconnect module might be named interconnect.gate.sv.

Standard Design Files

The following filenames are special: [design]_top.sv, [design]_pkg.sv.

Each design must have two standard files, [design]_top.sv and [design]_pkg.sv, where [design] is the unique name of the design.

A “design” here is one of the following hierarchical elements: project top, cluster (a collection of design blocks) or a design block.

A “design block” is the fundamental architecture or microarchitecture building block of the design. Every design block has its black box behavior fully specified. In particular, a design block’s interfaces are fully specified in the documentation (both the port list and the behavior of every port).

File Structure

The overall structure of a SystemVerilog file must start with the header, followed by the declaration and then the implementation.

Header Comment

Every file must start with a header comment.

A header comment contains a short description of the design element, explaining what problem it solves. Additional text must give an overview of any non-obvious parts of the theory of operation of this particular implementation.

Basic Template

A template that demonstrates many of the items detailed throughout the style guide.

Template:

// A quick description of the design element goes here. What its intent is,
// and a general theory of operation.

module my_module #(
    parameter int Width = 80,
    parameter int Height = 24
) (
    input logic clk,
    input logic rst,
    ...
);

  ...

endmodule

Generated Code

Generated code must be accompanied by all necessary input files and scripts necessary to automatically reproduce the generated output.

Generators include Verilog preprocessors and programs that create Verilog from other sources like Haskell, Chisel, C or C++.

Code generation tools that operate in-place on Verilog files must not strip out the generating macros, and must preserve those recipes within the file (in other words, as comments). Code generators that operate in this fashion must be idempotent and repeatable.

Comments wrapped in /** and **/ comment lines are reserved for use by generators.

Generated code must meet the style guidelines.

Aesthetics and Formatting

Summary

The Aesthetics and Formatting section addresses purely aesthetic aspects of style, primarily:

General File Appearance

Characters

Use only ASCII characters with UNIX-style line endings(“\n”).

POSIX File Endings

All lines on non-empty files must end with a newline (“\n”).

The POSIX standard mandates that lines end with a newline character. This includes the final line in every file.

Line Length

Wrap the code at 100 characters per line.

The maximum line length for style-compliant Verilog code is 100 characters per line.

Exceptions:

Line-wrapping contains additional guidelines on how to wrap long lines.

No Tabs

Do not use tabs anywhere.

Use spaces to indent or align text. See Indentation for rules about indentation and wrapping.

To convert tabs to spaces on any file, you can use the UNIX expand utility.

No Trailing Spaces

Delete trailing whitespace at the end of lines.

No Redundant Semicolons

Delete redundant semicolons.

Semicolons may represent null statements or items in SystemVerilog. One is enough; consecutive semicolons (;;) are likely typos.

Eschatology

Use begin and end unless the whole statement fits on a single line.

If a whole semicolon-terminated statement fits on a single line, begin and end may be omitted, otherwise enclose it in begin and end.

// Wrapped procedural block requires begin and end.
always @(posedge clk) begin
  q <= d;
end
// The exception case, where begin and end may be omitted as the entire
// structure fits on a single line.
always @(posedge clk) q <= d;
// Incorrect because a wrapped statement must have begin and end.
always @(posedge clk)
  q <= d;

Assume that any macro call could expand to multiple statements, and enclose them in begin and end where necessary (for example, inside conditional and loop blocks).

// The contents of the called macro are safely enclosed and controlled.
if (expr) begin
  `MACRO(...);
end

if (expr) ...;
else begin
  `MACRO(...);
end
// A multi-statement macro would have only its first statement controlled:
if (expr) `MACRO(...);

if (expr) ...;
else `MACRO(...);

begin must be on the same line as the preceding keyword, and ends the line. end must start a new line. end else begin must be together on one line. The only exception is if end has a label, a following else must be on a new line.

// "end else begin" are on the same line.
if (condition) begin
  foo = bar;
end else begin
  foo = bum;
end

For single-statement blocks, begin and end may be omitted if the statement fits on a single line. Both styles (including or omitting begin and end) are acceptable.

// begin/end may be omitted because each semicolon-terminated statement fits on
// a single line (both styles are acceptable).
if (condition) foo = bar;
else foo = bum;
// Incorrect because "else" must be on the same line as "end".
if (condition) begin
  foo = bar;
end
else begin
  foo = bum;
end
// An exception is made for labeled blocks.
if (condition) begin : a
  foo = bar;
end : a
else begin : b
  foo = bum;
end : b

The preceding style also applies to individual case items within a case statement. begin and end may be omitted if the entire case item (the case expression and the associated statement) fits on a single line. Otherwise, use the begin keyword on the same line as the case expression.

// Consistent use of begin and end for each case item is good.
unique case (state)
  kIdle: begin
    state_next = kA;
  end
  kA: begin
    state_next = kB;
  end
  kB: begin
    state_next = kIdle;
    foo = bar;
  end
  default: begin
    state_next = kIdle;
  end
endcase
// Case items that fit on a single line may omit begin and end.
unique case (state)
  kIdle: state_next = kA;
  kA: state_next = kB;
  kB: begin
    state_next = kIdle;
    foo = bar;
  end
  default: state_next = kIdle;
endcase
unique case (state)
  kIdle:              // These lines are incorrect because we must not wrap
    state_next = kA;  // case items at a block boundary without using begin
  kA:                 // and end.  Case items must fit on a single line, or
    state_next = kB;  // else the procedural block must have begin and end.
  kB: begin
    foo = bar;
    state_next = kIdle;
  end
  default: begin
    state_next = kIdle;
  end
endcase

Indentation

Indentation is two spaces per level.

Indentation is two spaces per level, no tabs. (Configure your editor to emit spaces when you hit the tab key.)

Indented Sections

Always add an additional level of indentation to the enclosed sections of all paired keywords. Examples of SystemVerilog keyword pairs:

Indent all members of struct, union, and enum declarations by two spaces.

typedef struct packed {
  logic bar;
} foo_t;

Line Wrapping

When wrapping a long expression, indent the continued part of the expression by four spaces, like this:

assign zulu = enabled && (
    alpha < bravo &&
    charlie < delta);

assign addr = addr_gen_function_with_many_params(
    thing, other_thing, long_parameter_name, x, y,
    extra_param1, extra_param2);

assign structure = '{
    src: src,
    dest: dest,
    default: '0};

assign foo = function_with_structure(model[offset].very.long.
    structure.name);

module my_module (
    input clk,  // this is a line wrap of the module header definition
    output bar
);

my_module my_module (
    .clk(clk),  // this is a line wrap of the module instantiation
    .bar(bar)
);

Or, if it improves readability, align the continued part of the expression with a grouping open parenthesis or brace. Similarly, an expression containing a nested struct may be aligned with the first structure member name. In the absence of other considerations, use the minimum number of lines.

assign zulu = enabled && (alpha < bravo &&
                          charlie < delta);

assign addr = addr_gen_function(thing, other_thing,
                                long_parameter_name,
                                x, y);

assign structure = '{src: src,
                     dest: dest,
                     default: '0};
assign foo = function_with_structure(model[offset].very.long.
                                                   structure.name);

Operators in a wrapped expression can be placed at either the end or the beginning of each line, but this must be done consistently within a file.

Preprocessor Directives

Keep branching preprocessor directives left-aligned and un-indented.

Keep branching preprocessor directives (`ifdef, `ifndef, `else, `elsif, `endif) aligned to the left, even if they are nested. Indent the conditional branches of text as if the preprocessor directives were absent. Non-branching preprocessor directives must follow the same indentation rules as the regular code.

package foo_pkg;
`ifdef FOO              // Good: branching directive left-aligned
  `include "foo.svh"    // normal indentation for non-branching directives
  parameter int A = 1;  // normal indentation for the regular code
`ifdef BAR              // Good: branching directive left-aligned
  parameter int A = 2;
`else
  parameter int A = 3;
`endif  // BAR
`endif  // FOO
endpackage : foo_pkg
package foo_pkg;
  `ifdef FOO            // Bad: needs to be un-indented
  `include "foo.svh"
  parameter int A = 1;
  `ifdef BAR            // Bad: needs to be un-indented
  parameter int A = 2;
  `else                 // Bad: needs to be un-indented
  parameter int A = 3;
  `endif  // BAR
  `endif  // FOO
endpackage : foo_pkg
package foo_pkg;
  `ifdef FOO              // Bad: needs to be un-indented
    `include "foo.svh"    // Bad: extra indentation level
    parameter int A = 1;  // Bad: extra indentation level
    `ifdef BAR
      parameter int A = 2;
    `else
      parameter int A = 3;
    `endif  // BAR
  `endif  // FOO
endpackage : foo_pkg

Un-indented branching preprocessor directives disrupt the flow of reading to emphasize that there is conditional text. Leaving conditional branch text un-indented will result in post-preprocessed text looking properly indented.

Spacing

Space Around Keywords

Include whitespace before and after SystemVerilog keywords.

Keywords must be delimited by whitespace, even if not syntactically required.

Do not include a whitespace:

For example:

// Normal indentation before if.  Include a space after if.
if (foo) begin
end
// Include a space after always, but not before posedge.
always @(posedge clk) begin
end

Space Around Binary Operators

Include whitespace on both sides of all binary operators.

Use spaces around all binary operators. Add sufficient whitespace to aid readability.

For example:

assign a = ((addr & mask) == MyAddress) ? b[1] : ~b[0];
parameter int Width = Msb - Lsb;
function logic crc(input logic [7:0] data, mask = 8'hff);
assign a=((addr&mask)==MyAddress)?b[1]:~b[0];  // Bad: no space around operators
parameter int Width=Msb - Lsb;  // Bad: no space around '='
function automatic logic crc(input logic [7:0] data, mask=8'hff);  // Bad: no space around '='

Exception: When declaring a bit vector or indexing or slicing an array, it is acceptable to use the compact notation for simple expressions. For example:

logic [Width-1:0] foo;          // Declaration: compact is acceptable
logic [Width - 1 : 0] foo;      // Declaration: spaced is fine also
assign foo = bar[Width-1:0];    // Slice: compact is acceptable
assign foo = bar[Width - 1:0];  // Slice: spaced is fine also

For more complex expressions in array indices, space-delimit all binary operators.

assign foo = bar[Factor1 - Factor0 - 1:0]; // Good: operators are space-delimited
assign foo = bar[Factor1-Factor0-1:0]; // Bad: complex expression without spaces

When splitting alternation expressions into multiple lines, use a format that is similar to an equivalent if-then-else line. For example:

assign a = ((addr & mask) == MyAddress)
           ? matches_value
           : doesnt_match_value;

Comma-delimited Lists

For multiple items on a line, one space must separate the comma and the next character.

Additional whitespace is allowed for readability.

bus = {addr, parity, data};
a = myfunc(lorem, ipsum, dolor, sit, amet, consectetur, adipiscing, elit,
           rhoncus);
mymodule mymodule (.a(a), .b(b));
{parity,data} = bus;
a = myfunc(a,b,c);
mymodule mymodule (.a(a),.b(b));

Tabular Alignment

Adding whitespace to align related items is recommended.

Where it is reasonable to do so, align a group of two or more similar lines so that the identical parts are directly above one another. This alignment makes it easy to see which characters are the same and which characters are different between lines.

Use spaces, not tabs.

For example:

logic [7:0]  my_interface_data;
logic [15:0] my_interface_address;
logic        my_interface_enable;

Array Dimensions in Declarations

Add a space around packed dimensions.

Do not add a space:

Applies to packed and unpacked arrays as well as dynamic arrays, associative arrays, and queues.

logic [7:0][3:0] data[128][2];
typedef logic [31:0] word_t;
bit bit_array[512];
data_t some_array[];
data_t some_map[addr_t];
data_t some_q[$];
// There must not be a space between dimensions.
logic [7:0] [3:0] data[128] [2];
// There must be a space around packed dimensions.
typedef logic[31:0]word_t;
// There must not be a space between identifier and unpacked dimension.
bit bit_array [512];
// Dynamic, associative, and queue "dimensions" are treated the same as unpacked
// dimensions.  There must not be a space.
data_t some_array [];
data_t some_map [addr_t];
data_t some_q [$];

Parameterized Types

Add one space before type parameters, except when the type is part of a qualified name.

A qualified name contains at least one scope :: operator connecting its segments. A space in a qualified name would break the continuity of a reference to one symbol, so it must not be added. Parameter lists must follow the space-after-comma rule.

my_fifo #(.Width($bits(nibble_t)), .Depth(2)) my_fifo_nibble(.in, .out);

class foo extends bar #(32, 8);  // unqualified base class
  ...
endclass

foo_h = my_class#(.X(1), .Y(0))::type_id::create("foo_h");  // static method call

my_pkg::x_class#(8, 1) bar;  // package-qualified name
my_fifo#(.Width(4), .Depth(2)) my_fifo_2by4(.in, .out);  // instantiation

class foo extends bar#(32, 8);  // unqualified base class
  ...
endclass

foo_h = my_class #(.X(1), .Y(0))::type_id::create("foo_h");  // static method call

my_pkg::x_class #(8, 1) bar;  // package-qualified name

Labels

Code block labels have one space before and after the colon. Statement labels have no space before the colon and one space after the colon.

For example:

begin : foo
  my_statement_label: assert(ptr > 0);
end : foo
end:bar              // Block label must have a space before and after the colon.
endmodule: foobar    // Block label must have a space before the colon.

my_assert : assert(ptr > 0);  // Statement label must have no space before the colon.

Case items

There must be no whitespace before a case item’s colon; there must be at least one space after the case item’s colon.

The default case item must include a colon.

For example:

unique case (my_state)
  kInit:   $display("Shall we begin");
  kError:  $display("/this is fine meme");
  default: begin
    my_state = kInit;
    interrupt = 1;
  end
endcase
unique case (1'b1)
  (my_state == kError)  : interrupt = 1;  // Excess whitespace before colon
  default:begin end                       // Missing space after colon
endcase

Module Header Spacing

Module header definitions have spaces after the module name and between any parameter lists and port lists.

For example:

module my_module (
    ...
);

module my_module #(
    ...
) (
    ...
);
module my_module( // There must be a space after the module name.
    ...
);

module my_module #(
    ...
)( // There must be a space between the parameter list and the port list.
    ...
);

Module Instantiation Spacing

Module instances have spaces after the instance name and between any parameter lists and port lists.

For example:

my_module #(...) my_module (...);
my_module my_module (...);
// The space after the instance name is missing.
my_module #(...) my_module(...);
my_module my_module(...);

Function And Task Calls

Function and task calls must not have any spaces between the function name or task name and the open parenthesis.

For example:

process_packet(pkt);
process_packet (pkt);  // There must not be a space before "("

Macro Calls

Macro calls must not have any spaces between the macro name and the open parenthesis.

For example:

`uvm_error(ID, "you fail")
`ASSERT(name_A, a & b, clk, rst)
`uvm_error (ID, "you fail")  // There must not be a space before "("
`ASSERT (name_A, a & b, clk, rst)

Line Continuation

It is recommended to right-align line continuations.

Aligning line continuations (‘\ ’ character) helps visually mark the end of a multi-line macro. The position of alignment only needs to be beyond the rightmost extent of a multi-line macro by at least one space, when a space does not split a token, but must not exceed the maximum line length.

`define REALLY_LONG_MACRO(arg1, arg2, arg3) \
    do_something(arg1);                     \
    do_something_else(arg2);                \
    final_action(arg3);

Parentheses

Use parentheses to make operations unambiguous.

In any instance where a reasonable human would need to expend thought or refer to an operator precedence chart, use parentheses instead to make the order of operations unambiguous.

Reduction Operator

Parentheses are required to isolate any reduction operation when immediately preceded by any other operator.

logic [7:0] a;
logic b;
logic c;
assign c = b & (&a);  // Parentheses make the intent clear
logic [7:0] a;
logic b;
logic c;
assign c = b & &a;  // Bad: This can easily be a typo, still valid Systemverilog

Ternary Expressions

Nested ternary expressions must be enclosed in parentheses.

For example:

assign state_next = condition_b
    ? (condition_a
       ? a_and_b
       : b_and_not_a)
    : state;

While the following nested ternary has only one meaning to the compiler, the meaning can be unclear and error-prone to humans:

assign state_next = condition_b
    ? condition_a
    ? a_and_b
    : b_and_not_a
    : state;

Comments

C++ style comments (// foo) are strongly recommended for single and multi-line comments. The /** token is reserved for use with generators.

A standalone comment above a block of code explains that code. An inline comment explains the code that is on the same line as the comment.

For example:

// This is a multi-line comment that describes the basic functionality and key
// features of the following module.
module foo;
  ...
endmodule : foo

localparam bit Baz = 1;  // This comment describes the item to the left. If
                         // using multiple lines, it must remain clear which
                         // line of code the comment is referring to.
/* Grepping this line looks commented.
   Grepping this line looks uncommented.
   That is confusing. */

Comments that start with /** are reserved for use by generator directives.

Punctuation, Spelling, and Grammar

Maintain consistent and correct punctuation, spelling, and grammar within comments.

Pay attention to punctuation, spelling, and grammar; it is easier to read well-written comments than badly written ones.

Comments must be as readable as narrative text, with proper capitalization and punctuation. In many cases, complete sentences are more readable than sentence fragments. Shorter comments, such as comments at the end of a line of code, can sometimes be less formal as long as the style remains consistent.

Although it can be frustrating to have a code reviewer point out that you are using a comma instead of a semicolon, it is very important that source code maintain a high level of clarity and readability. Proper punctuation, spelling, and grammar help with that goal.

Dead code

Do not keep commented out or unreachable code. Instead, rely on the version control system to preserve older versions of the code. In cases where the “dead code” might be there for special purposes, clearly document the intent.

Naming Conventions

Naming Overview

All identifiers are styled lower_snake_case unless otherwise specified in the following table.

“There are only two hard things in Computer Science: cache invalidation and naming things.” – Phil Karlton

All identifiers are styled lower_snake_case, except for the constructs summarized in the following table:

Construct Style
`define macros ALL_CAPS
Tunable parameters for parameterized modules, classes, and interfaces UpperCamelCase
Constants UpperCamelCase
Type parameter lower_snake_case_t
Struct and union types lower_snake_case_t
Constraints lower_snake_case_c
Interface declarations lower_snake_case_if
Enumeration types lower_snake_case_t or lower_snake_case_e
Enumerated value names kUpperCamelCase
Chip primary IOs UPPER_SNAKE_CASE or lower_snake_case

Everything else, including declarations (module, class, package), instance names, signals (nets and ports), genvars, labels, functions, tasks, and other variables, must be named consistent with the lower_snake_case convention.

External library identifiers and their overrides in the user code that violate the preceding naming conventions are permitted. Example: UVM’s convert2string() can be used without renaming.

When an identifier is styled as UpperCamelCase, it is permissible to separate numeric values with an underscore. For example, IndexedThing_5_12.

Further, there are a number of naming conventions for signals, constants, and ports that are documented in more detail in the following sections.

Use Descriptive Names

Names must describe what an element’s purpose is. Optimize for readability using names that would be clear even to people on a different team.

Use names that describe the purpose or intent of the object. Do not worry about saving horizontal space as it is far more important to make your code immediately understandable by a new reader. Minimize the use of abbreviations that would likely be unknown to someone outside your project (especially acronyms and initialisms). Do not abbreviate by deleting letters within a word. As a rule of thumb, an abbreviation is probably OK if it’s listed in Wikipedia. If additional abbreviations must be used, make sure your project has a glossary of acceptable abbreviations.

Descriptiveness is recommended to be proportional to the name’s scope of visibility. For example, n may be a fine name within a 5-line function, but within the scope of a 1000-line module, it’s likely too vague.

When adding a comment to a signal declaration, consider whether an improved signal name would be more appropriate.

Don’t reuse variables for convenience. Instead create a new variable with a unique, descriptive name.

logic temp;  // Bad: temp is not a descriptive name

always_comb begin
  temp = a ^ b;
  c = ~temp + 1'b1;

  temp = d | e;  // Bad: temp was reused.  Instead declare a new signal
  f = temp + 1'b1;
end

function automatic logic foo(logic bar);
  bar = !bar;  // Bad: reusing the input variable of a function
  return bar;
endfunction

Prefer Positive Enablement

Signals, ports, and parameters must be named positively.

All boolean signals are assumed to be active-high, unless explicitly named with a _n suffix to indicate that the signal is active-low. Active-low signals are not recommended.

When naming boolean parameters, prefer positive enablement names (“Foo” or “EnableFoo”) over disablement (“DisableFoo”).

// Good: binary parameters/localparams are named positively ("Enable"),
// regardless of their default values.
parameter bit EnableFoo = 0;
parameter bit EnableBar = 1;
// Bad: Parameter names are named negatively. Both the names and the values
// must instead be inverted.
parameter bit DisableFoo = 1;
parameter bit DisableBar = 0;

Hierarchical consistency

The same signal must have the same name at any level of the hierarchy.

A signal that connects to a port of an instance must have the same name as that port. By proceeding in this manner, signals that are directly connected must maintain the same name at any level of hierarchy.

Exceptions to this convention are expected, such as:

In each exceptional case, care must be taken to make the mapping of port names to signal names as unambiguous and consistent as possible.

A corollary of this principle is that signal names must change when that signal’s meaning changes. For example, if a write_address signal means “byte index” at one level of hierarchy but “word index” within a sub-module, those signals must have different names to indicate their differences (for example, write_byte_address and write_word_address).

Clock and Reset

These signals must be named clk, rst, and arst.

Clocks and resets must follow the following naming rules:

These names may optionally be prefixed to specify more specific use. Most commonly, these signals will be prefixed with a mnemonic indicating a clock domain, as required by the Clock Domain Prefixes section.

For example:

input logic       smbus_clk,  // Clocks an "smbus" clock domain.
input logic       smbus_rst,  // Synchronous reset, clocked by smbus_clk
input logic       smbus_arst,  // Asynchronous reset, de-assert clocked by smbus_clk
input logic [7:0] smbus_byte,  // Synchronous with smbus_clk
input logic       smbus_valid,  // Synchronous with smbus_clk

Avoid signal names that end with underscore-number

Signal names must never end with an underscore followed by a number (for example, foo_1, foo_2, etc.). Many synthesis tools map buses into nets using that naming convention, so similarly named nets can lead to confusion when examining a synthesized netlist.

Avoid reserved keywords as signal names

Reserved Verilog or SystemVerilog keywords may never be used as names (see Tables 22.1-22.6 of SystemVerilog-2017 standard for the full list of reserved keywords).

When interoperating with different languages, be mindful not to use keywords from other languages.

Prefixes

Use common prefixes to identify groups of signals that operate together. For example, all elements of an AXI-S interface would share a prefix: foo_valid, foo_ready, and foo_data.

Additionally, prefixes must be used to clearly label which signal is in which clock group for any module with multiple clocks. See the section on clock domains for more details.

Examples:

Code example:

module fifo_controller (
    input logic clk,
    input logic rst,

    // writer interface
    input  logic [15:0] in_data,
    input  logic        in_valid,
    output logic        in_ready,

    // reader interface
    output logic [15:0] out_data,
    output logic        out_valid,
    output logic [7:0]  out_fullness,
    input  logic        out_ack,

    // memory interface:
    output logic [7:0]  mem_addr,
    output logic [15:0] mem_wdata,
    output logic        mem_we,
    input  logic [15:0] mem_rdata
);

This naming convention makes it easier to map port names onto similar signal names using simple and consistent rules. See the section on Hierarchical Consistency for more information.

Clock Domain Prefixes

If a module contains multiple clocks, all signals must be prefixed by a clock domain mnemonic.

Generally, it is recommended to structure modules so that most modules only have a single clock domain (fully synchronous). For modules that must span multiple clock domains, it is required that every port and signal within the module be labelled with a prefix that uniquely associates each signal with a clock domain. This prefix must be the same prefix used in the naming of the associated clock.

Even asynchronous resets must be de-asserted synchronously with a specific clock domain, and so must be named accordingly.

For example:

module foo_to_bar_cdc_fifo (
  // This module has two clocks, foo and bar:
  input  logic       foo_clk,
  input  logic       foo_arst,  // reset for flops in the foo_clk domain
  input  logic       bar_clk,
  input  logic       bar_arst,  // reset for flops in the bar_clk domain

  // These signals are all in the foo_clk domain:
  input  logic [7:0] foo_payload_data,
  input  logic       foo_payload_valid,
  output logic       foo_payload_ready,

  // These signals are all in the bar_clk domain:
  output logic [7:0] bar_payload_data,
  output logic       bar_payload_valid,
  input  logic       bar_payload_ready
);

// Internal nets must all be labelled as well:
wire foo_transfer = foo_payload_valid && foo_payload_ready;

Source-Destination Prefixes

It is sometimes useful to document the expected connectivity between modules in the signal name. The convention for indicating this connectivity is <src>_<dest>__, where <src> and <dest> are convenient abbreviations for the endpoints of the communication (at least, from a top-level perspective). The endpoint abbreviation must specify a specific instance, or a reasonable generic name if the specific instance is not known.

The <src> endpoint must always be the producer or requester of a given communication (which can either be an individual flow-controlled channel or a protocol-level group of individual flow-controlled channels). <dest> must always be the consumer or completer of the communication. A communication direction is not necessarily the same as each signal’s direction. In this manner, a set of related signals that work together to form a distinct communication between modules always have the same naming prefix. The scope of the communication (in other words, channel or group of channels) must be explicitly documented using comments so that there is no naming ambiguity.

Source-destination prefixes are never mandatory, and must only be used at levels of hierarchy where they clarify the intent of the design. When stripping this prefix from a signal name at a hierarchical boundary, the right-hand side of the __ separator must remain unchanged.

An example:

module foo (
    input logic clk,
    input logic rst,

    // This is a simple standalone control signal from endpoint bar.
    input  logic  bar_foo__enable,

    // This is a protocol-level group of Ready/Valid flow-controlled channels
    // where foo is the requester endpoint and bar is the completer endpoint.
    // Good: All signals in this request channel have the same <src>_<dest>
    // prefix regardless of signal direction.
    input  logic  foo_bar__req_ready,
    output logic  foo_bar__req_valid,
    output req_t  foo_bar__req,

    // Good: Although this response channel flows in the opposite direction, it
    // is part of the same protocol interface as the request channel above, so
    // the <src>_<dest> prefix is still "foo_bar".
    output logic  foo_bar__resp_ready,
    input  logic  foo_bar__resp_valid,
    input  resp_t foo_bar__resp
);
module foo (
    input logic clk,
    input logic rst,

    // Bad: The ready signal prefix must be "foo_bar". The <src>_<dest> prefix
    // depends on the endpoint roles and not the signal direction.
    input  logic  bar_foo__req_ready,
    output logic  foo_bar__req_valid,
    output req_t  foo_bar__req,

    // Bad: Although this response channel flows in the opposite direction, it
    // is part of the same protocol interface as the request channel, where foo
    // is the requester endpoint and bar is the completer endpoint. Thus, the
    // <src>_<dest> prefix must still be "foo_bar".
    output logic  bar_foo__resp_ready,
    input  logic  bar_foo__resp_valid,
    input  resp_t bar_foo__resp
);

Pipeline Stage Prefixes

Clearly label your pipeline stage variables.

When implementing a multi-stage pipeline, label your variables so that it is clear which pipeline stage each variable is associated with. For example, a three stage pipeline might use the prefixes p1_, p2_, and p3_ to indicate which signal is associated with which stage.

Clearly labelling which pipeline stage a signal is associated with helps to detect and avoid bugs where signals that are valid in one pipeline stage are accidentally used in a different pipeline stage.

Example:

// Pipe stage 1:
always @(posedge clk) begin
  p1_x <= in_x;
  p1_y <= in_y;
  p1_valid <= in_valid;
end

// Pipe stage 2:
always @(posedge clk) begin
  p2_product <= p1_x * p1_y;
  p2_valid <= p1_valid;
end

// Pipe stage 3:
always @(posedge clk) begin
  p3_accumulator <= p3_accumulator + p2_product;
  p3_valid <= p2_valid;
end

NOTE: This guide makes a distinction between delayed signals and pipelines. A delayed signal is a stand-alone signal that is delayed by a number of cycles. A pipeline is a collection of signals across a number of cycles, as in the preceding example. In this example, the valid signal is a delayed version of the original in_valid signal, but the other signals are not delayed signals. The pipelining naming convention emphasizes the grouping of signals in the same stage, not their relative delays from their origin. The use of prefixes for pipeline stages and suffixes for delayed signals makes the distinction clear. See Delayed Signal Suffix for details.

Suffixes

There are some standard suffixes for signals that have special meanings. Signals must only end with these suffixes when the standard meanings are intended.

Some common conventions for suffixes are summarized here:

Specify units

Just as with constants, include the units for a signal in the signal’s symbolic name. Counters must indicate what is being counted. Address signals must be named to indicate the word size of the thing being indexed.

Unitless signals (for example, foo_is_enabled) are excepted from this rule. Additionally, some standard interfaces (for example, AXI-M) have standardized naming of signals. In that context, the standardized signal names (waddr) are preferable to more precise names (write_byte_address).

Prefer specifying units for logic-vector type signals assuming a 0-based least-significant bit. If that is not practical and a bit slice is required, then the signal name must encode the bit-vector range.

Examples:

Units for Constants

Just as with signals, include the units for a constant in the constant’s symbolic name (preferably, as a suffix). The exceptions to this rule are for constants that are inherently unitless, or if the constant is describing the default unit type, “bits.”

Example:

localparam int InterfaceWidth = 64;  // Bits
localparam int InterfaceWidthBytes = (InterfaceWidth + 7) / 8;
localparam int InterfaceWidth64bWords = (InterfaceWidth + 63) / 64;
localparam int ImageWidthPixels = 640;
localparam int Mega = 1000 * 1000;  // Unitless
localparam int Mebi = 1024 * 1024;  // Unitless
localparam int SystemClockHz = 200 * Mega;

Active-Low Signals

The _n suffix indicates an active-low signal.

It’s recommended to avoid active-low signals. If active-low signals are used, they must have the _n suffix in their name. Otherwise, all signals are assumed to be active high.

Differential Pairs

Use the _p and _n suffixes to indicate a differential pair.

For example, in_p and in_n comprise a differential pair set.

Valid Qualifier Suffix

foo_valid indicates when foo_* signals are valid.

Signals named with the “_valid” suffix indicate when a set of related signals are valid. The signals being labelled by the “foo_valid” signal must all have the same “foo_” prefix.

Signals named with the “_valid_next” suffix indicate when a set of related signals will be valid in the next clock cycle (distinct from the _next register suffix). An interface may use _valid_next instead of _valid to provide early valid indication; all of the following rules applied to _valid similarly apply to _valid_next.

Signals in the “foo_” group must be ignored when “foo_valid” is not asserted.

Note that the additional presence of a signal ending with _ready indicates the use of ready/valid flow control semantics.

For example:

output logic [`DATA_WORD] data,
output logic              data_valid,

output logic [`COMMAND_OP_WORD] command_operation,
output logic [`OPERAND_WORD]    command_operand_a,
output logic [`OPERAND_WORD]    command_operand_b,
output logic                    command_valid,

Register Signal Suffixes

The _next and _le suffixes have standard meanings related to registers.

The _next suffix indicates a signal that is a D input into a register. Therefore, the _next suffix is permitted on interface signals and module ports to indicate next-state signals passing across hierarchical boundaries.

The _le suffix indicates an optional load-enable signal to a register.

Example:

logic [15:0] foo;  // the Q output of a register
logic [15:0] foo_next;  // the D input of that same register
logic        foo_le;  // the load-enable for the foo register

always @(posedge clk) begin
  if (foo_le) begin
    foo <= foo_next;
  end
end

Additionally:

The _inc suffix indicates an increment-enable signal when implementing a counter.

The _clear suffix indicates a register-specific synchronous clear operation.

Delayed Signal Suffix

Signals delayed by a single clock cycle must end in a _d suffix.

If one signal is only a delayed version of another signal, the _d suffix must be used to indicate this relationship.

If another signal is then delayed by another clock cycle, the next signal must be identified with the _d2 suffix, and then _d3 and so on.

Example:

always @(posedge clk) begin
  data_valid_d <= data_valid;
  data_valid_d2 <= data_valid_d;
  data_valid_d3 <= data_valid_d2;
end

NOTE: This guide makes a distinction between delayed signals and pipelines. A delayed signal is a stand-alone signal that is delayed by a number of cycles. A pipeline is a collection of signals across a number of cycles. Pipelines have a different style.

Fixed-Point Signals

Signals used for fixed-point arithmetic may be suffixed to indicate their sign, number of integer bits (including sign bit for signed signals), and number of fractional bits.

In modules with multiple fixed-point data types, adding a suffix to signal names may be useful to clearly capture intended design behavior. If a suffix is used, signal names must be in the form _sMpN for signed signals and _uMpN for unsigned signals. “N” indicates the number of fractional bits. “M” indicates the number of integer bits, including the sign bit for signed signals. This format is based on Q Notation.

Example:

// signed, 3 bit integer, 12 bit fractional
logic signed [14:0] foo_s3p12;
// unsigned, 4 bit integer, 9 bit fractional
logic [12:0] bar_u4p9;

// Addition result of foo and bar signals
// signed, 6 bit integer, 12 bit fractional
logic signed [17:0] foo_bar_s6p12;
assign foo_bar_s6p12 = foo_s3p12 + signed'({1'b0, bar_u4p9, 3'd0});

Order of Suffixes

When combining multiple suffixes on a single signal, order them from inner semantic qualifiers to outer temporal qualifiers.

When multiple standard suffixes apply to a single signal, they must be appended in the following order:

  1. Units / Semantic Qualifiers: Functional qualifiers and units (for example, _bytes, _valid, _ready, _req, _ack).
  2. Fixed-Point Format: Fixed-point format specifiers (_sMpN, _uMpN).
  3. Active-Low Polarity: Active-low indicator (_n).
  4. Differential Polarity: Differential pair polarity (_p, _n).
  5. Stage / Delay / Register Qualifiers: Temporal qualifiers (_next, _le, _inc, _clear, _d, _d<N>).

Examples:

// Semantic/unit qualifier before register/delay suffix
logic foo_valid_d;
logic [15:0] packet_len_bytes_next;

// Fixed-point format before register/delay suffix
logic signed [14:0] coeff_s3p12_next;

// Active-low before register/delay suffix
logic rst_n_d;

// Active-low before differential pair
logic sync_n_p,  // Positive pin of active-low sync pair
logic sync_n_n,  // Negative pin of active-low sync pair

// Full multi-tier chain: semantic qualifier -> active-low -> delay
logic dma_req_n_d;
// Bad: Register/delay suffix placed before semantic or fixed-point suffix
logic in_d_valid;
logic [15:0] packet_len_next_bytes;
logic signed [14:0] coeff_next_s3p12;

// Bad: Differential suffix placed before active-low suffix
logic sync_p_n,
// Ambiguous whether second _n is differential or active-low
logic sync_n_n,

Preprocessor Directives and Pragmas

This section provides guidance on the use of verilog preprocessor directives and pragmas.

Global Macro Files

A global macro file is a file that contains only global defines. Global defines are only permitted within global macro files.

Global macro files are distinct from include files. Global macro files may not be included using `include. They are intended to be visible by all source files in a project.

Macro Definitions

Macro names must be ALL_CAPS (in other words, consist of only capital letters, digits, or underscores).

A global define is a tick-defined macro in a global macro file that is shared by all source files in a project. To reduce namespace collisions, global defines must be prefixed by the name of a group of related macros, followed by a pair of underscores:

// The following is the BAR macro in the FOO namespace (optional)
// of the DMA block.
`define DMA_FOO__BAR(params) \
    ...

A global define that is a natural extension or abbreviation of a construct in the UVM library is exempt from these rules - it is acceptable to define such macros with the same convention as the UVM library (in other words, lower_snake_case).

Local macros must be prefixed with a single underscore.

A local define is a tick-defined macro that must only be used within the scope of a single local file. It must be explicitly undefined after use, to avoid polluting the global macro namespace. To indicate that a macro is only meant to be used in the local scope, the macro name must be prefixed with a single underscore.

To ensure that local defines stay local, be careful not to `include other files between the macro definition and `undef.

Example:

`define _MAKE_THING(_x) \
    thing i_thing_##_x (.clk(clk), .i(i##_x) .o(o##_x));
`_MAKE_THING(a)
`_MAKE_THING(b)
`_MAKE_THING(c)
`undef _MAKE_THING

Standard Macros

Use standard macros when available.

Whenever possible, reuse standard macro libraries rather than writing your own. This style guide provides a reference implementation for the following standard macros.

STRINGIFY Macro

The STRINGIFY macro presents a standard way to convert a macro argument into a string.

`define STRINGIFY(_x) `"_x`"

For example:

`define DISPLAY_VALUE(_x) $display("%s: %p", `STRINGIFY(_x), (_x))

Don’t invent alternative implementations:

// This doesn't work with all tools, use STRINGIFY instead:
`define BAD_DISPLAY_VALUE(_x) $display("%s: %p", "_``_x``_", (_x))

BOUNDED_LOOKUP Macro

The BOUNDED_LOOKUP macro presents a standard way to build an rvalue that indexes into an array, when the array size is smaller than maximum possible value of the index.

The verilog expression array[index] would return 'X for out-of-bound indices. This alternative has the following properties:

`define BOUNDED_LOOKUP(__arr, __idx) \
    (($size(__arr) == 1'b1) ? __arr[$high(__arr)] \
    : (__idx[(($high(__arr) == 0) ? 1 : $clog2($high(__arr) + 1)) - 1 : 0] < $low(__arr) || \
       __idx[(($high(__arr) == 0) ? 1 : $clog2($high(__arr) + 1)) - 1 : 0] > $high(__arr)) ? '0 \
    : __arr[__idx[(($high(__arr) == 0) ? 1 : $clog2($high(__arr) + 1)) - 1 : 0]])

X_OF_TYPE Macro

The X_OF_TYPE macro presents a standard way to typecast 'X.

`define X_OF_TYPE(__type) \
  (__type'({$bits(__type){1'bx}}))

Assertion Macros

Use assertion libraries to enforce unified implementation throughout the design.

Following is a reference implementation of standard assertion macros used in this document.

// Concurrent assertion to check that a property is not violated. The assertion
// is disabled if reset is active. The sampling events are positive edges of the
// clock.
`define ASSERT(__name, __prop, __clk, __rst) \
`ifdef ASSERT_ON \
  __name: assert property ( \
      @(posedge __clk) disable iff (__rst !== 0) (__prop)); \
`endif

// Concurrent assertion to check that an expression evaluates to a known value
// (all bits are either '0' or '1') when not in reset.
`define ASSERT_KNOWN(__name, __expr, __clk, __rst) \
  `ASSERT(__name, ~$isunknown(__expr), __clk, __rst)

// A static assertion that evaluates during elaboration, to check that an
// expression is true.  Primarily used to validate module parameters.
`define ASSERT_STATIC(__name, __expr) \
  `ifdef ASSERT_ON \
    if (!(__expr)) \
      $fatal(2, "Failed static assertion: %s: (%s) is not true.", \
        `"__name`", `"__expr`"); \
  `endif  // ASSERT_ON

Synthesis Pragmas

Do not use synthesis pragmas that can lead to simulation-synthesis differences.

There are several pragmas that impact the creation of the design database leading to different tools inferring different functionality. These can lead to hard-to-debug simulation-synthesis mismatches that (depending on methodology) might not be caught by logical equivalence checks. These include translate_off and translate_on, synthesis_off and synthesis_on, read_comments_as_hdl on and read_comments_as_hdl off, parallel case, and full case. Do not use these pragmas.

`ifdef Directives

Do not use `ifdef, `else, `elsif, or `ifndef compiler directives except as prescribed.

Avoid `ifdef whenever possible. Misuse of `ifdef-style compiler directives can negatively impact code coverage, and can lead to a mismatch between simulation and synthesis. For most use cases, separate source units or generate-if statements provide a better solution.

The following exceptions are permitted.

`ifdef SIMULATION

May only be used additively, to add debug logic or logging statements. SIMULATION will typically be defined in simulation environments, and will never be defined in any other environment.

Do not use the SIMULATION macro to select between RTL and behavioral models (see anti-patterns). Do not use the SIMULATION macro to wrap simple assertions (see ASSERT_ON).

For example:

`ifdef SIMULATION

state_t prev_state = IDLE;
always @(state) begin
  $display("%t: %m: state transition: %s -> %s",
           $time, prev_state.name(), state.name());
  prev_state = state;
end

`endif  // SIMULATION

`ifdef ASSERT_ON

May only be used additively, to add assertions or assertion-specific modeling code. This enables an easy path to disable assertions in cases where faster simulation is needed. See the Assertion macros section for macros that implement this switch.

For example:

`ifdef ASSERT_ON

int outstanding_credits = 0;
always @(posedge clk) begin
  if (credit) outstanding_credits += 1;
  if (valid)  outstanding_credits -= 1;
  if (rst)    outstanding_credits = 0;
end

credits_never_underflow: assert property (
  @(posedge clk) disable iff (rst)
  outstanding_credits >= 0);

`endif  // ASSERT_ON

`ifdef FPV_ON

May only be used additively, to add monitors and logic used exclusively by formal property verification tools.

`ifdef <simulator>

Some simulators have simulator-specific debug features that need preprocessor guards. No functional RTL must ever be enclosed by an `ifdef <simulator> block.

The legal names to identify supported simulators are:

`ifdef VCS
  $stack();
`endif  // VCS

`ifdef <technology>

It is permitted to use `ifdef directives to select between implementations enabled by specific technologies (standard cell libraries, emulation, processes, FPGA architectures).

If used, there must always be a valid `else clause that describes a default implementation. There must also be unit tests that exercise all technology cases.

Some of the standard names are:

`ifdef Anti-patterns

Do not enable features with `ifdef. Instead, use parameters and generate-if statements.

Do not use directives similar to `ifdef DISABLED to disable blocks of code. Delete dead code instead.

Do not use include guards. Use a properly ordered compilation file list (flist) so that include guards are unnecessary. See the Include Guards section for more details.

`ifdef Exceptions

There are always unanticipated exceptional scenarios. If your project must use non-standard `ifdef directives, follow the following guidelines:

Includes

Include Files

Files intended for `include are referred to as “include files”.

Include Directives

Use `include directives only to include files into a verilog package file or to include files with code snippets.

Include directives used for the purpose of compiling each file standalone are forbidden. Instead, use a properly ordered compilation file list and Multi-File Compilation Unit (MFCU) mode.

There are only a few cases where include directives are allowed:

  1. Including files into a verilog package file is allowed. Each file must be included into only one package.

    package foo_pkg;
      // Good: breaking large package into smaller files is okay.
      `include "foo_functions.svh"
    endpackage
    
    // Include 'foo_pkg' package because module 'foo_top' depends on it.
    // Bad: can lead to a repeated declaration of a package.
    `include "foo_pkg.sv"
    module foo_top;
      localparam foo_pkg::data_t data = 0;
    endmodule
    
  2. Including files with code snippets is allowed, only when the code snippet cannot be wrapped into its own module or package.

    module foo (
        `include "generated_signals.svh"  // Good: snippet, which cannot be wrapped
    );
    endmodule
    
    module foo ();
      `include "common_assertions.svh"  // Bad: wrap into a module instead
    endmodule
    

Include Guards

Don’t use include guards.

Include guards protect against repeated expansion within the same translation unit. They must not be used since the current style guide rule on the use of includes will prevent this from happening.

// Bad: include guards are not needed.
`ifndef PATH_TO_MY_CODE_VH_
`define PATH_TO_MY_CODE_VH_
...
`endif  // PATH_TO_MY_CODE_VH_

Stopping Compilation

Use the `DIE() macro to fail compilation.

Using this (undefined) macro allows the source file to be parsed by tools that lack preprocessing, such as code search and IDE outline generators. Pass an explanation as a string to `DIE. This convention does not depend on any other macro files.

`ifndef USING_SIMULATOR_A
`ifndef USING_SIMULATOR_B
`DIE("Unknown or unsupported simulator.")
`endif  // USING_SIMULATOR_B
`endif  // USING_SIMULATOR_A
`ifndef USING_SIMULATOR_A
`ifndef USING_SIMULATOR_B
Error: intentional syntax error makes this unparseable without preprocessing
`endif  // USING_SIMULATOR_B
`endif  // USING_SIMULATOR_A

Modules

Module Header Definition

Use the Verilog-2001 full port declaration style, and use the following format.

Use the Verilog-2001 combined port and I/O declaration style. Do not use the Verilog-95 list style. Port declarations in the module header must fully declare the port name, type, and direction.

The clock port(s) must be declared first in the port list, followed by any and all reset inputs.

For port or parameter lists with declarations, the closing parenthesis of each list must be on its own line, in column zero. The opening parenthesis must occur on the same line as the module name or the previous list’s closing parenthesis. The following are exceptions to these rules:

Each import statement must appear on its own dedicated line of code.

Module Header Definition Examples

Example of a module header without parameters:

module foo (
    input  logic clk,
    input  logic rst,
    input  logic [7:0] d,
    output logic [7:0] q
);

Example of a module header with a parameter declaration:

module foo #(
    parameter int Width = 8
) (
    input  logic clk,
    input  logic rst,
    input  logic [Width-1:0] d,
    output logic [Width-1:0] q
);

Example of a module header with an import statement:

module foo
  // Imports the package for use in the header definition.
  // This is a complete statement within the module, so we indent one level.
  import foo_pkg::AddrWidth;
#(
    parameter int DataWidth = 8
) (
    input  logic clk,
    input  logic rst,
    input  logic [AddrWidth-1:0] addr,
    input  logic [DataWidth-1:0] d,
    output logic [DataWidth-1:0] q
);

Do not use Verilog-95 style:

module foo (a, b, c, d);
  input  logic [2:0] a;
  output logic b;
...

Module Names

The top level module for a design [design] must be named [design]_top. Unit test module names end with _test.

Module names must use lower_snake_case and contain only lowercase alphanumeric characters and underscores. Use lowercase even when module-names contain proper nouns, such as the project’s name.

Top-level modules must be named [design]_top. For example, the top-level module for Snout must be named snout_top. In this context, the “top level module” is the highest possible level of a logic design’s hierarchy, the pin-level interface.

Unit test modules must end with the string “_test”. For example, dma_controller_test.

Parameterized Modules

When to parameterize

Use module parameters to create customizable modules.

Examples of when to use parameterized modules:

Do not use module parameters to transmit constants through a design hierarchy (use package constants) or to declare a constant whose scope is internal to the module (use localparam).

For additional reading, see New Verilog-2001 Techniques for Creating Parameterized Models.

Naming module parameters

UpperCamelCase is the recommended style for module parameter names, except:

Use ANSI-style parameter port lists

Declare parameterized modules using a parameter port list, not with defparam.

module foo #(
    parameter int ParameterName = 1,
    parameter type type_parameter_name_t = logic
) (
    input  type_parameter_name_t ib_headward,
    ...etc...

Parameter defaults

Default values assigned to tunable parameters must be reasonable (in other words, default values must represent a valid design).

Omitting a default value assignment to a parameter is permitted if it helps mitigate potential integration bugs.

For example, a reusable IP’s clock frequency parameter may be reassigned in the majority of instantiations. In this case, the designer may choose to omit the default value to eliminate the possibility of unintentionally leaving a parameter unassigned.

Omitting default values for composite type parameters is not permitted due to inconsistent tool support.

Specify parameter data types

Explicitly declare the type for parameters. Use type “int” (signed integer) for all simple numeric parameters (for example, quantities). Always write assertions to validate parameters (bounds checks, etc.). See the Assertion macros section for a reference implementation.

For example:

module cdc_fifo #(
    parameter int Width = 16,
    parameter int Depth = 4
) (
    ...
);

  `ASSERT_STATIC(width_must_be_positive_A, Width > 0)
  `ASSERT_STATIC(depth_greater_than_1_A,   Depth > 1)

Module Instances

Module Instance Names

All module instance names must start with the name of the module being instantiated.

All module instance names must be equal to the module name, plus an optional unique suffix. This unique suffix may be a string or a number, as appropriate. The intent of this naming convention is to make it easier to map from instance names to module names, especially in contexts where the module name is not readily available (for example, synthesis reports, assertions, etc.).

Exceptions can be made in instances where this style decreases the overall legibility of the code (for example, when working with long, hard to read library module names). In such cases, the module name must follow a project specific pattern that readily identifies the original module.

Some examples of legal instance names:

// A single instance of my_decoder within a module:
my_decoder my_decoder ( ... );

// A set of enumerated instances:
my_counter my_counter0 ( ... );
my_counter my_counter1 ( ... );

// A set of symbolically named instances:
my_fifo my_fifo_foo ( ... );
my_fifo my_fifo_bar ( ... );

// Descriptive specifiers:
nand nand_this_that (.a(this), .b(that), .y(not_this_and_that));
register #(10) register_foo (.d(foo_next), .q(foo), .clk, .rst);
sram sram_bar (.address(bar_address), .data(bar_data), .clk, .rst);

Use named ports

Use named ports to fully specify all instantiations.

When connecting signals to ports for an instantiation, use the named port style, like this:

my_module my_module(
    .clk(clk),
    .rst(rst),
    .in(from_here),
    .out(to_there)
);

If the port and the connecting signal have the same name, you can use the .port syntax (without brackets) to indicate connectivity. For example:

my_module my_module(
    .clk,
    .rst,
    .in(from_here),
    .out(to_there)
);

All declared ports must be present in the instantiation blocks. Unused outputs must be explicitly written as no-connects (for example: .output_port()), and unused inputs must be explicitly tied to ground (for example: .unused_input_port(8'd0)).

.* is not permitted.

Do not use positional arguments to connect signals to ports.

Instantiate ports in the same order as they are defined in the module.

Instance connection widths

Port connections on module instances must always match widths correctly.

It is recommended to use explicit widths, rather than relying on Verilog’s implicit zero-extension and truncation operations, whenever practical.

Examples:

my_module my_module (
    .thirty_two_bit_input({16'd0, sixteen_bit_word})
);
my_module my_module (
    // Incorrectly implicitly extends from 16 bit to 32 bit
    .thirty_two_bit_input(sixteen_bit_word)
);

Parameterized Module Instantiation

Use named parameters for all instantiations.

Instances must be named as specified in Module Instance Names.

When parameterizing an instance, do not specify parameters positionally. Specify parameters using the named parameter style, as follows:

my_module #(
    .Height(5),
    .Width(10)
) my_module (...);
my_module #(5, 10) my_module (...);

Do not use defparam.

Recursive Instantiation

Do not instantiate recursively.

Modules may not instantiate themselves recursively.

Generate Constructs

Always name generate construct blocks. The block name must start with the “gen_” prefix.

When using a generate construct, always explicitly name each block of generated code. Name each possible outcome of the generating if statement, and name the iterated block of a generating for statement.

All generate construct block names must start with the “gen_” prefix.

This ensures that generated hierarchical signal names are consistent across different tools.

Example of a conditional generate construct:

if (TypeIsPosedge) begin : gen_posedge_type
  always @(posedge clk) foo <= bar;
end else begin : gen_negedge_type
  always @(negedge clk) foo <= bar;
end

Example of a loop generate construct:

for (genvar bus = 0; bus < NumberOfBuses; bus++) begin : gen_my_buses
  my_bus #(.index(bus)) my_bus (.foo(foo), .bar(bar[bus]));
end

Do not wrap a generate construct with an additional begin block.

Do not use generate regions {generate, endgenerate}.

Floating begin-end blocks

The use of generate blocks other than for loop, if, or case generate constructs is not LRM compliant. While such usage might be accepted by some tools, this guide prohibits such “bare” generate blocks. Note that the similar “sequential block” construct is LRM compliant and allowed.

module foo (
    input  logic bar,
    output logic foo
);
  begin  // illegal generate block
    assign foo = bar;
  end
endmodule

Packages

Package Declaration

There is no special guidance for declaring packages.

Package Names

Package names must use lower_snake_case and contain only lowercase alphanumeric characters and underscores. A package name must end with the suffix _pkg.

Package Dependencies

Packages must not have cyclic dependencies.

Package files may depend on constants and types in other package files, but there must not be any cyclic dependencies. That is: if package A depends on a constant from package B, package B must not depend on anything from package A. While cyclic dependencies are permitted by the SystemVerilog language specification, their use can break some tools.

For example:

package foo_pkg;
  parameter int PageSizeBytes = 16 * bar_pkg::Kibi;
  ...
endpackage

package bar_pkg;
  // Bad: cyclic dependency, package "bar_pkg" must
  // not depend on anything in "foo_pkg"
  import foo_pkg::PageSizeBytes;
  ...
endpackage

Package imports

Package imports are permitted within a local scope, like a module or package. Explicit imports are recommended because it is clear where everything comes from. Wildcard imports are permitted if there is a reasonable argument that the reduced verbosity enhances readability.

Packages must not be imported into the global scope, as this increases the risk of conflicts.

module foo
  import bar_pkg::AddressWidth; // Good: allows usage of package object in ports list
(
    input bar_pkg::data_t data,  // Use fully-qualified name in portlist
    input logic [AddressWidth-1:0] addr,  // Use imported parameter in ports list
    ...
);
  import foo_pkg::*;  // Good: but be mindful of potential conflicts
  import bar_pkg::PageSizeBytes;  // Good: preferred explicit import
import foo_pkg::*;  // Bad: pollutes global namespace
module foo
(
    input  data_t data,
    ...
);

Interfaces

Overview

There is a general desire to adopt interfaces in synthesizable code (DV code can and does use interfaces). However, in practice, projects have found that there are several quirks and challenges when working with interfaces. These include:

Since many of the benefits of interfaces can be realized with structs, the cost of allowing interfaces outweighs their benefits. There is likely a set of best practices that can be defined that would realize the potential of interfaces. Adoption of interfaces would require including such best practices in the style guide.

Ports, Signals, and Constants

Overview

Additionally, the semantics of specific naming conventions are elaborated on in the separate Naming Conventions section.

Declare all signals

Signals must be declared before they are used. This means that implicit net declarations must not be used.

All signals must be explicitly declared before use. All declared signals must specify a data type. A correct design contains no inferred nets.

Within modules, it is recommended that signals, types, enums, and localparams be declared close to their first use. This makes it easier for the reader to find the declaration and see the signal type.

Use logic for synthesis

Use logic for synthesis. wire is allowed when necessary.

All signals in synthesizable RTL must be implemented in terms of 4-state data types. This means that all signals must ultimately be constructed of nets with the storage type of logic. While SystemVerilog does provide other data primitives with 4-state storage (for example, integer), those primitives are prone to misunderstandings and misuse.

For example:

logic signed [31:0] x_velocity;  // say what you mean: a signed 32-bit integer.
typedef logic [7:0] byte_t;
bit signed [63:0] stars_in_the_sky;  // 2-state logic doesn't belong in RTL
int grains_of_sand;  // Or wait, did I mean integer?  Easy to confuse!

It is permissible to use wire as a short-hand to both declare a net and perform continuous assignment. Take care not to confuse continuous assignment with initialization. For example:

wire [7:0] sum = a + b;  // Continuous assignment

logic [7:0] acc = '0;  // Initialization

There are exceptions for places where logic is inappropriate. For example, nets that connect to bidirectional (inout) ports must be declared with wire. These exceptions must be justified with a short comment.

It is permissible for DV to make use of 2-state logic, but all interfaces between 4-state and 2-state signals must assert a check for X on the 4-state net before resolving to a 2-state variable.

Signal Naming

Use lower_snake_case when naming signals.

In this context, a signal is meant to mean a net, variable, or port within a SystemVerilog design.

Signal names may contain lowercase alphanumeric characters and underscores.

Ports which connect directly to chip primary IO pads (for example, die IOs, ASIC package IOs, or FPGA IO buffers) may use UPPER_SNAKE_CASE.

module chip_top (
    input logic RESET_N,
    output logic LINK_LED,
    inout wire SPI_DATA
);

There are a number of naming conventions associated with naming signals, which are complicated enough to warrant their own section of the style guide.

Default Port Values

Do not use default port values.

A default port value is a constant expression that is assigned to an input port of a module. Output ports that are assigned constant expressions are not considered default values and are permitted.

module foo (
    // Input d must be assigned when this module is instantiated
    input logic d,
    // This is an initialization of output q and is okay
    output logic q = '0
);
module foo (
    // Bad: Do not assign a default value to an input port
    input logic d = '0,
    ...
);

Constants

Declare design-wide constants using parameters in the design package file. Declare local constants using localparam.

In this context, constants are distinct from tunable parameters for objects such as parameterized modules, classes, etc.

Declaring Constants

When declaring a constant:

The recommended method of defining constants is to declare a package and declare all constants as a parameter within that package.

Define design-wide constants in the design’s package file.

Other packages may also be declared with their own parameter constants to facilitate the creation of reusable IP.

Naming Constants

UpperCamelCase is the recommended style for immutable constants. The exceptions are:

Specify constant data types

Explicitly declare the type for constants. Use type “int” (signed integer) for all simple numeric parameters (for example, quantities).

Avoid Magic Numbers

It is recommended to use symbolically named constants instead of unnamed numerical constants (aka magic numbers).

Try to give commonly used constants symbolic names rather than repeatedly typing magic numbers.

// Declares a symbolically named constant for the value 0xD5
localparam logic [DataWidthBits-1:0] StartFrameDelimiter = 8'hD5;
...
// The named constant helps clarify the code
if (data == StartFrameDelimiter) begin
  next_state = kRxData;
end
// Bad: What's the significance of this number?
if (data == 8'hD5) begin
  next_state = kRxData;
end

Unnamed numerical constants may be acceptable in cases where adding a symbolic name adds no value. These type of constants are not considered magic numbers.

// Counter uses unnamed constant 1, but the meaning is obvious
cycle_cnt <= cycle_cnt + 1;

localparam in parameter port lists

Generally, module scoped constants, including those derived from module parameters are declared as localparam within the module body close to first use. It can be useful to have module scoped constants (potentially derived from module parameters) that control module port dimensions. Declare and define these constants as localparam in the module port list to prevent them from being overridden when the module is instantiated.

module foo #(
    parameter int NumEntries = 1,  // Tunable parameter
    localparam int IndexWidth = $clog2(NumEntries)  // Must not be overridden
) (
    input [IndexWidth-1:0] foo
);

  localparam int Bar = 7;
...
endmodule
module foo #(
    localparam int Bar = 7,  // Bad: Bar is a constant so it goes close to usage
    parameter int NumEntries = 1,
    parameter int IndexWidth = $clog2(NumEntries)  // Bad: IndexWidth is linked to NumEntries
) (
    input [IndexWidth-1:0] foo
);
  ...
endmodule

Packed vs. Unpacked

Use packed arrays and structs for synthesizable signals. Unpacked arrays are appropriate for behavioral models of SRAMs and DV code.

Positive Array Bounds

When declaring vectors and arrays, the msb and lsb must be non-negative.

logic [14:0] foo;
logic [13:-1] foo;

Packed Ordering

Bit vectors and packed arrays must be little-endian.

When declaring bit vectors and packed arrays, the index of the most-significant bound (left of the colon) must be greater than or equal to the least-significant bound (right of the colon). This style of bit vector declaration keeps packed variables little-endian.

For example:

typedef logic [7:0] u8_t;
logic [31:0] u32_word;
u8_t [1:0] u16_word;
u8_t byte3, byte2, byte1, byte0;
assign u16_word = {byte1, byte0};
assign u32_word = {byte3, byte2, u16_word};

Unpacked Ordering

Unpacked arrays must be big-endian.

When declaring unpacked arrays, the index to the left of the colon must be less than or equal to the index to the right of the colon.

Declare zero-based unpacked arrays using the shorter notation [size]. It is understood that [size] is equivalent to the big-endian declaration [0:size-1].

Assigning directly to an unpacked array as a whole is acceptable, but not recommended, because mixing big-endian unpacked arrays with little-endian packed arrays is prone to reversed-ordering mistakes. Prefer assigning elements individually by index when possible (for parameter arrays, where element-by-element assignment is not possible without constant functions, initializing the unpacked array directly is acceptable with caution).

// Good: Assign unpacked array elements individually by index to avoid
// reversed-ordering mistakes.
logic [15:0] word_array[3];
assign word_array[0] = word0;
assign word_array[1] = word1;
assign word_array[2] = word2;

// Acceptable, but not recommended for signals: Avoid assigning to an unpacked
// array as a whole. Result: word_array[0] == word0, word_array[1] == word1, and
// word_array[2] == word2.
assign word_array = '{word0, word1, word2};

// Acceptable for parameters, but be cautious with big-endian ([0:3]) ordering.
// Result: Vec[0] == 0, Vec[1] == 1, Vec[2] == 2, and Vec[3] == 3.
parameter int Vec[4] = '{0, 1, 2, 3};

Rationale: Big-endian ordering was selected for unpacked arrays over little-endian because:

This rule only applies to variables and parameters, not to other things such as arrays of instances. Instance arrays must be declared so that the order of instances is correct for the ordering of packed ports.

Structs and Unions

Declaring Structs and Unions

Name structs and unions with a typedef.

Always name struct and union types using typedef. Anonymous types are not allowed because they are not reusable. Struct type names must contain only lowercase alphanumeric characters and underscores and end with _t.

typedef struct packed {
  logic  dir;
  int    value;
} data_t;  // new named type is created

data_t packet;
struct packed {  // Typedef is missing
  logic  dir;
  int    value;
} packet;  // anonymous struct type

Naming Union Members

Union member names must be prefixed with as_*.

Always prefix union member names with as_*. A consistent naming convention reduces the effort required to understand RTL with deeply nested struct or union type hierarchies by making the type-cast like behavior of unions explicit.

typedef union packed {
  read_id_t   as_read_id;  // as_read_id prefix to refer to read_id_t type.
  write_id_t  as_write_id;
} id_t;

assign write_id = cmd.id.as_write_id;
typedef union packed {
  read_id_t   read_id;  // as_* is missing
  write_id_t  write_id;
} id_t;

assign write_id = cmd.id.write_id;

Enumerations

Name enumeration types snake_case_t (or snake_case_e). Name enumeration constants kCamelCase.

Always name enum types using typedef. The storage type of any enumerated type must be specified. For synthesizable enums, the storage type must be a 4-state data type (logic rather than bit).

Anonymous enum types are not allowed as they make it harder to use the type in other places throughout the project and across projects.

Enumeration type names must contain only lowercase alphanumeric characters and underscores. You can suffix enumeration type names with _t or _e, as long as the suffix is consistent within the project.

Enumeration value names (constants) must be kCamelCase, for example, kReadyToSend. This indicates that the values are neither overrideable nor assignable.

typedef enum logic {  // A 4-state storage type
  kWrite,
  kRead
} access_t;  // new named type is created
access_t req_access, resp_access;
enum {  // Typedef is missing, storage type is missing.
  Write,
  Read
} req_access, resp_access;  // anonymous enum type

Vector Typedefs

Prefer no single vector typedefs unless they are broadly used.

Do not use typedef to define local or single-use vector types. Defining a new type for a vector that is only used in a limited scope adds unnecessary redirection. Instead, use the raw vector type directly.

For vectors that are used broadly across multiple modules or packages (for example, a global address type), defining a named type using typedef is recommended to ensure consistency.

// Declare design-wide vector typedefs in a package.
package my_pkg;
  typedef logic [AddrWidth-1:0] addr_t;
endpackage

module my_module (
  ...
);
  // Good: Use design-wide typedef for broadly used vector.
  my_pkg::addr_t addr;
  ...
endmodule
module my_module (
  ...
);
  // Bad: Unnecessary local typedef for a single-use vector.
  typedef logic [DataWidth-1:0] local_data_t;
  local_data_t local_data;
  ...
endmodule

An exception is made when defining signed packed arrays, where a typedef is necessary to apply signedness to the elements of the array rather than the array as a whole (see mixed-sign arithmetic operations).

// Good: Use local typedef to apply signedness to elements of a packed array.
typedef logic signed [DataWidth - 1:0] data_t;
data_t [NumChannels - 1:0] my_channel_data;
// Bad: Signedness applies to the entire packed array, not the elements.
logic signed [NumChannels - 1:0][DataWidth - 1:0] my_channel_data;

Functions and Tasks

Choosing Between Functions, Tasks, and Modules

In synthesizable RTL, use modules as the default construct to encapsulate behavior. Tasks must not be used in synthesizable RTL. Functions may be used for short, simple combinational logic with a single output.

Templates

Function and task definitions must follow the template in the following example.

// Description of the function's behavior, inputs, and outputs
function automatic logic foo(logic x, logic y);
  // Local variables
  logic z;
  // Statements
  z = x ^ y;

  // Return
  return z;
endfunction : foo

// Description of the task's behavior, inputs, and outputs
task automatic bar(input logic x, output logic y);
  // Variable declarations

  // Statements
  y = x;
endtask : bar

Some points about the function template to note in addition to the following sections:

Latches inside functions

Do not infer a latch inside a function, as this may cause a simulation-synthesis mismatch.

Lifetime

Explicitly declare functions and tasks defined outside of a class as static or automatic, preferring automatic. Functions must be automatic in synthesizable RTL.

Depending on their declaration context, functions and tasks have different default lifetimes. Within a class, they are always automatic, whereas the default is static in modules, interfaces, programs, and packages. To prevent potential confusion, functions and tasks declared outside of a class must always explicitly specify their lifetime. Automatic is generally the proper lifetime because it allows a function or task to be independently called from different contexts.

In synthesizable RTL, functions must always be declared as automatic.

Function Data Types

Explicitly declare the data type for a function, or declare the function as void.

// The return type of the function is explicitly defined as 'bit'
function automatic bit foo(int a);
// The function has no return value.
function automatic void foo(int a);
// The function's data type is implicitly declared
function automatic foo(int a);

Argument Types

Explicitly declare storage types for all arguments in function and task arguments lists.

For example:

// Both arguments have explicitly declared 'int' as their storage type.
task automatic foo(int a, int b);
// Bad: No storage type explicitly declared for 'b'.
task automatic foo(int a, b);

Argument Directions

Argument directions for functions and tasks are optional and considered inputs by default. If a direction is specified for any argument, then it must be specified for all arguments.

For example:

task automatic foo(int a, int b);  // Both arguments treated as inputs.
function automatic bit foo(input int a, output int b, inout int c, ref int d);
// Potential bug: Although not labeled, "b" will be treated as "ref".
task automatic foo(ref int a, int b);

Sequential Logic

always_ff vs always

There is no preference between always_ff and always for clocked processes, other than consistency within a project.

Use Non-Blocking Assignments

Sequential logic must use non-blocking assignments.

Never mix assignment types within a block declaration.

A sequential block (a block that latches state on a clock edge) must exclusively use non-blocking assignments, as shown in the section on declaring Registers.

This stands in contrast to combinational logic, which must use blocking assignments in process blocks.

This is one of Clifford Cummings’s Golden Rules of Verilog.

Sequential Logic (Latches)

The use of latches is not recommended - use flip-flops when possible.

Unless absolutely necessary, use flip-flops or registers instead of latches.

If you must use a latch, use always_latch over always, and use non-blocking assignments (<=). Never use blocking assignments (=).

Sequential Logic (Registers)

Use the standard format for declaring sequential blocks.

In a sequential always block, only use non-blocking assignments (<=). Never use blocking assignments (=).

Designs that mix blocking and non-blocking assignments for registers simulate incorrectly because simulators evaluate blocking assignments in a different simulation event region than non-blocking assignments. This causes race conditions where signals bypass pipeline stages, leading to simulation-synthesis mismatches.

If your next-state generation logic requires blocking assignments to state clearly, use a separate combinational-only block to generate that next-state value.

For reusable IP, it is recommended that registers support all three forms of reset so integrators can tie off unused resets:

Initialization is commonly recommended for SRAM-based FPGAs, whereas synchronous reset is recommended for ASICs.

A correctly implemented 8-bit register with an initial value of “0xAB” would be implemented as follows:

logic foo_le;
logic [7:0] foo, foo_next;

initial foo = 8'hAB;  // initialization
always @(posedge clk or posedge arst) begin
  if (arst) begin
    foo <= 8'hAB;
  end else if (rst) begin
    foo <= 8'hAB;
  end else if (foo_le) begin
    foo <= foo_next;
  end
end

Do not allow multiple non-blocking assignments to the same bit.

Example:

if (cond1) begin
  abc <= 4'h1;
end

if (cond2) begin
  abc <= 4'h2;
end

If both cond1 and cond2 are true, the Verilog standard says that the second assignment will take effect, but this is a style violation.

Even if cond1 and cond2 are mutually exclusive, make the second “if” into an “else if”.

Exception: It is fine to set default values first, then specific values. However, it is recommended to do this work in a separate combinational block with explicit blocking assignments.

Example:

`ASSERT_KNOWN(state_is_known_A, state, clk, rst)
always @(posedge clk) begin
  s1 <= s1_default;
  s2 <= s2_default;
  s3 <= s3_default;
  unique case (state)
    kState1: begin
      s1 <= real_s1_value;
    end
    kState2: begin
      s1 <= real_s1_value;
      s2 <= real_s2_value;
    end
    kState3: begin
      s3 <= real_s3_value;
    end
    default: ;  // empty default, ensures case statement is fully specified
  endcase
end

Keep work in sequential blocks simple. If a sequential block becomes sufficiently complicated, consider splitting the combinational logic into a separate combinational (always_comb) block. It’s recommended for a sequential block to contain only a register instantiation, with perhaps a load enable or an increment.

Resets

Reusable IP must consistently implement all three varieties of reset (synchronous reset, asynchronous reset, and configuration-time initialization) so that the non-preferred reset schemes can be disabled at implementation time. See Sequential Logic for more details.

Delay Modeling

Do not use #delay in synthesizable design modules.

Synthesizable design modules must be designed around a zero-delay simulation methodology. All forms of #delay, including #0, are not permitted.

Delays and Time Units

In non-synthesizable code always specify a time unit if #delay is used, except for #0.

Whenever using non-zero pound # delays, always specify a time unit in addition to the raw number, to avoid timescale issues.

q <= #10ns d;
#(wait_in_ns * 1ns);
#0;  // Special case.  No time units required
// Missing time units.
q <= #10 d;
#wait_in_ns;

Note that specifying time units alone does not guarantee proper behavior. Use project-wide timeunit and timeprecision settings in your module, package, or interface to ensure the proper behavior (see section 3.14.2.2 of the 1800-2017 LRM for more details).

Finite State Machines

State machines use an enum to define states, and are implemented with two process blocks: a combinational block and a clocked block.

Every state machine description has three parts:

  1. An enum that declares and describes the states.
  2. A combinational process block that decodes state to produce next state and other combinational outputs.
  3. A clocked process block that updates state from next state.

Enumerating States

The enum statement for the state machine must list each state in the state machine. Comments describing the states must be deferred to the case statement in the combinational process block, as shown in the following example.

States must be named in kCamelCase, like other enumeration constants.

Barring special circumstances, the initial idle state of the state machines will be named kIdle. (Alternate names are acceptable if they improve clarity.)

It’s recommended for each module to only contain one state machine. If your module needs more than one state machine, you will need to add a unique prefix (or suffix) to the states of each state machine, to distinguish which state is associated with which state machine. For example, a module with a “reader” machine and a “writer” machine might have a kRdIdle state and a kWrIdle state.

Combinational Decode of State

The combinational process block must contain:

The State Register

No logic except for reset must be performed in this process. The state variable must latch the value of the “next state” variable.

Other Guidelines

When possible, try to choose state names that differ near the beginning of their name, to make them more readable when viewing waveform traces.

Example

// Define the states
typedef enum logic [1:0] {
  kIdle, kFrameStart, kAccStoreWrite, kBandEnd
} fsm_state_t;

fsm_state_t state_next, state;

// Combinational decode of the state
// Check that frame_start is not X since it is used in an if statement
`ASSERT_KNOWN(frame_start_is_known_A, frame_start, clk, rst)
always_comb begin
  state_next = state;
  foo = 1'b0;
  bar = 1'b0;
  bum = 1'b0;
  unique case (state)
    // kIdle: waiting for frame_start
    kIdle:
      if (frame_start) begin
        foo = 1'b1;
        state_next = kFrameStart;
      end
    // kFrameStart: Reset accumulators
    kFrameStart: begin
      // ...
    end
    kAccStoreWrite: begin
      // ...
    end
    kBandEnd: begin
      // ...
    end
    default: begin
      // X's in the inputs propagate to X's in the outputs (Number of states is
      // a power of two, therefore case is fully specified and no don't-care
      // behavior will be introduced).
      // See "Don't-care in case statements" section for information on
      // case statements with non-power-of-two items or the X_OF_TYPE macro.
      state_next = `X_OF_TYPE(fsm_state_t);
      foo = 'X;
      bar = 'X;
      bum = 'X;
    end
  endcase
end

// Latch state
always_ff @(posedge clk) begin
  if (rst) begin
    state <= kIdle;
  end else begin
    state <= state_next;
  end
end

Combinational Logic

Use always_comb or assign

Avoid sensitivity lists.

Use either always_comb blocks or assign statements for combinational logic.

Use blocking assignments

Synthesizable combinational process blocks must only use blocking assignments.

All assignments within a synthesizable always_comb block must be blocking assignments.

Non-blocking assignments are used only in sequential logic blocks.

X Propagation

Either propagate X’s, or catch them via assertions. Do not silently squash X in logic.

Write logic that will either propagate X or assert when your inputs are X. Avoid silently squashing X by writing logic that resolves an X input to a non-X output. Instead, write logic that either explicitly propagates the X or uses an assertion to raise an exception when an input is X.

Logical operations and if statements

Note that logical operations involving X will propagate X in a way that accurately represents silicon behavior.

Example:

$display("%b", 1'bx == 1'b1);  // produces 1'bx
$display("%b", 1'bx != 1'b1);  // produces 1'bx
$display("%b", 1'bx & 1'b1);   // produces 1'bx
$display("%b", 1'bx & 1'b0);   // produces 1'b0
$display("%b", 1'bx | 1'b0);   // produces 1'bx
$display("%b", 1'bx | 1'b1);   // produces 1'b1
$display("%b", !(1'bx));       // produces 1'bx.

However, when evaluated in a boolean context, X always evaluates to false. As a result, typical if/else statements do not propagate X in a way that accurately represents silicon behavior.

Example:

always_comb begin
  if (value) result = 1'b0;  // value is 1'b1
  else result = 1'b1;        // value is 1'b0 or 1'bx
end

This can mask subtle problems in code and produce a mismatch between simulation and synthesis.

Instead, use conditional operators, which will propagate an X. If that hurts readability, an alternative is to use assertions to catch X.

always_comb begin
  result = value ? 1'b1 : 1'b0;  // 1'bx produces 1'bx
end
`ASSERT_KNOWN(value_is_known_A, value, clk, rst)
always_comb begin
  if (value) result = 1'b0;     // value is 1'b1
  else result = 1'b1;           // value is 1'b0 or 1'bx
end

Another alternative is to have explicit if and else if clauses for 0 and 1. However, this is verbose and more difficult to maintain.

always_comb begin
  if (value) result = 1'b0;        // value is 1'b1
  else if (!value) result = 1'b1;  // value is 1'b0
  else result = 1'bx;              // value is 1'bx
end

X Propagation in Case Statements

See Case Statements for guidance and examples.

Array indexing

Writing into an array when the index is X does not propagate the X to the array contents.

// An X in index gets silently squashed in simulation.
assign buf[index] = foo;

Instead, assertions must be used to catch X in the array index.

`ASSERT_KNOWN(index_is_known_A, index, clk, rst)
assign buf[index] = foo;

Clocks and Resets

The preceding X propagation guidelines do not apply to clocks and resets. Instead, extra care must be taken at each clock-domain level in the hierarchy to ensure clocks and resets are never driven to X along with !$isunknown() assertions.

The reason for this is that the recommended coding styles for sequential logic do not propagate X on clocks and resets. For example, posedge clk still triggers on a 0 to X transition. Also, synthesis tools recommend using if statements for asynchronous resets instead of conditional operators.

Further discussion

Don’t-Care Assignments

Do not specify don’t-care logic in synthesizable code.

For the purposes of this document, don’t-care logic is defined as the explicit or implicit assignment to X based on known (non-X) inputs. Don’t-care logic is a subset of unknown logic values, and not all assignments to X are considered don’t-care logic. For examples of allowed assignments to X, see X Propagation.

Don’t-care assignments are problematic for automated engineering change orders (ECOs) and complicate Logic Equivalence Checking (LEC) between netlists because synthesis can produce netlists that have chosen conflicting interpretations of the don’t-care states. If designers need to take advantage of don’t-care space for optimizations, there are external tools like Espresso that can be used instead.

Don’t-Care in Explicit Assignments

Do not assign don’t-care logic. A signal must be known if the signals driving it are known.

// Bad: When valid is '0', wdata is considered don't-care
assign wdata = valid ? data : 'x;
assign we = valid;
assign wdata = data;
assign we = valid;

Don’t-Care in Case Statements

See Case Statements for guidance on how to avoid introducing don’t-care logic in case statements.

Don’t-Care in Array Indexing

Do not access an index that is outside the bounds of an array. This can happen if the array size is not a power-of-two, or if the index contains superfluous MSBs.

// For values of index > 4, the output ‘foo’ is treated as don’t-care
logic       foo;
logic [2:0] index;
logic [4:0] buffer;
...
assign foo = buffer[index];

Instead, specify behavior for out-of-range indices or explicitly pad arrays up to power-of-two sizes. Also, add an assertion to catch out-of-range indices.

localparam BufferSize = 5;

logic       foo;
logic [2:0] index;
logic [BufferSize-1:0] buffer;
...
`ASSERT(index_within_array_bounds_A, (index < BufferSize), clk, rst)
// This approach specifies the behavior for out-of-range indices.
// It is concise but might require a lint waiver
assign foo = (index < BufferSize) ? buffer[index] : '0;

logic bar;
logic [7:0] buffer_padded;
// This approach explicitly pads out the array.  It eliminates the need for lint
// waivers, but it can be less concise.
assign buffer_padded = {3'b0, buffer};  // zero-pad the array
assign bar = buffer_padded[index];

Properly handling arbitrary array sizes is complex. It is recommended that projects use the standard BOUNDED_LOOKUP macro for the general case.

assign foo = `BOUNDED_LOOKUP(buffer, index)

Tri-State Logic

Do not use three-state logic (Z state) to accomplish on-chip logic such as muxing.

Case Statements

Use unique case in synthesizable RTL. Always define a default case-item. Do not specify don’t-care behavior. Do not use case-modifying pragmas.

Here is an example of a style-compliant case statement:

always_comb begin
  unique case (select)
    2'b00:   foo = bar0;
    2'b01:   foo = bar1;
    2'b10:   foo = bar2;
    2'b11:   foo = bar3;
    default: foo = 'X;
  endcase
end

The unique prefix is recommended before all case statements, as it creates simulation assertions that can catch certain mistakes. In some cases, priority may be used instead of unique, though if-else-if structures are a more readable representation for priority encoders. Code other than synthesizable RTL can use case without unique or priority if necessary.

Be sure to use unique case correctly. In particular, outputs for any unspecified case-items are treated as don’t-care by the synthesis tools. This can lead to a simulation-synthesis mismatch as described in Don Mills’ paper. Including a default case-item ensures the case statement is always fully defined, which helps avoid unintuitive don’t-care behavior.

The default case-item is also required to avoid accidental inference of latches, even if all cases are covered. In simulation, a case expression that evaluates to X will not match any case and will behave as a latch, leading to different behavior than synthesis. Instead, in most scenarios, the best choice is to propagate the X by assigning all output variables to X by default.

Note that the SystemVerilog-2009 unique0 keyword is not currently supported by all tools.

Wildcards in case items

Use case instead of casez whenever wildcard operator behavior is not required. When wildcard behavior is needed, use casez.

When expressing a wildcard in a case item, use the ‘?’ character since it more clearly expresses the intent.

Here is an example of a style-compliant casez statement:

always_comb begin
  unique casez (select)
    3'b000: operand = accum0 >> 0;
    3'b001: operand = accum0 >> 1;
    3'b010: operand = accum1 >> 0;
    3'b011: operand = accum1 >> 1;
    3'b1??: operand = regfile[select[1:0]];
    default: operand = 'X;  // propagate X
  endcase
end

casex must not be used. casex implements a symmetric wildcard operator such that an X in the case expression may match one or more case items. casez only treats high-impedance states (Z or ?) as a wildcard, and performs exact matches for undriven X inputs. While this does not completely fix the problems with symmetric wildcard matching, it is harder to accidentally produce a Z input than an X input, so this form is preferred.

Don’t-care in case statements

Don’t-care behavior is introduced anytime X assignments are made from a don’t-care branch of a case statement.

To avoid don’t-care behavior, specify non-X behavior for all outputs, across the full range of values in the case-expression. If this prevents x-propagation, assertions must be used to ensure X’s in the case-expression are not silently squashed.

typedef enum logic [1:0] {kMode0, kMode1, kMode2} mode_t;
mode_t mode;

always_comb begin
  unique case (mode)
    kMode0:  foo = bar0;
    kMode1:  foo = bar1;
    kMode2:  foo = bar2;
    default: foo = 'x;  // foo is don't-care when mode = 2'b11;
  endcase
end
typedef enum logic [1:0] {kMode0, kMode1, kMode2} mode_t;
mode_t mode;

`ASSERT_KNOWN(mode_is_known_A, mode, clk, rst)
always_comb begin
  unique case (mode)
    kMode0:  foo = bar0;
    kMode1:  foo = bar1;
    kMode2:  foo = bar2;
    default: foo = '0;  // explicit default value to avoid don't-care behavior
  endcase
end

Note that assigning to X from the default case-item of an otherwise fully-specified case statement does not introduce don’t-care logic. In this case, using the default for x-propagation is the recommended style.

typedef enum logic [1:0] {kMode0, kMode1, kMode2, kMode3} mode_t;
mode_t mode;

always_comb begin
  unique case (mode)
    kMode0:  foo = bar0;
    kMode1:  foo = bar1;
    kMode2:  foo = bar2;
    kMode3:  foo = bar3;
    default: foo = 'X;  // default can be used for x-propagation here,
                        // since the case statement is otherwise complete
  endcase
end

If assigning X to an enumerated type in the default case-item, then the assignment must be typecast. It is recommended that projects use a macro to safely contain this cast.

typedef enum logic [1:0] {kFoo0, kFoo1, kFoo2, kFoo3} foo_t;
foo_t foo1, foo2;

always_comb begin
  unique case (foo1)
    kFoo0:  foo2 = kFoo0;
    kFoo1:  foo2 = kFoo1;
    kFoo2:  foo2 = kFoo2;
    kFoo3:  foo2 = kFoo3;
    default: foo2 = `X_OF_TYPE(foo_t);  // typecast for X in default
  endcase
end

With multiple outputs, it can be convenient to specify default values outside of the case statement. If this is done, the unique case statement must be fully specified, otherwise these outputs are treated as don’t-cares for any unspecified case-items.

`ASSERT_KNOWN(mode_is_known_A, mode, clk, rst)
always_comb begin
  // Default behavior
  out0 = in0;
  out1 = in1;

  unique case (mode)
    kForceLow:  {out1, out0} = '0;
    kForceHigh: {out1, out0} = '1;
    default: ;  // empty default, ensures case statement is fully specified
  endcase
end

Upside-down unique case statements

Upside-down unique case statements are another way of introducing don’t-care behavior. If multiple conditions of the upside-down case statement are true, the output assignment is not explicitly provided in the code, effectively leading to a don’t-care assignment. This will cause the same LEC and ECO issues as if the condition was explicitly coded as an X output. See Don’t-Care Assignments. It is recommended to rewrite the code as a non-upside-down case statement, or do manual logic reduction if timing is a concern.

// If c_grant[0] == 1 and c_grant[1] == 1, out is don't-care.
always_comb begin
  unique case (1'b1)
    c_grant[0]: out = 1'b0;
    c_grant[1]: out = 1'b1;
    default: out = 1'b0;
  endcase
end
// If c_grant[0] == 1 and c_grant[1] == 1, out is explicitly 0.
always_comb begin
  unique case (c_grant)
    2'b01: out = 1'b0;
    2'b10: out = 1'b1;
    default: out = 1'b0;
  endcase
end

Synthesis Pragmas

Never use either the full_case or parallel_case pragmas. These pragmas can easily cause synthesis-simulation mismatches. This is a subset of the general guidance to avoid synthesis pragmas.

References:

Signed Operations

Use the available signed operator constructs wherever signed operations are used.

Mixing signed and unsigned signals in an operation can result in unexpected behavior and, in some cases, simulation-synthesis mismatches. For all operations where signed and unsigned signals need to be used together, the unsigned signals must first be explicitly cast to signed, using the signed' cast operator.

If any operand in a calculation is unsigned, Verilog implicitly casts all operands to unsigned and generates a warning. There must not be any signed-to-unsigned warnings from either the simulation or synthesis tools if all unsigned variables are properly cast.

Mixed-Sign Arithmetic Operations

Explicitly cast unsigned values to signed before use in mixed-sign arithmetic operations.

logic signed [7:0]  a = 8'sh80;   // equals signed(-128)
logic               incr = 1'b1;  // equals unsigned(1)
logic signed [15:0] sum;
always_comb begin
  sum = a + signed'({1'b0, incr});  // Good: explicitly casting incr to signed
                                    // produces expected sum equals signed(-127)
end
logic signed [7:0]  a = 8'sh80;   // equals signed(-128)
logic               incr = 1'b1;  // equals unsigned(1)
logic signed [15:0] sum;
always_comb begin
  sum = a + incr;  // Bad: mixed-sign addition results in implicit cast of `a`
                   // giving sum equals signed(129) instead of signed(-127) as
                   // expected
end

Mixed-Sign Comparison Operations

Explicitly cast unsigned values to signed before use in mixed-sign comparison operations.

logic        [7:0] unsigned_foo = 8'h00;   // equals unsigned(0)
logic signed [7:0] signed_bar   = 8'shFF;  // equals signed(-1)
logic              bar_less_than_foo;
logic              bar_less_than_zero;
always_comb begin
  // Good: Explicitly casting `foo` to signed so comparison returns `true` as
  // expected
  bar_less_than_foo = (signed_bar < signed'({1'b0, unsigned_foo}));
  // Good: Explicitly declare constant as signed decimal ensures consistent
  // compiler-to-compiler behavior.
  bar_less_than_zero = (signed_bar < 'sd0);
end
logic        [7:0] unsigned_foo = 8'h00;   // equals unsigned(0)
logic signed [7:0] signed_bar   = 8'shFF;  // equals signed(-1)
logic              bar_less_than_foo;
logic              bar_less_than_zero;
always_comb begin
  // Bad: Mixed-sign comparison returns unexpected `false`
  bar_less_than_foo = (signed_bar < unsigned_foo);
  // Bad: Non-explicitly signed constant results in inconsistent behavior
  // with different tools
  bar_less_than_zero = (signed_bar < 0);
end

Arithmetic Right Shift Operations

Always use the >>> operator instead of the >> operator when arithmetically right shifting signed values.

It is common practice to use right arithmetic shift operators to divide by powers of 2. With signed operands, care must be taken that the correct shift operator is used to preserve sign. The >> operator inserts a 0 at the most significant bit location. If the argument is a negative value, this will flip the sign. By using the >>> operator, the bit inserted at the most significant bit location is determined by the sign of the argument and will not flip.

logic signed [7:0] foo = 8'sh80;  // equals signed(-128)
logic signed [7:0] bar;
always_comb begin
  bar = foo >>> 1;  // Good: returns signed(-64)
end
logic signed [7:0] foo = 8'sh80;  // equals signed(-128)
logic signed [7:0] bar;
always_comb begin
  bar = foo >> 1;  // Bad: returns signed(64). The `>>` operator inserts a 0 at
                   // the most significant bit location, thereby flipping the
                   // sign of `bar`.
end

Bit Widths

Be careful about signal and expression widths.

Always be explicit about the widths of number literals.

Always explicitly size number literals to match their final width. Verilog’s implicit zero-extension and truncation operations can lead to subtle bugs.

If the literal is specified in a binary format then all digits must be explicitly written out to avoid unintentional zero-extension in the most significant bits. This can catch subtle bugs if an intermediate bit is dropped. Literals written in decimal and hexadecimal formats do not need to explicitly include all significant digits because they tend not to be used for hard-to-read patterns.

Exceptions:

Examples:

localparam int Width = 5;  // int doesn't need explicit width
localparam bit FeatureFlag = 1;  // bit type doesn't need explicit width
localparam logic [3:0] Bar = 4'd4;

logic [15:0] foo1, foo2, foo3, foo4;
logic [7:0]  foo5;
logic [63:0] foo6;
logic [Width-1:0] foo7, foo8;

assign foo1 = 16'd2;  // OK to not have the tens column
assign foo2 = '0;  // automatically sized 0 is ok
assign foo3 = {16{1'b1}};  // explicit "all ones"
assign foo4 = '1;  // automatically sized "all ones"
assign foo5 = 8'b0010_1111;  // exact width literal, no implicit zero-extension
assign foo6 = 64'h2000;  // OK not to write out the whole hex constant
// OK to not explicitly size the integer literal in a parameterized expression
assign foo7 = foo8 + 4;
localparam logic [3:0] Bar = 4;

logic [7:0] foo1, foo2, foo3, foo4;

assign foo1 = 2;  // literal is not explicitly sized
assign foo2 = 2'd2;  // 2-bit literal implicitly 0 extended to 8 bits
assign foo4 = 8'b001_1111;  // MSb is missing and has implicit zero-extension

Do not use multi-bit signals in a boolean context

Rather than letting boolean operations and if expressions reduce a multi-bit signal to a single bit, explicitly compare the multi-bit signal to 0. The implicit conversion can hide subtle logic bugs.

Examples:

logic [3:0] a, b;
logic out;

assign out = (a != '0) && (b == '0);  // Good: Explicit comparisons

always_comb begin
  if (a != '0) begin  // Good: Explicit comparison
    // ...
  end
end

logic any_a;

// Good: Unary reductions are okay in a boolean context.
assign any_a = |a;
always_comb begin
  if (&a) begin
    // ...
  end
end

logic [3:0] a, b;
logic out;

// Bad: Incorrect because it implicitly converts 4-bit signals to 1-bit before
// AND. Also, !b is different from ~b and can be hard to catch.
assign out = a && !b;

// Bad: Incorrect use of a multi-bit signal in an if expression.
always_comb begin
  if (a) begin
    // ...
  end
end

Prefer Logical Operators for Single-Bit Signals

When performing logical operations on single bit signals, it is recommended to use the logical operators (for example, &&, ||, !) instead of bitwise operators (for example, &, |, ~).

logic a, b, c;
...
// This aids readability as the logical operator makes clear `a` and `b`
// are single-bit signals.
assign c = a && b;
logic a, b, c;
...
// `a` and `b` could be multi-bit vectors, it is not clear from the assignment.
assign c = a & b; // Not recommended

Handling Width Overflow

Beware of shift operations, which can produce a result wider than the operand. Bit-selection and concatenation may be clearer than shifting by a constant amount.

Addition and negation operations produce a result one bit wider than the operands, due to carry. An allowable exception to the rule about matching widths is to silently drop the carry on assignment.

Example:

assign abc_next = abc + 4'h1;

Bit Slicing

Only use the bit slicing operator when the intent is to refer to a portion of a bit vector.

Examples:

logic [7:0] a, b;
logic [6:0] c;

assign a = 8'd7;       // Good

assign a[7:1] = 7'd5;  // Good: it's partial assignment.
assign a = b;          // Good: the parser would warn on width mismatch.
logic [7:0] a, b;

assign a[7:0] = 8'd7;  // Bad: redundant and can mask linter warnings.
assign a = b[7:0];     // Bad: redundant and masks linter warnings.

Alias statement

Do not use alias, use port connection if possible or tran if you must.

Alias statement can be used to connect inout nets, but there is a history for alias statement to not be supported by EDA tools (as of 2019). Hence do not use alias. The recommended alternatives are using conventional port connections, if possible, or tran (a bidirectional pass switch) to connect ports.

Implicitly Sized Expressions

Do not rely on the width of implicitly sized sub-expressions.

Instead, when requiring that a sub-expression produce a result of a specific width, either:

The bit lengths of self-determined expressions (summarized in Table 11-21 of the SystemVerilog-2017 standard) are counter-intuitive (for example, a * b produces a result of length max($bits(a), $bits(b))). To avoid these gotchas, it is best to not rely on implicitly sized results at all.

For example, the following code is wrong:

module compose_bitmap (
    input logic en_a,
    input logic en_b,
    output logic [7:0] en_bitmap);

  assign en_bitmap = {(en_a << 4), (en_b << 4)};

endmodule

The expression in the preceding example synthesizes to zero, because (surprisingly) the length of the implicitly sized expression en_a << 4 is 1 ($bits(en_a)).

A correct implementation would explicitly size each expression, either by adding explicit sizes to the sub-expressions:

assign en_bitmap = {4'(en_a << 3), 4'(en_b << 3)};

Or by decomposing the expression into explicitly sized partial results:

logic [3:0] en_a_bitmap;
logic [3:0] en_b_bitmap;
assign en_a_bitmap = en_a << 3;
assign en_b_bitmap = en_b << 3;
assign en_bitmap = {en_a_bitmap, en_b_bitmap};

Design Conventions

Summary

The key ideas in this section include:

Timescale

Every project must define a consistent way of specifying timescale.

There are multiple ways to specify the timescale, each with pros and cons. Each project must define a single way to specify the timescale for the entire project. Individuals must not implement one-off timescale solutions.

Bus Protocols

Bus protocols are commonly used to connect components of the chip. When using an industry standard protocol, use the terminology established by that protocol. When designing a new protocol, the agent that controls the bus or initiates transactions is named the requester while the agent that services the transaction is the completer.

Flow Control

Standardize flow control using Ready/Valid semantics wherever possible.

Flow control is often used to facilitate back pressure as data moves through a pipeline.

The recommended flow control semantic is the Ready/Valid semantic. Experience shows that connecting two modules with inconsistent flow control semantics is a common source of hard-to-catch errors.

If an interface is defined without flow control, a comment must explain why flow control and back pressure are unnecessary in that particular case.

Struct Usage

Do not group flow control and data signals in the same struct. All data signals in a struct must share the same flow control.

When grouping data signals, flow control signals must not be included in the same structure as the data they are qualifying. Additionally, all data signals in the structure must have the same flow control behavior and be qualified by the same external flow control signals. Otherwise, use of the structure could easily lead to data propagation bugs.

typedef struct packed {
  logic         rw;
  foo_address_t addr;
  foo_data_t    data;
} foo_t;

foo_t foo;
logic foo_valid;           // Good: this is the flow control signal for foo.
bar_interrupt_t bar_intr;  // Good: this has different flow control than foo.
typedef struct packed {
  logic           rw;
  foo_address_t   addr;
  foo_data_t      data;
  logic           valid;  // Bad: this is the flow control signal.
  bar_interrupt_t intr;   // Bad: this data signal is not qualified by valid.
} foo_t;

foo_t foo;

Ready/Valid

Ready/Valid semantics express the recommended method of flow control.

Ready/Valid semantics mimic the requirements of the AXI handshake process, as defined in the AMBA AXI Protocol Specification.

With Ready/Valid semantics, the sender uses the Valid signal to advertise that a data transfer is available. The receiver generates the Ready signal to indicate that it can accept a data transfer. The transfer only occurs on any cycle where Ready and Valid are both asserted.

The Ready signal must be identified with the “_ready” suffix. The Valid signal must be identified with the “_valid” suffix. The other signals in the data transfer must share a prefix with the Ready and Valid signals (for example: “foo_ready”, “foo_valid”, “foo_data1”, “foo_data2”, etc.).

Once Valid is asserted it must remain asserted until the data transfer occurs. For interfaces using _valid_next, once _valid_next is asserted in a given cycle, the data transfer is scheduled for the next clock cycle; if _ready is not asserted on that next cycle, the data transfer remains pending and the data must remain held constant until the transfer completes.

Once Valid is asserted, the data bus must be held constant until the data transfer occurs.

Once Ready is asserted, the receiver is permitted to deassert Ready at any time.

On the sender interface, there may be no combinational path between input and output signals. The receiver, however, is permitted to produce ready combinationally from inputs such as valid or data.

NOTE: This is at odds with the AXI4 specification, which forbids combinational paths between inputs and outputs.

Some modules might have important reasons to change this protocol. If the assumptions of this protocol are violated, use a different signal name convention to clearly indicate that the standard Ready/Valid semantics are not in force. For example, if your protocol permits a “skid” of an extra data transfer after Valid gets de-asserted, consider documenting this in the signal name (for example, “foo_valid_for_two”).

wr_en or rd_en are acceptable proxies for Valid, when appropriate.

Credit/Valid

Credit/Valid flow control decouples data transmission from flow-control feedback using a credit counter, making it suitable for long-distance, wide, or high-frequency pipelines where single-cycle Ready/Valid backpressure is impractical.

Req/Ack

Req/Ack semantics express an alternate method of flow control.

Req/Ack semantics are functionally equivalent to Ready/valid semantics. Ready/Valid is the recommended style for most new code. Credit/Valid is recommended to be considered for very long, very wide, or very high-speed buses.

A request and acknowledge interface is always defined by two signals:

Additional signals associated with this interface (in other words, information that accompanies the request) must share a signal name prefix with the request and acknowledge pair. These signals must be kept valid the entire time that request is asserted.

If foo_req is not asserted, no assumptions can be made about foo_ack (the receiver might choose to tie foo_ack to 1’b1 if it can never stall) – the transmitter must only care about the state of foo_ack on cycles where foo_req is valid.

For example:

output logic [63:0] write_to_fifo_data,
output logic        write_to_fifo_req,
input  logic        write_to_fifo_ack

Synchronization

Users must avoid implementing their own CDC synchronizers and instead rely upon a proven library implementation.

Any module that contains CDC synchronizers must separate and encapsulate logic for each clock domain in separate sub-modules.

It is not acceptable to have combinational or sequential code written in the same level of hierarchy that includes a CDC instantiation. If there are multiple clock domains in a module, then all logic that belongs to each domain must be separated from the others and encapsulated within one or more sub-modules.

Exception: CDC implementations (for example, from a design library) are not subject to this rule. By design, they are required to mix logic from multiple clock domains within the same module (at some level of hierarchy).

Rationale: Mixing functional logic from multiple clock domains in the same module results in poor readability of combinational and sequential logic (for example, extra clock name prefix on signals in logical expressions) and can increase the risk of accidental CDC bugs (for example, crossing clock domains without a synchronizer). This style rule enforces structural separation of logic that belongs to different clock domains.

module module_foo_bar (
    ...
);
  // Good: Using CDC synchronizer lib module.
  cdc cdc_foo_bar (.foo_clk, .bar_clk, ...);

  // Good: "foo" clock domain submodules; no unwrapped logic in a module that
  // instantiates a CDC.
  submodule_0 submodule_0_foo (.clk(foo_clk), ...);
  submodule_1 submodule_1_foo (.clk(foo_clk), ...);

  // Good: "bar" clock domain submodules; no unwrapped logic.
  submodule_2 submodule_2_bar (.clk(bar_clk), ...);

  // Good: No inferred logic here, just a structural assignment, and both
  // signals are clearly in the same clock domain.
  assign bar_x = bar_y;
endmodule
module module_foo_bar (
    ...
);
  cdc cdc_foo_bar (.foo_clk, .bar_clk, ...);

  // Bad: unwrapped combinational logic in a module that instantiates a CDC.
  always_comb begin
    // ... some foo_* combinational logic here ...
  end

  // Bad: unwrapped sequential logic in a module that instantiates a CDC.
  always @(posedge bar_clk) begin
    // ... some bar_* sequential logic here ...
  end

  // Bad: assign statement infers combinational logic in a module that
  // instantiates a CDC.
  assign bar_x = ^bar_y;
endmodule

Top-level Implementation

A set of guidelines that emphasize maintainability of the top-level implementation.

The top-level implementation is a pain point for many designs. The following recommendations focus on keeping top-level implementations simple and maintainable.

The following practices are recommended:

Registered Inputs and Outputs

Strongly prefer to place module-boundary registers on outputs.

When placing module-boundary registers, it is strongly recommended that the registers be placed on the module outputs, rather than the module inputs.

If the outputs of a non-trivial module aren’t registered, this must be clearly documented in the header comment of the module.

Top-level modules must have registered inputs and outputs for maximum top-level routing flexibility.

It is assumed that inputs to all modules come from the output flops of a previous module, so it is generally not required to register the inputs of a module, with the following two exceptions:

Verification

Summary

This section documents guidelines for writing Design Verification (DV) code in Verilog.

Assertions

Assumptions and invariants in logic must be formally expressed as runtime assertions.

Assertions must represent behaviors that ALWAYS happen (and not be used to flag error conditions that might be exercised in simulation).

Assertions must be placed within RTL files so that assertions and their related logic are close together.

Assertions must be placed in a design according to these guidelines:

Use standardized assertion macros whenever possible.

Unit Tests and Designer Tests

Thorough unit tests are required for shared libraries and reusable IP design blocks. All other design blocks require designer tests.

Naming Unit Tests

Unit test modules must end with the string “_test”. For example, dma_controller_test.

Minimum Testing Requirements

If the RTL is a reusable IP, every feature must be exhaustively tested.

Otherwise:

  1. The design block must elaborate and pass lint checks.
  2. The design block must come out of reset with no X’s on the outputs.
  3. At least one transaction must be sent through the block and observed correctly.

The second category is also referred to as “designer tests”.

Constraints

Use lower_snake_case when naming constraints with an _c suffix.

constraint legal_pkt_size_in_bytes_c {
  pkt_size_in_bytes inside {[64:1500]};
}
constraint c_My_Constraint { // c_ at the beginning.  Not lower_snake_case.
  pkt_size_in_bytes inside {[64:1500]};
}

Plusargs

Plusarg Value Assignment

Plusargs must specify a value to be assigned to a variable (for example, use +en_foo=1, not +en_foo). Do not use $test$plusargs.

For example:

bit en_ingress;
bit plusarg_found = $value$plusargs("en_ingress=%d", en_ingress);
if (en_ingress) begin
  // Do stuff...
end
bit en_ingress;
if ($test$plusargs("en_ingress")) begin
  // Do stuff...
end

Plusarg Naming

The plusarg name must match the variable that stores the passed plusarg, exclusive of any prefix or suffix of either the plusarg or the variable.

For example:

int foo;
int prefix_bar_suffix;
// Any of the following plusarg run-time calls would be acceptable:
// +foo=42
// +FOO=42
// +prefix_foo_suffix=42
// +bar=123
// +prefix_bar_suffix=123
// +PREFIX_BAR_SUFFIX=123
string file_name;
// Plusarg string name and variable names must match.  These are not acceptable:
// +filename="foo.txt"
// +file="foo.txt"

Text Formatting

Displayed Number Format

Printed numbers must be prefixed with their radix as shown in the following examples.

When formatting text representations of numbers for log files, make it clear what data you are including.

Make the base of a printed number clear. Print decimal numbers without the radix.

Binary: 0b or 'b

Hexadecimal: 0x or 'h

Decode individual fields of large structures individually, instead of expecting the user to manually decode raw values.

$display("0x%0x", some_hex_value);
$display("0b%0b", some_binary_value);
$display("'h%0h", some_hex_value);
$display("'b%0b", some_binary_value);
$display("%0d", some_decimal_value);
$display("%0x", some_hex_value);
$display("%0b", some_binary_value);
$display("0d%0d", some_decimal_value);
$display("'d%0d", some_decimal_value);

String Formatting

Use $sformatf() for string type formatting. Avoid using $display() to format string types.

The SystemVerilog-2017 standard is vague on how string type formatting must be handled by $display(). Simulator implementation of this functionality varies. $sformatf() has a more consistent interpretation and implementation.

string str_type = "Hello %s";
// Good: String type formatting using $sformatf()
string str_type_sformat = $sformatf(str_type, "world!");
$display(str_type_sformat);

// Good: Calling $sformatf() on the same line as $display()
$display($sformatf(str_type, "world!"));
// String literal formatting with $display() is allowed
$display("Hello %s", "world!");
string str_type = "Hello %s";
// Bad: String type formatting using $display()
$display(str_type, "world!");

Line Continuation of String Literals

Do not use a ‘' (backslash) character to continue a long string literal to the next line, use {} string concatenation operator instead.

$display({"a really long ",
          "long long quoted string"});
$display("a really long \
long long quoted string");  // Bad: continuation of a string literal

Loop operators

Prefer foreach loops.

Where possible, prefer foreach loops over equivalent loop operators. They are concise and less prone to subtle errors.

foreach (data[byte_i]) begin
  data[byte_i] = 0;
end

// Compact form for multidimensional arrays.
foreach (data[burst_i, byte_i]) begin
  data[burst_i][byte_i] = 0;
end
// Less readable and note the typo below, must be '<' not '<='.
for (int byte_i = 0; byte_i <= data.size(); byte_i++) begin
  data[byte_i] = 0;
end

Code Within Asserts

Do not assert on calls to randomize. More broadly, do not place any expression with side effects into an assert statement.

SystemVerilog has a couple ways to disable all assertions such as $assertoff or $assertkill. When an assertion is disabled, future assertion attempts will not evaluate the assertion expression, as per the LRM (not until the assertion is re-enabled via $asserton or one of its alternatives). This is particularly bad when randomize is used within the assertion expression, because disabled assertions result in unrandomized objects.

ret = item.randomize() with {<constraints>};
assert(ret);
assert(randomize(<some variables>) with {<constraints>});
assert(item.randomize() with {<constraints>});
assert(function_with_side_effects());

Wait and Fork

Always put wait fork or disable fork constructs inside of an isolation fork ... join block, or the environment can end up waiting on other, earlier forks it was not intending to wait for.

fork begin : isolation_fork
  fork
    begin
      fork1();
    end
    begin
      fork2();
    end
  join_any
  disable fork;
end : isolation_fork
join

Void Casts

Do not void cast calls that have useful or important return values. This includes calls to uvm_config_db::get, uvm_hdl_read, randomize, etc.

For example, do not do the following:

void'(randomize(<some variables>) with {<constraints>});
void'(item.randomize() with {<constraints>});
void'(uvm_config_db#(<object>)::get(<parameters>));
void'(uvm_hdl_read($sformatf("%sglink_gsw_link_down", glink_path), rd_lnk_dwn));

Instead, check the return value of calls to randomize and similar calls in order to handle the potential error.

if (!item.randomize() with {<constraints>}) begin
  <issue error>
end

if (!uvm_config_db#(<object>)::get(<parameters>)) begin
  <issue error>
end

Associative arrays

Do not use wildcard indexed [*] associative arrays.

Associative arrays with wildcard index are hard to use because the item type could be anything. Always specify a particular index type for associative arrays:

bit channel_enable[int];
register_t registers[string];
int object_count[index_type];
int object_count[*];  // Bad: wildcard indexed array

Bind Statements

bind statements may be used in verification. This is the preferred approach for using assertion based monitors.

”.*” is allowed for making implicit port declarations when binding a module whose ports are all inputs.

Program block

Do not use program and endprogram blocks.

It provides the false sense of security. Use module and clocking blocks to avoid DUT race conditions instead.

Simulator-specific code

All simulator-specific code must be wrapped inside preprocessor guards.

The following macro names are always defined by each simulator. These names must be used to wrap all simulator-specific code, so the code can be compiled for every simulator.

`ifdef VCS
  $stack();
`endif

Forbidden System Tasks and Functions

Do not use the following functions.

UVM

Phase Methods

Always call super.<phase_name>(phase) when overriding UVM phase methods in derived classes.

When overriding UVM phase methods (such as build_phase, connect_phase, run_phase, etc.) in a class derived from a UVM component, you must call the parent class’s implementation using super.<phase_name>(phase). This ensures that any setup or configuration performed by the parent class is executed.

Exceptions are allowed if the base class is a direct UVM library class (like uvm_component, uvm_test, uvm_env, etc.) where the default implementation is known to be empty, BUT it is still recommended to call it for consistency and future-proofing. For user-defined base classes, it is mandatory.

class my_test extends base_test;
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    // ... custom build logic ...
  endfunction
endclass
class my_test extends base_test;
  virtual function void build_phase(uvm_phase phase);
    // Missing super.build_phase(phase)
    // ... custom build logic ...
  endfunction
endclass

UVM Logging

Only use `uvm_info, `uvm_error, and `uvm_fatal UVM messaging macros for message logging in UVM environments.

Only use the following UVM macros to display log messages in UVM environments:

Do not call underlying UVM functions like uvm_report_*. Macros provide better control over verbosity, timestamping, and component identification.

Do not use the `uvm_warning macro. Categorize messages as informational (info) or as conveying an issue that needs to be addressed (error or fatal). Although forbidden, it’s recommended to setup your infrastructure to treat `uvm_warning as errors.

`uvm_error(get_full_name(), $sformatf("Unsupported burst type : %s", burst_type.name()))
$display("Debug: Byte enable=%d", byte_en);
uvm_report_info("ID", "Debug message", UVM_LOW)
`uvm_warning(get_full_name(), "Incorrect condition, maybe")

UVM Macro Semicolons

Do not append a semicolon to UVM macro calls.

UVM macros (including messaging macros like `uvm_info and sequence action macros like `uvm_do) must not be followed by a semicolon. Appending a semicolon can result in an empty statement, which can cause syntax errors in contexts like if statements without begin / end blocks.

// Good: UVM macros must not be followed by a semicolon.
`uvm_info("ID", "Message", UVM_LOW)
`uvm_do(my_seq)
// Bad: Do not append a trailing semicolon to UVM macro calls.
`uvm_info("ID", "Message", UVM_LOW);
`uvm_do(my_seq);

Sequences and Sequence_items

Call the randomize() or randomize() with {} function of the sequence before executing the sequence.

Creating Objects and Components

Use the create() API to create UVM objects and components.

Use the type_id::create() API to instantiate all UVM objects and components. This is standard UVM programming practice and is necessary to take advantage of UVM factory features, such as type and instance overrides.

Exceptions are permitted for objects or components that are not intended for reuse (such as TLM ports, exports, and analysis ports) or when significant performance gains can be achieved. In such exceptional cases, new() may be used directly.

When creating classes derived from uvm_component, pass this as the parent argument so that the component hierarchy is built properly.

env_h = tb_block_env::type_id::create("env_h", this);
ip_pkt = tx_seq_item::type_id::create("ip_pkt");
// Direct calls to new() bypass the UVM factory and prevent overrides.
env_h = new("env_h", this);
ip_pkt = new("ip_pkt");

UVM Naming

When creating a UVM object or component using type_id::create(), the string name argument must match the name of the variable to which it is assigned.

For array assignments, the string name must include the index in square brackets (for example, [<index>]).

This ensures consistency and helps with debugging, as the hierarchical path in UVM will match the variable names in the code.

my_agent = my_agent_type::type_id::create("my_agent", this);

// For arrays, include the index in the string name
my_agents[i] = my_agent_type::type_id::create($sformatf("my_agents[%0d]", i), this);
// String name does not match variable name
my_agent = my_agent_type::type_id::create("different_name", this);

// For arrays, string name is missing the index in brackets
my_agents[i] = my_agent_type::type_id::create($sformatf("my_agents_%0d", i), this);

Class Constructor Default Values

For classes derived from uvm_object, the constructor parameter name must default to "". For classes derived from uvm_component, the constructor must not specify default values.

When defining a constructor for a UVM class:

// uvm_object derived class constructor
function new (string name = "");

// uvm_component derived class constructor
function new (string name, uvm_component parent);
// Bad: name default parameter must be ""
function new (string name = "my_object");

// Bad: default parameter value provided for parent or name
function new (string name = "my_component", uvm_component parent = this);

Test Execution

Do not pass arguments to run_test().

Always call run_test() without arguments. Specify the test name on the command line using +UVM_TESTNAME=test_name instead of hardcoding it in the code.

initial begin
  run_test();
end
initial begin
  run_test("my_test_name");
end