diff --git a/include/circt/Conversion/Passes.h b/include/circt/Conversion/Passes.h index 30722ab98991..98bd987db830 100644 --- a/include/circt/Conversion/Passes.h +++ b/include/circt/Conversion/Passes.h @@ -41,10 +41,13 @@ #include "circt/Conversion/LoopScheduleToCalyx.h" #include "circt/Conversion/MooreToCore.h" #include "circt/Conversion/PipelineToHW.h" +#include "circt/Conversion/PrintsToSV.h" #include "circt/Conversion/SCFToCalyx.h" +#include "circt/Conversion/SCFToSV.h" #include "circt/Conversion/SMTToZ3LLVM.h" #include "circt/Conversion/SeqToSV.h" #include "circt/Conversion/SimToSV.h" +#include "circt/Conversion/TriggersToSV.h" #include "circt/Conversion/VerifToSMT.h" #include "circt/Conversion/VerifToSV.h" #include "mlir/IR/DialectRegistry.h" diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 0f3dc53a7b64..dd310eaa07f9 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -803,6 +803,51 @@ def LowerSimToSV: Pass<"lower-sim-to-sv", "mlir::ModuleOp"> { ]; } +//===----------------------------------------------------------------------===// +// LowerPrintsToSV +//===----------------------------------------------------------------------===// + +def LowerPrintsToSV: Pass<"lower-prints-to-sv", "hw::HWModuleOp"> { + let summary = "Lower `sim` print operations to SV."; + let dependentDialects = [ + "circt::sv::SVDialect" + ]; + let options = [ + Option<"printToStdErr", "stderr-output", "bool", "false", + "Print to STDERR instead of STDOUT"> + ]; +} + +//===----------------------------------------------------------------------===// +// LowerTriggersToSV +//===----------------------------------------------------------------------===// + +def LowerTriggersToSV: Pass<"lower-triggers-to-sv", "mlir::ModuleOp"> { + let summary = "Lower `sim` trigger trees to SV."; + let dependentDialects = [ + "circt::sv::SVDialect", + "circt::hw::HWDialect", + "circt::comb::CombDialect" + ]; + let options = [ + Option<"disallowForkJoin", "no-fork-join", "bool", "false", + "Do not produce concurrent triggered operations with fork/join. " + "An arbitrary non-concurrent sequence will be selected."> + ]; +} + +//===----------------------------------------------------------------------===// +// SCFToSV +//===----------------------------------------------------------------------===// + +def SCFToSV : Pass<"lower-scf-to-sv", "hw::HWModuleOp"> { + let summary = "Lower SCF to SV"; + let dependentDialects = [ + "sv::SVDialect" + ]; +} + + //===----------------------------------------------------------------------===// // ConvertCombToAIG //===----------------------------------------------------------------------===// diff --git a/include/circt/Conversion/PrintsToSV.h b/include/circt/Conversion/PrintsToSV.h new file mode 100644 index 000000000000..e9111b334584 --- /dev/null +++ b/include/circt/Conversion/PrintsToSV.h @@ -0,0 +1,26 @@ +//===- PrintsToSV.h - SV conversion for sim ops -------------===-*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// TODO +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_PRINTSTOSV_H +#define CIRCT_CONVERSION_PRINTSTOSV_H + +#include "circt/Support/LLVM.h" +#include + +namespace circt { + +#define GEN_PASS_DECL_LOWERPRINTSTOSV +#include "circt/Conversion/Passes.h.inc" + +} // namespace circt + +#endif // CIRCT_CONVERSION_PRINTSTOSV_H diff --git a/include/circt/Conversion/SCFToSV.h b/include/circt/Conversion/SCFToSV.h new file mode 100644 index 000000000000..15e8efaa8983 --- /dev/null +++ b/include/circt/Conversion/SCFToSV.h @@ -0,0 +1,26 @@ +//===- SCFToSV.h - SV conversion for scf ops ----------------===-*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// TODO +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_SCFTOSV_H +#define CIRCT_CONVERSION_SCFTOSV_H + +#include "circt/Support/LLVM.h" +#include + +namespace circt { + +#define GEN_PASS_DECL_SCFTOSV +#include "circt/Conversion/Passes.h.inc" + +} // namespace circt + +#endif // CIRCT_CONVERSION_SCFTOSV_H diff --git a/include/circt/Conversion/TriggersToSV.h b/include/circt/Conversion/TriggersToSV.h new file mode 100644 index 000000000000..fee145d3aa34 --- /dev/null +++ b/include/circt/Conversion/TriggersToSV.h @@ -0,0 +1,26 @@ +//===- TriggersToSV.h - SV conversion for sim ops -----------===-*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// TODO +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_TRIGGERSTOSV_H +#define CIRCT_CONVERSION_TRIGGERSTOSV_H + +#include "circt/Support/LLVM.h" +#include + +namespace circt { + +#define GEN_PASS_DECL_LOWERTRIGGERSTOSV +#include "circt/Conversion/Passes.h.inc" + +} // namespace circt + +#endif // CIRCT_CONVERSION_TRIGGERSTOSV_H diff --git a/include/circt/Dialect/SV/SVInOutOps.td b/include/circt/Dialect/SV/SVInOutOps.td index db8dbd0130d6..c568bfa3b92e 100644 --- a/include/circt/Dialect/SV/SVInOutOps.td +++ b/include/circt/Dialect/SV/SVInOutOps.td @@ -106,7 +106,7 @@ def XMROp : SVOp<"xmr", []> { path has public visibility so paths are not invalidated. }]; let arguments = (ins UnitAttr:$isRooted, StrArrayAttr:$path, StrAttr:$terminal); - let results = (outs InOutType:$result); + let results = (outs AnyType:$result); let assemblyFormat = "(`isRooted` $isRooted^)? custom($path, $terminal) attr-dict `:` qualified(type($result))"; } @@ -132,7 +132,7 @@ def XMRRefOp : SVOp<"xmr.ref", [ ins FlatSymbolRefAttr:$ref, DefaultValuedAttr:$verbatimSuffix ); - let results = (outs InOutType:$result); + let results = (outs AnyType:$result); let assemblyFormat = [{ $ref ( $verbatimSuffix^ )? attr-dict `:` qualified(type($result)) }]; @@ -268,3 +268,37 @@ def LogicOp : SVOp<"logic", [ } }]; } + +def BitOp : SVOp<"bit", [ + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { + let summary = "Define a bit"; + let description = [{ + Declare a SystemVerilog Variable Declaration of 'bit' type. + }]; + + let arguments = (ins StrAttr:$name, OptionalAttr:$inner_sym); + let results = (outs InOutType:$result); + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "::mlir::Type":$elementType, + CArg<"StringAttr", "StringAttr()">:$name, + CArg<"hw::InnerSymAttr", "hw::InnerSymAttr()">:$innerSym)>, + OpBuilder<(ins "::mlir::Type":$elementType, CArg<"StringRef">:$name), [{ + return build($_builder, $_state, elementType, + $_builder.getStringAttr(name)); + }]> + ]; + + let assemblyFormat = [{ + (`sym` $inner_sym^)? `` custom($name) attr-dict + `:` qualified(type($result)) + }]; + + let extraClassDeclaration = [{ + Type getElementType() { + return llvm::cast(getResult().getType()).getElementType(); + } + }]; +} diff --git a/include/circt/Dialect/SV/SVStatements.td b/include/circt/Dialect/SV/SVStatements.td index c5a09a082508..08b7883db728 100644 --- a/include/circt/Dialect/SV/SVStatements.td +++ b/include/circt/Dialect/SV/SVStatements.td @@ -342,6 +342,15 @@ def InitialOp : SVOp<"initial", [SingleBlock, NoTerminator, NoRegionArguments, }]; } +def ForkJoinOp : SVOp<"fork_join", [NoTerminator, NoRegionArguments, + RecursiveMemoryEffects, + RecursivelySpeculatable, + ProceduralRegion, ProceduralOp]> { + + let regions = (region VariadicRegion>:$regions); + let assemblyFormat = "attr-dict-with-keyword $regions"; +} + def CaseStmt: I32EnumAttrCase<"CaseStmt", 0, "case">; def CaseXStmt: I32EnumAttrCase<"CaseXStmt", 1, "casex">; def CaseZStmt: I32EnumAttrCase<"CaseZStmt", 2, "casez">; @@ -448,8 +457,6 @@ def PAssignOp : SVOp<"passign", [InOutTypeConstraint<"src", "dest">, let hasVerifier = 1; } - - //===----------------------------------------------------------------------===// // Other Statements //===----------------------------------------------------------------------===// @@ -519,6 +526,20 @@ def FWriteOp : SVOp<"fwrite", [ProceduralOp]> { }]; } +def DisplayOp : SVOp<"display", [ProceduralOp]> { + let summary = "'$display'/'$write' statement"; + + let arguments = (ins StrAttr:$format_string, + Variadic:$substitutions, + UnitAttr:$noNewLine); + let results = (outs); + + let assemblyFormat = [{ + (`nolf` $noNewLine^)? $format_string attr-dict (`(` $substitutions^ `)` `:` + qualified(type($substitutions)))? + }]; +} + def VerbatimOp : SVOp<"verbatim"> { let summary = "Verbatim opaque text emitted inline."; let description = [{ @@ -568,6 +589,18 @@ def MacroRefOp : SVOp<"macro.ref", [ }]; } +def WaitOp : SVOp<"wait", [ProceduralOp]> { + let arguments = (ins I1:$cond); + let results = (outs); + let assemblyFormat = "$cond attr-dict"; +} + +def DelayOp : SVOp<"delay", [ProceduralOp]> { + let arguments = (ins UI32Attr:$delayValue); + let results = (outs); + let assemblyFormat = "$delayValue attr-dict"; +} + //===----------------------------------------------------------------------===// // Bind Statements //===----------------------------------------------------------------------===// diff --git a/include/circt/Dialect/SV/SVVisitors.h b/include/circt/Dialect/SV/SVVisitors.h index 72581ba05c54..b58a5781a9b3 100644 --- a/include/circt/Dialect/SV/SVVisitors.h +++ b/include/circt/Dialect/SV/SVVisitors.h @@ -33,14 +33,14 @@ class Visitor { ConstantXOp, ConstantZOp, ConstantStrOp, MacroRefExprOp, MacroRefExprSEOp, UnpackedArrayCreateOp, UnpackedOpenArrayCastOp, // Declarations. - RegOp, WireOp, LogicOp, LocalParamOp, XMROp, XMRRefOp, + RegOp, WireOp, LogicOp, BitOp, LocalParamOp, XMROp, XMRRefOp, // Control flow. OrderedOutputOp, IfDefOp, IfDefProceduralOp, IfOp, AlwaysOp, - AlwaysCombOp, AlwaysFFOp, InitialOp, CaseOp, + AlwaysCombOp, AlwaysFFOp, InitialOp, CaseOp, ForkJoinOp, // Other Statements. AssignOp, BPAssignOp, PAssignOp, ForceOp, ReleaseOp, AliasOp, FWriteOp, SystemFunctionOp, VerbatimOp, MacroRefOp, FuncCallOp, - FuncCallProceduralOp, ReturnOp, + FuncCallProceduralOp, ReturnOp, WaitOp, DelayOp, DisplayOp, // Type declarations. InterfaceOp, InterfaceSignalOp, InterfaceModportOp, InterfaceInstanceOp, GetModportOp, AssignInterfaceSignalOp, @@ -92,6 +92,7 @@ class Visitor { HANDLE(RegOp, Unhandled); HANDLE(WireOp, Unhandled); HANDLE(LogicOp, Unhandled); + HANDLE(BitOp, Unhandled); HANDLE(LocalParamOp, Unhandled); HANDLE(XMROp, Unhandled); HANDLE(XMRRefOp, Unhandled); @@ -121,6 +122,7 @@ class Visitor { HANDLE(AlwaysCombOp, Unhandled); HANDLE(AlwaysFFOp, Unhandled); HANDLE(InitialOp, Unhandled); + HANDLE(ForkJoinOp, Unhandled); HANDLE(CaseOp, Unhandled); // Other Statements. @@ -137,6 +139,9 @@ class Visitor { HANDLE(ReturnOp, Unhandled); HANDLE(VerbatimOp, Unhandled); HANDLE(MacroRefOp, Unhandled); + HANDLE(WaitOp, Unhandled); + HANDLE(DelayOp, Unhandled); + HANDLE(DisplayOp, Unhandled); // Type declarations. HANDLE(InterfaceOp, Unhandled); diff --git a/include/circt/Dialect/Sim/CMakeLists.txt b/include/circt/Dialect/Sim/CMakeLists.txt index 0fe29d0d1681..888d731be06b 100644 --- a/include/circt/Dialect/Sim/CMakeLists.txt +++ b/include/circt/Dialect/Sim/CMakeLists.txt @@ -2,6 +2,13 @@ add_circt_dialect(Sim sim) add_circt_doc(SimOps Dialects/SimOps -gen-op-doc) add_circt_doc(SimTypes Dialects/SimTypes -gen-typedef-doc -dialect sim) +set(LLVM_TARGET_DEFINITIONS Sim.td) + +mlir_tablegen(SimAttributes.h.inc -gen-attrdef-decls -attrdefs-dialect=sim) +mlir_tablegen(SimAttributes.cpp.inc -gen-attrdef-defs -attrdefs-dialect=sim) +add_public_tablegen_target(MLIRSimAttributesIncGen) +add_dependencies(circt-headers MLIRSimAttributesIncGen) + set(LLVM_TARGET_DEFINITIONS SimPasses.td) mlir_tablegen(SimPasses.h.inc -gen-pass-decls) add_public_tablegen_target(CIRCTSimTransformsIncGen) diff --git a/include/circt/Dialect/Sim/Sim.td b/include/circt/Dialect/Sim/Sim.td index 080ef20420ed..9ded74ddc560 100644 --- a/include/circt/Dialect/Sim/Sim.td +++ b/include/circt/Dialect/Sim/Sim.td @@ -19,7 +19,8 @@ include "mlir/IR/OpAsmInterface.td" include "mlir/IR/SymbolInterfaces.td" include "circt/Dialect/Sim/SimDialect.td" -include "circt/Dialect/Sim/SimOps.td" include "circt/Dialect/Sim/SimTypes.td" +include "circt/Dialect/Sim/SimAttributes.td" +include "circt/Dialect/Sim/SimOps.td" #endif // CIRCT_DIALECT_SIM_SIM_TD diff --git a/include/circt/Dialect/Sim/SimAttributes.h b/include/circt/Dialect/Sim/SimAttributes.h new file mode 100644 index 000000000000..42d64f950b99 --- /dev/null +++ b/include/circt/Dialect/Sim/SimAttributes.h @@ -0,0 +1,19 @@ +//===- SimAttributes.h - Declare Sim dialect attributes ----------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_SIM_SIMATTRIBUTES_H +#define CIRCT_DIALECT_SIM_SIMATTRIBUTES_H + +#include "mlir/IR/Attributes.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/BuiltinTypes.h" + +#define GET_ATTRDEF_CLASSES +#include "circt/Dialect/Sim/SimAttributes.h.inc" + +#endif // CIRCT_DIALECT_SIM_SEQATTRIBUTES_H diff --git a/include/circt/Dialect/Sim/SimAttributes.td b/include/circt/Dialect/Sim/SimAttributes.td new file mode 100644 index 000000000000..a0b77900ad76 --- /dev/null +++ b/include/circt/Dialect/Sim/SimAttributes.td @@ -0,0 +1,23 @@ +//===- SimAttributes.td - Attributes for Sim dialect -------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_SIM_SIMATTRIBUTES_TD +#define CIRCT_DIALECT_SIM_SIMATTRIBUTES_TD + +include "circt/Dialect/Sim/SimDialect.td" +include "mlir/IR/BuiltinAttributeInterfaces.td" + +def NeverTriggerAttr : AttrDef { + let mnemonic = "never"; + let parameters = (ins AttributeSelfTypeParameter<"">:$type); + let assemblyFormat = ""; + let genVerifyDecl = true; +} + +#endif // CIRCT_DIALECT_SIM_SIMATTRIBUTES_TD diff --git a/include/circt/Dialect/Sim/SimDialect.td b/include/circt/Dialect/Sim/SimDialect.td index d17de4efc7b0..bf392c9b112d 100644 --- a/include/circt/Dialect/Sim/SimDialect.td +++ b/include/circt/Dialect/Sim/SimDialect.td @@ -26,13 +26,15 @@ def SimDialect : Dialect { let dependentDialects = ["circt::hw::HWDialect"]; - let useDefaultAttributePrinterParser = 0; + let useDefaultAttributePrinterParser = 1; let useDefaultTypePrinterParser = 1; let hasConstantMaterializer = 1; let extraClassDeclaration = [{ /// Register all Sim types. void registerTypes(); + /// Register all attributes. + void registerAttributes(); }]; } 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..9cf9c2a1d0b5 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,147 @@ 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 hasFolder = true; + 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 NeverOp : SimOp<"never", [Pure, ConstantLike]> { + let summary = "TODO."; + let results = (outs AnyTriggerType:$result); + let assemblyFormat = "attr-dict `:` qualified(type($result))"; + let hasFolder = true; +} + +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 TriggerGateOp : SimOp<"trigger_gate", [ + Pure, TypesMatchWith<"input and output trigger type must be identical", + "input", "output", "$_self"> +]> { + let arguments = (ins AnyTriggerType:$input, I1:$enable); + let results = (outs AnyTriggerType:$output); + let assemblyFormat = + "$input `if` $enable attr-dict `:` qualified(type($input))"; + let hasFolder = true; + let hasCanonicalizeMethod = true; +} + +def TriggeredOp : SimOp<"triggered", [ + 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, + Variadic:$inputs, + OptionalAttr>:$tieoffs + ); + let results = (outs Variadic); + let regions = (region SizedRegion<1>:$body); + + let assemblyFormat = [{ + ` ` `(` $inputs `)` + `on` ` ` `(` $trigger `:` qualified(type($trigger)) `)` + (`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; + + let builders = [ + OpBuilder<(ins "Value":$trigger, + "std::function":$bodyCtor)>, + OpBuilder<(ins "Value":$trigger, + CArg<"ValueRange", "{}">:$arguments, + CArg<"std::function", "{}">:$bodyCtor)>, + ]; +} + #endif // CIRCT_DIALECT_SIM_SIMOPS_TD diff --git a/include/circt/Dialect/Sim/SimPasses.td b/include/circt/Dialect/Sim/SimPasses.td index b2e6d8e98e23..860e0913e96f 100644 --- a/include/circt/Dialect/Sim/SimPasses.td +++ b/include/circt/Dialect/Sim/SimPasses.td @@ -28,4 +28,9 @@ def LowerDPIFunc : Pass<"sim-lower-dpi-func", "mlir::ModuleOp"> { let dependentDialects = ["mlir::func::FuncDialect", "mlir::LLVM::LLVMDialect"]; } +def SerializeTriggers : Pass<"sim-serialize-triggers", "hw::HWModuleOp"> { + let summary = "TODO"; +} + + #endif // CIRCT_DIALECT_SIM_SEQPASSES diff --git a/include/circt/Dialect/Sim/SimTypes.h b/include/circt/Dialect/Sim/SimTypes.h index 669eaaf14f5d..2e06f9ed6a9b 100644 --- a/include/circt/Dialect/Sim/SimTypes.h +++ b/include/circt/Dialect/Sim/SimTypes.h @@ -12,7 +12,18 @@ #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" +namespace circt { +namespace sim { +static inline bool isTriigerType(::mlir::Type ty) { + return isa_and_nonnull(ty); +} +} // namespace sim +} // namespace circt + #endif // CIRCT_DIALECT_SIM_SIMTYPES_H 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/CAPI/Conversion/CMakeLists.txt b/lib/CAPI/Conversion/CMakeLists.txt index fcb480f3e63e..c2e576560a12 100644 --- a/lib/CAPI/Conversion/CMakeLists.txt +++ b/lib/CAPI/Conversion/CMakeLists.txt @@ -31,6 +31,7 @@ add_circt_public_c_api_library(CIRCTCAPIConversion CIRCTMooreToCore CIRCTPipelineToHW CIRCTSCFToCalyx + CIRCTSCFToSV CIRCTSeqToSV CIRCTSimToSV CIRCTSMTToZ3LLVM diff --git a/lib/Conversion/CMakeLists.txt b/lib/Conversion/CMakeLists.txt index 61ddd4965cab..0cba81e1f58e 100644 --- a/lib/Conversion/CMakeLists.txt +++ b/lib/Conversion/CMakeLists.txt @@ -25,6 +25,7 @@ add_subdirectory(LoopScheduleToCalyx) add_subdirectory(MooreToCore) add_subdirectory(PipelineToHW) add_subdirectory(SCFToCalyx) +add_subdirectory(SCFToSV) add_subdirectory(SeqToSV) add_subdirectory(SimToSV) add_subdirectory(CFToHandshake) diff --git a/lib/Conversion/ExportVerilog/ExportVerilog.cpp b/lib/Conversion/ExportVerilog/ExportVerilog.cpp index d95f992a19ee..9cb139280398 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilog.cpp +++ b/lib/Conversion/ExportVerilog/ExportVerilog.cpp @@ -176,7 +176,7 @@ static bool isDuplicatableExpression(Operation *op) { if (auto read = dyn_cast(indexOp)) { auto *readSrc = read.getInput().getDefiningOp(); // A port or wire is ok to duplicate reads. - return !readSrc || isa(readSrc); + return !readSrc || isa(readSrc); } return false; @@ -1368,7 +1368,7 @@ StringAttr ExportVerilog::inferStructuralNameForTemporary(Value expr) { } else if (auto *op = expr.getDefiningOp()) { // Uses of a wire, register or logic can be done inline. - if (isa(op)) { + if (isa(op)) { StringRef name = getSymOpName(op); result = StringAttr::get(expr.getContext(), name); @@ -1569,7 +1569,7 @@ static StringRef getVerilogDeclWord(Operation *op, // "automatic" is added to its definition. bool stripAutomatic = isa_and_nonnull(emitter.currentModuleOp); - if (isa(op)) { + if (isa(op)) { // If the logic op is defined in a procedural region, add 'automatic' // keyword. If the op has a struct type, 'logic' keyword is already emitted // within a struct type definition (e.g. struct packed {logic foo;}). So we @@ -1577,7 +1577,7 @@ static StringRef getVerilogDeclWord(Operation *op, bool hasStruct = hasStructType(op->getResult(0).getType()); if (isProcedural && !stripAutomatic) return hasStruct ? "automatic" : "automatic logic"; - return hasStruct ? "" : "logic"; + return hasStruct ? "" : (isa(op) ? "bit" : "logic"); } if (!isProcedural) @@ -3949,6 +3949,7 @@ class StmtEmitter : public EmitterBase, LogicalResult visitSV(sv::WireOp op) { return emitDeclaration(op); } LogicalResult visitSV(RegOp op) { return emitDeclaration(op); } LogicalResult visitSV(LogicOp op) { return emitDeclaration(op); } + LogicalResult visitSV(BitOp op) { return emitDeclaration(op); } LogicalResult visitSV(LocalParamOp op) { return emitDeclaration(op); } template LogicalResult @@ -3961,6 +3962,7 @@ class StmtEmitter : public EmitterBase, LogicalResult visitSV(AssignOp op); LogicalResult visitSV(BPAssignOp op); LogicalResult visitSV(PAssignOp op); + LogicalResult visitSV(DelayOp op); LogicalResult visitSV(ForceOp op); LogicalResult visitSV(ReleaseOp op); LogicalResult visitSV(AliasOp op); @@ -3987,10 +3989,13 @@ class StmtEmitter : public EmitterBase, LogicalResult visitSV(AlwaysCombOp op); LogicalResult visitSV(AlwaysFFOp op); LogicalResult visitSV(InitialOp op); + LogicalResult visitSV(ForkJoinOp op); LogicalResult visitSV(CaseOp op); LogicalResult visitSV(FWriteOp op); + LogicalResult visitSV(DisplayOp op); LogicalResult visitSV(VerbatimOp op); LogicalResult visitSV(MacroRefOp op); + LogicalResult visitSV(WaitOp op); LogicalResult emitSimulationControlTask(Operation *op, PPExtString taskName, std::optional verbosity); @@ -4168,6 +4173,26 @@ LogicalResult StmtEmitter::visitSV(PAssignOp op) { return emitAssignLike(op, PPExtString("<=")); } +LogicalResult StmtEmitter::visitSV(DelayOp op) { + // Emit SV attributes. See Spec 12.3. + emitSVAttributes(op); + + SmallPtrSet ops; + ops.insert(op); + + startStatement(); + ps.addCallback({op, true}); + + ps.scopedBox(PP::ibox2, [&]() { + ps << PPExtString("#") << PPSaveString(std::to_string(op.getDelayValue())) + << PPExtString(";"); + }); + + ps.addCallback({op, false}); + emitLocationInfoAndNewLine(ops); + return success(); +} + LogicalResult StmtEmitter::visitSV(ForceOp op) { if (hasSVAttributes(op)) emitError(op, "SV attributes emission is unimplemented for the op"); @@ -4493,6 +4518,36 @@ LogicalResult StmtEmitter::visitSV(FWriteOp op) { return success(); } +LogicalResult StmtEmitter::visitSV(DisplayOp op) { + if (hasSVAttributes(op)) + emitError(op, "SV attributes emission is unimplemented for the op"); + + startStatement(); + SmallPtrSet ops; + ops.insert(op); + + ps.addCallback({op, true}); + ps << (op.getNoNewLine() ? "$write(" : "$display("); + ps.scopedBox(PP::ibox0, [&]() { + ps.writeQuotedEscaped(op.getFormatString()); + + // TODO: if any of these breaks, it'd be "nice" to break + // after the comma, instead of: + // $fwrite(5, "...", a + b, + // longexpr_goes + // + here, c); + // (without forcing breaking between all elements, like braced list) + for (auto operand : op.getSubstitutions()) { + ps << "," << PP::space; + emitExpression(operand, ops); + } + ps << ");"; + }); + ps.addCallback({op, false}); + emitLocationInfoAndNewLine(ops); + return success(); +} + LogicalResult StmtEmitter::visitSV(VerbatimOp op) { if (hasSVAttributes(op)) emitError(op, "SV attributes emission is unimplemented for the op"); @@ -4564,6 +4619,24 @@ LogicalResult StmtEmitter::visitSV(MacroRefOp op) { return success(); } +LogicalResult StmtEmitter::visitSV(WaitOp op) { + if (hasSVAttributes(op)) + emitError(op, "SV attributes emission is unimplemented for the op"); + + startStatement(); + SmallPtrSet ops; + ops.insert(op); + ps.addCallback({op, true}); + ps << "wait" + << "("; + ps.scopedBox(PP::ibox0, [&]() { emitExpression(op.getCond(), ops); }); + ps << ")" + << ";"; + ps.addCallback({op, false}); + emitLocationInfoAndNewLine(ops); + return success(); +} + /// Emit one of the simulation control tasks `$stop`, `$finish`, or `$exit`. LogicalResult StmtEmitter::emitSimulationControlTask(Operation *op, PPExtString taskName, @@ -5271,6 +5344,33 @@ LogicalResult StmtEmitter::visitSV(InitialOp op) { return success(); } +LogicalResult StmtEmitter::visitSV(ForkJoinOp op) { + emitSVAttributes(op); + SmallPtrSet ops, emptyOps; + ops.insert(op); + startStatement(); + ps.addCallback({op, true}); + ps << "fork"; + emitLocationInfoAndNewLine(ops); + ps.scopedBox(PP::bbox2, [&]() { + for (auto ®ion : op.getRegions()) { + auto *block = ®ion.front(); + auto count = countStatements(*block); + if (count == BlockStatementCount::One) + state.pendingNewline = false; + else + startStatement(); + emitBlockAsStatement(block, emptyOps); + } + }); + + startStatement(); + ps << "join"; + ps.addCallback({op, false}); + emitLocationInfoAndNewLine(ops); + return success(); +} + LogicalResult StmtEmitter::visitSV(CaseOp op) { emitSVAttributes(op); SmallPtrSet ops, emptyOps; @@ -5729,13 +5829,13 @@ isExpressionEmittedInlineIntoProceduralDeclaration(Operation *op, // Reject struct_field_inout/array_index_inout for now because it's // necessary to consider aliasing inout operations. - if (!isa(defOp)) + if (!isa(defOp)) return false; // It's safe to inline if all users are read op, passign or assign. // If the op is a logic op whose single assignment is inlined into // declaration, we can inline the read. - if (isa(defOp) && + if (isa(defOp) && stmtEmitter.emitter.expressionsEmittedIntoDecl.count(defOp)) continue; @@ -5921,7 +6021,8 @@ LogicalResult StmtEmitter::emitDeclaration(Operation *op) { // Try inlining a blocking assignment to logic op declaration. // FIXME: Unpacked array is not inlined since several tools doesn't support // that syntax. See Issue 6363. - if (isa(op) && op->getParentOp()->hasTrait() && + if (isa(op) && + op->getParentOp()->hasTrait() && !hasLeadingUnpackedType(op->getResult(0).getType())) { // Get a single assignment which might be possible to inline. if (auto singleAssign = getSingleAssignAndCheckUsers(op)) { diff --git a/lib/Conversion/ExportVerilog/LegalizeNames.cpp b/lib/Conversion/ExportVerilog/LegalizeNames.cpp index ad9b736d6a01..9a8f95a59c1c 100644 --- a/lib/Conversion/ExportVerilog/LegalizeNames.cpp +++ b/lib/Conversion/ExportVerilog/LegalizeNames.cpp @@ -198,9 +198,9 @@ static void legalizeModuleLocalNames(HWEmittableModuleLike module, if (auto name = op->getAttrOfType(verilogNameAttr)) { nameResolver.insertUsedName( op->getAttrOfType(verilogNameAttr)); - } else if (isa( - op)) { + } else if (isa(op)) { // Otherwise, get a verilog name via `getSymOpName`. nameEntries.emplace_back( op, StringAttr::get(op->getContext(), getSymOpName(op))); diff --git a/lib/Conversion/ExportVerilog/PrepareForEmission.cpp b/lib/Conversion/ExportVerilog/PrepareForEmission.cpp index 22759bb3c4b4..8c60d3c29840 100644 --- a/lib/Conversion/ExportVerilog/PrepareForEmission.cpp +++ b/lib/Conversion/ExportVerilog/PrepareForEmission.cpp @@ -61,7 +61,7 @@ bool ExportVerilog::isSimpleReadOrPort(Value v) { auto readSrc = read.getInput().getDefiningOp(); if (!readSrc) return false; - return isa(readSrc); + return isa(readSrc); } // Check if the value is deemed worth spilling into a wire. @@ -973,7 +973,7 @@ static LogicalResult legalizeHWModule(Block &block, // If a reg or logic is located in a procedural region, we have to move the // op declaration to a valid program point. - if (isProceduralRegion && isa(op)) { + if (isProceduralRegion && isa(op)) { if (options.disallowLocalVariables) { // When `disallowLocalVariables` is enabled, "automatic logic" is // prohibited so hoist the op to a non-procedural region. @@ -1096,7 +1096,7 @@ static LogicalResult legalizeHWModule(Block &block, Value readOp; if (auto maybeReadOp = op.getOperand(1).getDefiningOp()) { - if (isa_and_nonnull( + if (isa_and_nonnull( maybeReadOp.getInput().getDefiningOp())) { wireOp = maybeReadOp.getInput(); readOp = maybeReadOp; diff --git a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp index ecfb5d9c72c4..ca5e67b56cd6 100644 --- a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp +++ b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp @@ -1463,6 +1463,7 @@ struct FIRRTLLowering : public FIRRTLVisitor { Value getPossiblyInoutLoweredValue(Value value); Value getLoweredValue(Value value); Value getLoweredNonClockValue(Value value); + Value getPosedgeTriggerForClock(Value value); Value getLoweredAndExtendedValue(Value value, Type destType); Value getLoweredAndExtOrTruncValue(Value value, Type destType); Value getLoweredFmtOperand(Value operand); @@ -1763,6 +1764,8 @@ struct FIRRTLLowering : public FIRRTLVisitor { /// via a deduped `seq.from_clock` op. DenseMap fromClockMapping; + DenseMap clockToTriggerMapping; + /// This keeps track of constants that we have created so we can reuse them. /// This is populated by the getOrCreateIntConstant method. DenseMap hwConstantMap; @@ -1773,6 +1776,9 @@ struct FIRRTLLowering : public FIRRTLVisitor { DenseMap hwConstantXMap; DenseMap hwConstantZMap; + /// Cache for printf stirng literal tokens. + // llvm::StringMap printLiteralMap; + /// We auto-unique "ReadInOut" ops from wires and regs, enabling /// optimizations and CSEs of the read values to be more obvious. This /// caches a known ReadInOutOp for the given value and is managed by @@ -2109,6 +2115,20 @@ Value FIRRTLLowering::getLoweredNonClockValue(Value value) { return result; } +Value FIRRTLLowering::getPosedgeTriggerForClock(Value value) { + auto &cached = clockToTriggerMapping[value]; + if (cached) + return cached; + + auto loweredClock = getLoweredValue(value); + if (!isa(loweredClock.getType())) + loweredClock = builder.createOrFold(loweredClock); + + cached = builder.createOrFold(loweredClock, + hw::EventControl::AtPosEdge); + return cached; +} + /// Return the lowered aggregate value whose type is converted into /// `destType`. We have to care about the extension/truncation/signedness of /// each element. @@ -4395,42 +4415,119 @@ LogicalResult FIRRTLLowering::visitStmt(RefReleaseInitialOp op) { return success(); } -// Printf is a macro op that lowers to an sv.ifdef.procedural, an sv.if, -// and an sv.fwrite all nested together. LogicalResult FIRRTLLowering::visitStmt(PrintFOp op) { - auto clock = getLoweredNonClockValue(op.getClock()); + auto trigger = getPosedgeTriggerForClock(op.getClock()); auto cond = getLoweredValue(op.getCond()); - if (!clock || !cond) + if (!trigger || !cond) return failure(); SmallVector operands; operands.reserve(op.getSubstitutions().size()); for (auto operand : op.getSubstitutions()) { - Value loweredValue = getLoweredFmtOperand(operand); + Value loweredValue = getLoweredValue(operand); if (!loweredValue) return failure(); operands.push_back(loweredValue); } - // Emit an "#ifndef SYNTHESIS" guard into the always block. - circuitState.addMacroDecl(builder.getStringAttr("SYNTHESIS")); - addToIfDefBlock("SYNTHESIS", std::function(), [&]() { - addToAlwaysBlock(clock, [&]() { - circuitState.usedPrintfCond = true; - circuitState.addFragment(theModule, "PRINTF_COND_FRAGMENT"); - - // Emit an "sv.if '`PRINTF_COND_ & cond' into the #ifndef. - Value ifCond = - builder.create(cond.getType(), "PRINTF_COND_"); - ifCond = builder.createOrFold(ifCond, cond, true); - - addIfProceduralBlock(ifCond, [&]() { - // Emit the sv.fwrite, writing to stderr by default. - Value fdStderr = builder.create(APInt(32, 0x80000002)); - builder.create(fdStderr, op.getFormatString(), operands); - }); - }); - }); + circuitState.usedPrintfCond = true; + circuitState.addFragment(theModule, "PRINTF_COND_FRAGMENT"); + Value ifCond = + builder.create(cond.getType(), "PRINTF_COND_"); + ifCond = builder.createOrFold(ifCond, cond, true); + auto gateOp = builder.createOrFold(trigger, ifCond); + auto triggeredOp = builder.create(gateOp, operands); + auto &triggeredBody = triggeredOp.getBody().front(); + ImplicitLocOpBuilder triggeredBodyBuilder(builder.getLoc(), &triggeredBody, + triggeredBody.begin()); + + SmallVector tokens; + tokens.reserve(1 + 2 * op.getSubstitutions().size()); + auto fmtStr = op.getFormatString().str(); + auto fmtStrType = sim::FormatStringType::get(builder.getContext()); + + size_t pos = 0; + size_t skip = 0; + size_t substIndex = 0; + + // Split the format string into a list of tokens which are either string + // literals or formatting substitutions. + while (pos < fmtStr.size()) { + // Look for the next substitution. + auto substPos = fmtStr.find('%', pos + skip); + + if (substPos == std::string::npos) { + // No more substitutions. Create a literal for the remaining string. + auto tailToken = fmtStr.substr(pos, fmtStr.size() - pos); + auto lit = triggeredBodyBuilder.create( + sim::FormatStringType::get(builder.getContext()), tailToken); + tokens.push_back(lit); + break; + } + + // '%' at the last position? + if (substPos == fmtStr.size() - 1) + return op.emitError("Incomplete substitution in format string"); + + char fmtChar = fmtStr[substPos + 1]; + + // "%%" is not really a substitution. Remove one '%' and continue searching + // beyond it. + if (fmtChar == '%') { + fmtStr.erase(substPos, 1); + skip = substPos + 1 - pos; + continue; + } + + // Found an actual substitution. Create a literal for the preceeding string + // first. + if (substPos > pos) { + auto tailToken = fmtStr.substr(pos, substPos - pos); + auto lit = triggeredBodyBuilder.create( + sim::FormatStringType::get(builder.getContext()), tailToken); + tokens.push_back(lit); + } + + // Then create the matching substitution token. + assert(substIndex < operands.size() && + "Found more substitutions than operands."); + Value substToken; + if (fmtChar == 'x') + substToken = triggeredBodyBuilder.createOrFold( + fmtStrType, triggeredBody.getArgument(substIndex)); + else if (fmtChar == 'd') + substToken = triggeredBodyBuilder.createOrFold( + fmtStrType, triggeredBody.getArgument(substIndex), + type_cast(op.getSubstitutions()[substIndex].getType()) + .isSigned()); + else if (fmtChar == 'b') + substToken = triggeredBodyBuilder.createOrFold( + fmtStrType, triggeredBody.getArgument(substIndex)); + // Note: %c substitutions are not defined in the FIRRTL spec, but Chisel + // allows them. + else if (fmtChar == 'c') + substToken = triggeredBodyBuilder.createOrFold( + fmtStrType, triggeredBody.getArgument(substIndex)); + else + return op.emitError("Invalid substitution in format string: %" + + Twine(fmtChar)); + + tokens.push_back(substToken); + substIndex++; + + skip = 0; + pos = substPos + 2; + } + assert(!tokens.empty() && "No formatting tokens created"); + + Value combinedStrings; + if (tokens.size() > 1) + combinedStrings = + triggeredBodyBuilder.createOrFold(fmtStrType, + tokens); + else + combinedStrings = tokens.front(); + triggeredBodyBuilder.createOrFold(combinedStrings); return success(); } diff --git a/lib/Conversion/SCFToSV/CMakeLists.txt b/lib/Conversion/SCFToSV/CMakeLists.txt new file mode 100644 index 000000000000..7938a4c92052 --- /dev/null +++ b/lib/Conversion/SCFToSV/CMakeLists.txt @@ -0,0 +1,15 @@ +add_circt_conversion_library(CIRCTSCFToSV + SCFToSV.cpp + + DEPENDS + CIRCTConversionPassIncGen + + LINK_COMPONENTS + Core + + LINK_LIBS PUBLIC + CIRCTHW + CIRCTSV + MLIRSCFDialect + MLIRTransforms +) diff --git a/lib/Conversion/SCFToSV/SCFToSV.cpp b/lib/Conversion/SCFToSV/SCFToSV.cpp new file mode 100644 index 000000000000..23fd2ec88f1c --- /dev/null +++ b/lib/Conversion/SCFToSV/SCFToSV.cpp @@ -0,0 +1,113 @@ +//===- SCFToSV.cpp - Sim to SV lowering -----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// TODO +// +//===----------------------------------------------------------------------===// + +#include "circt/Conversion/SCFToSV.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/SV/SVOps.h" +#include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/DialectImplementation.h" +#include "mlir/IR/ImplicitLocOpBuilder.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" + +#define DEBUG_TYPE "lower-scf-to-sv" + +namespace circt { +#define GEN_PASS_DEF_SCFTOSV +#include "circt/Conversion/Passes.h.inc" +} // namespace circt + +using namespace circt; +using namespace mlir; + +struct IfOpConversionPattern : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(scf::IfOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + if (!llvm::all_of(op.getResults(), [](auto res) { + return llvm::isa(res.getType()); + })) { + op.emitError("Cannot convert non-integer result to SV"); + return failure(); + } + + if (adaptor.getThenRegion().getBlocks().size() > 1 || + adaptor.getElseRegion().getBlocks().size() > 1) { + op.emitError("Cannot convert scf.if region with more than one block."); + return failure(); + } + + SmallVector resultDecls; + auto assignResults = [&](scf::YieldOp yieldOp) { + assert(resultDecls.size() == yieldOp.getNumOperands()); + for (auto [decl, yield] : llvm::zip(resultDecls, yieldOp.getOperands())) + rewriter.create(yieldOp.getLoc(), decl.getResult(), + yield); + }; + + for (auto res : op.getResults()) + resultDecls.push_back( + rewriter.create(op.getLoc(), res.getType())); + + auto svIfOp = + rewriter.create(op.getLoc(), adaptor.getCondition()); + if (!adaptor.getThenRegion().empty()) { + auto yield = + cast(adaptor.getThenRegion().front().getTerminator()); + rewriter.mergeBlocks(&adaptor.getThenRegion().front(), + svIfOp.getThenBlock()); + rewriter.setInsertionPointToEnd(svIfOp.getThenBlock()); + assignResults(yield); + rewriter.eraseOp(yield); + } + + if (!adaptor.getElseRegion().empty()) { + auto yield = + cast(adaptor.getElseRegion().front().getTerminator()); + auto dest = rewriter.createBlock(&svIfOp.getElseRegion()); + rewriter.eraseOp(adaptor.getElseRegion().front().getTerminator()); + rewriter.mergeBlocks(&adaptor.getElseRegion().front(), dest); + rewriter.setInsertionPointToEnd(svIfOp.getElseBlock()); + assignResults(yield); + rewriter.eraseOp(yield); + } + + SmallVector reads; + for (auto decl : resultDecls) + reads.push_back( + rewriter.createOrFold(op.getLoc(), decl)); + + rewriter.replaceOp(op, reads); + return success(); + } +}; + +namespace { +struct SCFToSVPass : public circt::impl::SCFToSVBase { + void runOnOperation() override { + auto context = &getContext(); + ConversionTarget target(*context); + RewritePatternSet patterns(context); + target.addIllegalDialect(); + target.addLegalDialect(); + patterns.add(context); + + auto result = + applyPartialConversion(getOperation(), target, std::move(patterns)); + if (failed(result)) + signalPassFailure(); + } +}; +} // namespace diff --git a/lib/Conversion/SimToSV/CMakeLists.txt b/lib/Conversion/SimToSV/CMakeLists.txt index d7afea62d40e..24f6516b863a 100644 --- a/lib/Conversion/SimToSV/CMakeLists.txt +++ b/lib/Conversion/SimToSV/CMakeLists.txt @@ -1,5 +1,7 @@ add_circt_conversion_library(CIRCTSimToSV + PrintsToSV.cpp SimToSV.cpp + TriggersToSV.cpp DEPENDS CIRCTConversionPassIncGen diff --git a/lib/Conversion/SimToSV/PrintsToSV.cpp b/lib/Conversion/SimToSV/PrintsToSV.cpp new file mode 100644 index 000000000000..9ddab0ff7045 --- /dev/null +++ b/lib/Conversion/SimToSV/PrintsToSV.cpp @@ -0,0 +1,196 @@ +//===- PrintsToSV.cpp - Sim to SV lowering --------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// TODO +// +//===----------------------------------------------------------------------===// + +#include "circt/Conversion/PrintsToSV.h" +#include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Dialect/HW/HWInstanceGraph.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/HW/InnerSymbolNamespace.h" +#include "circt/Dialect/SV/SVOps.h" +#include "circt/Dialect/Sim/SimDialect.h" +#include "circt/Dialect/Sim/SimOps.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/DialectImplementation.h" +#include "mlir/IR/ImplicitLocOpBuilder.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" + +#define DEBUG_TYPE "lower-prints-to-sv" + +namespace circt { +#define GEN_PASS_DEF_LOWERPRINTSTOSV +#include "circt/Conversion/Passes.h.inc" +} // namespace circt + +using namespace circt; +using namespace sim; + +static void cleanUpFormatStringTree(ArrayRef deadFmts) { + SmallVector cleanupList; + SmallVector cleanupNextList; + SmallPtrSet erasedOps; + + for (auto deadFmt : deadFmts) { + cleanupNextList.push_back(deadFmt.getInput().getDefiningOp()); + erasedOps.insert(deadFmt); + deadFmt.erase(); + } + + bool hasChanged = true; + while (hasChanged && !cleanupNextList.empty()) { + cleanupList = std::move(cleanupNextList); + cleanupNextList.clear(); + hasChanged = false; + + for (auto op : cleanupList) { + if (!op || erasedOps.contains(op)) + continue; + + if (auto concat = dyn_cast(op)) { + if (!concat->use_empty()) { + cleanupNextList.push_back(concat); + continue; + } + for (auto arg : concat.getInputs()) + cleanupNextList.emplace_back(arg.getDefiningOp()); + hasChanged = true; + erasedOps.insert(concat); + concat.erase(); + continue; + } + + if (isa( + op)) { + if (op->use_empty()) { + erasedOps.insert(op); + op->erase(); + } else { + cleanupNextList.push_back(op); + } + continue; + } + } + } +} +struct PrintsToSVPass + : public circt::impl::LowerPrintsToSVBase { + + using circt::impl::LowerPrintsToSVBase::LowerPrintsToSVBase; + + void runOnOperation() override { + SmallVector printCleanupList; + + bool hasFailed = false; + getOperation().getBodyBlock()->walk([&](PrintFormattedProcOp printOp) { + OpBuilder builder(printOp); + if (failed(lowerProcPrint(builder, printOp))) + hasFailed = true; + printCleanupList.push_back(printOp); + }); + + if (hasFailed) { + signalPassFailure(); + return; + } + cleanUpFormatStringTree(printCleanupList); + }; + +private: + LogicalResult lowerProcPrint(OpBuilder &builder, + PrintFormattedProcOp printOp); +}; + +LogicalResult PrintsToSVPass::lowerProcPrint(OpBuilder &builder, + PrintFormattedProcOp printOp) { + SmallVector flatString; + if (auto concat = printOp.getInput().getDefiningOp()) { + auto isAcyclic = concat.getFlattenedInputs(flatString); + if (failed(isAcyclic)) + return printOp.emitOpError("Format string is cyclic."); + } else { + flatString.push_back(printOp.getInput()); + } + + SmallString<64> fmtString; + SmallVector substitutions; + SmallVector locs; + for (auto fmt : flatString) { + auto defOp = fmt.getDefiningOp(); + if (!defOp) + return printOp.emitError( + "Formatting tokens must not be passed as arguments."); + bool ok = + llvm::TypeSwitch(defOp) + .Case([&](auto literal) { + fmtString.reserve(fmtString.size() + literal.getLiteral().size()); + for (auto c : literal.getLiteral()) { + fmtString.push_back(c); + if (c == '%') + fmtString.push_back('%'); + } + return true; + }) + .Case([&](auto bin) { + fmtString.push_back('%'); + fmtString.push_back('b'); + substitutions.push_back(bin.getValue()); + return true; + }) + .Case([&](auto dec) { + fmtString.push_back('%'); + fmtString.push_back('d'); + Type ty = dec.getValue().getType(); + Value conv = builder.createOrFold( + dec.getLoc(), ty, dec.getIsSigned() ? "signed" : "unsigned", + dec.getValue()); + substitutions.push_back(conv); + return true; + }) + .Case([&](auto hex) { + fmtString.push_back('%'); + fmtString.push_back('x'); + substitutions.push_back(hex.getValue()); + return true; + }) + .Case([&](auto c) { + fmtString.push_back('%'); + fmtString.push_back('c'); + substitutions.push_back(c.getValue()); + return true; + }) + .Default([&](Operation *op) { + op->emitError("Unsupported format specifier op."); + return false; + }); + if (!ok) + return failure(); + locs.push_back(defOp->getLoc()); + } + locs.push_back(printOp.getLoc()); + if (fmtString.empty()) + return success(); + + auto fusedLoc = FusedLoc::get(builder.getContext(), locs); + if (printToStdErr) { + Value stdErr = builder.createOrFold( + printOp.getLoc(), builder.getI32IntegerAttr(0x80000002)); + builder.create( + fusedLoc, stdErr, builder.getStringAttr(fmtString), substitutions); + } else { + bool implicitNewline = (fmtString.back() == '\n'); + if (implicitNewline) + fmtString.pop_back(); + builder.create(fusedLoc, builder.getStringAttr(fmtString), + substitutions, !implicitNewline); + } + return success(); +} diff --git a/lib/Conversion/SimToSV/TriggersToSV.cpp b/lib/Conversion/SimToSV/TriggersToSV.cpp new file mode 100644 index 000000000000..2a3736a132ae --- /dev/null +++ b/lib/Conversion/SimToSV/TriggersToSV.cpp @@ -0,0 +1,773 @@ +//===- TriggersToSV.cpp - Sim to SV lowering ------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// TODO +// +//===----------------------------------------------------------------------===// + +#include "circt/Conversion/TriggersToSV.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Dialect/HW/HWInstanceGraph.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/HW/InnerSymbolNamespace.h" +#include "circt/Dialect/SV/SVOps.h" +#include "circt/Dialect/Sim/SimDialect.h" +#include "circt/Dialect/Sim/SimOps.h" +#include "circt/Support/Namespace.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/DialectImplementation.h" +#include "mlir/IR/ImplicitLocOpBuilder.h" +#include "mlir/IR/Threading.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" + +#define DEBUG_TYPE "lower-triggers-to-sv" + +namespace circt { +#define GEN_PASS_DEF_LOWERTRIGGERSTOSV +#include "circt/Conversion/Passes.h.inc" +} // namespace circt + +using namespace circt; +using namespace sim; + +namespace circt::sim::detail { +using IfDefGuardMap = llvm::SmallDenseMap; +} + +static inline bool isTriggerType(const mlir::Type ty) { + return isa(ty); +} + +static inline sv::EventControl convertEventControl(hw::EventControl eventCtrl) { + switch (eventCtrl) { + case hw::EventControl::AtEdge: + return sv::EventControl::AtEdge; + case hw::EventControl::AtPosEdge: + return sv::EventControl::AtPosEdge; + case hw::EventControl::AtNegEdge: + return sv::EventControl::AtNegEdge; + } + assert(false && "Invalid event control attr"); +} + +static bool isBitToTriggerCast(mlir::UnrealizedConversionCastOp castOp) { + if (!castOp || castOp.getNumResults() != 1 || castOp.getNumOperands() != 1) + return false; + auto ty = castOp.getResult(0).getType(); + if (!isTriggerType(ty)) + return false; + if (auto inout = dyn_cast(castOp.getOperand(0).getType())) { + if (!isa(inout.getElementType())) + return false; + return inout.getElementType().getIntOrFloatBitWidth() == 1; + } + return false; +} + +static void collectTriggeredOps(Value root, + SmallVector &trigOps) { + SmallVector worklist; + worklist.push_back(root); + while (!worklist.empty()) { + auto workItem = worklist.pop_back_val(); + for (auto user : workItem.getUsers()) { + if (auto trigOp = dyn_cast(user)) { + trigOps.push_back(trigOp); + } else if (auto trigOp = dyn_cast(user)) { + worklist.append(trigOp.getResults().begin(), trigOp.getResults().end()); + } else if (auto gateOp = dyn_cast(user)) { + worklist.push_back(gateOp.getResult()); + } else { + user->emitWarning("Unexpected trigger user op."); + } + } + } +} + +static Value materializeInitValue(OpBuilder &builder, TypedAttr cst, + Location loc) { + if (llvm::isa(cst.getDialect())) + return cst.getDialect() + .materializeConstant(builder, cst, cst.getType(), loc) + ->getResult(0); + auto *hwDialect = builder.getContext()->getLoadedDialect(); + return hwDialect->materializeConstant(builder, cst, cst.getType(), loc) + ->getResult(0); +} + +static LogicalResult stripTriggerIOs(hw::HWModuleOp moduleOp, + hw::InstanceGraph &igraph) { + auto isTriggerType = [](Type ty) -> bool { + return isa(ty); + }; + BitVector argsToRemove(moduleOp.getBody().getNumArguments()); + + for (auto [i, arg] : llvm::enumerate(moduleOp.getBody().getArguments())) { + if (isTriggerType(arg.getType())) { + argsToRemove.set(i); + if (!arg.use_empty()) { + OpBuilder builder(moduleOp); + builder.setInsertionPointToStart(moduleOp.getBodyBlock()); + auto loc = moduleOp.getPortLoc(moduleOp.getPortIdForInputId(i)); + auto never = builder.create(loc, arg.getType()); + arg.replaceAllUsesWith(never); + } + } + } + + moduleOp.getBodyBlock()->eraseArguments(argsToRemove); + auto outputOp = cast(moduleOp.getBodyBlock()->getTerminator()); + BitVector outputsToRemove(outputOp.getNumOperands()); + + for (auto [i, outTy] : llvm::enumerate(outputOp.getOperandTypes())) + if (isTriggerType(outTy)) + outputsToRemove.set(i); + outputOp->eraseOperands(outputsToRemove); + + SmallVector newPorts; + for (auto port : moduleOp.getModuleType().getPorts()) + if (!isTriggerType(port.type)) + newPorts.push_back(port); + moduleOp.setModuleType(hw::ModuleType::get(moduleOp.getContext(), newPorts)); + + auto *node = igraph.lookup(moduleOp); + for (auto instUse : llvm::make_early_inc_range(node->uses())) { + auto instanceOp = llvm::cast( + instUse->getInstance().getOperation()); + + SmallVector newOperands; + SmallVector newNamesAttr; + for (auto [operand, attr] : + llvm::zip(instanceOp.getOperands(), instanceOp.getArgNamesAttr())) { + if (!isTriggerType(operand.getType())) { + newOperands.push_back(operand); + newNamesAttr.push_back(attr); + } + } + ImplicitLocOpBuilder builder(instanceOp.getLoc(), instanceOp); + auto newInstance = builder.create( + outputOp.getOperandTypes(), instanceOp.getInstanceNameAttr(), + instanceOp.getModuleNameAttr(), newOperands, + builder.getArrayAttr(newNamesAttr), ArrayAttr{}, + instanceOp.getParametersAttr(), instanceOp.getInnerSymAttr(), + instanceOp.getDoNotPrintAttr()); + newNamesAttr.clear(); + size_t newResIdx = 0; + for (auto [idx, res] : llvm::enumerate(instanceOp.getResults())) { + if (outputsToRemove[idx]) { + auto never = builder.createOrFold(res.getType()); + res.replaceAllUsesWith(never); + } else { + res.replaceAllUsesWith(newInstance.getResult(newResIdx)); + newNamesAttr.push_back(instanceOp.getOutputNames()[idx]); + newResIdx++; + } + } + newInstance.setOutputNames(builder.getArrayAttr(newNamesAttr)); + instanceOp.erase(); + } + return success(); +} + +namespace { + +struct GlobalTriggersToSVPass { + + GlobalTriggersToSVPass(mlir::ModuleOp moduleOp, + detail::IfDefGuardMap &guardMap) + : mlirModuleOp(moduleOp), ifdefGuardBlocks(guardMap) { + for (auto hwModuleOp : mlirModuleOp.getOps()) + if (llvm::any_of(hwModuleOp.getPortTypes(), isTriggerType)) + crossTriggerModules.insert(hwModuleOp); + }; + + size_t getNumCrosstriggerModules() const { + return crossTriggerModules.size(); + } + LogicalResult run(hw::InstanceGraph &igraph) { + if (crossTriggerModules.empty()) + return success(); + + bool hasNonPrivateTriggersPorts = false; + for (auto hwModuleOp : crossTriggerModules) { + if (!hwModuleOp.isPrivate()) { + hwModuleOp.emitError( + "Trigger type in port list requires module to be private."); + hasNonPrivateTriggersPorts = true; + continue; + } + } + if (hasNonPrivateTriggersPorts) + return failure(); + + bool hasFailed = false; + for (auto crossTriggerMod : crossTriggerModules) + hasFailed |= failed(lowerCrossModuleTrigger(crossTriggerMod, igraph)); + + if (hasFailed) + return failure(); + + for (auto crossTriggerMod : crossTriggerModules) + hasFailed |= failed(stripTriggerIOs(crossTriggerMod, igraph)); + + if (hasFailed) + return failure(); + return success(); + } + +private: + using ArgNumToSymMap = + SmallDenseMap, + hw::InnerRefAttr>; + + std::optional globalNamespaceCache; + Namespace &getGlobalNamespace() { + if (!globalNamespaceCache.has_value()) { + globalNamespaceCache.emplace(); + SymbolCache cache; + cache.addDefinitions(mlirModuleOp); + globalNamespaceCache->add(cache); + } + return *globalNamespaceCache; + } + + Block *getOrCreateGuardBlock(hw::HWModuleOp &moduleOp) { + Block *guardBlock = ifdefGuardBlocks[moduleOp]; + if (!guardBlock) { + OpBuilder builder(moduleOp.getBodyBlock()->getTerminator()); + auto ifDef = builder.create( + moduleOp.getLoc(), "SYNTHESIS", [] {}, [] {}); + guardBlock = ifDef.getElseBlock(); + ifdefGuardBlocks[moduleOp] = guardBlock; + } + return guardBlock; + } + + LogicalResult lowerCrossModuleTrigger(hw::HWModuleOp moduleOp, + hw::InstanceGraph &igraph); + LogicalResult + lowerCrossModuleTriggerParent(igraph::InstanceRecord *instRecord, + ArrayRef triggerPorts, + const ArgNumToSymMap &argNumToInnerNames); + + mlir::ModuleOp mlirModuleOp; + llvm::SmallSetVector crossTriggerModules; + detail::IfDefGuardMap &ifdefGuardBlocks; +}; + +struct TriggersToSVPass + : public circt::impl::LowerTriggersToSVBase { + + using circt::impl::LowerTriggersToSVBase< + TriggersToSVPass>::LowerTriggersToSVBase; + + static constexpr bool useDelayedInitProcesses = true; + + void runOnOperation() override { + + ifdefGuardBlocks.clear(); + auto globalLowering = std::make_unique( + getOperation(), ifdefGuardBlocks); + bool hasCrosstriggers = globalLowering->getNumCrosstriggerModules() > 0; + if (hasCrosstriggers) { + auto &igraph = getAnalysis(); + if (failed(globalLowering->run(igraph))) { + signalPassFailure(); + return; + } + } else { + markAnalysesPreserved(); + } + + std::atomic anyChange = hasCrosstriggers; + auto result = mlir::failableParallelForEach( + &getContext(), getOperation().getOps(), + [&](hw::HWModuleOp op) -> LogicalResult { + size_t numRoots = 0; + if (failed(lowerLocalTriggers(op, numRoots))) + return failure(); + if (numRoots > 0) + anyChange = true; + return success(); + }); + + if (failed(result)) + signalPassFailure(); + + if (anyChange) { + Operation *op = getOperation().lookupSymbol("SYNTHESIS"); + if (op) { + if (!isa(op)) { + op->emitOpError("should be a macro declaration"); + return signalPassFailure(); + } + } else { + auto builder = ImplicitLocOpBuilder::atBlockBegin( + UnknownLoc::get(&getContext()), getOperation().getBody()); + builder.create("SYNTHESIS"); + } + } else { + markAllAnalysesPreserved(); + } + } + +private: + detail::IfDefGuardMap ifdefGuardBlocks; + LogicalResult lowerRootTrigger(Value root, + SmallVectorImpl &lowerdOps, + Block *guardBlock); + LogicalResult lowerLocalTriggers(hw::HWModuleOp moduleOp, size_t &numRoots); + LogicalResult buildSVProcessForRoot(OpBuilder &builder, Operation *rootOp, + Location loc); +}; + +LogicalResult GlobalTriggersToSVPass::lowerCrossModuleTriggerParent( + igraph::InstanceRecord *instRecord, ArrayRef triggerPorts, + const ArgNumToSymMap &argNumToInnerNames) { + auto instanceOp = llvm::cast( + instRecord->getInstance().getOperation()); + auto instParentMod = instRecord->getParent()->getModule(); + + auto innerName = instanceOp.getInnerName(); + if (!innerName) { + instanceOp.emitOpError("requires an inner name to lower cross trigger."); + return failure(); + } + + auto guardBlock = getOrCreateGuardBlock(instParentMod); + ImplicitLocOpBuilder builder(instanceOp.getLoc(), instanceOp); + builder.setInsertionPointToEnd(guardBlock); + + for (auto trigPort : triggerPorts) { + assert(isa(trigPort.type) || + isa(trigPort.type)); + + auto &activeSigSym = argNumToInnerNames.at({trigPort.dir, trigPort.argNum}); + hw::HierPathOp triggerActivePathOp; + { + OpBuilder::InsertionGuard g(builder); + builder.setInsertionPointToStart(mlirModuleOp.getBody()); + triggerActivePathOp = builder.create( + getGlobalNamespace().newName( + "crosstrigger_path_" + instParentMod.getName() + "_" + + *innerName + "_" + trigPort.getName() + "active"), + builder.getArrayAttr({instanceOp.getInnerRef(), activeSigSym})); + } + + Value triggerActiveXmr = builder.createOrFold( + hw::InOutType::get(builder.getI1Type()), + triggerActivePathOp.getSymNameAttr()); + + if (trigPort.isInput()) { + auto ssaValue = instanceOp.getInputs()[trigPort.argNum]; + assert(ssaValue.getType() == trigPort.type); + + instanceOp->setOperand(trigPort.argNum, + builder.createOrFold(trigPort.type)); + + builder.create(ssaValue, triggerActiveXmr, [&]() { + auto cst1 = builder.createOrFold( + IntegerAttr::get(builder.getI1Type(), 1)); + builder.create( + builder.getInsertionBlock()->getArgument(0), cst1); + auto readOp = builder.createOrFold( + builder.getInsertionBlock()->getArgument(0)); + auto notActiveOp = + builder.createOrFold(readOp, cst1, true); + builder.create(notActiveOp); + }); + } else { + assert(trigPort.isOutput()); + auto convCast = builder.create( + TypeRange{trigPort.type}, triggerActiveXmr); + auto res = instanceOp.getResult(trigPort.argNum); + res.replaceAllUsesWith(convCast.getResult(0)); + } + } + + return success(); +} + +LogicalResult +GlobalTriggersToSVPass::lowerCrossModuleTrigger(hw::HWModuleOp moduleOp, + hw::InstanceGraph &igraph) { + auto innerNamespace = hw::InnerSymbolNamespace(moduleOp); + SmallVector triggerPorts; + + auto createActiveSigName = [&](StringRef portName) -> StringRef { + return innerNamespace.newName("crosstrigger_" + portName, "active"); + }; + + ArgNumToSymMap argNumToInnerNames; + + for (auto [idx, portInfo] : llvm::enumerate(moduleOp.getPortList())) { + if (!isa(portInfo.type)) + continue; + if (portInfo.dir == hw::ModulePort::Direction::InOut) + continue; + + triggerPorts.push_back(portInfo); + + auto guardBlock = getOrCreateGuardBlock(moduleOp); + ImplicitLocOpBuilder builder(moduleOp.getPortLoc(idx), moduleOp); + builder.setInsertionPointToStart(guardBlock); + + auto activeSigName = + builder.getStringAttr(createActiveSigName(portInfo.name)); + auto activeSignalOp = + builder.create(builder.getI1Type(), activeSigName, + hw::InnerSymAttr::get(activeSigName)); + argNumToInnerNames.insert( + {{portInfo.dir, portInfo.argNum}, activeSignalOp.getInnerRef()}); + + builder.setInsertionPointToEnd(guardBlock); + if (portInfo.dir == hw::ModulePort::Direction::Input) { + auto convCast = builder.create( + TypeRange{portInfo.type}, activeSignalOp.getResult()); + auto arg = moduleOp.getArgumentForPort(idx); + arg.replaceAllUsesWith(convCast.getResult(0)); + } else { // portInfo.dir == hw::ModulePort::Direction::Output + assert(portInfo.isOutput()); + auto outputOp = + llvm::cast(moduleOp.getBodyBlock()->getTerminator()); + auto ssaValue = outputOp->getOperand(portInfo.argNum); + outputOp.setOperand(portInfo.argNum, builder.createOrFold( + ssaValue.getType())); + + builder.create( + ssaValue, activeSignalOp.getResult(), [&]() { + auto cst1 = builder.createOrFold( + IntegerAttr::get(builder.getI1Type(), 1)); + builder.create( + builder.getInsertionBlock()->getArgument(0), cst1); + auto readOp = builder.createOrFold( + builder.getInsertionBlock()->getArgument(0)); + auto notActiveOp = + builder.createOrFold(readOp, cst1, true); + builder.create(notActiveOp); + }); + } + } + + auto *node = igraph.lookup(moduleOp); + for (auto instUse : node->uses()) + if (failed(lowerCrossModuleTriggerParent(instUse, triggerPorts, + argNumToInnerNames))) + return failure(); + + return success(); +} + +static void externalizeResults( + TriggeredOp triggerdOp, OpBuilder &builder, + SmallDenseMap> &resultToRegMap) { + SmallVector newRegs; + SmallVector reads; + for (auto [res, tieoff] : + llvm::zip(triggerdOp.getResults(), *triggerdOp.getTieoffs())) { + auto cst = materializeInitValue(builder, cast(tieoff), + triggerdOp.getLoc()); + auto reg = builder.create(triggerdOp.getLoc(), res.getType(), + StringAttr(), hw::InnerSymAttr(), cst); + auto regRead = builder.createOrFold(triggerdOp.getLoc(), + reg.getResult()); + newRegs.emplace_back(reg.getResult()); + reads.emplace_back(regRead); + } + triggerdOp.replaceAllUsesWith(reads); + resultToRegMap.insert(std::pair>{ + triggerdOp, std::move(newRegs)}); +} + +LogicalResult TriggersToSVPass::buildSVProcessForRoot(OpBuilder &builder, + Operation *rootOp, + Location loc) { + std::optional clockEvent; + Value clockSig; + + if (auto convCastOp = dyn_cast(rootOp)) { + if (!isBitToTriggerCast(convCastOp)) { + convCastOp->emitError("Unsupported trigger root."); + return failure(); + } + clockSig = builder.createOrFold(rootOp->getLoc(), + convCastOp->getOperand(0)); + clockEvent = sv::EventControl::AtPosEdge; + } else if (auto edgeOp = dyn_cast(rootOp)) { + auto clockConv = builder.create( + loc, TypeRange{builder.getI1Type()}, edgeOp.getClock()); + clockSig = clockConv.getResult(0); + clockEvent = + convertEventControl(edgeOp.getResult().getType().getEdgeEvent()); + } else if (!isa(rootOp)) { + rootOp->emitError("Unsupported trigger root."); + return failure(); + } + + if (clockSig) { + auto alwaysOp = builder.create(loc, *clockEvent, clockSig); + builder.setInsertionPointToStart(alwaysOp.getBodyBlock()); + } else { + auto initOp = builder.create(loc); + builder.setInsertionPointToStart(initOp.getBodyBlock()); + if (useDelayedInitProcesses) + builder.create(loc, 0); + } + + if (isa(rootOp)) { + auto cst0 = builder.createOrFold( + rootOp->getLoc(), IntegerAttr::get(builder.getI1Type(), 0)); + auto assignOp = builder.create(rootOp->getLoc(), + rootOp->getOperand(0), cst0); + builder.setInsertionPoint(assignOp); + } + + return success(); +} + +LogicalResult TriggersToSVPass::lowerRootTrigger( + Value root, SmallVectorImpl &lowerdOps, Block *guardBlock) { + auto rootDefOp = root.getDefiningOp(); + assert(!!rootDefOp); + assert(isTriggerType(root.getType())); + OpBuilder builder(rootDefOp); + SmallVector locs; + SmallDenseMap> resultToRegMap; + SmallVector procs; + + collectTriggeredOps(root, procs); + + if (procs.empty()) + return success(); + + locs.reserve(procs.size() + 1); + locs.emplace_back(rootDefOp->getLoc()); + for (auto proc : procs) + locs.emplace_back(proc.getLoc()); + auto fusedLoc = FusedLoc::get(builder.getContext(), locs); + + if (!guardBlock) { + auto ifDefOp = builder.create( + fusedLoc, "SYNTHESIS", [] {}, [] {}); + guardBlock = ifDefOp.getElseBlock(); + } else { + builder.setInsertionPoint(guardBlock->getParentOp()); + } + + for (auto proc : procs) + if (proc.getNumResults() > 0) + externalizeResults(proc, builder, resultToRegMap); + + struct BuildStackEntry { + PointerUnion pv; + OpBuilder::InsertPoint ip; + }; + + builder.setInsertionPointToEnd(guardBlock); + + auto res = buildSVProcessForRoot(builder, rootDefOp, fusedLoc); + if (failed(res)) + return failure(); + + SmallVector buildStack; + buildStack.emplace_back(BuildStackEntry{root, builder.saveInsertionPoint()}); + while (!buildStack.empty()) { + auto popVal = buildStack.pop_back_val(); + builder.restoreInsertionPoint(popVal.ip); + + if (auto trigVal = dyn_cast(popVal.pv)) { + auto users = trigVal.getUsers(); + if (users.empty()) + continue; + if (disallowForkJoin || trigVal.hasOneUse()) { + for (auto user : users) + buildStack.emplace_back(BuildStackEntry{user, popVal.ip}); + continue; + } + + auto numUsers = + std::distance(trigVal.getUsers().begin(), trigVal.getUsers().end()); + auto forkJoinOp = + builder.create(rootDefOp->getLoc(), numUsers); + for (auto const &[user, region] : + llvm::zip(users, forkJoinOp.getRegions())) { + Block *block = builder.createBlock(®ion); + auto newIp = OpBuilder::InsertPoint(block, block->begin()); + buildStack.emplace_back(BuildStackEntry{user, newIp}); + } + continue; + } + auto op = cast(popVal.pv); + + if (auto sequence = dyn_cast(op)) { + for (auto res : llvm::reverse(sequence.getResults())) + buildStack.emplace_back(BuildStackEntry{res, popVal.ip}); + continue; + } + + if (auto gate = dyn_cast(op)) { + auto ifOp = builder.create(gate.getLoc(), gate.getEnable()); + auto newIp = OpBuilder::InsertPoint(ifOp.getThenBlock(), + ifOp.getThenBlock()->begin()); + buildStack.emplace_back(BuildStackEntry{gate.getResult(), newIp}); + continue; + } + + if (auto procedure = dyn_cast(op)) { + lowerdOps.push_back(procedure); + + auto yield = + cast(procedure.getBody().front().getTerminator()); + auto ®s = resultToRegMap[procedure]; + mlir::IRRewriter rewriter(builder); + + OpBuilder::InsertPoint ip = builder.saveInsertionPoint(); + rewriter.inlineBlockBefore(&procedure.getBody().front(), ip.getBlock(), + ip.getPoint(), procedure.getInputs()); + + assert(regs.size() == yield.getNumOperands() && + "Failed to lookup materialized result registers"); + for (auto [reg, res] : llvm::zip(regs, yield.getOperands())) + builder.create(yield.getLoc(), reg, res); + yield.erase(); + + continue; + } + op->emitWarning("Unable to lower trigger user."); + } + + return success(); +} + +static void cleanUpTriggerTree(ArrayRef procs) { + SmallVector cleanupList; + SmallVector cleanupNextList; + SmallPtrSet erasedOps; + for (auto proc : procs) { + auto trigger = proc.getTrigger(); + cleanupNextList.emplace_back(trigger.getDefiningOp()); + erasedOps.insert(proc); + proc.erase(); + } + + bool hasChanged = true; + while (hasChanged && !cleanupNextList.empty()) { + cleanupList = std::move(cleanupNextList); + cleanupNextList.clear(); + hasChanged = false; + for (auto op : cleanupList) { + if (!op || erasedOps.contains(op)) + continue; + if (auto seqOp = dyn_cast(op)) { + if (seqOp.use_empty()) { + cleanupNextList.push_back(seqOp.getParent().getDefiningOp()); + erasedOps.insert(seqOp); + hasChanged = true; + seqOp.erase(); + } else { + cleanupNextList.push_back(seqOp); + } + continue; + } + if (auto gate = dyn_cast(op)) { + if (gate.getResult().use_empty()) { + erasedOps.insert(gate); + hasChanged = true; + cleanupNextList.push_back(gate.getInput().getDefiningOp()); + gate.erase(); + } else { + cleanupNextList.push_back(gate); + } + continue; + } + if (isa(op) || + isBitToTriggerCast(dyn_cast(op))) { + if (op->use_empty()) { + erasedOps.insert(op); + op->erase(); + } else { + cleanupNextList.push_back(op); + } + continue; + } + } + } +} + +LogicalResult TriggersToSVPass::lowerLocalTriggers(hw::HWModuleOp moduleOp, + size_t &numRoots) { + SmallVector cleanupList; + SmallVector trueRoots; + + SmallVector castRoots; + + moduleOp.walk([&](Operation *op) { + TypeSwitch(op) + .Case([&](auto rootOp) { + if (!rootOp.getResult().use_empty()) + trueRoots.push_back(rootOp.getResult()); + else + rootOp->erase(); + }) + .Case([&](auto neverOp) { + if (!neverOp.getResult().use_empty()) + collectTriggeredOps(neverOp.getResult(), cleanupList); + else + neverOp->erase(); + }) + .Case([&](auto castOp) { + if (isBitToTriggerCast(castOp)) + castRoots.push_back(castOp.getResult(0)); + }); + }); + + // Tie off unused casts + for (auto castRoot : castRoots) { + if (castRoot.use_empty()) { + ImplicitLocOpBuilder builder(castRoot.getDefiningOp()->getLoc(), + castRoot.getDefiningOp()); + builder.setInsertionPointToStart(moduleOp.getBodyBlock()); + builder.create(castRoot); + } + } + SmallVector allRoots = std::move(castRoots); + + // Split true roots + for (auto trueRoot : trueRoots) { + if (trueRoot.hasOneUse()) { + allRoots.push_back(trueRoot); + continue; + } + for (auto &use : llvm::make_early_inc_range(trueRoot.getUses())) { + ImplicitLocOpBuilder builder(trueRoot.getDefiningOp()->getLoc(), + use.getOwner()); + auto clonedRoot = builder.clone(*trueRoot.getDefiningOp()); + use.assign(clonedRoot->getResult(0)); + allRoots.push_back(clonedRoot->getResult(0)); + } + assert(trueRoot.use_empty()); + trueRoot.getDefiningOp()->erase(); + } + + // Lower roots + bool hasFailed = false; + numRoots = allRoots.size(); + for (auto root : allRoots) + hasFailed |= + failed(lowerRootTrigger(root, cleanupList, ifdefGuardBlocks[moduleOp])); + + if (hasFailed) + return failure(); + + cleanUpTriggerTree(cleanupList); + return success(); +} + +} // anonymous namespace diff --git a/lib/Dialect/SV/SVOps.cpp b/lib/Dialect/SV/SVOps.cpp index 77eeb28250f0..7957e1cf919b 100644 --- a/lib/Dialect/SV/SVOps.cpp +++ b/lib/Dialect/SV/SVOps.cpp @@ -360,6 +360,33 @@ void LogicOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { std::optional LogicOp::getTargetResultIndex() { return 0; } +//===----------------------------------------------------------------------===// +// BitOp +//===----------------------------------------------------------------------===// + +void BitOp::build(OpBuilder &builder, OperationState &odsState, + Type elementType, StringAttr name, + hw::InnerSymAttr innerSym) { + if (!name) + name = builder.getStringAttr(""); + odsState.addAttribute("name", name); + if (innerSym) + odsState.addAttribute(hw::InnerSymbolTable::getInnerSymbolAttrName(), + innerSym); + odsState.addTypes(hw::InOutType::get(elementType)); +} + +/// Suggest a name for each result value based on the saved result names +/// attribute. +void BitOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + // If the bit has an optional 'name' attribute, use it. + auto nameAttr = (*this)->getAttrOfType("name"); + if (!nameAttr.getValue().empty()) + setNameFn(getResult(), nameAttr.getValue()); +} + +std::optional BitOp::getTargetResultIndex() { return 0; } + //===----------------------------------------------------------------------===// // Control flow like-operations //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/Sim/CMakeLists.txt b/lib/Dialect/Sim/CMakeLists.txt index 395e7216dfc8..98995b3be7f5 100644 --- a/lib/Dialect/Sim/CMakeLists.txt +++ b/lib/Dialect/Sim/CMakeLists.txt @@ -10,6 +10,7 @@ ##===----------------------------------------------------------------------===// add_circt_dialect_library(CIRCTSim + SimAttributes.cpp SimDialect.cpp SimOps.cpp SimTypes.cpp @@ -21,6 +22,7 @@ add_circt_dialect_library(CIRCTSim CIRCTHW CIRCTSV MLIRSimIncGen + MLIRSimAttributesIncGen LINK_COMPONENTS Support diff --git a/lib/Dialect/Sim/SimAttributes.cpp b/lib/Dialect/Sim/SimAttributes.cpp new file mode 100644 index 000000000000..c52fd149fa03 --- /dev/null +++ b/lib/Dialect/Sim/SimAttributes.cpp @@ -0,0 +1,41 @@ +//===- SimAttributes.cpp - Implement Sim attributes -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Sim/SimAttributes.h" +#include "circt/Dialect/Sim/SimDialect.h" +#include "circt/Dialect/Sim/SimTypes.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/DialectImplementation.h" +#include "llvm/ADT/TypeSwitch.h" + +using namespace circt; +using namespace sim; + +#define GET_ATTRDEF_CLASSES +#include "circt/Dialect/Sim/SimAttributes.cpp.inc" + +namespace circt { +namespace sim { +LogicalResult NeverTriggerAttr::verify( + ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError, + ::mlir::Type type) { + if (!isTriigerType(type)) { + emitError() << "type of never attribute must be a trigger type."; + return failure(); + } + return success(); +} +} // namespace sim +} // namespace circt + +void SimDialect::registerAttributes() { + addAttributes< +#define GET_ATTRDEF_LIST +#include "circt/Dialect/Sim/SimAttributes.cpp.inc" + >(); +} diff --git a/lib/Dialect/Sim/SimDialect.cpp b/lib/Dialect/Sim/SimDialect.cpp index a05f160036c7..57bf56bb3a9f 100644 --- a/lib/Dialect/Sim/SimDialect.cpp +++ b/lib/Dialect/Sim/SimDialect.cpp @@ -11,7 +11,9 @@ //===----------------------------------------------------------------------===// #include "circt/Dialect/Sim/SimDialect.h" +#include "circt/Dialect/HW/HWDialect.h" #include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/Sim/SimAttributes.h" #include "circt/Dialect/Sim/SimOps.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinTypes.h" @@ -29,8 +31,8 @@ void SimDialect::initialize() { #define GET_OP_LIST #include "circt/Dialect/Sim/Sim.cpp.inc" >(); - registerTypes(); + registerAttributes(); } #include "circt/Dialect/Sim/SimDialect.cpp.inc" @@ -40,8 +42,18 @@ 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)); + + if (auto neverTriggerAttr = llvm::dyn_cast(value)) + return builder.create(loc, neverTriggerAttr.getType()); + return nullptr; } diff --git a/lib/Dialect/Sim/SimOps.cpp b/lib/Dialect/Sim/SimOps.cpp index adbd35e5b51a..1a35d38483cd 100644 --- a/lib/Dialect/Sim/SimOps.cpp +++ b/lib/Dialect/Sim/SimOps.cpp @@ -11,8 +11,10 @@ //===----------------------------------------------------------------------===// #include "circt/Dialect/Sim/SimOps.h" +#include "circt/Dialect/Comb/CombOps.h" #include "circt/Dialect/HW/ModuleImplementation.h" #include "circt/Dialect/SV/SVOps.h" +#include "circt/Dialect/Sim/SimAttributes.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Interfaces/FunctionImplementation.h" @@ -397,6 +399,579 @@ 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(); +} + +OpFoldResult OnEdgeOp::fold(FoldAdaptor adaptor) { + if (!!adaptor.getClock()) + return NeverTriggerAttr::get(getContext(), getType()); + return {}; +} + +// --- TriggeredOp --- + +void TriggeredOp::build(OpBuilder &odsBuilder, OperationState &odsState, + Value trigger, ValueRange arguments, + std::function bodyCtor) { + OpBuilder::InsertionGuard guard(odsBuilder); + odsState.addOperands({trigger}); + odsState.addOperands({arguments}); + auto body = odsState.addRegion(); + auto block = odsBuilder.createBlock(body); + block->addArguments( + arguments.getTypes(), + SmallVector(arguments.size(), odsState.location)); + if (bodyCtor) + bodyCtor(); + odsBuilder.create(odsState.location); +} + +void TriggeredOp::build(OpBuilder &odsBuilder, OperationState &odsState, + Value trigger, std::function bodyCtor) { + return build(odsBuilder, odsState, trigger, {}, bodyCtor); +} + +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 (isa_and_nonnull(adaptor.getTrigger())) { + // Never enabled, fold to tie-offs. + if (getNumResults() > 0) { + results.append(adaptor.getTieoffsAttr().begin(), + adaptor.getTieoffsAttr().end()); + return success(); + } + } + return failure(); +} + +static LogicalResult sinkConstantArguments(TriggeredOp op, + PatternRewriter &rewriter) { + auto isConstant = [](Value arg) -> bool { + if (!arg || !arg.getDefiningOp()) + return false; + return arg.getDefiningOp()->hasTrait(); + }; + + size_t numConstantInputs = 0; + for (auto input : op.getInputs()) + if (isConstant(input)) + ++numConstantInputs; + + if (numConstantInputs == 0) + return failure(); + + SmallVector newInputs; + SmallVector newInputTypes; + SmallVector newInputLocs; + newInputs.reserve(op.getInputs().size() - numConstantInputs); + newInputTypes.reserve(op.getInputs().size() - numConstantInputs); + newInputLocs.reserve(op.getInputs().size() - numConstantInputs); + + for (auto [i, input] : llvm::enumerate(op.getInputs())) { + if (!isConstant(input)) { + newInputs.push_back(input); + newInputTypes.push_back(input.getType()); + newInputLocs.push_back(op.getBody().getArgument(i).getLoc()); + } + } + + auto newBody = std::make_unique(); + newBody->addArguments(newInputTypes, newInputLocs); + + rewriter.setInsertionPoint(op); + auto newTriggerdOp = rewriter.create( + op.getLoc(), op.getResultTypes(), op.getTrigger(), newInputs, + op.getTieoffsAttr()); + + rewriter.setInsertionPointToStart(&op.getBody().front()); + + SmallVector argRepl; + argRepl.reserve(op.getInputs().size()); + + size_t newArgIdx = 0; + for (auto input : op.getInputs()) { + if (!isConstant(input)) { + argRepl.push_back(newBody->getArgument(newArgIdx)); + ++newArgIdx; + } else { + auto cloned = rewriter.clone(*input.getDefiningOp()); + argRepl.push_back(cloned->getResult(0)); + } + } + + newTriggerdOp.getBodyRegion().push_back(newBody.release()); + rewriter.mergeBlocks(&op.getBody().front(), &newTriggerdOp.getBody().front(), + argRepl); + rewriter.replaceOp(op, newTriggerdOp); + return success(); +} + +LogicalResult TriggeredOp::canonicalize(TriggeredOp op, + PatternRewriter &rewriter) { + + if (succeeded(sinkConstantArguments(op, rewriter))) + return success(); + + if (op.getNumResults() > 0) + return failure(); + + bool isDeadOrEmpty = false; + + auto *bodyBlock = &op.getBodyRegion().front(); + isDeadOrEmpty = bodyBlock->without_terminator().empty(); + if (isa_and_nonnull(op.getTrigger().getDefiningOp())) + isDeadOrEmpty = true; + + 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."); + auto neverOp = + rewriter.create(op.getLoc(), op.getParent().getType()); + for (auto res : op.getResults()) + rewriter.replaceAllUsesWith(res, neverOp.getResult()); + rewriter.eraseOp(op); + return success(); + } + 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."); + auto neverOp = + rewriter.create(op.getLoc(), op.getParent().getType()); + for (auto res : op.getResults()) + rewriter.replaceAllUsesWith(res, neverOp.getResult()); + rewriter.eraseOp(op); + return success(); + } + + 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(); +} + +// ------- NeverOp --------- + +OpFoldResult NeverOp::fold(FoldAdaptor) { + return NeverTriggerAttr::get(getContext(), getType()); +} + +// ------- TriggerGateOp ----- +OpFoldResult TriggerGateOp::fold(FoldAdaptor adaptor) { + if (llvm::isa_and_nonnull(adaptor.getInput())) + return adaptor.getInput(); + if (auto cstEnable = + llvm::dyn_cast_or_null(adaptor.getEnable())) { + if (cstEnable.getValue().isZero()) + return NeverTriggerAttr::get(getContext(), getType()); + return getInput(); + } + return {}; +} + +static TriggerGateOp getSingleGateChild(Value trigger) { + if (!trigger.hasOneUse()) + return {}; + return dyn_cast(trigger.getUses().begin()->getOwner()); +} + +static bool hasSingleGateChild(Value trigger) { + return !!getSingleGateChild(trigger); +} + +template +static inline void insertFactors(TriggerGateOp gate, S &set) { + if (auto andOp = gate.getEnable().getDefiningOp()) + for (auto factor : andOp.getOperands()) + set.insert(factor); + else + set.insert(gate.getEnable()); +} + +static LogicalResult hoistCommonGateFactors(TriggerSequenceOp sequence, + PatternRewriter &rewriter) { + + Value nonCompositeFactor = {}; + Value firstFactor = {}; + bool allSame = true; + for (auto res : sequence.getResults()) { + auto gate = getSingleGateChild(res); + if (!gate) + return failure(); + + // Check if all conditions are identical + if (!firstFactor) + firstFactor = gate.getEnable(); + else if (gate.getEnable() != firstFactor) + allSame = false; + + bool isCompositeFactor = + isa_and_nonnull(gate.getEnable().getDefiningOp()); + if (!isCompositeFactor) { + // If there are two different non-composite conditions, there's nothing to + // to. + if (!nonCompositeFactor) + nonCompositeFactor = gate.getEnable(); + else if (nonCompositeFactor != gate.getEnable()) + return failure(); + } + } + + SmallVector locs; + + if (allSame) { + SmallVector locs; + // The easy path: All sequence elements are gated the same way. + // Remove the old gates: + for (auto res : sequence.getResults()) { + auto gate = getSingleGateChild(res); + locs.push_back(gate.getLoc()); + rewriter.replaceAllUsesWith(gate.getResult(), gate.getInput()); + rewriter.eraseOp(gate); + } + // Insert the gate above the sequence op: + auto fusedLoc = FusedLoc::get(sequence.getContext(), locs); + rewriter.setInsertionPoint(sequence); + auto newGate = rewriter.create( + fusedLoc, sequence.getParent(), firstFactor); + rewriter.modifyOpInPlace(sequence, [&]() { + sequence.getParentMutable().assign(newGate.getResult()); + }); + return success(); + } + + // The hard path: All sequence elements have at least one common condition, + // but they are not identical. + + // See if we can find a set of common factors. + auto firstGate = getSingleGateChild(sequence.getResults().front()); + llvm::SmallSetVector commonFactors; + insertFactors(firstGate, commonFactors); + + for (auto res : sequence.getResults().drop_front()) { + auto gate = getSingleGateChild(res); + // Intersect the common factors with the local ones + SmallPtrSet localFactors; + insertFactors(gate, localFactors); + commonFactors.remove_if([&](auto commonFactor) { + return !localFactors.contains(commonFactor); + }); + if (commonFactors.empty()) + return failure(); + } + + // Yay! We found some. + // Delete the common factors below. + bool allTwoState = true; + for (auto res : sequence.getResults()) { + SmallVector newFactors; + auto gate = getSingleGateChild(res); + locs.push_back(gate.getLoc()); + auto andOp = gate.getEnable().getDefiningOp(); + if (!!andOp) { + if (!andOp.getTwoState()) + allTwoState = false; + for (auto factor : andOp.getOperands()) + if (!commonFactors.contains(factor)) + newFactors.push_back(factor); + } else if (!commonFactors.contains(gate.getEnable())) { + newFactors.push_back(gate.getEnable()); + } + if (newFactors.empty()) { + rewriter.replaceAllUsesWith(gate.getResult(), gate.getInput()); + rewriter.eraseOp(gate); + } else if (newFactors.size() == 1) { + rewriter.modifyOpInPlace( + gate, [&]() { gate.getEnableMutable().assign(newFactors.front()); }); + } else { + assert(!!andOp); + rewriter.setInsertionPoint(gate); + auto newAnd = rewriter.createOrFold( + andOp.getLoc(), newFactors, andOp.getTwoState()); + rewriter.modifyOpInPlace( + gate, [&]() { gate.getEnableMutable().assign(newAnd); }); + } + } + + // Create the new gate above. + auto fusedLoc = FusedLoc::get(sequence.getContext(), locs); + auto commonFactorsVec = commonFactors.takeVector(); + rewriter.setInsertionPoint(sequence); + auto commonCond = rewriter.createOrFold( + fusedLoc, rewriter.getI1Type(), commonFactorsVec, allTwoState); + auto newGate = rewriter.create(fusedLoc, sequence.getParent(), + commonCond); + rewriter.modifyOpInPlace(sequence, [&]() { + sequence.getParentMutable().assign(newGate.getResult()); + }); + return success(); +} + +LogicalResult TriggerGateOp::canonicalize(TriggerGateOp op, + PatternRewriter &rewriter) { + + // Squash chained gates into a single one + if (!op.getInput().getDefiningOp()) { + if (hasSingleGateChild(op.getResult())) { + SmallVector squashedGates; + TriggerGateOp prevGate = {}; + TriggerGateOp gate = op; + llvm::SmallSetVector factors; + // Descend the chain and collect the enable factors + bool allTwoState = true; + while (!!gate) { + if (auto andOp = gate.getEnable().getDefiningOp()) { + for (auto factor : andOp.getInputs()) + factors.insert(factor); + allTwoState &= andOp.getTwoState(); + } else { + factors.insert(gate.getEnable()); + } + squashedGates.push_back(gate); + gate = getSingleGateChild(gate.getResult()); + } + // Combine them all + SmallVector locs; + locs.reserve(squashedGates.size()); + for (auto sgate : squashedGates) + locs.push_back(sgate.getLoc()); + auto fusesdLoc = FusedLoc::get(op.getContext(), locs); + auto factorsVec = factors.takeVector(); + auto newCond = rewriter.createOrFold( + fusesdLoc, rewriter.getI1Type(), factorsVec, allTwoState); + auto lastGate = squashedGates.pop_back_val(); + rewriter.replaceOpWithNewOp(lastGate, op.getInput(), + newCond); + for (auto remGate : squashedGates) + rewriter.eraseOp(remGate); + return success(); + } + } + + if (!op.getInput().hasOneUse()) + return failure(); + + if (auto seqParent = op.getInput().getDefiningOp()) { + auto sequenceIndex = cast(op.getInput()).getResultNumber(); + + // Hoist common factors of a sequence + if (sequenceIndex == 0) + if (succeeded(hoistCommonGateFactors(seqParent, rewriter))) + return success(); + + // Break out adjacent subsequences with identical conditions. + SmallVector locs; + size_t breakOutStart = sequenceIndex; + while (breakOutStart > 0) { + auto prevGate = + getSingleGateChild(seqParent.getResult(breakOutStart - 1)); + if (!!prevGate && prevGate.getEnable() == op.getEnable()) { + locs.push_back(prevGate.getLoc()); + breakOutStart--; + } else { + break; + } + } + size_t breakOutEnd = sequenceIndex; + while (breakOutEnd < seqParent.getLength() - 1) { + auto nextGate = getSingleGateChild(seqParent.getResult(breakOutEnd + 1)); + if (!!nextGate && nextGate.getEnable() == op.getEnable()) { + locs.push_back(nextGate.getLoc()); + breakOutEnd++; + } else { + break; + } + } + + if (breakOutStart != breakOutEnd) { + locs.push_back(op.getLoc()); + auto fusedLoc = FusedLoc::get(op.getContext(), locs); + op->setLoc(fusedLoc); + SmallVector subSeqVals; + subSeqVals.reserve(breakOutEnd - breakOutStart + 1); + for (size_t i = breakOutStart; i <= breakOutEnd; ++i) + subSeqVals.push_back( + getSingleGateChild(seqParent.getResult(i)).getResult()); + rewriter.setInsertionPointAfter(op); + auto subSeq = rewriter.create( + op.getLoc(), op.getResult(), subSeqVals.size()); + for (auto [i, useVal] : llvm::enumerate(subSeqVals)) { + auto parentGate = useVal.getDefiningOp(); + assert(useVal.hasOneUse() || parentGate == op); + assert(cast(parentGate).getEnable() == op.getEnable()); + rewriter.replaceAllUsesExcept(useVal, subSeq.getResult(i), subSeq); + if (parentGate != op) + rewriter.eraseOp(parentGate); + } + return success(); + } + } + + return failure(); +} + //===----------------------------------------------------------------------===// // TableGen generated logic. //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/Sim/Transforms/CMakeLists.txt b/lib/Dialect/Sim/Transforms/CMakeLists.txt index d6caf2ddc26f..fdb9201d50d4 100644 --- a/lib/Dialect/Sim/Transforms/CMakeLists.txt +++ b/lib/Dialect/Sim/Transforms/CMakeLists.txt @@ -1,6 +1,7 @@ add_circt_dialect_library(CIRCTSimTransforms LowerDPIFunc.cpp ProceduralizeSim.cpp + SerializeTriggers.cpp DEPENDS diff --git a/lib/Dialect/Sim/Transforms/SerializeTriggers.cpp b/lib/Dialect/Sim/Transforms/SerializeTriggers.cpp new file mode 100644 index 000000000000..12b9a306f46c --- /dev/null +++ b/lib/Dialect/Sim/Transforms/SerializeTriggers.cpp @@ -0,0 +1,120 @@ +//===- SerializeTriggers.cpp - TODO ---------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// TODO +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Sim/SimOps.h" +#include "circt/Dialect/Sim/SimTypes.h" +#include "mlir/Pass/Pass.h" +#include "llvm/ADT/TypeSwitch.h" +#include "llvm/Support/Debug.h" + +#define DEBUG_TYPE "serialize-triggers" + +namespace circt { +namespace sim { +#define GEN_PASS_DEF_SERIALIZETRIGGERS +#include "circt/Dialect/Sim/SimPasses.h.inc" +} // namespace sim +} // namespace circt + +using namespace mlir; +using namespace circt; +using namespace sim; + +namespace { +struct SerializeTriggersPass + : sim::impl::SerializeTriggersBase { +public: + void runOnOperation() override; + +private: +}; + +static Value getParentTrigger(Operation *op) { + if (!op) + return {}; + return TypeSwitch(op) + .Case([](auto op) { return op.getTrigger(); }) + .Case([](auto op) { return op.getInput(); }) + .Case([](auto op) { return op.getParent(); }) + .Default([](auto) { return Value{}; }); +} + +static void setParentTrigger(Operation *op, Value trigger) { + TypeSwitch(op) + .Case( + [&](auto op) { op.getTriggerMutable().assign(trigger); }) + .Case( + [&](auto op) { op.getInputMutable().assign(trigger); }) + .Case( + [&](auto op) { op.getParentMutable().assign(trigger); }) + .Default([](auto) { assert(false && "Not a valid trigger user op"); }); +} + +} // namespace + +void SerializeTriggersPass::runOnOperation() { + auto theModule = getOperation(); + + OpBuilder builder(theModule); + + bool anyChanged = false; + + struct ValueMapEntry { + size_t length; + size_t index; + TriggerSequenceOp sequence; + }; + + DenseMap concurrentTriggers; + + theModule.walk([&](Operation *op) { + auto parentTrigger = getParentTrigger(op); + if (!parentTrigger || parentTrigger.use_empty() || + parentTrigger.hasOneUse()) + return; + + auto mapEntryIt = concurrentTriggers.find(parentTrigger); + if (mapEntryIt == concurrentTriggers.end()) { + anyChanged = true; + auto defOp = parentTrigger.getDefiningOp(); + if (!!defOp && defOp->getBlock() == op->getBlock()) + builder.setInsertionPoint(op); + else + builder.setInsertionPointToStart(theModule.getBodyBlock()); + Location loc = !defOp ? theModule.getLoc() : defOp->getLoc(); + size_t numUsers = + std::distance(parentTrigger.use_begin(), parentTrigger.use_end()); + auto sequence = + builder.create(loc, parentTrigger, numUsers); + setParentTrigger(op, sequence.getResult(0)); + concurrentTriggers.insert(std::pair( + parentTrigger, ValueMapEntry{numUsers, 1, sequence})); + } else { + auto &index = mapEntryIt->second.index; + setParentTrigger(op, mapEntryIt->second.sequence.getResult(index)); + ++index; + if (index == mapEntryIt->second.length) + concurrentTriggers.erase(mapEntryIt); + } + }); + + if (!concurrentTriggers.empty()) { + signalPassFailure(); + for (auto leftoverEntry : concurrentTriggers) { + for (auto user : leftoverEntry.getFirst().getUsers()) + user->emitError("Failed to serialize trigger user op."); + } + } + + if (!anyChanged) + markAllAnalysesPreserved(); +} diff --git a/lib/Firtool/CMakeLists.txt b/lib/Firtool/CMakeLists.txt index 9441b95172cf..8a7b6d4b9072 100644 --- a/lib/Firtool/CMakeLists.txt +++ b/lib/Firtool/CMakeLists.txt @@ -10,9 +10,11 @@ add_circt_library(CIRCTFirtool CIRCTHWTransforms CIRCTLTLToCore CIRCTOMTransforms + CIRCTSCFToSV CIRCTSeqToSV CIRCTSimToSV CIRCTSeqTransforms + CIRCTSimTransforms CIRCTSVTransforms CIRCTTransforms CIRCTVerifToSV diff --git a/lib/Firtool/Firtool.cpp b/lib/Firtool/Firtool.cpp index 555e5e4d9419..b03c7c6bcf31 100644 --- a/lib/Firtool/Firtool.cpp +++ b/lib/Firtool/Firtool.cpp @@ -14,6 +14,7 @@ #include "circt/Dialect/OM/OMPasses.h" #include "circt/Dialect/SV/SVPasses.h" #include "circt/Dialect/Seq/SeqPasses.h" +#include "circt/Dialect/Sim/SimPasses.h" #include "circt/Dialect/Verif/VerifPasses.h" #include "circt/Support/Passes.h" #include "circt/Transforms/Passes.h" @@ -284,6 +285,7 @@ LogicalResult firtool::populateLowFIRRTLToHW(mlir::PassManager &pm, if (!opt.shouldDisableOptimization()) { auto &modulePM = pm.nest(); + modulePM.addPass(sim::createSerializeTriggers()); modulePM.addPass(mlir::createCSEPass()); modulePM.addPass(createSimpleCanonicalizerPass()); } @@ -294,8 +296,9 @@ LogicalResult firtool::populateLowFIRRTLToHW(mlir::PassManager &pm, // Check OM object fields. pm.addPass(om::createVerifyObjectFieldsPass()); + auto &modulePM = pm.nest(); // Run the verif op verification pass - pm.addNestedPass(verif::createVerifyClockedAssertLikePass()); + modulePM.addPass(verif::createVerifyClockedAssertLikePass()); return success(); } @@ -311,6 +314,13 @@ LogicalResult firtool::populateHWToSV(mlir::PassManager &pm, opt.shouldEtcDisableModuleInlining())); pm.addPass(seq::createExternalizeClockGatePass(opt.getClockGateOptions())); + pm.addPass(circt::createLowerTriggersToSV()); + { + auto &modulePM = pm.nest(); + modulePM.addPass(circt::createSCFToSV()); + modulePM.addPass(circt::createLowerPrintsToSV( + LowerPrintsToSVOptions{/*printToStdErr*/ true})); + } pm.addPass(circt::createLowerSimToSVPass()); pm.addPass(circt::createLowerSeqToSVPass( {/*disableRegRandomization=*/!opt.isRandomEnabled( 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..2adf90749c57 --- /dev/null +++ b/test/Dialect/Sim/triggers.mlir @@ -0,0 +1,202 @@ +// 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 @constant_clock +hw.module @constant_clock(out o0 : !sim.trigger.edge) { + // CHECK-NOT: sim.on_edge + // CHECK: %[[NEVER:.*]] = sim.never : !sim.trigger.edge + // CHECK: %[[NEVER]] + %clock = seq.const_clock low + %0 = sim.on_edge edge %clock + hw.output %0 : !sim.trigger.edge +} + +// CHECK-LABEL: hw.module @fold_gates +hw.module @fold_gates(in %trig : !sim.trigger.edge, in %en: i1, out o0 : !sim.trigger.edge, out o1 : !sim.trigger.edge, out o2 : !sim.trigger.edge) { + %true = hw.constant true + %false = hw.constant false + // CHECK: %[[NEVER:.*]] = sim.never : !sim.trigger.edge + %never = sim.never : !sim.trigger.edge + // CHECK-NOT: sim.trigger_gate + %on = sim.trigger_gate %trig if %true : !sim.trigger.edge + %off = sim.trigger_gate %trig if %false : !sim.trigger.edge + %dyn = sim.trigger_gate %never if %en : !sim.trigger.edge + // CHECK: hw.output %trig, %[[NEVER]], %[[NEVER]] + hw.output %on, %off, %dyn : !sim.trigger.edge, !sim.trigger.edge, !sim.trigger.edge +} + +// 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 + %false = hw.constant false + + // CHECK: %[[GATE:.*]] = sim.trigger_gate %trig if %en + %enGate = sim.trigger_gate %trig if %en : !sim.trigger.edge + %deadGate = sim.trigger_gate %trig if %false : !sim.trigger.edge + + // CHECK: sim.triggered () on (%[[GATE]] : !sim.trigger.edge) { + // CHECK-NEXT: "Don't touch live process" + sim.triggered () on (%enGate : !sim.trigger.edge) { + %0 = sim.fmt.lit "Don't touch live process" + sim.proc.print %0 + } : () -> () + + // CHECK: %[[RES:.*]]:2 = sim.triggered (%a) on (%[[GATE]] : !sim.trigger.edge) 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 (%enGate : !sim.trigger.edge) 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 (%deadGate : !sim.trigger.edge) 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 (%deadGate : !sim.trigger.edge) { + %0 = sim.fmt.lit "Remove dead process" + sim.proc.print %0 + } : () -> () + + sim.triggered () on (%enGate : !sim.trigger.edge) { + } : () -> () + + // 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 +} + +// CHECK-LABEL: hw.module @always_enabled_gate(in %trig : !sim.trigger.init, out o : !sim.trigger.init) +// CHECK: hw.output %trig +hw.module @always_enabled_gate(in %trig : !sim.trigger.init, out o : !sim.trigger.init) { + %true = hw.constant true + %gate = sim.trigger_gate %trig if %true : !sim.trigger.init + hw.output %gate : !sim.trigger.init +} + +// CHECK-LABEL: hw.module @always_disabled_gate(in %trig : !sim.trigger.init, out o : !sim.trigger.init) +// CHECK: [[NEVER:%.*]] = sim.never : !sim.trigger.init +// CHECK: hw.output [[NEVER]] : !sim.trigger.init +hw.module @always_disabled_gate(in %trig : !sim.trigger.init, out o : !sim.trigger.init) { + %false = hw.constant false + %gate = sim.trigger_gate %trig if %false : !sim.trigger.init + hw.output %gate : !sim.trigger.init +}