There's a persistent tension in hardware verification teams between the growing capability of automated analysis tools and the irreplaceable judgment that comes from a human reading RTL with the full context of a design intent. The mistake is treating these as alternatives. Static analysis tells you what the code does. Code review tells you whether the code does what it's supposed to do.
After building tooling that sits directly in the RTL review workflow, we've developed opinions about what a well-structured RTL code review actually looks like — and where automated pre-checks belong in that process.
Let Automated Checks Run Before the Review Starts
The single highest-leverage change most hardware teams can make to their review process costs nothing in reviewer time: run lint and static analysis before the review is even assigned. Not as a suggestion — as a prerequisite. If there are unwaived lint warnings or CDC topology errors in the submission, the review hasn't started yet. The author needs to address them first.
This sounds obvious, but it's violated constantly. Reviewers end up spending 20 minutes of attention on whether a particular sensitivity list is complete, or whether a signal assignment needs a default, when a lint pass would have flagged those in seconds. That time is not recoverable. The reviewer's cognitive load budget for the session has been partially spent on machine-detectable problems, leaving less capacity for the design-intent and architecture questions that actually require human judgment.
The discipline here is treating the automated check output as context for the reviewer, not just noise for the author. A reviewer who can see "this submission has 0 new lint violations and 1 waived CDC warning with an explanation" is in a different position than one who has no idea what the analysis found. At Fpgawright, the PR annotation flow we built does exactly this: it surfaces the static analysis summary at the top of the review, so reviewers know the mechanical checks are clean before they start reading.
What Reviewers Should Actually Be Looking For
Given that lint and structural analysis are handling the pattern-detectable violations, what should the human reviewer focus on? The categories that require genuine design intent knowledge and that automated tools handle poorly.
Reset strategy coherence: Does the reset behavior match the specification? Asynchronous vs. synchronous reset choices have timing implications that a lint tool won't validate against the design intent. Neither will a tool flag that a particular module uses asynchronous reset while the rest of the design uses synchronous — unless that inconsistency is explicitly configured as a violation class. A reviewer familiar with the SoC reset domain architecture catches this immediately.
Power intent alignment: For designs with multiple power domains, does the RTL reflect the UPF intent? Power gating logic, retention registers, and isolation cells are areas where a naive RTL reading looks fine but violates the power domain spec. This requires a reviewer who has read the power intent document alongside the RTL changes.
Parameter propagation: Are parameterized designs passing width, depth, and mode parameters correctly through the module hierarchy? Mismatched parameter values that happen to synthesize without error are a known source of functional bugs. A reviewer familiar with the parameterization conventions in the codebase spots these; static analysis often doesn't have enough cross-module context.
Spec coverage: Does the change implement all the conditions specified in the design document, including corner cases? This is entirely outside the scope of structural analysis. A reviewer should be asking "what happens if the FIFO is full when a write request arrives?" not because a tool told them to, but because the design spec documents that condition and the RTL needs to handle it.
The Clock and Reset Topology Review
One area where human review and automated analysis overlap productively: clock and reset topology. Static CDC analysis tells you about structural crossing issues. But it doesn't validate whether the synchronizer strategy matches the design intent — whether a two-flop synchronizer is appropriate, whether a handshake protocol should be used instead, or whether the crossing is truly asynchronous in the operational mode being reviewed.
We treat CDC review as a two-pass activity in our own codebase. Pass one is the automated structural check, which runs in CI. Pass two is the human review, which reads the CDC waivers and asks whether the waiver rationale is sound. A waiver that says "this crossing is safe because the signals are quasi-static in all operating modes" requires the reviewer to verify that the operating mode analysis is correct — not to take the waiver comment at face value.
The discipline of reviewing waivers carefully catches a specific failure mode that shows up in teams with mature lint setups: accumulated waivers that were valid when they were written but become invalid as the design evolves. A signal that was truly quasi-static in the original architecture becomes an active crossing after a state machine change three months later, but the waiver hasn't been updated. That kind of drift is invisible to static analysis without an explicit re-check, but visible to a reviewer who reads the waiver and checks it against the current design state.
Structuring Review Comments to Match Severity
RTL review comments often conflate must-fix issues with preferences and questions. This creates friction because the author has to read through the entire comment thread to understand what's blocking merge and what's just discussion. A simple severity tagging convention — blocking, suggestion, question — reduces this friction substantially and makes it easier to see at a glance whether a review pass is clean.
Blocking comments are the ones that, if not addressed, indicate the change is incorrect or incomplete. Suggestions are improvements that should be discussed but aren't necessarily blocking. Questions are requests for clarification that might or might not lead to a change. Mixing all three into undifferentiated comments forces both the author and the reviewer to hold the merge status in their heads rather than reading it from the comment structure.
We're not saying this is the only way to structure review comments. But teams that adopt some variant of this convention consistently report shorter review cycle times, because the author doesn't need to ask "is this blocking?" for every comment they receive.
Handling Complexity: When to Ask for a Redesign
One thing reviewers consistently struggle with is calling for a redesign when the RTL is technically functional but structurally problematic. It feels like a harsh judgment. But accepting structurally complex RTL because it passes the current testbench is exactly how teams accumulate technical debt that costs a disproportionate share of verification cycles in later blocks.
The cases where a redesign request is warranted: deeply nested conditional logic with no clear default path (synthesis can handle it; verification can't cover it); module interfaces that return different data widths depending on a mode bit that isn't in the port list; control FSMs that have grown to cover functional behaviors that belong in separate modules. These aren't lint violations. They're architecture problems that show up as maintainability and coverage holes over time.
A reviewer asking for a redesign isn't overreaching. It's applying the judgment that distinguishes a hardware code review from a structural scan. The automated tools we build handle the structural questions. The review handles the design intent questions. Both are necessary. Neither replaces the other, and understanding which category a comment belongs to keeps the review process productive rather than redundant.
The teams that get the most out of combining static analysis with structured review are the ones that treat the automated output as homework that the author does before showing up to the review meeting, not as a substitute for the meeting itself.