HomeRTL→SiToolsInterview
Chapter 1 of 10
← Overview Ch.2 Synthesis →
⚡ Interactive FSM Visualizer inside

RTL Coding

The starting point of every chip. Learn to write clean, synthesizable Verilog — the rules the synthesizer actually enforces, not just the simulator.

📖 ~30 min read 🎯 Combinational · Sequential · FSM · Pitfalls 🏭 Next: Logic Synthesis →
In this chapter
  1. What is RTL?
  2. Combinational Logic
  3. Sequential Logic & Flip-Flops
  4. Blocking vs Non-Blocking
  5. Finite State Machines (FSM)
  6. Interactive: FSM Visualizer
  7. Common RTL Pitfalls
  8. Coding Guidelines
  9. Key Takeaways

1. What is RTL?

Register Transfer Level (RTL) is an abstraction that describes how data flows between registers and how combinational logic transforms that data each clock cycle. RTL is the level of abstraction that synthesis tools consume — above gate-level netlist, below behavioral/algorithmic.

An RTL description must be synthesizable: every construct must have a well-defined hardware equivalent. Simulation-only constructs (delays #10, $display, initial blocks with random inputs) are ignored or cause errors during synthesis.

Key insight: Always think in hardware — every always block infers either combinational logic or flip-flops. The synthesizer does not "understand" your intent; it follows rules mechanically.

2. Combinational Logic

Combinational logic has no memory — output depends only on current inputs. In Verilog, use always @(*) (or always_comb in SystemVerilog). All inputs must appear in the sensitivity list; @(*) handles this automatically.

// Correct: 4-to-1 MUX
module mux4 (
  input  logic [3:0] d,
  input  logic [1:0] sel,
  output logic       y
);
  always_comb begin
    case (sel)
      2'b00: y = d[0];
      2'b01: y = d[1];
      2'b10: y = d[2];
      default: y = d[3];  // default prevents latch
    endcase
  end
endmodule
Latch warning: If a combinational always block does not assign an output in every branch (missing else or missing default), the synthesizer infers a latch to "hold" the previous value. Latches are almost always unintentional and cause timing problems. Always use default in case and cover all if/else branches.

3. Sequential Logic & Flip-Flops

Sequential logic stores state in flip-flops clocked on a clock edge. Use always_ff @(posedge clk) in SystemVerilog. Resets can be synchronous (sampled by the clock) or asynchronous (in sensitivity list).

// D flip-flop — synchronous reset
always_ff @(posedge clk) begin
  if (rst_n == 1'b0)
    q <= 1'b0;
  else
    q <= d;
end

// D flip-flop — asynchronous reset
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    q <= 1'b0;
  else
    q <= d;
end
PropertySynchronous ResetAsynchronous Reset
Reset timingSampled at clock edgeImmediate (any time)
Timing analysisReset path analyzed like dataRecovery/removal checks required
FPGA resourcesUses dedicated sync resetUses dedicated async reset
Preferred inMost ASIC flowsPower-on reset, safety-critical
CDC concernMust synchronize resetReset synchronizer needed

4. Blocking vs Non-Blocking Assignments

This is the most common RTL bug source. The rule is simple and must be followed without exception:

The golden rule:
<= (non-blocking) — always use in always_ff (sequential blocks)
= (blocking) — always use in always_comb (combinational blocks)
Never mix them in the same block.
// WRONG — blocking in sequential: creates combinational chain, not pipeline
always_ff @(posedge clk) begin
  a = b + c;   // BAD: '=' in sequential
  d = a + e;   // a is already updated above — no pipeline register!
end

// CORRECT — non-blocking: both sample old values, update simultaneously
always_ff @(posedge clk) begin
  stage1 <= b + c;      // samples b, c at current clock edge
  stage2 <= stage1 + e; // samples OLD stage1 — proper pipeline register
end

Non-blocking assignments schedule updates to occur after all RHS expressions in the time step have been evaluated — this models registers correctly because all FFs sample their inputs simultaneously at the clock edge.

5. Finite State Machines (FSM)

FSMs are the backbone of control logic. The recommended coding style separates state registers, next-state logic, and output logic into three always blocks — called 3-always style. This maps cleanly to hardware and passes lint cleanly.

StyleState regNext-stateOutputNotes
MealyFFCombComb (uses input)Faster output, harder to pipeline
MooreFFCombReg or Comb (state only)Easier to time, preferred in ASIC
// 3-process Moore FSM: simple 3-state traffic light controller
typedef enum logic [1:0] {RED, GREEN, YELLOW} state_t;
state_t state, next_state;

// Process 1: state register
always_ff @(posedge clk or negedge rst_n)
  if (!rst_n) state <= RED;
  else        state <= next_state;

// Process 2: next-state logic (combinational)
always_comb begin
  next_state = state; // default: stay in current state
  case (state)
    RED:    if (timer_done) next_state = GREEN;
    GREEN:  if (timer_done) next_state = YELLOW;
    YELLOW: if (timer_done) next_state = RED;
  endcase
end

// Process 3: output logic (Moore — depends only on state)
always_comb begin
  {red_light, green_light, yellow_light} = 3'b000;
  case (state)
    RED:    red_light    = 1'b1;
    GREEN:  green_light  = 1'b1;
    YELLOW: yellow_light = 1'b1;
  endcase
end
⚡ Interactive: FSM State Diagram Visualizer
Add states and transitions below. The state diagram auto-renders. Click a state to simulate stepping through it.

7. Common RTL Pitfalls

Incomplete sensitivity list (Verilog 2001)

Missing signals in always @(a, b) cause simulation/synthesis mismatch. Always use always @(*) or always_comb.

Inferring unintended latches

Every output of a combinational block must be assigned in every code path. Add a default assignment at the top of the always_comb block.

Multiple drivers

Assigning the same signal from two separate always blocks creates a multiple-driver error. A signal can only have one driver in synthesizable RTL.

Mixing posedge/negedge clocks

Mixing rising and falling edge-triggered FFs on the same data path creates half-cycle timing paths — legal but extremely tricky to close timing on. Avoid unless required.

Arithmetic overflow

// Bug: 8-bit + 8-bit = 8-bit result, carry lost
logic [7:0] a, b, sum;
sum = a + b;  // overflow if a+b > 255

// Fix: extend result width
logic [8:0] sum_ext;
sum_ext = {1'b0, a} + {1'b0, b};  // 9-bit result captures carry

8. Coding Guidelines

✅ Chapter 1 Key Takeaways

Next → Chapter 2
Logic Synthesis
How Genus/Design Compiler turns your Verilog into a gate-level netlist — technology mapping, SDC constraints, timing/area trade-offs.