Skip to content

Conversation

Clo91eaf
Copy link
Contributor

@Clo91eaf Clo91eaf commented Sep 26, 2025

This pr partially implement the issue #8673

LTL Dialect Explicit Clocking Model Refactoring

Problem and Motivation

The original LTL dialect uses implicit clock inference, violating IR locality principles. The ltl.clock operation creates non-local dependencies, hindering safe transformations.

Type Hierarchy

i1 --> sequence ------------> property
 |        |                      ↑
 |        |                       |(property level op)
└-------┴-> clocked_sequence --┘

Refactoring Principles

Input Unification: All clocked_* operations accept LTLClockedSequenceType as input

Result Clock Rules:

  • Multi-operand operations: Explicitly specify ClockEdgeAttr and clock parameters
  • Unary operations: Clock inherited from input
  • Property operations: Lift to clock-agnostic LTLPropertyType

Key Changes:

  • ltl.delayltl.clocked_delay: From input-dependent to standalone delay object
  • ltl.create_clocked_sequence: Create clocked sequence instead of adding a clock to sequence

Compatibility

  • Preserve legacy sequence operations for Immediate Assertion support
  • Enable new-old interoperability through ltl.create_clocked_sequence
  • Property-level methods' operand generics should add ClockedSequence in the next commit

Implementation roadmap:

  1. Implement clocked_* methods
  2. Add ClockedSequence to operand generics of property-level methods (ltl.implication, ltl.until, ltl.eventually, ltl.not)
  3. Implement LTLToCore lowering for clocked sequence
  4. clocked_sequence would be rename to sequence, and sequence would be rename to unclocked_sequence
  5. Add tests and complete documentation
Old Operation Operands Results New Operation Operands Results
ltl.clock input: LTLAnyPropertyType
edge: ClockEdgeAttr
clock: I1
LTLSequenceOrPropertyType ltl.create_clocked_sequence input: LTLAnySequenceType
edge: ClockEdgeAttr
clock: I1
LTLClockedSequenceType
ltl.and inputs: Variadic<LTLAnyPropertyType> LTLAnyPropertyType ltl.clocked_and inputs: Variadic<LTLClockedSequenceType>
edge: ClockEdgeAttr
clock: I1
LTLClockedSequenceType
ltl.or inputs: Variadic<LTLAnyPropertyType> LTLAnyPropertyType ltl.clocked_or inputs: Variadic<LTLClockedSequenceType>
edge: ClockEdgeAttr
clock: I1
LTLClockedSequenceType
ltl.intersect inputs: Variadic<LTLAnyPropertyType> LTLPropertyType ltl.clocked_intersect inputs: Variadic<LTLClockedSequenceType>
edge: ClockEdgeAttr
clock: I1
LTLClockedSequenceType
ltl.concat inputs: Variadic<LTLAnySequenceType> LTLSequenceType ltl.clocked_concat inputs: Variadic<LTLClockedSequenceType>
edge: ClockEdgeAttr
clock: I1
LTLClockedSequenceType
ltl.delay input: LTLAnySequenceType
delay: I64Attr
length: OptionalAttr<I64Attr>
LTLSequenceType ltl.clocked_delay input: LTLClockedSequenceType
edge: ClockEdgeAttr
clock: I1
delay: I64Attr
length: OptionalAttr<I64Attr>
LTLClockedSequenceType
ltl.repeat input: LTLAnySequenceType
base: I64Attr
more: OptionalAttr<I64Attr>
LTLSequenceType ltl.clocked_repeat input: LTLClockedSequenceType
base: I64Attr
more: OptionalAttr<I64Attr>
LTLClockedSequenceType
ltl.goto_repeat input: LTLAnySequenceType
base: I64Attr
more: I64Attr
LTLSequenceType ltl.clocked_goto_repeat input: LTLClockedSequenceType
base: I64Attr
more: I64Attr
LTLClockedSequenceType
ltl.non_consecutive_repeat input: LTLAnySequenceType
base: I64Attr
more: I64Attr
LTLSequenceType ltl.clocked_non_consecutive_repeat input: LTLClockedSequenceType
base: I64Attr
more: I64Attr
LTLClockedSequenceType

✅ indicates retention, ❌ indicates removal

@Clo91eaf Clo91eaf marked this pull request as draft September 26, 2025 12:39
@Clo91eaf Clo91eaf force-pushed the sva_clock branch 2 times, most recently from d18a2d2 to 6689f89 Compare September 27, 2025 07:35
@Clo91eaf Clo91eaf changed the title [LTL][SVA] Add explict clocked operations and types [LTL] Add explict clocked operations and types Sep 27, 2025
@Clo91eaf Clo91eaf force-pushed the sva_clock branch 2 times, most recently from c31983e to 175dc96 Compare September 28, 2025 07:29
- Introduced new clocked operations: ClockedAndOp, ClockedOrOp, ClockedIntersectOp, CreateClockedSequenceOp, ClockedDelayOp, ClockedConcatOp...
- Added LTLClockedSequenceType to represent clocked sequences.
- Updated LTLTypes and LTLVisitors to support new clocked operations.
@Clo91eaf
Copy link
Contributor Author

@fabianschuiki Sorry! Some things caused this PR to be late. Now I have time to implement #8673, wdyt about this PR?

Comment on lines +131 to +163
def ClockedAndOp : LTLOp<"clocked_and", [Pure, Commutative]> {
let arguments = (ins
Variadic<LTLClockedSequenceType>:$inputs,
ClockEdgeAttr:$edge,
I1:$clock);
let results = (outs LTLClockedSequenceType:$result);
let assemblyFormat = [{
$edge $clock `,` $inputs attr-dict
}];
let hasCanonicalizeMethod = 1;

let summary = "A conjunction of explicitly clocked sequences with a specified clock domain.";
let description = [{
See `ltl.and` op. $edge and $clock specify the clock domain for the result.
}];
}

def ClockedOrOp : LTLOp<"clocked_or", [Pure, Commutative]> {
let arguments = (ins
Variadic<LTLClockedSequenceType>:$inputs,
ClockEdgeAttr:$edge,
I1:$clock);
let results = (outs LTLClockedSequenceType:$result);
let assemblyFormat = [{
$edge $clock `,` $inputs attr-dict
}];
let hasCanonicalizeMethod = 1;

let summary = "A disjunction of explicitly clocked sequences with a specified clock domain.";
let description = [{
See `ltl.or` op. $edge and $clock specify the clock domain for the result.
}];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do and/or operations require a clock? These oparations sound like they only take two sequences and performa a boolean and/or on them. But if those sequences contain any delays, those delays would already specify a clock. Wouldn't that suffice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do and/or operations require a clock? These oparations sound like they only take two sequences and performa a boolean and/or on them. But if those sequences contain any delays, those delays would already specify a clock. Wouldn't that suffice?

The clock in the operand is mainly used to specify the clock of the result type, because when two operands are in different clock domains, the clock of the result becomes difficult to determine

Comment on lines +182 to +215
def ClockedDelayOp : LTLOp<"clocked_delay", [Pure]> {
let arguments = (ins
LTLAnySequenceType:$input,
ClockEdgeAttr:$edge,
I1:$clock,
I64Attr:$delay,
OptionalAttr<I64Attr>:$length);
let results = (outs LTLClockedSequenceType:$result);
let assemblyFormat = [{
$edge $clock `,` $input `,` $delay (`,` $length^)? attr-dict `:` type($input)
}];

let summary = "Create an explicitly clocked delay sequence from i1 or sequence.";
let description = [{
Creates a standalone "pure delay" sequence that is explicitly bound to a
specific clock. This sequence evaluates to true immediately and matches
after the specified number of clock ticks.

The `$input` must be an explicitly clocked sequence. The `$delay` specifies
the number of clock cycles to delay, and the optional `$length` specifies
the range of cycles during which the delay can match. Omitting `$length`
indicates an unbounded but finite delay.

Examples:
- `ltl.clocked_delay posedge %clk, %seq, 2, 0` creates a delay that matches
exactly 2 cycles after the current time on the positive edge of %clk.
- `ltl.clocked_delay posedge %clk, %seq, 2, 2` creates a delay that matches
2, 3, or 4 cycles after the current time.
- `ltl.clocked_delay posedge %clk, %seq, 2` creates an unbounded but finite
delay of 2 or more cycles.

This operation enables clean lowering of SVA expressions like `##1 b`
}];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It's very cool that we get a properly clocked delay operation! Could we just name this ltl.delay and delete the old unclocked ltl.delay? I don't think we need both ops.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's very cool that we get a properly clocked delay operation! Could we just name this ltl.delay and delete the old unclocked ltl.delay? I don't think we need both ops.

Yes! This is a good suggestion, in fact I'm not sure if break changes should be made in this PR, if you think it's okay then it would be a good idea to replace the delay entirely

Comment on lines +271 to +288
def ClockedConcatOp : LTLOp<"clocked_concat", [Pure]> {
let arguments = (ins
Variadic<LTLClockedSequenceType>:$inputs,
ClockEdgeAttr:$edge,
I1:$clock);
let results = (outs LTLClockedSequenceType:$result);
let assemblyFormat = [{
$edge $clock `,` $inputs attr-dict
}];
let hasFolder = 1;
let hasCanonicalizer = 1;

let summary = "Concatenate explicitly clocked sequences with a specified clock domain.";
let description = [{
See `ltl.concat` op. $edge and $clock specify the clock domain for the result.
}];
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Same as with the and/or operation: ltl.concat probably doesn't need to know what clock the input sequences are sensitive to. The concat corresponds to a ##0 b in Verilog, which means a and b happen at the exact same time, not a specific clocked delay apart. And since there's no delay, the op probably doesn't need a clock 🤔

Comment on lines +31 to 67
def LTLClockedSequenceType : LTLTypeDef<"ClockedSequence", "clocked_sequence"> {
let summary = "LTL clocked sequence type";
let description = [{
The `ltl.clocked_sequence` type is the fundamental type for representing
temporal sequences in linear temporal logic, for example, *"on the rising
edge of clk, A is true two cycles after B is true"*.

This type intrinsically carries its clocking context, explicitly binding a
temporal sequence of events to a specific clock. Operations like ltl.concat,
ltl.and, and ltl.or operate on `ltl.clocked_sequence` operands, ensuring
all sequence operands share the same clock. This makes clock domains
explicit and type-checked, preventing errors and clarifying clock domains
for analysis passes.

Boolean inputs (`i1`) are implicitly lifted to a zero-length clocked
sequence. Operations that accept a clocked sequence as an operand will use
the `AnyClockedSequence` constraint, which also accepts `i1`.
}];
}

def LTLPropertyType : LTLTypeDef<"Property", "property"> {
let summary = "LTL property type";
let description = [{
The `ltl.property` type represents a verifiable property built from linear
temporal logic sequences and quantifiers, for example, *"if you see sequence
A, eventually you will see sequence B"*.

Note that this type explicitly identifies a *property*. However, a boolean
value (`i1`) or a sequence (`ltl.sequence`) is also a valid property.
Operations that accept a property as an operand will use the `AnyProperty`
constraint, which also accepts `ltl.sequence` and `i1`.
The `ltl.property` type represents a clock-agnostic, verifiable property
built from explicitly clocked sequences and quantifiers, for example, *"if
you see sequence A, eventually you will see sequence B"*.

This type is fundamentally clock-independent - it represents a quantified
statement about explicitly clocked sequences rather than being a clocked
entity itself. The "lifting" from clocked sequences to clock-agnostic
properties occurs via property operators like `ltl.implication`,
`ltl.always`, and `ltl.eventually`.

Properties can describe relationships between temporal patterns that may
exist in different clock domains, enabling cross-domain verification.
}];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to distinguish between these two? If we have all clocked operations (basically just delay, repeat, and some of the goto stuff I'd guess) have an explicit clock, and all other operations are clock agnostic (because they don't need a clock to express their semantics), I think all of that would just be a plain old !ltl.property. I don't think we need an explicit distinction betweem clocked and unclocked here 🤔. When we emit things to Verilog, we can just scan the LTL operations to find all the clocks used, and insert @(posedge clock) around all the LTL ops that share the same clock 🤔

Copy link
Contributor Author

@Clo91eaf Clo91eaf Oct 2, 2025

Choose a reason for hiding this comment

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

Do we need to distinguish between these two? If we have all clocked operations (basically just delay, repeat, and some of the goto stuff I'd guess) have an explicit clock, and all other operations are clock agnostic (because they don't need a clock to express their semantics), I think all of that would just be a plain old !ltl.property. I don't think we need an explicit distinction betweem clocked and unclocked here 🤔. When we emit things to Verilog, we can just scan the LTL operations to find all the clocks used, and insert @(posedge clock) around all the LTL ops that share the same clock 🤔

Yes, thank you for your suggestion. After thinking about it, I agree with your point. There are actually two core aspects here: the "clock of the operands" and "clocked delays". For operations like concat/and/or, if the operands are in different clock domains, the semantics become unclear, so these operations do not need to explicitly carry a clock—it's enough to ensure that all their input operands have the same clock.

Although the proposal in #8673 suggests enforcing clock consistency for operands at the type system level, the fundamental goal is to prevent combining operands from different clock domains. In other words, these logical/combination operations can be designed as clock-agnostic—as long as the type system guarantees that operands with different clocks cannot be mixed, the semantics remain clear.

Additionally, I will remove APIs such as clocked_and and clocked_or. Based on our discussion and the recommendations from #8673, as long as these logical operations ensure operand clocks are consistent, there's no need to explicitly distinguish "clocked" versions of and/or. This will make the API simpler and the type system clearer.

Thank you again for your insightful suggestions!

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