RTL describes circuits
always_ff models registers, and nonblocking assignments make state updates occur together after the clock edge. always_comb models combinational logic and must assign every output on every path, or it infers a latch. Drive each register from one sequential block. Avoid arbitrary delays, dynamic simulation-only structures, and code whose result depends on simulator scheduling.
always_ff @(posedge clk) begin
if (!rst_n) begin
count_q <= '0;
sum_q <= '0;
end else if (accept) begin
count_q <= count_q + 1'b1;
sum_q <= sum_q + {{8{1'b0}}, in_data};
end
end
assign accept = in_valid && in_ready;
Make widths and signedness explicit. Unsized constants, signed/unsigned mixing, and silent truncation are common sources of silicon bugs. A useful naming convention is state_q for registered state and state_d for its combinational next value.
Separate control from datapath
Use IDLE, RUN, and RESULT states. IDLE accepts start; RUN accepts samples; RESULT holds out_valid and out_sum until the consumer completes a handshake. When output is stalled, both must remain stable. Keep the FSM responsible for sequencing and the datapath responsible for count and sum.
unique case (state_q)
IDLE: if (start_pulse) state_d = RUN;
RUN: begin
in_ready = 1'b1;
if (accept && last_sample) state_d = RESULT;
end
RESULT: begin
out_valid = 1'b1;
if (out_ready) state_d = IDLE;
end
endcase
Check the last-sample boundary carefully: the reported result must include the sample accepted on that same edge, not only the previous value of sum_q.
Reset and clock-domain crossings
Reset only state that affects control or externally visible behavior; large datapaths can often be masked by valid bits. For clock crossings, use a two-flop synchronizer for a stable single-bit level, a handshake for events, and an asynchronous FIFO for multi-bit streams. Synchronizing each bit of a bus independently does not preserve a coherent word.
Parameterize dimensions that genuinely vary, such as DATA_WIDTH, and assert legal parameter ranges. Every new configuration multiplies the verification space; a first tapeout benefits more from one proven configuration than unbounded genericity.
Chapter deliverables
- Top-level RTL, register block, and streaming datapath.
- Verilator lint or equivalent with all width, latch, and multiple-driver findings resolved.
- Waveform review for normal traffic, input bubbles, output stalls, and reset during a batch.
- Concise module documentation for clocks, resets, handshakes, and parameters.
Tools and further reading
- Verilator documentation for lint and RTL simulation.
- Yosys documentation for synthesis and netlist processing.
- OpenROAD documentation for the open RTL-to-GDS flow.
- SkyWater Open Source PDK for open process rules and libraries.