VHDL · Chapter 16.4 · Synthesis and RTL Implementation
RTL Coding Style for Synthesis
Good RTL style is not cosmetic. It is what makes synthesis predictable and the resulting hardware high quality, and it comes down to a short, learnable checklist of idioms. Separate combinational and clocked logic into distinct processes, use one clock per process, and reset every register. Default-assign combinational outputs so no latch is inferred, give each process complete sensitivity, and prefer registered outputs for clean timing. Use a case statement over long priority chains when the choices are parallel, name states with enumerations, avoid combinational feedback loops, and keep arithmetic in the standard numeric library. Code this way and your RTL is inferable, readable, lint-clean, and matches simulation. This lesson collects the idioms that turn correct-but-messy RTL into clean, synthesizable, maintainable hardware.
Foundation14 min readVHDLSynthesisCoding StyleRTLBest PracticesLint
1. Engineering intuition — write so the tool can't misread you
Synthesis is literal: it builds exactly the patterns it recognizes. So good style is about removing ambiguity — writing each piece of logic in the one clear idiom that infers exactly what you mean, with no room for an accidental latch, a missed sensitivity, or a guessed clock. The payoff is compounding: predictable inference means fewer surprises in the netlist; clean separation means each process has one job; explicit resets and defaults mean sim and hardware agree. Style is not about looking tidy — it is about making your intent so unambiguous that the synthesizer, the simulator, the linter, and the next engineer all read your code the same way.
2. Formal explanation — the core idioms
-- SEPARATE combinational (next-state/output) and CLOCKED (state) logic.
-- CLOCKED process: one clock, reset every register, registered state.
process (clk) begin
if rising_edge(clk) then
if rst = '1' then state <= IDLE; -- RESET every register
else state <= next_state; -- registered state
end if;
end if;
end process;
-- COMBINATIONAL process: complete sensitivity + DEFAULT-ASSIGN outputs (no latch) + case for parallel choice.
process (all) begin
next_state <= state; -- DEFAULT (covers all paths → no latch)
y <= (others => '0'); -- default outputs
case state is -- CASE for mutually-exclusive choices
when IDLE => if go = '1' then next_state <= RUN; end if;
when RUN => y <= data; next_state <= IDLE;
when others => null; -- defaults already cover outputs
end case;
end process;The idioms: separate comb/seq processes; one clock, reset all registers; default-assign comb
outputs and use complete sensitivity (process(all)) to prevent latches and sim/synth gaps; case for
parallel choices; enumerated state names; registered outputs where timing matters; no combinational
feedback. Each idiom maps a clear intent to a predictable primitive.
3. Production usage — the style checklist in practice
-- A practical RTL style checklist (per process / per module):
-- □ One CLOCK per clocked process; clocked vs combinational logic SEPARATED.
-- □ RESET every register (no reliance on initial values, 16.2/15.5).
-- □ Combinational process: process(all) + DEFAULT-ASSIGN every output → no inferred latch (16.3).
-- □ REGISTERED outputs at module boundaries → predictable timing / fewer glitches.
-- □ CASE for mutually-exclusive selection; priority IF only when priority is intended.
-- □ ENUM state types + meaningful names; safe handling of others (9.8).
-- □ numeric_std for arithmetic; resize to keep carries (16.5).
-- □ No combinational feedback loops (6.8); no gated/derived clocks (use enables, 7.5/15.4).
-- □ Lint-clean: resolve every synthesis/lint warning (latch, sensitivity, multi-driver).What hardware does this become? Predictable, clean hardware — that is the whole point. Each idiom removes a
class of defect: separation and one-clock-per-process keep inference unambiguous; resets and defaults make sim and
silicon agree; registered boundaries give clean timing; case and enums make FSMs synthesize as intended; lint
cleanliness catches the rest. The result is RTL whose netlist holds no surprises — the comparator, register,
and mux you expected, mapped cleanly — and that the next engineer can read and modify safely. Style is how you make
quality repeatable rather than accidental.
4. Structural interpretation — style produces a predictable netlist
5. Why this is structural, not timing
RTL coding style is a discipline — a set of idioms that map intent to predictable primitives — so the checklist/structure diagram above captures it, not a waveform. It does not introduce new behavior; it makes the existing behavior unambiguous to infer (the underlying waveforms of registers, logic, and FSMs were covered in their modules). The value is structural and at design time: predictability, readability, lint-cleanliness, and sim/synth agreement — properties of how the code is written, not of any single trace.
6. Debugging example — correct-but-messy RTL that synthesizes badly
Expected: clean, predictable hardware. Observed: the design simulates correctly but synthesis throws
latch/sensitivity warnings, infers unexpected storage, has glitchy combinational outputs, or behaves
differently in gate-level sim. Root cause: the RTL ignored the style idioms — combinational and clocked
logic mixed in one process, incomplete sensitivity, combinational outputs not default-assigned
(latches), registers not reset, or a long priority if where a case was meant — so inference was ambiguous
and quality suffered. Fix: apply the checklist — separate comb/seq, process(all) with default
assignments, reset every register, register boundary outputs, case for parallel choices — and drive
lint warnings to zero. Engineering takeaway: correct simulation is not enough; messy RTL synthesizes
unpredictably — follow the style idioms (separation, defaults, resets, complete sensitivity, registered outputs)
and clear every lint warning so the netlist is clean and matches simulation.
-- BUG: mixed comb+seq, incomplete sensitivity, no default → latch + ambiguous inference.
-- process (sel) begin if clk'event and clk='1' then ... end if; if sel then y<=a; end if; end process;
-- FIX: separate, complete, defaulted.
process (clk) begin if rising_edge(clk) then q <= d; end if; end process; -- clocked
process (all) begin y <= '0'; if sel='1' then y <= a; end if; end process; -- comb, no latch7. Common mistakes & what to watch for
- Mixing combinational and clocked logic. Separate them into distinct processes so inference is clear and each has one job.
- Incomplete sensitivity / missing defaults. Use
process(all)and default-assign outputs to avoid latches and sim/synth gaps. - Unreset registers. Reset every register; do not rely on initial values (16.2/15.5).
- Priority
ifwherecasefits. Usecasefor mutually-exclusive choices; reserve priorityiffor true priority. - Ignoring lint/synthesis warnings. A latch or multi-driver warning is a real defect; drive warnings to zero rather than suppressing them.
8. Engineering insight & continuity
RTL coding style is the discipline that makes synthesis predictable and hardware high quality: separate
comb/seq processes, one clock, reset every register, default-assign with complete sensitivity, registered outputs,
case for parallel choices, enumerated states, no combinational feedback — and lint-clean throughout. These idioms
map intent unambiguously to primitives, so the netlist matches your intent and simulation. With style established,
the next lessons go deep on specific synthesis topics, beginning with the most quality-sensitive one: Synthesizing
Arithmetic — how adders, multipliers, and operators map to logic and DSP, and how to keep them correct and
efficient.