[Moore] Improve WaitEventOp, lower to LLHD (#7518)

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.

Fixes #7482.

Co-authored-by: Martin Erhart <maerhart@outlook.com>
This commit is contained in:
Fabian Schuiki 2024-08-16 13:14:12 -07:00 committed by GitHub
parent 4492392eab
commit d71e6e30c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1087 additions and 418 deletions

View File

@ -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";
}

View File

@ -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.
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.
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
```
Example corresponding to the SystemVerilog `@(posedge x, negedge y iff z)`:
```
moore.wait_event {
%0 = moore.read %x : <i1>
%1 = moore.read %y : <i1>
%2 = moore.read %z : <i1>
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 : <i1>
%1 = moore.not %0 : i1
%2 = moore.read %i : <i19>
%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<BitType>:$condition
);
let assemblyFormat = [{
$edge $input attr-dict `:` type($input)
$edge $input (`if` $condition^)? attr-dict `:` type($input)
}];
}

View File

@ -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<moore::IntType>(value.getType()))
if (type.getBitSize() == 1)
return value;
if (auto type = dyn_cast_or_null<moore::UnpackedType>(value.getType()))
return builder.create<moore::BoolCastOp>(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<moore::ConversionOp>(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<moore::RefType>(value.getType()))
value = builder.create<moore::ReadOp>(loc, value);
if (isa<moore::RefType>(value.getType())) {
auto readOp = builder.create<moore::ReadOp>(loc, value);
if (context.rvalueReadCallback)
context.rvalueReadCallback(readOp);
value = readOp.getResult();
}
return value;
}
@ -229,7 +207,7 @@ struct RvalueExprVisitor {
return createReduction<moore::ReduceXorOp>(arg, true);
case UnaryOperator::LogicalNot:
arg = convertToBool(arg);
arg = context.convertToBool(arg);
if (!arg)
return {};
return builder.create<moore::NotOp>(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<moore::AndOp>(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<moore::OrOp>(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<moore::NotOp>(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<moore::NotOp>(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<moore::ConditionalOp>(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<moore::IntType>(value.getType()))
if (type.getBitSize() == 1)
return value;
if (auto type = dyn_cast_or_null<moore::UnpackedType>(value.getType()))
return builder.create<moore::BoolCastOp>(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<moore::ConversionOp>(value.getLoc(), type, value);
}

View File

@ -26,6 +26,8 @@
namespace circt {
namespace ImportVerilog {
using moore::Domain;
/// Port lowering information.
struct PortLowering {
const slang::ast::PortSymbol &ast;
@ -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<LoopFrame> 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<void(moore::ReadOp)> rvalueReadCallback;
};
} // namespace ImportVerilog

View File

@ -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.

View File

@ -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<moore::EventOp>(loc, static_cast<moore::Edge>(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<moore::DetectEventOp>(loc, edge, expr, condition);
return success();
}
// Handle a list of signal events.
LogicalResult visit(const slang::ast::EventListControl &ctrl) {
for (auto *event : ctrl.as<slang::ast::EventListControl>().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 <typename T>
LogicalResult visit(T &&node) {
mlir::emitError(loc, "unspported timing control: ")
<< slang::ast::toString(node.kind);
return failure();
}
LogicalResult visitInvalid(const slang::ast::TimingControl &ctrl) {
mlir::emitError(loc, "invalid timing control");
return failure();
LogicalResult visit(T &&ctrl) {
return mlir::emitError(loc)
<< "unsupported event control: " << slang::ast::toString(ctrl.kind);
}
};
// Handle any of the delay control constructs.
struct DelayControlVisitor {
Context &context;
Location loc;
OpBuilder &builder;
// Emit an error for all other timing controls.
template <typename T>
LogicalResult visit(T &&ctrl) {
return mlir::emitError(loc)
<< "unsupported delay control: " << slang::ast::toString(ctrl.kind);
}
};
} // namespace
LogicalResult
Context::convertTimingControl(const slang::ast::TimingControl &timingControl) {
auto loc = convertLocation(timingControl.sourceRange.start());
TimingCtrlVisitor visitor{*this, loc};
return timingControl.visit(visitor);
// 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<moore::WaitEventOp>(loc);
return success();
// Handle event control.
case TimingControlKind::SignalEvent:
case TimingControlKind::EventList: {
auto waitOp = builder.create<moore::WaitEventOp>(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 &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<Value, 8> 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<moore::ReadOp>(implicitWaitOp.getLoc(), readValue);
builder.create<moore::DetectEventOp>(
implicitWaitOp.getLoc(), moore::Edge::AnyChange, value, Value{});
}
}
return success();
}
// NOLINTEND(misc-no-recursion)

View File

@ -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 &region) {
SmallVector<Block *> 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 &region : op->getRegions())
eraseDeadBlocks(rewriter, region);
}
//===----------------------------------------------------------------------===//
// Structural Conversion
//===----------------------------------------------------------------------===//
@ -169,138 +194,62 @@ struct ProcedureOpConversion : public OpConversionPattern<ProcedureOp> {
LogicalResult
matchAndRewrite(ProcedureOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Location loc = op.getLoc();
auto procOp = rewriter.create<llhd::ProcessOp>(loc);
auto loc = op.getLoc();
if (failed(rewriter.convertRegionTypes(&op.getBody(), *typeConverter)))
return failure();
eraseDeadBlocks(rewriter, op);
// TODO: properly handle the procedure kind attribute
if (op.getKind() != ProcedureKind::Always)
return rewriter.notifyMatchFailure(loc, "not yet supported");
// 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<llhd::ProcessOp>(loc);
else
newOp = rewriter.create<llhd::FinalOp>(loc);
auto &body = newOp->getRegion(0);
rewriter.inlineRegionBefore(op.getBody(), body, body.end());
for (auto returnOp :
llvm::make_early_inc_range(body.getOps<ReturnOp>())) {
rewriter.setInsertionPoint(returnOp);
rewriter.replaceOpWithNewOp<llhd::HaltOp>(returnOp);
}
rewriter.eraseOp(op);
return success();
}
// Collect all event ops in the procedure.
SmallVector<EventOp> events(op.getOps<EventOp>());
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<llhd::ProcessOp>(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<cf::BranchOp>(loc, wait);
rewriter.createBlock(&newOp.getBody());
auto *block = &op.getBody().front();
rewriter.create<cf::BranchOp>(loc, block);
rewriter.inlineRegionBefore(op.getBody(), newOp.getBody(),
newOp.getBody().end());
// 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<UnrealizedConversionCastOp>(loc, convertedType, input)
->getResult(0);
}
return signal;
};
// All signals to observe in the `llhd.wait` operation.
SmallVector<Value> toObserve;
DenseSet<Value> 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<MemoryEffectOpInterface>(operation);
if (!memOp)
return;
// The process is only sensitive to values that are read.
if (isa<RefType>(operand.get().getType()) &&
memOp.getEffectOnValue<MemoryEffects::Read>(operand.get())
.has_value()) {
if (!alreadyObserved.contains(value))
toObserve.push_back(value);
alreadyObserved.insert(value);
}
}
});
// 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();
}
// Forall edge triggered events, probe the old value
SmallVector<Value> oldValues(events.size(), Value());
for (auto [i, event] : llvm::enumerate(events)) {
auto readOp = event.getInput().getDefiningOp<ReadOp>();
if (!readOp)
return failure();
Value signal = getSignal(readOp.getInput());
toObserve.push_back(signal);
// Non-edge triggered events only need the value in the present
if (event.getEdge() != Edge::None)
oldValues[i] = rewriter.create<llhd::PrbOp>(loc, signal);
}
rewriter.create<llhd::WaitOp>(loc, toObserve, Value(), ValueRange{}, check);
rewriter.setInsertionPointToStart(check);
if (events.empty()) {
rewriter.create<cf::BranchOp>(loc, &op.getBody().front());
} else {
SmallVector<Value> disjuncts;
for (auto [i, signal, event] : llvm::enumerate(toObserve, events)) {
if (event.getEdge() == Edge::None)
disjuncts.push_back(rewriter.create<llhd::PrbOp>(loc, signal));
if (event.getEdge() == Edge::PosEdge ||
event.getEdge() == Edge::BothEdges) {
Value currVal = rewriter.create<llhd::PrbOp>(loc, signal);
Value trueVal = rewriter.create<hw::ConstantOp>(loc, APInt(1, 1));
Value notOldVal =
rewriter.create<comb::XorOp>(loc, oldValues[i], trueVal);
Value posedge = rewriter.create<comb::AndOp>(loc, notOldVal, currVal);
disjuncts.push_back(posedge);
}
if (event.getEdge() == Edge::NegEdge ||
event.getEdge() == Edge::BothEdges) {
Value currVal = rewriter.create<llhd::PrbOp>(loc, signal);
Value trueVal = rewriter.create<hw::ConstantOp>(loc, APInt(1, 1));
Value notCurrVal =
rewriter.create<comb::XorOp>(loc, currVal, trueVal);
Value posedge =
rewriter.create<comb::AndOp>(loc, oldValues[i], notCurrVal);
disjuncts.push_back(posedge);
}
}
Value isValid = rewriter.create<comb::OrOp>(loc, disjuncts, false);
rewriter.create<cf::CondBranchOp>(loc, isValid, &op.getBody().front(),
wait);
}
for (auto event : events)
rewriter.eraseOp(event);
rewriter.inlineRegionBefore(op.getBody(), procOp.getBody(),
procOp.getBody().end());
for (auto returnOp : procOp.getOps<ReturnOp>()) {
// 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<ReturnOp>())) {
rewriter.setInsertionPoint(returnOp);
rewriter.create<cf::BranchOp>(loc, wait);
rewriter.create<cf::BranchOp>(loc, block);
rewriter.eraseOp(returnOp);
}
@ -309,6 +258,180 @@ struct ProcedureOpConversion : public OpConversionPattern<ProcedureOp> {
}
};
struct WaitEventOpConversion : public OpConversionPattern<WaitEventOp> {
using OpConversionPattern::OpConversionPattern;
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:
// <read "before" values>
// llhd.wait ^check, ...
// ^check:
// <read "after" values>
// <detect edges>
// 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<cf::BranchOp>(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<Value> valuesBefore;
rewriter.setInsertionPointToEnd(waitBlock);
auto clonedOp = cast<WaitEventOp>(rewriter.clone(*op));
for (auto detectOp :
llvm::make_early_inc_range(clonedOp.getOps<DetectEventOp>())) {
valuesBefore.push_back(detectOp.getInput());
rewriter.eraseOp(detectOp);
}
// 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<Value> observeValues;
SmallDenseSet<Value> alreadyObserved;
clonedOp.getBody().walk([&](Operation *operation) {
for (auto value : operation->getOperands()) {
if (clonedOp.getBody().isAncestor(value.getParentRegion()))
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<llhd::WaitOp>(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<DetectEventOp> detectOps(op.getBody().getOps<DetectEventOp>());
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<comb::ICmpOp>(loc, ICmpPredicate::ne, before,
after, true);
SmallVector<Value> disjuncts;
Value trueVal = rewriter.create<hw::ConstantOp>(loc, APInt(1, 1));
if (edge == Edge::PosEdge || edge == Edge::BothEdges) {
Value notOldVal =
rewriter.create<comb::XorOp>(loc, before, trueVal, true);
Value posedge =
rewriter.create<comb::AndOp>(loc, notOldVal, after, true);
disjuncts.push_back(posedge);
}
if (edge == Edge::NegEdge || edge == Edge::BothEdges) {
Value notCurrVal =
rewriter.create<comb::XorOp>(loc, after, trueVal, true);
Value posedge =
rewriter.create<comb::AndOp>(loc, before, notCurrVal, true);
disjuncts.push_back(posedge);
}
return rewriter.createOrFold<comb::OrOp>(loc, disjuncts, true);
};
// 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<Value> triggers;
for (auto [detectOp, before] : llvm::zip(detectOps, valuesBefore)) {
// TODO: Support multi-bit values. Edge detection occurs per-bit.
if (auto intType = dyn_cast<IntType>(before.getType());
!intType || intType.getWidth() != 1)
return detectOp->emitError() << "requires single bit operand";
rewriter.setInsertionPoint(detectOp);
auto trigger =
computeTrigger(before, detectOp.getInput(), detectOp.getEdge());
if (detectOp.getCondition()) {
auto condition = typeConverter->materializeTargetConversion(
rewriter, loc, rewriter.getI1Type(), detectOp.getCondition());
trigger = rewriter.create<comb::AndOp>(loc, trigger, condition, true);
}
triggers.push_back(trigger);
rewriter.eraseOp(detectOp);
}
// 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<comb::OrOp>(loc, triggers, true);
rewriter.create<cf::CondBranchOp>(loc, triggered, resumeBlock, waitBlock);
} else {
rewriter.create<cf::BranchOp>(loc, waitBlock);
}
return success();
}
};
//===----------------------------------------------------------------------===//
// Declaration Conversion
//===----------------------------------------------------------------------===//
@ -1133,9 +1256,11 @@ static void populateTypeConversion(TypeConverter &typeConverter) {
[&](mlir::OpBuilder &builder, mlir::Type resultType,
mlir::ValueRange inputs,
mlir::Location loc) -> std::optional<mlir::Value> {
if (inputs.size() != 1)
if (inputs.size() != 1 || !inputs[0])
return std::nullopt;
return inputs[0];
return builder
.create<UnrealizedConversionCastOp>(loc, resultType, inputs[0])
.getResult(0);
});
typeConverter.addSourceMaterialization(
@ -1199,7 +1324,7 @@ static void populateOpConversion(RewritePatternSet &patterns,
CaseXZEqOpConversion<CaseXZEqOp, false>,
// Patterns of structural operations.
SVModuleOpConversion, InstanceOpConversion, ProcedureOpConversion,
SVModuleOpConversion, InstanceOpConversion, ProcedureOpConversion, WaitEventOpConversion,
// Patterns of shifting operations.
ShrOpConversion, ShlOpConversion, AShrOpConversion,

View File

@ -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 : <l1>
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<i32>
// CHECK-SAME: [[Y:%[^:]+]]: !moore.ref<i32>
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<i32>
// CHECK-SAME: [[Y:%[^:]+]]: !moore.ref<i32>
// CHECK-SAME: [[U:%[^:]+]]: !moore.ref<i1>
// CHECK-SAME: [[V:%[^:]+]]: !moore.ref<l1>
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]] : <l1>
// 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

View File

@ -306,6 +306,20 @@ func.func @AdvancedConversion(%arg0: !moore.array<5 x struct<{exp_bits: i32, man
return %1, %2 : !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>, !moore.i320
}
// CHECK-LABEL: func @Statements
func.func @Statements(%arg0: !moore.i42) {
// CHECK: %x = llhd.sig
%x = moore.variable : <i42>
// CHECK: [[TMP:%.+]] = llhd.constant_time <0ns, 0d, 1e>
// CHECK: llhd.drv %x, %arg0 after [[TMP]] : !hw.inout<i42>
moore.blocking_assign %x, %arg0 : i42
// CHECK: [[TMP:%.+]] = llhd.constant_time <0ns, 1d, 0e>
// CHECK: llhd.drv %x, %arg0 after [[TMP]] : !hw.inout<i42>
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:%b]] = llhd.sig
// CHECK: [[C:%c]] = llhd.sig
// CHECK: [[D:%d]] = llhd.sig
// CHECK: [[E:%e]] = llhd.sig
%b = moore.variable : <i1>
%c = moore.variable : <i1>
%d = moore.variable : <i1>
%e = moore.variable : <i1>
// 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<i1>, !hw.inout<i1>), ^[[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 : <i1>
moore.wait_event none %br : i1
%cr = moore.read %c : <i1>
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<i1>, !hw.inout<i1>), ^[[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 : <i1>
%cr = moore.read %c : <i1>
%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<i1>), ^[[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 : <i1>
%cr = moore.read %c : <i1>
// CHECK: llhd.process
moore.procedure always {
// CHECK: cf.br ^[[BB1:.+]]
// CHECK: ^[[BB1]]:
// CHECK: [[C_OLD:%.+]] = llhd.prb [[C]]
// CHECK: llhd.wait ([[C]] : !hw.inout<i1>), ^[[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,218 @@ func.func @CaseXZ(%arg0: !moore.l8, %arg1: !moore.l8) {
return
}
// CHECK-LABEL: hw.module @Procedures
moore.module @Procedures() {
// CHECK: llhd.process {
// CHECK: func.call @dummyA()
// CHECK: llhd.halt
// CHECK: }
moore.procedure initial {
func.call @dummyA() : () -> ()
moore.return
}
// CHECK: llhd.final {
// CHECK: func.call @dummyA()
// CHECK: llhd.halt
// CHECK: }
moore.procedure final {
func.call @dummyA() : () -> ()
moore.return
}
// CHECK: llhd.process {
// CHECK: cf.br ^[[BB:.+]]
// CHECK: ^[[BB]]:
// CHECK: func.call @dummyA()
// CHECK: cf.br ^[[BB]]
// CHECK: }
moore.procedure always {
func.call @dummyA() : () -> ()
moore.return
}
// CHECK: llhd.process {
// CHECK: cf.br ^[[BB:.+]]
// CHECK: ^[[BB]]:
// CHECK: func.call @dummyA()
// CHECK: cf.br ^[[BB]]
// CHECK: }
moore.procedure always_ff {
func.call @dummyA() : () -> ()
moore.return
}
// TODO: moore.procedure always_comb
// TODO: moore.procedure always_latch
}
func.func private @dummyA() -> ()
func.func private @dummyB() -> ()
func.func private @dummyC() -> ()
// CHECK-LABEL: hw.module @WaitEvent
moore.module @WaitEvent() {
// CHECK: %a = llhd.sig
// CHECK: %b = llhd.sig
// CHECK: %c = llhd.sig
%a = moore.variable : <i1>
%b = moore.variable : <i1>
%c = moore.variable : <i1>
// CHECK: llhd.process {
// CHECK: func.call @dummyA()
// CHECK: cf.br ^[[WAIT:.+]]
// CHECK: ^[[WAIT]]:
// CHECK: func.call @dummyB()
// CHECK: llhd.wait ^[[CHECK:.+]]
// CHECK: ^[[CHECK]]:
// CHECK: func.call @dummyB()
// CHECK: cf.br ^[[WAIT:.+]]
// CHECK: ^[[RESUME:.+]]:
// CHECK: func.call @dummyC()
// CHECK: llhd.prb %a
// CHECK: llhd.halt
// CHECK: }
moore.procedure initial {
func.call @dummyA() : () -> ()
moore.wait_event {
func.call @dummyB() : () -> ()
}
func.call @dummyC() : () -> ()
moore.read %a : <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: [[TMP:%.+]] = comb.icmp bin ne [[BEFORE]], [[AFTER]]
// CHECK: cf.cond_br [[TMP]]
moore.wait_event {
%0 = moore.read %a : <i1>
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 : <i1>
%1 = moore.read %b : <i1>
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 : <i1>
%1 = moore.read %b : <i1>
%2 = moore.read %c : <i1>
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 : <i1>
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 : <i1>
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 : <i1>
moore.detect_event edge %0 : i1
}
moore.return
}
// CHECK: llhd.process {
moore.procedure initial {
// CHECK: llhd.wait (%a, %b :
moore.wait_event {
%0 = moore.constant 0 : i1
%1 = moore.conditional %0 : i1 -> i1 {
%2 = moore.read %a : <i1>
moore.yield %2 : !moore.i1
} {
%3 = moore.read %b : <i1>
moore.yield %3 : !moore.i1
}
moore.detect_event any %1 : i1
}
moore.return
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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<moore::SVModuleOp>();
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());
}
}