From 8ffa90796cd5a2368960312aa572dc6cd022ed61 Mon Sep 17 00:00:00 2001 From: Fabian Schuiki Date: Mon, 12 Aug 2024 16:24:35 -0700 Subject: [PATCH] [Moore] Improve WaitEventOp, lower to LLHD Rework the `moore.wait_event` op to be able to accurately model the semantics of SystemVerilog's `@...` event control statements. The op now has a body region which is executed to detect a relevant change in one or more interesting values. A new `moore.detect_event` op serves as the mechanism to encode whether a posedge, negedge, both, or any change at all on a value should be detected as an event. Based on this new `moore.wait_event` op we can properly convert most of the event control statements in `ImportVerilog` to a corresponding MLIR op. Delay control like `#1ns` is not handled yet. In the MooreToCore conversion this new op allows us to properly generate `llhd.wait` operations at the right place that suspend process execution until an interesting event has occurred. This now also allows us to support almost all SystemVerilog processes in the lowering. The only missing ones are `always_comb` and `always_latch` which require an implicit `llhd.wait` to be inserted. @maerhart has a version of that lowering almost ready though. This commit also adds an `llhd.final` op in order to be able to lower `final` procedures. Co-authored-by: Martin Erhart --- .../circt/Dialect/LLHD/IR/LLHDStructureOps.td | 40 ++- include/circt/Dialect/Moore/MooreOps.td | 119 ++++-- lib/Conversion/ImportVerilog/Expressions.cpp | 81 +++-- .../ImportVerilog/ImportVerilogInternals.h | 19 +- lib/Conversion/ImportVerilog/Statements.cpp | 6 +- .../ImportVerilog/TimingControls.cpp | 196 ++++++++-- lib/Conversion/MooreToCore/MooreToCore.cpp | 329 +++++++++++------ test/Conversion/ImportVerilog/basic.sv | 267 +++++++++++--- test/Conversion/MooreToCore/basic.mlir | 338 +++++++++++------- test/Dialect/LLHD/IR/basic.mlir | 10 + test/Dialect/Moore/basic.mlir | 19 + tools/circt-verilog/circt-verilog.cpp | 6 +- 12 files changed, 1025 insertions(+), 405 deletions(-) diff --git a/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td index 3f007c8c7689..58afcb53bd60 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"> @@ -108,12 +142,14 @@ def WaitOp : LLHDOp<"wait", [ }]; } -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/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..167b89408468 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -34,6 +34,7 @@ using namespace circt; using namespace moore; using comb::ICmpPredicate; +using llvm::SmallDenseSet; namespace { @@ -169,142 +170,234 @@ struct ProcedureOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(ProcedureOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - Location loc = op.getLoc(); - auto procOp = rewriter.create(loc); - - // 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 = rewriter.createBlock(&newOp.getBody()); + // TODO: Collect observed signals and add LLHD wait op. + return failure(); + } - // 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; + SmallDenseSet alreadyObserved; + clonedOp.getBody().walk([&](Operation *operation) { + for (auto value : operation->getOperands()) { + if (value.getParentRegion() == &clonedOp.getBody()) + continue; + if (!alreadyObserved.insert(value).second) + continue; + if (auto remapped = rewriter.getRemappedValue(value)) { + observeValues.push_back(remapped); + } else { + auto type = typeConverter->convertType(value.getType()); + auto converted = typeConverter->materializeTargetConversion( + rewriter, loc, type, value); + observeValues.push_back(converted); + } + } + }); + + // 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)) { + 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(); } }; @@ -1133,9 +1226,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 +1294,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, 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..4d233f67c5a0 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" + %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() { @@ -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,199 @@ 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 "a" + // CHECK: [[B:%.+]] = llhd.sig "b" + // CHECK: [[C:%.+]] = llhd.sig "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.halt + // CHECK: } + moore.procedure initial { + func.call @dummyA() : () -> () + moore.wait_event { + func.call @dummyB() : () -> () + } + func.call @dummyC() : () -> () + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb [[A]] + // CHECK: llhd.wait ([[A]] : {{.+}}), ^[[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 ([[A]], [[B]] : {{.+}}), ^[[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 ([[A]], [[B]], [[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 ([[A]] : {{.+}}), ^[[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 ([[A]] : {{.+}}), ^[[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 ([[A]] : {{.+}}), ^[[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 + } +} diff --git a/test/Dialect/LLHD/IR/basic.mlir b/test/Dialect/LLHD/IR/basic.mlir index 96d768958628..e5cad3b18bdb 100644 --- a/test/Dialect/LLHD/IR/basic.mlir +++ b/test/Dialect/LLHD/IR/basic.mlir @@ -199,3 +199,13 @@ hw.module @check_wait_3 (inout %arg0 : i64, inout %arg1 : i1) { 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/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/tools/circt-verilog/circt-verilog.cpp b/tools/circt-verilog/circt-verilog.cpp index 46e04a6fdaae..ac25cc476465 100644 --- a/tools/circt-verilog/circt-verilog.cpp +++ b/tools/circt-verilog/circt-verilog.cpp @@ -236,7 +236,11 @@ static void populateMooreTransforms(PassManager &pm) { // 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()); } {