-
Notifications
You must be signed in to change notification settings - Fork 435
Description
Background
Today, ltl.delay clock handling is inconsistent across layers:
- SVA allows clocked and unclocked
##Ndelays (clock from@(...)or default clocking context). - FIRRTL
circt_ltl_delaycurrently has no explicit clock operand and can rely on surrounding context (e.g.,circt_ltl_clock). - CIRCT lowering/emission currently requires special handling to recover/propagate clock context.
This mismatch leads to implicit clock inference spread across passes and conversions, making behavior hard to reason about and review.
Core Problem
We currently rely on implicit clock inference in multiple places.
The long-term direction is to make delay clock semantics explicit and uniform, while still preserving compatibility during migration.
Candidate Implementation Strategies
-
Aggressive full switch to explicit delay clocks (single large change)
- Pros: direct end-state.
- Cons: large review surface; placeholder-clock semantic issues.
-
Optional clock on
ltl.delay(incremental transition)- Pros: good intermediate compatibility; smaller, reviewable changes.
- Cons: temporary mixed mode (clocked + unclocked delay).
-
Two ops coexisting (
delay+clocked_delay)- Pros: explicit separation.
- Cons: duplicated infrastructure and migration overhead.
Here use Option 3 as the intermediate path, then converge to the explicit-clock end state.
Design
ltl.delay (unclocked)
%result = ltl.delay %input, <delay> [, <length>] : <type>
Pure sequence delay with no clock. Clock is provided externally by an enclosing ltl.clock operation or resolved by the InferLTLClocks pass.
ltl.clocked_delay (explicitly clocked)
%result = ltl.clocked_delay %clk, <edge>, %input, <delay> [, <length>] : <type>
Self-contained clocked delay. The clock and edge parameters are mandatory. Produced by:
InferLTLClockspass (from unclockedltl.delay+ enclosingltl.clockcontext)ImportVerilog(when assertion clock context is present)- Direct construction in frontends with explicit clock intent
Clock Lifecycle
ltl.clock and ltl.clocked_delay serve different roles at different pipeline stages:
- Before
InferLTLClocks:ltl.clockbroadcasts a clock to a subtree of delays;ltl.clocked_delaycarries its own clock. Both may coexist. InferLTLClocks: Convertsltl.delayunderltl.clocktoltl.clocked_delay, then eliminates theltl.clockop. Clones subtrees for multi-clock fan-out.- After: Only
ltl.clocked_delay(explicit clock) and unresolvedltl.delay(warning) remain. Noltl.clock. - ExportVerilog: Extracts clock from
clocked_delay, hoists to SVA@(edge clk), emits delays as##N.
InferLTLClocks pass
- Propagates clock/edge from
ltl.clockto unclockedltl.delay, producingltl.clocked_delay. - Removes the
ltl.clockop — clock now lives solely onclocked_delay. - Clones delay subtrees for multi-clock fan-out.
- Warns on unresolved
ltl.delay(no enclosing clock).
Implementation Plan (3 PRs)
PR 1: LTL IR foundation — ClockedDelayOp + fold infrastructure
- Pure LTL dialect changes. No conversions, no passes.
This is the foundational PR that all others depend on.
PR 2: InferLTLClocks pass
- New standalone pass + registration infrastructure.
PR 3: ExportVerilog + ImportVerilog adaptation
- Update both Verilog conversions to handle dual-op model.
This RFC was discussed by me and @Claude-opus-4.6, and reviewed by myself