Reset Domain Problems
The last two articles were about bugs you can find in one module. This one is about a bug that does not exist in any one module.
Module A passes every one of its unit tests. Module B passes every one of its unit tests. Wire them together at the top level and the integrated design fails, intermittently, around power-up. Neither module’s tests could catch it, because the bug lives in the relationship between the two, not in either alone.
Reset release is a timing event
The reset strategy article in Section 8.1 established that a project needs one consistent polarity and one assert/release discipline. Here is the sharper reason release timing matters: when a shared reset de-asserts, it does not reach every flip-flop at the same instant. Wire delay, clock tree skew and synchronizer stages (if the reset crosses clock domains) mean different parts of a design come out of reset on different clock edges, even when nominally driven by the same reset.
In Figure 1, module A comes out of reset after two edges of its fast clock, while module B’s synchronizer needs two edges of its slow clock. If A starts driving meaningful outputs the moment it is out of reset, B is still mid-reset and can capture garbage on its first active edges, before its own state has settled to what its reset was supposed to establish.
The failure patterns
Asymmetric reset release across clock domains. Exactly Figure 1: two modules on different clocks sharing one reset source, releasing at different times because of independent synchronizer chains. The fix is architectural: either hold every domain in reset until all domains’ synchronizers have released (a coordinating reset structure), or design the inter-module protocol so it does not assume both sides are out of reset together. A valid/ready handshake naturally tolerates one side coming up later, since it does not proceed until both are ready.
A downstream module assuming an upstream module’s outputs are valid immediately after reset. Even within one clock domain, if A’s outputs take a few cycles after reset release to become meaningful (its pipeline needs to fill, per Section 7.3), a B that trusts A’s outputs from the first post-reset cycle captures transient data. The fix is an explicit “outputs valid” signal from A rather than an undocumented assumption about how many cycles A needs.
A reset that is supposed to be global, but one module quietly has its own local override. A test-mode or debug reset that can hold a module in reset, or release it, regardless of the shared system reset. Reasonable on its own, and a real risk if the rest of the system does not know the override exists.
The integration checklist
Before trusting a multi-module design’s reset behaviour, walk through this rather than assuming the unit tests covered it:
- Does every clock domain have its own properly synchronized reset release, per the two-flop pattern from Section 7.5 applied to the reset, not a raw reset wired across a clock boundary?
- Do inter-module protocols tolerate asymmetric reset release, either through a valid/ready handshake or a coordinated release structure that holds every domain until all are ready?
- Does every module document how many cycles after its reset release its outputs become meaningful, and does every consumer respect that, rather than treating “reset released” and “outputs valid” as the same moment?
- Are there local reset overrides (test mode, debug) that could put a module out of step with the rest of the system, and are they documented at the top level?
Reset domain bugs are notorious for being intermittent in a specific way: they depend on the relative timing between reset release and the first active clock edges after it, which can shift between simulation runs (if the testbench randomises reset sequencing) or between silicon samples (process variation affecting gate delays). A bug that “only fails sometimes” in the lab, or “only on some units”, is a strong pointer at this class rather than at a deterministic logic error.
Practice Exercise
Two modules share a reset but run on independent clocks, clk_a at 100 MHz and clk_b at 33 MHz. Module A asserts data_valid to module B from its first post-reset edge. Module B’s reset synchronizer takes 2 cycles to release. Explain the risk and propose one architectural fix.
Solution
Because B’s synchronizer takes 2 clk_b cycles, B’s logic is held in reset for a real window after the shared reset has de-asserted at the source. If A asserts data_valid and data on its first post-reset clk_a edge, and clk_a runs about 3 times faster than clk_b, several clk_a cycles of “valid” data are presented before B has finished coming out of reset and can process anything. One fix: A holds data_valid low until it receives an explicit ready from B (a handshake, in the spirit of the request/acknowledge pattern in Section 7.5), rather than assuming its own reset release has any fixed relationship to B’s. That removes the dependence on relative reset timing, replacing an implicit timing assumption with an explicit protocol.
Reset release is a timing event, not an instantaneous flip, and different parts of a design, especially across clock domains where the reset itself needs two-flop synchronization, come out of reset at different times even from one source. The resulting bugs are invisible to unit tests and often intermittent in ways that look like flaky hardware. The fix is almost always architectural: explicit handshakes and documented “outputs valid” contracts rather than assumptions about relative timing. In the next article we look at multi-driver conflicts, another integration-time bug, this time two things trying to drive the same wire.