A single IP block usually lives on one or two clocks and one reset, and its own testbench verifies that world completely. An SoC is nothing like that: it's a collection of blocks each with their own clock sourced from a different PLL or divider, and their own reset sourced from power-on logic, a watchdog, a debug controller, or software - and every wire that crosses from one block's clock or reset domain into another's is a place a bug can live that no single block's testbench could ever have seen.

This chapter moves to a part of SoC verification that has nothing to do with functional correctness in the usual sense - the data can be perfectly right and the chip can still fail because a signal crossed between two asynchronous clocks or two independently-controlled resets without the right protection. This article covers why that problem only really exists at the SoC level, and what verification has to do differently to catch it.

9 min read | Intermediate Level

What You'll Learn

  • Why clock and reset domain bugs are fundamentally an SoC-integration problem, not a block-level one
  • The basics of clock domain crossing (CDC) and why a synchronizer alone isn't always enough
  • What reset domain crossing (RDC) is, and why it's a distinct problem from CDC rather than a variant of it
  • Why this class of bug needs static and formal analysis, not just simulation

Why This Is an SoC-Level Problem

A block's own testbench typically drives it with one clock and one reset, exactly as it will see in some configurations of the real chip - so a well-verified block, in isolation, genuinely has no clock or reset domain crossing bugs to find. Those bugs don't exist yet. They come into existence the moment that block is wired up next to other blocks running on different clocks and different resets, which only happens once, at SoC integration. No amount of additional block-level testing catches this category of bug, because the crossing itself doesn't exist until integration creates it.

A typical SoC might have a dozen or more clock domains - a CPU cluster clock, a peripheral bus clock, a display or media clock, each potentially running at a different, unrelated frequency - and just as many reset domains, since power-on reset, a watchdog timeout, a debug-triggered reset, and a software-requested reset each typically affect a different subset of the chip. Every signal that has to cross from one of these domains into another is a candidate for a bug that is, by definition, an SoC-level concern.

Clock Domain Crossing: The Familiar Half of the Problem

When a signal from one clock domain is sampled by a flip-flop in another, unrelated clock domain, that sample can occasionally land right on the receiving clock edge, violating the flop's setup or hold time. The result is metastability: the flop's output hovers between valid logic levels for an unpredictable amount of time before settling, and there is no way to guarantee in advance which way it settles or how long it takes.

The standard mitigation is a two-flop synchronizer: the first flop may go metastable, but it's given a full clock cycle to resolve before a second flop samples its output, making the odds of the second flop also seeing an unresolved value vanishingly small. A single-bit control signal crossing a synchronizer this way is usually fine. A multi-bit bus is not - each bit can resolve at a slightly different time, so independent per-bit synchronizers can hand the receiving domain a value that never existed on the sending side. Multi-bit crossings need either a coding scheme where only one bit changes at a time, or a proper asynchronous FIFO, not a row of single-bit synchronizers.


// Basic two-flop synchronizer for a single-bit control signal
// crossing from clk_a's domain into clk_b's domain
module sync_2ff (
  input  logic clk_b,
  input  logic rst_b_n,
  input  logic async_in,     // asynchronous, from clk_a's domain
  output logic sync_out
);
  logic meta_ff;

  always_ff @(posedge clk_b or negedge rst_b_n) begin
    if (!rst_b_n) begin
      meta_ff  <= 1'b0;
      sync_out <= 1'b0;
    end else begin
      meta_ff  <= async_in;   // may go metastable
      sync_out <= meta_ff;    // one extra cycle to resolve
    end
  end
endmodule

Reset Domain Crossing: The Less Familiar Half

Reset domain crossing gets far less attention than CDC, but it's arguably the more dangerous of the two. It happens when a register in one reset domain feeds a register in a different reset domain - even if both registers share the same clock. If the source register's reset releases at a different time than the destination's, the destination can sample the source mid-transition and go metastable in exactly the way a clock crossing does.

What makes RDC worse than CDC is how far the damage can travel. A metastable value from a clock crossing gets cleaned up at the synchronizer and stops there. A metastable value caused by a reset crossing can propagate straight through an entire chain of registers that have no reset of their own, corrupting every stage until it finally reaches a register that does get reset - which, on a real design, can be a long way downstream. In the worst case, that corrupted value reaches a clock-gating enable, and the result isn't just wrong data anymore; it's an actual glitch on a clock signal.

A modern SoC's reset tree - power-on reset, watchdog reset, debug reset, per-power-domain software resets, each covering a different subset of logic - routinely ends up more complex than its clock tree. Reset domain crossing is a direct consequence of that complexity, and it needs its own dedicated analysis; CDC tools generally don't look at reset trees at all.

Reset Sequencing: A Bug Class of Its Own

Beyond individual crossings, SoCs stagger reset de-assertion across domains on purpose - releasing resets in a specific order so downstream logic doesn't see garbage from upstream logic that hasn't started yet. Getting that order wrong is its own class of bug: if a consumer block's reset releases before the producer block feeding it has come out of reset, the consumer can latch invalid data into a functional register before anything meaningful exists to give it. This has nothing to do with metastability - it's a straightforward sequencing bug that only shows up once multiple reset domains with a required ordering are integrated together.

Why This Needs Static and Formal Analysis, Not Just Simulation

Metastability is inherently probabilistic - a given crossing might behave correctly in a thousand simulation runs and still be structurally unprotected, because simulation doesn't model the analog behavior that makes metastability actually happen. Relying on simulation alone to find a missing synchronizer means relying on luck. The industry answer is the same exhaustive, structural approach already used for connectivity verification elsewhere in this series: static CDC and RDC tools trace every crossing in the design and flag any one that lacks a proper synchronizer, independent of whether a test ever happened to exercise it, and formal tools can prove reset ordering and connectivity properties directly. Simulation still matters here, but mainly to confirm functional consequences - X-propagation-aware simulation, in particular, is what actually catches a reset sequencing bug turning into corrupted functional data.

Common Mistakes

Assuming a block that passed CDC checks at its own level is automatically clean at SoC level.

A block-level CDC run only knows about the clock domains that block's own testbench exercises. New crossings appear the moment that block's outputs feed a different domain at integration, and those have to be re-analyzed at the SoC level specifically.

Running CDC analysis and treating reset domain crossing as covered by the same check.

RDC is a structurally different problem that generally needs its own dedicated tool and its own trace through the reset tree, not just the clock tree. Skipping it leaves an entire bug class unchecked.

Verifying that every reset asserts correctly but not the order domains de-assert in.

Reset assertion is usually the easy, well-tested half. The de-assertion order across domains, especially under a staggered or phased reset scheme, is where sequencing bugs actually hide.

Summary

In this article, you learned:

  • Clock and reset domain crossings don't exist at the block level - they are created by SoC integration, and no block-level testbench can find them
  • Clock domain crossing risks metastability at every asynchronous boundary, mitigated with two-flop synchronizers for single bits and proper coding or async FIFOs for multi-bit buses
  • Reset domain crossing is a distinct, often more damaging problem, since a metastable value from a bad reset crossing can propagate through an entire chain of reset-less registers
  • Reset sequencing across domains - the order resets de-assert in, not just whether they assert correctly - is its own bug class unique to SoC integration
  • Because metastability is probabilistic, this category of bug needs static and formal analysis to find structurally, not simulation alone
Did you find what you were looking for?

Quiz

No quiz questions available for this article.

Cron Job Starts