mirror of https://github.com/llvm/circt.git
363 lines
13 KiB
C++
363 lines
13 KiB
C++
//===- Statements.cpp - Slang statement conversion ------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "ImportVerilogInternals.h"
|
|
|
|
using namespace mlir;
|
|
using namespace circt;
|
|
using namespace ImportVerilog;
|
|
|
|
// NOLINTBEGIN(misc-no-recursion)
|
|
namespace {
|
|
struct StmtVisitor {
|
|
Context &context;
|
|
Location loc;
|
|
OpBuilder &builder;
|
|
|
|
StmtVisitor(Context &context, Location loc)
|
|
: context(context), loc(loc), builder(context.builder) {}
|
|
|
|
// Skip empty statements (stray semicolons).
|
|
LogicalResult visit(const slang::ast::EmptyStatement &) { return success(); }
|
|
|
|
// Convert every statement in a statement list. The Verilog syntax follows a
|
|
// similar philosophy as C/C++, where things like `if` and `for` accept a
|
|
// single statement as body. But then a `{...}` block is a valid statement,
|
|
// which allows for the `if {...}` syntax. In Verilog, things like `final`
|
|
// accept a single body statement, but that can be a `begin ... end` block,
|
|
// 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)
|
|
if (failed(context.convertStatement(*stmt)))
|
|
return failure();
|
|
return success();
|
|
}
|
|
|
|
// Inline `begin ... end` blocks into the parent.
|
|
LogicalResult visit(const slang::ast::BlockStatement &stmt) {
|
|
return context.convertStatement(stmt.body);
|
|
}
|
|
|
|
// Handle expression statements.
|
|
LogicalResult visit(const slang::ast::ExpressionStatement &stmt) {
|
|
auto value = context.convertRvalueExpression(stmt.expr);
|
|
if (!value)
|
|
return failure();
|
|
|
|
// Expressions like calls to void functions return a dummy value that has no
|
|
// uses. If the returned value is trivially dead, remove it.
|
|
if (auto *defOp = value.getDefiningOp())
|
|
if (isOpTriviallyDead(defOp))
|
|
defOp->erase();
|
|
|
|
return success();
|
|
}
|
|
|
|
// Handle variable declarations.
|
|
LogicalResult visit(const slang::ast::VariableDeclStatement &stmt) {
|
|
const auto &var = stmt.symbol;
|
|
auto type = context.convertType(*var.getDeclaredType());
|
|
if (!type)
|
|
return failure();
|
|
|
|
Value initial;
|
|
if (const auto *init = var.getInitializer()) {
|
|
initial = context.convertRvalueExpression(*init);
|
|
if (!initial)
|
|
return failure();
|
|
}
|
|
|
|
// Collect local temporary variables.
|
|
auto varOp = builder.create<moore::VariableOp>(
|
|
loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
|
|
builder.getStringAttr(var.name), initial);
|
|
context.valueSymbols.insertIntoScope(context.valueSymbols.getCurScope(),
|
|
&var, varOp);
|
|
return success();
|
|
}
|
|
|
|
// Handle if statements.
|
|
LogicalResult visit(const slang::ast::ConditionalStatement &stmt) {
|
|
// Generate the condition. There may be multiple conditions linked with the
|
|
// `&&&` operator.
|
|
Value allConds;
|
|
for (const auto &condition : stmt.conditions) {
|
|
if (condition.pattern)
|
|
return mlir::emitError(loc,
|
|
"match patterns in if conditions not supported");
|
|
auto cond = context.convertRvalueExpression(*condition.expr);
|
|
if (!cond)
|
|
return failure();
|
|
cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
|
|
if (allConds)
|
|
allConds = builder.create<moore::AndOp>(loc, allConds, cond);
|
|
else
|
|
allConds = cond;
|
|
}
|
|
assert(allConds && "slang guarantees at least one condition");
|
|
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);
|
|
|
|
// Generate the "then" body.
|
|
builder.setInsertionPoint(ifOp.thenYield());
|
|
if (failed(context.convertStatement(stmt.ifTrue)))
|
|
return failure();
|
|
|
|
// Generate the "else" body if present.
|
|
if (stmt.ifFalse) {
|
|
builder.setInsertionPoint(ifOp.elseYield());
|
|
if (failed(context.convertStatement(*stmt.ifFalse)))
|
|
return failure();
|
|
}
|
|
|
|
return success();
|
|
}
|
|
|
|
// Handle case statements.
|
|
LogicalResult visit(const slang::ast::CaseStatement &caseStmt) {
|
|
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();
|
|
|
|
auto newEqOp = builder.create<moore::EqOp>(loc, caseExpr, itemExpr);
|
|
allConds.push_back(newEqOp);
|
|
}
|
|
// 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();
|
|
}
|
|
// 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);
|
|
builder.setInsertionPoint(ifOp.thenYield());
|
|
if (failed(context.convertStatement(*item.stmt)))
|
|
return failure();
|
|
}
|
|
// 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());
|
|
if (failed(context.convertStatement(*caseStmt.defaultCase)))
|
|
return failure();
|
|
}
|
|
return success();
|
|
}
|
|
|
|
// Handle `for` loops.
|
|
LogicalResult visit(const slang::ast::ForLoopStatement &stmt) {
|
|
if (!stmt.loopVars.empty())
|
|
return mlir::emitError(loc,
|
|
"variables in for loop initializer not supported");
|
|
|
|
// Generate the initializers.
|
|
for (auto *initExpr : stmt.initializers)
|
|
if (!context.convertRvalueExpression(*initExpr))
|
|
return failure();
|
|
|
|
// 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.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{});
|
|
|
|
// In the "after" region, generate the loop body and step expressions.
|
|
builder.createBlock(&whileOp.getAfter());
|
|
if (failed(context.convertStatement(stmt.body)))
|
|
return failure();
|
|
for (auto *stepExpr : stmt.steps)
|
|
if (!context.convertRvalueExpression(*stepExpr))
|
|
return failure();
|
|
builder.create<mlir::scf::YieldOp>(loc);
|
|
|
|
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);
|
|
cond = builder.create<moore::ConversionOp>(loc, builder.getI1Type(), cond);
|
|
builder.create<scf::ConditionOp>(loc, cond, counterArg);
|
|
|
|
// In the "after" region, generate the loop body and decrement the counter.
|
|
block = builder.createBlock(&whileOp.getAfter(), {}, type, loc);
|
|
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});
|
|
|
|
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();
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// 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);
|
|
|
|
// 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{});
|
|
|
|
// 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 timing control.
|
|
LogicalResult visit(const slang::ast::TimedStatement &stmt) {
|
|
if (failed(context.convertTimingControl(stmt.timing)))
|
|
return failure();
|
|
if (failed(context.convertStatement(stmt.stmt)))
|
|
return failure();
|
|
return success();
|
|
}
|
|
|
|
// Handle return statements.
|
|
LogicalResult visit(const slang::ast::ReturnStatement &stmt) {
|
|
Value expr;
|
|
if (stmt.expr) {
|
|
expr = context.convertRvalueExpression(*stmt.expr);
|
|
if (!expr)
|
|
return failure();
|
|
}
|
|
builder.create<mlir::func::ReturnOp>(loc, expr);
|
|
return success();
|
|
}
|
|
|
|
/// Emit an error for all other statements.
|
|
template <typename T>
|
|
LogicalResult visit(T &&stmt) {
|
|
mlir::emitError(loc, "unsupported statement: ")
|
|
<< slang::ast::toString(stmt.kind);
|
|
return mlir::failure();
|
|
}
|
|
|
|
LogicalResult visitInvalid(const slang::ast::Statement &stmt) {
|
|
mlir::emitError(loc, "invalid statement: ")
|
|
<< slang::ast::toString(stmt.kind);
|
|
return mlir::failure();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) {
|
|
auto loc = convertLocation(stmt.sourceRange);
|
|
return stmt.visit(StmtVisitor(*this, loc));
|
|
}
|
|
// NOLINTEND(misc-no-recursion)
|