Reset Strategy Consistency
The previous article was about names. This one is about the one convention where inconsistency is a functional risk rather than a review-time cost.
You know from Section 5.5 how to build a correct reset: synchronous or asynchronous, active-high or active-low, with a synchronizer for the async-assert, sync-release pattern. The problem here is different. Every individual reset in the design is built correctly, but the project as a whole never agreed which approach to use, or where one reset domain ends and another begins.
Mixing polarity is a functional risk
Picture a design where most modules use rst_n (active-low, per the previous article) but one module, written by an engineer used to a different codebase, uses rst (active-high). Both are internally correct. The danger appears the moment they share a reset at the top level:
// Top-level module has an active-low reset input
module soc_top (
input wire clk, rst_n,
...
);
// Module A expects active-low: wire it directly
module_a u_a (.clk(clk), .rst_n(rst_n), ...);
// Module B expects active-HIGH: someone has to remember to invert
module_b u_b (.clk(clk), .rst(~rst_n), ...); // easy to get backwards, or simply forget
That ~rst_n is a small detail sitting at exactly the integration boundary nobody looks at closely. Forgetting it, or getting the polarity backwards, does not produce a compile error. It produces a module held permanently in reset, or one that never resets at all, either of which looks like an unrelated bug during bring-up and sends the debugging effort in the wrong direction.
The fix is not clever wiring. It is a project-wide rule, decided once: one reset polarity for the whole project, documented, never deviated from. Active-low is the more common industry default, for the CMOS cell reason in Section 5.5, but the choice matters less than everyone following the same one.
Reset domains: when several resets are correct
Not every design should have exactly one reset. Some need several, independently controlled, and that is an architectural decision rather than an inconsistency. The usual reasons:
- Different clock domains usually get their own synchronized reset, following the reset synchronizer pattern from Section 5.5. A reset asserted in one domain still needs its release synchronized into every other domain it affects, using the two-flop discipline from Section 7.5.
- Independently power-gated domains (Section 7.6) need their own reset, since a domain waking from sleep has to reset its own logic on its own schedule, independent of domains that stayed powered.
- IP blocks with their own reset requirements. A third-party memory controller or PHY often specifies its own reset sequencing, which has to be respected even if it does not match the project convention.
The distinction that matters: three deliberate, documented reset domains is a sound architecture. Three reset domains that exist because three engineers each decided independently how reset should work, with no coordination and no record of why the boundaries are where they are, is a bring-up bug waiting to happen.
Documenting the strategy
A one-page reset strategy document, referenced from every module’s header comment, is worth more than it sounds:
Project Reset Strategy (see docs/reset_strategy.md):
- All modules use active-low reset: rst_n
- Assertion: asynchronous (immediate response to external reset events)
- Release: synchronous, via the shared sync_2ff-based reset synchronizer
in common/reset_sync.v; every clock domain instantiates its own copy
- Reset domains: 1 per clock domain (see clock_domains.md for the list)
- Power-gated blocks: additional reset re-assertion on wake, sequenced
by the power management FSM (see Module 7, Section 7.6)
Each module’s header can then point at this document rather than re-explaining reset locally. Any module whose reset behaviour deviates from the default (a third-party block with its own sequencing, say) should call that out explicitly, right where a reviewer or integrator will see it.
A subtler version of the same problem shows up within a single module: if (!rst_n) in one always block and if (rst) elsewhere in the same file, on a different signal that also happens to exist, perhaps a test-mode reset. Each check may be correct for its own signal, but a reviewer skimming the file and pattern-matching on “reset checks look like this” can easily misread one for the other. When a module needs more than one reset-like signal, name them so the difference cannot be missed: rst_n and test_mode_rst are safer neighbours than rst_n and rst.
Practice Exercise
A project’s reset strategy specifies active-low, asynchronous-assert, synchronous-release reset for every module. You are reviewing a new module that contains always @(posedge clk) if (rst) q <= 0; else q <= d;. Identify every way it deviates from the documented strategy.
Solution
Two deviations. First, polarity: if (rst) checks for active-high, while the standard is active-low (rst_n, checked as if (!rst_n)). This module’s reset would need inverting or renaming to integrate, exactly the easy-to-miss detail the top-level wiring example warned about. Second, and more subtly, this reset is entirely synchronous: the sensitivity list contains only posedge clk, with no negedge rst_n term, while the strategy specifies asynchronous assertion. A reset that only takes effect on a clock edge will not reset the design at power-on if the clock is not yet running or stable, the scenario the async-assert half of the standard pattern exists for. Both should be flagged, even though the module’s reset logic is, in isolation, correct Verilog.
Naming inconsistency costs review time; reset inconsistency can cost a design that silently fails to come out of reset at integration, in a way that looks like an unrelated bug. The fix is one documented reset strategy: one polarity, one assert and release discipline, and domain boundaries that are decisions rather than accidents. In the next article we look at the broader category this is a special case of, the RTL patterns that make simulation and synthesized silicon quietly disagree.