⚡ Digital Design Blog

Part 9 of 10

RTL Coding Guidelines

Write clean, synthesizable Verilog

By Praveen Kumar Vagala

1,094 views

Golden Rules

  1. Synthesizable code only (no delays, no initial blocks for synthesis)
  2. Use non-blocking (<=) for sequential, blocking (=) for combinational
  3. Complete sensitivity lists (use @(*))
  4. No latches (always assign all outputs in all branches)
  5. 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

CheckRule
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

TopicBest Practice
SequentialNon-blocking (<=)
CombinationalBlocking (=) or assign
SensitivityUse @(*)
CaseAlways have default
ResetInitialize all FFs
NamingConsistent, descriptive