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..198391b87f22 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 { @@ -102,6 +103,30 @@ static hw::ModulePortInfo getModulePortInfo(const TypeConverter &typeConverter, return hw::ModulePortInfo(inputs, outputs); } +/// Erase all dead blocks in a region. +static void eraseDeadBlocks(PatternRewriter &rewriter, Region ®ion) { + SmallVector worklist; + for (auto &block : llvm::make_early_inc_range(llvm::drop_begin(region, 1))) { + if (!block.use_empty()) + continue; + worklist.push_back(&block); + while (!worklist.empty()) { + auto *block = worklist.pop_back_val(); + if (!block->use_empty()) + continue; + for (auto *successor : block->getSuccessors()) + worklist.push_back(successor); + rewriter.eraseBlock(block); + } + } +} + +/// Erase all dead blocks in an op. +static void eraseDeadBlocks(PatternRewriter &rewriter, Operation *op) { + for (auto ®ion : op->getRegions()) + eraseDeadBlocks(rewriter, region); +} + //===----------------------------------------------------------------------===// // Structural Conversion //===----------------------------------------------------------------------===// @@ -169,142 +194,235 @@ 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"); - - // Collect all event ops in the procedure. - SmallVector events(op.getOps()); + auto loc = op.getLoc(); + if (failed(rewriter.convertRegionTypes(&op.getBody(), *typeConverter))) + return failure(); + eraseDeadBlocks(rewriter, op); + + // 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 +1251,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 +1319,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..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()); } }