[ImportVerilog] Switch from SCF to CF dialect for control flow (#7432)

Instead of using the SCF dialect and its `scf.if` and `scf.while`
operations for control flow, switch over to the CF dialect and its
`cf.br` and `cf.cond_br`. This allows us to support SystemVerilog's
`continue`, `break`, and `return` statements which don't map well to the
Structured Control Flow dialect.
This commit is contained in:
Fabian Schuiki 2024-08-05 17:10:19 -07:00 committed by GitHub
parent bec0deab4b
commit 5cc69bd6b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 646 additions and 359 deletions

View File

@ -131,8 +131,6 @@ def ProcedureKindAttr: I32EnumAttr<"ProcedureKind", "Procedure kind",
} }
def ProcedureOp : MooreOp<"procedure", [ def ProcedureOp : MooreOp<"procedure", [
SingleBlock,
NoTerminator,
NoRegionArguments, NoRegionArguments,
RecursiveMemoryEffects, RecursiveMemoryEffects,
RecursivelySpeculatable RecursivelySpeculatable
@ -172,15 +170,22 @@ def ProcedureOp : MooreOp<"procedure", [
See IEEE 1800-2017 § 9.2 "Structured procedures". See IEEE 1800-2017 § 9.2 "Structured procedures".
}]; }];
let regions = (region SizedRegion<1>:$bodyRegion);
let arguments = (ins ProcedureKindAttr:$kind); let arguments = (ins ProcedureKindAttr:$kind);
let results = (outs); let results = (outs);
let regions = (region AnyRegion:$body);
let assemblyFormat = [{ let assemblyFormat = [{
$kind attr-dict-with-keyword $bodyRegion $kind attr-dict-with-keyword $body
}]; }];
} }
def ReturnOp : MooreOp<"return", [
Pure, Terminator, HasParent<"ProcedureOp">
]> {
let summary = "Return from a procedure";
let assemblyFormat = [{ attr-dict }];
}
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// Declarations // Declarations
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@ -812,6 +817,16 @@ class CaseEqOpBase<string mnemonic> : MooreOp<mnemonic, [
`case_ne` returns 1). `case_eq` corresponds to the `===` operator and `case_ne` returns 1). `case_eq` corresponds to the `===` operator and
`case_ne` to the `!==` operator. `case_ne` to the `!==` operator.
`casez_eq` treats Z bits in either operand as wildcards and skips them
during the comparison. `casexz_eq` treats X and Z bits as wildcards. These
are different from the `wildcard_eq` operation, which only considers X/Z in
the right-hand operand as wildcards.
Case statements use this operation to perform case comparisons:
- `case` statements use `case_eq`
- `casez` statements use `casez_eq`
- `casex` statements use `casexz_eq`
See IEEE 1800-2017 § 11.4.5 "Equality operators". See IEEE 1800-2017 § 11.4.5 "Equality operators".
}]; }];
let arguments = (ins SimpleBitVectorType:$lhs, SimpleBitVectorType:$rhs); let arguments = (ins SimpleBitVectorType:$lhs, SimpleBitVectorType:$rhs);
@ -823,6 +838,12 @@ class CaseEqOpBase<string mnemonic> : MooreOp<mnemonic, [
def CaseEqOp : CaseEqOpBase<"case_eq"> { let summary = "Case equality"; } def CaseEqOp : CaseEqOpBase<"case_eq"> { let summary = "Case equality"; }
def CaseNeOp : CaseEqOpBase<"case_ne"> { let summary = "Case inequality"; } def CaseNeOp : CaseEqOpBase<"case_ne"> { let summary = "Case inequality"; }
def CaseZEqOp : CaseEqOpBase<"casez_eq"> {
let summary = "Case equality with Z as wildcard";
}
def CaseXZEqOp : CaseEqOpBase<"casexz_eq"> {
let summary = "Case equality with X and Z as wildcard";
}
class WildcardEqOpBase<string mnemonic> : MooreOp<mnemonic, [ class WildcardEqOpBase<string mnemonic> : MooreOp<mnemonic, [
Pure, Pure,
@ -1259,4 +1280,5 @@ def YieldOp : MooreOp<"yield", [
}]; }];
let hasVerifier = 1; let hasVerifier = 1;
} }
#endif // CIRCT_DIALECT_MOORE_MOOREOPS #endif // CIRCT_DIALECT_MOORE_MOOREOPS

View File

@ -264,8 +264,8 @@ LogicalResult ImportDriver::importVerilog(ModuleOp module) {
return success(); return success();
// Traverse the parsed Verilog AST and map it to the equivalent CIRCT ops. // Traverse the parsed Verilog AST and map it to the equivalent CIRCT ops.
mlirContext->loadDialect<moore::MooreDialect, hw::HWDialect, scf::SCFDialect, mlirContext->loadDialect<moore::MooreDialect, hw::HWDialect,
func::FuncDialect>(); cf::ControlFlowDialect, func::FuncDialect>();
auto conversionTimer = ts.nest("Verilog to dialect mapping"); auto conversionTimer = ts.nest("Verilog to dialect mapping");
Context context(*compilation, module, driver.sourceManager, bufferFilePaths); Context context(*compilation, module, driver.sourceManager, bufferFilePaths);
if (failed(context.convertCompilation())) if (failed(context.convertCompilation()))

View File

@ -13,8 +13,8 @@
#include "circt/Conversion/ImportVerilog.h" #include "circt/Conversion/ImportVerilog.h"
#include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/Moore/MooreOps.h" #include "circt/Dialect/Moore/MooreOps.h"
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
#include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "slang/ast/ASTVisitor.h" #include "slang/ast/ASTVisitor.h"
#include "llvm/ADT/ScopedHashTable.h" #include "llvm/ADT/ScopedHashTable.h"
#include "llvm/Support/Debug.h" #include "llvm/Support/Debug.h"
@ -46,6 +46,15 @@ struct FunctionLowering {
mlir::func::FuncOp op; mlir::func::FuncOp op;
}; };
/// Information about a loops continuation and exit blocks relevant while
/// lowering the loop's body statements.
struct LoopFrame {
/// The block to jump to from a `continue` statement.
Block *continueBlock;
/// The block to jump to from a `break` statement.
Block *breakBlock;
};
/// A helper class to facilitate the conversion from a Slang AST to MLIR /// A helper class to facilitate the conversion from a Slang AST to MLIR
/// operations. Keeps track of the destination MLIR module, builders, and /// operations. Keeps track of the destination MLIR module, builders, and
/// various worklists and utilities needed for conversion. /// various worklists and utilities needed for conversion.
@ -136,6 +145,13 @@ struct Context {
/// side. This allows expressions to resolve the opaque /// side. This allows expressions to resolve the opaque
/// `LValueReferenceExpression`s in the AST. /// `LValueReferenceExpression`s in the AST.
SmallVector<Value> lvalueStack; SmallVector<Value> lvalueStack;
/// A stack of loop continuation and exit blocks. Each loop will push the
/// relevant info onto this stack, lower its loop body statements, and pop the
/// info off the stack again. Continue and break statements encountered as
/// part of the loop body statements will use this information to branch to
/// the correct block.
SmallVector<LoopFrame> loopStack;
}; };
} // namespace ImportVerilog } // namespace ImportVerilog

View File

@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#include "ImportVerilogInternals.h" #include "ImportVerilogInternals.h"
#include "llvm/ADT/ScopeExit.h"
using namespace mlir; using namespace mlir;
using namespace circt; using namespace circt;
@ -22,6 +23,16 @@ struct StmtVisitor {
StmtVisitor(Context &context, Location loc) StmtVisitor(Context &context, Location loc)
: context(context), loc(loc), builder(context.builder) {} : context(context), loc(loc), builder(context.builder) {}
bool isTerminated() const { return !builder.getInsertionBlock(); }
void setTerminated() { builder.clearInsertionPoint(); }
Block &createBlock() {
assert(builder.getInsertionBlock());
auto block = std::make_unique<Block>();
block->insertAfter(builder.getInsertionBlock());
return *block.release();
}
// Skip empty statements (stray semicolons). // Skip empty statements (stray semicolons).
LogicalResult visit(const slang::ast::EmptyStatement &) { return success(); } LogicalResult visit(const slang::ast::EmptyStatement &) { return success(); }
@ -33,9 +44,15 @@ struct StmtVisitor {
// which in turn has a single body statement, which then commonly is a list of // which in turn has a single body statement, which then commonly is a list of
// statements. // statements.
LogicalResult visit(const slang::ast::StatementList &stmts) { LogicalResult visit(const slang::ast::StatementList &stmts) {
for (auto *stmt : stmts.list) for (auto *stmt : stmts.list) {
if (isTerminated()) {
auto loc = context.convertLocation(stmt->sourceRange);
mlir::emitWarning(loc, "unreachable code");
break;
}
if (failed(context.convertStatement(*stmt))) if (failed(context.convertStatement(*stmt)))
return failure(); return failure();
}
return success(); return success();
} }
@ -104,81 +121,123 @@ struct StmtVisitor {
allConds = allConds =
builder.create<moore::ConversionOp>(loc, builder.getI1Type(), allConds); builder.create<moore::ConversionOp>(loc, builder.getI1Type(), allConds);
// Generate the if operation. // Create the blocks for the true and false branches, and the exit block.
auto ifOp = Block &exitBlock = createBlock();
builder.create<scf::IfOp>(loc, allConds, stmt.ifFalse != nullptr); Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr;
OpBuilder::InsertionGuard guard(builder); Block &trueBlock = createBlock();
builder.create<cf::CondBranchOp>(loc, allConds, &trueBlock,
falseBlock ? falseBlock : &exitBlock);
// Generate the "then" body. // Generate the true branch.
builder.setInsertionPoint(ifOp.thenYield()); builder.setInsertionPointToEnd(&trueBlock);
if (failed(context.convertStatement(stmt.ifTrue))) if (failed(context.convertStatement(stmt.ifTrue)))
return failure(); return failure();
if (!isTerminated())
builder.create<cf::BranchOp>(loc, &exitBlock);
// Generate the "else" body if present. // Generate the false branch if present.
if (stmt.ifFalse) { if (stmt.ifFalse) {
builder.setInsertionPoint(ifOp.elseYield()); builder.setInsertionPointToEnd(falseBlock);
if (failed(context.convertStatement(*stmt.ifFalse))) if (failed(context.convertStatement(*stmt.ifFalse)))
return failure(); return failure();
if (!isTerminated())
builder.create<cf::BranchOp>(loc, &exitBlock);
} }
// If control never reaches the exit block, remove it and mark control flow
// as terminated. Otherwise we continue inserting ops in the exit block.
if (exitBlock.hasNoPredecessors()) {
exitBlock.erase();
setTerminated();
} else {
builder.setInsertionPointToEnd(&exitBlock);
}
return success(); return success();
} }
// Handle case statements. // Handle case statements.
LogicalResult visit(const slang::ast::CaseStatement &caseStmt) { LogicalResult visit(const slang::ast::CaseStatement &caseStmt) {
using slang::ast::CaseStatementCondition;
auto caseExpr = context.convertRvalueExpression(caseStmt.expr); auto caseExpr = context.convertRvalueExpression(caseStmt.expr);
if (!caseExpr) if (!caseExpr)
return failure(); return failure();
auto items = caseStmt.items; // Check each case individually. This currently ignores the `unique`,
// Used to generate the condition of the default case statement. // `unique0`, and `priority` modifiers which would allow for additional
SmallVector<Value> defaultConds; // optimizations.
// Traverse the case items. auto &exitBlock = createBlock();
for (auto item : items) {
// One statement will be matched with multi-conditions.
// Like case(cond) 0, 1 : y = x; endcase.
SmallVector<Value> allConds;
for (const auto *expr : item.expressions) {
auto itemExpr = context.convertRvalueExpression(*expr);
if (!itemExpr)
return failure();
auto newEqOp = builder.create<moore::EqOp>(loc, caseExpr, itemExpr); for (const auto &item : caseStmt.items) {
allConds.push_back(newEqOp); // Create the block that will contain the main body of the expression.
// This is where any of the comparisons will branch to if they match.
auto &matchBlock = createBlock();
// The SV standard requires expressions to be checked in the order
// specified by the user, and for the evaluation to stop as soon as the
// first matching expression is encountered.
for (const auto *expr : item.expressions) {
auto value = context.convertRvalueExpression(*expr);
if (!value)
return failure();
auto itemLoc = value.getLoc();
// Generate the appropriate equality operator.
Value cond;
switch (caseStmt.condition) {
case CaseStatementCondition::Normal:
cond = builder.create<moore::CaseEqOp>(itemLoc, caseExpr, value);
break;
case CaseStatementCondition::WildcardXOrZ:
cond = builder.create<moore::CaseXZEqOp>(itemLoc, caseExpr, value);
break;
case CaseStatementCondition::WildcardJustZ:
cond = builder.create<moore::CaseZEqOp>(itemLoc, caseExpr, value);
break;
case CaseStatementCondition::Inside:
mlir::emitError(loc, "unsupported set membership case statement");
return failure();
}
cond = builder.create<moore::ConversionOp>(itemLoc, builder.getI1Type(),
cond);
// If the condition matches, branch to the match block. Otherwise
// continue checking the next expression in a new block.
auto &nextBlock = createBlock();
builder.create<mlir::cf::CondBranchOp>(itemLoc, cond, &matchBlock,
&nextBlock);
builder.setInsertionPointToEnd(&nextBlock);
} }
// Bound all conditions of an item into one.
auto cond = allConds.back(); // The current block is the fall-through after all conditions have been
allConds.pop_back(); // checked and nothing matched. Move the match block up before this point
while (!allConds.empty()) { // to make the IR easier to read.
cond = builder.create<moore::OrOp>(loc, allConds.back(), cond); matchBlock.moveBefore(builder.getInsertionBlock());
allConds.pop_back();
} // Generate the code for this item's statement in the match block.
// Gather all items' conditions.
defaultConds.push_back(cond);
cond =
builder.create<moore::ConversionOp>(loc, builder.getI1Type(), cond);
auto ifOp = builder.create<mlir::scf::IfOp>(loc, cond);
OpBuilder::InsertionGuard guard(builder); OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPoint(ifOp.thenYield()); builder.setInsertionPointToEnd(&matchBlock);
if (failed(context.convertStatement(*item.stmt))) if (failed(context.convertStatement(*item.stmt)))
return failure(); return failure();
} if (!isTerminated()) {
// Handle the 'default case' statement if it exists. auto loc = context.convertLocation(item.stmt->sourceRange);
if (caseStmt.defaultCase) { builder.create<mlir::cf::BranchOp>(loc, &exitBlock);
auto cond = defaultConds.back();
defaultConds.pop_back();
while (!defaultConds.empty()) {
cond = builder.create<moore::OrOp>(loc, defaultConds.back(), cond);
defaultConds.pop_back();
} }
cond = builder.create<moore::NotOp>(loc, cond); }
cond =
builder.create<moore::ConversionOp>(loc, builder.getI1Type(), cond); // Generate the default case if present.
auto ifOp = builder.create<mlir::scf::IfOp>(loc, cond); if (caseStmt.defaultCase)
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPoint(ifOp.thenYield());
if (failed(context.convertStatement(*caseStmt.defaultCase))) if (failed(context.convertStatement(*caseStmt.defaultCase)))
return failure(); return failure();
if (!isTerminated())
builder.create<mlir::cf::BranchOp>(loc, &exitBlock);
// If control never reaches the exit block, remove it and mark control flow
// as terminated. Otherwise we continue inserting ops in the exit block.
if (exitBlock.hasNoPredecessors()) {
exitBlock.erase();
setTerminated();
} else {
builder.setInsertionPointToEnd(&exitBlock);
} }
return success(); return success();
} }
@ -190,127 +249,179 @@ struct StmtVisitor {
if (!context.convertRvalueExpression(*initExpr)) if (!context.convertRvalueExpression(*initExpr))
return failure(); return failure();
// Create the while op. // Create the blocks for the loop condition, body, step, and exit.
auto whileOp = builder.create<scf::WhileOp>(loc, TypeRange{}, ValueRange{}); auto &exitBlock = createBlock();
OpBuilder::InsertionGuard guard(builder); auto &stepBlock = createBlock();
auto &bodyBlock = createBlock();
auto &checkBlock = createBlock();
builder.create<cf::BranchOp>(loc, &checkBlock);
// In the "before" region, check that the condition holds. // Push the blocks onto the loop stack such that we can continue and break.
builder.createBlock(&whileOp.getBefore()); context.loopStack.push_back({&stepBlock, &exitBlock});
auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); });
// Generate the loop condition check.
builder.setInsertionPointToEnd(&checkBlock);
auto cond = context.convertRvalueExpression(*stmt.stopExpr); auto cond = context.convertRvalueExpression(*stmt.stopExpr);
if (!cond) if (!cond)
return failure(); return failure();
cond = builder.createOrFold<moore::BoolCastOp>(loc, cond); cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
cond = builder.create<moore::ConversionOp>(loc, builder.getI1Type(), cond); cond = builder.create<moore::ConversionOp>(loc, builder.getI1Type(), cond);
builder.create<mlir::scf::ConditionOp>(loc, cond, ValueRange{}); builder.create<cf::CondBranchOp>(loc, cond, &bodyBlock, &exitBlock);
// In the "after" region, generate the loop body and step expressions. // Generate the loop body.
builder.createBlock(&whileOp.getAfter()); builder.setInsertionPointToEnd(&bodyBlock);
if (failed(context.convertStatement(stmt.body))) if (failed(context.convertStatement(stmt.body)))
return failure(); return failure();
if (!isTerminated())
builder.create<cf::BranchOp>(loc, &stepBlock);
// Generate the step expressions.
builder.setInsertionPointToEnd(&stepBlock);
for (auto *stepExpr : stmt.steps) for (auto *stepExpr : stmt.steps)
if (!context.convertRvalueExpression(*stepExpr)) if (!context.convertRvalueExpression(*stepExpr))
return failure(); return failure();
builder.create<mlir::scf::YieldOp>(loc); if (!isTerminated())
builder.create<cf::BranchOp>(loc, &checkBlock);
// If control never reaches the exit block, remove it and mark control flow
// as terminated. Otherwise we continue inserting ops in the exit block.
if (exitBlock.hasNoPredecessors()) {
exitBlock.erase();
setTerminated();
} else {
builder.setInsertionPointToEnd(&exitBlock);
}
return success(); return success();
} }
// Handle `repeat` loops. // Handle `repeat` loops.
LogicalResult visit(const slang::ast::RepeatLoopStatement &stmt) { LogicalResult visit(const slang::ast::RepeatLoopStatement &stmt) {
// Create the while op and feed in the repeat count as the initial counter
// value.
auto count = context.convertRvalueExpression(stmt.count); auto count = context.convertRvalueExpression(stmt.count);
if (!count) if (!count)
return failure(); return failure();
auto type = cast<moore::IntType>(count.getType());
auto whileOp = builder.create<scf::WhileOp>(loc, type, count);
OpBuilder::InsertionGuard guard(builder);
// In the "before" region, check that the counter is non-zero. // Create the blocks for the loop condition, body, step, and exit.
auto *block = builder.createBlock(&whileOp.getBefore(), {}, type, loc); auto &exitBlock = createBlock();
auto counterArg = block->getArgument(0); auto &stepBlock = createBlock();
auto cond = builder.createOrFold<moore::BoolCastOp>(loc, counterArg); auto &bodyBlock = createBlock();
auto &checkBlock = createBlock();
auto currentCount = checkBlock.addArgument(count.getType(), count.getLoc());
builder.create<cf::BranchOp>(loc, &checkBlock, count);
// Push the blocks onto the loop stack such that we can continue and break.
context.loopStack.push_back({&stepBlock, &exitBlock});
auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); });
// Generate the loop condition check.
builder.setInsertionPointToEnd(&checkBlock);
auto cond = builder.createOrFold<moore::BoolCastOp>(loc, currentCount);
cond = builder.create<moore::ConversionOp>(loc, builder.getI1Type(), cond); cond = builder.create<moore::ConversionOp>(loc, builder.getI1Type(), cond);
builder.create<scf::ConditionOp>(loc, cond, counterArg); builder.create<cf::CondBranchOp>(loc, cond, &bodyBlock, &exitBlock);
// In the "after" region, generate the loop body and decrement the counter. // Generate the loop body.
block = builder.createBlock(&whileOp.getAfter(), {}, type, loc); builder.setInsertionPointToEnd(&bodyBlock);
if (failed(context.convertStatement(stmt.body))) if (failed(context.convertStatement(stmt.body)))
return failure(); return failure();
counterArg = block->getArgument(0); if (!isTerminated())
auto constOne = builder.create<moore::ConstantOp>(loc, type, 1); builder.create<cf::BranchOp>(loc, &stepBlock);
auto subOp = builder.create<moore::SubOp>(loc, counterArg, constOne);
builder.create<scf::YieldOp>(loc, ValueRange{subOp});
// Decrement the current count and branch back to the check block.
builder.setInsertionPointToEnd(&stepBlock);
auto one = builder.create<moore::ConstantOp>(
count.getLoc(), cast<moore::IntType>(count.getType()), 1);
Value nextCount =
builder.create<moore::SubOp>(count.getLoc(), currentCount, one);
builder.create<cf::BranchOp>(loc, &checkBlock, nextCount);
// If control never reaches the exit block, remove it and mark control flow
// as terminated. Otherwise we continue inserting ops in the exit block.
if (exitBlock.hasNoPredecessors()) {
exitBlock.erase();
setTerminated();
} else {
builder.setInsertionPointToEnd(&exitBlock);
}
return success();
}
// Handle `while` and `do-while` loops.
LogicalResult createWhileLoop(const slang::ast::Expression &condExpr,
const slang::ast::Statement &bodyStmt,
bool atLeastOnce) {
// Create the blocks for the loop condition, body, and exit.
auto &exitBlock = createBlock();
auto &bodyBlock = createBlock();
auto &checkBlock = createBlock();
builder.create<cf::BranchOp>(loc, atLeastOnce ? &bodyBlock : &checkBlock);
if (atLeastOnce)
bodyBlock.moveBefore(&checkBlock);
// Push the blocks onto the loop stack such that we can continue and break.
context.loopStack.push_back({&checkBlock, &exitBlock});
auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); });
// Generate the loop condition check.
builder.setInsertionPointToEnd(&checkBlock);
auto cond = context.convertRvalueExpression(condExpr);
if (!cond)
return failure();
cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
cond = builder.create<moore::ConversionOp>(loc, builder.getI1Type(), cond);
builder.create<cf::CondBranchOp>(loc, cond, &bodyBlock, &exitBlock);
// Generate the loop body.
builder.setInsertionPointToEnd(&bodyBlock);
if (failed(context.convertStatement(bodyStmt)))
return failure();
if (!isTerminated())
builder.create<cf::BranchOp>(loc, &checkBlock);
// If control never reaches the exit block, remove it and mark control flow
// as terminated. Otherwise we continue inserting ops in the exit block.
if (exitBlock.hasNoPredecessors()) {
exitBlock.erase();
setTerminated();
} else {
builder.setInsertionPointToEnd(&exitBlock);
}
return success(); return success();
} }
// Handle `while` loops.
LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) { LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) {
// Create the while op. return createWhileLoop(stmt.cond, stmt.body, false);
auto whileOp = builder.create<scf::WhileOp>(loc, TypeRange{}, ValueRange{});
OpBuilder::InsertionGuard guard(builder);
// In the "before" region, check that the condition holds.
builder.createBlock(&whileOp.getBefore());
auto cond = context.convertRvalueExpression(stmt.cond);
if (!cond)
return failure();
cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
cond = builder.create<moore::ConversionOp>(loc, builder.getI1Type(), cond);
builder.create<mlir::scf::ConditionOp>(loc, cond, ValueRange{});
// In the "after" region, generate the loop body.
builder.createBlock(&whileOp.getAfter());
if (failed(context.convertStatement(stmt.body)))
return failure();
builder.create<mlir::scf::YieldOp>(loc);
return success();
} }
// Handle `do ... while` loops.
LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) { LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) {
// Create the while op. return createWhileLoop(stmt.cond, stmt.body, true);
auto whileOp = builder.create<scf::WhileOp>(loc, TypeRange{}, ValueRange{});
OpBuilder::InsertionGuard guard(builder);
// In the "before" region, generate the loop body and check that the
// condition holds.
builder.createBlock(&whileOp.getBefore());
if (failed(context.convertStatement(stmt.body)))
return failure();
auto cond = context.convertRvalueExpression(stmt.cond);
if (!cond)
return failure();
cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
cond = builder.create<moore::ConversionOp>(loc, builder.getI1Type(), cond);
builder.create<mlir::scf::ConditionOp>(loc, cond, ValueRange{});
// Generate an empty "after" region.
builder.createBlock(&whileOp.getAfter());
builder.create<mlir::scf::YieldOp>(loc);
return success();
} }
// Handle `forever` loops. // Handle `forever` loops.
LogicalResult visit(const slang::ast::ForeverLoopStatement &stmt) { LogicalResult visit(const slang::ast::ForeverLoopStatement &stmt) {
// Create the while op. // Create the blocks for the loop body and exit.
auto whileOp = builder.create<scf::WhileOp>(loc, TypeRange{}, ValueRange{}); auto &exitBlock = createBlock();
OpBuilder::InsertionGuard guard(builder); auto &bodyBlock = createBlock();
builder.create<cf::BranchOp>(loc, &bodyBlock);
// In the "before" region, return true for the condition. // Push the blocks onto the loop stack such that we can continue and break.
builder.createBlock(&whileOp.getBefore()); context.loopStack.push_back({&bodyBlock, &exitBlock});
auto cond = builder.create<hw::ConstantOp>(loc, builder.getI1Type(), 1); auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); });
builder.create<mlir::scf::ConditionOp>(loc, cond, ValueRange{});
// In the "after" region, generate the loop body. // Generate the loop body.
builder.createBlock(&whileOp.getAfter()); builder.setInsertionPointToEnd(&bodyBlock);
if (failed(context.convertStatement(stmt.body))) if (failed(context.convertStatement(stmt.body)))
return failure(); return failure();
builder.create<mlir::scf::YieldOp>(loc); if (!isTerminated())
builder.create<cf::BranchOp>(loc, &bodyBlock);
// If control never reaches the exit block, remove it and mark control flow
// as terminated. Otherwise we continue inserting ops in the exit block.
if (exitBlock.hasNoPredecessors()) {
exitBlock.erase();
setTerminated();
} else {
builder.setInsertionPointToEnd(&exitBlock);
}
return success(); return success();
} }
@ -325,13 +436,34 @@ struct StmtVisitor {
// Handle return statements. // Handle return statements.
LogicalResult visit(const slang::ast::ReturnStatement &stmt) { LogicalResult visit(const slang::ast::ReturnStatement &stmt) {
Value expr;
if (stmt.expr) { if (stmt.expr) {
expr = context.convertRvalueExpression(*stmt.expr); auto expr = context.convertRvalueExpression(*stmt.expr);
if (!expr) if (!expr)
return failure(); return failure();
builder.create<mlir::func::ReturnOp>(loc, expr);
} else {
builder.create<mlir::func::ReturnOp>(loc);
} }
builder.create<mlir::func::ReturnOp>(loc, expr); setTerminated();
return success();
}
// Handle continue statements.
LogicalResult visit(const slang::ast::ContinueStatement &stmt) {
if (context.loopStack.empty())
return mlir::emitError(loc,
"cannot `continue` without a surrounding loop");
builder.create<cf::BranchOp>(loc, context.loopStack.back().continueBlock);
setTerminated();
return success();
}
// Handle break statements.
LogicalResult visit(const slang::ast::BreakStatement &stmt) {
if (context.loopStack.empty())
return mlir::emitError(loc, "cannot `break` without a surrounding loop");
builder.create<cf::BranchOp>(loc, context.loopStack.back().breakBlock);
setTerminated();
return success(); return success();
} }
@ -352,6 +484,7 @@ struct StmtVisitor {
} // namespace } // namespace
LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) { LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) {
assert(builder.getInsertionBlock());
auto loc = convertLocation(stmt.sourceRange); auto loc = convertLocation(stmt.sourceRange);
return stmt.visit(StmtVisitor(*this, loc)); return stmt.visit(StmtVisitor(*this, loc));
} }

View File

@ -440,11 +440,14 @@ struct ModuleVisitor : public BaseVisitor {
LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) { LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
auto procOp = builder.create<moore::ProcedureOp>( auto procOp = builder.create<moore::ProcedureOp>(
loc, convertProcedureKind(procNode.procedureKind)); loc, convertProcedureKind(procNode.procedureKind));
procOp.getBodyRegion().emplaceBlock();
OpBuilder::InsertionGuard guard(builder); OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToEnd(procOp.getBody()); builder.setInsertionPointToEnd(&procOp.getBody().emplaceBlock());
Context::ValueSymbolScope scope(context.valueSymbols); Context::ValueSymbolScope scope(context.valueSymbols);
return context.convertStatement(procNode.getBody()); if (failed(context.convertStatement(procNode.getBody())))
return failure();
if (builder.getBlock())
builder.create<moore::ReturnOp>(loc);
return success();
} }
// Handle parameters. // Handle parameters.
@ -913,15 +916,15 @@ Context::convertFunction(const slang::ast::SubroutineSymbol &subroutine) {
// If there was no explicit return statement provided by the user, insert a // If there was no explicit return statement provided by the user, insert a
// default one. // default one.
if (block.empty() || !block.back().hasTrait<OpTrait::IsTerminator>()) { if (builder.getBlock()) {
if (returnVar && !subroutine.getReturnType().isVoid()) { if (returnVar && !subroutine.getReturnType().isVoid()) {
returnVar = builder.create<moore::ReadOp>(returnVar.getLoc(), returnVar); Value read = builder.create<moore::ReadOp>(returnVar.getLoc(), returnVar);
builder.create<mlir::func::ReturnOp>(lowering->op.getLoc(), returnVar); builder.create<mlir::func::ReturnOp>(lowering->op.getLoc(), read);
} else { } else {
builder.create<mlir::func::ReturnOp>(lowering->op.getLoc(), ValueRange{}); builder.create<mlir::func::ReturnOp>(lowering->op.getLoc(), ValueRange{});
} }
} }
if (returnVar.use_empty()) if (returnVar && returnVar.use_empty())
returnVar.getDefiningOp()->erase(); returnVar.getDefiningOp()->erase();
return success(); return success();
} }

View File

@ -44,7 +44,8 @@ void SimplifyProceduresPass::runOnOperation() {
// Use to collect blocking assignments that have been replaced by a "shadow" // Use to collect blocking assignments that have been replaced by a "shadow"
// variable. // variable.
DenseSet<Operation *> assignOps; DenseSet<Operation *> assignOps;
for (auto &nestedOp : procedureOp) { procedureOp.walk([&](Operation *op) {
auto &nestedOp = *op;
// Only create a "shadow" varaible for the global variable used by other // Only create a "shadow" varaible for the global variable used by other
// operations in the procedure body. // operations in the procedure body.
if (isa<ReadOp>(nestedOp) && if (isa<ReadOp>(nestedOp) &&
@ -54,14 +55,14 @@ void SimplifyProceduresPass::runOnOperation() {
DenseSet<Operation *> users; DenseSet<Operation *> users;
for (auto *user : nestedOp.getOperand(0).getUsers()) for (auto *user : nestedOp.getOperand(0).getUsers())
// Ensuring don't handle the users existing in another procedure body. // Ensuring don't handle the users existing in another procedure body.
if (user->getBlock() == procedureOp.getBody()) if (procedureOp->isAncestor(user))
users.insert(user); users.insert(user);
// Because the operand of moore.event_wait is net. // Because the operand of moore.event_wait is net.
if (auto varOp = llvm::dyn_cast_or_null<VariableOp>( if (auto varOp = llvm::dyn_cast_or_null<VariableOp>(
nestedOp.getOperand(0).getDefiningOp())) { nestedOp.getOperand(0).getDefiningOp())) {
auto resultType = varOp.getResult().getType(); auto resultType = varOp.getResult().getType();
builder.setInsertionPointToStart(procedureOp.getBody()); builder.setInsertionPointToStart(&procedureOp.getBody().front());
auto readOp = builder.create<ReadOp>( auto readOp = builder.create<ReadOp>(
nestedOp.getLoc(), cast<RefType>(resultType).getNestedType(), nestedOp.getLoc(), cast<RefType>(resultType).getNestedType(),
varOp.getResult()); varOp.getResult());
@ -96,6 +97,6 @@ void SimplifyProceduresPass::runOnOperation() {
builder.clearInsertionPoint(); builder.clearInsertionPoint();
assignOps.erase(assignOp); assignOps.erase(assignOp);
} }
} });
}); });
} }

View File

@ -231,220 +231,324 @@ module Basic;
MyStruct s3 = ~s2; MyStruct s3 = ~s2;
endmodule endmodule
// CHECK-LABEL: func.func private @dummyA(
// CHECK-LABEL: func.func private @dummyB(
// CHECK-LABEL: func.func private @dummyC(
function void dummyA(); endfunction
function void dummyB(); endfunction
function void dummyC(); endfunction
// CHECK-LABEL: func.func private @ConditionalStatements(
// CHECK-SAME: %arg0: !moore.i1
// CHECK-SAME: %arg1: !moore.i1
function void ConditionalStatements(bit x, bit y);
// CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1
// CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]]
// CHECK: ^[[BB1]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB2]]
// CHECK: ^[[BB2]]:
if (x) dummyA();
// CHECK: [[COND1:%.+]] = moore.and %arg0, %arg1
// CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1
// CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]]
// CHECK: ^[[BB1]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB2]]
// CHECK: ^[[BB2]]:
if (x &&& y) dummyA();
// CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1
// CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]]
// CHECK: ^[[BB1]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB3:.+]]
// CHECK: ^[[BB2]]:
// CHECK: call @dummyB()
// CHECK: cf.br ^[[BB3]]
// CHECK: ^[[BB3]]:
if (x)
dummyA();
else
dummyB();
// CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1
// CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]]
// CHECK: ^[[BB1]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB6:.+]]
// CHECK: ^[[BB2]]:
// CHECK: [[COND:%.+]] = moore.conversion %arg1 : !moore.i1 -> i1
// CHECK: cf.cond_br [[COND]], ^[[BB3:.+]], ^[[BB4:.+]]
// CHECK: ^[[BB3]]:
// CHECK: call @dummyB()
// CHECK: cf.br ^[[BB5:.+]]
// CHECK: ^[[BB4]]:
// CHECK: call @dummyC()
// CHECK: cf.br ^[[BB5]]
// CHECK: ^[[BB5]]:
// CHECK: cf.br ^[[BB6]]
// CHECK: ^[[BB6]]:
if (x)
dummyA();
else if (y)
dummyB();
else
dummyC();
// CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1
// CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]]
// CHECK: ^[[BB1]]:
// CHECK: return
// CHECK: ^[[BB2]]:
if (x) return;
endfunction
// CHECK-LABEL: func.func private @CaseStatements(
// CHECK-SAME: %arg0: !moore.i32
// CHECK-SAME: %arg1: !moore.i32
// CHECK-SAME: %arg2: !moore.i32
// CHECK-SAME: %arg3: !moore.i32
function void CaseStatements(int x, int a, int b, int c);
// CHECK: [[FLAG:%.+]] = moore.add %arg0, %arg0
case (x + x)
// CHECK: [[COND1:%.+]] = moore.case_eq [[FLAG]], %arg1
// CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1
// CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]]
// CHECK: ^[[BB1]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB3:.+]]
a: dummyA();
// CHECK: ^[[BB2]]:
// CHECK: call @dummyB()
// CHECK: cf.br ^[[BB3]]
default: dummyB();
// CHECK: ^[[BB3]]:
endcase
// CHECK: [[COND1:%.+]] = moore.case_eq %arg0, %arg1
// CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1
// CHECK: cf.cond_br [[COND2]], ^[[BB_MATCH:.+]], ^[[BB1:.+]]
// CHECK: ^[[BB1]]:
// CHECK: [[TMP:%.+]] = moore.add %arg2, %arg3
// CHECK: [[COND1:%.+]] = moore.case_eq %arg0, [[TMP]]
// CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1
// CHECK: cf.cond_br [[COND2]], ^[[BB_MATCH:.+]], ^[[BB2:.+]]
// CHECK: ^[[BB_MATCH]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB_EXIT:.+]]
// CHECK: ^[[BB2]]:
// CHECK: call @dummyB()
// CHECK: cf.br ^[[BB_EXIT]]
// CHECK: ^[[BB_EXIT]]:
case (x)
a, (b+c): dummyA();
default: dummyB();
endcase
// CHECK: [[COND1:%.+]] = moore.casez_eq %arg0, %arg1
// CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1
// CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]]
// CHECK: ^[[BB1]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB3:.+]]
// CHECK: ^[[BB2]]:
// CHECK: cf.br ^[[BB3]]
// CHECK: ^[[BB3]]:
casez (x)
a: dummyA();
endcase
// CHECK: [[COND1:%.+]] = moore.casexz_eq %arg0, %arg1
// CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1
// CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]]
// CHECK: ^[[BB1]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB3:.+]]
// CHECK: ^[[BB2]]:
// CHECK: cf.br ^[[BB3]]
// CHECK: ^[[BB3]]:
casex (x)
a: dummyA();
endcase
endfunction
// CHECK-LABEL: func.func private @ForLoopStatements(
// CHECK-SAME: %arg0: !moore.i32
// CHECK-SAME: %arg1: !moore.i32
// CHECK-SAME: %arg2: !moore.i1
function void ForLoopStatements(int a, int b, bit c);
int x;
// CHECK: moore.blocking_assign %x, %arg0
// CHECK: cf.br ^[[BB_CHECK:.+]]
// CHECK: ^[[BB_CHECK]]:
// CHECK: [[TMP1:%.+]] = moore.read %x
// CHECK: [[TMP2:%.+]] = moore.slt [[TMP1]], %arg1
// CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1
// CHECK: cf.cond_br [[TMP3]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]]
// CHECK: ^[[BB_BODY]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB_STEP:.+]]
// CHECK: ^[[BB_STEP]]:
// CHECK: call @dummyB()
// CHECK: cf.br ^[[BB_CHECK]]
// CHECK: ^[[BB_EXIT]]:
for (x = a; x < b; dummyB()) dummyA();
// CHECK: %y = moore.variable %arg0 : <i32>
// CHECK: cf.br ^[[BB_CHECK:.+]]
// CHECK: ^[[BB_CHECK]]:
// CHECK: [[TMP1:%.+]] = moore.read %y
// CHECK: [[TMP2:%.+]] = moore.slt [[TMP1]], %arg1
// CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1
// CHECK: cf.cond_br [[TMP3]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]]
// CHECK: ^[[BB_BODY]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB_STEP:.+]]
// CHECK: ^[[BB_STEP]]:
// CHECK: call @dummyB()
// CHECK: cf.br ^[[BB_CHECK]]
// CHECK: ^[[BB_EXIT]]:
for (int y = a; y < b; dummyB()) dummyA();
// CHECK: cf.br ^[[BB_CHECK:.+]]
// CHECK: ^[[BB_CHECK]]:
// CHECK: [[TMP:%.+]] = moore.conversion %arg2 : !moore.i1 -> i1
// CHECK: cf.cond_br [[TMP]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]]
// CHECK: ^[[BB_BODY]]:
// CHECK: [[TMP:%.+]] = moore.conversion %arg2 : !moore.i1 -> i1
// CHECK: cf.cond_br [[TMP]], ^[[BB_TRUE:.+]], ^[[BB_FALSE:.+]]
// CHECK: ^[[BB_TRUE]]:
// CHECK: cf.br ^[[BB_STEP:.+]]
// CHECK: ^[[BB_FALSE]]:
// CHECK: cf.br ^[[BB_EXIT]]
// CHECK: ^[[BB_STEP]]:
// CHECK: call @dummyB()
// CHECK: cf.br ^[[BB_CHECK]]
// CHECK: ^[[BB_EXIT]]:
for (; c; dummyB())
if (c)
continue;
else
break;
endfunction
// CHECK-LABEL: func.func private @ForeverLoopStatements(
// CHECK-SAME: %arg0: !moore.i1
// CHECK-SAME: %arg1: !moore.i1
function void ForeverLoopStatements(bit x, bit y);
// CHECK: cf.br ^[[BB_BODY:.+]]
// CHECK: ^[[BB_BODY]]:
forever begin
if (x) begin
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB_EXIT:.+]]
dummyA();
break;
end else begin
// CHECK: call @dummyB()
// CHECK: cf.br ^[[BB_BODY]]
dummyB();
continue;
end
end
// CHECK: ^[[BB_EXIT]]:
// CHECK: cf.br ^[[BB_BODY:.+]]
// CHECK: ^[[BB_BODY]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB_BODY]]
forever dummyA();
endfunction
// CHECK-LABEL: func.func private @WhileLoopStatements(
// CHECK-SAME: %arg0: !moore.i1
// CHECK-SAME: %arg1: !moore.i1
function void WhileLoopStatements(bit x, bit y);
// CHECK: cf.br ^[[BB_CHECK:.+]]
// CHECK: ^[[BB_CHECK]]:
// CHECK: [[TMP:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1
// CHECK: cf.cond_br [[TMP]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]]
// CHECK: ^[[BB_BODY]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB_CHECK]]
// CHECK: ^[[BB_EXIT]]:
while (x) dummyA();
// CHECK: cf.br ^[[BB_BODY:.+]]
// CHECK: ^[[BB_BODY]]:
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB_CHECK:.+]]
// CHECK: ^[[BB_CHECK]]:
// CHECK: [[TMP:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1
// CHECK: cf.cond_br [[TMP]], ^[[BB_BODY]], ^[[BB_EXIT:.+]]
// CHECK: ^[[BB_EXIT]]:
do dummyA(); while (x);
// CHECK: cf.br ^[[BB_CHECK:.+]]
// CHECK: ^[[BB_CHECK]]:
// CHECK: [[TMP:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1
// CHECK: cf.cond_br [[TMP]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]]
// CHECK: ^[[BB_BODY]]:
while (x) begin
if (y) begin
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB_EXIT]]
dummyA();
break;
end else begin
// CHECK: call @dummyB()
// CHECK: cf.br ^[[BB_CHECK]]
dummyB();
continue;
end
end
// CHECK: ^[[BB_EXIT]]:
endfunction
// CHECK-LABEL: func.func private @RepeatLoopStatements(
// CHECK-SAME: %arg0: !moore.i32
// CHECK-SAME: %arg1: !moore.i1
function void RepeatLoopStatements(int x, bit y);
// CHECK: cf.br ^[[BB_CHECK:.+]](%arg0 : !moore.i32)
repeat (x) begin
// CHECK: ^[[BB_CHECK]]([[COUNT:%.+]]: !moore.i32):
// CHECK: [[TMP1:%.+]] = moore.bool_cast [[COUNT]] : i32 -> i1
// CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1
// CHECK: cf.cond_br [[TMP2]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]]
// CHECK: ^[[BB_BODY]]:
if (y) begin
// CHECK: call @dummyA()
// CHECK: cf.br ^[[BB_EXIT]]
dummyA();
break;
end else begin
// CHECK: call @dummyB()
// CHECK: cf.br ^[[BB_STEP:.+]]
dummyB();
continue;
end
// CHECK: ^[[BB_STEP]]:
// CHECK: [[TMP1:%.+]] = moore.constant 1 : i32
// CHECK: [[TMP2:%.+]] = moore.sub [[COUNT]], [[TMP1]] : i32
// CHECK: cf.br ^[[BB_CHECK]]([[TMP2]] : !moore.i32)
end
// CHECK: ^[[BB_EXIT]]:
endfunction
// CHECK-LABEL: moore.module @Statements // CHECK-LABEL: moore.module @Statements
module Statements; module Statements;
bit x, y, z; bit x, y, z;
int i; int i;
initial begin initial begin
// CHECK: %a = moore.variable : <i32> // CHECK: %a = moore.variable : <i32>
automatic int a; automatic int a;
// CHECK: [[TMP1:%.+]] = moore.read %a // CHECK: [[TMP1:%.+]] = moore.read %a
// CHECK moore.blocking_assign %i, [[TMP1]] : i32 // CHECK moore.blocking_assign %i, [[TMP1]] : i32
i = a; i = a;
//===------------------------------------------------------------------===//
// Conditional statements
// CHECK: [[TMP1:%.+]] = moore.read %x
// CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1
// CHECK: scf.if [[COND]] {
// CHECK: [[TMP2:%.+]] = moore.read %y :
// CHECK: moore.blocking_assign %x, [[TMP2]] : i1
// CHECK: }
if (x) x = y;
// CHECK: [[TMP1:%.+]] = moore.read %x
// CHECK: [[TMP2:%.+]] = moore.read %y
// CHECK: [[COND0:%.+]] = moore.and [[TMP1]], [[TMP2]]
// CHECK: [[COND1:%.+]] = moore.conversion [[COND0]] : !moore.i1 -> i1
// CHECK: scf.if [[COND1]] {
// CHECK: [[TMP3:%.+]] = moore.read %y
// CHECK: moore.blocking_assign %x, [[TMP3]]
// CHECK: }
if (x &&& y) x = y;
// CHECK: [[TMP1:%.+]] = moore.read %x
// CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1
// CHECK: scf.if [[COND]] {
// CHECK: [[TMP2:%.+]] = moore.read %z
// CHECK: moore.blocking_assign %x, [[TMP2]]
// CHECK: } else {
// CHECK: [[TMP3:%.+]] = moore.read %y
// CHECK: moore.blocking_assign %x, [[TMP3]]
// CHECK: }
if (x) x = z; else x = y;
// CHECK: [[TMP1:%.+]] = moore.read %x
// CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1
// CHECK: scf.if [[COND]] {
// CHECK: [[TMP2:%.+]] = moore.read %x
// CHECK: moore.blocking_assign %x, [[TMP2]]
// CHECK: } else {
// CHECK: [[TMP3:%.+]] = moore.read %y
// CHECK: [[COND:%.+]] = moore.conversion [[TMP3]] : !moore.i1 -> i1
// CHECK: scf.if [[COND]] {
// CHECK: [[TMP4:%.+]] = moore.read %y
// CHECK: moore.blocking_assign %x, [[TMP4]]
// CHECK: } else {
// CHECK: [[TMP5:%.+]] = moore.read %z
// CHECK: moore.blocking_assign %x, [[TMP5]]
// CHECK: }
// CHECK: }
if (x) begin
x = x;
end else if (y) begin
x = y;
end else begin
x = z;
end
//===------------------------------------------------------------------===//
// Case statements
// CHECK: [[TMP1:%.+]] = moore.read %x
// CHECK: [[TMP2:%.+]] = moore.read %x
// CHECK: [[TMP3:%.+]] = moore.eq [[TMP1]], [[TMP2]] : i1 -> i1
// CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i1 -> i1
// CHECK: scf.if [[TMP4]] {
// CHECK: [[TMP5:%.+]] = moore.read %x
// CHECK: moore.blocking_assign %x, [[TMP5]] : i1
// CHECK: }
// CHECK: [[TMP6:%.+]] = moore.read %x
// CHECK: [[TMP7:%.+]] = moore.eq [[TMP1]], [[TMP6]] : i1 -> i1
// CHECK: [[TMP8:%.+]] = moore.read %y
// CHECK: [[TMP9:%.+]] = moore.eq [[TMP1]], [[TMP8]] : i1 -> i1
// CHECK: [[TMP10:%.+]] = moore.or [[TMP7]], [[TMP9]] : i1
// CHECK: [[TMP11:%.+]] = moore.conversion [[TMP10]] : !moore.i1 -> i1
// CHECK: scf.if [[TMP11]] {
// CHECK: [[TMP12:%.+]] = moore.read %y
// CHECK: moore.blocking_assign %x, [[TMP12]] : i1
// CHECK: }
case (x)
x: x = x;
x, y: x = y;
endcase
// CHECK: [[TMP1:%.+]] = moore.read %x
// CHECK: [[TMP2:%.+]] = moore.read %x
// CHECK: [[TMP3:%.+]] = moore.eq [[TMP1]], [[TMP2]] : i1 -> i1
// CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i1 -> i1
// CHECK: scf.if [[TMP4]] {
// CHECK: [[TMP5:%.+]] = moore.read %x
// CHECK: moore.blocking_assign %x, [[TMP5]] : i1
// CHECK: }
// CHECK: [[TMP6:%.+]] = moore.read %x
// CHECK: [[TMP7:%.+]] = moore.eq [[TMP1]], [[TMP6]] : i1 -> i1
// CHECK: [[TMP8:%.+]] = moore.read %y
// CHECK: [[TMP9:%.+]] = moore.eq [[TMP1]], [[TMP8]] : i1 -> i1
// CHECK: [[TMP10:%.+]] = moore.or [[TMP7]], [[TMP9]] : i1
// CHECK: [[TMP11:%.+]] = moore.conversion [[TMP10]] : !moore.i1 -> i1
// CHECK: scf.if [[TMP11]] {
// CHECK: [[TMP12:%.+]] = moore.read %y
// CHECK: moore.blocking_assign %x, [[TMP12]] : i1
// CHECK: }
// CHECK: [[TMP13:%.+]] = moore.read %z
// CHECK: [[TMP14:%.+]] = moore.eq [[TMP1]], [[TMP13]] : i1 -> i1
// CHECK: [[TMP15:%.+]] = moore.conversion [[TMP14]] : !moore.i1 -> i1
// CHECK: scf.if [[TMP15]] {
// CHECK: [[TMP16:%.+]] = moore.read %z
// CHECK: moore.blocking_assign %x, [[TMP16]] : i1
// CHECK: }
// CHECK: [[TMP17:%.+]] = moore.or [[TMP10]], [[TMP14]] : i1
// CHECK: [[TMP18:%.+]] = moore.or [[TMP3]], [[TMP17]] : i1
// CHECK: [[TMP19:%.+]] = moore.not [[TMP18]] : i1
// CHECK: [[TMP20:%.+]] = moore.conversion [[TMP19]] : !moore.i1 -> i1
// CHECK: scf.if [[TMP20]] {
// CHECK: [[TMP21:%.+]] = moore.read %x
// CHECK: moore.blocking_assign %x, [[TMP21]] : i1
// CHECK: }
case (x)
x: x = x;
x, y: x = y;
z: x = z;
default x = x;
endcase
//===------------------------------------------------------------------===//
// Loop statements
// CHECK: [[TMP1:%.+]] = moore.read %x
// CHECK: moore.blocking_assign %y, [[TMP1]] : i1
// CHECK: scf.while : () -> () {
// CHECK: [[TMP2:%.+]] = moore.read %x
// CHECK: [[COND:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1
// CHECK: scf.condition([[COND]])
// CHECK: } do {
// CHECK: [[TMP3:%.+]] = moore.read %y
// CHECK: moore.blocking_assign %x, [[TMP3]] : i1
// CHECK: [[TMP4:%.+]] = moore.read %z
// CHECK: moore.blocking_assign %x, [[TMP4]] : i1
// CHECK: scf.yield
// CHECK: }
for (y = x; x; x = z) x = y;
// CHECK: [[TMP:%.+]] = moore.constant true : i1
// CHECK: %iv = moore.variable [[TMP]] : <i1>
// CHECK: scf.while : () -> () {
// CHECK: [[TMP:%.+]] = moore.read %iv
// CHECK: [[COND:%.+]] = moore.conversion [[TMP]] : !moore.i1 -> i1
// CHECK: scf.condition([[COND]])
// CHECK: } do {
// CHECK: [[TMP:%.+]] = moore.read %iv
// CHECK: moore.blocking_assign %y, [[TMP]] : i1
// CHECK: [[TMP:%.+]] = moore.read %iv
// CHECK: moore.blocking_assign %x, [[TMP]] : i1
// CHECK: scf.yield
// CHECK: }
for (bit iv = '1; iv; x = iv) y = iv;
// CHECK: [[TMP1:%.+]] = moore.read %i
// CHECK: scf.while (%arg0 = [[TMP1]]) : (!moore.i32) -> !moore.i32 {
// CHECK: [[TMP2:%.+]] = moore.bool_cast %arg0 : i32 -> i1
// CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1
// CHECK: scf.condition([[TMP3]]) %arg0 : !moore.i32
// CHECK: } do {
// CHECK: ^bb0(%arg0: !moore.i32):
// CHECK: [[TMP4:%.+]] = moore.read %y
// CHECK: moore.blocking_assign %x, [[TMP4]] : i1
// CHECK: [[TMP5:%.+]] = moore.constant 1 : i32
// CHECK: [[TMP6:%.+]] = moore.sub %arg0, [[TMP5]] : i32
// CHECK: scf.yield [[TMP6]] : !moore.i32
// CHECK: }
repeat (i) x = y;
// CHECK: scf.while : () -> () {
// CHECK: [[TMP1:%.+]] = moore.read %x
// CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1
// CHECK: scf.condition([[COND]])
// CHECK: } do {
// CHECK: [[TMP2:%.+]] = moore.read %y
// CHECK: moore.blocking_assign %x, [[TMP2]] : i1
// CHECK: scf.yield
// CHECK: }
while (x) x = y;
// CHECK: scf.while : () -> () {
// CHECK: [[TMP1:%.+]] = moore.read %y
// CHECK: moore.blocking_assign %x, [[TMP1]] : i1
// CHECK: [[TMP2:%.+]] = moore.read %x
// CHECK: [[COND:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1
// CHECK: scf.condition([[COND]])
// CHECK: } do {
// CHECK: scf.yield
// CHECK: }
do x = y; while (x);
// CHECK: scf.while : () -> () {
// CHECK: %true = hw.constant true
// CHECK: scf.condition(%true)
// CHECK: } do {
// CHECK: [[TMP1:%.+]] = moore.read %y
// CHECK: moore.blocking_assign %x, [[TMP1]] : i1
// CHECK: scf.yield
// CHECK: }
forever x = y;
//===------------------------------------------------------------------===// //===------------------------------------------------------------------===//
// Assignments // Assignments

View File

@ -77,12 +77,12 @@ moore.module @Module() {
// CHECK: moore.procedure always_comb { // CHECK: moore.procedure always_comb {
// CHECK: moore.procedure always_latch { // CHECK: moore.procedure always_latch {
// CHECK: moore.procedure always_ff { // CHECK: moore.procedure always_ff {
moore.procedure initial {} moore.procedure initial { moore.return }
moore.procedure final {} moore.procedure final { moore.return }
moore.procedure always {} moore.procedure always { moore.return }
moore.procedure always_comb {} moore.procedure always_comb { moore.return }
moore.procedure always_latch {} moore.procedure always_latch { moore.return }
moore.procedure always_ff {} moore.procedure always_ff { moore.return }
// CHECK: %[[TMP1:.+]] = moore.read %v2 // CHECK: %[[TMP1:.+]] = moore.read %v2
// CHECK: moore.assign %v1, %[[TMP1]] : i1 // CHECK: moore.assign %v1, %[[TMP1]] : i1
@ -100,6 +100,7 @@ moore.module @Module() {
moore.nonblocking_assign %v1, %4 : i1 moore.nonblocking_assign %v1, %4 : i1
// CHECK: %a = moore.variable : <i32> // CHECK: %a = moore.variable : <i32>
%a = moore.variable : <i32> %a = moore.variable : <i32>
moore.return
} }
} }

View File

@ -43,6 +43,7 @@
// CHECK: moore.nonblocking_assign %v, %[[TMP2]] : l42 // CHECK: moore.nonblocking_assign %v, %[[TMP2]] : l42
moore.nonblocking_assign %7, %8 : l9002 moore.nonblocking_assign %7, %8 : l9002
} }
moore.return
} }
moore.output moore.output
} }
@ -73,6 +74,7 @@ moore.module @Nested() {
// CHECK: %[[TMP5:.+]] = moore.extract %[[TMP2]] from 0 : i96 -> i32 // CHECK: %[[TMP5:.+]] = moore.extract %[[TMP2]] from 0 : i96 -> i32
// CHECK: moore.blocking_assign %y, %[[TMP5]] : i32 // CHECK: moore.blocking_assign %y, %[[TMP5]] : i32
moore.blocking_assign %3, %6 : i96 moore.blocking_assign %3, %6 : i96
moore.return
} }
moore.output moore.output
} }

View File

@ -29,5 +29,6 @@ moore.module @LocalVar() {
moore.blocking_assign %a, %6 : i32 moore.blocking_assign %a, %6 : i32
%7 = moore.read %a : <i32> %7 = moore.read %a : <i32>
moore.blocking_assign %y, %7 : i32 moore.blocking_assign %y, %7 : i32
moore.return
} }
} }

View File

@ -33,6 +33,8 @@ moore.module @Foo() {
%3 = moore.constant 1 : i32 %3 = moore.constant 1 : i32
%4 = moore.add %2, %3 : i32 %4 = moore.add %2, %3 : i32
moore.blocking_assign %a, %4 : i32 moore.blocking_assign %a, %4 : i32
moore.return
} }
// CHECK: moore.procedure always_comb // CHECK: moore.procedure always_comb
@ -44,5 +46,7 @@ moore.module @Foo() {
// CHECK: moore.blocking_assign %y, [[TMP]] // CHECK: moore.blocking_assign %y, [[TMP]]
%0 = moore.read %a : <i32> %0 = moore.read %a : <i32>
moore.blocking_assign %y, %0 : i32 moore.blocking_assign %y, %0 : i32
moore.return
} }
} }