Style and Conventions
RTL lives a long time. A module written today gets debugged by someone else in three years, moved to a new process in five and dropped into a new chip in ten. Style rules in hardware are about readability, as in software, but most of them are also about correctness: each one exists because of a specific bug that kept happening. This article is the set used in the course, with the reason for each.
Naming
`timescale 1ns / 1ps
`default_nettype none
module naming_demo (
input wire clk, // the clock is clk; with several, clk_a, clk_b
input wire rst_n, // _n means active low
input wire cs_n, // chip select, active low
input wire wr_en, // active high, no suffix
input wire [7:0] data_in, // _in and _out where the direction is not obvious
output wire [7:0] data_out,
output wire valid_out,
output wire error_flag
);
wire [7:0] data_byte; // lowercase with underscores
reg [3:0] state_reg; // _reg marks a flip-flop output
reg [3:0] next_state; // next_ marks the combinational next value
wire pipeline_valid;
localparam IDLE = 2'd0; // UPPER_CASE constants
localparam ACTIVE = 2'd1;
endmodule
The _n suffix on every active-low signal is not optional. Seeing rst_n tells the reader the reset condition is !rst_n, and the commonest reset bug of all is writing if (rst_n) for if (!rst_n). The _reg suffix tells the reader which signals are flip-flop outputs, which matters when reading timing paths. The next_ prefix separates the combinational next state from the registered current state, and mixing those up is the classic state machine error.
Instances take a u_ prefix and a name that says what they do:
adder #(.WIDTH(16)) u_alu_adder (/* ports */);
fifo #(.DEPTH(64)) u_tx_fifo (/* ports */);
uart_rx u_uart_rx (/* ports */);
pipeline_stage u_stage0 (/* ports */);
pipeline_stage u_stage1 (/* ports */);
Searching a netlist for u_ then finds instances and nothing else.
Reset
The ASIC convention is an asynchronous, active-low reset. Asynchronous because at power-up the clock may not be running yet and the flip-flops must still reach a known state. Active-low because library flip-flops have active-low reset pins, so an active-high reset in the RTL costs an inverter on every reset path, and because reset comes from a pin, where a pull-up and an open-drain driver naturally give an active-low signal.
`timescale 1ns / 1ps
`default_nettype none
module reset_demo (
input wire clk,
input wire rst_n, // asynchronous, active low
input wire [7:0] d,
output reg [7:0] q
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 8'h00;
else
q <= d;
end
endmodule
Three things to see: negedge rst_n in the sensitivity list makes it asynchronous; the condition matches the polarity; the reset value is sized.
Many FPGA flows prefer a synchronous reset, which is the same block without negedge rst_n in the list. It costs nothing in an FPGA flip-flop and it avoids the reset-release timing problem that section 3.2 covered. The simple examples earlier in this module use it. Either is fine. What is not fine is mixing them, or mixing polarities, in one design: two blocks driven from the same reset pin with one inverted can leave one block in reset while the other is running, and everything between them is undefined. Pick one convention and use it everywhere.
One register per always block
When two registers have different reset values or different enables, give each its own block.
// Poor: two unrelated registers in one block
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
counter_reg <= 8'd0;
status_reg <= 8'hFF;
end else begin
if (count_en)
counter_reg <= counter_reg + 8'd1;
status_reg <= {error_flag, 7'b0};
end
end
`timescale 1ns / 1ps
`default_nettype none
module separate_always (
input wire clk,
input wire rst_n,
input wire count_en,
input wire error_flag,
output reg [7:0] counter_reg,
output reg [7:0] status_reg
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
counter_reg <= 8'd0;
else if (count_en)
counter_reg <= counter_reg + 8'd1;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
status_reg <= 8'hFF;
else
status_reg <= {error_flag, 7'b000_0000};
end
endmodule
The netlist is the same. The difference is that a reader can see each register’s reset and enable without untangling them from the other’s.
Layout
Four spaces, no tabs. Align the direction, type, range and name columns in port lists and declarations, because the eye scans columns faster than it parses ragged lines. Wrap at 100 characters. Separate the sections of a module with blank lines and a short comment:
module well_structured (/* ports */);
// ---- Parameters ----
localparam IDLE = 2'd0;
localparam BUSY = 2'd1;
// ---- Signals ----
reg [1:0] state_reg;
reg [1:0] next_state;
// ---- State register ----
always @(posedge clk or negedge rst_n) begin
/* ... */
end
// ---- Next-state logic ----
always @(*) begin
/* ... */
end
endmodule
Comments
Comment why, not what. assign result = a + b; // add a and b says nothing. // wraps on overflow; the caller checks carry says something. Use TODO, FIXME, HACK and NOTE with their usual meanings, and a block comment at the top of the file saying what the module is for.
Do not repeat yourself
If the same logic appears three times, it is a function or a module:
// Poor: the same Gray conversion three times
assign grey_a = {bin_a[3], bin_a[3]^bin_a[2], bin_a[2]^bin_a[1], bin_a[1]^bin_a[0]};
assign grey_b = {bin_b[3], bin_b[3]^bin_b[2], bin_b[2]^bin_b[1], bin_b[1]^bin_b[0]};
assign grey_c = {bin_c[3], bin_c[3]^bin_c[2], bin_c[2]^bin_c[1], bin_c[1]^bin_c[0]};
Three copies are three places to fix a bug. Every large company has an RTL style guide of fifty pages or more, and some are public; the lowRISC one is worth reading once.
A complete example
// =============================================================================
// Module: debounce
// Description: Button debouncer: the output changes only after FILTER_BITS
// consecutive identical samples
// =============================================================================
`timescale 1ns / 1ps
`default_nettype none
module debounce #(
parameter FILTER_BITS = 16
)(
input wire clk,
input wire rst_n, // asynchronous, active low
input wire btn_raw,
output reg btn_clean
);
// ---- Signals ----
reg [FILTER_BITS-1:0] shift_reg;
wire all_ones;
wire all_zeros;
// ---- Stable-state detection ----
assign all_ones = &shift_reg;
assign all_zeros = ~(|shift_reg);
// ---- Sample shift register ----
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
shift_reg <= {FILTER_BITS{1'b0}};
else
shift_reg <= {shift_reg[FILTER_BITS-2:0], btn_raw};
end
// ---- Output: changes only when the filter is saturated ----
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
btn_clean <= 1'b0;
else if (all_ones)
btn_clean <= 1'b1;
else if (all_zeros)
btn_clean <= 1'b0;
// otherwise hold: a bounce shorter than FILTER_BITS cycles is ignored
end
endmodule
`default_nettype wire
Practice Exercise
Find the style problems and rewrite:
module bad_style(input clk, input reset, input [7:0] D, output reg [7:0] Q);
always @(posedge clk) begin
if(reset) Q <= 0;
else Q <= D;
end
endmodule
Solution
No timescale or default_nettype; no wire on the inputs; nothing aligned; an active-high reset with no convention stated and no _n; an unsized reset value; upper-case signal names; no indentation.
// =============================================================================
// Module: dff_byte
// Description: 8-bit register with asynchronous active-low reset
// =============================================================================
`timescale 1ns / 1ps
`default_nettype none
module dff_byte (
input wire clk,
input wire rst_n,
input wire [7:0] d,
output reg [7:0] q
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
q <= 8'h00;
else
q <= d;
end
endmodule
`default_nettype wire
That completes the grammar. The next section is the operators that go inside the expressions: arithmetic first, and what each one costs in gates.