The Digital Electronics Blog
A popular Technology blog on Semiconductors and Innovation, inspiring since 2004.

Top 10 Common RTL Coding Mistakes and ways to avoid them

Murugavel Ganesan
by
0


Engineers inexperienced with Register Transfer Level (RTL) coding for hardware design often make mistakes due to a lack of familiarity with hardware description languages (HDLs) like Verilog or VHDL, as well as the unique constraints of hardware design.

Here are the top 10 common mistakes they make, based on industry insights and best practices:

1. Not Understanding Clock Domains
2. Writing Software like code
3. Ignoring Synchronous Design Principles
4. Poor Reset Handling
5. Overcomplicating State Machines
6. Not Simulating Enough
7. Ignoiring Timing Costraints
8. Hardcoding Parameters
9. Not following Coding Guidelines
10. Not Verifying Synthesis Output

Below is a detailed breakdown of each of them, including explanations, examples, consequences, and best practices to avoid them.


Not Understanding Clock Domains

Failure to properly manage signals crossing between different clock domains can result in metastability, data corruption, or unpredictable behavior. Junior engineers may overlook the complexities of clock domain crossings, failing to recognize when a signal transitions from one domain (e.g., 100 MHz) to another (e.g., 50 MHz) or mistakenly assuming that simple assignments suffice without proper synchronization. Metastability can drive flip-flops into undefined states, causing functional failures that are difficult to diagnose and debug.


// Direct signal crossing between clock domains
module bad_cdc (
input clk_a, clk_b,
input data_in,
output reg data_out
);
always @(posedge clk_b) begin
data_out <= data_in; // data_in is from clk_a domain
end
endmodule


// Two-stage synchronizer
module good_cdc (
input clk_b, rst_n,
input data_in, // From clk_a domain
output reg data_out
);
reg sync1, sync2;
always @(posedge clk_b or negedge rst_n) begin
if (!rst_n) begin
sync1 <= 0;
sync2 <= 0;
data_out <= 0;
end else begin
sync1 <= data_in; // First stage
sync2 <= sync1; // Second stage
data_out <= sync2;
end
end
endmodule


Best Practices:
  • Study CDC techniques (synchronizers, handshake protocols, FIFOs).
  • Use CDC analysis tools (e.g., Synopsys SpyGlass) to identify crossings.
  • Simulate designs with random clock phase differences to catch CDC issues.


  • Writing Software like code

    Mistakenly treating HDL code like a software program using constructs such as loops or functions without considering synthesis can lead to serious design issues. Engineers with software backgrounds (e.g., C/C++) may apply programming paradigms without realizing that HDL defines physical hardware rather than procedural execution. As a result, the code may be unsynthesizable, generate excessive hardware, or behave unpredictably, deviating from the intended functionality.

    module bad_loop (
    input [7:0] data_in,
    output reg [7:0] data_out
    );
    integer i;
    always @(*) begin
    data_out = 0;
    for (i = 0; i < 8; i = i + 1) begin
    data_out = data_out + data_in[i]; // Sequential addition
    end
    end
    endmodule

    module good_loop (
    input [7:0] data_in,
    output [7:0] data_out
    );
    assign data_out = data_in[0] + data_in[1] + data_in[2] + data_in[3] +
    data_in[4] + data_in[5] + data_in[6] + data_in[7];
    endmodule

    Best Practices:
  • Understand synthesis rules (e.g., avoid unsynthesizable constructs like initial for synthesis).
  • Visualize the hardware (e.g., adders, multiplexers) your code implies.
  • Use simulation to verify behavior matches intent before synthesis.


  • Ignoring Synchronous Design Principles

    Failing to use clocked logic for state changes or neglecting to register inputs and outputs can lead to timing violations, glitches, or race conditions. Junior engineers may underestimate the necessity of synchronous logic, assuming combinational logic offers a simpler solution. However, combinational logic in critical paths can introduce unintended glitches, fail timing analysis, and ultimately result in an unreliable design.

    module bad_sync (
    input clk, en, data_in,
    output reg data_out
    );
    always @(*) begin
    if (en)
    data_out = data_in; // Combinational assignment
    end
    endmodule

    module good_sync (
    input clk, rst_n, en, data_in,
    output reg data_out
    );
    always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
    data_out <= 0;
    else if (en)
    data_out <= data_in;
    end
    endmodule

    Best Practices:
  • Register all outputs of a module to simplify timing analysis.
  • Avoid combinational feedback loops, which are prone to race conditions.
  • Use static timing analysis (STA) to verify setup and hold times.


  • Poor Reset Handling

    Inconsistent or incomplete reset logic such as mixing synchronous and asynchronous resets or failing to reset all state variables can introduce significant design issues. Inexperienced engineers may not establish a clear reset strategy or may overlook registers that require initialization. As a result, unreset registers can lead to undefined states, causing simulation-synthesis mismatches or functional bugs.

    module bad_reset (
    input clk, rst_n, data_in,
    output reg data_out,
    output reg [1:0] state
    );
    always @(posedge clk) begin
    if (!rst_n)
    data_out <= 0; // state not reset
    else
    state <= data_in ? 2'b01 : 2'b10;
    end
    endmodule

    module good_reset (
    input clk, rst_n, data_in,
    output reg data_out,
    output reg [1:0] state
    );
    always @(posedge clk) begin
    if (!rst_n) begin
    data_out <= 0;
    state <= 2'b00;
    end else begin
    state <= data_in ? 2'b01 : 2'b10;
    data_out <= data_in;
    end
    end
    endmodule

    Best Practices:
  • Choose one reset type (synchronous or asynchronous) and stick to it.
  • Reset all registers unless explicitly unnecessary.
  • Verify reset behavior in simulation and gate-level netlists.


  • Overcomplicating State Machines

    Designing complex or poorly structured finite state machines (FSMs) can lead to hard-to-debug logic and inefficient hardware. Inexperienced engineers may fail to break down FSMs into manageable states or neglect standard templates such as Moore or Mealy models. As a result, overly complex FSMs heighten the risk of bugs, complicate verification, and may synthesize into larger, slower hardware.


    module bad_fsm (
    input clk, rst_n, in,
    output reg out
    );
    reg [3:0] state;
    always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
    state <= 4'h0;
    else begin
    case (state)
    4'h0: state <= in ? 4'h5 : 4'h3;
    4'h3: state <= in ? 4'h7 : 4'h2;
    default: state <= 4'h0;
    endcase
    out <= (state == 4'h5 || state == 4'h7) ? 1 : 0;
    end
    end
    endmodule

    module good_fsm (
    input clk, rst_n, in,
    output reg out
    );
    localparam IDLE = 2'b00, S1 = 2'b01, S2 = 2'b10;
    reg [1:0] state, next_state;

    always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
    state <= IDLE;
    else
    state <= next_state;
    end

    always @(*) begin next_state = state; case (state) IDLE: next_state = in ? S1 : IDLE; S1: next_state = in ? S2 : IDLE; S2: next_state = IDLE; endcase end

    always @(*) begin
    out = (state == S2);
    end
    endmodule

    Best Practices:
    Use Moore or Mealy FSM templates for clarity. Document state transitions with diagrams or comments. Simulate all state transitions and edge cases.


    Not Simulating Enough

    Writing RTL without thorough simulation can lead to overlooked corner cases, initialization bugs, and functional errors. Inexperienced engineers may underestimate the importance of simulation, neglect testbench development, or rush to synthesis prematurely. As a result, undetected bugs often surface during synthesis or hardware testing, significantly extending debug time.

    module counter (
    input clk, rst_n, en,
    output reg [3:0] count
    );

    always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
    count <= 0;
    else if (en)
    count <= count + 1;
    end
    endmodule

    module counter_tb;
    reg clk, rst_n, en;
    wire [3:0] count; counter uut (.clk(clk), .rst_n(rst_n), .en(en), .count(count));
    initial begin
    clk = 0;
    forever #5 clk = ~clk; // 100 MHz clock
    end
    initial begin
    rst_n = 0; en = 0;
    #20 rst_n = 1; // Release reset
    #10 en = 1; // Enable counter
    #100 en = 0; // Disable counter
    #20 $finish;
    end

    endmodule

    Best Practices:
  • Write self-checking testbenches with assertions.
  • Use code coverage tools (e.g., Synopsys VCS) to ensure all paths are tested.
  • Simulate corner cases (e.g., reset during operation, maximum counter values).


  • Ignoiring Timing Costraints

    Failing to define or incorrectly specifying timing constraints for synthesis and place-and-route can lead to critical timing violations. Inexperienced engineers may lack a deep understanding of static timing analysis (STA) or mistakenly assume that default constraints are sufficient. Consequently, timing failures can prevent the design from achieving target frequencies or cause unpredictable behavior in hardware.

    # Missing clock definition
    set_input_delay 2 -clock clk [get_ports data_in]

    # Proper clock and input/output delays
    create_clock -period 10 -name clk [get_ports clk]
    set_input_delay 2 -clock clk [get_ports data_in]
    set_output_delay 2 -clock clk [get_ports data_out]

    Best Practices:
  • Learn STA concepts (setup/hold times, slack).
  • Use tools like Synopsys PrimeTime to validate timing reports.
  • Collaborate with senior engineers to review SDC files.

  • Timing constraints are typically defined in SDC files, which are language-agnostic, so no Verilog/SystemVerilog/VHDL-specific examples are needed here.


    Hardcoding Parameters

    Relying on fixed values such as bus widths or delays instead of parameterized code can severely limit reusability and scalability. Inexperienced engineers may overlook future design changes or prioritize quick implementation, leading to hardcoded designs that are difficult to modify or reuse, ultimately increasing maintenance effort.

    module bad_param (
    input [7:0] data_in,
    output [7:0] data_out
    );
    assign data_out = data_in;
    endmodule

    module good_param #(
    parameter WIDTH = 8
    ) (
    input [WIDTH-1:0] data_in,
    output [WIDTH-1:0] data_out
    );
    assign data_out = data_in;
    endmodule

    Best Practices:
  • Parameterize all configurable aspects (e.g., bit widths, pipeline stages).
  • Document parameter usage and default values.
  • Test the design with different parameter values.


  • Not following Coding Guidelines

    Writing inconsistent, poorly commented, or unstructured code can make it difficult to read, debug, and maintain. Inexperienced engineers may be unfamiliar with team or industry coding standards or may prioritize functionality over readability. As a result, poorly structured code hinders collaboration, extends debug time, and increases the risk of errors during maintenance.

    module m1 (input c,r,i,output o);reg [3:0] s;always@(posedge c or negedge r)begin if(!r)s<=0;else if(i)s<=s+1;end assign o=s[0];endmodule

    module counter (
    input clk, // System clock
    input rst_n, // Active-low reset
    input enable, // Counter enable
    output state // LSB of counter
    );

    reg [3:0] count; // 4-bit counter state
    always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
    count <= 4'b0000; // Reset to 0
    else if (enable)
    count <= count + 1; // Increment on enable
    end assign state = count[0]; // Output LSB
    endmodule

    Best Practices:
  • Adopt team or industry standards (e.g., consistent naming like clk for clocks).
  • Use comments to explain intent, especially for complex logic.
  • Run linting tools (e.g., Verilator, SpyGlass) to enforce coding rules.


  • Not Verifying Synthesis Output

    Neglecting to verify that the synthesized netlist matches RTL behavior can lead to missed optimization-related bugs. Inexperienced engineers may bypass gate-level simulation or formal equivalence checking due to time constraints or lack of knowledge, risking unintended discrepancies caused by synthesis optimizations such as logic pruning. These discrepancies can result in hardware failures.

    Recommendation: Use tools like Synopsys Formality or Cadence Conformal to ensure RTL-to-netlist equivalence. Run gate-level simulations using the same testbench employed for RTL. Best Practices: Always compare RTL and gate-level simulation results. Review synthesis logs for warnings (e.g., unmapped logic, removed registers). Incorporate timing information in gate-level simulations to identify timing-related issues.


    General Advice for Junior Engineers

    Master the Tools:
    Learn simulation (e.g., ModelSim, VCS), synthesis (e.g., Synopsys Design Compiler), and STA tools (e.g., PrimeTime). Understand their outputs and warnings. Seek Code Reviews: Share your RTL with senior engineers to catch mistakes early and learn best practices.
    Study Real Designs:
    Analyze open-source RTL projects (e.g., OpenCores, RISC-V cores) or company IP to understand practical implementations.
    Practice Debugging:
    Spend time debugging simulation failures to build intuition for hardware behavior. Learn from Synthesis Reports: Review synthesis reports to understand area, timing, and power implications of your code.
    Document Everything:
    Maintain clear documentation for your design, including block diagrams, state machines, and interface specifications.
    Additional Resources “Verilog HDL” by Samir Palnitkar
    “SystemVerilog for Design” by Stuart Sutherland
    “VHDL for Logic Synthesis” by Andrew Rushton.
    Platforms like Coursera or Udemy offer FPGA and HDL courses. Experiment with open-source tools like Verilator or Yosys for simulation and synthesis.

    Post a Comment

    0Comments

    Your comments will be moderated before it can appear here.

    Post a Comment (0)

    #buttons=(Ok, Go it!) #days=(20)

    Our website uses cookies to enhance your experience. Learn more
    Ok, Go it!