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:
#10delays;$display,$finish,$dumpfile,$dumpvars;initialblocks unless your target FPGA flow explicitly supports register or memory initialization;- file I/O;
- unconstrained
forloops 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:
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 = 0andsel = 1.
Common Mistakes
- Missing default assignments in combinational blocks.
- Using
#delayin RTL and expecting hardware delay. - Creating derived clocks with LUT logic.
- Leaving width warnings unresolved.
- Copying
$displayor 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
flagordata.
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.