142 CIRCT ltl Dialect
The ltl dialect is CIRCT’s dialect for Linear Temporal Logic, temporal sequences, and temporal properties. It is mainly used for hardware verification: assertions, assumptions, coverage, and formal checks that talk about behavior over time instead of only values in one cycle.
For a beginner, the most important idea is that normal hardware IR usually describes what a circuit computes now. LTL describes what must happen across cycles. A property might say that if a request is seen now, an acknowledge must eventually appear, or that a signal must remain true until another signal fires. The ltl dialect gives CIRCT a structured way to represent those statements inside MLIR.
142.1 Why This Dialect Exists
SystemVerilog Assertions are the common hardware-verification language for temporal behavior, but SVA has a lot of syntax, special cases, and legacy surface details. CIRCT’s LTL dialect is not intended to be a direct SVA syntax tree. Its purpose is to capture the core temporal formalism underneath SVA in a clean IR.
This matters because hardware tools need to do more than print assertions back out. They may canonicalize temporal formulas, lower simple pieces to gates, preserve rich properties for Verilog emission, or feed formal tooling. A dedicated LTL dialect gives these tools typed operations for sequences, properties, delays, repetitions, implications, clocks, and temporal quantifiers.
The dialect depends on CIRCT’s hw and comb dialects because temporal properties are usually built over hardware values. It also works closely with the verif dialect, whose assertion and assumption operations can carry !ltl.sequence and !ltl.property values.
142.2 Types
The dialect defines two explicit types.
| Type | Meaning |
|---|---|
!ltl.sequence |
A sequence of observations over time, similar to a regular expression over clock cycles. |
!ltl.property |
A verifiable temporal claim built from sequences and property operators. |
An i1 value is also accepted where a sequence is expected: a boolean is a sequence that holds in the current cycle. An i1 or !ltl.sequence value is also accepted where a property is expected. This makes simple boolean assertions, sequence assertions, and full temporal properties fit into the same verification flow.
The difference is conceptual. A sequence matches behavior. A property states that behavior must satisfy a temporal claim.
142.3 Operation Inventory
The current ltl dialect defines 17 operations.
| Operation | Role |
|---|---|
ltl.and |
Conjunction over booleans, sequences, or properties. |
ltl.or |
Disjunction over booleans, sequences, or properties. |
ltl.intersect |
Intersection that requires operands to hold with the same start and end times. |
ltl.clock |
Associates a sequence or property tree with a clock edge. |
ltl.delay |
Delays a sequence by a fixed, bounded, or unbounded finite number of cycles. |
ltl.clocked_delay |
Delays a sequence using an explicit clock and edge. |
ltl.past |
Observes a hardware value a fixed number of clock cycles earlier. |
ltl.sampled |
Models SystemVerilog sampled-value semantics for assertion action contexts. |
ltl.concat |
Concatenates sequences into a longer sequence. |
ltl.repeat |
Repeats a sequence consecutively a fixed, bounded, or unbounded finite number of times. |
ltl.goto_repeat |
Non-consecutive go-to style repetition where the final counted evaluation must match. |
ltl.non_consecutive_repeat |
Non-consecutive repetition where the final evaluation does not have to match. |
ltl.boolean_constant |
Produces a constant boolean !ltl.property. |
ltl.not |
Negates a property. |
ltl.implication |
Checks a consequent property when an antecedent sequence matches. |
ltl.until |
Weak until: one property holds until another property holds. |
ltl.eventually |
Strong eventually: a property must hold after a finite number of cycles. |
The dialect also defines a clock-edge attribute with the spelling posedge, negedge, or edge.
142.4 Sequences
Sequences are the part of the dialect that model temporal matching. A plain boolean value is a one-cycle sequence. ltl.delay moves a sequence into the future. For example, a fixed delay means the input sequence must match exactly that many cycles later. A delay with a length gives a bounded window. A delay without a length is unbounded but still finite, meaning the match must happen eventually after the base delay.
ltl.concat combines sequences. It is important that concatenation itself does not add an implicit clock cycle. It aligns the end of one sequence with the start of the next. If the intended meaning is “the next sequence starts one cycle later,” that delay must be represented explicitly with ltl.delay.
This is one of the dialect’s major design choices. SVA writes delays inside concatenation syntax, such as a ##1 b. The LTL dialect separates that into two concepts: ltl.delay for the cycle distance and ltl.concat for joining sequences. That makes zero-length and repeated sequences easier to reason about in IR.
ltl.repeat models consecutive repetition. It can represent exactly N repetitions, a bounded N-to-M range, or an unbounded finite range. The two non-consecutive forms, ltl.goto_repeat and ltl.non_consecutive_repeat, model SVA-style repetitions where matches may be separated by cycles that do not match. The go-to form requires the final counted evaluation to match; the non-consecutive form does not.
142.5 Properties
Properties are temporal claims. ltl.implication is the common bridge from a sequence into a property: if the antecedent sequence matches, then the consequent property is checked starting at the end point of that match. If the antecedent does not match, the implication holds vacuously.
The dialect models the overlapping implication form directly. A non-overlapping SVA implication can be represented by explicitly concatenating a one-cycle delay before the consequent is checked. This keeps clock-related behavior explicit instead of hidden inside a separate operator.
ltl.until is weak until. It says that the input property holds until the condition property holds once. Because it is weak, the result can still hold if the condition never arrives as long as the input keeps holding.
ltl.eventually is strong. It requires that the input property hold after a finite number of cycles. If the input can never hold in the future, the eventuality does not hold.
ltl.not, ltl.and, and ltl.or provide boolean-style composition at the property level. ltl.boolean_constant gives canonicalization patterns a property-typed constant.
142.6 Clocking And Sampling
Temporal operators need a notion of cycle. ltl.clock associates a sequence or property expression with a clock and edge. The edge can be posedge, negedge, or edge. The clock applies to the expression tree below it, except where another nested clock overrides that context.
ltl.clocked_delay is more explicit. It carries the clock and edge directly on the delay operation. This is useful when a delay needs to be tied to a specific clock without relying on an enclosing ltl.clock.
ltl.past observes a hardware integer value a fixed number of cycles earlier on a clock. In the lower-ltl-to-core conversion, this operation becomes a sequential shift register.
ltl.sampled models SystemVerilog sampled-value semantics. This is useful in concurrent assertion action contexts, where the value read in an action block must be the same sampled value that triggered the assertion.
142.7 Transformations
Several LTL operations have folders or canonicalizers. These are local simplifications, not full temporal proof engines.
The declarative fold patterns include:
- merging nested
ltl.delayoperations by adding their delays and combining bounded lengths, - pushing
ltl.delayinto the first element of anltl.concat, - merging nested
ltl.clocked_delayoperations when the clock and edge match, - pushing
ltl.clocked_delayinto concatenations, - flattening nested
ltl.concatoperations.
The operation definitions also mark folders or canonicalizers on operations such as ltl.delay, ltl.clocked_delay, ltl.concat, ltl.repeat, ltl.goto_repeat, ltl.non_consecutive_repeat, and ltl.boolean_constant. ltl.and, ltl.or, and ltl.intersect have canonicalization hooks and infer their result type from their operands: if any operand is a property the result is a property; otherwise, if any operand is a sequence the result is a sequence; otherwise the result is i1.
These transformations make the IR cleaner and remove obvious structural redundancy. They do not replace model checking or assertion solving.
142.8 Conversion And Emission
The dedicated conversion pass for this dialect is lower-ltl-to-core.
| Pass | What it does |
|---|---|
lower-ltl-to-core |
Converts selected LTL and Verif operations inside hw.module operations to core HW, Comb, SV, and Seq dialect operations. |
This pass is intentionally partial. It can lower boolean-shaped ltl.and, ltl.or, ltl.not, and ltl.implication to comb operations when doing so is legal. For example, boolean implication becomes “not antecedent or consequent.” It lowers ltl.past to a seq.shiftreg on the given clock. It also handles a related verif.has_been_reset operation. The pass type-converts !ltl.sequence and !ltl.property to i1 where needed for simple assertion uses, but it does not try to compile every rich temporal property into gates.
The other major path is Verilog emission. CIRCT’s ExportVerilog support knows how to emit LTL expressions as SystemVerilog assertion syntax in appropriate contexts. For example, ltl.implication can emit as an SVA implication, and repetition operators map to SVA repetition forms. This is why LTL IR may remain present late in the pipeline: the desired output may be an assertion, not a hardware circuit implementing the assertion.
142.9 How To Read LTL IR
Start by asking whether a value is a sequence or a property. If it is a sequence, read it as a temporal pattern to be matched. If it is a property, read it as a claim about what must hold.
Then look for clocking. A delay without an obvious clock gets its cycle meaning from surrounding clock context, usually an ltl.clock or an assertion context. An ltl.clocked_delay is explicit and can be read locally.
For sequence expressions, read delays and concatenations carefully. ltl.concat does not mean “next cycle” by itself. The next-cycle behavior must appear as a delay. For property expressions, implication, until, and eventually are the high-level temporal operators that describe the verification intent.
Finally, look at how the value is used. If it feeds verif.assert, verif.assume, or verif.cover, it is part of a verification statement. If it is being lowered by lower-ltl-to-core, only the boolean-like parts may become core gates. If it is being exported, it may turn into SystemVerilog assertion syntax.
142.10 What It Implies
The presence of ltl implies that the IR is carrying verification intent, not just implementation logic. That intent may need to survive until formal tooling or Verilog emission. Rewriting it as ordinary boolean logic too early can lose temporal meaning.
It also implies that type matters. An i1, a !ltl.sequence, and a !ltl.property may all look like “truth-like” values, but they live at different levels. A boolean is about one cycle. A sequence is about a match over time. A property is about a claim that can be asserted, assumed, covered, or combined with temporal quantifiers.
The main pitfall is assuming that SVA syntax maps one-to-one to one LTL op. CIRCT deliberately factors SVA constructs into smaller core temporal concepts. For example, a non-overlapping implication is modeled using explicit delay and concatenation plus ltl.implication, not a separate non-overlap operation.
142.11 Summary
The ltl dialect is CIRCT’s clean IR for temporal hardware verification. It models sequences, properties, delays, repetitions, implications, clocking, past-value sampling, and eventuality. It is important when assertions need to survive as structured temporal formulas, when SVA-like constructs need a compiler-friendly representation, and when parts of those formulas must either lower to core hardware operations or emit as SystemVerilog assertions.