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:
| Abstraction | What you describe | Example |
|---|---|---|
| Algorithmic / Behavioural | What the system does over time | "Decode H.265 video at 60 fps" |
| Architectural | The block diagram + performance budget | "Decoder block + memory controller + NoC" |
| RTL (Register Transfer Level) | Registers + combinational logic between them | A 32-bit accumulator with synchronous load |
| Gate-level | Standard cells + wires | DFFRX1, MUX2X1, AND2X1, … |
| Transistor-level | NMOS + PMOS, sizing, layout | Schematic capture or SPICE netlist |
| Layout | Polygons on metal layers | GDSII 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.
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 flowThis 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 write | Software interpretation | RTL 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 + d | Three 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:
// 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:
- State — a
regupdated on every rising clock edge, with an asynchronous reset to a defined value. This becomes one (or more) D flip-flops on the chip. - Combinational next-state logic — a function of the current state and the module's inputs, produced by
assignor byalways @(*). This becomes a network of gates. - 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_qfor the current value of a register (_q= the "Q" output of the flop).state_dfor 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)
// 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→32Every 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
// 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
endThree rules — every combinational always block must obey:
- 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. - Use blocking assignment (
=) inside combinational blocks. Non-blocking (<=) is for sequential blocks. - 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
defaultcase + explicitelseclauses prevent latch inference.
7.3 Common pitfall — the inferred latch
always @(*) begin
if (sel == 2'b00) result = a;
if (sel == 2'b01) result = b;
// sel == 2'b10 or 2'b11 — result UNCOVERED
endWhen 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:
always @(*) begin
case (sel)
2'b00: result = a;
2'b01: result = b;
default: result = '0; // every branch covered → no latch
endcase
endEvery 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
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q_q <= 1'b0;
else q_q <= d_i;
endThis 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 (here1'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 itsDinput into itsQoutput.- 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:
always @(posedge clk) begin
if (!rst_n) q_q <= 1'b0; // reset checked only on clock edge
else q_q <= d_i;
endTrade-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
// 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;
endEvery 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
| Construct | Becomes |
|---|---|
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 always | A mux or priority encoder |
for loop with compile-time-bounded range | Unrolled — N copies of the loop body |
module / endmodule / instance / port-list | Hierarchy in the netlist |
parameter / localparam | Compile-time substitution |
generate / endgenerate | Conditional structural instantiation |
9.2 What does NOT synthesise — testbench-only constructs
| Construct | Why it's not synthesisable |
|---|---|
initial begin … end | Runs once at t=0; silicon has no "t=0" |
#10 delays | Hardware has no wall-clock delay in RTL |
$display, $monitor, $strobe | System tasks — no hardware equivalent |
$random, $urandom | Random-number generators are runtime-only |
$finish, $stop | Simulation control |
real, realtime | IEEE 754 doubles have no hardware fixed-point form |
fork ... join | Process forking is a simulation construct |
event declarations | Event types are simulation-only |
Most $ system tasks | System 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:
- Synthesisable RTL lives in
.vfiles that do not contain testbench code. Testbenches live in separate files (*_tb.vor*_test.sv) and are never read by the synthesis flow. `default_nettype noneat the top of every file. Disables the implicit-net rule.- Lint runs as part of CI. Tools like SpyGlass, Verissimo, or Verilator's
--lint-onlycatch 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:
| Block | What it is | Typical use |
|---|---|---|
| Register | A single bank of N flops | Hold a value across cycles |
| Counter | A register + adder feedback | Cycle counting, address generation, throughput throttle |
| Shift register | Chain of flops, each loading from the previous | Serial-to-parallel, parallel-to-serial, delay lines |
| FIFO | Dual-port memory + read/write pointers + full/empty logic | Decoupling producer/consumer rate |
| FSM (state machine) | State register + combinational next-state + output logic | Control flow, protocol handling |
| Mux / demux | Combinational selector | Bus routing, datapath multiplexing |
| Arithmetic | Adders, subtractors, multipliers, shifters | Datapath compute |
| Comparator | Combinational compare | Range checks, equality, ordering |
| Memory array | Inferable BRAM/ROM or instantiated cells | Cache, 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:
`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
endmoduleWhat every reviewer reads off this in five seconds:
`default_nettype noneat the top — disables implicit nets.- ANSI-style port list — every port has explicit direction and type.
- Parameterised width —
WIDTH = 32defaults but the module works at any size. _qnaming onsum_q— current state of the register; the only state in the module._dnaming onsum_d— combinational next-state.- Single
alwaysblock for the register — exactly one driver ofsum_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 theWIDTHvalue.
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 cyclesThree things to read off this waveform — every engineer reads these three things on every sequential-RTL waveform for the rest of their career:
- Reset behaviour. While
rst_n = 0(cycles 0–1),sum_qis held at0. The async reset takes effect immediately, regardless of the clock. - One-cycle latency. The
+5arrives on the clock edge ending cycle 4. The newsum_q = 5appears 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. - Hold behaviour. When
enable = 0(last two cycles),sum_qretains its last value (15). Theelsebranch of theif (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 <=
| Where | Use | Why |
|---|---|---|
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
| Pattern | Use for |
|---|---|
clk | Clock — never clock, clk_in, sysclk |
rst_n | Active-low reset — append _n |
*_q | Output of a register (state) |
*_d | Next-state to be sampled |
*_en | Enable signal |
*_i / *_o | Module input / output (less common; explicit _q / _d preferred) |
*_axi, *_apb, *_ahb | Bus-specific signals |
wr_*, rd_* | Write / read |
13.3 Module organisation
- Every module gets
`default_nettype noneat 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/assignblocks at the bottom. - One
alwaysblock 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:
- Read the spec. Architecture document, interface protocol, performance budget. Understand the input artefact before you write a line.
- 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.
- Write the RTL skeleton. Module header, port list, parameters, signal declarations, empty
alwaysblocks. Get the structure right before filling in logic. - Fill in combinational logic first. It's the easiest to reason about and to lint.
assignstatements where possible; combinationalalways @(*)where case-driven logic helps readability. - 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. - Write a directed testbench. Drive each register and each combinational path. Verify reset, basic operation, edge cases.
- Run lint + CDC. Catch latch inferences, multi-driver nets, sensitivity-list mistakes, CDC violations, missing resets. Treat warnings as errors.
- 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:
- Read the waveform. Find the cycle where things first go wrong. Backtrack the signal chain.
- Check reset. 90% of "weird behaviour at t=0" bugs are missing or wrong reset values. Walk every
regand confirm its reset path. - Check sensitivity lists. A combinational
always @(*)should be implicit; a hand-rolledalways @(a or b or c)that's missing an input produces stale outputs and a simulation-vs-synthesis mismatch. - Check blocking-vs-non-blocking. Mixed
=/<=inside a singlealwaysblock is the canonical race. Each block uses one style. - Check multi-driver
wires. Awirewith twoassignstatements drivesX. Lint catches it; if lint is silent the bug is harder to find. - Check inferred latches. If a combinational
alwaysblock doesn't assign every output on every path, a latch is inferred. Synthesis warns; treat as an error. - 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:
`default_nettype noneat the top of every file. Non-negotiable.- ANSI port-list style. Old-style separate
input/outputdeclarations are a porting accident; reject them. - Every
reghas 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 defaultX. - One
alwaysblock per register. Multiple registers in one block is a multi-driver-in-disguise pattern; split them. - No mixed
=and<=in a singlealwaysblock. Pick one and commit. always @(*)for combinational (never a hand-rolled sensitivity list).- Every combinational
alwaysassigns every output on every path —defaultcases, explicitelseclauses, or unconditional initial assignments at the top of the block. - Naming follows the project convention.
_q/_dfor state vs next;rst_nfor active-low reset;clkfor clock. Inconsistency is a red flag. - No
initial/#delays/$displayin the synthesisable hierarchy. Testbench code lives in separate files. - Parameters used for widths and depths. Hard-coded
8is a magic number;WIDTH = 8is 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
- Chapter 4 — Lexical Conventions. Comments, identifiers, keywords, operators, numbers, strings, whitespace.
- Chapter 5 — Variables & Data Types. Nets and variables; the type system every RTL declaration draws from. Includes:
- Physical Data Types (5.1) — the net family.
wireandtriNets (5.1.1) — the two workhorse net keywords.
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.
always @(*) begin
case (op)
2'b00: y = a + b;
2'b01: y = a - b;
2'b10: y = a & b;
endcase
endalways @(posedge clk) begin
if (load) count_q = data_in;
else count_q = count_q + 1;
endassign sum = a + b;
assign sum = c + d;always @(posedge clk or negedge rst_n) begin
if (!rst_n) state_q <= IDLE;
else state_q <= next_state;
endWhat 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. Ifwrap=1, wrap from 15 back to 0; ifwrap=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.
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
endmoduleStimulus:
| Cycle | rst_n | shift_en | bit_in |
|---|---|---|---|
| 0 | 0 | 0 | x |
| 1 | 1 | 0 | x |
| 2 | 1 | 1 | 1 |
| 3 | 1 | 1 | 0 |
| 4 | 1 | 1 | 1 |
| 5 | 1 | 1 | 1 |
| 6 | 1 | 0 | x |
| 7 | 1 | 0 | x |
| 8 | 1 | 1 | 0 |
| 9 | 1 | 1 | 1 |
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:
- State — a
regupdated on every rising clock edge with a defined reset. - Combinational next-state logic — a function of the current state and the module's inputs.
- 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,$displaybelong in testbenches; the synthesiser silently drops them from RTL. - Combinational vs sequential is the first reflex. Combinational
always @(*)with blocking=; sequentialalways @(posedge clk)with non-blocking<=. - Every
reghas a defined reset. Async-low is the working-ASIC default; never rely on the simulator's defaultX. _q/_dnaming makes the cycle visible. Readstate_qand the reviewer knows "current"; readstate_dand they know "next."- One
alwaysblock 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.
Related Tutorials
- Introduction & Overview — Chapter 1; what Verilog is and where it fits.
- Typical VLSI Design Flow — Chapter 2; the full path from spec to silicon.
- Lexical Conventions — Chapter 4; the lexer layer underneath RTL.
- Variables & Data Types — Chapter 5; nets, variables, four-state values, vectors and arrays.
- Physical Data Types — Chapter 5.1; the deep drill into the net family.
wireandtriNets — Chapter 5.1.1; the two workhorse net keywords.