Part 9 of 10
RTL Coding Guidelines
Write clean, synthesizable Verilog
By Praveen Kumar Vagala
Golden Rules
- Synthesizable code only (no delays, no initial blocks for synthesis)
- Use non-blocking (<=) for sequential, blocking (=) for combinational
- Complete sensitivity lists (use @(*))
- No latches (always assign all outputs in all branches)
- One clock, one edge per always block
Naming Conventions
// Signals
input_data // Input signals
output_valid // Output signals
internal_count // Internal signals
next_state // Combinational "next" values
// Active-low signals
rst_n // Active-low reset
cs_n // Active-low chip select
// Clock and reset
clk // Clock
clk_100mhz // Specific clock
rst_n // Async reset (active low)
sync_rst // Sync reset
// Parameters
parameter WIDTH = 8;
localparam STATE_IDLE = 2'b00; // Local to module
// Prefixes/Suffixes
_d, _q // D input, Q output of FF
_ff // Registered version
_sync // Synchronized signal
_gray // Gray-coded value
Module Structure Template
module module_name #(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
// Clock and Reset
input clk,
input rst_n,
// Input interface
input valid_in,
input [WIDTH-1:0] data_in,
output ready_out,
// Output interface
output reg valid_out,
output reg [WIDTH-1:0] data_out,
input ready_in
);
//=========================================
// Local Parameters
//=========================================
localparam PTR_WIDTH = $clog2(DEPTH);
//=========================================
// Internal Signals
//=========================================
reg [WIDTH-1:0] data_reg;
wire [WIDTH-1:0] data_next;
//=========================================
// Combinational Logic
//=========================================
assign data_next = data_in + 1;
//=========================================
// Sequential Logic
//=========================================
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_reg <= {WIDTH{1'b0}};
end else begin
data_reg <= data_next;
end
end
//=========================================
// Output Assignments
//=========================================
assign ready_out = 1'b1;
endmodule
Combinational Logic
Always Block Style
// GOOD: Complete sensitivity, complete assignment
always @(*) begin
case (sel)
2'b00: out = a;
2'b01: out = b;
2'b10: out = c;
default: out = d; // Always have default!
endcase
end
// GOOD: Use default assignment
always @(*) begin
out = 1'b0; // Default
if (condition)
out = 1'b1;
end
// BAD: Creates latch!
always @(*) begin
if (condition)
out = 1'b1;
// No else! What is out when condition=0?
end
Continuous Assignment
// Simple logic - use assign
assign sum = a + b;
assign mux_out = sel ? a : b;
assign and_out = x & y & z;
// Don't use assign for complex logic - hard to read
Sequential Logic
// Standard synchronous with async reset
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
count <= 4'b0;
state <= IDLE;
end else begin
count <= count + 1;
state <= next_state;
end
end
// With synchronous reset
always @(posedge clk) begin
if (sync_rst) begin
count <= 4'b0;
end else begin
count <= count + 1;
end
end
// With enable
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
data <= 8'b0;
else if (enable)
data <= data_in;
end
Common Mistakes
1. Mixing Blocking and Non-Blocking
// BAD: Mixed in sequential block
always @(posedge clk) begin
temp = a + b; // Blocking
result <= temp; // Non-blocking - confusing!
end
// GOOD: All non-blocking in sequential
always @(posedge clk) begin
temp <= a + b;
result <= temp; // Gets OLD value of temp
end
// OR: Separate combinational and sequential
wire [7:0] temp = a + b; // Combinational
always @(posedge clk)
result <= temp;
2. Multiple Drivers
// BAD: Two always blocks driving same signal
always @(posedge clk)
if (cond1) out <= a;
always @(posedge clk)
if (cond2) out <= b; // Conflict!
// GOOD: Single driver
always @(posedge clk) begin
if (cond1)
out <= a;
else if (cond2)
out <= b;
end
3. Incomplete Case Statements
// BAD: Missing states create latch
always @(*) begin
case (state)
IDLE: out = 1'b0;
RUN: out = 1'b1;
// What about other states?
endcase
end
// GOOD: Use default
always @(*) begin
case (state)
IDLE: out = 1'b0;
RUN: out = 1'b1;
default: out = 1'b0;
endcase
end
Parameterization
// Parameterized width
module fifo #(
parameter WIDTH = 8,
parameter DEPTH = 16,
parameter ALMOST_FULL = DEPTH - 2
)(
input clk, rst_n,
input [WIDTH-1:0] din,
output [WIDTH-1:0] dout,
output [$clog2(DEPTH):0] count
);
// Use parameters throughout
reg [WIDTH-1:0] mem [0:DEPTH-1];
endmodule
// Instantiation
fifo #(
.WIDTH(32),
.DEPTH(64)
) my_fifo (
.clk(clk),
.rst_n(rst_n),
// ...
);
Generate Statements
// Generate multiple instances
module multi_adder #(
parameter N = 4,
parameter WIDTH = 8
)(
input [N*WIDTH-1:0] a,
input [N*WIDTH-1:0] b,
output [N*WIDTH-1:0] sum
);
genvar i;
generate
for (i = 0; i < N; i = i + 1) begin : gen_adder
assign sum[i*WIDTH +: WIDTH] =
a[i*WIDTH +: WIDTH] +
b[i*WIDTH +: WIDTH];
end
endgenerate
endmodule
// Conditional generate
generate
if (USE_FAST_ADDER) begin : fast
fast_adder u_add (.a(a), .b(b), .sum(sum));
end else begin : slow
assign sum = a + b;
end
endgenerate
Coding Checklist
| Check | Rule |
| ✓ | No latches (check synthesis warnings) |
| ✓ | All case statements have default |
| ✓ | All if statements have else (or default assignment) |
| ✓ | Non-blocking for sequential, blocking for combinational |
| ✓ | Single clock edge per always block |
| ✓ | Reset initializes all sequential elements |
| ✓ | No combinational loops |
| ✓ | Proper bit widths (no truncation warnings) |
Summary
| Topic | Best Practice |
| Sequential | Non-blocking (<=) |
| Combinational | Blocking (=) or assign |
| Sensitivity | Use @(*) |
| Case | Always have default |
| Reset | Initialize all FFs |
| Naming | Consistent, descriptive |