Skip to content

[circt-bmc] Add multi-clock BMC support#9729

Closed
robert-at-pretension-io wants to merge 5 commits intollvm:mainfrom
robert-at-pretension-io:bmc-multi-clock-pr
Closed

[circt-bmc] Add multi-clock BMC support#9729
robert-at-pretension-io wants to merge 5 commits intollvm:mainfrom
robert-at-pretension-io:bmc-multi-clock-pr

Conversation

@robert-at-pretension-io
Copy link

@robert-at-pretension-io robert-at-pretension-io commented Feb 22, 2026

Implement robust multi-clock BMC support to enable formal verification of Clock Domain
Crossing (CDC) logic and asynchronous clock drift.

Core Changes:

  • Async Modeling: LowerToBMC now defaults to independent clock toggling via
    verif.symbolic_value, allowing Z3 to mathematically explore all asynchronous
    interleavings.
  • SSA-First Tracking: Uses the new verif.clocked_by op to associate registers with
    clocks, replacing fragile port-index tracking and ensuring stability during IR
    passes.
  • Compatibility: Adds a --sync-clocks flag to both circt-opt and circt-bmc to restore
    legacy lockstep behavior.

Validation:

  • cdc-gray-counter.mlir: Proves the async model catches a subtle Gray counter
    protocol failure (multi-bit jumps) that is "invisible" to the synchronous model.
  • multi-clock-bmc.mlir: Verifies end-to-end pipeline support for multiple clock
    inputs.
  • externalize-registers.mlir: Confirms correct hoisting and mapping of registers in
    multi-clock designs.

This PR removes the single-clock restriction from ExternalizeRegisters and LowerToBMC
and updates VerifToSMT to handle the resulting SMT lowering.

Remove the single-clock restriction from ExternalizeRegisters and
LowerToBMC. The ExternalizeRegisters pass already handles each register
independently via regOp.getClk(), so the foundClk guard that rejected
multiple clock ports was unnecessary.

LowerToBMC now counts all top-level clock inputs (numClocks) and
generates one init/loop entry per clock: each clock is initialized to 0
(false) and toggled every step via seq.from_clock → comb.xor → seq.to_clock.
Struct-embedded clocks are still rejected with a clear diagnostic.

Tests:
- Remove the two-clock error case from externalize-registers-errors.mlir
- Update lower-to-bmc-errors.mlir: update struct-clock error message
- Add two_clk_regs positive case to externalize-registers.mlir
- Add multi-clock-bmc.mlir: end-to-end pipeline test for a two-clock
  design verifying init yields 2 clocks and loop toggles each independently
Copy link
Contributor

@TaoBi22 TaoBi22 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @robert-at-pretension-io, thanks for taking a look at this! I'm not certain this is exactly the right approach to tackle this though. The commenting in this PR claims that by toggling each clock in the loop region, we expore all possible interleavings. I don't agree with this - this means that we toggle every clock at the same time, which could potentially be a desired behaviour but certainly should not be the default (as it only explores one, fairly unlikely interleaving). If this is a behaviour you have a use-case for, then it's obviously worth supporting, but I'd suggest we probably want to have that be enabled by a flag.

Probably worth noting that a remaining challenge for multiple clock support when the clocks are toggled separately is that we currently don't have a way to identify which externalized registers are clocked by which values. Previous discussions on this came to the conclusion that we might want some kind of clocked_by verif operation to annotate outputs with their corresponding clock value (since we can't refer to an SSA value in an attribute)

…erif.clocked_by

- Replaced fragile integer attribute tracking with verif.clocked_by SSA values.
- Replaced synchronous clock toggling with independent verif.symbolic_value toggling in LowerToBMC to explore asynchronous CDC interleavings.
- Added --sync-clocks flag for legacy synchronous behavior.
- Added multi-clock integration tests.
@robert-at-pretension-io
Copy link
Author

@TaoBi22 Thanks for the feedback. I'm very excited to be a part of this project :)

Please let me know if this implementation is better suited, always willing to make changes!

@TaoBi22
Copy link
Contributor

TaoBi22 commented Feb 24, 2026

Thanks for taking another pass @robert-at-pretension-io - it looks like there are a few separate things happening here now, so it might make sense to split them across PRs so we don't have multiple unrelated changes at once (makes reviewing a bit easier for me!). Might be easiest to stick to the flag to toggle all clocks together in this PR (if this is something you have a use case for), then add the additional ops/lowerings/changes in later PRs?

@robert-at-pretension-io
Copy link
Author

I am closing this PR to keep the commit history clean, and I have opened a fresh, streamlined PR with
the new architecture here: #9803 . Thank you for the guidance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants