mirror of https://github.com/llvm/circt.git
1098 lines
40 KiB
C++
1098 lines
40 KiB
C++
//===- Structure.cpp - Slang hierarchy 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"
|
|
#include "slang/ast/Compilation.h"
|
|
|
|
using namespace circt;
|
|
using namespace ImportVerilog;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Utilities
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static void guessNamespacePrefix(const slang::ast::Symbol &symbol,
|
|
SmallString<64> &prefix) {
|
|
if (symbol.kind != slang::ast::SymbolKind::Package)
|
|
return;
|
|
guessNamespacePrefix(symbol.getParentScope()->asSymbol(), prefix);
|
|
if (!symbol.name.empty()) {
|
|
prefix += symbol.name;
|
|
prefix += "::";
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Base Visitor
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
/// Base visitor which ignores AST nodes that are handled by Slang's name
|
|
/// resolution and type checking.
|
|
struct BaseVisitor {
|
|
Context &context;
|
|
Location loc;
|
|
OpBuilder &builder;
|
|
|
|
BaseVisitor(Context &context, Location loc)
|
|
: context(context), loc(loc), builder(context.builder) {}
|
|
|
|
// Skip semicolons.
|
|
LogicalResult visit(const slang::ast::EmptyMemberSymbol &) {
|
|
return success();
|
|
}
|
|
|
|
// Skip members that are implicitly imported from some other scope for the
|
|
// sake of name resolution, such as enum variant names.
|
|
LogicalResult visit(const slang::ast::TransparentMemberSymbol &) {
|
|
return success();
|
|
}
|
|
|
|
// Skip typedefs.
|
|
LogicalResult visit(const slang::ast::TypeAliasType &) { return success(); }
|
|
LogicalResult visit(const slang::ast::ForwardingTypedefSymbol &) {
|
|
return success();
|
|
}
|
|
|
|
// Skip imports. The AST already has its names resolved.
|
|
LogicalResult visit(const slang::ast::ExplicitImportSymbol &) {
|
|
return success();
|
|
}
|
|
LogicalResult visit(const slang::ast::WildcardImportSymbol &) {
|
|
return success();
|
|
}
|
|
|
|
// Skip type parameters. The Slang AST is already monomorphized.
|
|
LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
|
|
return success();
|
|
}
|
|
|
|
// Skip elaboration system tasks. These are reported directly by Slang.
|
|
LogicalResult visit(const slang::ast::ElabSystemTaskSymbol &) {
|
|
return success();
|
|
}
|
|
|
|
// Handle parameters.
|
|
LogicalResult visit(const slang::ast::ParameterSymbol ¶m) {
|
|
visitParameter(param);
|
|
return success();
|
|
}
|
|
|
|
LogicalResult visit(const slang::ast::SpecparamSymbol ¶m) {
|
|
visitParameter(param);
|
|
return success();
|
|
}
|
|
|
|
template <class Node>
|
|
void visitParameter(const Node ¶m) {
|
|
// If debug info is enabled, try to materialize the parameter's constant
|
|
// value on a best-effort basis and create a `dbg.variable` to track the
|
|
// value.
|
|
if (!context.options.debugInfo)
|
|
return;
|
|
auto value =
|
|
context.materializeConstant(param.getValue(), param.getType(), loc);
|
|
if (!value)
|
|
return;
|
|
if (builder.getInsertionBlock()->getParentOp() == context.intoModuleOp)
|
|
context.orderedRootOps.insert({param.location, value.getDefiningOp()});
|
|
|
|
// Prefix the parameter name with the surrounding namespace to create
|
|
// somewhat sane names in the IR.
|
|
SmallString<64> paramName;
|
|
guessNamespacePrefix(param.getParentScope()->asSymbol(), paramName);
|
|
paramName += param.name;
|
|
|
|
debug::VariableOp::create(builder, loc, builder.getStringAttr(paramName),
|
|
value, Value{});
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Top-Level Item Conversion
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
struct RootVisitor : public BaseVisitor {
|
|
using BaseVisitor::BaseVisitor;
|
|
using BaseVisitor::visit;
|
|
|
|
// Handle packages.
|
|
LogicalResult visit(const slang::ast::PackageSymbol &package) {
|
|
return context.convertPackage(package);
|
|
}
|
|
|
|
// Handle functions and tasks.
|
|
LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
|
|
return context.convertFunction(subroutine);
|
|
}
|
|
|
|
// Emit an error for all other members.
|
|
template <typename T>
|
|
LogicalResult visit(T &&node) {
|
|
mlir::emitError(loc, "unsupported construct: ")
|
|
<< slang::ast::toString(node.kind);
|
|
return failure();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Package Conversion
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
struct PackageVisitor : public BaseVisitor {
|
|
using BaseVisitor::BaseVisitor;
|
|
using BaseVisitor::visit;
|
|
|
|
// Handle functions and tasks.
|
|
LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
|
|
return context.convertFunction(subroutine);
|
|
}
|
|
|
|
/// Emit an error for all other members.
|
|
template <typename T>
|
|
LogicalResult visit(T &&node) {
|
|
mlir::emitError(loc, "unsupported package member: ")
|
|
<< slang::ast::toString(node.kind);
|
|
return failure();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Module Conversion
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static moore::ProcedureKind
|
|
convertProcedureKind(slang::ast::ProceduralBlockKind kind) {
|
|
switch (kind) {
|
|
case slang::ast::ProceduralBlockKind::Always:
|
|
return moore::ProcedureKind::Always;
|
|
case slang::ast::ProceduralBlockKind::AlwaysComb:
|
|
return moore::ProcedureKind::AlwaysComb;
|
|
case slang::ast::ProceduralBlockKind::AlwaysLatch:
|
|
return moore::ProcedureKind::AlwaysLatch;
|
|
case slang::ast::ProceduralBlockKind::AlwaysFF:
|
|
return moore::ProcedureKind::AlwaysFF;
|
|
case slang::ast::ProceduralBlockKind::Initial:
|
|
return moore::ProcedureKind::Initial;
|
|
case slang::ast::ProceduralBlockKind::Final:
|
|
return moore::ProcedureKind::Final;
|
|
}
|
|
llvm_unreachable("all procedure kinds handled");
|
|
}
|
|
|
|
static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind) {
|
|
switch (kind) {
|
|
case slang::ast::NetType::Supply0:
|
|
return moore::NetKind::Supply0;
|
|
case slang::ast::NetType::Supply1:
|
|
return moore::NetKind::Supply1;
|
|
case slang::ast::NetType::Tri:
|
|
return moore::NetKind::Tri;
|
|
case slang::ast::NetType::TriAnd:
|
|
return moore::NetKind::TriAnd;
|
|
case slang::ast::NetType::TriOr:
|
|
return moore::NetKind::TriOr;
|
|
case slang::ast::NetType::TriReg:
|
|
return moore::NetKind::TriReg;
|
|
case slang::ast::NetType::Tri0:
|
|
return moore::NetKind::Tri0;
|
|
case slang::ast::NetType::Tri1:
|
|
return moore::NetKind::Tri1;
|
|
case slang::ast::NetType::UWire:
|
|
return moore::NetKind::UWire;
|
|
case slang::ast::NetType::Wire:
|
|
return moore::NetKind::Wire;
|
|
case slang::ast::NetType::WAnd:
|
|
return moore::NetKind::WAnd;
|
|
case slang::ast::NetType::WOr:
|
|
return moore::NetKind::WOr;
|
|
case slang::ast::NetType::Interconnect:
|
|
return moore::NetKind::Interconnect;
|
|
case slang::ast::NetType::UserDefined:
|
|
return moore::NetKind::UserDefined;
|
|
case slang::ast::NetType::Unknown:
|
|
return moore::NetKind::Unknown;
|
|
}
|
|
llvm_unreachable("all net kinds handled");
|
|
}
|
|
|
|
namespace {
|
|
struct ModuleVisitor : public BaseVisitor {
|
|
using BaseVisitor::visit;
|
|
|
|
// A prefix of block names such as `foo.bar.` to put in front of variable and
|
|
// instance names.
|
|
StringRef blockNamePrefix;
|
|
|
|
ModuleVisitor(Context &context, Location loc, StringRef blockNamePrefix = "")
|
|
: BaseVisitor(context, loc), blockNamePrefix(blockNamePrefix) {}
|
|
|
|
// Skip ports which are already handled by the module itself.
|
|
LogicalResult visit(const slang::ast::PortSymbol &) { return success(); }
|
|
LogicalResult visit(const slang::ast::MultiPortSymbol &) { return success(); }
|
|
|
|
// Skip genvars.
|
|
LogicalResult visit(const slang::ast::GenvarSymbol &genvarNode) {
|
|
return success();
|
|
}
|
|
|
|
// Skip defparams which have been handled by slang.
|
|
LogicalResult visit(const slang::ast::DefParamSymbol &) { return success(); }
|
|
|
|
// Ignore type parameters. These have already been handled by Slang's type
|
|
// checking.
|
|
LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
|
|
return success();
|
|
}
|
|
|
|
// Handle instances.
|
|
LogicalResult visit(const slang::ast::InstanceSymbol &instNode) {
|
|
using slang::ast::ArgumentDirection;
|
|
using slang::ast::AssignmentExpression;
|
|
using slang::ast::MultiPortSymbol;
|
|
using slang::ast::PortSymbol;
|
|
|
|
auto *moduleLowering = context.convertModuleHeader(&instNode.body);
|
|
if (!moduleLowering)
|
|
return failure();
|
|
auto module = moduleLowering->op;
|
|
auto moduleType = module.getModuleType();
|
|
|
|
// Set visibility attribute for instantiated module.
|
|
SymbolTable::setSymbolVisibility(module, SymbolTable::Visibility::Private);
|
|
|
|
// Prepare the values that are involved in port connections. This creates
|
|
// rvalues for input ports and appropriate lvalues for output, inout, and
|
|
// ref ports. We also separate multi-ports into the individual underlying
|
|
// ports with their corresponding connection.
|
|
SmallDenseMap<const PortSymbol *, Value> portValues;
|
|
portValues.reserve(moduleType.getNumPorts());
|
|
|
|
for (const auto *con : instNode.getPortConnections()) {
|
|
const auto *expr = con->getExpression();
|
|
|
|
// Handle unconnected behavior. The expression is null if it have no
|
|
// connection for the port.
|
|
if (!expr) {
|
|
auto *port = con->port.as_if<PortSymbol>();
|
|
if (auto *existingPort =
|
|
moduleLowering->portsBySyntaxNode.lookup(port->getSyntax()))
|
|
port = existingPort;
|
|
|
|
switch (port->direction) {
|
|
case ArgumentDirection::In: {
|
|
auto refType = moore::RefType::get(
|
|
cast<moore::UnpackedType>(context.convertType(port->getType())));
|
|
|
|
if (const auto *net =
|
|
port->internalSymbol->as_if<slang::ast::NetSymbol>()) {
|
|
auto netOp = moore::NetOp::create(
|
|
builder, loc, refType,
|
|
StringAttr::get(builder.getContext(), net->name),
|
|
convertNetKind(net->netType.netKind), nullptr);
|
|
auto readOp = moore::ReadOp::create(builder, loc, netOp);
|
|
portValues.insert({port, readOp});
|
|
} else if (const auto *var =
|
|
port->internalSymbol
|
|
->as_if<slang::ast::VariableSymbol>()) {
|
|
auto varOp = moore::VariableOp::create(
|
|
builder, loc, refType,
|
|
StringAttr::get(builder.getContext(), var->name), nullptr);
|
|
auto readOp = moore::ReadOp::create(builder, loc, varOp);
|
|
portValues.insert({port, readOp});
|
|
} else {
|
|
return mlir::emitError(loc)
|
|
<< "unsupported internal symbol for unconnected port `"
|
|
<< port->name << "`";
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// No need to express unconnected behavior for output port, skip to the
|
|
// next iteration of the loop.
|
|
case ArgumentDirection::Out:
|
|
continue;
|
|
|
|
// TODO: Mark Inout port as unsupported and it will be supported later.
|
|
default:
|
|
return mlir::emitError(loc)
|
|
<< "unsupported port `" << port->name << "` ("
|
|
<< slang::ast::toString(port->kind) << ")";
|
|
}
|
|
}
|
|
|
|
// Unpack the `<expr> = EmptyArgument` pattern emitted by Slang for
|
|
// output and inout ports.
|
|
if (const auto *assign = expr->as_if<AssignmentExpression>())
|
|
expr = &assign->left();
|
|
|
|
// Regular ports lower the connected expression to an lvalue or rvalue and
|
|
// either attach it to the instance as an operand (for input, inout, and
|
|
// ref ports), or assign an instance output to it (for output ports).
|
|
if (auto *port = con->port.as_if<PortSymbol>()) {
|
|
// Convert as rvalue for inputs, lvalue for all others.
|
|
auto value = (port->direction == ArgumentDirection::In)
|
|
? context.convertRvalueExpression(*expr)
|
|
: context.convertLvalueExpression(*expr);
|
|
if (!value)
|
|
return failure();
|
|
if (auto *existingPort =
|
|
moduleLowering->portsBySyntaxNode.lookup(con->port.getSyntax()))
|
|
port = existingPort;
|
|
portValues.insert({port, value});
|
|
continue;
|
|
}
|
|
|
|
// Multi-ports lower the connected expression to an lvalue and then slice
|
|
// it up into multiple sub-values, one for each of the ports in the
|
|
// multi-port.
|
|
if (const auto *multiPort = con->port.as_if<MultiPortSymbol>()) {
|
|
// Convert as lvalue.
|
|
auto value = context.convertLvalueExpression(*expr);
|
|
if (!value)
|
|
return failure();
|
|
unsigned offset = 0;
|
|
for (const auto *port : llvm::reverse(multiPort->ports)) {
|
|
if (auto *existingPort = moduleLowering->portsBySyntaxNode.lookup(
|
|
con->port.getSyntax()))
|
|
port = existingPort;
|
|
unsigned width = port->getType().getBitWidth();
|
|
auto sliceType = context.convertType(port->getType());
|
|
if (!sliceType)
|
|
return failure();
|
|
Value slice = moore::ExtractRefOp::create(
|
|
builder, loc,
|
|
moore::RefType::get(cast<moore::UnpackedType>(sliceType)), value,
|
|
offset);
|
|
// Create the "ReadOp" for input ports.
|
|
if (port->direction == ArgumentDirection::In)
|
|
slice = moore::ReadOp::create(builder, loc, slice);
|
|
portValues.insert({port, slice});
|
|
offset += width;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
mlir::emitError(loc) << "unsupported instance port `" << con->port.name
|
|
<< "` (" << slang::ast::toString(con->port.kind)
|
|
<< ")";
|
|
return failure();
|
|
}
|
|
|
|
// Match the module's ports up with the port values determined above.
|
|
SmallVector<Value> inputValues;
|
|
SmallVector<Value> outputValues;
|
|
inputValues.reserve(moduleType.getNumInputs());
|
|
outputValues.reserve(moduleType.getNumOutputs());
|
|
|
|
for (auto &port : moduleLowering->ports) {
|
|
auto value = portValues.lookup(&port.ast);
|
|
if (port.ast.direction == ArgumentDirection::Out)
|
|
outputValues.push_back(value);
|
|
else
|
|
inputValues.push_back(value);
|
|
}
|
|
|
|
// Insert conversions for input ports.
|
|
for (auto [value, type] :
|
|
llvm::zip(inputValues, moduleType.getInputTypes()))
|
|
if (value.getType() != type)
|
|
value =
|
|
moore::ConversionOp::create(builder, value.getLoc(), type, value);
|
|
|
|
// Here we use the hierarchical value recorded in `Context::valueSymbols`.
|
|
// Then we pass it as the input port with the ref<T> type of the instance.
|
|
for (const auto &hierPath : context.hierPaths[&instNode.body])
|
|
if (auto hierValue = context.valueSymbols.lookup(hierPath.valueSym);
|
|
hierPath.hierName && hierPath.direction == ArgumentDirection::In)
|
|
inputValues.push_back(hierValue);
|
|
|
|
// Create the instance op itself.
|
|
auto inputNames = builder.getArrayAttr(moduleType.getInputNames());
|
|
auto outputNames = builder.getArrayAttr(moduleType.getOutputNames());
|
|
auto inst = moore::InstanceOp::create(
|
|
builder, loc, moduleType.getOutputTypes(),
|
|
builder.getStringAttr(Twine(blockNamePrefix) + instNode.name),
|
|
FlatSymbolRefAttr::get(module.getSymNameAttr()), inputValues,
|
|
inputNames, outputNames);
|
|
|
|
// Record instance's results generated by hierarchical names.
|
|
for (const auto &hierPath : context.hierPaths[&instNode.body])
|
|
if (hierPath.idx && hierPath.direction == ArgumentDirection::Out)
|
|
context.valueSymbols.insert(hierPath.valueSym,
|
|
inst->getResult(*hierPath.idx));
|
|
|
|
// Assign output values from the instance to the connected expression.
|
|
for (auto [lvalue, output] : llvm::zip(outputValues, inst.getOutputs())) {
|
|
if (!lvalue)
|
|
continue;
|
|
Value rvalue = output;
|
|
auto dstType = cast<moore::RefType>(lvalue.getType()).getNestedType();
|
|
if (dstType != rvalue.getType())
|
|
rvalue = moore::ConversionOp::create(builder, loc, dstType, rvalue);
|
|
moore::ContinuousAssignOp::create(builder, loc, lvalue, rvalue);
|
|
}
|
|
|
|
return success();
|
|
}
|
|
|
|
// Handle variables.
|
|
LogicalResult visit(const slang::ast::VariableSymbol &varNode) {
|
|
auto loweredType = context.convertType(*varNode.getDeclaredType());
|
|
if (!loweredType)
|
|
return failure();
|
|
|
|
Value initial;
|
|
if (const auto *init = varNode.getInitializer()) {
|
|
initial = context.convertRvalueExpression(*init, loweredType);
|
|
if (!initial)
|
|
return failure();
|
|
}
|
|
|
|
auto varOp = moore::VariableOp::create(
|
|
builder, loc,
|
|
moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
|
|
builder.getStringAttr(Twine(blockNamePrefix) + varNode.name), initial);
|
|
context.valueSymbols.insert(&varNode, varOp);
|
|
return success();
|
|
}
|
|
|
|
// Handle nets.
|
|
LogicalResult visit(const slang::ast::NetSymbol &netNode) {
|
|
auto loweredType = context.convertType(*netNode.getDeclaredType());
|
|
if (!loweredType)
|
|
return failure();
|
|
|
|
Value assignment;
|
|
if (const auto *init = netNode.getInitializer()) {
|
|
assignment = context.convertRvalueExpression(*init, loweredType);
|
|
if (!assignment)
|
|
return failure();
|
|
}
|
|
|
|
auto netkind = convertNetKind(netNode.netType.netKind);
|
|
if (netkind == moore::NetKind::Interconnect ||
|
|
netkind == moore::NetKind::UserDefined ||
|
|
netkind == moore::NetKind::Unknown)
|
|
return mlir::emitError(loc, "unsupported net kind `")
|
|
<< netNode.netType.name << "`";
|
|
|
|
auto netOp = moore::NetOp::create(
|
|
builder, loc,
|
|
moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
|
|
builder.getStringAttr(Twine(blockNamePrefix) + netNode.name), netkind,
|
|
assignment);
|
|
context.valueSymbols.insert(&netNode, netOp);
|
|
return success();
|
|
}
|
|
|
|
// Handle continuous assignments.
|
|
LogicalResult visit(const slang::ast::ContinuousAssignSymbol &assignNode) {
|
|
if (const auto *delay = assignNode.getDelay()) {
|
|
auto loc = context.convertLocation(delay->sourceRange);
|
|
return mlir::emitError(loc,
|
|
"delayed continuous assignments not supported");
|
|
}
|
|
|
|
const auto &expr =
|
|
assignNode.getAssignment().as<slang::ast::AssignmentExpression>();
|
|
auto lhs = context.convertLvalueExpression(expr.left());
|
|
if (!lhs)
|
|
return failure();
|
|
|
|
auto rhs = context.convertRvalueExpression(
|
|
expr.right(), cast<moore::RefType>(lhs.getType()).getNestedType());
|
|
if (!rhs)
|
|
return failure();
|
|
|
|
moore::ContinuousAssignOp::create(builder, loc, lhs, rhs);
|
|
return success();
|
|
}
|
|
|
|
// Handle procedures.
|
|
LogicalResult convertProcedure(moore::ProcedureKind kind,
|
|
const slang::ast::Statement &body) {
|
|
auto procOp = moore::ProcedureOp::create(builder, loc, kind);
|
|
OpBuilder::InsertionGuard guard(builder);
|
|
builder.setInsertionPointToEnd(&procOp.getBody().emplaceBlock());
|
|
Context::ValueSymbolScope scope(context.valueSymbols);
|
|
if (failed(context.convertStatement(body)))
|
|
return failure();
|
|
if (builder.getBlock())
|
|
moore::ReturnOp::create(builder, loc);
|
|
return success();
|
|
}
|
|
|
|
LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
|
|
// Detect `always @(*) <stmt>` and convert to `always_comb <stmt>` if
|
|
// requested by the user.
|
|
if (context.options.lowerAlwaysAtStarAsComb) {
|
|
auto *stmt = procNode.getBody().as_if<slang::ast::TimedStatement>();
|
|
if (procNode.procedureKind == slang::ast::ProceduralBlockKind::Always &&
|
|
stmt &&
|
|
stmt->timing.kind == slang::ast::TimingControlKind::ImplicitEvent)
|
|
return convertProcedure(moore::ProcedureKind::AlwaysComb, stmt->stmt);
|
|
}
|
|
|
|
return convertProcedure(convertProcedureKind(procNode.procedureKind),
|
|
procNode.getBody());
|
|
}
|
|
|
|
// Handle generate block.
|
|
LogicalResult visit(const slang::ast::GenerateBlockSymbol &genNode) {
|
|
// Ignore uninstantiated blocks.
|
|
if (genNode.isUninstantiated)
|
|
return success();
|
|
|
|
// If the block has a name, add it to the list of block name prefices.
|
|
SmallString<64> prefixBuffer;
|
|
auto prefix = blockNamePrefix;
|
|
if (!genNode.name.empty()) {
|
|
prefixBuffer += blockNamePrefix;
|
|
prefixBuffer += genNode.name;
|
|
prefixBuffer += '.';
|
|
prefix = prefixBuffer;
|
|
}
|
|
|
|
// Visit each member of the generate block.
|
|
for (auto &member : genNode.members())
|
|
if (failed(member.visit(ModuleVisitor(context, loc, prefix))))
|
|
return failure();
|
|
return success();
|
|
}
|
|
|
|
// Handle generate block array.
|
|
LogicalResult visit(const slang::ast::GenerateBlockArraySymbol &genArrNode) {
|
|
// If the block has a name, add it to the list of block name prefices and
|
|
// prepare to append the array index and a `.` in each iteration.
|
|
SmallString<64> prefixBuffer;
|
|
if (!genArrNode.name.empty()) {
|
|
prefixBuffer += blockNamePrefix;
|
|
prefixBuffer += genArrNode.name;
|
|
}
|
|
auto prefixBufferBaseLen = prefixBuffer.size();
|
|
|
|
// Visit each iteration entry of the generate block.
|
|
for (const auto *entry : genArrNode.entries) {
|
|
// Append the index to the prefix if this block has a name.
|
|
auto prefix = blockNamePrefix;
|
|
if (prefixBufferBaseLen > 0) {
|
|
prefixBuffer.resize(prefixBufferBaseLen);
|
|
prefixBuffer += '_';
|
|
if (entry->arrayIndex)
|
|
prefixBuffer += entry->arrayIndex->toString();
|
|
else
|
|
Twine(entry->constructIndex).toVector(prefixBuffer);
|
|
prefixBuffer += '.';
|
|
prefix = prefixBuffer;
|
|
}
|
|
|
|
// Visit this iteration entry.
|
|
if (failed(entry->asSymbol().visit(ModuleVisitor(context, loc, prefix))))
|
|
return failure();
|
|
}
|
|
return success();
|
|
}
|
|
|
|
// Ignore statement block symbols. These get generated by Slang for blocks
|
|
// with variables and other declarations. For example, having an initial
|
|
// procedure with a variable declaration, such as `initial begin int x;
|
|
// end`, will create the procedure with a block and variable declaration as
|
|
// expected, but will also create a `StatementBlockSymbol` with just the
|
|
// variable layout _next to_ the initial procedure.
|
|
LogicalResult visit(const slang::ast::StatementBlockSymbol &) {
|
|
return success();
|
|
}
|
|
|
|
// Ignore sequence declarations. The declarations are already evaluated by
|
|
// Slang and are part of an AssertionInstance.
|
|
LogicalResult visit(const slang::ast::SequenceSymbol &seqNode) {
|
|
return success();
|
|
}
|
|
|
|
// Ignore property declarations. The declarations are already evaluated by
|
|
// Slang and are part of an AssertionInstance.
|
|
LogicalResult visit(const slang::ast::PropertySymbol &propNode) {
|
|
return success();
|
|
}
|
|
|
|
// Handle functions and tasks.
|
|
LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
|
|
return context.convertFunction(subroutine);
|
|
}
|
|
|
|
/// Emit an error for all other members.
|
|
template <typename T>
|
|
LogicalResult visit(T &&node) {
|
|
mlir::emitError(loc, "unsupported module member: ")
|
|
<< slang::ast::toString(node.kind);
|
|
return failure();
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Structure and Hierarchy Conversion
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Convert an entire Slang compilation to MLIR ops. This is the main entry
|
|
/// point for the conversion.
|
|
LogicalResult Context::convertCompilation() {
|
|
const auto &root = compilation.getRoot();
|
|
|
|
// First only to visit the whole AST to collect the hierarchical names without
|
|
// any operation creating.
|
|
for (auto *inst : root.topInstances)
|
|
if (failed(traverseInstanceBody(inst->body)))
|
|
return failure();
|
|
|
|
// Visit all top-level declarations in all compilation units. This does not
|
|
// include instantiable constructs like modules, interfaces, and programs,
|
|
// which are listed separately as top instances.
|
|
for (auto *unit : root.compilationUnits) {
|
|
for (const auto &member : unit->members()) {
|
|
auto loc = convertLocation(member.location);
|
|
if (failed(member.visit(RootVisitor(*this, loc))))
|
|
return failure();
|
|
}
|
|
}
|
|
|
|
// Prime the root definition worklist by adding all the top-level modules.
|
|
SmallVector<const slang::ast::InstanceSymbol *> topInstances;
|
|
for (auto *inst : root.topInstances)
|
|
if (!convertModuleHeader(&inst->body))
|
|
return failure();
|
|
|
|
// Convert all the root module definitions.
|
|
while (!moduleWorklist.empty()) {
|
|
auto *module = moduleWorklist.front();
|
|
moduleWorklist.pop();
|
|
if (failed(convertModuleBody(module)))
|
|
return failure();
|
|
}
|
|
|
|
return success();
|
|
}
|
|
|
|
/// Convert a module and its ports to an empty module op in the IR. Also adds
|
|
/// the op to the worklist of module bodies to be lowered. This acts like a
|
|
/// module "declaration", allowing instances to already refer to a module even
|
|
/// before its body has been lowered.
|
|
ModuleLowering *
|
|
Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
|
|
using slang::ast::ArgumentDirection;
|
|
using slang::ast::MultiPortSymbol;
|
|
using slang::ast::ParameterSymbol;
|
|
using slang::ast::PortSymbol;
|
|
using slang::ast::TypeParameterSymbol;
|
|
|
|
auto parameters = module->parameters;
|
|
bool hasModuleSame = false;
|
|
// If there is already exist a module that has the same name with this
|
|
// module ,has the same parent scope and has the same parameters we can
|
|
// define this module is a duplicate module
|
|
for (auto const &existingModule : modules) {
|
|
if (module->getDeclaringDefinition() ==
|
|
existingModule.getFirst()->getDeclaringDefinition()) {
|
|
auto moduleParameters = existingModule.getFirst()->parameters;
|
|
hasModuleSame = true;
|
|
for (auto it1 = parameters.begin(), it2 = moduleParameters.begin();
|
|
it1 != parameters.end() && it2 != moduleParameters.end();
|
|
it1++, it2++) {
|
|
// Parameters size different
|
|
if (it1 == parameters.end() || it2 == moduleParameters.end()) {
|
|
hasModuleSame = false;
|
|
break;
|
|
}
|
|
const auto *para1 = (*it1)->symbol.as_if<ParameterSymbol>();
|
|
const auto *para2 = (*it2)->symbol.as_if<ParameterSymbol>();
|
|
// Parameters kind different
|
|
if ((para1 == nullptr) ^ (para2 == nullptr)) {
|
|
hasModuleSame = false;
|
|
break;
|
|
}
|
|
// Compare ParameterSymbol
|
|
if (para1 != nullptr) {
|
|
hasModuleSame = para1->getValue() == para2->getValue();
|
|
}
|
|
// Compare TypeParameterSymbol
|
|
if (para1 == nullptr) {
|
|
auto para1Type = convertType(
|
|
(*it1)->symbol.as<TypeParameterSymbol>().getTypeAlias());
|
|
auto para2Type = convertType(
|
|
(*it2)->symbol.as<TypeParameterSymbol>().getTypeAlias());
|
|
hasModuleSame = para1Type == para2Type;
|
|
}
|
|
if (!hasModuleSame)
|
|
break;
|
|
}
|
|
if (hasModuleSame) {
|
|
module = existingModule.first;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto &slot = modules[module];
|
|
if (slot)
|
|
return slot.get();
|
|
slot = std::make_unique<ModuleLowering>();
|
|
auto &lowering = *slot;
|
|
|
|
auto loc = convertLocation(module->location);
|
|
OpBuilder::InsertionGuard g(builder);
|
|
|
|
// We only support modules for now. Extension to interfaces and programs
|
|
// should be trivial though, since they are essentially the same thing with
|
|
// only minor differences in semantics.
|
|
if (module->getDefinition().definitionKind !=
|
|
slang::ast::DefinitionKind::Module) {
|
|
mlir::emitError(loc) << "unsupported definition: "
|
|
<< module->getDefinition().getKindString();
|
|
return {};
|
|
}
|
|
|
|
// Handle the port list.
|
|
auto block = std::make_unique<Block>();
|
|
SmallVector<hw::ModulePort> modulePorts;
|
|
|
|
// It's used to tag where a hierarchical name is on the port list.
|
|
unsigned int outputIdx = 0, inputIdx = 0;
|
|
for (auto *symbol : module->getPortList()) {
|
|
auto handlePort = [&](const PortSymbol &port) {
|
|
auto portLoc = convertLocation(port.location);
|
|
auto type = convertType(port.getType());
|
|
if (!type)
|
|
return failure();
|
|
auto portName = builder.getStringAttr(port.name);
|
|
BlockArgument arg;
|
|
if (port.direction == ArgumentDirection::Out) {
|
|
modulePorts.push_back({portName, type, hw::ModulePort::Output});
|
|
outputIdx++;
|
|
} else {
|
|
// Only the ref type wrapper exists for the time being, the net type
|
|
// wrapper for inout may be introduced later if necessary.
|
|
if (port.direction != ArgumentDirection::In)
|
|
type = moore::RefType::get(cast<moore::UnpackedType>(type));
|
|
modulePorts.push_back({portName, type, hw::ModulePort::Input});
|
|
arg = block->addArgument(type, portLoc);
|
|
inputIdx++;
|
|
}
|
|
lowering.ports.push_back({port, portLoc, arg});
|
|
return success();
|
|
};
|
|
|
|
if (const auto *port = symbol->as_if<PortSymbol>()) {
|
|
if (failed(handlePort(*port)))
|
|
return {};
|
|
} else if (const auto *multiPort = symbol->as_if<MultiPortSymbol>()) {
|
|
for (auto *port : multiPort->ports)
|
|
if (failed(handlePort(*port)))
|
|
return {};
|
|
} else {
|
|
mlir::emitError(convertLocation(symbol->location))
|
|
<< "unsupported module port `" << symbol->name << "` ("
|
|
<< slang::ast::toString(symbol->kind) << ")";
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Mapping hierarchical names into the module's ports.
|
|
for (auto &hierPath : hierPaths[module]) {
|
|
auto hierType = convertType(hierPath.valueSym->getType());
|
|
if (!hierType)
|
|
return {};
|
|
|
|
if (auto hierName = hierPath.hierName) {
|
|
// The type of all hierarchical names are marked as the "RefType".
|
|
hierType = moore::RefType::get(cast<moore::UnpackedType>(hierType));
|
|
if (hierPath.direction == ArgumentDirection::Out) {
|
|
hierPath.idx = outputIdx++;
|
|
modulePorts.push_back({hierName, hierType, hw::ModulePort::Output});
|
|
} else {
|
|
hierPath.idx = inputIdx++;
|
|
modulePorts.push_back({hierName, hierType, hw::ModulePort::Input});
|
|
auto hierLoc = convertLocation(hierPath.valueSym->location);
|
|
block->addArgument(hierType, hierLoc);
|
|
}
|
|
}
|
|
}
|
|
auto moduleType = hw::ModuleType::get(getContext(), modulePorts);
|
|
|
|
// Pick an insertion point for this module according to the source file
|
|
// location.
|
|
auto it = orderedRootOps.upper_bound(module->location);
|
|
if (it == orderedRootOps.end())
|
|
builder.setInsertionPointToEnd(intoModuleOp.getBody());
|
|
else
|
|
builder.setInsertionPoint(it->second);
|
|
|
|
// Create an empty module that corresponds to this module.
|
|
auto moduleOp =
|
|
moore::SVModuleOp::create(builder, loc, module->name, moduleType);
|
|
orderedRootOps.insert(it, {module->location, moduleOp});
|
|
moduleOp.getBodyRegion().push_back(block.release());
|
|
lowering.op = moduleOp;
|
|
|
|
// Add the module to the symbol table of the MLIR module, which uniquifies its
|
|
// name as we'd expect.
|
|
symbolTable.insert(moduleOp);
|
|
|
|
// Schedule the body to be lowered.
|
|
moduleWorklist.push(module);
|
|
|
|
// Map duplicate port by Syntax
|
|
for (const auto &port : lowering.ports)
|
|
lowering.portsBySyntaxNode.insert({port.ast.getSyntax(), &port.ast});
|
|
|
|
return &lowering;
|
|
}
|
|
|
|
/// Convert a module's body to the corresponding IR ops. The module op must have
|
|
/// already been created earlier through a `convertModuleHeader` call.
|
|
LogicalResult
|
|
Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
|
|
auto &lowering = *modules[module];
|
|
OpBuilder::InsertionGuard g(builder);
|
|
builder.setInsertionPointToEnd(lowering.op.getBody());
|
|
|
|
ValueSymbolScope scope(valueSymbols);
|
|
|
|
// Collect downward hierarchical names. Such as,
|
|
// module SubA; int x = Top.y; endmodule. The "Top" module is the parent of
|
|
// the "SubA", so "Top.y" is the downward hierarchical name.
|
|
for (auto &hierPath : hierPaths[module])
|
|
if (hierPath.direction == slang::ast::ArgumentDirection::In && hierPath.idx)
|
|
valueSymbols.insert(hierPath.valueSym,
|
|
lowering.op.getBody()->getArgument(*hierPath.idx));
|
|
|
|
// Convert the body of the module.
|
|
for (auto &member : module->members()) {
|
|
auto loc = convertLocation(member.location);
|
|
if (failed(member.visit(ModuleVisitor(*this, loc))))
|
|
return failure();
|
|
}
|
|
|
|
// Create additional ops to drive input port values onto the corresponding
|
|
// internal variables and nets, and to collect output port values for the
|
|
// terminator.
|
|
SmallVector<Value> outputs;
|
|
for (auto &port : lowering.ports) {
|
|
Value value;
|
|
if (auto *expr = port.ast.getInternalExpr()) {
|
|
value = convertLvalueExpression(*expr);
|
|
} else if (port.ast.internalSymbol) {
|
|
if (const auto *sym =
|
|
port.ast.internalSymbol->as_if<slang::ast::ValueSymbol>())
|
|
value = valueSymbols.lookup(sym);
|
|
}
|
|
if (!value)
|
|
return mlir::emitError(port.loc, "unsupported port: `")
|
|
<< port.ast.name
|
|
<< "` does not map to an internal symbol or expression";
|
|
|
|
// Collect output port values to be returned in the terminator.
|
|
if (port.ast.direction == slang::ast::ArgumentDirection::Out) {
|
|
if (isa<moore::RefType>(value.getType()))
|
|
value = moore::ReadOp::create(builder, value.getLoc(), value);
|
|
outputs.push_back(value);
|
|
continue;
|
|
}
|
|
|
|
// Assign the value coming in through the port to the internal net or symbol
|
|
// of that port.
|
|
Value portArg = port.arg;
|
|
if (port.ast.direction != slang::ast::ArgumentDirection::In)
|
|
portArg = moore::ReadOp::create(builder, port.loc, port.arg);
|
|
moore::ContinuousAssignOp::create(builder, port.loc, value, portArg);
|
|
}
|
|
|
|
// Ensure the number of operands of this module's terminator and the number of
|
|
// its(the current module) output ports remain consistent.
|
|
for (auto &hierPath : hierPaths[module])
|
|
if (auto hierValue = valueSymbols.lookup(hierPath.valueSym))
|
|
if (hierPath.direction == slang::ast::ArgumentDirection::Out)
|
|
outputs.push_back(hierValue);
|
|
|
|
moore::OutputOp::create(builder, lowering.op.getLoc(), outputs);
|
|
return success();
|
|
}
|
|
|
|
/// Convert a package and its contents.
|
|
LogicalResult
|
|
Context::convertPackage(const slang::ast::PackageSymbol &package) {
|
|
OpBuilder::InsertionGuard g(builder);
|
|
builder.setInsertionPointToEnd(intoModuleOp.getBody());
|
|
ValueSymbolScope scope(valueSymbols);
|
|
for (auto &member : package.members()) {
|
|
auto loc = convertLocation(member.location);
|
|
if (failed(member.visit(PackageVisitor(*this, loc))))
|
|
return failure();
|
|
}
|
|
return success();
|
|
}
|
|
|
|
/// Convert a function and its arguments to a function declaration in the IR.
|
|
/// This does not convert the function body.
|
|
FunctionLowering *
|
|
Context::declareFunction(const slang::ast::SubroutineSymbol &subroutine) {
|
|
using slang::ast::ArgumentDirection;
|
|
|
|
// Check if there already is a declaration for this function.
|
|
auto &lowering = functions[&subroutine];
|
|
if (lowering) {
|
|
if (!lowering->op)
|
|
return {};
|
|
return lowering.get();
|
|
}
|
|
lowering = std::make_unique<FunctionLowering>();
|
|
auto loc = convertLocation(subroutine.location);
|
|
|
|
// Pick an insertion point for this function according to the source file
|
|
// location.
|
|
OpBuilder::InsertionGuard g(builder);
|
|
auto it = orderedRootOps.upper_bound(subroutine.location);
|
|
if (it == orderedRootOps.end())
|
|
builder.setInsertionPointToEnd(intoModuleOp.getBody());
|
|
else
|
|
builder.setInsertionPoint(it->second);
|
|
|
|
// Class methods are currently not supported.
|
|
if (subroutine.thisVar) {
|
|
mlir::emitError(loc) << "unsupported class method";
|
|
return {};
|
|
}
|
|
|
|
// Determine the function type.
|
|
SmallVector<Type> inputTypes;
|
|
SmallVector<Type, 1> outputTypes;
|
|
|
|
for (const auto *arg : subroutine.getArguments()) {
|
|
auto type = cast<moore::UnpackedType>(convertType(arg->getType()));
|
|
if (!type)
|
|
return {};
|
|
if (arg->direction == ArgumentDirection::In) {
|
|
inputTypes.push_back(type);
|
|
} else {
|
|
inputTypes.push_back(moore::RefType::get(type));
|
|
}
|
|
}
|
|
|
|
if (!subroutine.getReturnType().isVoid()) {
|
|
auto type = convertType(subroutine.getReturnType());
|
|
if (!type)
|
|
return {};
|
|
outputTypes.push_back(type);
|
|
}
|
|
|
|
auto funcType = FunctionType::get(getContext(), inputTypes, outputTypes);
|
|
|
|
// Prefix the function name with the surrounding namespace to create somewhat
|
|
// sane names in the IR.
|
|
SmallString<64> funcName;
|
|
guessNamespacePrefix(subroutine.getParentScope()->asSymbol(), funcName);
|
|
funcName += subroutine.name;
|
|
|
|
// Create a function declaration.
|
|
auto funcOp = mlir::func::FuncOp::create(builder, loc, funcName, funcType);
|
|
SymbolTable::setSymbolVisibility(funcOp, SymbolTable::Visibility::Private);
|
|
orderedRootOps.insert(it, {subroutine.location, funcOp});
|
|
lowering->op = funcOp;
|
|
|
|
// Add the function to the symbol table of the MLIR module, which uniquifies
|
|
// its name.
|
|
symbolTable.insert(funcOp);
|
|
|
|
return lowering.get();
|
|
}
|
|
|
|
/// Convert a function.
|
|
LogicalResult
|
|
Context::convertFunction(const slang::ast::SubroutineSymbol &subroutine) {
|
|
// First get or create the function declaration.
|
|
auto *lowering = declareFunction(subroutine);
|
|
if (!lowering)
|
|
return failure();
|
|
ValueSymbolScope scope(valueSymbols);
|
|
|
|
// Create a function body block and populate it with block arguments.
|
|
SmallVector<moore::VariableOp> argVariables;
|
|
auto &block = lowering->op.getBody().emplaceBlock();
|
|
for (auto [astArg, type] :
|
|
llvm::zip(subroutine.getArguments(),
|
|
lowering->op.getFunctionType().getInputs())) {
|
|
auto loc = convertLocation(astArg->location);
|
|
auto blockArg = block.addArgument(type, loc);
|
|
|
|
if (isa<moore::RefType>(type)) {
|
|
valueSymbols.insert(astArg, blockArg);
|
|
} else {
|
|
// Convert the body of the function.
|
|
OpBuilder::InsertionGuard g(builder);
|
|
builder.setInsertionPointToEnd(&block);
|
|
|
|
auto shadowArg = moore::VariableOp::create(
|
|
builder, loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
|
|
StringAttr{}, blockArg);
|
|
valueSymbols.insert(astArg, shadowArg);
|
|
argVariables.push_back(shadowArg);
|
|
}
|
|
}
|
|
|
|
// Convert the body of the function.
|
|
OpBuilder::InsertionGuard g(builder);
|
|
builder.setInsertionPointToEnd(&block);
|
|
|
|
Value returnVar;
|
|
if (subroutine.returnValVar) {
|
|
auto type = convertType(*subroutine.returnValVar->getDeclaredType());
|
|
if (!type)
|
|
return failure();
|
|
returnVar = moore::VariableOp::create(
|
|
builder, lowering->op.getLoc(),
|
|
moore::RefType::get(cast<moore::UnpackedType>(type)), StringAttr{},
|
|
Value{});
|
|
valueSymbols.insert(subroutine.returnValVar, returnVar);
|
|
}
|
|
|
|
if (failed(convertStatement(subroutine.getBody())))
|
|
return failure();
|
|
|
|
// If there was no explicit return statement provided by the user, insert a
|
|
// default one.
|
|
if (builder.getBlock()) {
|
|
if (returnVar && !subroutine.getReturnType().isVoid()) {
|
|
Value read =
|
|
moore::ReadOp::create(builder, returnVar.getLoc(), returnVar);
|
|
mlir::func::ReturnOp::create(builder, lowering->op.getLoc(), read);
|
|
} else {
|
|
mlir::func::ReturnOp::create(builder, lowering->op.getLoc(),
|
|
ValueRange{});
|
|
}
|
|
}
|
|
if (returnVar && returnVar.use_empty())
|
|
returnVar.getDefiningOp()->erase();
|
|
|
|
for (auto var : argVariables) {
|
|
if (llvm::all_of(var->getUsers(),
|
|
[](auto *user) { return isa<moore::ReadOp>(user); })) {
|
|
for (auto *user : llvm::make_early_inc_range(var->getUsers())) {
|
|
user->getResult(0).replaceAllUsesWith(var.getInitial());
|
|
user->erase();
|
|
}
|
|
var->erase();
|
|
}
|
|
}
|
|
return success();
|
|
}
|