Skip to content

Commit

Permalink
[ImportVerilog] Add HierarchicalNames.cpp to support hierarchical nam…
Browse files Browse the repository at this point in the history
…es. (#7382)

* [ImportVerilog] Add HierarchicalNames.cpp to support hierarchical names.

* [ImportVerilog] Distinguish hierarchical names into upward and downward.

Based on the SystemVerilog IEEE Std 1800-2017 § 23.6 Hierarchical names. Hierarchical names are separated into upward and downward. For example:

module Top;
  int x;
  SubA subA();
  assign x = subA.a;  // upward: The Sub module's variable is used at the Top module.
endmodule

module SubA;
  int a;
  assign a = Top.x;  // downward: The Top module's variable is used at the Sub module.
endmodule
Therefore, we mark upward as outputs and downward as inputs, meanwhile, all hierarchical names are marked as RefType.

Unsupported cases:
However, we don't support hierarchical names invoked by two irrelevant modules at the same level. For example:

module A;
  int a = B.b;
endmodule

module B;
  int b = A.a;
endmodule
And we also don't support hierarchical names existing in the repeat modules. For example:

module Bar;
  SubC subC1();
  SubC subC2();
  int u = subC1.a;
  assign subC2.b = u;
endmodule

module SubC();
  int a, b;
endmodule

Co-authored-by: Fabian Schuiki <fabian@schuiki.ch>
  • Loading branch information
hailongSun2000 and fabianschuiki authored Dec 6, 2024
1 parent b5141b7 commit 4a73177
Show file tree
Hide file tree
Showing 6 changed files with 405 additions and 26 deletions.
1 change: 1 addition & 0 deletions lib/Conversion/ImportVerilog/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ endif ()
add_circt_translation_library(CIRCTImportVerilog
Expressions.cpp
FormatStrings.cpp
HierarchicalNames.cpp
ImportVerilog.cpp
Statements.cpp
Structure.cpp
Expand Down
44 changes: 40 additions & 4 deletions lib/Conversion/ImportVerilog/Expressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,28 @@ struct RvalueExprVisitor {
return {};
}

// Handle hierarchical values, such as `x = Top.sub.var`.
Value visit(const slang::ast::HierarchicalValueExpression &expr) {
auto hierLoc = context.convertLocation(expr.symbol.location);
if (auto value = context.valueSymbols.lookup(&expr.symbol)) {
if (isa<moore::RefType>(value.getType())) {
auto readOp = builder.create<moore::ReadOp>(hierLoc, value);
if (context.rvalueReadCallback)
context.rvalueReadCallback(readOp);
value = readOp.getResult();
}
return value;
}

// Emit an error for those hierarchical values not recorded in the
// `valueSymbols`.
auto d = mlir::emitError(loc, "unknown hierarchical name `")
<< expr.symbol.name << "`";
d.attachNote(hierLoc) << "no rvalue generated for "
<< slang::ast::toString(expr.symbol.kind);
return {};
}

// Handle type conversions (explicit and implicit).
Value visit(const slang::ast::ConversionExpression &expr) {
auto type = context.convertType(*expr.type);
Expand Down Expand Up @@ -803,7 +825,7 @@ struct RvalueExprVisitor {
auto type = cast<moore::UnpackedType>(value.getType());
auto intType = moore::IntType::get(
context.getContext(), type.getBitSize().value(), type.getDomain());
// do not care if it's signed, because we will not do expansion
// Do not care if it's signed, because we will not do expansion.
value = context.materializeConversion(intType, value, false, loc);
} else {
value = context.convertRvalueExpression(*stream.operand);
Expand All @@ -821,7 +843,7 @@ struct RvalueExprVisitor {

if (operands.size() == 1) {
// There must be at least one element, otherwise slang will report an
// error
// error.
value = operands.front();
} else {
value = builder.create<moore::ConcatOp>(loc, operands).getResult();
Expand Down Expand Up @@ -891,6 +913,20 @@ struct LvalueExprVisitor {
return {};
}

// Handle hierarchical values, such as `Top.sub.var = x`.
Value visit(const slang::ast::HierarchicalValueExpression &expr) {
if (auto value = context.valueSymbols.lookup(&expr.symbol))
return value;

// Emit an error for those hierarchical values not recorded in the
// `valueSymbols`.
auto d = mlir::emitError(loc, "unknown hierarchical name `")
<< expr.symbol.name << "`";
d.attachNote(context.convertLocation(expr.symbol.location))
<< "no lvalue generated for " << slang::ast::toString(expr.symbol.kind);
return {};
}

// Handle concatenations.
Value visit(const slang::ast::ConcatenationExpression &expr) {
SmallVector<Value> operands;
Expand Down Expand Up @@ -1012,7 +1048,7 @@ struct LvalueExprVisitor {
cast<moore::RefType>(value.getType()).getNestedType());
auto intType = moore::RefType::get(moore::IntType::get(
context.getContext(), type.getBitSize().value(), type.getDomain()));
// do not care if it's signed, because we will not do expansion
// Do not care if it's signed, because we will not do expansion.
value = context.materializeConversion(intType, value, false, loc);
} else {
value = context.convertLvalueExpression(*stream.operand);
Expand All @@ -1025,7 +1061,7 @@ struct LvalueExprVisitor {
Value value;
if (operands.size() == 1) {
// There must be at least one element, otherwise slang will report an
// error
// error.
value = operands.front();
} else {
value = builder.create<moore::ConcatRefOp>(loc, operands).getResult();
Expand Down
191 changes: 191 additions & 0 deletions lib/Conversion/ImportVerilog/HierarchicalNames.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
//===- Expressions.cpp - Slang expression 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 circt;
using namespace ImportVerilog;

namespace {
struct HierPathValueExprVisitor {
Context &context;
Location loc;
OpBuilder &builder;

// Such as `sub.a`, the `sub` is the outermost module for the hierarchical
// variable `a`.
const slang::ast::Symbol &outermostModule;

HierPathValueExprVisitor(Context &context, Location loc,
const slang::ast::Symbol &outermostModule)
: context(context), loc(loc), builder(context.builder),
outermostModule(outermostModule) {}

// Handle hierarchical values
LogicalResult visit(const slang::ast::HierarchicalValueExpression &expr) {
auto *currentInstBody =
expr.symbol.getParentScope()->getContainingInstance();
auto *outermostInstBody =
outermostModule.as_if<slang::ast::InstanceBodySymbol>();

// Like module Foo; int a; Foo.a; endmodule.
// Ignore "Foo.a" invoked by this module itself.
if (currentInstBody == outermostInstBody)
return success();

auto hierName = builder.getStringAttr(expr.symbol.name);
const slang::ast::InstanceBodySymbol *parentInstBody = nullptr;

// Collect hierarchical names that are added to the port list.
std::function<void(const slang::ast::InstanceBodySymbol *, bool)>
collectHierarchicalPaths = [&](auto sym, bool isUpward) {
// Here we use "sameHierPaths" to avoid collecting the repeat
// hierarchical names on the same path.
if (!context.sameHierPaths.contains(hierName) ||
!context.hierPaths.contains(sym)) {
context.hierPaths[sym].push_back(
HierPathInfo{hierName,
{},
isUpward ? slang::ast::ArgumentDirection::Out
: slang::ast::ArgumentDirection::In,
&expr.symbol});
context.sameHierPaths.insert(hierName);
}

// Iterate up from the current instance body symbol until meeting the
// outermost module.
parentInstBody =
sym->parentInstance->getParentScope()->getContainingInstance();
if (!parentInstBody)
return;

if (isUpward) {
// Avoid collecting hierarchical names into the outermost module.
if (parentInstBody && parentInstBody != outermostInstBody) {
hierName =
builder.getStringAttr(sym->parentInstance->name +
llvm::Twine(".") + hierName.getValue());
collectHierarchicalPaths(parentInstBody, isUpward);
}
} else {
if (parentInstBody && parentInstBody != currentInstBody)
collectHierarchicalPaths(parentInstBody, isUpward);
}
};

// Determine whether hierarchical names are upward or downward.
auto *tempInstBody = currentInstBody;
while (tempInstBody) {
tempInstBody = tempInstBody->parentInstance->getParentScope()
->getContainingInstance();
if (tempInstBody == outermostInstBody) {
collectHierarchicalPaths(currentInstBody, true);
return success();
}
}

hierName = builder.getStringAttr(currentInstBody->parentInstance->name +
llvm::Twine(".") + hierName.getValue());
collectHierarchicalPaths(outermostInstBody, false);
return success();
}

/// TODO:Skip all others.
/// But we should output a warning to display which symbol had been skipped.
/// However, to ensure we can test smoothly, we didn't do that.
template <typename T>
LogicalResult visit(T &&node) {
return success();
}

LogicalResult visitInvalid(const slang::ast::Expression &expr) {
mlir::emitError(loc, "invalid expression");
return failure();
}
};
} // namespace

LogicalResult
Context::collectHierarchicalValues(const slang::ast::Expression &expr,
const slang::ast::Symbol &outermostModule) {
auto loc = convertLocation(expr.sourceRange);
return expr.visit(HierPathValueExprVisitor(*this, loc, outermostModule));
}

/// Traverse the instance body.
namespace {
struct InstBodyVisitor {
Context &context;
Location loc;

InstBodyVisitor(Context &context, Location loc)
: context(context), loc(loc) {}

// Handle instances.
LogicalResult visit(const slang::ast::InstanceSymbol &instNode) {
return context.traverseInstanceBody(instNode.body);
}

// Handle variables.
LogicalResult visit(const slang::ast::VariableSymbol &varNode) {
auto &outermostModule = varNode.getParentScope()->asSymbol();
if (const auto *init = varNode.getInitializer())
if (failed(context.collectHierarchicalValues(*init, outermostModule)))
return failure();
return success();
}

// Handle nets.
LogicalResult visit(const slang::ast::NetSymbol &netNode) {
auto &outermostModule = netNode.getParentScope()->asSymbol();
if (const auto *init = netNode.getInitializer())
if (failed(context.collectHierarchicalValues(*init, outermostModule)))
return failure();
return success();
}

// Handle continuous assignments.
LogicalResult visit(const slang::ast::ContinuousAssignSymbol &assignNode) {
const auto &expr =
assignNode.getAssignment().as<slang::ast::AssignmentExpression>();

// Such as `sub.a`, the `sub` is the outermost module for the hierarchical
// variable `a`.
auto &outermostModule = assignNode.getParentScope()->asSymbol();
if (expr.left().hasHierarchicalReference())
if (failed(
context.collectHierarchicalValues(expr.left(), outermostModule)))
return failure();

if (expr.right().hasHierarchicalReference())
if (failed(
context.collectHierarchicalValues(expr.right(), outermostModule)))
return failure();

return success();
}

/// TODO:Skip all others.
/// But we should output a warning to display which symbol had been skipped.
/// However, to ensure we can test smoothly, we didn't do that.
template <typename T>
LogicalResult visit(T &&node) {
return success();
}
};
}; // namespace

LogicalResult Context::traverseInstanceBody(const slang::ast::Symbol &symbol) {
if (auto *instBodySymbol = symbol.as_if<slang::ast::InstanceBodySymbol>())
for (auto &member : instBodySymbol->members()) {
auto loc = convertLocation(member.location);
if (failed(member.visit(InstBodyVisitor(*this, loc))))
return failure();
}
return success();
}
29 changes: 29 additions & 0 deletions lib/Conversion/ImportVerilog/ImportVerilogInternals.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ struct LoopFrame {
Block *breakBlock;
};

/// Hierarchical path information.
/// The "hierName" means a different hierarchical name at different module
/// levels.
/// The "idx" means where the current hierarchical name is on the portlists.
/// The "direction" means hierarchical names whether downward(In) or
/// upward(Out).
struct HierPathInfo {
mlir::StringAttr hierName;
std::optional<unsigned int> idx;
slang::ast::ArgumentDirection direction;
const slang::ast::ValueSymbol *valueSym;
};

/// 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.
Expand Down Expand Up @@ -105,6 +118,12 @@ struct Context {
Type requiredType = {});
Value convertLvalueExpression(const slang::ast::Expression &expr);

// Traverse the whole AST to collect hierarchical names.
LogicalResult
collectHierarchicalValues(const slang::ast::Expression &expr,
const slang::ast::Symbol &outermostModule);
LogicalResult traverseInstanceBody(const slang::ast::Symbol &symbol);

// Convert a slang timing control into an MLIR timing control.
LogicalResult convertTimingControl(const slang::ast::TimingControl &ctrl,
const slang::ast::Statement &stmt);
Expand Down Expand Up @@ -183,6 +202,16 @@ struct Context {
using ValueSymbolScope = ValueSymbols::ScopeTy;
ValueSymbols valueSymbols;

/// Collect all hierarchical names used for the per module/instance.
DenseMap<const slang::ast::InstanceBodySymbol *, SmallVector<HierPathInfo>>
hierPaths;

/// It's used to collect the repeat hierarchical names on the same path.
/// Such as `Top.sub.a` and `sub.a`, they are equivalent. The variable "a"
/// will be added to the port list. But we only record once. If we don't do
/// that. We will view the strange IR, such as `module @Sub(out y, out y)`;
DenseSet<StringAttr> sameHierPaths;

/// A stack of assignment left-hand side values. Each assignment will push its
/// lowered left-hand side onto this stack before lowering its right-hand
/// side. This allows expressions to resolve the opaque
Expand Down
Loading

0 comments on commit 4a73177

Please sign in to comment.