Lint reads the RTL as written. It catches width, latch, unused-logic, and style findings without knowing the clocks.
RDC reads the RTL plus clock and reset intent. It checks crossings and reset structure that lint cannot see.
Check | Needs | Catches |
|---|---|---|
Lint | Only the RTL | Truncation, latches, unused or undriven signals, naming and style breaks |
RDC | RTL plus clock/reset definitions and sequencing intent | Data transitions caused by independent resets, reset interactions and unsafe release into live receivers |
Both are design feedback, not noise. A finding is either a bug to fix or an intent to document.
Take two flops on the same clock, src_q and dst_q, with independent asynchronous resets. Same-clock timing covers changes that happen at a clock edge. An asserted source reset is not an edge: it changes src_q whenever it arrives, possibly just before the receiver samples. That is the case RDC looks for and lint cannot see.
The trace below runs the page's reset_crossing block. raw_dst_q is an unprotected receiver; safe_dst_q is the same receiver with an isolation clamp, iso, asserted before the source reset.
Each slot is one clock edge, numbered from 0: even slots are just after a rising edge (clk high), odd slots just after a falling edge (clk low).
Slots 1 and 2: iso asserts in the low phase, and the next rising edge loads the clamp, so safe_dst_q is 0 before anything is reset.
Slot 3: src_rst_n falls between edges and src_q drops at once. No clock edge caused it; in silicon this change can land inside the receiver's setup/hold window.
Slot 4: raw_dst_q captures the reset-induced 0. safe_dst_q never saw it.
Slots 5 to 8: reset releases in slot 5, a source edge restores src_q in slot 6, and only then does iso drop (slot 7), so safe_dst_q resumes with valid data in slot 8.
Find the crossing: a flop whose reset can assert while a receiver on a different reset is still live.
Choose the remedy from the architecture: coordinate the resets so the receiver is also in reset; or isolate the receiver before the source reset and hold the clamp until a source edge after release; or synchronise a single-bit level if latency and lost events are acceptable. Multi-bit data and events need a protocol, not per-bit synchronisers.
Show the evidence: a trace like the one above for the chosen sequence, plus an RDC run with the reset intent described. A reset-release synchroniser alone is not RDC signoff, because it does nothing about the asynchronous assertion.
The block and its self-checking bench:
module reset_crossing (
input logic clk, src_rst_n, dst_rst_n, isolate,
output logic src_q, raw_dst_q, safe_dst_q
);
always_ff @(posedge clk or negedge src_rst_n)
if (!src_rst_n) src_q <= 1'b0;
else src_q <= 1'b1;
// raw_dst_q intentionally illustrates an unprotected live receiver.
always_ff @(posedge clk or negedge dst_rst_n) begin
if (!dst_rst_n) begin
raw_dst_q <= 1'b0;
safe_dst_q <= 1'b0;
end else begin
raw_dst_q <= src_q;
safe_dst_q <= isolate ? 1'b0 : src_q;
end
end
endmodule
Rule names differ between tools, so the names in the table are illustrative; check that your tool has each rule.
Rule (illustrative) | Catches | Fix |
|---|---|---|
| An assignment that drops bits, such as a 9-bit sum assigned to 8 bits (carry loss) | Size the expression and keep the carry |
| A combinational output with no value on some path | Default assignments plus a full |
| A signal that is written but never read | Remove it, or connect the missing load |
| An asynchronous reset in logic meant to be synchronous | Synchronise, or waive with a rationale |
Real output for the two listings below, from Verilator with all warnings on, trimmed to the finding lines. The latch is reported twice, once as an incomplete case and once as a latch. The carry loss is not reported at all: 8-bit = 8-bit + 8-bit is sized to 8 bits, so it is not a width mismatch to this tool. Some commercial linters have a separate carry-loss rule; if yours does not, a review or a test has to catch it.
$ verilator --lint-only -Wall width_bug.sv
(no findings)
$ verilator --lint-only -Wall latch_bug.sv
%Warning-CASEINCOMPLETE: latch_bug.sv:7:5: Case values incompletely covered (example pattern 0x3)
%Warning-LATCH: latch_bug.sv:6:3: Latch inferred for signal 'y' (not all control paths of combinational always assign a value)Most findings have a one-line RTL fix. The two listings below trip the first two rules; the Testbench section shows the fixed pair. The fixed adder there is shrunk to 4 bits, with a separate carry output, so the bench can sweep every input pair quickly.
// bad-example: drops the carry (illustrative rule WIDTH-TRUNC)
module width_bug (
input logic [7:0] a_i,
input logic [7:0] b_i,
output logic [7:0] sum_o
);
assign sum_o = a_i + b_i;
endmodule// bad-example: missing select value infers a latch (illustrative rule LATCH-INF)
module latch_bug (
input logic [1:0] sel,
input logic [7:0] a, b, c,
output logic [7:0] y
);
always_comb begin
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
endcase
end
endmoduleResize the sum to preserve carry. For RDC, identify whether reset can change source data asynchronously while the receiver is live, then choose justified sequencing, isolation or a protocol-appropriate synchronizer. A release synchronizer does not solve every reset-domain data crossing; a waiver needs evidence for the actual architecture.
The cause is shared: a style habit, a macro, or a template. Fix the source once instead of touching ten sites.
The fixed adder and mux, plus the testbench that checks all 256 input pairs and all four select values.
module width_safe_add (
input logic [3:0] a_i,
input logic [3:0] b_i,
output logic [3:0] sum_o,
output logic carry_o
);
logic [4:0] full;
always_comb begin
full = {1'b0, a_i} + {1'b0, b_i};
sum_o = full[3:0];
carry_o = full[4];
end
endmodule
module latch_free_mux (
input logic [1:0] sel_i,
input logic [7:0] a_i, b_i, c_i, d_i,
output logic [7:0] y_o
);
always_comb begin
case (sel_i)
2'd0: y_o = a_i;
2'd1: y_o = b_i;
2'd2: y_o = c_i;
default: y_o = d_i;
endcase
end
endmodule
Isolation trace run with Icarus Verilog 13.0 (2026-10-16): prints PASS; the waveform is generated from the same block by Icarus Verilog 13.0. It checks digital sequencing only. Isolation-control timing, recovery and removal, reset-tree skew and metastability are not modelled.
Lint output from Verilator 5.048 (2026-10-17), --lint-only -Wall, trimmed to the finding lines: no finding for width_bug, CASEINCOMPLETE and LATCH for latch_bug. No RDC tool was run.
Fixed adder and mux testbench run with Icarus Verilog 13.0 (2026-10-16): prints PASS; it checks all 256 input pairs and all four select values. Rule names in the table are illustrative; check which rules your tool has.