Skip to main content
Verilog, as a hardware description language, has several common anti-patterns that can lead to simulation-synthesis mismatches, timing issues, and hard-to-debug hardware designs. Here are the most important anti-patterns to avoid when writing Verilog code.
Always use non-blocking assignments (<=) for sequential logic within an always @(posedge clk) block. Using blocking assignments (=) in sequential logic can lead to race conditions and simulation-synthesis mismatches.
Use blocking assignments (=) for combinational logic within an always @(*) block. Using non-blocking assignments (<=) in combinational logic can lead to simulation-synthesis mismatches and incorrect behavior.
Always use complete sensitivity lists or the automatic sensitivity list @(*) for combinational logic. Incomplete sensitivity lists can lead to simulation-synthesis mismatches and incorrect behavior.
Always provide default assignments to all variables at the beginning of combinational always blocks, and include a default case in case statements. Incomplete assignments can create unintended latches in your design.
Ensure that each signal has only one driver. Multiple drivers for the same signal can lead to simulation-synthesis mismatches and undefined behavior.
Avoid mixing blocking and non-blocking assignments within the same always block. Use non-blocking assignments consistently for sequential logic and blocking assignments consistently for combinational logic.
Synchronize asynchronous reset release to avoid metastability issues. While asynchronous reset assertion is often used for reliable system initialization, the release should be synchronized to the clock to prevent timing violations.
Avoid creating combinational loops in your design. Combinational loops can lead to oscillations, unstable behavior, and synthesis issues. Break loops using registers or redesign your logic.
Avoid inferring clock gating using simple logic gates. Use enable signals within sequential blocks or instantiate proper clock gating cells provided by your technology library.
Avoid using initial blocks for hardware initialization, as they are generally not synthesizable for hardware (except for some specific cases like memory initialization). Use reset signals to initialize your design.
Always use proper synchronization techniques when crossing clock domains. For single-bit signals, use a double-flop synchronizer. For multi-bit signals, use handshaking protocols, gray coding, or asynchronous FIFOs.
Avoid using delays (#) in synthesizable code. Delays are for simulation only and are ignored during synthesis. Let the synthesis tool handle timing based on your timing constraints.
Don’t ignore warnings from your synthesis and simulation tools. Warnings often indicate potential issues in your design that could lead to unexpected behavior. Fix the issues causing warnings or document why they can be safely ignored.
Use parameters for constants in your design. This makes your code more maintainable, reusable, and easier to modify. Parameters can be overridden during module instantiation, allowing for flexible designs.
Use named port connections when instantiating modules. Named connections are clearer, less error-prone, and allow for port reordering without breaking your design.
Use generate blocks for repetitive structures in your design. Generate blocks allow for parameterized, scalable designs and reduce code duplication.
Use assertions to verify the correctness of your design. Assertions can catch bugs early in the design process and serve as documentation for expected behavior.