diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index dddc507bd80f..6eb67b865093 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -654,7 +654,7 @@ def ConvertToArcs : Pass<"convert-to-arcs", "mlir::ModuleOp"> { latency. }]; let constructor = "circt::createConvertToArcsPass()"; - let dependentDialects = ["circt::arc::ArcDialect"]; + let dependentDialects = ["circt::arc::ArcDialect", "circt::hw::HWDialect"]; let options = [ Option<"tapRegisters", "tap-registers", "bool", "true", "Make registers observable">, diff --git a/include/circt/Dialect/Arc/ArcOps.td b/include/circt/Dialect/Arc/ArcOps.td index a4ebdae14dde..02781979d217 100644 --- a/include/circt/Dialect/Arc/ArcOps.td +++ b/include/circt/Dialect/Arc/ArcOps.td @@ -134,6 +134,9 @@ def StateOp : ArcOp<"state", [ DeclareOpInterfaceMethods, AttrSizedOperandSegments, DeclareOpInterfaceMethods, + PredOpTrait<"types of initial arguments match result types", + CPred<[{getInitials().empty() || + llvm::equal(getInitials().getType(), getResults().getType())}]>> ]> { let summary = "State transfer arc"; @@ -143,13 +146,15 @@ def StateOp : ArcOp<"state", [ Optional:$enable, Optional:$reset, I32Attr:$latency, - Variadic:$inputs); + Variadic:$inputs, + Variadic:$initials); let results = (outs Variadic:$outputs); let assemblyFormat = [{ $arc `(` $inputs `)` (`clock` $clock^)? (`enable` $enable^)? - (`reset` $reset^)? `latency` $latency attr-dict - `:` functional-type($inputs, results) + (`reset` $reset^)? + ( `initial` ` ` `(` $initials^ `:` type($initials) `)`)? + `latency` $latency attr-dict `:` functional-type($inputs, results) }]; let hasFolder = 1; @@ -157,21 +162,24 @@ def StateOp : ArcOp<"state", [ let builders = [ OpBuilder<(ins "DefineOp":$arc, "mlir::Value":$clock, "mlir::Value":$enable, - "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs), [{ + "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials), [{ build($_builder, $_state, mlir::SymbolRefAttr::get(arc), arc.getFunctionType().getResults(), clock, enable, latency, - inputs); + inputs, initials); }]>, OpBuilder<(ins "mlir::SymbolRefAttr":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, - CArg<"mlir::ValueRange", "{}">:$inputs + CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ build($_builder, $_state, arc, results, clock, enable, Value(), latency, - inputs); + inputs, initials); }]>, OpBuilder<(ins "mlir::SymbolRefAttr":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "mlir::Value":$reset, - "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs + "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ if (clock) $_state.addOperands(clock); @@ -180,6 +188,7 @@ def StateOp : ArcOp<"state", [ if (reset) $_state.addOperands(reset); $_state.addOperands(inputs); + $_state.addOperands(initials); $_state.addAttribute("arc", arc); $_state.addAttribute("latency", $_builder.getI32IntegerAttr(latency)); $_state.addAttribute(getOperandSegmentSizeAttr(), @@ -187,23 +196,26 @@ def StateOp : ArcOp<"state", [ clock ? 1 : 0, enable ? 1 : 0, reset ? 1 : 0, - static_cast(inputs.size())})); + static_cast(inputs.size()), + static_cast(initials.size())})); $_state.addTypes(results); }]>, OpBuilder<(ins "mlir::StringAttr":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, - CArg<"mlir::ValueRange", "{}">:$inputs + CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ build($_builder, $_state, mlir::SymbolRefAttr::get(arc), results, clock, - enable, latency, inputs); + enable, latency, inputs, initials); }]>, OpBuilder<(ins "mlir::StringRef":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, - CArg<"mlir::ValueRange", "{}">:$inputs + CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ build($_builder, $_state, mlir::StringAttr::get($_builder.getContext(), arc), - results, clock, enable, latency, inputs); + results, clock, enable, latency, inputs, initials); }]> ]; let skipDefaultBuilders = 1; @@ -429,26 +441,37 @@ def ClockDomainOp : ArcOp<"clock_domain", [ let hasCanonicalizeMethod = 1; } -def ClockTreeOp : ArcOp<"clock_tree", [NoTerminator, NoRegionArguments]> { +//===----------------------------------------------------------------------===// +// (Pseudo) Clock Trees +//===----------------------------------------------------------------------===// + +class ClockTreeLikeOp traits = []>: + ArcOp +])> { + let regions = (region SizedRegion<1>:$body); +} + +def ClockTreeOp : ClockTreeLikeOp<"clock_tree"> { let summary = "A clock tree"; let arguments = (ins I1:$clock); - let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ $clock attr-dict-with-keyword $body }]; - let extraClassDeclaration = [{ - mlir::Block &getBodyBlock() { return getBody().front(); } - }]; } -def PassThroughOp : ArcOp<"passthrough", [NoTerminator, NoRegionArguments]> { +def PassThroughOp : ClockTreeLikeOp<"passthrough"> { let summary = "Clock-less logic that is on the pass-through path"; - let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ attr-dict-with-keyword $body }]; - let extraClassDeclaration = [{ - mlir::Block &getBodyBlock() { return getBody().front(); } +} + +def InitialOp : ClockTreeLikeOp<"initial"> { + let summary = "Clock-less logic called at the start of simulation"; + let assemblyFormat = [{ + attr-dict-with-keyword $body }]; } @@ -651,19 +674,22 @@ def TapOp : ArcOp<"tap"> { let assemblyFormat = [{ $value attr-dict `:` type($value) }]; } -def ModelOp : ArcOp<"model", [RegionKindInterface, IsolatedFromAbove, - NoTerminator, Symbol]> { +def ModelOp : ArcOp<"model", [ + RegionKindInterface, IsolatedFromAbove, NoTerminator, Symbol, + DeclareOpInterfaceMethods +]> { let summary = "A model with stratified clocks"; let description = [{ A model with stratified clocks. The `io` optional attribute specifies the I/O of the module associated to this model. }]; let arguments = (ins SymbolNameAttr:$sym_name, - TypeAttrOf:$io); + TypeAttrOf:$io, + OptionalAttr:$initialFn); let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ - $sym_name `io` $io attr-dict-with-keyword $body + $sym_name `io` $io (`initializer` $initialFn^)? attr-dict-with-keyword $body }]; let extraClassDeclaration = [{ diff --git a/include/circt/Dialect/Arc/ModelInfo.h b/include/circt/Dialect/Arc/ModelInfo.h index 322f38b774c5..ca3918a772f0 100644 --- a/include/circt/Dialect/Arc/ModelInfo.h +++ b/include/circt/Dialect/Arc/ModelInfo.h @@ -36,11 +36,13 @@ struct ModelInfo { std::string name; size_t numStateBytes; llvm::SmallVector states; + mlir::FlatSymbolRefAttr initialFnSym; ModelInfo(std::string name, size_t numStateBytes, - llvm::SmallVector states) + llvm::SmallVector states, + mlir::FlatSymbolRefAttr initialFnSym) : name(std::move(name)), numStateBytes(numStateBytes), - states(std::move(states)) {} + states(std::move(states)), initialFnSym(initialFnSym) {} }; /// Collects information about states within the provided Arc model storage diff --git a/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td b/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td index 1fd8847385ac..9b184a96868d 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td @@ -36,10 +36,50 @@ class ReferableDeclOp traits = []> : /// located in a hardware-creating context, such as the body of a module. class HardwareDeclOp traits = []> : ReferableDeclOp]> {} +def FormalOp : FIRRTLOp<"formal", [ + HasParent<"firrtl::CircuitOp">, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods +]> { + let summary = "A formal test definition."; + let description = [{ + The `firrtl.formal` operation defines a formal verification problem in the same + context as the rest of the design. This problem is solved using bounded model + checking and should be given a bound k, which represents the number of cycles considered + during model checking. This definition marks a test harness defined as an internal module to + be verified using bounded model checking. + + Example: + ```mlir + // DUT + firrtl.module @Foo(in %bar: !firrtl.uint<8>, out %out: !firrtl.uint<8>) { ... } + + // Test harness + firrtl.module @FooTest(in %bar_s: !firrtl.uint<8>) { + %bar, %out = firrtl.instance foo @Foo(in bar: %bar_s: !firrtl.uint<8>, out out: !firrtl.uint<8>) + %c42_8 = firrtl.constant 42 : !firrtl.uint<8> + firrtl.connect %bar, %c42_8: !firrtl.uint<8>, !firrtl.uint<8> + %c69_8 = firrtl.constant 69 : !firrtl.uint<8> + %cond = firrtl.eq %c69_8, %out : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<1> + firrtl.assert %cond + } + + // Mark test harness as formal test + firrtl.formal @formal1 of @FooTest bound 20 + ``` + }]; + + let arguments = (ins SymbolNameAttr:$sym_name, FlatSymbolRefAttr:$moduleName, UI64Attr:$bound); + let results = (outs); + let assemblyFormat = [{ + $sym_name `of` $moduleName `bound` $bound attr-dict + }]; +} + def InstanceOp : HardwareDeclOp<"instance", [ DeclareOpInterfaceMethods, DeclareOpInterfaceMethods, diff --git a/include/circt/Dialect/FIRRTL/Passes.h b/include/circt/Dialect/FIRRTL/Passes.h index d3dd11735179..1ae1e07e5358 100644 --- a/include/circt/Dialect/FIRRTL/Passes.h +++ b/include/circt/Dialect/FIRRTL/Passes.h @@ -214,6 +214,8 @@ std::unique_ptr createLowerDPIPass(); std::unique_ptr createAssignOutputDirsPass(mlir::StringRef outputDir = ""); +std::unique_ptr createCheckRecursiveInstantiation(); + /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION #include "circt/Dialect/FIRRTL/Passes.h.inc" diff --git a/include/circt/Dialect/FIRRTL/Passes.td b/include/circt/Dialect/FIRRTL/Passes.td index 3c3e3c4d841a..ec7043fb6a97 100644 --- a/include/circt/Dialect/FIRRTL/Passes.td +++ b/include/circt/Dialect/FIRRTL/Passes.td @@ -970,4 +970,18 @@ def ProbesToSignals : Pass<"firrtl-probes-to-signals", "firrtl::CircuitOp"> { let constructor = "circt::firrtl::createProbesToSignalsPass()"; } +def CheckRecursiveInstantiation : Pass<"firrtl-check-recursive-instantiation", + "firrtl::CircuitOp"> { + let summary = "Check for illegal recursive instantiation"; + let description = [{ + This pass checks for illegal recursive module instantion. Recursive + instantiation is when a module instantiates itself, either directly or + indirectly through other modules it instantiates. Recursive module + instantiation is illegal because it would require infinite hardware to + synthesize. Recursive class instantiation is illegal as it would create an + infinite loop. + }]; + let constructor = "circt::firrtl::createCheckRecursiveInstantiation()"; +} + #endif // CIRCT_DIALECT_FIRRTL_PASSES_TD diff --git a/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td b/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td index 165218abcb3b..ae9c60bd0cc8 100644 --- a/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td @@ -11,12 +11,14 @@ //===----------------------------------------------------------------------===// include "mlir/IR/EnumAttr.td" - -def SigOp : LLHDOp<"sig", [ - TypesMatchWith< - "type of 'init' and underlying type of 'signal' have to match.", - "init", "result", "hw::InOutType::get($_self)"> - ]> { +include "mlir/IR/OpAsmInterface.td" + +def SignalOp : LLHDOp<"sig", [ + DeclareOpInterfaceMethods, + TypesMatchWith< + "type of 'init' and underlying type of 'signal' have to match.", + "init", "result", "hw::InOutType::get($_self)"> +]> { let summary = "Create a signal."; let description = [{ The `llhd.sig` instruction introduces a new signal in the IR. The input @@ -28,17 +30,22 @@ def SigOp : LLHDOp<"sig", [ ```mlir %c123_i64 = hw.constant 123 : i64 - %sig_i64 = llhd.sig "foo" %c123_i64 : i64 + %foo = llhd.sig %c123_i64 : i64 + %0 = llhd.sig name "foo" %c123_i64 : i64 ``` This example creates a new signal named "foo", carrying an `i64` type with initial value of 123. }]; - - let arguments = (ins StrAttr: $name, HWValueType: $init); - let results = (outs InOutType: $result); - - let assemblyFormat = "$name $init attr-dict `:` qualified(type($init))"; + let arguments = (ins + OptionalAttr:$name, + HWValueType:$init + ); + let results = (outs Res:$result); + let assemblyFormat = [{ + `` custom($name) $init attr-dict + `:` type($init) + }]; } def PrbOp : LLHDOp<"prb", [ diff --git a/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td index 3f007c8c7689..c288c68e559b 100644 --- a/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td @@ -12,6 +12,7 @@ def ProcessOp : LLHDOp<"process", [ NoRegionArguments, + RecursiveMemoryEffects, HasParent<"hw::HWModuleOp"> ]> { let summary = "create a process"; @@ -51,6 +52,39 @@ def ProcessOp : LLHDOp<"process", [ let assemblyFormat = "attr-dict-with-keyword $body"; } +def FinalOp : LLHDOp<"final", [ + NoRegionArguments, + RecursiveMemoryEffects, + HasParent<"hw::HWModuleOp">, +]> { + let summary = "A process that runs at the end of simulation"; + let description = [{ + An `llhd.final` op encapsulates a region of IR that is to be executed after + the last time step of a simulation has completed. This can be used to + implement various forms of state cleanup and tear-down. Some verifications + ops may also want to check that certain final conditions hold at the end of + a simulation run. + + The `llhd.wait` terminator is not allowed in `llhd.final` processes since + there is no later time slot for the execution to resume. Control flow must + eventually end in an `llhd.halt` terminator. + + Execution order between multiple `llhd.final` ops is undefined. + + Example: + ```mlir + hw.module @Foo() { + llhd.final { + func.call @printSimulationStatistics() : () -> () + llhd.halt + } + } + ``` + }]; + let regions = (region MinSizedRegion<1>: $body); + let assemblyFormat = "attr-dict-with-keyword $body"; +} + def ConnectOp : LLHDOp<"con", [ SameTypeOperands, HasParent<"hw::HWModuleOp"> @@ -96,24 +130,26 @@ def WaitOp : LLHDOp<"wait", [ ``` }]; - let arguments = (ins Variadic:$obs, + let arguments = (ins Variadic:$observed, Optional:$time, Variadic:$destOps); let successors = (successor AnySuccessor:$dest); let assemblyFormat = [{ - (`for` $time^ `,`)? (`(`$obs^ `:` qualified(type($obs))`)` `,`)? + (`for` $time^ `,`)? (`(`$observed^ `:` qualified(type($observed))`)` `,`)? $dest (`(` $destOps^ `:` qualified(type($destOps)) `)`)? attr-dict }]; } -def HaltOp : LLHDOp<"halt", [Terminator, HasParent<"ProcessOp">]> { +def HaltOp : LLHDOp<"halt", [ + Terminator, + ParentOneOf<["ProcessOp", "FinalOp"]> +]> { let summary = "Terminates execution of a process."; let description = [{ The `halt` instruction terminates execution of a process. All processes must halt eventually or consist of an infinite loop. }]; - let assemblyFormat = "attr-dict"; } diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index 27413163d204..b0ca268ec8c1 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -358,58 +358,103 @@ def NonBlockingAssignOp : AssignOpBase<"nonblocking_assign"> { // Statements //===----------------------------------------------------------------------===// -def None: I32EnumAttrCase<"None", 0, "none">; -/// Transit from 0 to x/z/1, and from x/z to 1. +// Any change on the input. +def AnyChange: I32EnumAttrCase<"AnyChange", 0, "any">; +// A transition from 0 to X/Z/1, or from X/Z to 1. def PosEdge: I32EnumAttrCase<"PosEdge", 1, "posedge">; -/// Transit from 1 to x/z/0, and from x/z to 0. +// A transition from 1 to X/Z/0, or from X/Z to 0. def NegEdge: I32EnumAttrCase<"NegEdge", 2, "negedge">; -/// Include the negedge and posedge. +// The combination of `PosEdge` and `NegEdge`. def BothEdges: I32EnumAttrCase<"BothEdges", 3, "edge">; -def EdgeAtrr: I32EnumAttr<"Edge", "Edge kind", - [None, PosEdge, NegEdge, BothEdges]>{ +def EdgeAttr: I32EnumAttr<"Edge", "Edge kind", + [AnyChange, PosEdge, NegEdge, BothEdges]> { let cppNamespace = "circt::moore"; } -def EventOp : MooreOp<"wait_event", [ - HasParent<"ProcedureOp"> +def WaitEventOp : MooreOp<"wait_event", [ + RecursiveMemoryEffects, + NoRegionArguments, + SingleBlock, + NoTerminator ]> { - let summary = "Detecting posedge and negedge"; + let summary = "Suspend execution until an event occurs"; let description = [{ - It is introduced by the symbol `@`, and it allows statement execution to - be delayed until the occurrence of some simulation event occurring in a - procedure executing concurrently with this procedure. - - For the implicit event control(`@(*)`), there are two situations that are - not automatically added to event expression: - 1. Identifiers that only appear in wait or event expressions. - ``` - always @(*) begin // equivalent to @(b) - @(n) kid = b; // n is not added to @(*) - end - ``` - 2. Identifiers that only appear as a hierarchical_variable_identifier - in the variable_lvalue of the left-hand side of assignments. - ``` - always @(*) begin - a = b + c; // a is not added to @(*) - end - ``` + The `moore.wait_event` op suspends execution of the current process until + its body signals that an event has been the detected. Conceptually, the body + of this op is executed whenever any potentially relevant signal has changed. + If one of the contained `moore.detect_event` ops detect an event, execution + resumes after the `moore.wait_event` operation. If no event is detected, the + current process remains suspended. + + Example corresponding to the SystemVerilog `@(posedge x, negedge y iff z)`: + ``` + moore.wait_event { + %0 = moore.read %x : + %1 = moore.read %y : + %2 = moore.read %z : + moore.detect_event posedge %0 : i1 + moore.detect_event negedge %1 if %2 : i1 + } + ``` - Example: + The body may also contain any operations necessary to evaluate the event + conditions. For example, the SV `@(posedge ~x iff i == 42)`: ``` - @(a, b, c) // none - @(posedge clk) // positive edge - @(negedge clk) // negative edge - @(edge clk) // both edge - @(*) // implicit event control + moore.wait_event { + %0 = moore.read %x : + %1 = moore.not %0 : i1 + %2 = moore.read %i : + %3 = moore.constant 42 : i19 + %4 = moore.eq %2, %3 : i19 + moore.detect_event posedge %0 if %4 : i1 + } ``` + See IEEE 1800-2017 § 9.4.2 "Event control". }]; - let arguments = (ins EdgeAtrr:$edge, UnpackedType:$input); - let results = (outs); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = [{ attr-dict-with-keyword $body }]; +} + +def DetectEventOp : MooreOp<"detect_event", [ + HasParent<"WaitEventOp"> +]> { + let summary = "Check if an event occured within a `wait_event` op"; + let description = [{ + The `moore.detect_event` op is used inside the body of a `moore.wait_event` + to check if an interesting value change has occurred on its operand. The + `moore.detect_event` op implicitly stores the previous value of its operand + and compares it against the current value to detect an interesting edge: + + - `posedge` checks for a low-to-high transition + - `negedge` checks for a high-to-low transition + - `edge` checks for either a `posedge` or a `negedge` + - `any` checks for any value change (including e.g. X to Z) + + The edges are detected as follows: + + - `0` to `1 X Z`: `posedge` + - `1` to `0 X Z`: `negedge` + - `X Z` to `1`: `posedge` + - `X Z` to `0`: `negedge` + + | From | To 0 | To 1 | To X | To Z | + |-------|---------|---------|---------|---------| + | 0 | - | posedge | posedge | posedge | + | 1 | negedge | - | negedge | negedge | + | X | negedge | posedge | - | - | + | Z | negedge | posedge | - | - | + + See IEEE 1800-2017 § 9.4.2 "Event control". + }]; + let arguments = (ins + EdgeAttr:$edge, + UnpackedType:$input, + Optional:$condition + ); let assemblyFormat = [{ - $edge $input attr-dict `:` type($input) + $edge $input (`if` $condition^)? attr-dict `:` type($input) }]; } diff --git a/include/circt/Dialect/Seq/SeqEnums.h b/include/circt/Dialect/Seq/SeqEnums.h index 85a7e478f8c5..6007eb060dbf 100644 --- a/include/circt/Dialect/Seq/SeqEnums.h +++ b/include/circt/Dialect/Seq/SeqEnums.h @@ -17,4 +17,4 @@ enum class ReadEnableMode { Zero, Ignore, Undefined }; } // namespace seq } // namespace circt -#endif // CIRCT_DIALECT_SEQ_SEQENUMS_H \ No newline at end of file +#endif // CIRCT_DIALECT_SEQ_SEQENUMS_H diff --git a/integration_test/arcilator/JIT/initial-shift-reg.mlir b/integration_test/arcilator/JIT/initial-shift-reg.mlir new file mode 100644 index 000000000000..3724962d8a7f --- /dev/null +++ b/integration_test/arcilator/JIT/initial-shift-reg.mlir @@ -0,0 +1,69 @@ +// RUN: arcilator %s --run --jit-entry=main | FileCheck %s +// REQUIRES: arcilator-jit + +// CHECK-LABEL: output = ca +// CHECK-NEXT: output = ca +// CHECK-NEXT: output = 0 +// CHECK-NEXT: output = fe +// CHECK-NEXT: output = ff + +module { + + hw.module @shiftreg(in %clock : i1, in %reset : i1, in %en : i1, in %din : i8, out dout : i8) { + %seq_clk = seq.to_clock %clock + %srA = seq.firreg %0 clock %seq_clk preset 0xFE : i8 + %srB = seq.firreg %1 clock %seq_clk : i8 + %srC = seq.firreg %2 clock %seq_clk preset 0xCA : i8 + %0 = comb.mux bin %en, %din, %srA : i8 + %1 = comb.mux bin %en, %srA, %srB : i8 + %2 = comb.mux bin %en, %srB, %srC : i8 + hw.output %srC : i8 + } + + func.func @main() { + %ff = arith.constant 0xFF : i8 + %false = arith.constant 0 : i1 + %true = arith.constant 1 : i1 + + arc.sim.instantiate @shiftreg as %model { + arc.sim.set_input %model, "en" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "reset" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "din" = %ff : i8, !arc.sim.instance<@shiftreg> + + %res0 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res0 : i8 + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + + %res1 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res1 : i8 + + arc.sim.set_input %model, "en" = %true : i1, !arc.sim.instance<@shiftreg> + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + %res2 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res2 : i8 + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + %res3 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res3 : i8 + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + %res4 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res4 : i8 + } + return + } +} diff --git a/integration_test/arcilator/JIT/initial.mlir b/integration_test/arcilator/JIT/initial.mlir new file mode 100644 index 000000000000..7cde23b21072 --- /dev/null +++ b/integration_test/arcilator/JIT/initial.mlir @@ -0,0 +1,25 @@ +// RUN: arcilator %s --run --jit-entry=main 2>&1 >/dev/null | FileCheck %s +// REQUIRES: arcilator-jit + +// CHECK: - Init - + +module { + llvm.func @_arc_env_get_print_stream(i32) -> !llvm.ptr + llvm.func @_arc_libc_fputs(!llvm.ptr, !llvm.ptr) -> i32 + llvm.mlir.global internal constant @global_init_str(" - Init -\0A\00") {addr_space = 0 : i32} + + arc.model @initmodel io !hw.modty<> { + ^bb0(%arg0: !arc.storage): + arc.initial { + %cst0 = llvm.mlir.constant(0 : i32) : i32 + %stderr = llvm.call @_arc_env_get_print_stream(%cst0) : (i32) -> !llvm.ptr + %str = llvm.mlir.addressof @global_init_str : !llvm.ptr + %0 = llvm.call @_arc_libc_fputs(%str, %stderr) : (!llvm.ptr, !llvm.ptr) -> i32 + } + } + func.func @main() { + arc.sim.instantiate @initmodel as %arg0 { + } + return + } +} diff --git a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp index 086038148c04..d6e4f5bcc130 100644 --- a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp +++ b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp @@ -318,6 +318,7 @@ namespace { struct ModelInfoMap { size_t numStateBytes; llvm::DenseMap states; + mlir::FlatSymbolRefAttr initialFnSymbol; }; template @@ -378,6 +379,16 @@ struct SimInstantiateOpLowering Value zero = rewriter.create(loc, rewriter.getI8Type(), 0); rewriter.create(loc, allocated, zero, numStateBytes, false); + + // Call the model's 'initial' function if present. + if (model.initialFnSymbol) { + auto initialFnType = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(op.getContext()), + {LLVM::LLVMPointerType::get(op.getContext())}); + rewriter.create(loc, initialFnType, model.initialFnSymbol, + ValueRange{allocated}); + } + rewriter.inlineBlockBefore(&adaptor.getBody().getBlocks().front(), op, {allocated}); rewriter.create(loc, freeFunc, ValueRange{allocated}); @@ -646,7 +657,8 @@ void LowerArcToLLVMPass::runOnOperation() { for (StateInfo &stateInfo : modelInfo.states) states.insert({stateInfo.name, stateInfo}); modelMap.insert({modelInfo.name, - ModelInfoMap{modelInfo.numStateBytes, std::move(states)}}); + ModelInfoMap{modelInfo.numStateBytes, std::move(states), + modelInfo.initialFnSym}}); } patterns.add(callOp.getOperation()); Value clock = stateOp ? stateOp.getClock() : Value{}; Value reset; + SmallVector initialValues; SmallVector absorbedRegs; SmallVector absorbedNames(callOp->getNumResults(), {}); if (auto names = callOp->getAttrOfType("names")) @@ -307,6 +308,8 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { } } + initialValues.push_back(regOp.getPowerOnValue()); + absorbedRegs.push_back(regOp); // If we absorb a register into the arc, the arc effectively produces that // register's value. So if the register had a name, ensure that we assign @@ -345,6 +348,28 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { "had a reset."); arc.getResetMutable().assign(reset); } + + bool onlyDefaultInitializers = + llvm::all_of(initialValues, [](auto val) -> bool { return !val; }); + + if (!onlyDefaultInitializers) { + if (!arc.getInitials().empty()) { + return arc.emitError( + "StateOp tried to infer initial values from CompReg, but already " + "had an initial value."); + } + // Create 0 constants for default initialization + for (unsigned i = 0; i < initialValues.size(); ++i) { + if (!initialValues[i]) { + OpBuilder zeroBuilder(arc); + initialValues[i] = zeroBuilder.createOrFold( + arc.getLoc(), + zeroBuilder.getIntegerAttr(arc.getResult(i).getType(), 0)); + } + } + arc.getInitialsMutable().assign(initialValues); + } + if (tapRegisters && llvm::any_of(absorbedNames, [](auto name) { return !cast(name).getValue().empty(); })) @@ -385,6 +410,7 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { SmallVector outputs; SmallVector names; SmallVector types; + SmallVector initialValues; SmallDenseMap mapping; SmallVector regToOutputMapping; for (auto regOp : regOps) { @@ -395,6 +421,7 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { types.push_back(regOp.getType()); outputs.push_back(block->addArgument(regOp.getType(), regOp.getLoc())); names.push_back(regOp->getAttrOfType("name")); + initialValues.push_back(regOp.getPowerOnValue()); } regToOutputMapping.push_back(it->second); } @@ -411,9 +438,22 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { defOp.getBody().push_back(block.release()); builder.setInsertionPoint(module.getBodyBlock()->getTerminator()); + + bool onlyDefaultInitializers = + llvm::all_of(initialValues, [](auto val) -> bool { return !val; }); + + if (onlyDefaultInitializers) + initialValues.clear(); + else + for (unsigned i = 0; i < initialValues.size(); ++i) { + if (!initialValues[i]) + initialValues[i] = builder.createOrFold( + loc, builder.getIntegerAttr(types[i], 0)); + } + auto arcOp = builder.create(loc, defOp, std::get<0>(clockAndResetAndOp), - /*enable=*/Value{}, 1, inputs); + /*enable=*/Value{}, 1, inputs, initialValues); auto reset = std::get<1>(clockAndResetAndOp); if (reset) arcOp.getResetMutable().assign(reset); diff --git a/lib/Conversion/ExportVerilog/ExportVerilog.cpp b/lib/Conversion/ExportVerilog/ExportVerilog.cpp index 74e5760ccae1..a67d02581c64 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilog.cpp +++ b/lib/Conversion/ExportVerilog/ExportVerilog.cpp @@ -341,6 +341,13 @@ static Type stripUnpackedTypes(Type type) { .Default([](Type type) { return type; }); } +/// Return true if the type has a leading unpacked type. +static bool hasLeadingUnpackedType(Type type) { + assert(isa(type) && "inout type is expected"); + auto elementType = cast(type).getElementType(); + return stripUnpackedTypes(elementType) != elementType; +} + /// Return true if type has a struct type as a subtype. static bool hasStructType(Type type) { return TypeSwitch(type) @@ -5855,8 +5862,11 @@ LogicalResult StmtEmitter::emitDeclaration(Operation *op) { } // Try inlining an assignment into declarations. + // FIXME: Unpacked array is not inlined since several tools doesn't support + // that syntax. See Issue 6363. if (isa(op) && - !op->getParentOp()->hasTrait()) { + !op->getParentOp()->hasTrait() && + !hasLeadingUnpackedType(op->getResult(0).getType())) { // Get a single assignments if any. if (auto singleAssign = getSingleAssignAndCheckUsers(op)) { auto *source = singleAssign.getSrc().getDefiningOp(); @@ -5877,7 +5887,10 @@ LogicalResult StmtEmitter::emitDeclaration(Operation *op) { } // Try inlining a blocking assignment to logic op declaration. - if (isa(op) && op->getParentOp()->hasTrait()) { + // FIXME: Unpacked array is not inlined since several tools doesn't support + // that syntax. See Issue 6363. + 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)) { // It is necessary for the assignment to dominate users of the op. diff --git a/lib/Conversion/ImportVerilog/Expressions.cpp b/lib/Conversion/ImportVerilog/Expressions.cpp index ded3bd8bd8e3..8ae630cc9432 100644 --- a/lib/Conversion/ImportVerilog/Expressions.cpp +++ b/lib/Conversion/ImportVerilog/Expressions.cpp @@ -62,32 +62,6 @@ struct RvalueExprVisitor { return {}; } - /// Helper function to convert a value to its "truthy" boolean value. - Value convertToBool(Value value) { - if (!value) - return {}; - if (auto type = dyn_cast_or_null(value.getType())) - if (type.getBitSize() == 1) - return value; - if (auto type = dyn_cast_or_null(value.getType())) - return builder.create(loc, value); - mlir::emitError(loc, "expression of type ") - << value.getType() << " cannot be cast to a boolean"; - return {}; - } - - /// Helper function to convert a value to its "truthy" boolean value and - /// convert it to the given domain. - Value convertToBool(Value value, Domain domain) { - value = convertToBool(value); - if (!value) - return {}; - auto type = moore::IntType::get(context.getContext(), 1, domain); - if (value.getType() == type) - return value; - return builder.create(loc, type, value); - } - // Handle references to the left-hand side of a parent assignment. Value visit(const slang::ast::LValueReferenceExpression &expr) { assert(!context.lvalueStack.empty() && "parent assignments push lvalue"); @@ -98,8 +72,12 @@ struct RvalueExprVisitor { // Handle named values, such as references to declared variables. Value visit(const slang::ast::NamedValueExpression &expr) { if (auto value = context.valueSymbols.lookup(&expr.symbol)) { - if (isa(value.getType())) - value = builder.create(loc, value); + if (isa(value.getType())) { + auto readOp = builder.create(loc, value); + if (context.rvalueReadCallback) + context.rvalueReadCallback(readOp); + value = readOp.getResult(); + } return value; } @@ -229,7 +207,7 @@ struct RvalueExprVisitor { return createReduction(arg, true); case UnaryOperator::LogicalNot: - arg = convertToBool(arg); + arg = context.convertToBool(arg); if (!arg) return {}; return builder.create(loc, arg); @@ -358,10 +336,10 @@ struct RvalueExprVisitor { case BinaryOperator::LogicalAnd: { // TODO: This should short-circuit. Put the RHS code into a separate // block. - lhs = convertToBool(lhs, domain); + lhs = context.convertToBool(lhs, domain); if (!lhs) return {}; - rhs = convertToBool(rhs, domain); + rhs = context.convertToBool(rhs, domain); if (!rhs) return {}; return builder.create(loc, lhs, rhs); @@ -369,20 +347,20 @@ struct RvalueExprVisitor { case BinaryOperator::LogicalOr: { // TODO: This should short-circuit. Put the RHS code into a separate // block. - lhs = convertToBool(lhs, domain); + lhs = context.convertToBool(lhs, domain); if (!lhs) return {}; - rhs = convertToBool(rhs, domain); + rhs = context.convertToBool(rhs, domain); if (!rhs) return {}; return builder.create(loc, lhs, rhs); } case BinaryOperator::LogicalImplication: { // `(lhs -> rhs)` equivalent to `(!lhs || rhs)`. - lhs = convertToBool(lhs, domain); + lhs = context.convertToBool(lhs, domain); if (!lhs) return {}; - rhs = convertToBool(rhs, domain); + rhs = context.convertToBool(rhs, domain); if (!rhs) return {}; auto notLHS = builder.create(loc, lhs); @@ -390,10 +368,10 @@ struct RvalueExprVisitor { } case BinaryOperator::LogicalEquivalence: { // `(lhs <-> rhs)` equivalent to `(lhs && rhs) || (!lhs && !rhs)`. - lhs = convertToBool(lhs, domain); + lhs = context.convertToBool(lhs, domain); if (!lhs) return {}; - rhs = convertToBool(rhs, domain); + rhs = context.convertToBool(rhs, domain); if (!rhs) return {}; auto notLHS = builder.create(loc, lhs); @@ -671,7 +649,8 @@ struct RvalueExprVisitor { mlir::emitError(loc) << "unsupported conditional expression with pattern"; return {}; } - auto value = convertToBool(context.convertRvalueExpression(*cond.expr)); + auto value = + context.convertToBool(context.convertRvalueExpression(*cond.expr)); if (!value) return {}; auto conditionalOp = builder.create(loc, type, value); @@ -1044,3 +1023,29 @@ Value Context::convertLvalueExpression(const slang::ast::Expression &expr) { return expr.visit(LvalueExprVisitor(*this, loc)); } // NOLINTEND(misc-no-recursion) + +/// Helper function to convert a value to its "truthy" boolean value. +Value Context::convertToBool(Value value) { + if (!value) + return {}; + if (auto type = dyn_cast_or_null(value.getType())) + if (type.getBitSize() == 1) + return value; + if (auto type = dyn_cast_or_null(value.getType())) + return builder.create(value.getLoc(), value); + mlir::emitError(value.getLoc(), "expression of type ") + << value.getType() << " cannot be cast to a boolean"; + return {}; +} + +/// Helper function to convert a value to its "truthy" boolean value and +/// convert it to the given domain. +Value Context::convertToBool(Value value, Domain domain) { + value = convertToBool(value); + if (!value) + return {}; + auto type = moore::IntType::get(getContext(), 1, domain); + if (value.getType() == type) + return value; + return builder.create(value.getLoc(), type, value); +} diff --git a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h index e490b48b03c0..536b5ea84bb7 100644 --- a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h +++ b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h @@ -26,6 +26,8 @@ namespace circt { namespace ImportVerilog { +using moore::Domain; + /// Port lowering information. struct PortLowering { const slang::ast::PortSymbol * @@ -102,8 +104,15 @@ struct Context { Value convertLvalueExpression(const slang::ast::Expression &expr); // Convert a slang timing control into an MLIR timing control. - LogicalResult - convertTimingControl(const slang::ast::TimingControl &timingControl); + LogicalResult convertTimingControl(const slang::ast::TimingControl &ctrl, + const slang::ast::Statement &stmt); + + /// Helper function to convert a value to its "truthy" boolean value. + Value convertToBool(Value value); + + /// Helper function to convert a value to its "truthy" boolean value and + /// convert it to the given domain. + Value convertToBool(Value value, Domain domain); slang::ast::Compilation &compilation; mlir::ModuleOp intoModuleOp; @@ -152,6 +161,12 @@ struct Context { /// part of the loop body statements will use this information to branch to /// the correct block. SmallVector loopStack; + + /// A listener called for every variable or net being read. This can be used + /// to collect all variables read as part of an expression or statement, for + /// example to populate the list of observed signals in an implicit event + /// control `@*`. + std::function rvalueReadCallback; }; } // namespace ImportVerilog diff --git a/lib/Conversion/ImportVerilog/Statements.cpp b/lib/Conversion/ImportVerilog/Statements.cpp index f3fa376012cf..6e674fdeaa4f 100644 --- a/lib/Conversion/ImportVerilog/Statements.cpp +++ b/lib/Conversion/ImportVerilog/Statements.cpp @@ -427,11 +427,7 @@ struct StmtVisitor { // Handle timing control. LogicalResult visit(const slang::ast::TimedStatement &stmt) { - if (failed(context.convertTimingControl(stmt.timing))) - return failure(); - if (failed(context.convertStatement(stmt.stmt))) - return failure(); - return success(); + return context.convertTimingControl(stmt.timing, stmt.stmt); } // Handle return statements. diff --git a/lib/Conversion/ImportVerilog/TimingControls.cpp b/lib/Conversion/ImportVerilog/TimingControls.cpp index 962f8db5e0ee..cf9535e3b975 100644 --- a/lib/Conversion/ImportVerilog/TimingControls.cpp +++ b/lib/Conversion/ImportVerilog/TimingControls.cpp @@ -8,57 +8,197 @@ #include "ImportVerilogInternals.h" #include "slang/ast/TimingControl.h" +#include "llvm/ADT/ScopeExit.h" + using namespace circt; using namespace ImportVerilog; + +static moore::Edge convertEdgeKind(const slang::ast::EdgeKind edge) { + using slang::ast::EdgeKind; + switch (edge) { + case EdgeKind::None: + return moore::Edge::AnyChange; + case EdgeKind::PosEdge: + return moore::Edge::PosEdge; + case EdgeKind::NegEdge: + return moore::Edge::NegEdge; + case EdgeKind::BothEdges: + return moore::Edge::BothEdges; + } + llvm_unreachable("all edge kinds handled"); +} + +// NOLINTBEGIN(misc-no-recursion) namespace { -struct TimingCtrlVisitor { + +// Handle any of the event control constructs. +struct EventControlVisitor { Context &context; Location loc; OpBuilder &builder; - TimingCtrlVisitor(Context &context, Location loc) - : context(context), loc(loc), builder(context.builder) {} - + // Handle single signal events like `posedge x`, `negedge y iff z`, or `w`. LogicalResult visit(const slang::ast::SignalEventControl &ctrl) { - // TODO: When updating slang to the latest version, we will handle - // "iffCondition". - auto loc = context.convertLocation(ctrl.sourceRange.start()); - auto input = context.convertRvalueExpression(ctrl.expr); - builder.create(loc, static_cast(ctrl.edge), - input); - return success(); - } - - LogicalResult visit(const slang::ast::ImplicitEventControl &ctrl) { + auto edge = convertEdgeKind(ctrl.edge); + auto expr = context.convertRvalueExpression(ctrl.expr); + if (!expr) + return failure(); + Value condition; + if (ctrl.iffCondition) { + condition = context.convertRvalueExpression(*ctrl.iffCondition); + condition = context.convertToBool(condition, Domain::TwoValued); + if (!condition) + return failure(); + } + builder.create(loc, edge, expr, condition); return success(); } + // Handle a list of signal events. LogicalResult visit(const slang::ast::EventListControl &ctrl) { - for (auto *event : ctrl.as().events) { - if (failed(context.convertTimingControl(*event))) + for (const auto *event : ctrl.events) { + auto visitor = *this; + visitor.loc = context.convertLocation(event->sourceRange); + if (failed(event->visit(visitor))) return failure(); } return success(); } - /// Emit an error for all other timing controls. + // Emit an error for all other timing controls. template - LogicalResult visit(T &&node) { - mlir::emitError(loc, "unspported timing control: ") - << slang::ast::toString(node.kind); - return failure(); + LogicalResult visit(T &&ctrl) { + return mlir::emitError(loc) + << "unsupported event control: " << slang::ast::toString(ctrl.kind); } +}; - LogicalResult visitInvalid(const slang::ast::TimingControl &ctrl) { - mlir::emitError(loc, "invalid timing control"); - return failure(); +// Handle any of the delay control constructs. +struct DelayControlVisitor { + Context &context; + Location loc; + OpBuilder &builder; + + // Emit an error for all other timing controls. + template + LogicalResult visit(T &&ctrl) { + return mlir::emitError(loc) + << "unsupported delay control: " << slang::ast::toString(ctrl.kind); } }; + } // namespace +// Entry point to timing control handling. This deals with the layer of repeats +// that a timing control may be wrapped in, and also handles the implicit event +// control which may appear at that point. For any event control a `WaitEventOp` +// will be created and populated by `handleEventControl`. Any delay control will +// be handled by `handleDelayControl`. +static LogicalResult handleRoot(Context &context, + const slang::ast::TimingControl &ctrl, + moore::WaitEventOp &implicitWaitOp) { + auto &builder = context.builder; + auto loc = context.convertLocation(ctrl.sourceRange); + + using slang::ast::TimingControlKind; + switch (ctrl.kind) { + // TODO: Actually implement a lowering for repeated event control. The main + // way to trigger this is through an intra-assignment timing control, which + // is not yet supported: + // + // a = repeat(3) @(posedge b) c; + // + // This will want to recursively call this function at the right insertion + // point to handle the timing control being repeated. + case TimingControlKind::RepeatedEvent: + return mlir::emitError(loc) << "unsupported repeated event control"; + + // Handle implicit events, i.e. `@*` and `@(*)`. This implicitly includes + // all variables read within the statement that follows after the event + // control. Since we haven't converted that statement yet, simply create and + // empty wait op and let `Context::convertTimingControl` populate it once + // the statement has been lowered. + case TimingControlKind::ImplicitEvent: + implicitWaitOp = builder.create(loc); + return success(); + + // Handle event control. + case TimingControlKind::SignalEvent: + case TimingControlKind::EventList: { + auto waitOp = builder.create(loc); + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&waitOp.getBody().emplaceBlock()); + EventControlVisitor visitor{context, loc, builder}; + return ctrl.visit(visitor); + } + + // Handle delay control. + case TimingControlKind::Delay: + case TimingControlKind::Delay3: + case TimingControlKind::OneStepDelay: + case TimingControlKind::CycleDelay: { + DelayControlVisitor visitor{context, loc, builder}; + return ctrl.visit(visitor); + } + + default: + return mlir::emitError(loc, "unsupported timing control: ") + << slang::ast::toString(ctrl.kind); + } +} + LogicalResult -Context::convertTimingControl(const slang::ast::TimingControl &timingControl) { - auto loc = convertLocation(timingControl.sourceRange.start()); - TimingCtrlVisitor visitor{*this, loc}; - return timingControl.visit(visitor); +Context::convertTimingControl(const slang::ast::TimingControl &ctrl, + const slang::ast::Statement &stmt) { + // Convert the timing control. Implicit event control will create a new empty + // `WaitEventOp` and assign it to `implicitWaitOp`. This op will be populated + // further down. + moore::WaitEventOp implicitWaitOp; + { + auto previousCallback = rvalueReadCallback; + auto done = + llvm::make_scope_exit([&] { rvalueReadCallback = previousCallback; }); + // Reads happening as part of the event control should not be added to a + // surrounding implicit event control's list of implicitly observed + // variables. + rvalueReadCallback = nullptr; + if (failed(handleRoot(*this, ctrl, implicitWaitOp))) + return failure(); + } + + // Convert the statement. In case `implicitWaitOp` is set, we register a + // callback to collect all the variables read by the statement into + // `readValues`, such that we can populate the op with implicitly observed + // variables afterwards. + llvm::SmallSetVector readValues; + { + auto previousCallback = rvalueReadCallback; + auto done = + llvm::make_scope_exit([&] { rvalueReadCallback = previousCallback; }); + if (implicitWaitOp) { + rvalueReadCallback = [&](moore::ReadOp readOp) { + readValues.insert(readOp.getInput()); + if (previousCallback) + previousCallback(readOp); + }; + } + if (failed(convertStatement(stmt))) + return failure(); + } + + // Populate the implicit wait op with reads from the variables read by the + // statement. + if (implicitWaitOp) { + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&implicitWaitOp.getBody().emplaceBlock()); + for (auto readValue : readValues) { + auto value = + builder.create(implicitWaitOp.getLoc(), readValue); + builder.create( + implicitWaitOp.getLoc(), moore::Edge::AnyChange, value, Value{}); + } + } + + return success(); } +// NOLINTEND(misc-no-recursion) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index e55d85332753..0913f42afc73 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -19,9 +19,11 @@ #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/BuiltinDialect.h" +#include "mlir/IR/Iterators.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/DialectConversion.h" +#include "mlir/Transforms/RegionUtils.h" #include "llvm/ADT/TypeSwitch.h" namespace circt { @@ -34,6 +36,7 @@ using namespace circt; using namespace moore; using comb::ICmpPredicate; +using llvm::SmallDenseSet; namespace { @@ -163,148 +166,286 @@ struct InstanceOpConversion : public OpConversionPattern { } }; +static void getValuesToObserve(Region *region, + function_ref setInsertionPoint, + const TypeConverter *typeConverter, + ConversionPatternRewriter &rewriter, + SmallVector &observeValues) { + SmallDenseSet alreadyObserved; + Location loc = region->getLoc(); + + auto probeIfSignal = [&](Value value) -> Value { + if (!isa(value.getType())) + return value; + return rewriter.create(loc, value); + }; + + region->getParentOp()->walk>( + [&](Operation *operation) { + for (auto value : operation->getOperands()) { + if (region->isAncestor(value.getParentRegion())) + continue; + if (!alreadyObserved.insert(value).second) + continue; + + OpBuilder::InsertionGuard g(rewriter); + if (auto remapped = rewriter.getRemappedValue(value)) { + setInsertionPoint(remapped); + observeValues.push_back(probeIfSignal(remapped)); + } else { + setInsertionPoint(value); + auto type = typeConverter->convertType(value.getType()); + auto converted = typeConverter->materializeTargetConversion( + rewriter, loc, type, value); + observeValues.push_back(probeIfSignal(converted)); + } + } + }); +} + struct ProcedureOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult matchAndRewrite(ProcedureOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - Location loc = op.getLoc(); - auto procOp = rewriter.create(loc); + // Collect values to observe before we do any modifications to the region. + SmallVector observedValues; + if (op.getKind() == ProcedureKind::AlwaysComb || + op.getKind() == ProcedureKind::AlwaysLatch) { + auto setInsertionPoint = [&](Value value) { + rewriter.setInsertionPoint(op); + }; + getValuesToObserve(&op.getBody(), setInsertionPoint, typeConverter, + rewriter, observedValues); + } - // TODO: properly handle the procedure kind attribute - if (op.getKind() != ProcedureKind::Always) - return rewriter.notifyMatchFailure(loc, "not yet supported"); + auto loc = op.getLoc(); + if (failed(rewriter.convertRegionTypes(&op.getBody(), *typeConverter))) + return failure(); - // Collect all event ops in the procedure. - SmallVector events(op.getOps()); + // Handle initial and final procedures. These lower to a corresponding + // `llhd.process` or `llhd.final` op that executes the body and then halts. + if (op.getKind() == ProcedureKind::Initial || + op.getKind() == ProcedureKind::Final) { + Operation *newOp; + if (op.getKind() == ProcedureKind::Initial) + newOp = rewriter.create(loc); + else + newOp = rewriter.create(loc); + auto &body = newOp->getRegion(0); + rewriter.inlineRegionBefore(op.getBody(), body, body.end()); + for (auto returnOp : + llvm::make_early_inc_range(body.getOps())) { + rewriter.setInsertionPoint(returnOp); + rewriter.replaceOpWithNewOp(returnOp); + } + rewriter.eraseOp(op); + return success(); + } - auto *entry = rewriter.createBlock(&procOp.getBody()); - auto *wait = rewriter.createBlock(&procOp.getBody()); - auto *check = rewriter.createBlock(&procOp.getBody()); + // All other procedures lower to a an `llhd.process`. + auto newOp = rewriter.create(loc); // We need to add an empty entry block because it is not allowed in MLIR to // branch back to the entry block. Instead we put the logic in the second // block and branch to that. - rewriter.setInsertionPointToStart(entry); - rewriter.create(loc, wait); - - // The block in which we can sample the past and where the wait terminator - // resides. - rewriter.setInsertionPointToStart(wait); - - auto getSignal = [&](Value input) -> Value { - // If the read op input is defined outside and before the procedure - // operation, we can get the remapped value directly. - Value signal = rewriter.getRemappedValue(input); - - // Otherwise, it hasn't been converted yet, so we take the old one and - // insert a cast. - if (!signal) { - Type convertedType = typeConverter->convertType(input.getType()); - assert(convertedType && - "if the input has not been converted yet, it should have a " - "moore type and a valid type conversion"); - signal = - rewriter - .create(loc, convertedType, input) - ->getResult(0); - } - - return signal; - }; + rewriter.createBlock(&newOp.getBody()); + auto *block = &op.getBody().front(); + rewriter.create(loc, block); + rewriter.inlineRegionBefore(op.getBody(), newOp.getBody(), + newOp.getBody().end()); + + // Add special handling for `always_comb` and `always_latch` procedures. + // These run once at simulation startup and then implicitly wait for any of + // the values they access to change before running again. To implement this, + // we create another basic block that contains the implicit wait, and make + // all `moore.return` ops branch to that wait block instead of immediately + // jumping back up to the body. + if (op.getKind() == ProcedureKind::AlwaysComb || + op.getKind() == ProcedureKind::AlwaysLatch) { + Block *waitBlock = rewriter.createBlock(&newOp.getBody()); + rewriter.create(loc, observedValues, Value(), ValueRange{}, + block); + block = waitBlock; + } - // All signals to observe in the `llhd.wait` operation. - SmallVector toObserve; - DenseSet alreadyObserved; - // If there are no event operations in the procedure, it's a combinational - // one. Thus we need to collect all signals used. - if (events.empty()) { - op->walk([&](Operation *operation) { - for (auto &operand : operation->getOpOperands()) { - Value value = getSignal(operand.get()); - auto memOp = dyn_cast(operation); - if (!memOp) - return; - - // The process is only sensitive to values that are read. - if (isa(operand.get().getType()) && - memOp.getEffectOnValue(operand.get()) - .has_value()) { - if (!alreadyObserved.contains(value)) - toObserve.push_back(value); - - alreadyObserved.insert(value); - } - } - }); + // Make all `moore.return` ops branch back up to the beginning of the + // process, or the wait block created above for `always_comb` and + // `always_latch` procedures. + for (auto returnOp : llvm::make_early_inc_range(newOp.getOps())) { + rewriter.setInsertionPoint(returnOp); + rewriter.create(loc, block); + rewriter.eraseOp(returnOp); } - // Forall edge triggered events, probe the old value - SmallVector oldValues(events.size(), Value()); - for (auto [i, event] : llvm::enumerate(events)) { - auto readOp = event.getInput().getDefiningOp(); - if (!readOp) - return failure(); + rewriter.eraseOp(op); + return success(); + } +}; - Value signal = getSignal(readOp.getInput()); - toObserve.push_back(signal); +struct WaitEventOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; - // Non-edge triggered events only need the value in the present - if (event.getEdge() != Edge::None) - oldValues[i] = rewriter.create(loc, signal); + LogicalResult + matchAndRewrite(WaitEventOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // In order to convert the `wait_event` op we need to create three separate + // blocks at the location of the op: + // + // - A "wait" block that reads the current state of any values used to + // detect events and then waits until any of those values change. When a + // change occurs, control transfers to the "check" block. + // - A "check" block which is executed after any interesting signal has + // changed. This is where any `detect_event` ops read the current state of + // interesting values and compare them against their state before the wait + // in order to detect an event. If any events were detected, control + // transfers to the "resume" block; otherwise control goes back to the + // "wait" block. + // - A "resume" block which holds any ops after the `wait_event` op. This is + // where control is expected to resume after an event has happened. + // + // Block structure before: + // opA + // moore.wait_event { ... } + // opB + // + // Block structure after: + // opA + // cf.br ^wait + // ^wait: + // + // llhd.wait ^check, ... + // ^check: + // + // + // cf.cond_br %event, ^resume, ^wait + // ^resume: + // opB + auto *resumeBlock = + rewriter.splitBlock(op->getBlock(), ++Block::iterator(op)); + auto *waitBlock = rewriter.createBlock(resumeBlock); + auto *checkBlock = rewriter.createBlock(resumeBlock); + + auto loc = op.getLoc(); + rewriter.setInsertionPoint(op); + rewriter.create(loc, waitBlock); + + // We need to inline two copies of the `wait_event`'s body region: one is + // used to determine the values going into `detect_event` ops before the + // `llhd.wait`, and one will do the actual event detection after the + // `llhd.wait`. + // + // Create a copy of the entire `wait_event` op in the wait block, which also + // creates a copy of its region. Take note of all inputs to `detect_event` + // ops and delete the `detect_event` ops in this copy. + SmallVector valuesBefore; + rewriter.setInsertionPointToEnd(waitBlock); + auto clonedOp = cast(rewriter.clone(*op)); + for (auto detectOp : + llvm::make_early_inc_range(clonedOp.getOps())) { + valuesBefore.push_back(detectOp.getInput()); + rewriter.eraseOp(detectOp); } - rewriter.create(loc, toObserve, Value(), ValueRange{}, check); - rewriter.setInsertionPointToStart(check); + // Determine the values used during event detection that are defined outside + // the `wait_event`'s body region. We want to wait for a change on these + // signals before we check if any interesting event happened. + SmallVector observeValues; + auto setInsertionPointAfterDef = [&](Value value) { + if (auto *op = value.getDefiningOp()) + rewriter.setInsertionPointAfter(op); + if (auto arg = dyn_cast(value)) + rewriter.setInsertionPointToStart(value.getParentBlock()); + }; + + getValuesToObserve(&clonedOp.getBody(), setInsertionPointAfterDef, + typeConverter, rewriter, observeValues); + + // Create the `llhd.wait` op that suspends the current process and waits for + // a change in the interesting values listed in `observeValues`. When a + // change is detected, execution resumes in the "check" block. + auto waitOp = rewriter.create(loc, observeValues, Value(), + ValueRange{}, checkBlock); + rewriter.inlineBlockBefore(&clonedOp.getBody().front(), waitOp); + rewriter.eraseOp(clonedOp); + + // Collect a list of all detect ops and inline the `wait_event` body into + // the check block. + SmallVector detectOps(op.getBody().getOps()); + rewriter.inlineBlockBefore(&op.getBody().front(), checkBlock, + checkBlock->end()); + rewriter.eraseOp(op); + + // Helper function to detect if a certain change occurred between a value + // before the `llhd.wait` and after. + auto computeTrigger = [&](Value before, Value after, Edge edge) -> Value { + before = typeConverter->materializeTargetConversion( + rewriter, loc, rewriter.getI1Type(), before); + after = typeConverter->materializeTargetConversion( + rewriter, loc, rewriter.getI1Type(), after); + + if (edge == Edge::AnyChange) + return rewriter.create(loc, ICmpPredicate::ne, before, + after, true); - if (events.empty()) { - rewriter.create(loc, &op.getBody().front()); - } else { SmallVector disjuncts; - for (auto [i, signal, event] : llvm::enumerate(toObserve, events)) { - if (event.getEdge() == Edge::None) - disjuncts.push_back(rewriter.create(loc, signal)); - - if (event.getEdge() == Edge::PosEdge || - event.getEdge() == Edge::BothEdges) { - Value currVal = rewriter.create(loc, signal); - Value trueVal = rewriter.create(loc, APInt(1, 1)); - Value notOldVal = - rewriter.create(loc, oldValues[i], trueVal); - Value posedge = rewriter.create(loc, notOldVal, currVal); - disjuncts.push_back(posedge); - } - if (event.getEdge() == Edge::NegEdge || - event.getEdge() == Edge::BothEdges) { - Value currVal = rewriter.create(loc, signal); - Value trueVal = rewriter.create(loc, APInt(1, 1)); - Value notCurrVal = - rewriter.create(loc, currVal, trueVal); - Value posedge = - rewriter.create(loc, oldValues[i], notCurrVal); - disjuncts.push_back(posedge); - } + Value trueVal = rewriter.create(loc, APInt(1, 1)); + + if (edge == Edge::PosEdge || edge == Edge::BothEdges) { + Value notOldVal = + rewriter.create(loc, before, trueVal, true); + Value posedge = + rewriter.create(loc, notOldVal, after, true); + disjuncts.push_back(posedge); } - Value isValid = rewriter.create(loc, disjuncts, false); - rewriter.create(loc, isValid, &op.getBody().front(), - wait); - } + if (edge == Edge::NegEdge || edge == Edge::BothEdges) { + Value notCurrVal = + rewriter.create(loc, after, trueVal, true); + Value posedge = + rewriter.create(loc, before, notCurrVal, true); + disjuncts.push_back(posedge); + } - for (auto event : events) - rewriter.eraseOp(event); + return rewriter.createOrFold(loc, disjuncts, true); + }; - rewriter.inlineRegionBefore(op.getBody(), procOp.getBody(), - procOp.getBody().end()); + // Convert all `detect_event` ops into a check for the corresponding event + // between the value before and after the `llhd.wait`. The "before" value + // has been collected into `valuesBefore` in the "wait" block; the "after" + // value corresponds to the detect op's input. + SmallVector triggers; + for (auto [detectOp, before] : llvm::zip(detectOps, valuesBefore)) { + // TODO: Support multi-bit values. Edge detection occurs per-bit. + if (auto intType = dyn_cast(before.getType()); + !intType || intType.getWidth() != 1) + return detectOp->emitError() << "requires single bit operand"; + + rewriter.setInsertionPoint(detectOp); + auto trigger = + computeTrigger(before, detectOp.getInput(), detectOp.getEdge()); + if (detectOp.getCondition()) { + auto condition = typeConverter->materializeTargetConversion( + rewriter, loc, rewriter.getI1Type(), detectOp.getCondition()); + trigger = rewriter.create(loc, trigger, condition, true); + } + triggers.push_back(trigger); + rewriter.eraseOp(detectOp); + } - for (auto returnOp : procOp.getOps()) { - rewriter.setInsertionPoint(returnOp); - rewriter.create(loc, wait); - rewriter.eraseOp(returnOp); + // If any `detect_event` op detected an event, branch to the "resume" block + // which contains any code after the `wait_event` op. If no events were + // detected, branch back to the "wait" block to wait for the next change on + // the interesting signals. + rewriter.setInsertionPointToEnd(checkBlock); + if (!triggers.empty()) { + auto triggered = rewriter.createOrFold(loc, triggers, true); + rewriter.create(loc, triggered, resumeBlock, waitBlock); + } else { + rewriter.create(loc, waitBlock); } - rewriter.eraseOp(op); return success(); } }; @@ -337,8 +478,8 @@ struct VariableOpConversion : public OpConversionPattern { init = rewriter.createOrFold(loc, elementType, constZero); } - rewriter.replaceOpWithNewOp(op, resultType, op.getNameAttr(), - init); + rewriter.replaceOpWithNewOp(op, resultType, + op.getNameAttr(), init); return success(); } }; @@ -1133,9 +1274,11 @@ static void populateTypeConversion(TypeConverter &typeConverter) { [&](mlir::OpBuilder &builder, mlir::Type resultType, mlir::ValueRange inputs, mlir::Location loc) -> std::optional { - if (inputs.size() != 1) + if (inputs.size() != 1 || !inputs[0]) return std::nullopt; - return inputs[0]; + return builder + .create(loc, resultType, inputs[0]) + .getResult(0); }); typeConverter.addSourceMaterialization( @@ -1199,7 +1342,7 @@ static void populateOpConversion(RewritePatternSet &patterns, CaseXZEqOpConversion, // Patterns of structural operations. - SVModuleOpConversion, InstanceOpConversion, ProcedureOpConversion, + SVModuleOpConversion, InstanceOpConversion, ProcedureOpConversion, WaitEventOpConversion, // Patterns of shifting operations. ShrOpConversion, ShlOpConversion, AShrOpConversion, @@ -1246,6 +1389,9 @@ void MooreToCorePass::runOnOperation() { MLIRContext &context = getContext(); ModuleOp module = getOperation(); + IRRewriter rewriter(module); + (void)mlir::eraseUnreachableBlocks(rewriter, module->getRegions()); + ConversionTarget target(context); TypeConverter typeConverter; RewritePatternSet patterns(&context); diff --git a/lib/Dialect/Arc/ArcOps.cpp b/lib/Dialect/Arc/ArcOps.cpp index 87070ed8c398..76e457b8d77d 100644 --- a/lib/Dialect/Arc/ArcOps.cpp +++ b/lib/Dialect/Arc/ArcOps.cpp @@ -8,6 +8,7 @@ #include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/HW/HWOpInterfaces.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/Builders.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/PatternMatch.h" @@ -307,6 +308,30 @@ LogicalResult ModelOp::verify() { return success(); } +LogicalResult ModelOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + if (!getInitialFn().has_value()) + return success(); + + auto referencedOp = + symbolTable.lookupNearestSymbolFrom(*this, getInitialFnAttr()); + if (!referencedOp) + return emitError("Cannot find declaration of initializer function '") + << *getInitialFn() << "'."; + auto funcOp = dyn_cast(referencedOp); + if (!funcOp) { + auto diag = emitError("Referenced initializer must be a 'func.func' op."); + diag.attachNote(referencedOp->getLoc()) << "Initializer declared here:"; + return diag; + } + if (!llvm::equal(funcOp.getArgumentTypes(), getBody().getArgumentTypes())) { + auto diag = emitError("Arguments of initializer function must match " + "arguments of model body."); + diag.attachNote(referencedOp->getLoc()) << "Initializer declared here:"; + return diag; + } + return success(); +} + //===----------------------------------------------------------------------===// // LutOp //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/Arc/CMakeLists.txt b/lib/Dialect/Arc/CMakeLists.txt index e6ed7c9c4533..c1703987ed40 100644 --- a/lib/Dialect/Arc/CMakeLists.txt +++ b/lib/Dialect/Arc/CMakeLists.txt @@ -32,6 +32,7 @@ add_circt_dialect_library(CIRCTArc CIRCTSeq MLIRIR MLIRInferTypeOpInterface + MLIRFuncDialect MLIRSideEffectInterfaces MLIRFuncDialect ) diff --git a/lib/Dialect/Arc/ModelInfo.cpp b/lib/Dialect/Arc/ModelInfo.cpp index a16dc0b68e24..91e0449df396 100644 --- a/lib/Dialect/Arc/ModelInfo.cpp +++ b/lib/Dialect/Arc/ModelInfo.cpp @@ -105,6 +105,7 @@ LogicalResult circt::arc::collectStates(Value storage, unsigned offset, LogicalResult circt::arc::collectModels(mlir::ModuleOp module, SmallVector &models) { + for (auto modelOp : module.getOps()) { auto storageArg = modelOp.getBody().getArgument(0); auto storageType = cast(storageArg.getType()); @@ -115,7 +116,7 @@ LogicalResult circt::arc::collectModels(mlir::ModuleOp module, llvm::sort(states, [](auto &a, auto &b) { return a.offset < b.offset; }); models.emplace_back(std::string(modelOp.getName()), storageType.getSize(), - std::move(states)); + std::move(states), modelOp.getInitialFnAttr()); } return success(); @@ -130,6 +131,9 @@ void circt::arc::serializeModelInfoToJson(llvm::raw_ostream &outputStream, json.object([&] { json.attribute("name", model.name); json.attribute("numStateBytes", model.numStateBytes); + json.attribute("initialFnSym", !model.initialFnSym + ? "" + : model.initialFnSym.getValue()); json.attributeArray("states", [&] { for (const auto &state : model.states) { json.object([&] { diff --git a/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp b/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp index 486235d0bf5b..b68abef62513 100644 --- a/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp +++ b/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp @@ -30,6 +30,8 @@ using namespace arc; /// Check if an operation partakes in state accesses. static bool isOpInteresting(Operation *op) { + if (isa(op)) + return false; if (isa(op)) return true; if (op->getNumRegions() > 0) diff --git a/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp b/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp index 0ef473e428cf..c76467e80f38 100644 --- a/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp +++ b/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp @@ -11,6 +11,7 @@ #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/Pass/Pass.h" +#include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/Debug.h" #define DEBUG_TYPE "arc-lower-clocks-to-funcs" @@ -50,6 +51,9 @@ struct LowerClocksToFuncsPass Statistic numOpsCopied{this, "ops-copied", "Ops copied into clock trees"}; Statistic numOpsMoved{this, "ops-moved", "Ops moved into clock trees"}; + +private: + bool hasPassthroughOp; }; } // namespace @@ -65,11 +69,38 @@ LogicalResult LowerClocksToFuncsPass::lowerModel(ModelOp modelOp) { << "`\n"); // Find the clocks to extract. + SmallVector initialOps; + SmallVector passthroughOps; SmallVector clocks; modelOp.walk([&](Operation *op) { - if (isa(op)) - clocks.push_back(op); + TypeSwitch(op) + .Case([&](auto) { clocks.push_back(op); }) + .Case([&](auto initOp) { + initialOps.push_back(initOp); + clocks.push_back(initOp); + }) + .Case([&](auto ptOp) { + passthroughOps.push_back(ptOp); + clocks.push_back(ptOp); + }); }); + hasPassthroughOp = !passthroughOps.empty(); + + // Sanity check + if (passthroughOps.size() > 1) { + auto diag = modelOp.emitOpError() + << "containing multiple PassThroughOps cannot be lowered."; + for (auto ptOp : passthroughOps) + diag.attachNote(ptOp.getLoc()) << "Conflicting PassThroughOp:"; + } + if (initialOps.size() > 1) { + auto diag = modelOp.emitOpError() + << "containing multiple InitialOps is currently unsupported."; + for (auto initOp : initialOps) + diag.attachNote(initOp.getLoc()) << "Conflicting InitialOp:"; + } + if (passthroughOps.size() > 1 || initialOps.size() > 1) + return failure(); // Perform the actual extraction. OpBuilder funcBuilder(modelOp); @@ -84,7 +115,7 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, Value modelStorageArg, OpBuilder &funcBuilder) { LLVM_DEBUG(llvm::dbgs() << "- Lowering clock " << clockOp->getName() << "\n"); - assert((isa(clockOp))); + assert((isa(clockOp))); // Add a `StorageType` block argument to the clock's body block which we are // going to use to pass the storage pointer to the clock once it has been @@ -103,8 +134,16 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, // Pick a name for the clock function. SmallString<32> funcName; - funcName.append(clockOp->getParentOfType().getName()); - funcName.append(isa(clockOp) ? "_passthrough" : "_clock"); + auto modelOp = clockOp->getParentOfType(); + funcName.append(modelOp.getName()); + + if (isa(clockOp)) + funcName.append("_passthrough"); + else if (isa(clockOp)) + funcName.append("_initial"); + else + funcName.append("_clock"); + auto funcOp = funcBuilder.create( clockOp->getLoc(), funcName, builder.getFunctionType({modelStorageArg.getType()}, {})); @@ -114,21 +153,41 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, // Create a call to the function within the model. builder.setInsertionPoint(clockOp); - if (auto treeOp = dyn_cast(clockOp)) { - auto ifOp = - builder.create(clockOp->getLoc(), treeOp.getClock(), false); - auto builder = ifOp.getThenBodyBuilder(); - builder.create(clockOp->getLoc(), funcOp, - ValueRange{modelStorageArg}); - } else { - builder.create(clockOp->getLoc(), funcOp, - ValueRange{modelStorageArg}); - } + TypeSwitch(clockOp) + .Case([&](auto treeOp) { + auto ifOp = builder.create(clockOp->getLoc(), + treeOp.getClock(), false); + auto builder = ifOp.getThenBodyBuilder(); + builder.template create(clockOp->getLoc(), funcOp, + ValueRange{modelStorageArg}); + }) + .Case([&](auto) { + builder.template create(clockOp->getLoc(), funcOp, + ValueRange{modelStorageArg}); + }) + .Case([&](auto) { + if (modelOp.getInitialFn().has_value()) + modelOp.emitWarning() << "Existing model initializer '" + << modelOp.getInitialFnAttr().getValue() + << "' will be overridden."; + modelOp.setInitialFnAttr( + FlatSymbolRefAttr::get(funcOp.getSymNameAttr())); + }); // Move the clock's body block to the function and remove the old clock op. funcOp.getBody().takeBody(clockRegion); - clockOp->erase(); + if (isa(clockOp) && hasPassthroughOp) { + // Call PassThroughOp after init + builder.setInsertionPoint(funcOp.getBlocks().front().getTerminator()); + funcName.clear(); + funcName.append(modelOp.getName()); + funcName.append("_passthrough"); + builder.create(clockOp->getLoc(), funcName, TypeRange{}, + ValueRange{funcOp.getBody().getArgument(0)}); + } + + clockOp->erase(); return success(); } diff --git a/lib/Dialect/Arc/Transforms/LowerState.cpp b/lib/Dialect/Arc/Transforms/LowerState.cpp index 7f5025b8e457..d659e8870397 100644 --- a/lib/Dialect/Arc/Transforms/LowerState.cpp +++ b/lib/Dialect/Arc/Transforms/LowerState.cpp @@ -8,6 +8,7 @@ #include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/Arc/ArcPasses.h" +#include "circt/Dialect/Comb/CombDialect.h" #include "circt/Dialect/Comb/CombOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Seq/SeqOps.h" @@ -63,7 +64,7 @@ struct Statistics { struct ClockLowering { /// The root clock this lowering is for. Value clock; - /// A `ClockTreeOp` or `PassThroughOp`. + /// A `ClockTreeOp` or `PassThroughOp` or `InitialOp`. Operation *treeOp; /// Pass statistics. Statistics &stats; @@ -76,15 +77,21 @@ struct ClockLowering { /// A cache of OR gates created for aggregating enable conditions. DenseMap, Value> orCache; + // Prevent accidental construction and copying + ClockLowering() = delete; + ClockLowering(const ClockLowering &other) = delete; + ClockLowering(Value clock, Operation *treeOp, Statistics &stats) : clock(clock), treeOp(treeOp), stats(stats), builder(treeOp) { - assert((isa(treeOp))); + assert((isa(treeOp))); builder.setInsertionPointToStart(&treeOp->getRegion(0).front()); } Value materializeValue(Value value); Value getOrCreateAnd(Value lhs, Value rhs, Location loc); Value getOrCreateOr(Value lhs, Value rhs, Location loc); + + bool isInitialTree() const { return isa(treeOp); } }; struct GatedClockLowering { @@ -102,6 +109,7 @@ struct ModuleLowering { MLIRContext *context; DenseMap> clockLowerings; DenseMap gatedClockLowerings; + std::unique_ptr initialLowering; Value storageArg; OpBuilder clockBuilder; OpBuilder stateBuilder; @@ -112,6 +120,7 @@ struct ModuleLowering { GatedClockLowering getOrCreateClockLowering(Value clock); ClockLowering &getOrCreatePassThrough(); + ClockLowering &getInitial(); Value replaceValueWithStateRead(Value value, Value state); void addStorageArg(); @@ -121,7 +130,8 @@ struct ModuleLowering { template LogicalResult lowerStateLike(Operation *op, Value clock, Value enable, Value reset, ArrayRef inputs, - FlatSymbolRefAttr callee); + FlatSymbolRefAttr callee, + ArrayRef initialValues = {}); LogicalResult lowerState(StateOp stateOp); LogicalResult lowerState(sim::DPICallOp dpiCallOp); LogicalResult lowerState(MemoryOp memOp); @@ -159,6 +169,17 @@ static bool shouldMaterialize(Value value) { return shouldMaterialize(op); } +static bool canBeMaterializedInInitializer(Operation *op) { + if (!op) + return false; + if (op->hasTrait()) + return true; + if (isa(op->getDialect())) + return true; + // TODO: There are some other ops we probably want to allow + return false; +} + /// Materialize a value within this clock tree. This clones or moves all /// operations required to produce this value inside the clock tree. Value ClockLowering::materializeValue(Value value) { @@ -206,6 +227,10 @@ Value ClockLowering::materializeValue(Value value) { while (!worklist.empty()) { auto &workItem = worklist.back(); + if (isInitialTree() && !canBeMaterializedInInitializer(workItem.op)) { + workItem.op->emitError("Value cannot be used in initializer."); + return {}; + } if (!workItem.operands.empty()) { auto operand = workItem.operands.pop_back_val(); if (materializedValues.contains(operand) || !shouldMaterialize(operand)) @@ -317,6 +342,11 @@ ClockLowering &ModuleLowering::getOrCreatePassThrough() { return *slot; } +ClockLowering &ModuleLowering::getInitial() { + assert(!!initialLowering && "Initial tree op should have been constructed"); + return *initialLowering; +} + /// Replace all uses of a value with a `StateReadOp` on a state. Value ModuleLowering::replaceValueWithStateRead(Value value, Value state) { OpBuilder builder(state.getContext()); @@ -415,7 +445,8 @@ LogicalResult ModuleLowering::lowerStates() { template LogicalResult ModuleLowering::lowerStateLike( Operation *stateOp, Value stateClock, Value stateEnable, Value stateReset, - ArrayRef stateInputs, FlatSymbolRefAttr callee) { + ArrayRef stateInputs, FlatSymbolRefAttr callee, + ArrayRef initialValues) { // Grab all operands from the state op at the callsite and make it drop all // its references. This allows `materializeValue` to move an operation if this // state was the last user. @@ -470,10 +501,23 @@ LogicalResult ModuleLowering::lowerStateLike( thenBuilder.create(stateOp->getLoc(), alloc, constZero, Value()); } - nonResetBuilder = ifOp.getElseBodyBuilder(); } + if (!initialValues.empty()) { + assert(initialValues.size() == allocatedStates.size() && + "Unexpected number of initializers"); + auto &initialTree = getInitial(); + for (auto [alloc, init] : llvm::zip(allocatedStates, initialValues)) { + // TODO: Can we get away without materialization? + auto matierializedInit = initialTree.materializeValue(init); + if (!matierializedInit) + return failure(); + initialTree.builder.create(stateOp->getLoc(), alloc, + matierializedInit, Value()); + } + } + stateOp->dropAllReferences(); auto newStateOp = nonResetBuilder.create( @@ -501,10 +545,11 @@ LogicalResult ModuleLowering::lowerState(StateOp stateOp) { return stateOp.emitError("state with latency > 1 not supported"); auto stateInputs = SmallVector(stateOp.getInputs()); + auto stateInitializers = SmallVector(stateOp.getInitials()); - return lowerStateLike(stateOp, stateOp.getClock(), - stateOp.getEnable(), stateOp.getReset(), - stateInputs, stateOp.getArcAttr()); + return lowerStateLike( + stateOp, stateOp.getClock(), stateOp.getEnable(), stateOp.getReset(), + stateInputs, stateOp.getArcAttr(), stateInitializers); } LogicalResult ModuleLowering::lowerState(sim::DPICallOp callOp) { @@ -829,6 +874,13 @@ LogicalResult LowerStatePass::runOnModule(HWModuleOp moduleOp, Operation *clockSentinel = lowering.stateBuilder.create(moduleOp.getLoc()); + // Create the 'initial' pseudo clock tree. + auto initialTreeOp = + lowering.stateBuilder.create(moduleOp.getLoc()); + initialTreeOp.getBody().emplaceBlock(); + lowering.initialLowering = + std::make_unique(Value{}, initialTreeOp, stats); + lowering.stateBuilder.setInsertionPoint(stateSentinel); lowering.clockBuilder.setInsertionPoint(clockSentinel); @@ -856,9 +908,9 @@ LogicalResult LowerStatePass::runOnModule(HWModuleOp moduleOp, moduleOp.getBodyBlock()->eraseArguments( [&](auto arg) { return arg != lowering.storageArg; }); ImplicitLocOpBuilder builder(moduleOp.getLoc(), moduleOp); - auto modelOp = - builder.create(moduleOp.getLoc(), moduleOp.getModuleNameAttr(), - TypeAttr::get(moduleOp.getModuleType())); + auto modelOp = builder.create( + moduleOp.getLoc(), moduleOp.getModuleNameAttr(), + TypeAttr::get(moduleOp.getModuleType()), mlir::FlatSymbolRefAttr()); modelOp.getBody().takeBody(moduleOp.getBody()); moduleOp->erase(); sortTopologically(&modelOp.getBodyBlock()); diff --git a/lib/Dialect/Arc/Transforms/StripSV.cpp b/lib/Dialect/Arc/Transforms/StripSV.cpp index f18602261b40..dbe400e81bfb 100644 --- a/lib/Dialect/Arc/Transforms/StripSV.cpp +++ b/lib/Dialect/Arc/Transforms/StripSV.cpp @@ -151,9 +151,19 @@ void StripSVPass::runOnOperation() { else next = reg.getNext(); + Value presetValue; + // Materialize initial value, assume zero initialization as default. + if (reg.getPreset() && !reg.getPreset()->isZero()) { + assert(hw::type_isa(reg.getType()) && + "cannot lower non integer preset"); + presetValue = builder.createOrFold( + reg.getLoc(), IntegerAttr::get(reg.getType(), *reg.getPreset())); + } + Value compReg = builder.create( reg.getLoc(), next.getType(), next, reg.getClk(), reg.getNameAttr(), - Value{}, Value{}, Value{}, reg.getInnerSymAttr()); + Value{}, Value{}, /*powerOnValue*/ presetValue, + reg.getInnerSymAttr()); reg.replaceAllUsesWith(compReg); opsToDelete.push_back(reg); continue; diff --git a/lib/Dialect/ESI/runtime/CMakeLists.txt b/lib/Dialect/ESI/runtime/CMakeLists.txt index fdda1847c2da..3f30cd7160e4 100644 --- a/lib/Dialect/ESI/runtime/CMakeLists.txt +++ b/lib/Dialect/ESI/runtime/CMakeLists.txt @@ -59,6 +59,7 @@ if(ZLIB_FOUND) set(ZLIB_LIBRARY ZLIB::ZLIB) else() message("-- zlib not found, pulling down zlib from git") + set(ZLIB_BUILD_EXAMPLES OFF) FetchContent_Declare( ZLIB GIT_REPOSITORY https://github.com/madler/zlib.git diff --git a/lib/Dialect/FIRRTL/Export/FIREmitter.cpp b/lib/Dialect/FIRRTL/Export/FIREmitter.cpp index 76f7fc862288..203ed9df5d7d 100644 --- a/lib/Dialect/FIRRTL/Export/FIREmitter.cpp +++ b/lib/Dialect/FIRRTL/Export/FIREmitter.cpp @@ -60,6 +60,7 @@ struct Emitter { void emitModuleParameters(Operation *op, ArrayAttr parameters); void emitDeclaration(LayerOp op); void emitDeclaration(OptionOp op); + void emitDeclaration(FormalOp op); void emitEnabledLayers(ArrayRef layers); void emitParamAssign(ParamDeclAttr param, Operation *op, @@ -406,6 +407,7 @@ void Emitter::emitCircuit(CircuitOp op) { }) .Case([&](auto op) { emitDeclaration(op); }) .Case([&](auto op) { emitDeclaration(op); }) + .Case([&](auto op) { emitDeclaration(op); }) .Default([&](auto op) { emitOpError(op, "not supported for emission inside circuit"); }); @@ -623,6 +625,21 @@ void Emitter::emitDeclaration(OptionOp op) { ps << PP::newline << PP::newline; } +/// Emit a formal test definition. +void Emitter::emitDeclaration(FormalOp op) { + startStatement(); + ps << "formal " << PPExtString(op.getSymName()) << " of " + << PPExtString(op.getModuleName()) << ", bound = "; + ps.addAsString(op.getBound()); + + if (auto outputFile = op->getAttrOfType("output_file")) { + ps << ", "; + ps.writeQuotedEscaped(outputFile.getFilename().getValue()); + } + + emitLocationAndNewLine(op); +} + /// Check if an operation is inlined into the emission of their users. For /// example, subfields are always inlined. static bool isEmittedInline(Operation *op) { diff --git a/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp b/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp index 7403c72b81b0..2e3c72b394e3 100644 --- a/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp @@ -16,7 +16,6 @@ LogicalResult instance_like_impl::verifyReferencedModule(Operation *instanceOp, SymbolTableCollection &symbolTable, mlir::FlatSymbolRefAttr moduleName) { - auto module = instanceOp->getParentOfType(); auto referencedModule = symbolTable.lookupNearestSymbolFrom(instanceOp, moduleName); if (!referencedModule) { @@ -29,15 +28,6 @@ instance_like_impl::verifyReferencedModule(Operation *instanceOp, .attachNote(referencedModule.getLoc()) << "class declared here"; - // Check that this instance doesn't recursively instantiate its wrapping - // module. - if (referencedModule == module) { - auto diag = instanceOp->emitOpError() - << "is a recursive instantiation of its containing module"; - return diag.attachNote(module.getLoc()) - << "containing module declared here"; - } - // Small helper add a note to the original declaration. auto emitNote = [&](InFlightDiagnostic &&diag) -> InFlightDiagnostic && { diag.attachNote(referencedModule->getLoc()) diff --git a/lib/Dialect/FIRRTL/FIRRTLOps.cpp b/lib/Dialect/FIRRTL/FIRRTLOps.cpp index 4e5c94de3146..8462bd9b102b 100644 --- a/lib/Dialect/FIRRTL/FIRRTLOps.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLOps.cpp @@ -3362,6 +3362,25 @@ void RegResetOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { return forceableAsmResultNames(*this, getName(), setNameFn); } +//===----------------------------------------------------------------------===// +// FormalOp +//===----------------------------------------------------------------------===// + +LogicalResult +FormalOp::verifySymbolUses(::mlir::SymbolTableCollection &symbolTable) { + // The referenced symbol is restricted to FModuleOps + auto referencedModule = symbolTable.lookupNearestSymbolFrom( + *this, getModuleNameAttr()); + if (!referencedModule) + return (*this)->emitOpError("invalid symbol reference"); + + return success(); +} + +//===----------------------------------------------------------------------===// +// WireOp +//===----------------------------------------------------------------------===// + void WireOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { return forceableAsmResultNames(*this, getName(), setNameFn); } @@ -3384,6 +3403,10 @@ LogicalResult WireOp::verifySymbolUses(SymbolTableCollection &symbolTable) { symbolTable, Twine("'") + getOperationName() + "' op is"); } +//===----------------------------------------------------------------------===// +// ObjectOp +//===----------------------------------------------------------------------===// + void ObjectOp::build(OpBuilder &builder, OperationState &state, ClassLike klass, StringRef name) { build(builder, state, klass.getInstanceType(), diff --git a/lib/Dialect/FIRRTL/Import/FIRParser.cpp b/lib/Dialect/FIRRTL/Import/FIRParser.cpp index 40f6ddf2246d..69dc43049589 100644 --- a/lib/Dialect/FIRRTL/Import/FIRParser.cpp +++ b/lib/Dialect/FIRRTL/Import/FIRParser.cpp @@ -1301,8 +1301,9 @@ struct FIRModuleContext : public FIRParser { llvm::DenseMap, Value> constantCache; /// Get a cached constant. - Value getCachedConstantInt(ImplicitLocOpBuilder &builder, Attribute attr, - IntType type, APInt &value) { + template + Value getCachedConstant(ImplicitLocOpBuilder &builder, Attribute attr, + Type type, Args &&...args) { auto &result = constantCache[{attr, type}]; if (result) return result; @@ -1312,15 +1313,15 @@ struct FIRModuleContext : public FIRParser { OpBuilder::InsertPoint savedIP; auto *parentOp = builder.getInsertionBlock()->getParentOp(); - if (!isa(parentOp)) { + if (!isa(parentOp)) { savedIP = builder.saveInsertionPoint(); - while (!isa(parentOp)) { + while (!isa(parentOp)) { builder.setInsertionPoint(parentOp); parentOp = builder.getInsertionBlock()->getParentOp(); } } - result = builder.create(type, value); + result = builder.create(type, std::forward(args)...); if (savedIP.isSet()) builder.setInsertionPoint(savedIP.getBlock(), savedIP.getPoint()); @@ -1913,8 +1914,9 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, "expected string literal in String expression") || parseToken(FIRToken::r_paren, "expected ')' in String expression")) return failure(); - result = builder.create( - builder.getStringAttr(FIRToken::getStringValue(spelling))); + auto attr = builder.getStringAttr(FIRToken::getStringValue(spelling)); + result = moduleContext.getCachedConstant( + builder, attr, builder.getType(), attr); break; } case FIRToken::kw_Integer: { @@ -1927,8 +1929,10 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, parseIntLit(value, "expected integer literal in Integer expression") || parseToken(FIRToken::r_paren, "expected ')' in Integer expression")) return failure(); - result = - builder.create(APSInt(value, /*isUnsigned=*/false)); + APSInt apint(value, /*isUnsigned=*/false); + result = moduleContext.getCachedConstant( + builder, IntegerAttr::get(getContext(), apint), + builder.getType(), apint); break; } case FIRToken::kw_Bool: { @@ -1947,7 +1951,9 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, return emitError("expected true or false in Bool expression"); if (parseToken(FIRToken::r_paren, "expected ')' in Bool expression")) return failure(); - result = builder.create(value); + auto attr = builder.getBoolAttr(value); + result = moduleContext.getCachedConstant( + builder, attr, builder.getType(), value); break; } case FIRToken::kw_Double: { @@ -1967,7 +1973,9 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, double d; if (!llvm::to_float(spelling, d)) return emitError("invalid double"); - result = builder.create(builder.getF64FloatAttr(d)); + auto attr = builder.getF64FloatAttr(d); + result = moduleContext.getCachedConstant( + builder, attr, builder.getType(), attr); break; } case FIRToken::kw_List: { @@ -2424,7 +2432,7 @@ ParseResult FIRStmtParser::parseIntegerLiteralExp(Value &result) { } locationProcessor.setLoc(loc); - result = moduleContext.getCachedConstantInt(builder, attr, type, value); + result = moduleContext.getCachedConstant(builder, attr, type, attr); return success(); } @@ -3749,7 +3757,7 @@ ParseResult FIRStmtParser::parseRefForceInitial() { value.getBitWidth(), IntegerType::Unsigned), value); - auto pred = moduleContext.getCachedConstantInt(builder, attr, type, value); + auto pred = moduleContext.getCachedConstant(builder, attr, type, attr); builder.create(pred, dest, src); return success(); @@ -3812,7 +3820,7 @@ ParseResult FIRStmtParser::parseRefReleaseInitial() { value.getBitWidth(), IntegerType::Unsigned), value); - auto pred = moduleContext.getCachedConstantInt(builder, attr, type, value); + auto pred = moduleContext.getCachedConstant(builder, attr, type, attr); builder.create(pred, dest); return success(); @@ -4634,6 +4642,7 @@ struct FIRCircuitParser : public FIRParser { ParseResult parseExtModule(CircuitOp circuit, unsigned indent); ParseResult parseIntModule(CircuitOp circuit, unsigned indent); ParseResult parseModule(CircuitOp circuit, bool isPublic, unsigned indent); + ParseResult parseFormal(CircuitOp circuit, unsigned indent); ParseResult parseLayerName(SymbolRefAttr &result); ParseResult parseOptionalEnabledLayers(ArrayAttr &result); @@ -4939,6 +4948,7 @@ ParseResult FIRCircuitParser::skipToModuleEnd(unsigned indent) { case FIRToken::kw_extclass: case FIRToken::kw_extmodule: case FIRToken::kw_intmodule: + case FIRToken::kw_formal: case FIRToken::kw_module: case FIRToken::kw_public: case FIRToken::kw_layer: @@ -5202,6 +5212,39 @@ ParseResult FIRCircuitParser::parseModule(CircuitOp circuit, bool isPublic, return success(); } +ParseResult FIRCircuitParser::parseFormal(CircuitOp circuit, unsigned indent) { + consumeToken(FIRToken::kw_formal); + StringRef id, moduleName, boundSpelling; + int64_t bound = -1; + LocWithInfo info(getToken().getLoc(), this); + + // Parse the formal operation + if (parseId(id, "expected a formal test name") || + parseToken(FIRToken::kw_of, + "expected keyword 'of' after formal test name") || + parseId(moduleName, "expected the name of a module") || + parseToken(FIRToken::comma, "expected ','") || + parseGetSpelling(boundSpelling) || + parseToken(FIRToken::identifier, + "expected parameter 'bound' after ','") || + parseToken(FIRToken::equal, "expected '=' after 'bound'") || + parseIntLit(bound, "expected integer in bound specification") || + info.parseOptionalInfo()) + return failure(); + + // Check that the parameter is valid + if (boundSpelling != "bound" || bound <= 0) + return emitError("Invalid parameter given to formal test: ") + << boundSpelling << " = " << bound, + failure(); + + // Build out the firrtl mlir op + auto builder = circuit.getBodyBuilder(); + builder.create(info.getLoc(), id, moduleName, bound); + + return success(); +} + ParseResult FIRCircuitParser::parseToplevelDefinition(CircuitOp circuit, unsigned indent) { switch (getToken().getKind()) { @@ -5216,6 +5259,10 @@ ParseResult FIRCircuitParser::parseToplevelDefinition(CircuitOp circuit, return parseExtClass(circuit, indent); case FIRToken::kw_extmodule: return parseExtModule(circuit, indent); + case FIRToken::kw_formal: + if (requireFeature({4, 0, 0}, "inline formal tests")) + return failure(); + return parseFormal(circuit, indent); case FIRToken::kw_intmodule: if (removedFeature({4, 0, 0}, "intrinsic modules")) return failure(); @@ -5563,6 +5610,7 @@ ParseResult FIRCircuitParser::parseCircuit( case FIRToken::kw_extmodule: case FIRToken::kw_intmodule: case FIRToken::kw_layer: + case FIRToken::kw_formal: case FIRToken::kw_module: case FIRToken::kw_option: case FIRToken::kw_public: @@ -5631,11 +5679,11 @@ ParseResult FIRCircuitParser::parseCircuit( // a SymbolRefAttr. auto parseLayerName = [&](StringRef name) { // Parse the layer name into a SymbolRefAttr. - auto [head, rest] = name.split("::"); + auto [head, rest] = name.split("."); SmallVector nestedRefs; while (!rest.empty()) { StringRef next; - std::tie(next, rest) = rest.split("::"); + std::tie(next, rest) = rest.split("."); nestedRefs.push_back(FlatSymbolRefAttr::get(getContext(), next)); } return SymbolRefAttr::get(getContext(), head, nestedRefs); diff --git a/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def b/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def index b3983a86068c..7d25c9191735 100644 --- a/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def +++ b/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def @@ -117,6 +117,7 @@ TOK_KEYWORD(extclass) TOK_KEYWORD(extmodule) TOK_KEYWORD(false) TOK_KEYWORD(flip) +TOK_KEYWORD(formal) TOK_KEYWORD(group) TOK_KEYWORD(infer) TOK_KEYWORD(input) @@ -151,6 +152,7 @@ TOK_KEYWORD(regreset) TOK_KEYWORD(reset) TOK_KEYWORD(skip) TOK_KEYWORD(smem) +TOK_KEYWORD(symbolic) TOK_KEYWORD(true) TOK_KEYWORD(type) TOK_KEYWORD(undefined) diff --git a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt index c885ea49e910..e86980a540a3 100755 --- a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt +++ b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt @@ -3,6 +3,7 @@ add_circt_dialect_library(CIRCTFIRRTLTransforms AddSeqMemPorts.cpp BlackBoxReader.cpp CheckCombLoops.cpp + CheckRecursiveInstantiation.cpp CreateCompanionAssume.cpp CreateSiFiveMetadata.cpp Dedup.cpp diff --git a/lib/Dialect/FIRRTL/Transforms/CheckRecursiveInstantiation.cpp b/lib/Dialect/FIRRTL/Transforms/CheckRecursiveInstantiation.cpp new file mode 100644 index 000000000000..52329f8236db --- /dev/null +++ b/lib/Dialect/FIRRTL/Transforms/CheckRecursiveInstantiation.cpp @@ -0,0 +1,66 @@ +//===- CheckRecursiveInstantiation.cpp - Check recurisve instantiation ----===// +// +// 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/FIRRTL/FIRRTLInstanceGraph.h" +#include "circt/Dialect/FIRRTL/Passes.h" +#include "mlir/Pass/Pass.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SCCIterator.h" + +namespace circt { +namespace firrtl { +#define GEN_PASS_DEF_CHECKRECURSIVEINSTANTIATION +#include "circt/Dialect/FIRRTL/Passes.h.inc" +} // namespace firrtl +} // namespace circt + +using namespace circt; +using namespace firrtl; + +static void printPath(InstanceGraph &instanceGraph, + ArrayRef nodes) { + assert(nodes.size() > 0 && "an scc should have at least one node"); + auto diag = + emitError(nodes.front()->getModule().getLoc(), "recursive instantiation"); + llvm::SmallPtrSet scc(nodes.begin(), nodes.end()); + for (auto *node : nodes) { + for (auto *record : *node) { + auto *target = record->getTarget(); + if (!scc.contains(target)) + continue; + auto ¬e = diag.attachNote(record->getInstance().getLoc()); + note << record->getParent()->getModule().getModuleName(); + note << " instantiates " + << record->getTarget()->getModule().getModuleName() << " here"; + } + } +} + +namespace { +class CheckRecursiveInstantiationPass + : public impl::CheckRecursiveInstantiationBase< + CheckRecursiveInstantiationPass> { +public: + void runOnOperation() override { + auto &instanceGraph = getAnalysis(); + for (auto it = llvm::scc_begin(&instanceGraph), + end = llvm::scc_end(&instanceGraph); + it != end; ++it) { + if (it.hasCycle()) { + printPath(instanceGraph, *it); + signalPassFailure(); + } + } + markAllAnalysesPreserved(); + } +}; +} // namespace + +std::unique_ptr circt::firrtl::createCheckRecursiveInstantiation() { + return std::make_unique(); +} diff --git a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp index 87487aca1c48..24ff2c94078a 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp @@ -81,17 +81,18 @@ static bool shouldCreateClassImpl(igraph::InstanceGraphNode *node) { /// to the targeted operation. struct PathInfo { PathInfo() = default; - PathInfo(Operation *op, FlatSymbolRefAttr symRef, + PathInfo(Location loc, bool canBeInstanceTarget, FlatSymbolRefAttr symRef, StringAttr altBasePathModule) - : op(op), symRef(symRef), altBasePathModule(altBasePathModule) { - assert(op && "op must not be null"); + : loc(loc), canBeInstanceTarget(canBeInstanceTarget), symRef(symRef), + altBasePathModule(altBasePathModule) { assert(symRef && "symRef must not be null"); } - operator bool() const { return op != nullptr; } + /// The Location of the hardware component targeted by this path. + std::optional loc = std::nullopt; - /// The hardware component targeted by this path. - Operation *op = nullptr; + /// Flag to indicate if the hardware component can be targeted as an instance. + bool canBeInstanceTarget = false; /// A reference to the hierarchical path targeting the op. FlatSymbolRefAttr symRef = nullptr; @@ -584,17 +585,23 @@ LogicalResult PathTracker::updatePathInfoTable(PathInfoTable &pathInfoTable, auto [it, inserted] = pathInfoTable.table.try_emplace(entry.id); auto &pathInfo = it->second; if (!inserted) { - auto diag = - emitError(pathInfo.op->getLoc(), "duplicate identifier found"); + assert(pathInfo.loc.has_value() && "all PathInfo should have a Location"); + auto diag = emitError(pathInfo.loc.value(), "duplicate identifier found"); diag.attachNote(entry.op->getLoc()) << "other identifier here"; return failure(); } - if (entry.pathAttr) - pathInfo = {entry.op, cache.getRefFor(entry.pathAttr), - entry.altBasePathModule}; - else - pathInfo.op = entry.op; + // Check if the op is targetable by an instance target. The op pointer may + // be invalidated later, so this is the last time we want to access it here. + bool canBeInstanceTarget = isa(entry.op); + + if (entry.pathAttr) { + pathInfo = {entry.op->getLoc(), canBeInstanceTarget, + cache.getRefFor(entry.pathAttr), entry.altBasePathModule}; + } else { + pathInfo.loc = entry.op->getLoc(); + pathInfo.canBeInstanceTarget = canBeInstanceTarget; + } } return success(); } @@ -1468,14 +1475,14 @@ struct PathOpConversion : public OpConversionPattern { ConversionPatternRewriter &rewriter) const override { auto *context = op->getContext(); auto pathType = om::PathType::get(context); - auto pathInfo = pathInfoTable.table.lookup(op.getTarget()); + auto pathInfoIt = pathInfoTable.table.find(op.getTarget()); // The 0'th argument is the base path by default. auto basePath = op->getBlock()->getArgument(0); // If the target was optimized away, then replace the path operation with // a deleted path. - if (!pathInfo) { + if (pathInfoIt == pathInfoTable.table.end()) { if (op.getTargetKind() == firrtl::TargetKind::DontTouch) return emitError(op.getLoc(), "DontTouch target was deleted"); if (op.getTargetKind() == firrtl::TargetKind::Instance) @@ -1484,6 +1491,7 @@ struct PathOpConversion : public OpConversionPattern { return success(); } + auto pathInfo = pathInfoIt->second; auto symbol = pathInfo.symRef; // Convert the target kind to an OMIR target. Member references are updated @@ -1497,15 +1505,15 @@ struct PathOpConversion : public OpConversionPattern { targetKind = om::TargetKind::Reference; break; case firrtl::TargetKind::Instance: - if (!isa(pathInfo.op)) + if (!pathInfo.canBeInstanceTarget) return emitError(op.getLoc(), "invalid target for instance path") - .attachNote(pathInfo.op->getLoc()) + .attachNote(pathInfo.loc) << "target not instance or module"; targetKind = om::TargetKind::Instance; break; case firrtl::TargetKind::MemberInstance: case firrtl::TargetKind::MemberReference: - if (isa(pathInfo.op)) + if (pathInfo.canBeInstanceTarget) targetKind = om::TargetKind::MemberInstance; else targetKind = om::TargetKind::MemberReference; diff --git a/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp b/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp index b87786e66252..cf5aff491e1a 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp @@ -181,6 +181,20 @@ void LowerIntmodulesPass::runOnOperation() { }; auto inputs = replaceResults(builder, inst.getResults().drop_back()); + // en and test_en are swapped between extmodule and intrinsic. + if (inputs.size() > 2) { + auto port1 = inst.getPortName(1); + auto port2 = inst.getPortName(2); + if (port1 != "test_en") { + mlir::emitError(op.getPortLocation(1), + "expected port named 'test_en'"); + return signalPassFailure(); + } else if (port2 != "en") { + mlir::emitError(op.getPortLocation(2), "expected port named 'en'"); + return signalPassFailure(); + } else + std::swap(inputs[1], inputs[2]); + } auto intop = builder.create( builder.getType(), "circt_clock_gate", inputs, op.getParameters()); diff --git a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp index ffa8d5dec734..96b91a8ec3d2 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp @@ -837,7 +837,7 @@ void LowerLayersPass::runOnOperation() { StringRef circuitName = circuitOp.getName(); circuitOp.walk([&](LayerOp layerOp) { auto parentOp = layerOp->getParentOfType(); - while (parentOp && parentOp != layers.back().first) + while (!layers.empty() && parentOp != layers.back().first) layers.pop_back(); if (layerOp.getConvention() == LayerConvention::Inline) { diff --git a/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp b/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp index 6b629bf968fe..07e2dc0bf821 100644 --- a/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp @@ -605,6 +605,7 @@ struct SpecializeLayers { llvm::make_early_inc_range(block->getOps())) { nestedRefs.push_back(SymbolRefAttr::get(nested)); handleLayer(nested, Block::iterator(nested), ""); + nestedRefs.pop_back(); } return; } @@ -617,6 +618,7 @@ struct SpecializeLayers { nestedRefs.push_back(SymbolRefAttr::get(nested)); handleLayer(nested, insertionPoint, prefix + layer.getSymName() + "_"); + nestedRefs.pop_back(); } // Erase the now empty layer. layer->erase(); diff --git a/lib/Dialect/LLHD/IR/LLHDOps.cpp b/lib/Dialect/LLHD/IR/LLHDOps.cpp index 6e85ea56163b..7abe6541da69 100644 --- a/lib/Dialect/LLHD/IR/LLHDOps.cpp +++ b/lib/Dialect/LLHD/IR/LLHDOps.cpp @@ -12,6 +12,7 @@ #include "circt/Dialect/LLHD/IR/LLHDOps.h" #include "circt/Dialect/HW/HWOps.h" +#include "circt/Support/CustomDirectiveImpl.h" #include "mlir/IR/Attributes.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Matchers.h" @@ -24,6 +25,7 @@ using namespace circt; using namespace mlir; +using namespace llhd; unsigned circt::llhd::getLLHDTypeWidth(Type type) { if (auto sig = dyn_cast(type)) @@ -64,6 +66,15 @@ void llhd::ConstantTimeOp::build(OpBuilder &builder, OperationState &result, return build(builder, result, TimeType::get(ctx), attr); } +//===----------------------------------------------------------------------===// +// SignalOp +//===----------------------------------------------------------------------===// + +void SignalOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + if (getName() && !getName()->empty()) + setNameFn(getResult(), *getName()); +} + //===----------------------------------------------------------------------===// // SigExtractOp and PtrExtractOp //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp b/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp index bab5cb513ffd..27b7a6afe3fe 100644 --- a/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp +++ b/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp @@ -70,7 +70,7 @@ void EarlyCodeMotionPass::runOnProcess(llhd::ProcessOp proc) { for (auto iter = block->getOperations().begin(); iter != block->getOperations().end(); ++iter) { Operation &op = *iter; - if (!isa(op) && !isa(op) && + if (!isa(op) && !isa(op) && (!mlir::isMemoryEffectFree(&op) || op.hasTrait())) continue; diff --git a/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp b/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp index d20a5f437e88..2ec645d6ae9c 100644 --- a/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp +++ b/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp @@ -34,40 +34,6 @@ struct ProcessLoweringPass void runOnOperation() override; }; -/// Backtrack a signal value and make sure that every part of it is in the -/// observer list at some point. Assumes that there is no operation that adds -/// parts to a signal that it does not take as input (e.g. something like -/// llhd.sig.zext %sig : !hw.inout -> !hw.inout). -static LogicalResult checkSignalsAreObserved(OperandRange obs, Value value) { - // If the value in the observer list, we don't need to backtrack further. - if (llvm::is_contained(obs, value)) - return success(); - - if (Operation *op = value.getDefiningOp()) { - // If no input is a signal, this operation creates one and thus this is the - // last point where it could have been observed. As we've already checked - // that, we can fail here. This includes for example llhd.sig - if (llvm::none_of(op->getOperands(), [](Value arg) { - return isa(arg.getType()); - })) - return failure(); - - // Only recusively backtrack signal values. Other values cannot be changed - // from outside or with a delay. If they come from probes at some point, - // they are covered by that probe. As soon as we find a signal that is not - // observed no matter how far we backtrack, we fail. - return success(llvm::all_of(op->getOperands(), [&](Value arg) { - return !isa(arg.getType()) || - succeeded(checkSignalsAreObserved(obs, arg)); - })); - } - - // If the value is a module argument (no block arguments except for the entry - // block are allowed here) and was not observed, we cannot backtrack further - // and thus fail. - return failure(); -} - static LogicalResult isProcValidToLower(llhd::ProcessOp op) { size_t numBlocks = op.getBody().getBlocks().size(); @@ -98,13 +64,24 @@ static LogicalResult isProcValidToLower(llhd::ProcessOp op) { "during process-lowering: llhd.wait terminators with optional time " "argument cannot be lowered to structural LLHD"); + SmallVector observedSignals; + for (Value obs : wait.getObserved()) + if (auto prb = obs.getDefiningOp()) + if (!op.getBody().isAncestor(prb->getParentRegion())) + observedSignals.push_back(prb.getSignal()); + // Every probed signal has to occur in the observed signals list in // the wait instruction - WalkResult result = op.walk([&wait](llhd::PrbOp prbOp) -> WalkResult { - if (failed(checkSignalsAreObserved(wait.getObs(), prbOp.getSignal()))) - return wait.emitOpError( - "during process-lowering: the wait terminator is required to " - "have all probed signals as arguments"); + WalkResult result = op.walk([&](Operation *operation) -> WalkResult { + // TODO: value does not need to be observed if all values this value is + // a combinatorial result of are observed. + for (Value operand : operation->getOperands()) + if (!op.getBody().isAncestor(operand.getParentRegion()) && + !llvm::is_contained(wait.getObserved(), operand) && + !llvm::is_contained(observedSignals, operand)) + return wait.emitOpError( + "during process-lowering: the wait terminator is required to " + "have values used in the process as arguments"); return WalkResult::advance(); }); diff --git a/lib/Firtool/Firtool.cpp b/lib/Firtool/Firtool.cpp index 07d93548de3b..fe93765ce7e3 100644 --- a/lib/Firtool/Firtool.cpp +++ b/lib/Firtool/Firtool.cpp @@ -26,6 +26,8 @@ using namespace circt; LogicalResult firtool::populatePreprocessTransforms(mlir::PassManager &pm, const FirtoolOptions &opt) { + pm.nest().addPass( + firrtl::createCheckRecursiveInstantiation()); // Legalize away "open" aggregates to hw-only versions. pm.nest().addPass(firrtl::createLowerOpenAggsPass()); diff --git a/test/Conversion/ConvertToArcs/convert-to-arcs.mlir b/test/Conversion/ConvertToArcs/convert-to-arcs.mlir index 21fac9ecc0b5..047a82f59eb3 100644 --- a/test/Conversion/ConvertToArcs/convert-to-arcs.mlir +++ b/test/Conversion/ConvertToArcs/convert-to-arcs.mlir @@ -110,6 +110,36 @@ hw.module @Reshuffling(in %clockA: !seq.clock, in %clockB: !seq.clock, out z0: i hw.module.extern private @Reshuffling2(out z0: i4, out z1: i4, out z2: i4, out z3: i4) +// CHECK-LABEL: arc.define @ReshufflingInit_arc(%arg0: i4, %arg1: i4) +// CHECK-NEXT: arc.output %arg0, %arg1 +// CHECK-NEXT: } + +// CHECK-LABEL: arc.define @ReshufflingInit_arc_0(%arg0: i4, %arg1: i4) +// CHECK-NEXT: arc.output %arg0, %arg1 +// CHECK-NEXT: } + +// CHECK-LABEL: hw.module @ReshufflingInit +hw.module @ReshufflingInit(in %clockA: !seq.clock, in %clockB: !seq.clock, out z0: i4, out z1: i4, out z2: i4, out z3: i4) { + // CHECK-NEXT: [[C1:%.+]] = hw.constant 1 : i4 + // CHECK-NEXT: [[C2:%.+]] = hw.constant 2 : i4 + // CHECK-NEXT: [[C3:%.+]] = hw.constant 3 : i4 + // CHECK-NEXT: hw.instance "x" @Reshuffling2() + // CHECK-NEXT: [[C0:%.+]] = hw.constant 0 : i4 + // CHECK-NEXT: arc.state @ReshufflingInit_arc(%x.z0, %x.z1) clock %clockA initial ([[C0]], [[C1]] : i4, i4) latency 1 + // CHECK-NEXT: arc.state @ReshufflingInit_arc_0(%x.z2, %x.z3) clock %clockB initial ([[C2]], [[C3]] : i4, i4) latency 1 + // CHECK-NEXT: hw.output + %cst1 = hw.constant 1 : i4 + %cst2 = hw.constant 2 : i4 + %cst3 = hw.constant 3 : i4 + %x.z0, %x.z1, %x.z2, %x.z3 = hw.instance "x" @Reshuffling2() -> (z0: i4, z1: i4, z2: i4, z3: i4) + %4 = seq.compreg %x.z0, %clockA : i4 + %5 = seq.compreg %x.z1, %clockA powerOn %cst1 : i4 + %6 = seq.compreg %x.z2, %clockB powerOn %cst2 : i4 + %7 = seq.compreg %x.z3, %clockB powerOn %cst3 : i4 + hw.output %4, %5, %6, %7 : i4, i4, i4, i4 +} +// CHECK-NEXT: } + // CHECK-LABEL: arc.define @FactorOutCommonOps_arc( // CHECK-NEXT: comb.xor @@ -196,6 +226,22 @@ hw.module @Trivial(in %clock: !seq.clock, in %i0: i4, in %reset: i1, out out: i4 } // CHECK-NEXT: } +// CHECK: arc.define @[[TRIVIALINIT_ARC:.+]]([[ARG0:%.+]]: i4) +// CHECK-NEXT: arc.output [[ARG0]] +// CHECK-NEXT: } + +// CHECK-LABEL: hw.module @TrivialWithInit( +hw.module @TrivialWithInit(in %clock: !seq.clock, in %i0: i4, in %reset: i1, out out: i4) { + // CHECK: [[CST2:%.+]] = hw.constant 2 : i4 + // CHECK: [[RES0:%.+]] = arc.state @[[TRIVIALINIT_ARC]](%i0) clock %clock reset %reset initial ([[CST2]] : i4) latency 1 {names = ["foo"] + // CHECK-NEXT: hw.output [[RES0:%.+]] + %0 = hw.constant 0 : i4 + %cst2 = hw.constant 2 : i4 + %foo = seq.compreg %i0, %clock reset %reset, %0 powerOn %cst2: i4 + hw.output %foo : i4 +} +// CHECK-NEXT: } + // CHECK-NEXT: arc.define @[[NONTRIVIAL_ARC_0:.+]]([[ARG0_1:%.+]]: i4) // CHECK-NEXT: arc.output [[ARG0_1]] // CHECK-NEXT: } diff --git a/test/Conversion/ExportVerilog/bugs.mlir b/test/Conversion/ExportVerilog/bugs.mlir index 9d6792b4f705..cb31e847ac5f 100644 --- a/test/Conversion/ExportVerilog/bugs.mlir +++ b/test/Conversion/ExportVerilog/bugs.mlir @@ -10,4 +10,4 @@ module attributes {circt.loweringOptions = "disallowExpressionInliningInPorts"} // CHECK: .a (a), %bar.b = hw.instance "bar" @Bar(a: %a: !hw.inout) -> (b: i1) } -} \ No newline at end of file +} diff --git a/test/Conversion/ExportVerilog/sv-dialect.mlir b/test/Conversion/ExportVerilog/sv-dialect.mlir index 3d75fd441e62..8146d9dfc231 100644 --- a/test/Conversion/ExportVerilog/sv-dialect.mlir +++ b/test/Conversion/ExportVerilog/sv-dialect.mlir @@ -521,7 +521,8 @@ hw.module @reg_0(in %in4: i4, in %in8: i8, in %in8_2: i8, out a: i8, out b: i8) %unpacked_array = sv.unpacked_array_create %in8, %in8_2 : (i8, i8) -> !hw.uarray<2xi8> %unpacked_wire = sv.wire : !hw.inout> - // CHECK: wire [7:0] unpacked_wire[0:1] = '{in8_2, in8}; + // CHECK: wire [7:0] unpacked_wire[0:1]; + // CHECK-NEXT: assign unpacked_wire = '{in8_2, in8}; sv.assign %unpacked_wire, %unpacked_array: !hw.uarray<2xi8> // CHECK-NEXT: assign a = myReg; @@ -1111,6 +1112,23 @@ hw.module @DontDuplicateSideEffectingVerbatim() { } } +// Issue 6363 +// CHECK-LABEL: module DontInlineAssignmentForUnpackedArrays( +hw.module @DontInlineAssignmentForUnpackedArrays(in %a: !hw.uarray<2xi1>) { +// CHECK: wire w[0:1]; +// CHECK-NEXT: assign w = a; + %w = sv.wire : !hw.inout> + sv.assign %w, %a : !hw.uarray<2xi1> + // CHECK: logic u[0:1]; + // CHECK-NEXT: u = a; + sv.initial { + %u = sv.logic : !hw.inout> + sv.bpassign %u, %a : !hw.uarray<2xi1> + } + + hw.output +} + hw.generator.schema @verbatim_schema, "Simple", ["ports", "write_latency", "read_latency"] hw.module.extern @verbatim_inout_2 () // CHECK-LABEL: module verbatim_M1( @@ -1789,7 +1807,8 @@ sv.func private @open_array(in %array : !sv.open_uarray) sv.func.dpi.import @open_array // CHECK-LABEL: test_open_array -// CHECK: wire [7:0] _GEN[0:1] = '{in_0, in_1}; +// CHECK: wire [7:0] _GEN[0:1]; +// CHECK-NEXT: assign _GEN = '{in_0, in_1}; // CHECK-NEXT: always @(posedge clock) // CHECK-NEXT: open_array(_GEN); hw.module @test_open_array(in %clock : i1, in %in_0 : i8, in %in_1 : i8) { diff --git a/test/Conversion/ExportVerilog/verilog-basic.mlir b/test/Conversion/ExportVerilog/verilog-basic.mlir index 2f28b0e88c04..55586662ac4f 100644 --- a/test/Conversion/ExportVerilog/verilog-basic.mlir +++ b/test/Conversion/ExportVerilog/verilog-basic.mlir @@ -489,7 +489,8 @@ hw.module @ArrayParamsInst() { uarr: %uarr : !hw.uarray<2 x i8>) -> () } // CHECK: wire [1:0][7:0] [[G0:_.*]] = '{8'h1, 8'h2}; -// CHECK: wire [7:0] [[G1:_.*]][0:1] = '{8'h1, 8'h2}; +// CHECK: wire [7:0] [[G1:_.*]][0:1]; +// CHECK: assign [[G1]] = '{8'h1, 8'h2}; // CHECK: ArrayParams #( // CHECK: .param(2) // CHECK: ) arrays ( diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index d26b65f9772a..b5b0085ef92d 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -235,9 +235,12 @@ endmodule // CHECK-LABEL: func.func private @dummyA( // CHECK-LABEL: func.func private @dummyB( // CHECK-LABEL: func.func private @dummyC( +// CHECK-LABEL: func.func private @dummyD( function void dummyA(); endfunction function void dummyB(); endfunction function void dummyC(); endfunction +function void dummyD(int a); endfunction +function bit dummyE(bit a); return a; endfunction // CHECK-LABEL: func.func private @ConditionalStatements( // CHECK-SAME: %arg0: !moore.i1 @@ -1523,53 +1526,6 @@ module PortsUnconnected( // CHECK: moore.output [[D_READ]], [[E_READ]] : !moore.l1, !moore.l1 endmodule -// CHECK-LABEL: moore.module @EventControl(in %clk : !moore.l1) -module EventControl(input clk); - // CHECK: %clk_0 = moore.net name "clk" wire : - - int a1, a2, b, c; - - // CHECK: moore.procedure always - // CHECK: [[CLK_READ:%.+]] = moore.read %clk_0 - // CHECK: moore.wait_event posedge [[CLK_READ]] : l1 - always @(posedge clk) begin end; - - // CHECK: moore.procedure always - // CHECK: [[CLK_READ:%.+]] = moore.read %clk_0 - // CHECK: moore.wait_event negedge [[CLK_READ]] : l1 - always @(negedge clk) begin end; - - // CHECK: moore.procedure always - // CHECK: [[CLK_READ:%.+]] = moore.read %clk_0 - // CHECK: moore.wait_event edge [[CLK_READ]] : l1 - always @(edge clk) begin end; - - // CHECK: moore.procedure always { - // CHECK: [[B_READ:%.+]] = moore.read %b - // CHECK: moore.wait_event none [[B_READ]] : i32 - // CHECK: [[C_READ:%.+]] = moore.read %c - // CHECK: moore.wait_event none [[C_READ]] : i32 - always @(b, c) begin - // CHECK: [[B_READ:%.+]] = moore.read %b - // CHECK: [[C_READ:%.+]] = moore.read %c - // CHECK: [[ADD:%.+]] = moore.add [[B_READ]], [[C_READ]] : i32 - // CHECK: moore.blocking_assign %a1, [[ADD]] : i32 - a1 = b + c; - end; - - // CHECK: moore.procedure always - always @(*) begin - // CHECK: [[B_READ:%.+]] = moore.read %b - // CHECK: [[C_READ:%.+]] = moore.read %c - // CHECK: [[ADD:%.+]] = moore.add [[B_READ]], [[C_READ]] : i32 - // CHECK: moore.blocking_assign %a2, [[ADD]] : i32 - a2 = b + c; - end - - // CHECK: moore.assign %clk_0, %clk : l1 - // CHECK: moore.output -endmodule - // CHECK-LABEL: moore.module @GenerateConstructs() module GenerateConstructs; genvar i; @@ -1731,3 +1687,220 @@ function void ConvertConditionalExprsToResultType(bit [15:0] x, struct packed { // CHECK: } r = z ? y : x; endfunction + +// CHECK-LABEL: func.func private @ImplicitEventControl( +// CHECK-SAME: [[X:%[^:]+]]: !moore.ref +// CHECK-SAME: [[Y:%[^:]+]]: !moore.ref +task automatic ImplicitEventControl(ref int x, ref int y); + // CHECK: moore.wait_event { + // CHECK: } + // CHECK: call @dummyA() + @* dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: call @dummyD([[TMP]]) + @* dummyD(x); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read [[Y]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[Y]] + // CHECK: [[TMP3:%.+]] = moore.add [[TMP1]], [[TMP2]] + // CHECK: call @dummyD([[TMP3]]) + @* dummyD(x + y); +endtask + +// CHECK-LABEL: func.func private @SignalEventControl( +// CHECK-SAME: [[X:%[^:]+]]: !moore.ref +// CHECK-SAME: [[Y:%[^:]+]]: !moore.ref +// CHECK-SAME: [[U:%[^:]+]]: !moore.ref +// CHECK-SAME: [[V:%[^:]+]]: !moore.ref +task automatic SignalEventControl(ref int x, ref int y, ref bit u, ref logic v); + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @x dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event posedge [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event negedge [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(negedge x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event edge [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(edge x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[U]] + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP2]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff u) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[V]] : + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.l1 -> !moore.i1 + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP3]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff v) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[Y]] + // CHECK: [[TMP3:%.+]] = moore.bool_cast [[TMP2]] : i32 -> i1 + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP3]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff y) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read [[Y]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(x or y) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read [[Y]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(x, y) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[U]] + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP2]] + // CHECK: [[TMP1:%.+]] = moore.read [[Y]] + // CHECK: [[TMP2:%.+]] = moore.read [[V]] + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.l1 -> !moore.i1 + // CHECK: moore.detect_event negedge [[TMP1]] if [[TMP3]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff u, negedge y iff v) dummyA(); +endtask + +// CHECK-LABEL: func.func private @ImplicitEventControlExamples( +task automatic ImplicitEventControlExamples(); + // Taken from IEEE 1800-2017 section 9.4.2.2 "Implicit event_expression list". + bit a, b, c, d, f, y, tmp1, tmp2; + int x; + + // Example 1 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %f + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @(*) y = (a & b) | (c & d) | dummyE(f); // equivalent to @(a, b, c, d, f) + + // Example 2 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %tmp1 + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %tmp2 + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(a, b, c, d, tmp1, tmp2) + tmp1 = a & b; + tmp2 = c & d; + y = tmp1 | tmp2; + end + + // Example 3 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(b) + @(a) y = b; // a inside @(a) is not added to outer @* + end + + // Example 4 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(a, b, c, d) + y = a ^ b; + @* y = c ^ d; // equivalent to @(c, d) + end + + // Example 5 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(a, b) + x[a] = !b; + end +endtask diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index a0b8f3cd39a7..b90dbb6f7958 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -306,6 +306,20 @@ func.func @AdvancedConversion(%arg0: !moore.array<5 x struct<{exp_bits: i32, man return %1, %2 : !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>, !moore.i320 } +// CHECK-LABEL: func @Statements +func.func @Statements(%arg0: !moore.i42) { + // CHECK: %x = llhd.sig + %x = moore.variable : + // CHECK: [[TMP:%.+]] = llhd.constant_time <0ns, 0d, 1e> + // CHECK: llhd.drv %x, %arg0 after [[TMP]] : !hw.inout + moore.blocking_assign %x, %arg0 : i42 + // CHECK: [[TMP:%.+]] = llhd.constant_time <0ns, 1d, 0e> + // CHECK: llhd.drv %x, %arg0 after [[TMP]] : !hw.inout + moore.nonblocking_assign %x, %arg0 : i42 + // CHECK-NEXT: return + return +} + // CHECK-LABEL: hw.module @InstanceNull() { moore.module @InstanceNull() { @@ -369,31 +383,31 @@ moore.module @ParamTest(){ moore.module @Variable() { // CHECK: [[TMP0:%.+]] = hw.constant 0 : i32 - // CHECK: [[A:%.+]] = llhd.sig "a" [[TMP0]] : i32 + // CHECK: %a = llhd.sig [[TMP0]] : i32 %a = moore.variable : // CHECK: [[TMP1:%.+]] = hw.constant 0 : i8 - // CHECK: [[B:%.+]] = llhd.sig "b1" [[TMP1]] : i8 + // CHECK: %b1 = llhd.sig [[TMP1]] : i8 %b1 = moore.variable : - // CHECK: [[PRB:%.+]] = llhd.prb [[B]] : !hw.inout + // CHECK: [[PRB:%.+]] = llhd.prb %b1 : !hw.inout %0 = moore.read %b1 : - // CHECK: llhd.sig "b2" [[PRB]] : i8 + // CHECK: %b2 = llhd.sig [[PRB]] : i8 %b2 = moore.variable %0 : // CHECK: %true = hw.constant true %1 = moore.constant 1 : l1 - // CHECK: llhd.sig "l" %true : i1 + // CHECK: %l = llhd.sig %true : i1 %l = moore.variable %1 : // CHECK: [[TMP:%.+]] = hw.constant 0 : i19 - // CHECK: llhd.sig "m" [[TMP]] : i19 + // CHECK: %m = llhd.sig [[TMP]] : i19 %m = moore.variable : // CHECK: [[TMP2:%.+]] = hw.constant 10 : i32 %3 = moore.constant 10 : i32 // CHECK: [[TIME:%.+]] = llhd.constant_time <0ns, 0d, 1e> - // CHECK: llhd.drv [[A]], [[TMP2]] after [[TIME]] : !hw.inout + // CHECK: llhd.drv %a, [[TMP2]] after [[TIME]] : !hw.inout moore.assign %a, %3 : i32 // CHECK: hw.output @@ -411,8 +425,8 @@ moore.module @Struct(in %a : !moore.i32, in %b : !moore.i32, in %arg0 : !moore.s // CHECK: [[C0:%.+]] = hw.constant 0 : i64 // CHECK: [[INIT:%.+]] = hw.bitcast [[C0]] : (i64) -> !hw.struct - // CHECK: llhd.sig "" [[INIT]] : !hw.struct - // CHECK: llhd.sig "" %arg0 : !hw.struct + // CHECK: llhd.sig [[INIT]] : !hw.struct + // CHECK: llhd.sig %arg0 : !hw.struct %1 = moore.variable : > %2 = moore.variable %arg0 : > @@ -425,134 +439,6 @@ moore.module @Struct(in %a : !moore.i32, in %b : !moore.i32, in %arg0 : !moore.s moore.output %0, %3, %4 : !moore.i32, !moore.struct<{exp_bits: i32, man_bits: i32}>, !moore.struct<{exp_bits: i32, man_bits: i32}> } -// CHECK-LABEL: hw.module @Process -moore.module @Process(in %cond : i1) { - // CHECK: [[B:%.+]] = llhd.sig "b" - // CHECK: [[C:%.+]] = llhd.sig "c" - // CHECK: [[D:%.+]] = llhd.sig "d" - // CHECK: [[E:%.+]] = llhd.sig "e" - %b = moore.variable : - %c = moore.variable : - %d = moore.variable : - %e = moore.variable : - - // CHECK: llhd.process - moore.procedure always { - // CHECK: cf.br ^[[BB1:.+]] - // CHECK: ^[[BB1]]: - // CHECK: [[C_OLD:%.+]] = llhd.prb [[C]] - // CHECK: llhd.wait ([[B]], [[C]] : !hw.inout, !hw.inout), ^[[BB2:.+]] - // CHECK: ^[[BB2]]: - // CHECK: [[B_CURR:%.+]] = llhd.prb [[B]] - // CHECK: [[C_CURR:%.+]] = llhd.prb [[C]] - // CHECK: [[NOT_C_OLD:%.+]] = comb.xor [[C_OLD]], %true - // CHECK: [[POSEDGE:%.+]] = comb.and [[NOT_C_OLD]], [[C_CURR]] : i1 - // CHECK: [[PROCEED:%.+]] = comb.or [[B_CURR]], [[POSEDGE]] : i1 - // CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]] - // CHECK: ^[[BB3]]: - // CHECK: [[B_PRB:%.+]] = llhd.prb [[B]] - // CHECK: [[C_PRB:%.+]] = llhd.prb [[C]] - // CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1 - // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> - // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] - // CHECK: [[T1:%.+]] = llhd.constant_time <0ns, 0d, 1e> - // CHECK: llhd.drv [[E]], [[RES]] after [[T1]] - // CHECK: cf.br ^[[BB1]] - %br = moore.read %b : - moore.wait_event none %br : i1 - %cr = moore.read %c : - moore.wait_event posedge %cr : i1 - - %0 = moore.add %br, %cr : i1 - - moore.nonblocking_assign %d, %0 : i1 - moore.blocking_assign %e, %0 : i1 - moore.return - } - - // CHECK: llhd.process - moore.procedure always { - // CHECK: cf.br ^[[BB1:.+]] - // CHECK: ^[[BB1]]: - // CHECK: llhd.wait ([[B]], [[C]] : !hw.inout, !hw.inout), ^[[BB2:.+]] - // CHECK: ^[[BB2]]: - // CHECK: cf.br ^[[BB3:.+]] - // CHECK: ^[[BB3]]: - // CHECK: [[B_PRB:%.+]] = llhd.prb [[B]] - // CHECK: [[C_PRB:%.+]] = llhd.prb [[C]] - // CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1 - // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> - // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] - // CHECK: cf.br ^[[BB1]] - %br = moore.read %b : - %cr = moore.read %c : - %0 = moore.add %br, %cr : i1 - moore.nonblocking_assign %d, %0 : i1 - moore.return - } - - // CHECK: llhd.process - moore.procedure always { - // CHECK: cf.br ^[[BB1:.+]] - // CHECK: ^[[BB1]]: - // CHECK: [[C_OLD:%.+]] = llhd.prb [[C]] - // CHECK: llhd.wait ([[C]] : !hw.inout), ^[[BB2:.+]] - // CHECK: ^[[BB2]]: - // CHECK: [[C_CURR:%.+]] = llhd.prb [[C]] - // CHECK: [[NOT_C_OLD:%.+]] = comb.xor [[C_OLD]], %true - // CHECK: [[POSEDGE:%.+]] = comb.and [[NOT_C_OLD]], [[C_CURR]] : i1 - // CHECK: [[C_CURR1:%.+]] = llhd.prb [[C]] - // CHECK: [[NOT_C_CURR:%.+]] = comb.xor [[C_CURR1]], %true - // CHECK: [[NEGEDGE:%.+]] = comb.and [[C_OLD]], [[NOT_C_CURR]] : i1 - // CHECK: [[PROCEED:%.+]] = comb.or [[POSEDGE]], [[NEGEDGE]] : i1 - // CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]] - // CHECK: ^[[BB3]]: - // CHECK: [[RES:%.+]] = comb.add [[B_PRB:%.+]], [[C_PRB:%.+]] : i1 - // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> - // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] - // CHECK: cf.br ^[[BB1]] - moore.wait_event edge %cr : i1 - %0 = moore.add %br, %cr : i1 - moore.nonblocking_assign %d, %0 : i1 - moore.return - } - - // CHECK: [[B_PRB]] = llhd.prb [[B]] - // CHECK: [[C_PRB]] = llhd.prb [[C]] - %br = moore.read %b : - %cr = moore.read %c : - // CHECK: llhd.process - moore.procedure always { - // CHECK: cf.br ^[[BB1:.+]] - // CHECK: ^[[BB1]]: - // CHECK: [[C_OLD:%.+]] = llhd.prb [[C]] - // CHECK: llhd.wait ([[C]] : !hw.inout), ^[[BB2:.+]] - // CHECK: ^[[BB2]]: - // CHECK: [[C_CURR:%.+]] = llhd.prb [[C]] - // CHECK: [[NOT_C_CURR:%.+]] = comb.xor [[C_CURR]], %true - // CHECK: [[NEGEDGE:%.+]] = comb.and [[C_OLD]], [[NOT_C_CURR]] : i1 - // CHECK: [[PROCEED:%.+]] = comb.or [[NEGEDGE]] : i1 - // CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]] - // CHECK: ^[[BB3]]: - // CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1 - // CHECK: cf.cond_br %cond, ^[[BB4:.+]], ^[[BB5:.+]] - // CHECK: ^[[BB4]]: - // CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e> - // CHECK: llhd.drv [[D]], [[RES]] after [[T0]] - // CHECK: cf.br ^[[BB1]] - // CHECK: ^[[BB5]]: - // CHECK: cf.br ^[[BB1]] - moore.wait_event negedge %cr : i1 - %0 = moore.add %br, %cr : i1 - cf.cond_br %cond, ^bb1, ^bb2 - ^bb1: - moore.nonblocking_assign %d, %0 : i1 - moore.return - ^bb2: - moore.return - } -} - // CHECK-LABEL: func.func @CaseXZ( func.func @CaseXZ(%arg0: !moore.l8, %arg1: !moore.l8) { // CHECK: hw.constant -124 : i8 @@ -596,3 +482,303 @@ func.func @CaseXZ(%arg0: !moore.l8, %arg1: !moore.l8) { return } + +// CHECK-LABEL: hw.module @Procedures +moore.module @Procedures() { + // CHECK: llhd.process { + // CHECK: func.call @dummyA() + // CHECK: llhd.halt + // CHECK: } + moore.procedure initial { + func.call @dummyA() : () -> () + moore.return + } + + // CHECK: llhd.final { + // CHECK: func.call @dummyA() + // CHECK: llhd.halt + // CHECK: } + moore.procedure final { + func.call @dummyA() : () -> () + moore.return + } + + // CHECK: llhd.process { + // CHECK: cf.br ^[[BB:.+]] + // CHECK: ^[[BB]]: + // CHECK: func.call @dummyA() + // CHECK: cf.br ^[[BB]] + // CHECK: } + moore.procedure always { + func.call @dummyA() : () -> () + moore.return + } + + // CHECK: llhd.process { + // CHECK: cf.br ^[[BB:.+]] + // CHECK: ^[[BB]]: + // CHECK: func.call @dummyA() + // CHECK: cf.br ^[[BB]] + // CHECK: } + moore.procedure always_ff { + func.call @dummyA() : () -> () + moore.return + } + + // TODO: moore.procedure always_comb + // TODO: moore.procedure always_latch +} + +func.func private @dummyA() -> () +func.func private @dummyB() -> () +func.func private @dummyC() -> () + +// CHECK-LABEL: hw.module @WaitEvent +moore.module @WaitEvent() { + // CHECK: %a = llhd.sig + // CHECK: [[PRB_A6:%.+]] = llhd.prb %a + // CHECK: [[PRB_A5:%.+]] = llhd.prb %a + // CHECK: [[PRB_A4:%.+]] = llhd.prb %a + // CHECK: [[PRB_A3:%.+]] = llhd.prb %a + // CHECK: [[PRB_A2:%.+]] = llhd.prb %a + // CHECK: [[PRB_A1:%.+]] = llhd.prb %a + // CHECK: [[PRB_A0:%.+]] = llhd.prb %a + // CHECK: %b = llhd.sig + // CHECK: [[PRB_B2:%.+]] = llhd.prb %b + // CHECK: [[PRB_B1:%.+]] = llhd.prb %b + // CHECK: [[PRB_B0:%.+]] = llhd.prb %b + // CHECK: %c = llhd.sig + // CHECK: [[PRB_C:%.+]] = llhd.prb %c + %a = moore.variable : + %b = moore.variable : + %c = moore.variable : + + // CHECK: llhd.process { + // CHECK: func.call @dummyA() + // CHECK: cf.br ^[[WAIT:.+]] + // CHECK: ^[[WAIT]]: + // CHECK: func.call @dummyB() + // CHECK: llhd.wait ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: func.call @dummyB() + // CHECK: cf.br ^[[WAIT:.+]] + // CHECK: ^[[RESUME:.+]]: + // CHECK: func.call @dummyC() + // CHECK: llhd.prb %a + // CHECK: llhd.halt + // CHECK: } + moore.procedure initial { + func.call @dummyA() : () -> () + moore.wait_event { + func.call @dummyB() : () -> () + } + func.call @dummyC() : () -> () + moore.read %a : + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait ([[PRB_A0]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TMP:%.+]] = comb.icmp bin ne [[BEFORE]], [[AFTER]] + // CHECK: cf.cond_br [[TMP]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event any %0 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE_A:%.+]] = llhd.prb %a + // CHECK: [[BEFORE_B:%.+]] = llhd.prb %b + // CHECK: llhd.wait ([[PRB_A1]], [[PRB_B0]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER_A:%.+]] = llhd.prb %a + // CHECK: [[AFTER_B:%.+]] = llhd.prb %b + // CHECK: [[TMP1:%.+]] = comb.icmp bin ne [[BEFORE_A]], [[AFTER_A]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[TMP1]], [[AFTER_B]] + // CHECK: cf.cond_br [[TMP2]] + moore.wait_event { + %0 = moore.read %a : + %1 = moore.read %b : + moore.detect_event any %0 if %1 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE_A:%.+]] = llhd.prb %a + // CHECK: [[BEFORE_B:%.+]] = llhd.prb %b + // CHECK: [[BEFORE_C:%.+]] = llhd.prb %c + // CHECK: llhd.wait ([[PRB_A2]], [[PRB_B1]], [[PRB_C]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER_A:%.+]] = llhd.prb %a + // CHECK: [[AFTER_B:%.+]] = llhd.prb %b + // CHECK: [[AFTER_C:%.+]] = llhd.prb %c + // CHECK: [[TMP1:%.+]] = comb.icmp bin ne [[BEFORE_A]], [[AFTER_A]] + // CHECK: [[TMP2:%.+]] = comb.icmp bin ne [[BEFORE_B]], [[AFTER_B]] + // CHECK: [[TMP3:%.+]] = comb.icmp bin ne [[BEFORE_C]], [[AFTER_C]] + // CHECK: [[TMP4:%.+]] = comb.or bin [[TMP1]], [[TMP2]], [[TMP3]] + // CHECK: cf.cond_br [[TMP4]] + moore.wait_event { + %0 = moore.read %a : + %1 = moore.read %b : + %2 = moore.read %c : + moore.detect_event any %0 : i1 + moore.detect_event any %1 : i1 + moore.detect_event any %2 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait ([[PRB_A3]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TRUE:%.+]] = hw.constant true + // CHECK: [[TMP1:%.+]] = comb.xor bin [[BEFORE]], [[TRUE]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[TMP1]], [[AFTER]] + // CHECK: cf.cond_br [[TMP2]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event posedge %0 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait ([[PRB_A4]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TRUE:%.+]] = hw.constant true + // CHECK: [[TMP1:%.+]] = comb.xor bin [[AFTER]], [[TRUE]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[BEFORE]], [[TMP1]] + // CHECK: cf.cond_br [[TMP2]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event negedge %0 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait ([[PRB_A5]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TRUE:%.+]] = hw.constant true + // CHECK: [[TMP1:%.+]] = comb.xor bin [[BEFORE]], [[TRUE]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[TMP1]], [[AFTER]] + // CHECK: [[TMP3:%.+]] = comb.xor bin [[AFTER]], [[TRUE]] + // CHECK: [[TMP4:%.+]] = comb.and bin [[BEFORE]], [[TMP3]] + // CHECK: [[TMP5:%.+]] = comb.or bin [[TMP2]], [[TMP4]] + // CHECK: cf.cond_br [[TMP5]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event edge %0 : i1 + } + moore.return + } + + // CHECK: [[FALSE:%.+]] = hw.constant false + // CHECK: [[PRB_A:%.+]] = llhd.prb %a + // CHECK: [[PRB_B:%.+]] = llhd.prb %b + // CHECK: llhd.process { + %cond = moore.constant 0 : i1 + moore.procedure always_comb { + // CHECK: cf.br ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: llhd.prb %a + // CHECK: llhd.prb %b + // CHECK: cf.br ^[[BB2:.+]] + // CHECK: ^[[BB2]]: + // CHECK: llhd.wait ([[FALSE]], [[PRB_A]], [[PRB_B]] : {{.*}}), ^[[BB1]] + %1 = moore.conditional %cond : i1 -> i1 { + %2 = moore.read %a : + moore.yield %2 : !moore.i1 + } { + %3 = moore.read %b : + moore.yield %3 : !moore.i1 + } + moore.return + } + + // CHECK: [[PRB_D:%.+]] = llhd.prb %d + // CHECK: llhd.process { + // CHECK: cf.br ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: llhd.prb %d + // CHECK: cf.br ^[[BB2:.+]] + // CHECK: ^[[BB2]]: + // CHECK: llhd.wait ([[PRB_D]] : {{.*}}), ^[[BB1]] + moore.procedure always_latch { + %3 = moore.read %d : + moore.return + } + + // CHECK: %d = llhd.sig %false + %d = moore.variable : + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: llhd.wait ([[PRB_A6]], [[PRB_B2]] : + moore.wait_event { + %0 = moore.constant 0 : i1 + %1 = moore.conditional %0 : i1 -> i1 { + %2 = moore.read %a : + moore.yield %2 : !moore.i1 + } { + %3 = moore.read %b : + moore.yield %3 : !moore.i1 + } + moore.detect_event any %1 : i1 + } + moore.return + } +} + +// Just check that block without predecessors are handled without crashing +// CHECK-LABEL: @NoPredecessorBlockErasure +moore.module @NoPredecessorBlockErasure(in %clk_i : !moore.l1, in %raddr_i : !moore.array<2 x l5>, out rdata_o : !moore.array<2 x l32>, in %waddr_i : !moore.array<1 x l5>, in %wdata_i : !moore.array<1 x l32>, in %we_i : !moore.l1) { + %0 = moore.constant 0 : l32 + %1 = moore.constant 1 : i32 + %2 = moore.constant 0 : i32 + %rdata_o = moore.variable : > + %mem = moore.variable : > + moore.procedure always_ff { + cf.br ^bb1(%2 : !moore.i32) + ^bb1(%4: !moore.i32): // 2 preds: ^bb0, ^bb8 + moore.return + ^bb2: // no predecessors + cf.br ^bb3(%2 : !moore.i32) + ^bb3(%5: !moore.i32): // 2 preds: ^bb2, ^bb6 + cf.br ^bb8 + ^bb4: // no predecessors + cf.br ^bb6 + ^bb5: // no predecessors + cf.br ^bb6 + ^bb6: // 2 preds: ^bb4, ^bb5 + %6 = moore.add %5, %1 : i32 + cf.br ^bb3(%6 : !moore.i32) + ^bb7: // no predecessors + %7 = moore.extract_ref %mem from 0 : > -> + moore.nonblocking_assign %7, %0 : l32 + cf.br ^bb8 + ^bb8: // 2 preds: ^bb3, ^bb7 + %8 = moore.add %4, %1 : i32 + cf.br ^bb1(%8 : !moore.i32) + } + %3 = moore.read %rdata_o : > + moore.output %3 : !moore.array<2 x l32> +} diff --git a/test/Dialect/Arc/basic-errors.mlir b/test/Dialect/Arc/basic-errors.mlir index 6fe538beef05..723a29ebd5ee 100644 --- a/test/Dialect/Arc/basic-errors.mlir +++ b/test/Dialect/Arc/basic-errors.mlir @@ -524,3 +524,42 @@ hw.module @vectorize(in %in0: i4, in %in1: i4, out out0: i4) { // expected-error @below {{state type must have a known bit width}} func.func @InvalidStateType(%arg0: !arc.state) + +// ----- + +// expected-error @below {{Cannot find declaration of initializer function 'MissingInitilaizer_initial'.}} +arc.model @MissingInitilaizer io !hw.modty<> initializer @MissingInitilaizer_initial { + ^bb0(%arg0: !arc.storage<42>): +} + +// ----- + +// expected-note @below {{Initializer declared here:}} +hw.module @NonFuncInitilaizer_initial() { +} + +// expected-error @below {{Referenced initializer must be a 'func.func' op.}} +arc.model @NonFuncInitilaizer io !hw.modty<> initializer @NonFuncInitilaizer_initial { + ^bb0(%arg0: !arc.storage<42>): +} + +// ----- + +// expected-note @below {{Initializer declared here:}} +func.func @IncorrectArg_initial(!arc.storage<24>) { + ^bb0(%arg0: !arc.storage<24>): + return +} + +// expected-error @below {{Arguments of initializer function must match arguments of model body.}} +arc.model @IncorrectArg io !hw.modty<> initializer @IncorrectArg_initial { + ^bb0(%arg0: !arc.storage<42>): +} + +// ----- + +hw.module @InvalidInitType(in %clock: !seq.clock, in %input: i7) { + %cst = hw.constant 0 : i8 + // expected-error @below {{failed to verify that types of initial arguments match result types}} + %res = arc.state @Bar(%input) clock %clock initial (%cst: i8) latency 1 : (i7) -> i7 +} diff --git a/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir b/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir index a80487779825..a9100f6b9430 100644 --- a/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir +++ b/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir @@ -1,4 +1,4 @@ -// RUN: circt-opt %s --arc-lower-clocks-to-funcs --verify-diagnostics +// RUN: circt-opt %s --arc-lower-clocks-to-funcs --split-input-file --verify-diagnostics arc.model @NonConstExternalValue io !hw.modty<> { ^bb0(%arg0: !arc.storage<42>): @@ -12,3 +12,31 @@ arc.model @NonConstExternalValue io !hw.modty<> { %1 = comb.sub %0, %0 : i9001 } } + +// ----- + +func.func @VictimInit(%arg0: !arc.storage<42>) { + return +} + +// expected-warning @below {{Existing model initializer 'VictimInit' will be overridden.}} +arc.model @ExistingInit io !hw.modty<> initializer @VictimInit { +^bb0(%arg0: !arc.storage<42>): + arc.initial {} +} + +// ----- + +// expected-error @below {{op containing multiple PassThroughOps cannot be lowered.}} +// expected-error @below {{op containing multiple InitialOps is currently unsupported.}} +arc.model @MultiInitAndPassThrough io !hw.modty<> { +^bb0(%arg0: !arc.storage<1>): + // expected-note @below {{Conflicting PassThroughOp:}} + arc.passthrough {} + // expected-note @below {{Conflicting InitialOp:}} + arc.initial {} + // expected-note @below {{Conflicting PassThroughOp:}} + arc.passthrough {} + // expected-note @below {{Conflicting InitialOp:}} + arc.initial {} +} diff --git a/test/Dialect/Arc/lower-clocks-to-funcs.mlir b/test/Dialect/Arc/lower-clocks-to-funcs.mlir index 4226706910ee..9dd7d8899965 100644 --- a/test/Dialect/Arc/lower-clocks-to-funcs.mlir +++ b/test/Dialect/Arc/lower-clocks-to-funcs.mlir @@ -14,7 +14,15 @@ // CHECK-NEXT: return // CHECK-NEXT: } -// CHECK-LABEL: arc.model @Trivial io !hw.modty<> { +// CHECK-LABEL: func.func @Trivial_initial(%arg0: !arc.storage<42>) { +// CHECK-NEXT: %true = hw.constant true +// CHECK-NEXT: %c1_i9002 = hw.constant 1 : i9002 +// CHECK-NEXT: %0 = comb.mux %true, %c1_i9002, %c1_i9002 : i9002 +// CHECK-NEXT: call @Trivial_passthrough(%arg0) : (!arc.storage<42>) -> () +// CHECK-NEXT: return +// CHECK-NEXT: } + +// CHECK-LABEL: arc.model @Trivial io !hw.modty<> initializer @Trivial_initial { // CHECK-NEXT: ^bb0(%arg0: !arc.storage<42>): // CHECK-NEXT: %true = hw.constant true // CHECK-NEXT: %false = hw.constant false @@ -36,6 +44,10 @@ arc.model @Trivial io !hw.modty<> { %c1_i9001 = hw.constant 1 : i9001 %0 = comb.mux %true, %c1_i9001, %c1_i9001 : i9001 } + arc.initial { + %c1_i9002 = hw.constant 1 : i9002 + %0 = comb.mux %true, %c1_i9002, %c1_i9002 : i9002 + } } //===----------------------------------------------------------------------===// diff --git a/test/Dialect/Arc/lower-state-errors.mlir b/test/Dialect/Arc/lower-state-errors.mlir new file mode 100644 index 000000000000..cd6135a3e072 --- /dev/null +++ b/test/Dialect/Arc/lower-state-errors.mlir @@ -0,0 +1,24 @@ +// RUN: circt-opt %s --arc-lower-state --split-input-file --verify-diagnostics + +arc.define @DummyArc(%arg0: i42) -> i42 { + arc.output %arg0 : i42 +} + +// expected-error @+1 {{Value cannot be used in initializer.}} +hw.module @argInit(in %clk: !seq.clock, in %input: i42) { + %0 = arc.state @DummyArc(%0) clock %clk initial (%input : i42) latency 1 : (i42) -> i42 +} + + +// ----- + + +arc.define @DummyArc(%arg0: i42) -> i42 { + arc.output %arg0 : i42 +} + +hw.module @argInit(in %clk: !seq.clock, in %input: i42) { + // expected-error @+1 {{Value cannot be used in initializer.}} + %0 = arc.state @DummyArc(%0) clock %clk latency 1 : (i42) -> i42 + %1 = arc.state @DummyArc(%1) clock %clk initial (%0 : i42) latency 1 : (i42) -> i42 +} diff --git a/test/Dialect/Arc/lower-state.mlir b/test/Dialect/Arc/lower-state.mlir index b312bb115c8d..87f3e19cd6c6 100644 --- a/test/Dialect/Arc/lower-state.mlir +++ b/test/Dialect/Arc/lower-state.mlir @@ -366,3 +366,41 @@ hw.module @adder(in %clock : i1, in %a : i32, in %b : i32, out c : i32) { // CHECK-NEXT: %[[RESULT:.+]] = func.call @func(%6, %7) : (i32, i32) -> i32 hw.output %1 : i32 } + +// CHECK-LABEL: arc.model @InitializedStates +hw.module @InitializedStates(in %clk: !seq.clock, in %reset: i1, in %input: i42) { + +// CHECK: [[ST1:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST2:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST3:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST4:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST5:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state + +// CHECK: arc.initial { + + %csta = hw.constant 1 : i42 + %cstb = hw.constant 10 : i42 + %cstc = hw.constant 100 : i42 + %cstd = hw.constant 1000 : i42 + %add = comb.add bin %cstb, %cstc, %csta : i42 + %mul = comb.mul bin %add, %csta : i42 + + // CHECK-NEXT: [[CSTD:%.+]] = hw.constant 1000 : i42 + // CHECK-NEXT: arc.state_write [[ST1]] = [[CSTD]] : + %0 = arc.state @DummyArc(%input) clock %clk initial (%cstd : i42) latency 1 : (i42) -> i42 + + // CHECK-DAG: [[CSTA:%.+]] = hw.constant 1 : i42 + // CHECK-DAG: [[CSTB:%.+]] = hw.constant 10 : i42 + // CHECK-DAG: [[CSTC:%.+]] = hw.constant 100 : i42 + // CHECK-DAG: [[ADD:%.+]] = comb.add bin [[CSTB]], [[CSTC]], [[CSTA]] : i42 + // CHECK-DAG: [[MUL:%.+]] = comb.mul bin [[ADD]], [[CSTA]] : i42 + + // CHECK: arc.state_write [[ST2]] = [[MUL]] : + %1 = arc.state @DummyArc(%0) clock %clk initial (%mul : i42) latency 1 : (i42) -> i42 + // CHECK-NEXT: arc.state_write [[ST3]] = [[CSTB]] : + %2 = arc.state @DummyArc(%1) clock %clk reset %reset initial (%cstb : i42) latency 1 : (i42) -> i42 + // CHECK-DAG: arc.state_write [[ST4]] = [[CSTB]] : + // CHECK-DAG: arc.state_write [[ST5]] = [[ADD]] : + %3, %4 = arc.state @DummyArc2(%2) clock %clk initial (%cstb, %add : i42, i42) latency 1 : (i42) -> (i42, i42) +// CHECK: } +} diff --git a/test/Dialect/DC/materialize-forks-sinks.mlir b/test/Dialect/DC/materialize-forks-sinks.mlir index 79590c654549..9dfa3e4df040 100644 --- a/test/Dialect/DC/materialize-forks-sinks.mlir +++ b/test/Dialect/DC/materialize-forks-sinks.mlir @@ -54,4 +54,4 @@ func.func @testUnusedArg(%t: !dc.token, %v : !dc.value) -> () { // CHECK: } func.func @testForkOfValue(%v : !dc.value) -> (!dc.value, !dc.value) { return %v, %v : !dc.value, !dc.value -} \ No newline at end of file +} diff --git a/test/Dialect/FIRRTL/annotations.mlir b/test/Dialect/FIRRTL/annotations.mlir index bc0da5dd8d22..d753d1905bf0 100644 --- a/test/Dialect/FIRRTL/annotations.mlir +++ b/test/Dialect/FIRRTL/annotations.mlir @@ -1984,4 +1984,4 @@ firrtl.circuit "Top" attributes { // CHECK-SAME: (in %reset: !firrtl.asyncreset) attributes {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} // expected-warning @+1 {{'sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation' is deprecated, use 'circt.ExcludeFromFullResetAnnotation' instead}} firrtl.module @Top(in %reset: !firrtl.asyncreset) {} -} \ No newline at end of file +} diff --git a/test/Dialect/FIRRTL/check-recursive-instantiation-errors.mlir b/test/Dialect/FIRRTL/check-recursive-instantiation-errors.mlir new file mode 100644 index 000000000000..608b1f2cc8ab --- /dev/null +++ b/test/Dialect/FIRRTL/check-recursive-instantiation-errors.mlir @@ -0,0 +1,89 @@ +// RUN: circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-check-recursive-instantiation))' %s --verify-diagnostics --split-input-file + +firrtl.circuit "SelfLoop0" { + // expected-error @below {{recursive instantiation}} + firrtl.module @SelfLoop0() { + // expected-note @below {{SelfLoop0 instantiates SelfLoop0 here}} + firrtl.instance inst @SelfLoop0() + } +} + +// ----- + +firrtl.circuit "SelfLoop1" { + // expected-error @below {{recursive instantiation}} + firrtl.module @SelfLoop1() { + // expected-note @below {{SelfLoop1 instantiates SelfLoop1 here}} + firrtl.instance inst @SelfLoop1() + // expected-note @below {{SelfLoop1 instantiates SelfLoop1 here}} + firrtl.instance inst @SelfLoop1() + } +} + +// ----- + +firrtl.circuit "TwoLoops" { + // expected-error @below {{recursive instantiation}} + firrtl.module @TwoLoops() { + // expected-note @below {{TwoLoops instantiates TwoLoops here}} + firrtl.instance inst @TwoLoops() + firrtl.instance inst @OtherModule() + } + // expected-error @below {{recursive instantiation}} + firrtl.module @OtherModule() { + // expected-note @below {{OtherModule instantiates OtherModule here}} + firrtl.instance inst @OtherModule() + } +} + +// ----- + +firrtl.circuit "MutualLoop" { + firrtl.module @MutualLoop() { + firrtl.instance a @A() + } + firrtl.module @A() { + // expected-note @below {{A instantiates B here}} + firrtl.instance b @B() + } + // expected-error @below {{recursive instantiation}} + firrtl.module @B() { + // expected-note @below {{B instantiates A here}} + firrtl.instance a @A() + } +} + +// ----- + +// Should disallow recursive class instantiation. +firrtl.circuit "Classes" { + firrtl.module @Classes() { + firrtl.object @RecursiveClass() + } + + // expected-error @below {{recursive instantiation}} + firrtl.class @RecursiveClass() { + // expected-note @below {{RecursiveClass instantiates RecursiveClass here}} + %0 = firrtl.object @RecursiveClass() + } +} + +// ----- + +firrtl.circuit "A" { + firrtl.module @A() { + // expected-note @below {{A instantiates B here}} + firrtl.instance b @B() + } + firrtl.module @B() { + // expected-note @below {{B instantiates C here}} + firrtl.instance c @C() + // expected-note @below {{B instantiates A here}} + firrtl.instance a @A() + } + // expected-error @below {{recursive instantiation}} + firrtl.module @C() { + // expected-note @below {{C instantiates B here}} + firrtl.instance b @B() + } +} diff --git a/test/Dialect/FIRRTL/check-recursive-instantiation.mlir b/test/Dialect/FIRRTL/check-recursive-instantiation.mlir new file mode 100644 index 000000000000..c9ede2b247bb --- /dev/null +++ b/test/Dialect/FIRRTL/check-recursive-instantiation.mlir @@ -0,0 +1,6 @@ +// RUN: circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-check-recursive-instantiation))' %s | FileCheck %s + +// CHECK-LABEL: firrtl.circuit "NoLoop" +firrtl.circuit "NoLoop" { + firrtl.module @NoLoop() { } +} diff --git a/test/Dialect/FIRRTL/eliminate-wires.mlir b/test/Dialect/FIRRTL/eliminate-wires.mlir index ee4f8eddc085..c6a2e55e3ece 100644 --- a/test/Dialect/FIRRTL/eliminate-wires.mlir +++ b/test/Dialect/FIRRTL/eliminate-wires.mlir @@ -26,4 +26,4 @@ firrtl.circuit "TopLevel" { // CHECK-NEXT: %b = firrtl.node %[[inv]] : !firrtl.uint<3> // CHECK-NEXT: %a = firrtl.node %b : !firrtl.uint<3> } -} \ No newline at end of file +} diff --git a/test/Dialect/FIRRTL/errors.mlir b/test/Dialect/FIRRTL/errors.mlir index ebb1c5dbaf74..02905986d31b 100644 --- a/test/Dialect/FIRRTL/errors.mlir +++ b/test/Dialect/FIRRTL/errors.mlir @@ -285,16 +285,6 @@ firrtl.circuit "Foo" { // ----- -firrtl.circuit "Foo" { - // expected-note @+1 {{containing module declared here}} - firrtl.module @Foo() { - // expected-error @+1 {{'firrtl.instance' op is a recursive instantiation of its containing module}} - firrtl.instance "" @Foo() - } -} - -// ----- - firrtl.circuit "Foo" { // expected-note @+1 {{original module declared here}} firrtl.module @Callee(in %arg0: !firrtl.uint<1>) { } diff --git a/test/Dialect/FIRRTL/infer-resets-errors.mlir b/test/Dialect/FIRRTL/infer-resets-errors.mlir index 15c45ef61f18..b43520ccb901 100644 --- a/test/Dialect/FIRRTL/infer-resets-errors.mlir +++ b/test/Dialect/FIRRTL/infer-resets-errors.mlir @@ -280,4 +280,4 @@ firrtl.circuit "UninferredRefReset" { firrtl.circuit "Top" { // expected-error @+1 {{'FullResetAnnotation' requires resetType == 'sync' | 'async', but got resetType == "potato"}} firrtl.module @Top(in %reset: !firrtl.asyncreset) attributes {portAnnotations = [[{class = "circt.FullResetAnnotation", resetType = "potato"}]]} {} -} \ No newline at end of file +} diff --git a/test/Dialect/FIRRTL/lower-classes.mlir b/test/Dialect/FIRRTL/lower-classes.mlir index 0e2dc31c4107..34780a54581b 100644 --- a/test/Dialect/FIRRTL/lower-classes.mlir +++ b/test/Dialect/FIRRTL/lower-classes.mlir @@ -492,3 +492,16 @@ firrtl.circuit "OwningModulePrefix" { firrtl.path reference distinct[0]<> } } + +// CHECK-LABEL: firrtl.circuit "PathTargetReplaced" +firrtl.circuit "PathTargetReplaced" { + // CHECK: hw.hierpath private [[NLA:@.+]] [@PathTargetReplaced::[[SYM:@.+]]] + firrtl.module @PathTargetReplaced() { + // CHECK: firrtl.instance replaced sym [[SYM]] + firrtl.instance replaced {annotations = [{class = "circt.tracker", id = distinct[0]<>}]} @WillBeReplaced(out output: !firrtl.integer) + // CHECK: om.path_create instance %basepath [[NLA]] + %path = firrtl.path instance distinct[0]<> + } + firrtl.module private @WillBeReplaced(out %output: !firrtl.integer) { + } +} diff --git a/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir b/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir index 9e91432796ce..cb7ffd83e63b 100644 --- a/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir +++ b/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir @@ -11,13 +11,37 @@ firrtl.circuit "FixupEICGWrapper" { annotations = [{class = "firrtl.transforms.DedupGroupAnnotation", group = "foo"}]} // CHECK: FixupEICGWrapper - firrtl.module @FixupEICGWrapper(in %clock: !firrtl.clock, in %en: !firrtl.uint<1>) { + firrtl.module @FixupEICGWrapper(in %clock: !firrtl.clock, in %test_en: !firrtl.uint<1>, in %en: !firrtl.uint<1>) { // CHECK-NOEICG: firrtl.instance // CHECK-EICG-NOT: firrtl.instance - // CHECK-EICG: firrtl.int.generic "circt_clock_gate" + // CHECK-EICG-DAG: firrtl.matchingconnect %[[CLK:.+]], %clock : !firrtl.clock + // CHECK-EICG-DAG: firrtl.matchingconnect %[[TEST_EN:.+]], %test_en : !firrtl.uint<1> + // CHECK-EICG-DAG: firrtl.matchingconnect %[[EN:.+]], %en : !firrtl.uint<1> + // CHECK-EICG-DAG: %[[CLK]] = firrtl.wire : !firrtl.clock + // CHECK-EICG-DAG: %[[TEST_EN]] = firrtl.wire : !firrtl.uint<1> + // CHECK-EICG-DAG: %[[EN]] = firrtl.wire : !firrtl.uint<1> + // CHECK-EICG-DAG: %3 = firrtl.int.generic "circt_clock_gate" %[[CLK]], %[[EN]], %[[TEST_EN]] : (!firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.clock %ckg_in, %ckg_test_en, %ckg_en, %ckg_out = firrtl.instance ckg @LegacyClockGate(in in: !firrtl.clock, in test_en: !firrtl.uint<1>, in en: !firrtl.uint<1>, out out: !firrtl.clock) firrtl.matchingconnect %ckg_in, %clock : !firrtl.clock - firrtl.matchingconnect %ckg_test_en, %en : !firrtl.uint<1> + firrtl.matchingconnect %ckg_test_en, %test_en : !firrtl.uint<1> + firrtl.matchingconnect %ckg_en, %en : !firrtl.uint<1> + } +} + +// ----- + +firrtl.circuit "EICGWrapperPortName" { + firrtl.extmodule @BadClockGate(in in: !firrtl.clock, + // expected-error @below {{expected port named 'test_en'}} + in en: !firrtl.uint<1>, + in test_en: !firrtl.uint<1>, + out out: !firrtl.clock) + attributes { defname = "EICG_wrapper" } + + firrtl.module @EICGWrapperPortName(in %clock: !firrtl.clock, in %test_en: !firrtl.uint<1>, in %en: !firrtl.uint<1>) { + %ckg_in, %ckg_en, %ckg_test_en, %ckg_out = firrtl.instance ckg @BadClockGate(in in: !firrtl.clock, in en: !firrtl.uint<1>, in test_en: !firrtl.uint<1>, out out: !firrtl.clock) + firrtl.matchingconnect %ckg_in, %clock : !firrtl.clock + firrtl.matchingconnect %ckg_test_en, %test_en : !firrtl.uint<1> firrtl.matchingconnect %ckg_en, %en : !firrtl.uint<1> } } diff --git a/test/Dialect/FIRRTL/lower-layers.mlir b/test/Dialect/FIRRTL/lower-layers.mlir index 8ff13ed3a649..74b6f7af71f7 100644 --- a/test/Dialect/FIRRTL/lower-layers.mlir +++ b/test/Dialect/FIRRTL/lower-layers.mlir @@ -717,3 +717,22 @@ firrtl.circuit "Foo" attributes { // // CHECK: sv.verbatim // CHECK-SAME: #hw.output_file<"testbench{{/|\\\\}}layers_Foo_A.sv", excludeFromFileList> + + +// ----- +// Check that we correctly implement the verilog header and footer for B. + +firrtl.circuit "Foo" { + firrtl.layer @A bind { + firrtl.layer @X bind {} + } + firrtl.layer @B bind {} + firrtl.module @Foo() {} +} + +// CHECK: firrtl.circuit "Foo" { +// CHECK: sv.verbatim "`ifndef layers_Foo_B\0A`define layers_Foo_B" {output_file = #hw.output_file<"layers_Foo_B.sv", excludeFromFileList>} +// CHECK: firrtl.module @Foo() { +// CHECK: } +// CHECK: sv.verbatim "`endif // layers_Foo_B" {output_file = #hw.output_file<"layers_Foo_B.sv", excludeFromFileList>} +// CHECK: } diff --git a/test/Dialect/FIRRTL/parse-basic.fir b/test/Dialect/FIRRTL/parse-basic.fir index 6bfd861f650f..89e60d66f03f 100644 --- a/test/Dialect/FIRRTL/parse-basic.fir +++ b/test/Dialect/FIRRTL/parse-basic.fir @@ -1921,3 +1921,41 @@ circuit GenericIntrinsics: ; CHECK-NEXT: %[[SZ:.+]] = firrtl.int.generic "circt_isX" ; CHECK-NEXT: "circt_verif_assert" %[[SZ]] intrinsic(circt_verif_assert, intrinsic(circt_isX: UInt<1>, data)) + +;// ----- +FIRRTL version 4.0.0 +; CHECK-LABEL: circuit "Foo" +circuit Foo: + + ; CHECK-LABEL: firrtl.module @Foo(in %data: !firrtl.uint<32>, in %c: !firrtl.uint<1>, out %out: !firrtl.uint<32>) + public module Foo: + input data : UInt<32> + input c : UInt<1> + output out : UInt<32> + + when c: + node add1 = add(data, UInt<32>(1)) + out <= add1 + else: + out <= data + + ; CHECK-LABEL: firrtl.module @FooTest(in %s_foo_c: !firrtl.uint<1>, in %s_foo_data: !firrtl.uint<32>) + public module FooTest: + input s_foo_c : UInt<1> + input s_foo_data : UInt<32> + + ; CHECK-NEXT: %foo_data, %foo_c, %foo_out = firrtl.instance foo interesting_name @Foo(in data: !firrtl.uint<32>, in c: !firrtl.uint<1>, out out: !firrtl.uint<32>) + inst foo of Foo + ; CHECK-NEXT: firrtl.matchingconnect %foo_c, %s_foo_c : !firrtl.uint<1> + connect foo.c, s_foo_c + ; CHECK-NEXT: firrtl.matchingconnect %foo_data, %s_foo_data : !firrtl.uint<32> + connect foo.data, s_foo_data + ; CHECK-NEXT: %0 = firrtl.eq %foo_out, %s_foo_data : (!firrtl.uint<32>, !firrtl.uint<32>) -> !firrtl.uint<1> + ; CHECK-NEXT: %1 = firrtl.int.generic "circt_ltl_implication" %s_foo_c, %0 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<1> + ; CHECK-NEXT: firrtl.int.generic "circt_verif_assert" %1 : (!firrtl.uint<1>) -> () + intrinsic(circt_verif_assert, intrinsic(circt_ltl_implication : UInt<1>, s_foo_c, eq(foo.out, s_foo_data))) + + ; CHECK: firrtl.formal @testFormal of @FooTest bound 20 + formal testFormal of FooTest, bound = 20 + + diff --git a/test/Dialect/FIRRTL/passive-wire.mlir b/test/Dialect/FIRRTL/passive-wire.mlir index 4a7c2e301562..a728295e4359 100644 --- a/test/Dialect/FIRRTL/passive-wire.mlir +++ b/test/Dialect/FIRRTL/passive-wire.mlir @@ -42,4 +42,4 @@ firrtl.circuit "TopLevel" { // CHECK: firrtl.ref.define %b, %0 : !firrtl.probe, 2>>> } -} \ No newline at end of file +} diff --git a/test/Dialect/FIRRTL/specialize-layers.mlir b/test/Dialect/FIRRTL/specialize-layers.mlir index 102e49856062..c6f0a042a09e 100644 --- a/test/Dialect/FIRRTL/specialize-layers.mlir +++ b/test/Dialect/FIRRTL/specialize-layers.mlir @@ -115,6 +115,21 @@ firrtl.circuit "LayerDisableInARow" attributes { firrtl.extmodule @LayerDisableInARow() } +// CHECK: firrtl.circuit "LayerblockEnableNestedChildren" +// CHECK-NOT: firrtl.layer +firrtl.circuit "LayerblockEnableNestedChildren" attributes { + enable_layers = [@A, @A::@B, @A::@C] +} { + firrtl.layer @A bind { + firrtl.layer @B bind { + } + firrtl.layer @C bind { + } + } + firrtl.module @LayerblockEnableNestedChildren() { + } +} + //===----------------------------------------------------------------------===// // LayerBlock Specialization //===----------------------------------------------------------------------===// @@ -202,7 +217,7 @@ firrtl.circuit "LayerblockDisableB" attributes { } } } - + //===----------------------------------------------------------------------===// // Default Specialization //===----------------------------------------------------------------------===// @@ -254,7 +269,7 @@ firrtl.circuit "LayerDefaultDisable" attributes { //===----------------------------------------------------------------------===// // CHECK-LABEL: firrtl.circuit "EnableLayerB" -firrtl.circuit "EnableLayerB" +firrtl.circuit "EnableLayerB" attributes { enable_layers = [@A::@B] } { @@ -266,13 +281,13 @@ attributes { firrtl.option @Option { firrtl.option_case @Option } - + firrtl.module public @EnableLayerB() { firrtl.layerblock @A { firrtl.layerblock @A::@B { firrtl.layerblock @A::@B::@C { // CHECK: firrtl.instance instance {layers = [@A, @A, @A::@B_C]} @ExtModule() - firrtl.instance instance {layers = [@A, @A::@B, @A::@B::@C]} @ExtModule() + firrtl.instance instance {layers = [@A, @A::@B, @A::@B::@C]} @ExtModule() // CHECK: firrtl.instance_choice instance_choice // CHECK-SAME: {layers = [@A, @A, @A::@B_C]} @ExtModule firrtl.instance_choice instance_choice @@ -343,8 +358,8 @@ firrtl.circuit "ProbeOpsEnableA" attributes { } // CHECK: firrtl.instance instance @ExtModule(out out: !firrtl.probe>) - firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) - + firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) + // CHECK: firrtl.instance_choice instance_choice @ExtModule // CHECK-SAME: alternatives @Option { @Option -> @ExtModule } // CHECK-SAME: (out out: !firrtl.probe>) @@ -352,7 +367,7 @@ firrtl.circuit "ProbeOpsEnableA" attributes { alternatives @Option { @Option -> @ExtModule } (out out : !firrtl.probe, @A>) } - + firrtl.extmodule @ExtModule(out out : !firrtl.probe, @A>) } @@ -392,7 +407,7 @@ firrtl.circuit "ProbeOpsDisableA" attributes { } // CHECK: firrtl.instance instance @ExtModule() - firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) + firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) // CHECK: firrtl.instance_choice instance_choice @ExtModule // CHECK-SAME: alternatives @Option { @Option -> @ExtModule } // CHECK-SAME: () @@ -429,13 +444,13 @@ firrtl.circuit "HierPathDelete" attributes { firrtl.module @HierPathDelete() { firrtl.instance middle sym @middle @Middle() } - + firrtl.module @Middle() { firrtl.layerblock @Layer { firrtl.instance leaf sym @leaf @Leaf() } } - + firrtl.extmodule @Leaf() // CHECK-NOT: hw.hierpath private @DeletedPath [@Deleted] @@ -463,13 +478,13 @@ firrtl.circuit "HierPathDelete2" attributes { firrtl.instance middle sym @middle @Middle() } } - + firrtl.module @Middle() { firrtl.layerblock @Layer { firrtl.instance leaf sym @leaf @Leaf() } } - + firrtl.extmodule @Leaf() // CHECK-NOT: hw.hierpath private @DeletedPath [@Deleted] @@ -495,11 +510,11 @@ firrtl.circuit "Annotations" attributes { firrtl.instance leaf sym @leaf @Leaf(in in : !firrtl.uint<1>) } } - + // CHECK: firrtl.module @Leaf(in %in: !firrtl.uint<1>) { firrtl.module @Leaf(in %in : !firrtl.uint<1> [{circt.nonlocal = @Path}]) attributes {annotations = [{circt.nonlocal = @Path}]} { - + // CHECK: %w = firrtl.wire : !firrtl.uint<1> %w = firrtl.wire {annotations = [{circt.nonlocal = @Path}]} : !firrtl.uint<1> diff --git a/test/Dialect/Handshake/split-merge.mlir b/test/Dialect/Handshake/split-merge.mlir index dd28703ca154..02ea3b69f185 100644 --- a/test/Dialect/Handshake/split-merge.mlir +++ b/test/Dialect/Handshake/split-merge.mlir @@ -59,4 +59,4 @@ handshake.func @m3(%in0 : i32, %in1 : i32, %in2 : i32) -> (i32) { handshake.func @m5(%in0 : i32, %in1 : i32, %in2 : i32, %in3 : i32, %in4 : i32) -> (i32) { %out = handshake.merge %in0, %in1, %in2, %in3, %in4 : i32 return %out : i32 -} \ No newline at end of file +} diff --git a/test/Dialect/LLHD/Canonicalization/probeCSE.mlir b/test/Dialect/LLHD/Canonicalization/probeCSE.mlir index c1b968830c0b..8568ec6ddf57 100644 --- a/test/Dialect/LLHD/Canonicalization/probeCSE.mlir +++ b/test/Dialect/LLHD/Canonicalization/probeCSE.mlir @@ -18,7 +18,8 @@ hw.module @checkPrbDceAndCseIn(inout %arg0 : i32, inout %arg1 : i32, inout %arg2 // CHECK-LABEL: @checkPrbDceButNotCse hw.module @checkPrbDceButNotCse(inout %arg0 : i32, inout %arg1 : i32, inout %arg2 : i32) { - // CHECK-NEXT: llhd.process + %prb = llhd.prb %arg0 : !hw.inout + // CHECK: llhd.process llhd.process { // CHECK-NEXT: llhd.constant_time %time = llhd.constant_time <0ns, 1d, 0e> @@ -26,7 +27,7 @@ hw.module @checkPrbDceButNotCse(inout %arg0 : i32, inout %arg1 : i32, inout %arg // CHECK-NEXT: [[P1:%.*]] = llhd.prb %1 = llhd.prb %arg0 : !hw.inout // CHECK-NEXT: llhd.wait - llhd.wait (%arg0: !hw.inout), ^bb1 + llhd.wait (%prb: i32), ^bb1 // CHECK-NEXT: ^bb1: ^bb1: // CHECK-NEXT: [[P2:%.*]] = llhd.prb diff --git a/test/Dialect/LLHD/IR/basic.mlir b/test/Dialect/LLHD/IR/basic.mlir index 96d768958628..618d1a05b0db 100644 --- a/test/Dialect/LLHD/IR/basic.mlir +++ b/test/Dialect/LLHD/IR/basic.mlir @@ -90,22 +90,22 @@ func.func @check_store(%int : !llhd.ptr, %intC : i32 , %array : !llhd.ptr - // CHECK-NEXT: %{{.*}} = llhd.sig "sigTup" %[[TUP]] : !hw.struct - %sigTup = llhd.sig "sigTup" %tup : !hw.struct + // CHECK-NEXT: %sigTup = llhd.sig %[[TUP]] : !hw.struct + %sigTup = llhd.sig %tup : !hw.struct // CHECK-NEXT: %[[ARRAY:.*]] = hw.array_create %array = hw.array_create %cI1, %cI1 : i1 - // CHECK-NEXT: %{{.*}} = llhd.sig "sigArray" %[[ARRAY]] : !hw.array<2xi1> - %sigArray = llhd.sig "sigArray" %array : !hw.array<2xi1> + // CHECK-NEXT: %sigArray = llhd.sig %[[ARRAY]] : !hw.array<2xi1> + %sigArray = llhd.sig %array : !hw.array<2xi1> } // CHECK-LABEL: @checkPrb @@ -176,10 +176,14 @@ hw.module @check_wait_1 () { // CHECK: @check_wait_2(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { hw.module @check_wait_2 (inout %arg0 : i64, inout %arg1 : i1) { + // CHECK: [[PRB0:%.+]] = llhd.prb %arg0 + %prb0 = llhd.prb %arg0 : !hw.inout + // CHECK: [[PRB1:%.+]] = llhd.prb %arg1 + %prb1 = llhd.prb %arg1 : !hw.inout // CHECK-NEXT: llhd.process llhd.process { - // CHECK-NEXT: llhd.wait (%[[ARG0]], %[[ARG1]] : !hw.inout, !hw.inout), ^[[BB:.*]](%[[ARG1]] : !hw.inout) - llhd.wait (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1(%arg1 : !hw.inout) + // CHECK-NEXT: llhd.wait ([[PRB0]], [[PRB1]] : i64, i1), ^[[BB:.*]](%[[ARG1]] : !hw.inout) + llhd.wait (%prb0, %prb1 : i64, i1), ^bb1(%arg1 : !hw.inout) // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout): ^bb1(%a: !hw.inout): llhd.halt @@ -188,14 +192,28 @@ hw.module @check_wait_2 (inout %arg0 : i64, inout %arg1 : i1) { // CHECK: hw.module @check_wait_3(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { hw.module @check_wait_3 (inout %arg0 : i64, inout %arg1 : i1) { + // CHECK: [[PRB0:%.+]] = llhd.prb %arg0 + %prb0 = llhd.prb %arg0 : !hw.inout + // CHECK: [[PRB1:%.+]] = llhd.prb %arg1 + %prb1 = llhd.prb %arg1 : !hw.inout // CHECK-NEXT: llhd.process llhd.process { // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time %time = llhd.constant_time #llhd.time<0ns, 0d, 0e> - // CHECK-NEXT: llhd.wait for %[[TIME]], (%[[ARG0]], %[[ARG1]] : !hw.inout, !hw.inout), ^[[BB:.*]](%[[ARG1]], %[[ARG0]] : !hw.inout, !hw.inout) - llhd.wait for %time, (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1(%arg1, %arg0 : !hw.inout, !hw.inout) + // CHECK-NEXT: llhd.wait for %[[TIME]], ([[PRB0]], [[PRB1]] : i64, i1), ^[[BB:.*]](%[[ARG1]], %[[ARG0]] : !hw.inout, !hw.inout) + llhd.wait for %time, (%prb0, %prb1 : i64, i1), ^bb1(%arg1, %arg0 : !hw.inout, !hw.inout) // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout, %[[B:.*]]: !hw.inout): ^bb1(%a: !hw.inout, %b: !hw.inout): llhd.halt } } + +// CHECK-LABEL: @FinalProcess +hw.module @FinalProcess () { + // CHECK-NEXT: llhd.final { + // CHECK-NEXT: llhd.halt + // CHECK-NEXT: } + llhd.final { + llhd.halt + } +} diff --git a/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir b/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir index 9cdf16d1f013..3ab23494b85a 100644 --- a/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir +++ b/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir @@ -123,6 +123,7 @@ hw.module @check_blockarg(inout %sig : i32) { // CHECK-LABEL: @loop // CHECK-SAME: (inout %[[VAL_0:.*]] : i2) +// CHECK: [[PRB:%.+]] = llhd.prb %in_i // CHECK: llhd.process // CHECK: %[[VAL_1:.*]] = hw.constant 0 : i32 // CHECK: %[[VAL_2:.*]] = hw.constant 2 : i32 @@ -138,7 +139,7 @@ hw.module @check_blockarg(inout %sig : i32) { // CHECK: %[[VAL_8:.*]] = llhd.prb %[[VAL_0]] : !hw.inout // CHECK: cf.cond_br %[[VAL_7]], ^[[BB4:.+]], ^[[BB3:.+]] // CHECK: ^[[BB3]]: -// CHECK: llhd.wait (%[[VAL_0]] : !hw.inout), ^[[BB1]] +// CHECK: llhd.wait ([[PRB]] : i2), ^[[BB1]] // CHECK: ^[[BB4]]: // CHECK: %[[VAL_9:.*]] = llhd.load %[[VAL_5]] : !llhd.ptr // CHECK: %[[VAL_10:.*]] = comb.add %[[VAL_9]], %[[VAL_4]] : i32 @@ -146,6 +147,7 @@ hw.module @check_blockarg(inout %sig : i32) { // CHECK: cf.br ^[[BB2]] // CHECK: } hw.module @loop(inout %in_i : i2) { + %prb0 = llhd.prb %in_i : !hw.inout llhd.process { // TR: -1 cf.br ^body @@ -162,7 +164,7 @@ hw.module @loop(inout %in_i : i2) { cf.cond_br %2, ^loop_continue, ^check ^check: // TR: 1 - llhd.wait (%in_i : !hw.inout), ^body + llhd.wait (%prb0 : i2), ^body ^loop_continue: // TR: 1 %3 = hw.constant 0 : i2 @@ -177,6 +179,8 @@ hw.module @loop(inout %in_i : i2) { // CHECK-LABEL: @complicated // CHECK-SAME: (inout %[[VAL_0:.*]] : i1, inout %[[VAL_1:.*]] : i1, inout %[[VAL_2:.*]] : i1, inout %[[VAL_3:.*]] : i1, inout %[[VAL_4:.*]] : i1) +// CHECK: [[PRB_CLK:%.+]] = llhd.prb %[[VAL_1]] +// CHECK: [[PRB_RST:%.+]] = llhd.prb %[[VAL_0]] // CHECK: llhd.process // CHECK: %[[ALLSET:.*]] = hw.constant true // CHECK: %[[VAL_5:.*]] = hw.constant false @@ -191,7 +195,7 @@ hw.module @loop(inout %in_i : i2) { // CHECK: %[[VAL_10:.*]] = llhd.prb %[[VAL_0]] : !hw.inout // CHECK: %[[VAL_11:.*]] = comb.icmp eq %[[VAL_9]], %[[VAL_5]] : i1 // CHECK: %[[VAL_12:.*]] = comb.icmp ne %[[VAL_10]], %[[VAL_5]] : i1 -// CHECK: llhd.wait (%[[VAL_1]], %[[VAL_0]] : !hw.inout, !hw.inout), ^[[BB3:.+]] +// CHECK: llhd.wait ([[PRB_CLK]], [[PRB_RST]] : i1, i1), ^[[BB3:.+]] // CHECK: ^[[BB3]]: // CHECK: %[[VAL_13:.*]] = llhd.prb %[[VAL_3]] : !hw.inout // CHECK: llhd.store %[[VAL_8]], %[[VAL_13]] : !llhd.ptr @@ -221,6 +225,8 @@ hw.module @loop(inout %in_i : i2) { // CHECK: cf.br ^[[BB1]] // CHECK: } hw.module @complicated(inout %rst_ni: i1, inout %clk_i: i1, inout %async_ack_i: i1, inout %ack_src_q: i1, inout %ack_q: i1) { + %prb_clk = llhd.prb %clk_i : !hw.inout + %prb_rst = llhd.prb %rst_ni : !hw.inout llhd.process { %allset = hw.constant 1 : i1 // TR: -1 @@ -234,7 +240,7 @@ hw.module @complicated(inout %rst_ni: i1, inout %clk_i: i1, inout %async_ack_i: // TR: 2 %clk_i_prb = llhd.prb %clk_i : !hw.inout %rst_ni_prb = llhd.prb %rst_ni : !hw.inout - llhd.wait (%clk_i, %rst_ni : !hw.inout, !hw.inout), ^check + llhd.wait (%prb_clk, %prb_rst : i1, i1), ^check ^check: // TR: 0 %2 = llhd.prb %ack_src_q : !hw.inout diff --git a/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir b/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir index 50cc527fce9c..2c259e61e333 100644 --- a/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir +++ b/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir @@ -262,6 +262,7 @@ hw.module @multiple_store_one_block() { // CHECK-LABEL: @loop // CHECK-SAME: (inout %[[VAL_0:.*]] : i2) +// CHECK: [[PRB:%.+]] = llhd.prb %[[VAL_0]] // CHECK: cf.br ^bb1 // CHECK: ^bb1: // CHECK: %[[VAL_1:.*]] = hw.constant 0 : i32 @@ -271,7 +272,7 @@ hw.module @multiple_store_one_block() { // CHECK: %[[VAL_4:.*]] = comb.icmp ult %[[VAL_2]], %[[VAL_3]] : i32 // CHECK: cf.cond_br %[[VAL_4]], ^bb4, ^bb3 // CHECK: ^bb3: -// CHECK: llhd.wait (%[[VAL_0]] : !hw.inout), ^bb1 +// CHECK: llhd.wait ([[PRB]] : i2), ^bb1 // CHECK: ^bb4: // CHECK: %[[VAL_5:.*]] = hw.constant 0 : i2 // CHECK: %[[VAL_6:.*]] = hw.constant 1 : i32 @@ -279,6 +280,7 @@ hw.module @multiple_store_one_block() { // CHECK: cf.br ^bb2(%[[VAL_7]] : i32) // CHECK: } hw.module @loop(inout %in_i : i2) { + %prb = llhd.prb %in_i : !hw.inout llhd.process { cf.br ^body ^body: @@ -291,7 +293,7 @@ hw.module @loop(inout %in_i : i2) { %2 = comb.icmp ult %i_ld, %1 : i32 cf.cond_br %2, ^loop_continue, ^check ^check: - llhd.wait (%in_i : !hw.inout), ^body + llhd.wait (%prb : i2), ^body ^loop_continue: %3 = hw.constant 0 : i2 %5 = hw.constant 1 : i32 diff --git a/test/Dialect/LLHD/Transforms/processLowering.mlir b/test/Dialect/LLHD/Transforms/processLowering.mlir index c29335419820..8420c3cb80e4 100644 --- a/test/Dialect/LLHD/Transforms/processLowering.mlir +++ b/test/Dialect/LLHD/Transforms/processLowering.mlir @@ -23,31 +23,41 @@ hw.module @simpleWait() { // Check wait with observing probed signals // CHECK-LABEL: hw.module @prbAndWait hw.module @prbAndWait(inout %arg0 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout llhd.process { // CHECK-NEXT: %{{.*}} = llhd.prb // CHECK-NEXT: hw.output cf.br ^bb1 ^bb1: %0 = llhd.prb %arg0 : !hw.inout - llhd.wait (%arg0 : !hw.inout), ^bb1 + llhd.wait (%1 : i64), ^bb1 } } // Check wait with observing probed signals // CHECK-LABEL: hw.module @prbAndWaitMoreObserved hw.module @prbAndWaitMoreObserved(inout %arg0 : i64, inout %arg1 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout + // CHECK-NEXT: %{{.*}} = llhd.prb + %2 = llhd.prb %arg1 : !hw.inout llhd.process { // CHECK-NEXT: %{{.*}} = llhd.prb // CHECK-NEXT: hw.output cf.br ^bb1 ^bb1: %0 = llhd.prb %arg0 : !hw.inout - llhd.wait (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1 + llhd.wait (%1, %2 : i64, i64), ^bb1 } } // CHECK-LABEL: hw.module @muxedSignal hw.module @muxedSignal(inout %arg0 : i64, inout %arg1 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout + // CHECK-NEXT: %{{.*}} = llhd.prb + %2 = llhd.prb %arg1 : !hw.inout llhd.process { cf.br ^bb1 ^bb1: @@ -58,28 +68,32 @@ hw.module @muxedSignal(inout %arg0 : i64, inout %arg1 : i64) { %cond = hw.constant true %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout %0 = llhd.prb %sig : !hw.inout - llhd.wait (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1 + llhd.wait (%1, %2 : i64, i64), ^bb1 } } // CHECK-LABEL: hw.module @muxedSignal2 hw.module @muxedSignal2(inout %arg0 : i64, inout %arg1 : i64) { + // CHECK-NEXT: %{{.*}} = hw.constant + // CHECK-NEXT: %{{.*}} = comb.mux + // CHECK-NEXT: %{{.*}} = llhd.prb + %cond = hw.constant true + %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout + %0 = llhd.prb %sig : !hw.inout llhd.process { cf.br ^bb1 ^bb1: - // CHECK-NEXT: %{{.*}} = hw.constant - // CHECK-NEXT: %{{.*}} = comb.mux - // CHECK-NEXT: %{{.*}} = llhd.prb + // CHECK-NEXT: comb.and + %1 = comb.and %0, %0 : i64 // CHECK-NEXT: hw.output - %cond = hw.constant true - %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout - %0 = llhd.prb %sig : !hw.inout - llhd.wait (%sig : !hw.inout), ^bb1 + llhd.wait (%0 : i64), ^bb1 } } // CHECK-LABEL: hw.module @partialSignal hw.module @partialSignal(inout %arg0 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout llhd.process { cf.br ^bb1 ^bb1: @@ -90,6 +104,6 @@ hw.module @partialSignal(inout %arg0 : i64) { %c = hw.constant 16 : i6 %sig = llhd.sig.extract %arg0 from %c : (!hw.inout) -> !hw.inout %0 = llhd.prb %sig : !hw.inout - llhd.wait (%arg0 : !hw.inout), ^bb1 + llhd.wait (%1 : i64), ^bb1 } } diff --git a/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir b/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir index 7e2a6dfe6d83..0ecf43626355 100644 --- a/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir +++ b/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir @@ -6,7 +6,7 @@ hw.module @prbAndWaitNotObserved(inout %arg0 : i64) { cf.br ^bb1 ^bb1: %0 = llhd.prb %arg0 : !hw.inout - // expected-error @+1 {{during process-lowering: the wait terminator is required to have all probed signals as arguments}} + // expected-error @+1 {{during process-lowering: the wait terminator is required to have values used in the process as arguments}} llhd.wait ^bb1 } } @@ -17,9 +17,10 @@ hw.module @prbAndWaitNotObserved(inout %arg0 : i64) { hw.module @blockArgumentsNotAllowed(inout %arg0 : i64) { // expected-error @+1 {{during process-lowering: the second block (containing the llhd.wait) is not allowed to have arguments}} llhd.process { - cf.br ^bb1(%arg0 : !hw.inout) - ^bb1(%a : !hw.inout): - llhd.wait ^bb1(%a : !hw.inout) + %prb = llhd.prb %arg0 : !hw.inout + cf.br ^bb1(%prb : i64) + ^bb1(%a : i64): + llhd.wait ^bb1(%a: i64) } } @@ -83,7 +84,7 @@ hw.module @muxedSignal(inout %arg0 : i64, inout %arg1 : i64, inout %arg2 : i1) { %cond = llhd.prb %arg2 : !hw.inout %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout %0 = llhd.prb %sig : !hw.inout - // expected-error @+1 {{during process-lowering: the wait terminator is required to have all probed signals as arguments}} - llhd.wait (%arg0, %arg2 : !hw.inout, !hw.inout), ^bb1 + // expected-error @+1 {{during process-lowering: the wait terminator is required to have values used in the process as arguments}} + llhd.wait (%cond : i1), ^bb1 } } diff --git a/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir b/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir index d6bce05e2164..fb5c8d03946c 100644 --- a/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir +++ b/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir @@ -9,36 +9,36 @@ hw.module @basic(in %cond: i1) { // CHECK: [[V1:%.+]] = llhd.constant_time <0ns, 0d, 1e> %0 = llhd.constant_time <0ns, 1d, 0e> %1 = llhd.constant_time <0ns, 0d, 1e> - // CHECK: [[V2:%.+]] = llhd.sig "a" - // CHECK: [[V3:%.+]] = llhd.sig "b" - // CHECK: [[V4:%.+]] = llhd.sig "c" - // CHECK: [[V5:%.+]] = llhd.sig "d" - // CHECK: [[V6:%.+]] = llhd.sig "e" - // CHECK: [[V7:%.+]] = llhd.sig "f" - // CHECK: [[V8:%.+]] = llhd.sig "g" - // CHECK: [[V9:%.+]] = llhd.sig "h" - // CHECK: [[V10:%.+]] = llhd.sig "i" - // CHECK: [[V11:%.+]] = llhd.sig "j" - // CHECK: [[V12:%.+]] = llhd.sig "k" - // CHECK: [[V13:%.+]] = llhd.sig "l" - // CHECK: [[V13_1:%.+]] = llhd.sig "m" - // CHECK: [[V13_2:%.+]] = llhd.sig "n" - // CHECK: [[V13_3:%.+]] = llhd.sig "o" - %2 = llhd.sig "a" %false : i1 - %3 = llhd.sig "b" %false : i1 - %4 = llhd.sig "c" %false : i1 - %5 = llhd.sig "d" %false : i1 - %6 = llhd.sig "e" %false : i1 - %7 = llhd.sig "f" %false : i1 - %8 = llhd.sig "g" %c0_i4 : i4 - %9 = llhd.sig "h" %c0_i4 : i4 - %10 = llhd.sig "i" %c0_i4 : i4 - %11 = llhd.sig "j" %false : i1 - %12 = llhd.sig "k" %c0_i5 : i5 - %13 = llhd.sig "l" %c0_i5 : i5 - %14 = llhd.sig "m" %c0_i5 : i5 - %15 = llhd.sig "n" %c0_i5 : i5 - %16 = llhd.sig "o" %c0_i5 : i5 + // CHECK: %c = llhd.sig + // CHECK: %d = llhd.sig + // CHECK: %e = llhd.sig + // CHECK: %f = llhd.sig + // CHECK: %g = llhd.sig + // CHECK: %h = llhd.sig + // CHECK: %k = llhd.sig + // CHECK: %l = llhd.sig + // CHECK: %m = llhd.sig + // CHECK: %n = llhd.sig + // CHECK: %o = llhd.sig + %c = llhd.sig %false : i1 + %d = llhd.sig %false : i1 + %e = llhd.sig %false : i1 + %f = llhd.sig %false : i1 + %g = llhd.sig %c0_i4 : i4 + %h = llhd.sig %c0_i4 : i4 + %k = llhd.sig %c0_i5 : i5 + %l = llhd.sig %c0_i5 : i5 + %m = llhd.sig %c0_i5 : i5 + %n = llhd.sig %c0_i5 : i5 + %o = llhd.sig %c0_i5 : i5 + + %prb_k = llhd.prb %k : !hw.inout + %prb_c = llhd.prb %c : !hw.inout + %prb_e = llhd.prb %e : !hw.inout + %prb_h = llhd.prb %h : !hw.inout + %prb_d = llhd.prb %d : !hw.inout + %prb_f = llhd.prb %f : !hw.inout + %prb_g = llhd.prb %g : !hw.inout // COM: Check that an auxillary block is created and all drives are moved to // COM: the exit block with the correct enable condition @@ -49,45 +49,45 @@ hw.module @basic(in %cond: i1) { // CHECK: ^[[BB1]]: ^bb1: // CHECK: llhd.wait ({{.*}}), ^[[BB2:.+]] - llhd.wait (%12, %4, %6, %9, %5, %7, %8 : !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout), ^bb2 + llhd.wait (%prb_k, %prb_c, %prb_e, %prb_h, %prb_d, %prb_f, %prb_g : i5, i1, i1, i4, i1, i1, i4), ^bb2 // CHECK: ^[[BB2]]: ^bb2: - // CHECK: [[V14:%.+]] = llhd.prb [[V12]] - // CHECK: [[V15:%.+]] = llhd.prb [[V4]] - // CHECK: [[V16:%.+]] = llhd.prb [[V6]] - // CHECK: [[V17:%.+]] = llhd.prb [[V9]] + // CHECK: [[V14:%.+]] = llhd.prb %k + // CHECK: [[V15:%.+]] = llhd.prb %c + // CHECK: [[V16:%.+]] = llhd.prb %e + // CHECK: [[V17:%.+]] = llhd.prb %h // CHECK: [[V18:%.+]] = comb.concat %false{{.*}}, [[V17]] : i1, i4 - // CHECK: [[V19:%.+]] = llhd.prb [[V5]] - // CHECK: [[V20:%.+]] = llhd.prb [[V7]] - // CHECK: [[V21:%.+]] = llhd.prb [[V12]] - // CHECK: [[V22:%.+]] = llhd.prb [[V8]] + // CHECK: [[V19:%.+]] = llhd.prb %d + // CHECK: [[V20:%.+]] = llhd.prb %f + // CHECK: [[V21:%.+]] = llhd.prb %k + // CHECK: [[V22:%.+]] = llhd.prb %g // CHECK: [[V23:%.+]] = comb.concat %false{{.*}}, [[V22]] : i1, i4 // CHECK: [[V24:%.+]] = comb.sub [[V21]], [[V23]] : i5 - // CHECK: [[V25:%.+]] = llhd.prb [[V12]] - // CHECK: [[V26:%.+]] = llhd.prb [[V8]] + // CHECK: [[V25:%.+]] = llhd.prb %k + // CHECK: [[V26:%.+]] = llhd.prb %g // CHECK: [[V27:%.+]] = comb.concat %false{{.*}}, [[V26]] : i1, i4 // CHECK: [[V28:%.+]] = comb.add [[V25]], [[V27]] : i5 // CHECK: cf.cond_br [[V15]], ^[[BB3:.+]], ^[[BB4:.+]] - %25 = llhd.prb %12 : !hw.inout - llhd.drv %12, %25 after %1 : !hw.inout - %26 = llhd.prb %4 : !hw.inout - %27 = llhd.prb %6 : !hw.inout - %28 = llhd.prb %9 : !hw.inout + %25 = llhd.prb %k : !hw.inout + llhd.drv %k, %25 after %1 : !hw.inout + %26 = llhd.prb %c : !hw.inout + %27 = llhd.prb %e : !hw.inout + %28 = llhd.prb %h : !hw.inout %29 = comb.concat %false, %28 : i1, i4 - %30 = llhd.prb %5 : !hw.inout - %31 = llhd.prb %7 : !hw.inout - %32 = llhd.prb %12 : !hw.inout - %33 = llhd.prb %8 : !hw.inout + %30 = llhd.prb %d : !hw.inout + %31 = llhd.prb %f : !hw.inout + %32 = llhd.prb %k : !hw.inout + %33 = llhd.prb %g : !hw.inout %34 = comb.concat %false, %33 : i1, i4 %35 = comb.sub %32, %34 : i5 - %36 = llhd.prb %12 : !hw.inout - %37 = llhd.prb %8 : !hw.inout + %36 = llhd.prb %k : !hw.inout + %37 = llhd.prb %g : !hw.inout %38 = comb.concat %false, %37 : i1, i4 %39 = comb.add %36, %38 : i5 cf.cond_br %26, ^bb3, ^bb4 // CHECK: ^[[BB3]]: ^bb3: - llhd.drv %13, %c0_i5 after %1 if %cond : !hw.inout + llhd.drv %l, %c0_i5 after %1 if %cond : !hw.inout // CHECK: cf.br ^[[BB10:.+]] cf.br ^bb1 // CHECK: ^[[BB4]]: @@ -96,7 +96,7 @@ hw.module @basic(in %cond: i1) { cf.cond_br %27, ^bb5, ^bb6 // CHECK: ^[[BB5]]: ^bb5: - llhd.drv %14, %29 after %1 : !hw.inout + llhd.drv %m, %29 after %1 : !hw.inout // CHECK: cf.br ^[[BB10]] cf.br ^bb1 // CHECK: ^[[BB6]]: @@ -109,28 +109,28 @@ hw.module @basic(in %cond: i1) { cf.cond_br %31, ^bb8, ^bb9 // CHECK: ^[[BB8]]: ^bb8: - llhd.drv %15, %35 after %1 : !hw.inout + llhd.drv %n, %35 after %1 : !hw.inout // CHECK: cf.br ^[[BB10]] cf.br ^bb1 // CHECK: ^[[BB9]]: ^bb9: - llhd.drv %16, %39 after %1 : !hw.inout + llhd.drv %o, %39 after %1 : !hw.inout // CHECK: cf.br ^[[BB10]] cf.br ^bb1 // CHECK: ^[[BB10]]: - // CHECK: llhd.drv [[V12]], [[V14]] after [[V1]] if %true{{.*}} : !hw.inout + // CHECK: llhd.drv %k, [[V14]] after [[V1]] if %true{{.*}} : !hw.inout // CHECK: [[V29:%.+]] = comb.and %true{{.*}}, [[V15]] : i1 // CHECK: [[V30:%.+]] = comb.or %false{{.*}}, [[V29]] : i1 // CHECK: [[V31:%.+]] = comb.and %cond, [[V30]] : i1 - // CHECK: llhd.drv [[V13]], %c0_i5 after [[V1]] if [[V31]] : !hw.inout + // CHECK: llhd.drv %l, %c0_i5 after [[V1]] if [[V31]] : !hw.inout // CHECK: [[V33:%.+]] = comb.xor [[V15]], %true{{.*}} : i1 // CHECK: [[V34:%.+]] = comb.and %true{{.*}}, [[V33]] : i1 // CHECK: [[V35:%.+]] = comb.or %false{{.*}}, [[V34]] : i1 // CHECK: [[V36:%.+]] = comb.and [[V35]], [[V16]] : i1 // CHECK: [[V37:%.+]] = comb.or %false{{.*}}, [[V36]] : i1 - // CHECK: llhd.drv [[V13_1]], [[V18]] after [[V1]] if [[V37]] : !hw.inout + // CHECK: llhd.drv %m, [[V18]] after [[V1]] if [[V37]] : !hw.inout // CHECK: [[V40:%.+]] = comb.xor [[V16]], %true{{.*}} : i1 // CHECK: [[V41:%.+]] = comb.and [[V35]], [[V40]] : i1 @@ -139,12 +139,12 @@ hw.module @basic(in %cond: i1) { // CHECK: [[V44:%.+]] = comb.or %false{{.*}}, [[V43]] : i1 // CHECK: [[V45:%.+]] = comb.and [[V44]], [[V20]] : i1 // CHECK: [[V46:%.+]] = comb.or %false{{.*}}, [[V45]] : i1 - // CHECK: llhd.drv [[V13_2]], [[V24]] after [[V1]] if [[V46]] : !hw.inout + // CHECK: llhd.drv %n, [[V24]] after [[V1]] if [[V46]] : !hw.inout // CHECK: [[V49:%.+]] = comb.xor [[V20]], %true{{.*}} : i1 // CHECK: [[V50:%.+]] = comb.and [[V44]], [[V49]] : i1 // CHECK: [[V51:%.+]] = comb.or %false{{.*}}, [[V50]] : i1 - // CHECK: llhd.drv [[V13_3]], [[V28]] after [[V1]] if [[V51]] : !hw.inout + // CHECK: llhd.drv %o, [[V28]] after [[V1]] if [[V51]] : !hw.inout // CHECK: cf.br ^[[BB1]] } @@ -154,27 +154,27 @@ hw.module @basic(in %cond: i1) { ^bb1: llhd.wait ^bb2 ^bb2: - // CHECK: [[V20:%.+]] = llhd.prb [[V13]] - // CHECK-NEXT: [[V21:%.+]] = llhd.prb [[V13_1]] - %20 = llhd.prb %13 : !hw.inout - %21 = llhd.prb %14 : !hw.inout + // CHECK: [[V20:%.+]] = llhd.prb %l + // CHECK-NEXT: [[V21:%.+]] = llhd.prb %m + %20 = llhd.prb %l : !hw.inout + %21 = llhd.prb %m : !hw.inout - // CHECK-NEXT: llhd.drv [[V12]], [[V21]] after [[V1]] : !hw.inout - llhd.drv %12, %20 after %1 : !hw.inout - llhd.drv %12, %21 after %1 : !hw.inout + // CHECK-NEXT: llhd.drv %k, [[V21]] after [[V1]] : !hw.inout + llhd.drv %k, %20 after %1 : !hw.inout + llhd.drv %k, %21 after %1 : !hw.inout // CHECK-NEXT: [[V22:%.+]] = comb.mux %cond, [[V21]], [[V20]] - // CHECK-NEXT: llhd.drv [[V13]], [[V22]] after [[V1]] : !hw.inout - llhd.drv %13, %20 after %1 : !hw.inout - llhd.drv %13, %21 after %1 if %cond : !hw.inout + // CHECK-NEXT: llhd.drv %l, [[V22]] after [[V1]] : !hw.inout + llhd.drv %l, %20 after %1 : !hw.inout + llhd.drv %l, %21 after %1 if %cond : !hw.inout // CHECK-NEXT: [[V23:%.+]] = comb.xor %cond, %true // CHECK-NEXT: [[V24:%.+]] = comb.mux %cond, [[V21]], [[V20]] // CHECK-NEXT: [[V25:%.+]] = comb.or %cond, [[V23]] - // CHECK-NEXT: llhd.drv [[V13_1]], [[V24]] after [[V1]] if [[V25]] : !hw.inout + // CHECK-NEXT: llhd.drv %m, [[V24]] after [[V1]] if [[V25]] : !hw.inout %22 = comb.xor %cond, %true : i1 - llhd.drv %14, %20 after %1 if %22 : !hw.inout - llhd.drv %14, %21 after %1 if %cond : !hw.inout + llhd.drv %m, %20 after %1 if %22 : !hw.inout + llhd.drv %m, %21 after %1 if %cond : !hw.inout // CHECK-NEXT: cf.br cf.br ^bb1 diff --git a/test/Dialect/Moore/basic.mlir b/test/Dialect/Moore/basic.mlir index eb7d9b03b678..7b589d3582e3 100644 --- a/test/Dialect/Moore/basic.mlir +++ b/test/Dialect/Moore/basic.mlir @@ -310,3 +310,22 @@ moore.module @GraphRegion() { %1 = moore.add %0, %0 : i32 %0 = moore.constant 0 : i32 } + +// CHECK-LABEL: func.func @WaitEvent +func.func @WaitEvent(%arg0: !moore.i1, %arg1: !moore.i1) { + // CHECK: moore.wait_event { + moore.wait_event { + // CHECK: moore.detect_event any %arg0 : i1 + moore.detect_event any %arg0 : i1 + // CHECK: moore.detect_event posedge %arg0 : i1 + moore.detect_event posedge %arg0 : i1 + // CHECK: moore.detect_event negedge %arg0 : i1 + moore.detect_event negedge %arg0 : i1 + // CHECK: moore.detect_event edge %arg0 : i1 + moore.detect_event edge %arg0 : i1 + // CHECK: moore.detect_event any %arg0 if %arg1 : i1 + moore.detect_event any %arg0 if %arg1 : i1 + } + // CHECK: } + return +} diff --git a/test/firtool/dpi.fir b/test/firtool/dpi.fir index ce47feb2529e..f4bf89d6a541 100644 --- a/test/firtool/dpi.fir +++ b/test/firtool/dpi.fir @@ -33,7 +33,8 @@ circuit DPI: ; CHECK-LABEL: module DPI( ; CHECK: logic [7:0] [[TMP:_.+]]; ; CHECK-NEXT: reg [7:0] [[RESULT1:_.+]]; -; CHECK-NEXT: wire [7:0] [[OPEN_ARRAY:_.+]][0:1] = '{in_0, in_1}; +; CHECK-NEXT: wire [7:0] [[OPEN_ARRAY:_.+]][0:1]; +; CHECK-NEXT: assign [[OPEN_ARRAY]] = '{in_0, in_1}; ; CHECK-NEXT: always @(posedge clock) begin ; CHECK-NEXT: if (enable) begin ; CHECK-NEXT: clocked_result(in_0, in_1, [[TMP]]); diff --git a/test/firtool/lower-layers.fir b/test/firtool/lower-layers.fir index 8342bba67d5a..930e72ba1015 100644 --- a/test/firtool/lower-layers.fir +++ b/test/firtool/lower-layers.fir @@ -1,4 +1,4 @@ -; RUN: firtool -verilog %s | FileCheck %s +; RUN: firtool %s -disable-all-randomization -split-input-file | FileCheck %s ; This is an end-to-end example of a test-bench (Foo) enabling verification, ; probing into a device-under-test (Bar), and reading from hardware which is @@ -67,6 +67,113 @@ circuit Foo: %[[ ; CHECK: module Foo(); ; CHECK: wire d = Foo.bar.verification.c_probe; ; CHECK: Bar bar ( - ; CHECK: .a (d) + ; CHECK: .a (d) ; CHECK: ); ; CHECK: endmodule + +; // ----- + +; This is an end-to-end example of a test-harness enabling verification, probing +; into a device-under-test, and reading from hardware which is only present if +; the verification layer is enabled. + +FIRRTL version 4.0.0 + +circuit TestHarness: + + layer Verification, bind: + + ; CHECK: module DUT_Verification( + ; CHECK: input clock, + ; CHECK: input [31:0] a + ; CHECK: ); + ; CHECK: reg [31:0] pc_d; + ; CHECK: wire [31:0] pc_d_probe = pc_d; + ; CHECK: always @(posedge clock) + ; CHECK: pc_d <= a; + ; CHECK: endmodule + + ; CHECK: module DUT( + ; CHECK: input clock, + ; CHECK: input [31:0] a, + ; CHECK: output [31:0] b + ; CHECK: ); + ; CHECK: reg [31:0] pc; + ; CHECK: always @(posedge clock) + ; CHECK: pc <= a; + ; CHECK: assign b = pc; + ; CHECK: endmodule + module DUT: + input clock: Clock + input reset: UInt<1> + input a: UInt<32> + output b: UInt<32> + output trace: Probe, Verification> + + reg pc: UInt<32>, clock + connect pc, a + connect b, pc + + wire x : Probe, Verification> + + layerblock Verification: + reg pc_d: UInt<32>, clock + connect pc_d, a + define x = probe(pc_d) + + layerblock Verification: + define trace = x + + ; CHECK: module TestHarness_Verification( + ; CHECK: input [31:0] dut_trace, + ; CHECK: input clock, + ; CHECK: reset + ; CHECK: ); + ; CHECK: `ifndef SYNTHESIS + ; CHECK: always @(posedge clock) begin + ; CHECK: if ((`PRINTF_COND_) & reset) + ; CHECK: $fwrite(32'h80000002, "The last PC was: %x", dut_trace); + ; CHECK: end // always @(posedge) + ; CHECK: `endif // not def SYNTHESIS + ; CHECK: endmodule + + ; CHECK: module TestHarness( + ; CHECK: input clock, + ; CHECK: reset, + ; CHECK: input [31:0] a, + ; CHECK: output [31:0] b + ; CHECK: ); + ; CHECK: DUT dut ( + ; CHECK: .clock (clock), + ; CHECK: .a (a), + ; CHECK: .b (b) + ; CHECK: ); + ; CHECK: endmodule + public module TestHarness: + input clock: Clock + input reset: UInt<1> + input a: UInt<32> + output b: UInt<32> + + inst dut of DUT + connect dut.clock, clock + connect dut.reset, reset + connect dut.a, a + connect b, dut.b + + layerblock Verification: + printf(clock, reset, "The last PC was: %x", read(dut.trace)) + +; CHECK: FILE "layers_TestHarness_Verification.sv" +; CHECK: `ifndef layers_TestHarness_Verification +; CHECK: `define layers_TestHarness_Verification +; CHECK: bind DUT DUT_Verification verification ( +; CHECK: .clock (clock), +; CHECK: .a (a) +; CHECK: ); +; CHECK: bind TestHarness TestHarness_Verification verification ( +; CHECK: .dut_trace (TestHarness.dut.verification.pc_d_probe), +; CHECK: .clock (clock), +; CHECK: .reset (reset) +; CHECK: ); +; CHECK: `endif // layers_TestHarness_Verification diff --git a/test/firtool/lower-layers2.fir b/test/firtool/lower-layers2.fir deleted file mode 100644 index 13c4e566c79f..000000000000 --- a/test/firtool/lower-layers2.fir +++ /dev/null @@ -1,106 +0,0 @@ -; RUN: firtool -verilog -disable-all-randomization %s | FileCheck %s - -; This is an end-to-end example of a test-harness enabling verification, probing -; into a device-under-test, and reading from hardware which is only present if -; the verification layer is enabled. - -FIRRTL version 4.0.0 - -circuit TestHarness: - - layer Verification, bind: - - ; CHECK: module DUT_Verification( - ; CHECK: input clock, - ; CHECK: input [31:0] a - ; CHECK: ); - ; CHECK: reg [31:0] pc_d; - ; CHECK: wire [31:0] pc_d_probe = pc_d; - ; CHECK: always @(posedge clock) - ; CHECK: pc_d <= a; - ; CHECK: endmodule - - ; CHECK: module DUT( - ; CHECK: input clock, - ; CHECK: input [31:0] a, - ; CHECK: output [31:0] b - ; CHECK: ); - ; CHECK: reg [31:0] pc; - ; CHECK: always @(posedge clock) - ; CHECK: pc <= a; - ; CHECK: assign b = pc; - ; CHECK: endmodule - module DUT: - input clock: Clock - input reset: UInt<1> - input a: UInt<32> - output b: UInt<32> - output trace: Probe, Verification> - - reg pc: UInt<32>, clock - connect pc, a - connect b, pc - - wire x : Probe, Verification> - - layerblock Verification: - reg pc_d: UInt<32>, clock - connect pc_d, a - define x = probe(pc_d) - - layerblock Verification: - define trace = x - - ; CHECK: module TestHarness_Verification( - ; CHECK: input [31:0] dut_trace, - ; CHECK: input clock, - ; CHECK: reset - ; CHECK: ); - ; CHECK: `ifndef SYNTHESIS - ; CHECK: always @(posedge clock) begin - ; CHECK: if ((`PRINTF_COND_) & reset) - ; CHECK: $fwrite(32'h80000002, "The last PC was: %x", dut_trace); - ; CHECK: end // always @(posedge) - ; CHECK: `endif // not def SYNTHESIS - ; CHECK: endmodule - - ; CHECK: module TestHarness( - ; CHECK: input clock, - ; CHECK: reset, - ; CHECK: input [31:0] a, - ; CHECK: output [31:0] b - ; CHECK: ); - ; CHECK: DUT dut ( - ; CHECK: .clock (clock), - ; CHECK: .a (a), - ; CHECK: .b (b) - ; CHECK: ); - ; CHECK: endmodule - public module TestHarness: - input clock: Clock - input reset: UInt<1> - input a: UInt<32> - output b: UInt<32> - - inst dut of DUT - connect dut.clock, clock - connect dut.reset, reset - connect dut.a, a - connect b, dut.b - - layerblock Verification: - printf(clock, reset, "The last PC was: %x", read(dut.trace)) - -; CHECK: // ----- 8< ----- FILE "layers_TestHarness_Verification.sv" ----- 8< ----- -; CHECK: `ifndef layers_TestHarness_Verification -; CHECK: `define layers_TestHarness_Verification -; CHECK: bind DUT DUT_Verification verification ( -; CHECK: .clock (clock), -; CHECK: .a (a) -; CHECK: ); -; CHECK: bind TestHarness TestHarness_Verification verification ( -; CHECK: .dut_trace (TestHarness.dut.verification.pc_d_probe), -; CHECK: .clock (clock), -; CHECK: .reset (reset) -; CHECK: ); -; CHECK: `endif // layers_TestHarness_Verification diff --git a/test/firtool/specialize-layers-2.fir b/test/firtool/specialize-layers-2.fir deleted file mode 100644 index c733179b17ec..000000000000 --- a/test/firtool/specialize-layers-2.fir +++ /dev/null @@ -1,11 +0,0 @@ -; RUN: firtool %s -disable-layers=X,Y -; RUN: firtool %s -enable-layers=X,Y - -; https://github.com/llvm/circt/issues/7345 -; Check that we can specify more than one layer in the command line options. - -FIRRTL version 4.0.0 -circuit Foo: - layer X, bind: - layer Y, bind: - public module Foo: diff --git a/test/firtool/specialize-layers-cli.fir b/test/firtool/specialize-layers-cli.fir new file mode 100644 index 000000000000..128b2eeddddf --- /dev/null +++ b/test/firtool/specialize-layers-cli.fir @@ -0,0 +1,21 @@ +; RUN: firtool -parse-only %s | FileCheck %s --check-prefixes=NONE +; RUN: firtool -parse-only -disable-layers=A %s | FileCheck %s --check-prefixes=DISABLEA +; RUN: firtool -parse-only -enable-layers=A %s | FileCheck %s --check-prefixes=ENABLEA +; RUN: firtool -parse-only -enable-layers=A.B %s | FileCheck %s --check-prefixes=ENABLEB +; RUN: firtool -parse-only -disable-layers=A,A.B %s | FileCheck %s --check-prefixes=DISABLEBOTH +; RUN: firtool -parse-only -disable-layers=A -enable-layers=A.B %s | FileCheck %s --check-prefixes=BOTH + +; Check that the command line options are working correctly. +; https://github.com/llvm/circt/issues/7345 + +FIRRTL version 4.0.0 +; NONE: firrtl.circuit "Foo" { +; DISABLEA: firrtl.circuit "Foo" attributes {disable_layers = [@A]} { +; ENABLEA: firrtl.circuit "Foo" attributes {enable_layers = [@A]} { +; ENABLEB: firrtl.circuit "Foo" attributes {enable_layers = [@A::@B]} { +; DISABLEBOTH: firrtl.circuit "Foo" attributes {disable_layers = [@A, @A::@B]} { +; BOTH: firrtl.circuit "Foo" attributes {disable_layers = [@A], enable_layers = [@A::@B]} { +circuit Foo: + layer A, bind: + layer B, bind: + public module Foo: diff --git a/test/firtool/specialize-layers-1.fir b/test/firtool/specialize-layers.fir similarity index 100% rename from test/firtool/specialize-layers-1.fir rename to test/firtool/specialize-layers.fir diff --git a/tools/arcilator/arcilator-header-cpp.py b/tools/arcilator/arcilator-header-cpp.py index 6a1545d950fc..113aa3094be2 100755 --- a/tools/arcilator/arcilator-header-cpp.py +++ b/tools/arcilator/arcilator-header-cpp.py @@ -63,12 +63,13 @@ class StateHierarchy: class ModelInfo: name: str numStateBytes: int + initialFnSym: str states: List[StateInfo] io: List[StateInfo] hierarchy: List[StateHierarchy] def decode(d: dict) -> "ModelInfo": - return ModelInfo(d["name"], d["numStateBytes"], + return ModelInfo(d["name"], d["numStateBytes"], d.get("initialFnSym", ""), [StateInfo.decode(d) for d in d["states"]], list(), list()) @@ -240,6 +241,8 @@ def indent(s: str, amount: int = 1): io.name = io.name + "_" print('extern "C" {') + if model.initialFnSym: + print(f"void {model.name}_initial(void* state);") print(f"void {model.name}_eval(void* state);") print('}') @@ -297,8 +300,11 @@ def indent(s: str, amount: int = 1): print(f" {model.name}View view;") print() print( - f" {model.name}() : storage({model.name}Layout::numStateBytes, 0), view(&storage[0]) {{}}" + f" {model.name}() : storage({model.name}Layout::numStateBytes, 0), view(&storage[0]) {{" ) + if model.initialFnSym: + print(f" {model.initialFnSym}(&storage[0]);") + print(" }") print(f" void eval() {{ {model.name}_eval(&storage[0]); }}") print( f" ValueChangeDump<{model.name}Layout> vcd(std::basic_ostream &os) {{" diff --git a/tools/circt-verilog/circt-verilog.cpp b/tools/circt-verilog/circt-verilog.cpp index 46e04a6fdaae..f07fc252a34d 100644 --- a/tools/circt-verilog/circt-verilog.cpp +++ b/tools/circt-verilog/circt-verilog.cpp @@ -229,14 +229,18 @@ static void populateMooreTransforms(PassManager &pm) { // modules/functions. auto &anyPM = pm.nestAny(); anyPM.addPass(mlir::createCSEPass()); - anyPM.addPass(createSimpleCanonicalizerPass()); + anyPM.addPass(mlir::createCanonicalizerPass()); } { // Perform module-specific transformations. auto &modulePM = pm.nest(); modulePM.addPass(moore::createLowerConcatRefPass()); - modulePM.addPass(moore::createSimplifyProceduresPass()); + // TODO: Enable the following once it not longer interferes with @(...) + // event control checks. The introduced dummy variables make the event + // control observe a static local variable that never changes, instead of + // observing a module-wide signal. + // modulePM.addPass(moore::createSimplifyProceduresPass()); } { @@ -245,7 +249,7 @@ static void populateMooreTransforms(PassManager &pm) { anyPM.addPass(mlir::createSROA()); anyPM.addPass(mlir::createMem2Reg()); anyPM.addPass(mlir::createCSEPass()); - anyPM.addPass(createSimpleCanonicalizerPass()); + anyPM.addPass(mlir::createCanonicalizerPass()); } } @@ -259,7 +263,7 @@ static void populateMooreToCoreLowering(PassManager &pm) { // opportunities. auto &anyPM = pm.nestAny(); anyPM.addPass(mlir::createCSEPass()); - anyPM.addPass(createSimpleCanonicalizerPass()); + anyPM.addPass(mlir::createCanonicalizerPass()); } } diff --git a/utils/linkify.sh b/utils/linkify.sh new file mode 100755 index 000000000000..1113552eaaea --- /dev/null +++ b/utils/linkify.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# This script linkifies (i.e. makes clickable in the terminal) text that appears +# to be a pull request or issue reference (e.g. #12345 or PR12345) or a +# 40-character commit hash (e.g. abc123). You can configure git to automatically +# send the output of commands that pipe their output through a pager, such as +# `git log` and `git show`, through this script by running this command from +# within your CIRCT checkout: +# +# git config core.pager 'utils/linkify.sh | pager' +# +# Consider: +# +# git config core.pager 'utils/linkify.sh | ${PAGER:-less}' +# +# The pager command is run from the root of the repository even if the git +# command is run from a subdirectory, so the relative path should always work. +# +# It requires OSC 8 support in the terminal. For a list of compatible terminals, +# see https://github.com/Alhadis/OSC8-Adoption +# +# Copied from upstream LLVM's llvm/utils/git/linkify. + +sed \ + -e 's,\(#\|\bPR\)\([0-9]\+\),\x1b]8;;https://github.com/llvm/circt/issues/\2\x1b\\\0\x1b]8;;\x1b\\,gi' \ + -e 's,[0-9a-f]\{40\},\x1b]8;;https://github.com/llvm/circt/commit/\0\x1b\\\0\x1b]8;;\x1b\\,g'