Loading header...

Verilog Style for Synthesis

Synthesizable Verilog is not just text accepted by a compiler. It is a hardware description that must survive synthesis, place and route, timing analysis, lab debug, and future review. Good style makes the intended hardware obvious and makes tool warnings meaningful.

Learning Objectives

By the end of this lesson, you should be able to:

  • separate combinational and sequential logic cleanly;
  • choose blocking, nonblocking, and continuous assignments correctly;
  • write reset and enable logic that synthesis tools infer predictably;
  • avoid width, latch, clock, and simulation-only mistakes;
  • use lint, synthesis, and timing reports as design feedback.

The Core Style Rule

Keep each kind of hardware in its own recognizable pattern:

Hardware intent Verilog pattern Assignment
wire or LUT equation assign y = expression; continuous
combinational decision always @* begin ... end blocking =
flip-flop register always @(posedge clk) begin ... end nonblocking <=

This is not a cosmetic rule. It keeps simulation behavior aligned with synthesized hardware and makes accidental latches or multiple drivers easier to find.

Combinational Template

always @* begin
    y     = 1'b0;
    valid = 1'b0;

    case (sel)
        2'b00: begin
            y     = a;
            valid = 1'b1;
        end
        2'b01: begin
            y     = b;
            valid = 1'b1;
        end
        2'b10: begin
            y     = c;
            valid = 1'b1;
        end
        2'b11: begin
            y     = d;
            valid = 1'b1;
        end
        default: begin
            y     = 1'b0;
            valid = 1'b0;
        end
    endcase
end

Defaults at the top keep the block latch-free. The default branch documents the intended behavior for unknown or future selector values.

Sequential Template

always @(posedge clk) begin
    if (rst) begin
        count <= 8'd0;
    end else if (en) begin
        count <= count + 8'd1;
    end
end

Use one clock edge per register block. Avoid assigning the same register from multiple always blocks. If a register belongs to a clock domain, make that clock domain clear in the signal name or module boundary.

Widths and Constants

Width bugs are common because Verilog allows unsized constants.

Prefer:

count <= count + 8'd1;
mask  <= 16'h00ff;
addr  <= base + {8'd0, offset};

Avoid relying on:

count <= count + 1;
mask  <= 'hff;

Unsized constants can be wider than expected, signed in surprising ways, or truncated silently. Treat width warnings as design issues until proven harmless.

Reset and Initialization Style

Reset what must be known for control, safety, or interface behavior:

  • FSM state;
  • valid bits;
  • user-visible outputs;
  • configuration registers;
  • counters that affect startup behavior.

Avoid resetting every pipeline data register just to make waveforms look clean. A valid bit can often mark whether datapath contents should be trusted.

always @(posedge clk) begin
    if (rst) begin
        valid_q <= 1'b0;
    end else begin
        valid_q <= valid_d;
        data_q  <= data_d;
    end
end

Here data_q is not reset. It is ignored until valid_q says it is meaningful.

Simulation-Only Code

These constructs are useful in testbenches but normally do not belong in synthesizable RTL:

  • #10 delays;
  • $display, $finish, $dumpfile, $dumpvars;
  • initial blocks unless your target FPGA flow explicitly supports register or memory initialization;
  • file I/O;
  • unconstrained for loops whose hardware size is not statically bounded.

Keep RTL and testbench files separate. Use names such as *_tb.v or tb_* for simulation-only modules.

Clocking Discipline

Do not create ordinary logic clocks for normal RTL:

assign divided_clk = counter[23];  // avoid as a generated fabric clock

Use enables:

wire sample_tick = (counter == SAMPLE_DIVIDER - 1);

always @(posedge clk) begin
    if (rst)
        sample <= 1'b0;
    else if (sample_tick)
        sample <= next_sample;
end

If you truly need multiple clock domains, treat the boundary as a design feature. Synchronize single-bit controls, use asynchronous FIFOs for data streams, and constrain the clocks correctly.

Worked Example: Fix a Latch

Buggy combinational code:

always @* begin
    if (sel)
        y = a;
end

When sel is 0, y has no assignment. Synthesis may infer a latch so y can remember its old value.

Safe rewrite:

always @* begin
    y = b;
    if (sel)
        y = a;
end

Now y is always assigned. The hardware is a mux:

y = sel ? a : b

For this simple case, a continuous assignment is even clearer:

assign y = sel ? a : b;

Review Checklist

Before committing synthesizable Verilog, check:

  • each register is assigned in one clocked block;
  • each combinational output has a default assignment;
  • constants and arithmetic widths are explicit;
  • all simulator and lint warnings are understood;
  • every external asynchronous input is synchronized before use;
  • clock-domain crossings are named and reviewed;
  • testbench-only constructs are absent from RTL;
  • vendor primitives are isolated behind wrapper modules;
  • reset behavior matches the board and system requirements;
  • timing reports are checked after place and route.

Tool Feedback

Use tools as reviewers:

flowchart LR RTL[RTL files] --> LINT[lint warnings] RTL --> SIM[simulation] RTL --> SYN[synthesis] SYN --> PNR[place and route] PNR --> STA[static timing] STA --> BIT[bitstream]

Simulation proves behavior for tested cases. Lint catches suspicious code patterns. Synthesis reports inferred hardware. Place and route reports timing, resource use, and routing results. None of these replaces the others.

Exercise

Rewrite this buggy block safely:

always @* begin
    if (sel)
        y = a;
end

Then add a second output valid that is high only when sel is high. Requirements:

  • no inferred latch;
  • blocking assignments in combinational logic;
  • explicit defaults for both outputs;
  • a short testbench that checks sel = 0 and sel = 1.

Common Mistakes

  • Missing default assignments in combinational blocks.
  • Using #delay in RTL and expecting hardware delay.
  • Creating derived clocks with LUT logic.
  • Leaving width warnings unresolved.
  • Copying $display or file I/O into design files.
  • Assigning a register in two clocked blocks.
  • Mixing reset polarity or reset synchrony without naming it clearly.
  • Letting CDC issues hide behind generic names such as flag or data.

Summary

Good Verilog style makes hardware intent obvious. Use continuous assignments for wire logic, blocking assignments for combinational procedural logic, and nonblocking assignments for clocked registers. Keep widths explicit, resets deliberate, clocks disciplined, and warnings clean. The goal is not pretty code; it is predictable hardware.

Next: Simulation and Testbenches.

Further Reading

  • Verilator warnings guide for lint categories and suggested fixes.
  • Yosys documentation for Verilog synthesis behavior and inferred cells.
  • Xilinx, Intel, and Lattice FPGA HDL coding guidelines.
  • Clifford Cummings papers on nonblocking assignments, resets, and FSM style.

Mind Map

mindmap root((Synthesis Style)) Core concept RTL becomes hardware Style reveals intent Warnings are feedback Predictable synthesis Applications Portable FPGA RTL Code review Timing closure Lab debug Team handoff Calculations Explicit bit widths Counter plus sized one Reset fanout costs routing Timing path limits fmax Design rules assign for wires Blocking in comb Nonblocking in flops One driver per register Use enables not gated clocks Practical checks Lint clean No inferred latches CDC named and synchronized Testbench code separated Read synthesis report Common mistakes Unsized constants Delay in RTL Multiple drivers Reset everything Ignoring warnings