diff --git a/include/circt/Dialect/Sim/SimOps.h b/include/circt/Dialect/Sim/SimOps.h index 2e41f6eb71d6..7d27d76caad7 100644 --- a/include/circt/Dialect/Sim/SimOps.h +++ b/include/circt/Dialect/Sim/SimOps.h @@ -18,6 +18,7 @@ #include "mlir/IR/SymbolTable.h" #include "circt/Dialect/HW/HWOpInterfaces.h" +#include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Seq/SeqDialect.h" #include "circt/Dialect/Seq/SeqTypes.h" #include "circt/Dialect/Sim/SimDialect.h" @@ -25,6 +26,7 @@ #include "circt/Support/BuilderUtils.h" #include "mlir/Interfaces/CallInterfaces.h" #include "mlir/Interfaces/FunctionInterfaces.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" #define GET_OP_CLASSES #include "circt/Dialect/Sim/Sim.h.inc" diff --git a/include/circt/Dialect/Sim/SimOps.td b/include/circt/Dialect/Sim/SimOps.td index c3bdb370f23b..53878122a969 100644 --- a/include/circt/Dialect/Sim/SimOps.td +++ b/include/circt/Dialect/Sim/SimOps.td @@ -15,9 +15,13 @@ include "mlir/Interfaces/SideEffectInterfaces.td" include "mlir/Interfaces/FunctionInterfaces.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/IR/BuiltinAttributes.td" +include "mlir/IR/RegionKindInterface.td" include "circt/Dialect/Sim/SimDialect.td" include "circt/Dialect/Sim/SimTypes.td" include "circt/Dialect/Seq/SeqTypes.td" +include "circt/Dialect/HW/HWEnums.td" include "circt/Dialect/HW/HWOpInterfaces.td" include "circt/Dialect/HW/HWTypes.td" @@ -319,12 +323,6 @@ def FormatStringConcatOp : SimOp<"fmt.concat", [Pure]> { /// and flattening continues without recursing into the cycle. LogicalResult getFlattenedInputs(llvm::SmallVectorImpl &flatOperands); }]; - - let builders = [ - OpBuilder<(ins "mlir::ValueRange":$inputs), [{ - return build($_builder, $_state, circt::sim::FormatStringType::get($_builder.getContext()), inputs); - }]> - ]; } def PrintFormattedOp : SimOp<"print"> { @@ -365,4 +363,122 @@ def PrintFormattedProcOp : SimOp<"proc.print"> { let assemblyFormat = "$input attr-dict"; } +// --- Trigger Ops --- + +def OnEdgeOp : SimOp<"on_edge", [ + Pure, + DeclareOpInterfaceMethods +]> { + let summary = "Invoke a trigger on a clock edge event."; + let arguments = (ins ClockType:$clock, EventControlAttr:$event); + let results = (outs EdgeTriggerType:$result); + let assemblyFormat = "$event $clock attr-dict"; +} + +def OnInitOp : SimOp<"on_init", [Pure]> { + let summary = "Invoke a trigger at the start of simulation."; + let results = (outs InitTriggerType:$result); + let assemblyFormat = "attr-dict"; +} + +def TriggerSequenceOp : SimOp<"trigger_sequence", [ + DeclareOpInterfaceMethods +]> { + let summary = "Derive a sequence of triggers from a parent trigger."; + let description = [{ + Creates a series of sequenced triggers. + The first resulting trigger is invoked when the parent trigger is invoked. + The subsequent triggers are invoked after all preceeding triggers + have completed. The operation completes after all result triggers have + completed. + }]; + let arguments = (ins AnyTriggerType:$parent, UI32Attr:$length); + let results = (outs Variadic:$triggers); + let assemblyFormat = + "$parent `,` $length attr-dict `:` qualified(type($parent))"; + let hasFolder = true; + let hasCanonicalizeMethod = true; + let hasVerifier = true; +} + +def YieldSeqOp : SimOp<"yield_seq",[ + Terminator, HasParent<"circt::sim::TriggeredOp"> +]> { + let summary = [{Yield results form a triggerd region with 'seq' + (i.e. register-like) semantics."}]; + let description = [{ + Terminates a triggered region and produces the given list of values. + The results only become visible after all triggers and register updates + occuring on the same event as the parent operation have completed. + E.g., the following snippet produces a counter that increments on every + rising edge of '%clk': + ``` + %posedge = sim.on_edge posedge %clk + %counter = sim.triggered (%counter) on %posedge tieoff [0 : i8] { + ^bb0(%arg0: i8): + %cst1 = hw.constant 1 : i8 + %inc = comb.add bin %arg0, %cst1 : i8 + sim.yield_seq %inc : i8 + } : (i8) -> (i8) + ``` + }]; + let arguments = (ins Variadic:$inputs); + let assemblyFormat = "($inputs^ `:` qualified(type($inputs)))? attr-dict"; + let builders = [OpBuilder<(ins), "build($_builder, $_state, {});">]; +} + +def TriggeredOp : SimOp<"triggered", [ + AttrSizedOperandSegments, + IsolatedFromAbove, + RegionKindInterface, + RecursiveMemoryEffects, + RecursivelySpeculatable, + SingleBlockImplicitTerminator<"sim::YieldSeqOp">, + HasParent<"circt::hw::HWModuleOp"> +]> { + let summary = [{ + Defines a procedure invoked on a given trigger and condition. + }]; + let description = [{ + Creates a procedural region which is invoked on a given trigger. + The optional condition allows the execution of the body to be skipped, if + the condition evaluates to `false` at the time when the trigger's + root event occurs. + The body region must complete without 'consuming' simulation time. It + may not run indefinitely or wait for any simulation event. It is allowed to + have side-effects and produce results. + For every result a 'tieoff' constant must be provided. It specifies the + respective result's value before the body is first invoked. + For non-simulation flows the results are replaced by their tie-off values. + }]; + let arguments = (ins AnyTriggerType:$trigger, + Optional:$condition, + Variadic:$inputs, + OptionalAttr>:$tieoffs + ); + let results = (outs Variadic); + let regions = (region SizedRegion<1>:$body); + + let assemblyFormat = [{ + ` ` `(` $inputs `)` + `on` ` ` `(` $trigger `:` qualified(type($trigger)) `)` + (`if` $condition^)? + (`tieoff` $tieoffs^)? attr-dict-with-keyword + $body + `:` functional-type($inputs, results) + }]; + + let extraClassDeclaration = [{ + // Implement RegionKindInterface. + static RegionKind getRegionKind(unsigned index) { + return RegionKind::SSACFG; + } + }]; + + let hasVerifier = true; + let hasFolder = true; + let hasCanonicalizeMethod = true; +} + #endif // CIRCT_DIALECT_SIM_SIMOPS_TD diff --git a/include/circt/Dialect/Sim/SimTypes.h b/include/circt/Dialect/Sim/SimTypes.h index 669eaaf14f5d..12f10213e237 100644 --- a/include/circt/Dialect/Sim/SimTypes.h +++ b/include/circt/Dialect/Sim/SimTypes.h @@ -12,6 +12,8 @@ #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Types.h" +#include "circt/Dialect/HW/HWEnums.h" + #define GET_TYPEDEF_CLASSES #include "circt/Dialect/Sim/SimTypes.h.inc" diff --git a/include/circt/Dialect/Sim/SimTypes.td b/include/circt/Dialect/Sim/SimTypes.td index 90c901b5236b..cb80cd871aca 100644 --- a/include/circt/Dialect/Sim/SimTypes.td +++ b/include/circt/Dialect/Sim/SimTypes.td @@ -26,4 +26,19 @@ def FormatStringType : SimTypeDef<"FormatString"> { }]; } + +def EdgeTriggerType : SimTypeDef<"EdgeTrigger"> { + let summary = "Trigger derived from an edge event."; + let parameters = (ins "::circt::hw::EventControl":$edgeEvent); + let mnemonic = "trigger.edge"; + let assemblyFormat = "`<` $edgeEvent `>`"; +} + +def InitTriggerType : SimTypeDef<"InitTrigger"> { + let summary = "Trigger derived from the simulation start event."; + let mnemonic = "trigger.init"; +} + +def AnyTriggerType : AnyTypeOf<[EdgeTriggerType, InitTriggerType]>; + #endif // CIRCT_DIALECT_SIM_SIMTYPES_TD diff --git a/lib/Dialect/Sim/SimDialect.cpp b/lib/Dialect/Sim/SimDialect.cpp index a05f160036c7..031656ba48c4 100644 --- a/lib/Dialect/Sim/SimDialect.cpp +++ b/lib/Dialect/Sim/SimDialect.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "circt/Dialect/Sim/SimDialect.h" +#include "circt/Dialect/HW/HWDialect.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Sim/SimOps.h" #include "mlir/IR/Builders.h" @@ -40,6 +41,12 @@ Operation *SimDialect::materializeConstant(::mlir::OpBuilder &builder, ::mlir::Type type, ::mlir::Location loc) { + // Delegate non 'sim' types to the HW dialect materializer. + if (!isa(type.getDialect())) + return builder.getContext() + ->getLoadedDialect() + ->materializeConstant(builder, value, type, loc); + if (auto fmtStrType = llvm::dyn_cast(type)) return builder.create(loc, fmtStrType, llvm::cast(value)); diff --git a/lib/Dialect/Sim/SimOps.cpp b/lib/Dialect/Sim/SimOps.cpp index 7ae834a12931..23aa735e9fe5 100644 --- a/lib/Dialect/Sim/SimOps.cpp +++ b/lib/Dialect/Sim/SimOps.cpp @@ -397,6 +397,188 @@ LogicalResult PrintFormattedProcOp::canonicalize(PrintFormattedProcOp op, return failure(); } +// --- OnEdgeOp --- + +LogicalResult OnEdgeOp::inferReturnTypes( + MLIRContext *context, std::optional location, ValueRange operands, + DictionaryAttr attributes, OpaqueProperties properties, RegionRange regions, + SmallVectorImpl &inferredReturnTypes) { + auto eventAttr = properties.as()->getEvent(); + inferredReturnTypes.emplace_back( + EdgeTriggerType::get(context, eventAttr.getValue())); + return success(); +} + +// --- TriggeredOp --- + +LogicalResult TriggeredOp::verify() { + if (getNumResults() > 0 && !getTieoffs()) + return emitError("Tie-off constants must be provided for all results."); + auto numTieoffs = !getTieoffs() ? 0 : getTieoffsAttr().size(); + if (numTieoffs != getNumResults()) + return emitError( + "Number of tie-off constants does not match number of results."); + if (numTieoffs == 0) + return success(); + unsigned idx = 0; + bool failed = false; + for (const auto &[res, tieoff] : + llvm::zip(getResultTypes(), getTieoffsAttr())) { + if (res != cast(tieoff).getType()) { + emitError("Tie-off type does not match for result at index " + + Twine(idx)); + failed = true; + } + ++idx; + } + return success(!failed); +} + +LogicalResult TriggeredOp::fold(FoldAdaptor adaptor, + SmallVectorImpl &results) { + if (auto constCond = dyn_cast_or_null(adaptor.getCondition())) { + if (constCond.getValue().isAllOnes()) { + // Strip constant true condition. + getConditionMutable().clear(); + return success(); + } + // Never enabled, fold to tie-offs. + if (getNumResults() > 0) { + results.append(adaptor.getTieoffsAttr().begin(), + adaptor.getTieoffsAttr().end()); + return success(); + } + } + return failure(); +} + +LogicalResult TriggeredOp::canonicalize(TriggeredOp op, + PatternRewriter &rewriter) { + if (op.getNumResults() > 0) + return failure(); + + bool isDeadOrEmpty = false; + + auto *bodyBlock = &op.getBodyRegion().front(); + isDeadOrEmpty = bodyBlock->without_terminator().empty(); + + if (!isDeadOrEmpty && !!op.getCondition()) + if (auto cstCond = op.getCondition().getDefiningOp()) + isDeadOrEmpty = cstCond.getValue().isZero(); + + if (!isDeadOrEmpty) + return failure(); + + rewriter.eraseOp(op); + return success(); +} + +// --- TriggerSequenceOp --- + +LogicalResult TriggerSequenceOp::inferReturnTypes( + MLIRContext *context, std::optional location, ValueRange operands, + DictionaryAttr attributes, OpaqueProperties properties, RegionRange regions, + SmallVectorImpl &inferredReturnTypes) { + // Create N results matching the type of the parent trigger, where N is the + // specified length of the sequence. + auto lengthAttr = + properties.as()->getLength(); + uint32_t len = lengthAttr.getValue().getZExtValue(); + Type trigType = operands.front().getType(); + inferredReturnTypes.resize_for_overwrite(len); + for (size_t i = 0; i < len; ++i) + inferredReturnTypes[i] = trigType; + return success(); +} + +LogicalResult TriggerSequenceOp::verify() { + if (getLength() != getNumResults()) + return emitOpError("specified length does not match number of results."); + return success(); +} + +LogicalResult TriggerSequenceOp::fold(FoldAdaptor adaptor, + SmallVectorImpl &results) { + // Fold trivial sequences to the parent trigger. + if (getLength() == 1 && getResult(0) != getParent()) { + results.push_back(getParent()); + return success(); + } + return failure(); +} + +LogicalResult TriggerSequenceOp::canonicalize(TriggerSequenceOp op, + PatternRewriter &rewriter) { + if (op.getNumResults() == 0) { + rewriter.eraseOp(op); + return success(); + } + + // Check if there are unused results (which can be removed) or + // non-concurrent sub-sequences (which can be inlined). + auto getSingleSequenceUser = [](Value trigger) -> TriggerSequenceOp { + if (!trigger.hasOneUse()) + return {}; + return dyn_cast(trigger.use_begin()->getOwner()); + }; + + bool canBeChanged = false; + for (auto res : op.getResults()) { + auto singleSeqUser = getSingleSequenceUser(res); + if (singleSeqUser == op) { + op.emitWarning("Recursive trigger sequence."); + return failure(); + } + if (res.use_empty() || !!singleSeqUser) { + canBeChanged = true; + break; + } + } + + if (!canBeChanged) + return failure(); + + // Build a list of new result values. + SmallVector resultValues; + SmallVector locs; + SmallVector childSeqs; + locs.emplace_back(op.getLoc()); + resultValues.reserve(op.getNumResults()); + for (auto res : op.getResults()) { + if (res.use_empty()) + continue; + + if (auto seqUser = getSingleSequenceUser(res)) { + resultValues.append(seqUser.getResults().begin(), + seqUser.getResults().end()); + locs.emplace_back(seqUser.getLoc()); + childSeqs.emplace_back(seqUser); + } else { + resultValues.emplace_back(res); + } + } + + // Remove empty sequences. + if (resultValues.empty()) { + rewriter.eraseOp(op); + return success(); + } + + // Replace the current operation with a new sequence. + rewriter.setInsertionPoint(op); + auto fusedLoc = FusedLoc::get(rewriter.getContext(), locs); + auto newOp = rewriter.create(fusedLoc, op.getParent(), + resultValues.size()); + for (auto [rval, newRes] : llvm::zip(resultValues, newOp.getResults())) + rewriter.replaceAllUsesWith(rval, newRes); + // Remove sequences that have been inlined + for (auto child : childSeqs) + rewriter.eraseOp(child); + + rewriter.eraseOp(op); + return success(); +} + //===----------------------------------------------------------------------===// // TableGen generated logic. //===----------------------------------------------------------------------===// diff --git a/test/Dialect/Sim/sim-errors.mlir b/test/Dialect/Sim/sim-errors.mlir index a70360b1cdfe..bcd4a99ca621 100644 --- a/test/Dialect/Sim/sim-errors.mlir +++ b/test/Dialect/Sim/sim-errors.mlir @@ -51,3 +51,48 @@ hw.module @dpi_call(in %clock : !seq.clock, in %in: i1) { // expected-error @below {{callee must be 'sim.dpi.func' or 'func.func' but got 'hw.module.extern'}} %0, %1 = sim.func.dpi.call @non_func(%in) : (i1) -> (i1, i1) } + +// ----- + +hw.module @not_enough_triggers(in %in : !sim.trigger.edge) { + // expected-error @below {{operation defines 1 results but was provided 2 to bind}} + %res:2 = sim.trigger_sequence %in, 1 : !sim.trigger.edge +} + +// ----- + +hw.module @recursive_trigger() { + // expected-warning @below {{Recursive trigger sequence}} + %res = sim.trigger_sequence %res, 1 : !sim.trigger.edge +} + +// ----- + +hw.module @missing_tieoffs(in %trig : !sim.trigger.edge) { + // expected-error @below {{Tie-off constants must be provided for all results}} + %res = sim.triggered () on (%trig : !sim.trigger.edge) { + %cst = hw.constant 0 : i2 + sim.yield_seq %cst : i2 + } : () -> i2 +} + +// ----- + +hw.module @wrong_tieoff(in %trig : !sim.trigger.edge) { + // expected-error @below {{Tie-off type does not match for result at index 0}} + %res = sim.triggered () on (%trig : !sim.trigger.edge) tieoff [0 : i1] { + %cst = hw.constant 0 : i2 + sim.yield_seq %cst : i2 + } : () -> i2 +} + +// ----- + +hw.module @too_many_tieoffs(in %trig : !sim.trigger.edge) { + // expected-error @below {{Number of tie-off constants does not match number of results}} + %res = sim.triggered () on (%trig : !sim.trigger.edge) tieoff [0 : i2, 0 : i2] { + %cst = hw.constant 0 : i2 + sim.yield_seq %cst : i2 + } : () -> i2 +} + diff --git a/test/Dialect/Sim/triggers.mlir b/test/Dialect/Sim/triggers.mlir new file mode 100644 index 000000000000..ebba4a4488a0 --- /dev/null +++ b/test/Dialect/Sim/triggers.mlir @@ -0,0 +1,140 @@ +// RUN: circt-opt %s --canonicalize | FileCheck %s +// RUN: circt-opt %s --canonicalize --cse | FileCheck %s + +// CHECK-LABEL: hw.module @root_triggers +// CHECK-DAG: [[PE:%.*]] = sim.on_edge posedge %clock +// CHECK-DAG: [[NE:%.*]] = sim.on_edge negedge %clock +// CHECK-DAG: [[BE:%.*]] = sim.on_edge edge %clock +// CHECK-DAG: [[IT:%.*]] = sim.on_init +// CHECK: hw.output [[PE]], [[NE]], [[BE]], [[IT]] : !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.init +hw.module @root_triggers(in %clock : !seq.clock, out o0 : !sim.trigger.edge, out o1 : !sim.trigger.edge, out o2 : !sim.trigger.edge, out o3 : !sim.trigger.init) { + %0 = sim.on_edge posedge %clock + %1 = sim.on_edge negedge %clock + %2 = sim.on_edge edge %clock + %3 = sim.on_init + hw.output %0, %1, %2, %3 : !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.init +} + +// CHECK-LABEL: hw.module @fold_triggered +hw.module @fold_triggered(in %a : i8, in %en : i1, in %trig : !sim.trigger.edge, out o0 : i8, out o1 : i9, out o2 : i8) { + // CHECK: %[[CST12:.*]] = hw.constant 12 : i8 + %true = hw.constant true + %false = hw.constant false + + // CHECK: sim.triggered () on (%trig : !sim.trigger.edge) { + sim.triggered () on (%trig : !sim.trigger.edge) if %true { + %0 = sim.fmt.lit "Remove constant true condition" + sim.proc.print %0 + } : () -> () + + // CHECK: sim.triggered () on (%trig : !sim.trigger.edge) if %en { + // CHECK-NEXT: "Don't touch live process" + sim.triggered () on (%trig : !sim.trigger.edge) if %en { + %0 = sim.fmt.lit "Don't touch live process" + sim.proc.print %0 + } : () -> () + + // CHECK: %[[RES:.*]]:2 = sim.triggered (%a) on (%trig : !sim.trigger.edge) if %en tieoff [12 : i8, 33 : i9] { + // CHECK: "Don't touch live process with results" + // CHECK: arith.extui %{{.+}} : i8 to i9 + // CHECK: (i8) -> (i8, i9) + + %res:2 = sim.triggered (%a) on (%trig : !sim.trigger.edge) if %en tieoff [12 : i8, 33 : i9] { + ^bb0(%arg: i8): + %0 = sim.fmt.lit "Don't touch live process with results" + sim.proc.print %0 + %ext = arith.extui %arg : i8 to i9 + sim.yield_seq %arg, %ext : i8, i9 + } : (i8) -> (i8, i9) + + // CHECK-NOT: sim.triggered + + %fold = sim.triggered () on (%trig : !sim.trigger.edge) if %false tieoff [12 : i8] { + %cst0_i8 = hw.constant 0 : i8 + %0 = sim.fmt.lit "Fold dead process with result" + sim.proc.print %0 + sim.yield_seq %cst0_i8 : i8 + } : () -> (i8) + + sim.triggered () on (%trig : !sim.trigger.edge) if %false { + %0 = sim.fmt.lit "Remove dead process" + sim.proc.print %0 + } : () -> () + + sim.triggered () on (%trig : !sim.trigger.edge) if %en { + } : () -> () + + // CHECK: hw.output %[[RES]]#0, %[[RES]]#1, %[[CST12]] : i8, i9, i8 + hw.output %res#0, %res#1, %fold : i8, i9, i8 +} + +// CHECK-LABEL: hw.module @empty_sequence +// CHECK-NOT: sim.trigger_sequence +hw.module @empty_sequence(in %trig : !sim.trigger.init) { + sim.trigger_sequence %trig, 0 : !sim.trigger.init +} + +// CHECK-LABEL: hw.module @trivial_sequence +// CHECK-NOT: sim.trigger_sequence +// CHECK: hw.output %trig +hw.module @trivial_sequence(in %trig : !sim.trigger.init, out o: !sim.trigger.init) { + %out = sim.trigger_sequence %trig, 1 : !sim.trigger.init + hw.output %out : !sim.trigger.init +} + +// CHECK-LABEL: hw.module @dead_sequence +// CHECK-NOT: sim.trigger_sequence +hw.module @dead_sequence(in %trig : !sim.trigger.init) { + %dead:128 = sim.trigger_sequence %trig, 128 : !sim.trigger.init +} + +// CHECK-LABEL: hw.module @mostly_dead_sequence +// CHECK: %[[RES:.*]]:2 = sim.trigger_sequence %trig, 2 : !sim.trigger.init +// CHECK-NEXT: hw.output %[[RES]]#1, %[[RES]]#0, %[[RES]]#1 +hw.module @mostly_dead_sequence(in %trig : !sim.trigger.init, out o0: !sim.trigger.init, out o1: !sim.trigger.init, out o2: !sim.trigger.init) { + %notdead:128 = sim.trigger_sequence %trig, 128 : !sim.trigger.init + hw.output %notdead#50, %notdead#2, %notdead#50 : !sim.trigger.init, !sim.trigger.init, !sim.trigger.init +} + +// CHECK-LABEL: hw.module @nested_sequence_0 +// CHECK: [[R:%.*]]:8 = sim.trigger_sequence %trig, 8 : !sim.trigger.edge +// CHECK-NEXT: hw.output [[R]]#0, [[R]]#1, [[R]]#2, [[R]]#3, [[R]]#4, [[R]]#5, [[R]]#6, [[R]]#7 +hw.module @nested_sequence_0(in %trig : !sim.trigger.edge, out o0: !sim.trigger.edge, out o1: !sim.trigger.edge, out o2: !sim.trigger.edge, out o3: !sim.trigger.edge, out o4: !sim.trigger.edge, out o5: !sim.trigger.edge, out o6: !sim.trigger.edge, out o7: !sim.trigger.edge) { + %a:4 = sim.trigger_sequence %trig, 4 : !sim.trigger.edge + %b:2 = sim.trigger_sequence %a#0, 2 : !sim.trigger.edge + %c:3 = sim.trigger_sequence %a#1, 3 : !sim.trigger.edge + %d:2 = sim.trigger_sequence %a#2, 2 : !sim.trigger.edge + + hw.output %b#0, %b#1, %c#0, %c#1, %c#2, %d#0, %d#1, %a#3 : !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge +} + +// CHECK-LABEL: hw.module @nested_sequence_1 +// CHECK: [[R:%.*]]:8 = sim.trigger_sequence %trig, 8 : !sim.trigger.edge +// CHECK-NEXT: hw.output [[R]]#0, [[R]]#1, [[R]]#2, [[R]]#3, [[R]]#4, [[R]]#5, [[R]]#6, [[R]]#7 +hw.module @nested_sequence_1(in %trig : !sim.trigger.edge, out o0: !sim.trigger.edge, out o1: !sim.trigger.edge, out o2: !sim.trigger.edge, out o3: !sim.trigger.edge, out o4: !sim.trigger.edge, out o5: !sim.trigger.edge, out o6: !sim.trigger.edge, out o7: !sim.trigger.edge) { + %a:2 = sim.trigger_sequence %trig, 2 : !sim.trigger.edge + + %b:2 = sim.trigger_sequence %a#0, 2 : !sim.trigger.edge + %c:2 = sim.trigger_sequence %a#1, 2 : !sim.trigger.edge + + %d:2 = sim.trigger_sequence %b#0, 2 : !sim.trigger.edge + %e:2 = sim.trigger_sequence %b#1, 2 : !sim.trigger.edge + %f:2 = sim.trigger_sequence %c#0, 2 : !sim.trigger.edge + %g:2 = sim.trigger_sequence %c#1, 2 : !sim.trigger.edge + + hw.output %d#0, %d#1, %e#0, %e#1, %f#0, %f#1, %g#0, %g#1 : !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge +} + +// CHECK-LABEL: hw.module @nested_sequence_2 +// CHECK: [[R0:%.*]]:4 = sim.trigger_sequence %trig, 4 +// CHECK: [[R1:%.*]]:2 = sim.trigger_sequence [[R0]]#3, 2 +// CHECK: [[R2:%.*]]:2 = sim.trigger_sequence [[R0]]#3, 2 +// CHECK: hw.output [[R0]]#0, [[R0]]#1, [[R0]]#2, [[R0]]#0, [[R1]]#0, [[R1]]#1, [[R2]]#0, [[R2]]#1 +hw.module @nested_sequence_2(in %trig : !sim.trigger.edge, out o0: !sim.trigger.edge, out o1: !sim.trigger.edge, out o2: !sim.trigger.edge, out o3: !sim.trigger.edge, out o4: !sim.trigger.edge, out o5: !sim.trigger.edge, out o6: !sim.trigger.edge, out o7: !sim.trigger.edge) { + %a:4 = sim.trigger_sequence %trig, 4 : !sim.trigger.edge + %b:3 = sim.trigger_sequence %a#1, 3 : !sim.trigger.edge + %c:2 = sim.trigger_sequence %b#0, 2 : !sim.trigger.edge + %d:3 = sim.trigger_sequence %a#3, 3 : !sim.trigger.edge + %e:3 = sim.trigger_sequence %a#3, 3 : !sim.trigger.edge + hw.output %b#0, %b#1, %b#2, %c#1, %d#0, %d#2, %e#1, %e#2 : !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge +}