mirror of https://github.com/llvm/circt.git
[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:
parent
bec0deab4b
commit
5cc69bd6b1
|
@ -131,8 +131,6 @@ def ProcedureKindAttr: I32EnumAttr<"ProcedureKind", "Procedure kind",
|
|||
}
|
||||
|
||||
def ProcedureOp : MooreOp<"procedure", [
|
||||
SingleBlock,
|
||||
NoTerminator,
|
||||
NoRegionArguments,
|
||||
RecursiveMemoryEffects,
|
||||
RecursivelySpeculatable
|
||||
|
@ -172,15 +170,22 @@ def ProcedureOp : MooreOp<"procedure", [
|
|||
See IEEE 1800-2017 § 9.2 "Structured procedures".
|
||||
}];
|
||||
|
||||
let regions = (region SizedRegion<1>:$bodyRegion);
|
||||
let arguments = (ins ProcedureKindAttr:$kind);
|
||||
let results = (outs);
|
||||
let regions = (region AnyRegion:$body);
|
||||
|
||||
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
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -812,6 +817,16 @@ class CaseEqOpBase<string mnemonic> : MooreOp<mnemonic, [
|
|||
`case_ne` returns 1). `case_eq` corresponds to the `===` operator and
|
||||
`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".
|
||||
}];
|
||||
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 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, [
|
||||
Pure,
|
||||
|
@ -1259,4 +1280,5 @@ def YieldOp : MooreOp<"yield", [
|
|||
}];
|
||||
let hasVerifier = 1;
|
||||
}
|
||||
|
||||
#endif // CIRCT_DIALECT_MOORE_MOOREOPS
|
||||
|
|
|
@ -264,8 +264,8 @@ LogicalResult ImportDriver::importVerilog(ModuleOp module) {
|
|||
return success();
|
||||
|
||||
// Traverse the parsed Verilog AST and map it to the equivalent CIRCT ops.
|
||||
mlirContext->loadDialect<moore::MooreDialect, hw::HWDialect, scf::SCFDialect,
|
||||
func::FuncDialect>();
|
||||
mlirContext->loadDialect<moore::MooreDialect, hw::HWDialect,
|
||||
cf::ControlFlowDialect, func::FuncDialect>();
|
||||
auto conversionTimer = ts.nest("Verilog to dialect mapping");
|
||||
Context context(*compilation, module, driver.sourceManager, bufferFilePaths);
|
||||
if (failed(context.convertCompilation()))
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
#include "circt/Conversion/ImportVerilog.h"
|
||||
#include "circt/Dialect/HW/HWOps.h"
|
||||
#include "circt/Dialect/Moore/MooreOps.h"
|
||||
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h"
|
||||
#include "mlir/Dialect/SCF/IR/SCF.h"
|
||||
#include "slang/ast/ASTVisitor.h"
|
||||
#include "llvm/ADT/ScopedHashTable.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
|
@ -46,6 +46,15 @@ struct FunctionLowering {
|
|||
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
|
||||
/// operations. Keeps track of the destination MLIR module, builders, and
|
||||
/// various worklists and utilities needed for conversion.
|
||||
|
@ -136,6 +145,13 @@ struct Context {
|
|||
/// side. This allows expressions to resolve the opaque
|
||||
/// `LValueReferenceExpression`s in the AST.
|
||||
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
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "ImportVerilogInternals.h"
|
||||
#include "llvm/ADT/ScopeExit.h"
|
||||
|
||||
using namespace mlir;
|
||||
using namespace circt;
|
||||
|
@ -22,6 +23,16 @@ struct StmtVisitor {
|
|||
StmtVisitor(Context &context, Location loc)
|
||||
: 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).
|
||||
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
|
||||
// statements.
|
||||
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)))
|
||||
return failure();
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
|
@ -104,81 +121,123 @@ struct StmtVisitor {
|
|||
allConds =
|
||||
builder.create<moore::ConversionOp>(loc, builder.getI1Type(), allConds);
|
||||
|
||||
// Generate the if operation.
|
||||
auto ifOp =
|
||||
builder.create<scf::IfOp>(loc, allConds, stmt.ifFalse != nullptr);
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
// Create the blocks for the true and false branches, and the exit block.
|
||||
Block &exitBlock = createBlock();
|
||||
Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr;
|
||||
Block &trueBlock = createBlock();
|
||||
builder.create<cf::CondBranchOp>(loc, allConds, &trueBlock,
|
||||
falseBlock ? falseBlock : &exitBlock);
|
||||
|
||||
// Generate the "then" body.
|
||||
builder.setInsertionPoint(ifOp.thenYield());
|
||||
// Generate the true branch.
|
||||
builder.setInsertionPointToEnd(&trueBlock);
|
||||
if (failed(context.convertStatement(stmt.ifTrue)))
|
||||
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) {
|
||||
builder.setInsertionPoint(ifOp.elseYield());
|
||||
builder.setInsertionPointToEnd(falseBlock);
|
||||
if (failed(context.convertStatement(*stmt.ifFalse)))
|
||||
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();
|
||||
}
|
||||
|
||||
// Handle case statements.
|
||||
LogicalResult visit(const slang::ast::CaseStatement &caseStmt) {
|
||||
using slang::ast::CaseStatementCondition;
|
||||
auto caseExpr = context.convertRvalueExpression(caseStmt.expr);
|
||||
if (!caseExpr)
|
||||
return failure();
|
||||
|
||||
auto items = caseStmt.items;
|
||||
// Used to generate the condition of the default case statement.
|
||||
SmallVector<Value> defaultConds;
|
||||
// Traverse the case items.
|
||||
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();
|
||||
// Check each case individually. This currently ignores the `unique`,
|
||||
// `unique0`, and `priority` modifiers which would allow for additional
|
||||
// optimizations.
|
||||
auto &exitBlock = createBlock();
|
||||
|
||||
auto newEqOp = builder.create<moore::EqOp>(loc, caseExpr, itemExpr);
|
||||
allConds.push_back(newEqOp);
|
||||
for (const auto &item : caseStmt.items) {
|
||||
// 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();
|
||||
}
|
||||
// Bound all conditions of an item into one.
|
||||
auto cond = allConds.back();
|
||||
allConds.pop_back();
|
||||
while (!allConds.empty()) {
|
||||
cond = builder.create<moore::OrOp>(loc, allConds.back(), cond);
|
||||
allConds.pop_back();
|
||||
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);
|
||||
}
|
||||
// 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);
|
||||
|
||||
// The current block is the fall-through after all conditions have been
|
||||
// checked and nothing matched. Move the match block up before this point
|
||||
// to make the IR easier to read.
|
||||
matchBlock.moveBefore(builder.getInsertionBlock());
|
||||
|
||||
// Generate the code for this item's statement in the match block.
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
builder.setInsertionPoint(ifOp.thenYield());
|
||||
builder.setInsertionPointToEnd(&matchBlock);
|
||||
if (failed(context.convertStatement(*item.stmt)))
|
||||
return failure();
|
||||
if (!isTerminated()) {
|
||||
auto loc = context.convertLocation(item.stmt->sourceRange);
|
||||
builder.create<mlir::cf::BranchOp>(loc, &exitBlock);
|
||||
}
|
||||
// Handle the 'default case' statement if it exists.
|
||||
if (caseStmt.defaultCase) {
|
||||
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);
|
||||
auto ifOp = builder.create<mlir::scf::IfOp>(loc, cond);
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
builder.setInsertionPoint(ifOp.thenYield());
|
||||
|
||||
// Generate the default case if present.
|
||||
if (caseStmt.defaultCase)
|
||||
if (failed(context.convertStatement(*caseStmt.defaultCase)))
|
||||
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();
|
||||
}
|
||||
|
@ -190,127 +249,179 @@ struct StmtVisitor {
|
|||
if (!context.convertRvalueExpression(*initExpr))
|
||||
return failure();
|
||||
|
||||
// Create the while op.
|
||||
auto whileOp = builder.create<scf::WhileOp>(loc, TypeRange{}, ValueRange{});
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
// Create the blocks for the loop condition, body, step, and exit.
|
||||
auto &exitBlock = createBlock();
|
||||
auto &stepBlock = createBlock();
|
||||
auto &bodyBlock = createBlock();
|
||||
auto &checkBlock = createBlock();
|
||||
builder.create<cf::BranchOp>(loc, &checkBlock);
|
||||
|
||||
// In the "before" region, check that the condition holds.
|
||||
builder.createBlock(&whileOp.getBefore());
|
||||
// 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 = context.convertRvalueExpression(*stmt.stopExpr);
|
||||
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{});
|
||||
builder.create<cf::CondBranchOp>(loc, cond, &bodyBlock, &exitBlock);
|
||||
|
||||
// In the "after" region, generate the loop body and step expressions.
|
||||
builder.createBlock(&whileOp.getAfter());
|
||||
// Generate the loop body.
|
||||
builder.setInsertionPointToEnd(&bodyBlock);
|
||||
if (failed(context.convertStatement(stmt.body)))
|
||||
return failure();
|
||||
if (!isTerminated())
|
||||
builder.create<cf::BranchOp>(loc, &stepBlock);
|
||||
|
||||
// Generate the step expressions.
|
||||
builder.setInsertionPointToEnd(&stepBlock);
|
||||
for (auto *stepExpr : stmt.steps)
|
||||
if (!context.convertRvalueExpression(*stepExpr))
|
||||
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();
|
||||
}
|
||||
|
||||
// Handle `repeat` loops.
|
||||
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);
|
||||
if (!count)
|
||||
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.
|
||||
auto *block = builder.createBlock(&whileOp.getBefore(), {}, type, loc);
|
||||
auto counterArg = block->getArgument(0);
|
||||
auto cond = builder.createOrFold<moore::BoolCastOp>(loc, counterArg);
|
||||
// Create the blocks for the loop condition, body, step, and exit.
|
||||
auto &exitBlock = createBlock();
|
||||
auto &stepBlock = createBlock();
|
||||
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);
|
||||
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.
|
||||
block = builder.createBlock(&whileOp.getAfter(), {}, type, loc);
|
||||
// Generate the loop body.
|
||||
builder.setInsertionPointToEnd(&bodyBlock);
|
||||
if (failed(context.convertStatement(stmt.body)))
|
||||
return failure();
|
||||
counterArg = block->getArgument(0);
|
||||
auto constOne = builder.create<moore::ConstantOp>(loc, type, 1);
|
||||
auto subOp = builder.create<moore::SubOp>(loc, counterArg, constOne);
|
||||
builder.create<scf::YieldOp>(loc, ValueRange{subOp});
|
||||
if (!isTerminated())
|
||||
builder.create<cf::BranchOp>(loc, &stepBlock);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Handle `while` loops.
|
||||
LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) {
|
||||
// Create the while op.
|
||||
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();
|
||||
return createWhileLoop(stmt.cond, stmt.body, false);
|
||||
}
|
||||
|
||||
// Handle `do ... while` loops.
|
||||
LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) {
|
||||
// Create the while op.
|
||||
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();
|
||||
return createWhileLoop(stmt.cond, stmt.body, true);
|
||||
}
|
||||
|
||||
// Handle `forever` loops.
|
||||
LogicalResult visit(const slang::ast::ForeverLoopStatement &stmt) {
|
||||
// Create the while op.
|
||||
auto whileOp = builder.create<scf::WhileOp>(loc, TypeRange{}, ValueRange{});
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
// Create the blocks for the loop body and exit.
|
||||
auto &exitBlock = createBlock();
|
||||
auto &bodyBlock = createBlock();
|
||||
builder.create<cf::BranchOp>(loc, &bodyBlock);
|
||||
|
||||
// In the "before" region, return true for the condition.
|
||||
builder.createBlock(&whileOp.getBefore());
|
||||
auto cond = builder.create<hw::ConstantOp>(loc, builder.getI1Type(), 1);
|
||||
builder.create<mlir::scf::ConditionOp>(loc, cond, ValueRange{});
|
||||
// Push the blocks onto the loop stack such that we can continue and break.
|
||||
context.loopStack.push_back({&bodyBlock, &exitBlock});
|
||||
auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); });
|
||||
|
||||
// In the "after" region, generate the loop body.
|
||||
builder.createBlock(&whileOp.getAfter());
|
||||
// Generate the loop body.
|
||||
builder.setInsertionPointToEnd(&bodyBlock);
|
||||
if (failed(context.convertStatement(stmt.body)))
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -325,13 +436,34 @@ struct StmtVisitor {
|
|||
|
||||
// Handle return statements.
|
||||
LogicalResult visit(const slang::ast::ReturnStatement &stmt) {
|
||||
Value expr;
|
||||
if (stmt.expr) {
|
||||
expr = context.convertRvalueExpression(*stmt.expr);
|
||||
auto expr = context.convertRvalueExpression(*stmt.expr);
|
||||
if (!expr)
|
||||
return failure();
|
||||
}
|
||||
builder.create<mlir::func::ReturnOp>(loc, expr);
|
||||
} else {
|
||||
builder.create<mlir::func::ReturnOp>(loc);
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -352,6 +484,7 @@ struct StmtVisitor {
|
|||
} // namespace
|
||||
|
||||
LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) {
|
||||
assert(builder.getInsertionBlock());
|
||||
auto loc = convertLocation(stmt.sourceRange);
|
||||
return stmt.visit(StmtVisitor(*this, loc));
|
||||
}
|
||||
|
|
|
@ -440,11 +440,14 @@ struct ModuleVisitor : public BaseVisitor {
|
|||
LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
|
||||
auto procOp = builder.create<moore::ProcedureOp>(
|
||||
loc, convertProcedureKind(procNode.procedureKind));
|
||||
procOp.getBodyRegion().emplaceBlock();
|
||||
OpBuilder::InsertionGuard guard(builder);
|
||||
builder.setInsertionPointToEnd(procOp.getBody());
|
||||
builder.setInsertionPointToEnd(&procOp.getBody().emplaceBlock());
|
||||
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.
|
||||
|
@ -913,15 +916,15 @@ Context::convertFunction(const slang::ast::SubroutineSymbol &subroutine) {
|
|||
|
||||
// If there was no explicit return statement provided by the user, insert a
|
||||
// default one.
|
||||
if (block.empty() || !block.back().hasTrait<OpTrait::IsTerminator>()) {
|
||||
if (builder.getBlock()) {
|
||||
if (returnVar && !subroutine.getReturnType().isVoid()) {
|
||||
returnVar = builder.create<moore::ReadOp>(returnVar.getLoc(), returnVar);
|
||||
builder.create<mlir::func::ReturnOp>(lowering->op.getLoc(), returnVar);
|
||||
Value read = builder.create<moore::ReadOp>(returnVar.getLoc(), returnVar);
|
||||
builder.create<mlir::func::ReturnOp>(lowering->op.getLoc(), read);
|
||||
} else {
|
||||
builder.create<mlir::func::ReturnOp>(lowering->op.getLoc(), ValueRange{});
|
||||
}
|
||||
}
|
||||
if (returnVar.use_empty())
|
||||
if (returnVar && returnVar.use_empty())
|
||||
returnVar.getDefiningOp()->erase();
|
||||
return success();
|
||||
}
|
||||
|
|
|
@ -44,7 +44,8 @@ void SimplifyProceduresPass::runOnOperation() {
|
|||
// Use to collect blocking assignments that have been replaced by a "shadow"
|
||||
// variable.
|
||||
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
|
||||
// operations in the procedure body.
|
||||
if (isa<ReadOp>(nestedOp) &&
|
||||
|
@ -54,14 +55,14 @@ void SimplifyProceduresPass::runOnOperation() {
|
|||
DenseSet<Operation *> users;
|
||||
for (auto *user : nestedOp.getOperand(0).getUsers())
|
||||
// Ensuring don't handle the users existing in another procedure body.
|
||||
if (user->getBlock() == procedureOp.getBody())
|
||||
if (procedureOp->isAncestor(user))
|
||||
users.insert(user);
|
||||
|
||||
// Because the operand of moore.event_wait is net.
|
||||
if (auto varOp = llvm::dyn_cast_or_null<VariableOp>(
|
||||
nestedOp.getOperand(0).getDefiningOp())) {
|
||||
auto resultType = varOp.getResult().getType();
|
||||
builder.setInsertionPointToStart(procedureOp.getBody());
|
||||
builder.setInsertionPointToStart(&procedureOp.getBody().front());
|
||||
auto readOp = builder.create<ReadOp>(
|
||||
nestedOp.getLoc(), cast<RefType>(resultType).getNestedType(),
|
||||
varOp.getResult());
|
||||
|
@ -96,6 +97,6 @@ void SimplifyProceduresPass::runOnOperation() {
|
|||
builder.clearInsertionPoint();
|
||||
assignOps.erase(assignOp);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -231,6 +231,313 @@ module Basic;
|
|||
MyStruct s3 = ~s2;
|
||||
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
|
||||
module Statements;
|
||||
bit x, y, z;
|
||||
|
@ -242,209 +549,6 @@ module Statements;
|
|||
// CHECK moore.blocking_assign %i, [[TMP1]] : i32
|
||||
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
|
||||
|
||||
|
|
|
@ -77,12 +77,12 @@ moore.module @Module() {
|
|||
// CHECK: moore.procedure always_comb {
|
||||
// CHECK: moore.procedure always_latch {
|
||||
// CHECK: moore.procedure always_ff {
|
||||
moore.procedure initial {}
|
||||
moore.procedure final {}
|
||||
moore.procedure always {}
|
||||
moore.procedure always_comb {}
|
||||
moore.procedure always_latch {}
|
||||
moore.procedure always_ff {}
|
||||
moore.procedure initial { moore.return }
|
||||
moore.procedure final { moore.return }
|
||||
moore.procedure always { moore.return }
|
||||
moore.procedure always_comb { moore.return }
|
||||
moore.procedure always_latch { moore.return }
|
||||
moore.procedure always_ff { moore.return }
|
||||
|
||||
// CHECK: %[[TMP1:.+]] = moore.read %v2
|
||||
// CHECK: moore.assign %v1, %[[TMP1]] : i1
|
||||
|
@ -100,6 +100,7 @@ moore.module @Module() {
|
|||
moore.nonblocking_assign %v1, %4 : i1
|
||||
// CHECK: %a = moore.variable : <i32>
|
||||
%a = moore.variable : <i32>
|
||||
moore.return
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
// CHECK: moore.nonblocking_assign %v, %[[TMP2]] : l42
|
||||
moore.nonblocking_assign %7, %8 : l9002
|
||||
}
|
||||
moore.return
|
||||
}
|
||||
moore.output
|
||||
}
|
||||
|
@ -73,6 +74,7 @@ moore.module @Nested() {
|
|||
// CHECK: %[[TMP5:.+]] = moore.extract %[[TMP2]] from 0 : i96 -> i32
|
||||
// CHECK: moore.blocking_assign %y, %[[TMP5]] : i32
|
||||
moore.blocking_assign %3, %6 : i96
|
||||
moore.return
|
||||
}
|
||||
moore.output
|
||||
}
|
||||
|
|
|
@ -29,5 +29,6 @@ moore.module @LocalVar() {
|
|||
moore.blocking_assign %a, %6 : i32
|
||||
%7 = moore.read %a : <i32>
|
||||
moore.blocking_assign %y, %7 : i32
|
||||
moore.return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ moore.module @Foo() {
|
|||
%3 = moore.constant 1 : i32
|
||||
%4 = moore.add %2, %3 : i32
|
||||
moore.blocking_assign %a, %4 : i32
|
||||
|
||||
moore.return
|
||||
}
|
||||
|
||||
// CHECK: moore.procedure always_comb
|
||||
|
@ -44,5 +46,7 @@ moore.module @Foo() {
|
|||
// CHECK: moore.blocking_assign %y, [[TMP]]
|
||||
%0 = moore.read %a : <i32>
|
||||
moore.blocking_assign %y, %0 : i32
|
||||
|
||||
moore.return
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue