diff --git a/include/circt/Dialect/Sim/SimOps.h b/include/circt/Dialect/Sim/SimOps.h index 85061a8389c9..55492bae4b2f 100644 --- a/include/circt/Dialect/Sim/SimOps.h +++ b/include/circt/Dialect/Sim/SimOps.h @@ -14,6 +14,7 @@ #define CIRCT_DIALECT_SIM_SIMOPS_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" diff --git a/include/circt/Dialect/Sim/SimOps.td b/include/circt/Dialect/Sim/SimOps.td index 0c776ba5d6c3..0c9d28c093b4 100644 --- a/include/circt/Dialect/Sim/SimOps.td +++ b/include/circt/Dialect/Sim/SimOps.td @@ -13,6 +13,12 @@ #ifndef CIRCT_DIALECT_SIM_SIMOPS_TD #define CIRCT_DIALECT_SIM_SIMOPS_TD +include "mlir/Interfaces/SideEffectInterfaces.td" +include "mlir/Interfaces/FunctionInterfaces.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" include "circt/Dialect/Seq/SeqTypes.td" @@ -21,7 +27,9 @@ include "circt/Dialect/Sim/SimTypes.td" include "mlir/Interfaces/FunctionInterfaces.td" include "mlir/Interfaces/InferTypeOpInterface.td" include "mlir/Interfaces/SideEffectInterfaces.td" +include "mlir/IR/BuiltinAttributes.td" include "mlir/IR/OpAsmInterface.td" +include "mlir/IR/RegionKindInterface.td" class SimOp traits = []> : Op; @@ -359,4 +367,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 6c9c3736751a..84a10716ce65 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 adbd35e5b51a..cfe8bd846517 100644 --- a/lib/Dialect/Sim/SimOps.cpp +++ b/lib/Dialect/Sim/SimOps.cpp @@ -397,6 +397,223 @@ 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(); + } + + // If the current op can be inlined into the parent, + // leave it to the parent's canonicalization. + if (auto parentSeq = op.getParent().getDefiningOp()) { + if (parentSeq == op) { + op.emitWarning("Recursive trigger sequence."); + return failure(); + } + if (op.getParent().hasOneUse()) + return failure(); + } + + auto getSingleSequenceUser = [](Value trigger) -> TriggerSequenceOp { + if (!trigger.hasOneUse()) + return {}; + return dyn_cast(trigger.use_begin()->getOwner()); + }; + + // Check if there are unused results (which can be removed) or + // non-concurrent sub-sequences (which can be inlined). + + bool canBeChanged = false; + for (auto res : op.getResults()) { + auto singleSeqUser = getSingleSequenceUser(res); + if (res.use_empty() || !!singleSeqUser) { + canBeChanged = true; + break; + } + } + + if (!canBeChanged) + return failure(); + + // DFS for inlinable values. + SmallVector newResultValues; + SmallVector inlinedSequences; + llvm::SmallVector> sequenceOpStack; + + sequenceOpStack.push_back({op, 0}); + while (!sequenceOpStack.empty()) { + auto &top = sequenceOpStack.back(); + auto currentSequence = top.first; + unsigned resultIndex = top.second; + + while (resultIndex < currentSequence.getNumResults()) { + auto currentResult = currentSequence.getResult(resultIndex); + // Check we do not walk in a cycle. + if (currentResult == op.getParent()) { + op.emitWarning("Recursive trigger sequence."); + return failure(); + } + + if (auto inlinableChildSequence = getSingleSequenceUser(currentResult)) { + // Save the next result index to visit on the + // stack and put the new sequence on top. + top.second = resultIndex + 1; + sequenceOpStack.push_back({inlinableChildSequence, 0}); + inlinedSequences.push_back(inlinableChildSequence); + inlinableChildSequence->dropAllReferences(); + break; + } + + if (!currentResult.use_empty()) + newResultValues.push_back(currentResult); + resultIndex++; + } + // Pop the sequence off of the stack if we have visited all results. + if (resultIndex >= currentSequence.getNumResults()) + sequenceOpStack.pop_back(); + } + + // Remove dead sequences. + if (newResultValues.empty()) { + for (auto deadSubSequence : inlinedSequences) + rewriter.eraseOp(deadSubSequence); + rewriter.eraseOp(op); + return success(); + } + + // Replace the current operation with a new sequence. + rewriter.setInsertionPoint(op); + + SmallVector inlinedLocs; + inlinedLocs.reserve(inlinedSequences.size() + 1); + inlinedLocs.push_back(op.getLoc()); + for (auto subSequence : inlinedSequences) + inlinedLocs.push_back(subSequence.getLoc()); + auto fusedLoc = FusedLoc::get(op.getContext(), inlinedLocs); + inlinedLocs.clear(); + + auto newOp = rewriter.create(fusedLoc, op.getParent(), + newResultValues.size()); + for (auto [rval, newRes] : llvm::zip(newResultValues, newOp.getResults())) + rewriter.replaceAllUsesWith(rval, newRes); + + for (auto deadSubSequence : inlinedSequences) + rewriter.eraseOp(deadSubSequence); + 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..ebda1e3f26de --- /dev/null +++ b/test/Dialect/Sim/triggers.mlir @@ -0,0 +1,164 @@ +// 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 +} + +// CHECK-LABEL: hw.module @skewed_binary_tree +// CHECK: [[R0:%.*]]:7 = sim.trigger_sequence %trig, 7 +// CHECK-NEXT: hw.output [[R0]]#0, [[R0]]#1, [[R0]]#2, [[R0]]#3, [[R0]]#4, [[R0]]#5, [[R0]]#6 +hw.module @skewed_binary_tree(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) { + %h:2 = sim.trigger_sequence %g#1, 2 : !sim.trigger.edge + %g:2 = sim.trigger_sequence %f#1, 2 : !sim.trigger.edge + %f:2 = sim.trigger_sequence %e#1, 2 : !sim.trigger.edge + %e:2 = sim.trigger_sequence %d#1, 2 : !sim.trigger.edge + %d:2 = sim.trigger_sequence %c#1, 2 : !sim.trigger.edge + %c:2 = sim.trigger_sequence %b#1, 2 : !sim.trigger.edge + %b:2 = sim.trigger_sequence %a#1, 2 : !sim.trigger.edge + %a:2 = sim.trigger_sequence %trig, 2 : !sim.trigger.edge + hw.output %a#0, %b#0, %c#0, %d#0, %f#0, %g#0, %h#0 : !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 @dead_skewed_binary_tree +// CHECK-NOT: sim.trigger_sequence %trig, +hw.module @dead_skewed_binary_tree(in %trig : !sim.trigger.edge) { + %a:2 = sim.trigger_sequence %trig, 2 : !sim.trigger.edge + %b:2 = sim.trigger_sequence %a#1, 2 : !sim.trigger.edge + %c:2 = sim.trigger_sequence %b#1, 2 : !sim.trigger.edge + %d:2 = sim.trigger_sequence %c#1, 2 : !sim.trigger.edge +}