Skip to content

Verilog · Chapter 3 · Foundations

RTL Designing in Verilog

RTL design is the engineering discipline of writing Verilog that maps cleanly to gates, flip-flops, and wires on a chip. Every line you write must answer two questions, what gates does this become and which clock edge updates them, or it is not RTL yet. This chapter introduces the three idioms every clocked block in every working chip is composed from, the building blocks such as registers, counters, FIFOs, and state machines that those idioms combine into, and the synthesisable-versus-simulation cut-line that determines what survives the trip to silicon. It also covers the review and debug habits that keep RTL out of trouble all the way through tape-out, so the hardware you describe in text becomes the hardware you actually get.

Foundation32 min readRTLVerilogCombinationalSequentialSynthesis

Chapter 3 · Page 1.3 · Foundations

1. The Engineering Problem

You have a one-page spec from architecture: "Build a 32-bit fetch-and-add accumulator. Inputs: a clock, an active-low reset, an enable, and a 32-bit data input. Output: a 32-bit running sum, updated on every clock edge when enable is high. The reset must clear the accumulator to zero."

A C programmer would think of a function: acc += data; inside a loop. A schematic-era engineer would draw an adder, a register, and a mux feeding the register, then place-and-route those gates by hand. Today neither approach scales.

The problem RTL solves is the gap between those two extremes. Software is too far from the silicon — there is no notion of a clock edge, no notion of a flip-flop, no notion of parallel evaluation. A hand-drawn schematic is too close to the silicon — every gate placement is one engineer-hour, and the spec needs ten thousand gates.

RTL Design is the discipline of writing text that describes what the gates should do — at the level of registers transferring values to each other through combinational logic — and letting the synthesis tool generate the gates. The accumulator above becomes fifteen lines of Verilog. The synthesis tool reads those fifteen lines and produces an adder, a 32-bit flop bank, a multiplexer, and the wires between them — exactly the gates a 1995-era engineer would have drawn by hand, but in fifteen lines of text instead of fifteen pages of schematic.

This chapter is about how to write those fifteen lines correctly — and how to read the thousands of similar lines that compose every working chip.

2. Why RTL Exists

The acronym RTL stands for Register Transfer Level. It names a specific level of abstraction in the chip-design hierarchy:

AbstractionWhat you describeExample
Algorithmic / BehaviouralWhat the system does over time"Decode H.265 video at 60 fps"
ArchitecturalThe block diagram + performance budget"Decoder block + memory controller + NoC"
RTL (Register Transfer Level)Registers + combinational logic between themA 32-bit accumulator with synchronous load
Gate-levelStandard cells + wiresDFFRX1, MUX2X1, AND2X1, …
Transistor-levelNMOS + PMOS, sizing, layoutSchematic capture or SPICE netlist
LayoutPolygons on metal layersGDSII file

RTL exists because it is the sweet spot for human reasoning about a chip's behaviour. At the algorithmic layer, you can't reason about clock cycles or area; at the gate level, you can't reason about the design as a whole. RTL is the abstraction at which an engineer can write down a 32-bit accumulator in fifteen lines and have the synthesis tool turn it into a thousand gates — without losing the ability to reason about what each register does on each clock edge.

The whole flow that surrounds RTL (see Chapter 2) is built around this abstraction: RTL is the input to simulation (which models behaviour) and to synthesis (which produces gates). Both downstream artefacts derive from the same source. Get the RTL right and the chip works; get it wrong and no amount of physical-design effort recovers.

3. Mental Model

The working test for every Verilog line you write:

  • Is this combinational logic? It depends only on its inputs (no clock, no state).
  • Is this state? It holds a value across clock cycles (must have a defined update event and a defined reset).
  • If neither, what is it? Probably a bug.

That ternary covers 99% of decisions you will make as an RTL designer.

4. Visual Explanation

The RTL abstraction sits in the middle of the chip-design ladder — high enough to be human-readable, low enough to map deterministically to gates.

RTL abstraction ladder — specification, RTL, gate-level netlist, physical layout1Specificationbehavioural intent · architecture2RTLVerilog / SystemVerilog · the source of truth3Gate-Level Netlistpost-synthesis standard cells4Physical Layoutplaced + routed → GDS
The abstraction ladder of chip design. RTL is the human-writable layer that maps deterministically to gates; algorithmic descriptions above it are too far from silicon for synthesis, and gate-level netlists below it are too verbose for human authoring at modern transistor counts.

Inside that RTL layer, every clocked design is structured the same way: a register-transfer cycle in three parts.

The RTL design cycle — registers + combinational logic + next-state

data flow
The RTL design cycle — registers + combinational logic + next-statestate_qstate_dresultState (regs)current value of every flopCombinationalnext-state + output logicOutputsmodule port values
Three actors per clock cycle: the current state (held in registers), the combinational logic (computes next-state and outputs), and the module outputs that emit to the rest of the chip. On every rising clock edge, the registers swallow next-state and the cycle repeats from a new starting point.

This three-actor pattern is the canonical RTL cycle. Every working chip — from a thermostat controller to a 4 GHz CPU — is some variation of this loop. Read three pieces of RTL and you'll see the same shape; read three hundred and the shape is still the same.

5. RTL vs Software Programming

If you come to Verilog from a software background, the first mental shift is this: every line of synthesisable RTL describes a piece of hardware that will exist on silicon. The Verilog you write is a blueprint that the synthesis tool builds; it is not a recipe that a CPU runs.

You writeSoftware interpretationRTL hardware
assign y = a & b;Compute a & b, assign to y once.A 2-input AND gate. y is one physical net on the chip.
always @(posedge clk) q <= d;At every clock edge, copy d to q.A D flip-flop. q is the output pin of a real flop.
if (sel) y = a; else y = b;Branch on sel.A 2-to-1 multiplexer. Both branches exist in silicon.
for (i = 0; i < 4; i = i + 1)Loop four times.The synthesiser unrolls — four copies exist in parallel.
a + b + c + dThree sequential adds.An adder tree — three adders in parallel structure.

There is no program counter inside Verilog hardware. There is no call stack. There is no "execute line 1, then line 2." Inside a module, every assign, every always block, every sub-module instance evaluates simultaneously on every relevant event. Carrying a sequential-programming intuition into RTL produces every bug you will write in your first month.

The shorthand: software runs over time; hardware exists in space. A C for loop reuses the same ALU four times in sequence. A Verilog for loop unrolls — four copies of the body exist as four pieces of silicon, all running in parallel. The trade-offs you make in hardware (more gates for parallelism vs fewer gates with sequencing) are completely invisible in software.

6. The Three RTL Idioms

The single most important pattern in this chapter. Every clocked digital block in every working RTL file is composed from three idioms:

The three RTL idioms — every clocked block, every time
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 1. STATE — a clocked register (sequential logic)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) state_q <= RESET_VAL;     // reset to a known value
    else        state_q <= state_d;        // commit next-state on every edge
end
 
// 2. COMBINATIONAL NEXT-STATE LOGIC
assign state_d = f(state_q, inputs);
 
// 3. OUTPUT LOGIC
assign result = g(state_q);                // Moore output (state-only)
// or:
// assign result = g(state_q, inputs);     // Mealy output (state + inputs)

Read those three idioms left-to-right:

  1. State — a reg updated on every rising clock edge, with an asynchronous reset to a defined value. This becomes one (or more) D flip-flops on the chip.
  2. Combinational next-state logic — a function of the current state and the module's inputs, produced by assign or by always @(*). This becomes a network of gates.
  3. Output logic — a function of the current state (Moore) or of state + inputs (Mealy). Drives the module's output ports. Also combinational.

Once you can read those three idioms cleanly, you can read 95% of every Verilog file you'll ever open. Counters, shift registers, FIFOs, FSMs, datapath stages — every one of them is the same pattern with different f() and g().

The naming convention you should adopt today and never deviate from:

  • state_q for the current value of a register (_q = the "Q" output of the flop).
  • state_d for the next value to be loaded (_d = the "D" input of the flop).
  • Inputs are nouns describing what they carry (data_in, enable, wr_addr).
  • Outputs are nouns describing the produced result (data_out, valid_o, count_q).

This convention makes the state_q → combinational logic → state_d → register loop visible in every line of code. A senior reviewer reads state_q and immediately knows "this is the current register value"; reads state_d and immediately knows "this is computed combinationally and sampled on the next clock edge."

7. Combinational RTL

Combinational logic is hardware whose output is a pure function of its inputs — no clock dependency, no internal state. Whenever any input changes, the output settles to a new value after the propagation delay of the gates. There are exactly two ways to write combinational RTL in Verilog:

7.1 Continuous assignment (assign)

combinational-assign.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Pure-function arithmetic
assign sum     = a + b;
assign diff    = a - b;
assign max_val = (a > b) ? a : b;          // mux via ternary
 
// Pure-function boolean
assign and_out = a & b;
assign xor_out = a ^ b;
assign parity  = ^data_bus;                // reduction XOR — even parity
 
// Concatenation and replication
assign extended = {{16{data[15]}}, data};  // sign-extend 16→32

Every assign is a piece of combinational logic. The expression on the right side describes the function; the synthesiser picks the gates.

7.2 Combinational always blocks

combinational-always.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Cleaner syntax for case-driven combinational logic
always @(*) begin
    case (opcode)
        2'b00: result = a + b;
        2'b01: result = a - b;
        2'b10: result = a & b;
        2'b11: result = a | b;
        default: result = 32'h0;            // every branch covered
    endcase
end

Three rules — every combinational always block must obey:

  1. Use always @(*) (the implicit sensitivity list — synthesiser-friendly), not a hand-rolled list (always @(a or b or opcode)) which goes stale when an input is added.
  2. Use blocking assignment (=) inside combinational blocks. Non-blocking (<=) is for sequential blocks.
  3. Assign every output on every path. Otherwise the synthesiser infers a latch — a memory element that holds the last value, which is rarely what you want and a frequent source of bugs. The default case + explicit else clauses prevent latch inference.

7.3 Common pitfall — the inferred latch

latch-bug.v — what NOT to write
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(*) begin
    if (sel == 2'b00) result = a;
    if (sel == 2'b01) result = b;
    // sel == 2'b10 or 2'b11 — result UNCOVERED
end

When sel is 2'b10 or 2'b11, result is not assigned. The synthesiser sees this as "must hold the last value" — and infers a latch to do so. The fix is to cover every branch:

latch-fixed.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(*) begin
    case (sel)
        2'b00:   result = a;
        2'b01:   result = b;
        default: result = '0;               // every branch covered → no latch
    endcase
end

Every working synthesis tool emits a warning when a latch is inferred. Treat that warning as an error.

8. Sequential RTL

Sequential logic is hardware that holds state between clock cycles — a register, a counter, an FSM, an FIFO pointer. The defining feature of sequential RTL is the clock: every state update happens on a clock edge, never between edges.

8.1 The flip-flop idiom

sequential-flop.v — the canonical flip-flop
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) q_q <= 1'b0;
    else        q_q <= d_i;
end

This is a D flip-flop with an asynchronous active-low reset. Four pieces every working engineer reads instantly:

  • @(posedge clk or negedge rst_n) — the sensitivity list. The block runs on a rising clock edge or a falling reset edge. Both are async — they need no synchronisation with anything else.
  • if (!rst_n) — the reset branch. When reset asserts, the flop loads its reset value (here 1'b0). This branch runs immediately, regardless of the clock.
  • q_q <= d_i; — the data path. On a rising clock edge with reset deasserted, the flop loads its D input into its Q output.
  • Non-blocking assignment (<=) — the assignment style for sequential blocks. See §13 for the deeper rule.

8.2 Sync vs async reset

The reset above is asynchronous — it asserts the moment rst_n falls, without waiting for a clock edge. The alternative is synchronous reset:

sequential-sync-reset.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    if (!rst_n) q_q <= 1'b0;                // reset checked only on clock edge
    else        q_q <= d_i;
end

Trade-offs:

  • Async reset (@(posedge clk or negedge rst_n)) is the default for most ASIC libraries — most flip-flop standard cells have a dedicated async-reset pin. The reset is fast and reliable but the rising edge of the reset deassertion must be synchronised to the clock to avoid metastability.
  • Sync reset (@(posedge clk)) requires no async reset pin in the standard cell, simpler timing, but the reset signal must propagate through the same combinational logic as the data and can lengthen the reset network.

Most working ASICs use async-assert / sync-deassert reset — the falling edge propagates async (fast power-on reset); the rising edge is sampled by a reset synchroniser before the rest of the chip sees deassertion.

8.3 Common sequential idioms

sequential-idioms.v — counters, shifters, enables
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Up-counter with synchronous load and enable
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)        count_q <= 8'h00;
    else if (load_en)  count_q <= load_value;
    else if (count_en) count_q <= count_q + 8'h01;
    // else: hold (implicit — no else means count_q retains its value)
end
 
// Shift register
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) shift_q <= 8'h00;
    else        shift_q <= {shift_q[6:0], serial_in};
end
 
// Enable-driven register (used everywhere — clock-gating-friendly form)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)       data_q <= 32'h0;
    else if (data_en) data_q <= data_i;
end

Every one of these is a flip-flop bank with combinational logic deciding what value to load. The else if (count_en) ... else: hold pattern is the canonical enable-driven register — the synthesiser can extract a clock-gate from it for power savings.

9. Synthesisable vs Non-Synthesisable RTL

The single most important conceptual cut-line in Verilog. The same .v file can contain text the synthesiser reads and text it cannot — and what survives the trip to silicon is only the synthesisable subset.

9.1 What synthesises

ConstructBecomes
assign y = expr;Combinational logic — gates driving y
always @(*) y = expr;Same — combinational logic
always @(posedge clk) q <= d;A D flip-flop
always @(posedge clk or negedge rst_n) ...A flip-flop with async reset
if / else / case inside an alwaysA mux or priority encoder
for loop with compile-time-bounded rangeUnrolled — N copies of the loop body
module / endmodule / instance / port-listHierarchy in the netlist
parameter / localparamCompile-time substitution
generate / endgenerateConditional structural instantiation

9.2 What does NOT synthesise — testbench-only constructs

ConstructWhy it's not synthesisable
initial begin … endRuns once at t=0; silicon has no "t=0"
#10 delaysHardware has no wall-clock delay in RTL
$display, $monitor, $strobeSystem tasks — no hardware equivalent
$random, $urandomRandom-number generators are runtime-only
$finish, $stopSimulation control
real, realtimeIEEE 754 doubles have no hardware fixed-point form
fork ... joinProcess forking is a simulation construct
event declarationsEvent types are simulation-only
Most $ system tasksSystem task family is simulation-only

A design that simulates correctly but uses non-synthesisable constructs in its RTL silently breaks at synthesis — the synthesis tool drops the offending lines and emits gates as if they weren't there. The simulation passes; the silicon does something different. The most common first-month mistake.

9.3 The discipline

Every working RTL house enforces three rules:

  1. Synthesisable RTL lives in .v files that do not contain testbench code. Testbenches live in separate files (*_tb.v or *_test.sv) and are never read by the synthesis flow.
  2. `default_nettype none at the top of every file. Disables the implicit-net rule.
  3. Lint runs as part of CI. Tools like SpyGlass, Verissimo, or Verilator's --lint-only catch non-synthesisable constructs at the source level — before the synthesis tool ever sees the code.

Get into the habit of separating the synthesisable subset from the simulation-only world on day one. Every chapter from here on assumes this discipline.

10. Common RTL Building Blocks

Most working RTL is built from a small set of recurring blocks. Each one is the same three idioms (state + combinational + output) wired differently:

BlockWhat it isTypical use
RegisterA single bank of N flopsHold a value across cycles
CounterA register + adder feedbackCycle counting, address generation, throughput throttle
Shift registerChain of flops, each loading from the previousSerial-to-parallel, parallel-to-serial, delay lines
FIFODual-port memory + read/write pointers + full/empty logicDecoupling producer/consumer rate
FSM (state machine)State register + combinational next-state + output logicControl flow, protocol handling
Mux / demuxCombinational selectorBus routing, datapath multiplexing
ArithmeticAdders, subtractors, multipliers, shiftersDatapath compute
ComparatorCombinational compareRange checks, equality, ordering
Memory arrayInferable BRAM/ROM or instantiated cellsCache, lookup tables, configuration

A typical SoC block contains a dozen FSMs, a hundred registers, several FIFOs, an arithmetic datapath, and many muxes — all assembled into the larger structure of the block. Mastering each building block in isolation is the path to reading and writing real RTL fluently.

11. Worked Example — A Fetch-and-Add Accumulator

Tying together everything in this chapter: the three idioms, the synthesisable subset, naming convention, and a recognisable building block. The accumulator from §1, written as production RTL:

accumulator module — clock, reset, enable, and data_in on the left; sum_q on the rightaccumulator32-bit running sum · synchronous · enabled32-bit running sum · synchronous · enabledclkrst_nenabledata_in[31:0]sum_q[31:0]6
Accumulator module — 32-bit data in, 32-bit running sum out, gated by an enable. Reset clears the sum to zero. The internal structure is one of the simplest sequential designs: a register, an adder, a mux feeding the register's D input.
rtl/accumulator.v — production-grade RTL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
 
module accumulator #(
    parameter WIDTH = 32
) (
    input  wire              clk,
    input  wire              rst_n,
    input  wire              enable,
    input  wire [WIDTH-1:0]  data_in,
    output reg  [WIDTH-1:0]  sum_q
);
 
    // ─── Combinational next-state ──────────────────────────────────────
    wire [WIDTH-1:0] sum_d;
    assign sum_d = sum_q + data_in;
 
    // ─── Clocked state (the only register in the module) ───────────────
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)      sum_q <= {WIDTH{1'b0}};
        else if (enable) sum_q <= sum_d;
        // else: hold (implicit)
    end
 
endmodule

What every reviewer reads off this in five seconds:

  • `default_nettype none at the top — disables implicit nets.
  • ANSI-style port list — every port has explicit direction and type.
  • Parameterised widthWIDTH = 32 defaults but the module works at any size.
  • _q naming on sum_q — current state of the register; the only state in the module.
  • _d naming on sum_d — combinational next-state.
  • Single always block for the register — exactly one driver of sum_q. No multi-driver bugs.
  • Async-low reset — defined value at power-on; matches the standard-cell library's reset behaviour.
  • Enable-gated update — the else if (enable) form is clock-gate-friendly for power optimisation.
  • {WIDTH{1'b0}} — parametric zero literal. Sizes correctly regardless of the WIDTH value.

That is the level of RTL discipline every working ASIC house expects on the first commit.

12. Waveform Analysis

To see the accumulator behave, follow it through six clock cycles of stimulus:

accumulator — running sum across six clock cycles

12 cycles
accumulator — running sum across six clock cyclesreset releasedreset released+5 sampled+5 sampledsum = 0+5 = 5sum = 0+5 = 5sum = 5+3 = 8sum = 5+3 = 8sum = 8+7 = 15sum = 8+7 = 15clkrst_nenabledata_inXXXX553377XXsum_q1515t0t1t2t3t4t5t6t7t8t9t10t11
Each rising clock edge while enable=1 samples data_in and adds it to sum_q. The new sum appears on the cycle AFTER the addition's stimulus — flip-flops sample their D input on the clock edge and present the new Q value for the rest of that half-cycle. When enable=0 (last cycles), sum_q holds.

Three things to read off this waveform — every engineer reads these three things on every sequential-RTL waveform for the rest of their career:

  1. Reset behaviour. While rst_n = 0 (cycles 0–1), sum_q is held at 0. The async reset takes effect immediately, regardless of the clock.
  2. One-cycle latency. The +5 arrives on the clock edge ending cycle 4. The new sum_q = 5 appears starting cycle 6 (the next clock edge after the data was sampled). This one-cycle latency is the universal signature of a clocked flip-flop — what arrived at the D input on edge N appears at the Q output starting edge N.
  3. Hold behaviour. When enable = 0 (last two cycles), sum_q retains its last value (15). The else branch of the if (enable) is implicit hold — the synthesiser maps this to a clock-gate or an input mux, both of which let the flop keep its current state.

This three-part reading — reset / sample-and-update / hold — applies to every sequential RTL waveform you will ever debug. The first thing you check on a misbehaving register is whether the reset is right; the second is whether the sample edge matches the data arrival; the third is whether the hold condition matches your spec.

13. Coding Style — Blocking vs Non-Blocking, and Other Rules

A handful of style rules separate working RTL from RTL that almost works.

13.1 Blocking = vs non-blocking <=

WhereUseWhy
Combinational always @(*)= (blocking)Models combinational evaluation — each assignment takes effect immediately, mirroring how a gate's output settles
Sequential always @(posedge clk)<= (non-blocking)All sequential registers sample their inputs simultaneously at the clock edge; non-blocking semantics model this concurrent update
Continuous assign(n/a — there is no = choice; assign is always combinational)
initial (testbench)Either, but typically =No hardware semantics; testbench style

The single most important rule: never mix = and <= in the same always block. Mixing them produces the canonical race-condition bug — the simulator produces one answer, the synthesiser-built silicon produces another, and you spend three days debugging.

A masterpiece-worthy chapter on this topic (Blocking vs Non-Blocking — see the Masterpiece Standard §7) is planned for later in the curriculum.

13.2 Naming

PatternUse for
clkClock — never clock, clk_in, sysclk
rst_nActive-low reset — append _n
*_qOutput of a register (state)
*_dNext-state to be sampled
*_enEnable signal
*_i / *_oModule input / output (less common; explicit _q / _d preferred)
*_axi, *_apb, *_ahbBus-specific signals
wr_*, rd_*Write / read

13.3 Module organisation

  • Every module gets `default_nettype none at the top.
  • ANSI port-list style (every port declared in the header, none in the body).
  • Parameters at the top of the module body; localparams next; then signal declarations; then always/assign blocks at the bottom.
  • One always block per register (multiple registers in one block is a lint violation in most suites).
  • Hierarchical comments — every module has a one-line header comment naming what it is.

14. Real-World RTL Workflow

How RTL is actually written on a working ASIC team. Eight steps from spec to RTL-freeze:

  1. Read the spec. Architecture document, interface protocol, performance budget. Understand the input artefact before you write a line.
  2. Sketch the block diagram. On paper or a whiteboard. Identify the registers, the combinational logic, the interface ports. This is the design; the Verilog is just one encoding of it.
  3. Write the RTL skeleton. Module header, port list, parameters, signal declarations, empty always blocks. Get the structure right before filling in logic.
  4. Fill in combinational logic first. It's the easiest to reason about and to lint. assign statements where possible; combinational always @(*) where case-driven logic helps readability.
  5. Add the sequential logic. One always @(posedge clk or negedge rst_n) per register or per related register group. Every register gets a defined reset.
  6. Write a directed testbench. Drive each register and each combinational path. Verify reset, basic operation, edge cases.
  7. Run lint + CDC. Catch latch inferences, multi-driver nets, sensitivity-list mistakes, CDC violations, missing resets. Treat warnings as errors.
  8. Run a constrained-random regression. Hand off to the DV team for full coverage closure.

The seasoned RTL designer iterates between steps 4–6 dozens of times — write a small piece, simulate, inspect, refine. Every commit closes a tiny loop. The discipline that gets RTL to clean tape-out is hundreds of these tiny loops, not one big design effort.

15. Debugging RTL

When a piece of RTL misbehaves — and it will — the debug workflow is consistent. The cheap-to-expensive ordering:

  1. Read the waveform. Find the cycle where things first go wrong. Backtrack the signal chain.
  2. Check reset. 90% of "weird behaviour at t=0" bugs are missing or wrong reset values. Walk every reg and confirm its reset path.
  3. Check sensitivity lists. A combinational always @(*) should be implicit; a hand-rolled always @(a or b or c) that's missing an input produces stale outputs and a simulation-vs-synthesis mismatch.
  4. Check blocking-vs-non-blocking. Mixed = / <= inside a single always block is the canonical race. Each block uses one style.
  5. Check multi-driver wires. A wire with two assign statements drives X. Lint catches it; if lint is silent the bug is harder to find.
  6. Check inferred latches. If a combinational always block doesn't assign every output on every path, a latch is inferred. Synthesis warns; treat as an error.
  7. Compare expected vs observed precisely. State what you expected in words before looking at the waveform. Vague expectations produce vague debugging.

16. Design Review Notes

What a senior RTL reviewer flags on every commit:

  1. `default_nettype none at the top of every file. Non-negotiable.
  2. ANSI port-list style. Old-style separate input/output declarations are a porting accident; reject them.
  3. Every reg has a defined reset path. Either async (@(posedge clk or negedge rst_n)) or sync (@(posedge clk) if (!rst_n)). Never rely on the simulator's default X.
  4. One always block per register. Multiple registers in one block is a multi-driver-in-disguise pattern; split them.
  5. No mixed = and <= in a single always block. Pick one and commit.
  6. always @(*) for combinational (never a hand-rolled sensitivity list).
  7. Every combinational always assigns every output on every pathdefault cases, explicit else clauses, or unconditional initial assignments at the top of the block.
  8. Naming follows the project convention. _q / _d for state vs next; rst_n for active-low reset; clk for clock. Inconsistency is a red flag.
  9. No initial / #delays / $display in the synthesisable hierarchy. Testbench code lives in separate files.
  10. Parameters used for widths and depths. Hard-coded 8 is a magic number; WIDTH = 8 is documentation.

Every chip that ships has had this checklist applied to every RTL commit. Internalise it before you submit your first pull request.

17. Interview Insights

Foundational RTL questions that come up early in every chip-design interview. Reason through the answers; don't memorise them.

18. Learning Roadmap

This chapter introduces RTL design. The Verilog track drills into each piece of the framing from here:

18.1 Continue the Verilog track

18.2 Masterpiece-worthy topics in the Verilog track

Four topics from the Masterpiece Standard §7 deepen what you learned here:

  • Blocking vs Non-Blocking Assignments — the canonical RTL race-condition trap (§13).
  • FSM Design — the canonical sequential-RTL pattern (encoding, reset, latch-free style, Mealy vs Moore).
  • Clock Domain Crossing (CDC) — what happens when two RTL blocks live on different clocks.
  • Async FIFO — the canonical CDC structure; gray-coded pointers, full/empty under different clocks.

Each is a flagship chapter; together they cover the bedrock of L3 Senior RTL Engineer competency.

18.3 Adjacent curricula

Once the Verilog track lands the four masterpiece topics above, the adjacent curricula deepen the surrounding stages:

  • SystemVerilog + UVM — verification methodology.
  • STA — static timing analysis; closing timing on the RTL you've written.
  • Front-End Design — synthesis and DFT.

The Verilog track gives you the RTL layer; the adjacent tracks give you everything that surrounds it.

19. Exercises

Three exercises to convert the chapter's mental model into reflex.

Exercise 1 — Identify the idiom

For each Verilog snippet below, classify it as (a) combinational, (b) sequential, or (c) buggy / non-RTL. Explain why.

exercise-1a.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(*) begin
    case (op)
        2'b00: y = a + b;
        2'b01: y = a - b;
        2'b10: y = a & b;
    endcase
end
exercise-1b.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    if (load) count_q = data_in;
    else      count_q = count_q + 1;
end
exercise-1c.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign sum = a + b;
assign sum = c + d;
exercise-1d.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) state_q <= IDLE;
    else        state_q <= next_state;
end

What to produce. A one-line classification + a one-sentence justification per snippet. (Hint: look for missing default cases, mixed assignment styles, missing reset, multi-driver patterns.)

Exercise 2 — Build a counter

Write a synthesisable Verilog module synchronous_counter with:

  • Inputs: clk, rst_n, enable, wrap (signal — when high, count wraps; when low, count saturates).
  • Output: count_q — 4-bit register.
  • Behaviour: on each clock edge with enable=1, increment. If wrap=1, wrap from 15 back to 0; if wrap=0, saturate at 15.

Use `default_nettype none, ANSI port-list style, parameterised width (default 4 bits), _q / _d naming, and a single always block per register. Add one paragraph of explanation naming the three idioms used.

Exercise 3 — Predict the waveform

Given the following sequential module and stimulus, sketch (on paper or in a <Waveform>-shaped table) the value of out_q across 10 clock cycles.

exercise-3.v — predict out_q over 10 cycles
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module shifter (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        shift_en,
    input  wire        bit_in,
    output reg  [3:0]  out_q
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)        out_q <= 4'h0;
        else if (shift_en) out_q <= {out_q[2:0], bit_in};
    end
endmodule

Stimulus:

Cyclerst_nshift_enbit_in
000x
110x
2111
3110
4111
5111
610x
710x
8110
9111

Hint. The shift idiom {out_q[2:0], bit_in} drops the MSB and shifts a new bit into the LSB. New value of out_q appears on the cycle after the stimulus (one-cycle flip-flop latency).

20. Summary

RTL design is the discipline of writing Verilog at the abstraction of registers + combinational logic between them. Every clocked digital block in every working chip is composed of three idioms:

  1. State — a reg updated on every rising clock edge with a defined reset.
  2. Combinational next-state logic — a function of the current state and the module's inputs.
  3. Output logic — a function of state (Moore) or state + inputs (Mealy).

The takeaways every later chapter assumes:

  • The synthesisable subset is the contract. initial, #delays, $display belong in testbenches; the synthesiser silently drops them from RTL.
  • Combinational vs sequential is the first reflex. Combinational always @(*) with blocking =; sequential always @(posedge clk) with non-blocking <=.
  • Every reg has a defined reset. Async-low is the working-ASIC default; never rely on the simulator's default X.
  • _q / _d naming makes the cycle visible. Read state_q and the reviewer knows "current"; read state_d and they know "next."
  • One always block per register, every output assigned on every path, no mixed = / <=. These are the latch-free and race-free disciplines.

Internalise the three idioms, the synthesisable-subset cut-line, and the naming convention, and you can read 95% of every working RTL file on the planet. Chapter 4 picks up with the lexical layer — comments, identifiers, keywords, operators, numbers — that every line of RTL is composed of.