[ImportVerilog] Add HierarchicalNames.cpp to support hierarchical names. (#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>
This commit is contained in:
Hailong Sun 2024-12-06 12:03:08 +08:00 committed by GitHub
parent b5141b7f1d
commit 4a73177e9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 405 additions and 26 deletions

View File

@ -31,6 +31,7 @@ endif ()
add_circt_translation_library(CIRCTImportVerilog
Expressions.cpp
FormatStrings.cpp
HierarchicalNames.cpp
ImportVerilog.cpp
Statements.cpp
Structure.cpp

View File

@ -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);
@ -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);
@ -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();
@ -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;
@ -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);
@ -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();

View File

@ -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();
}

View File

@ -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.
@ -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);
@ -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

View File

@ -281,7 +281,7 @@ struct ModuleVisitor : public BaseVisitor {
port = existingPort;
switch (port->direction) {
case slang::ast::ArgumentDirection::In: {
case ArgumentDirection::In: {
auto refType = moore::RefType::get(
cast<moore::UnpackedType>(context.convertType(port->getType())));
@ -290,8 +290,7 @@ struct ModuleVisitor : public BaseVisitor {
auto netOp = builder.create<moore::NetOp>(
loc, refType, StringAttr::get(builder.getContext(), net->name),
convertNetKind(net->netType.netKind), nullptr);
auto readOp = builder.create<moore::ReadOp>(
loc, refType.getNestedType(), netOp);
auto readOp = builder.create<moore::ReadOp>(loc, netOp);
portValues.insert({port, readOp});
} else if (const auto *var =
port->internalSymbol
@ -299,8 +298,7 @@ struct ModuleVisitor : public BaseVisitor {
auto varOp = builder.create<moore::VariableOp>(
loc, refType, StringAttr::get(builder.getContext(), var->name),
nullptr);
auto readOp = builder.create<moore::ReadOp>(
loc, refType.getNestedType(), varOp);
auto readOp = builder.create<moore::ReadOp>(loc, varOp);
portValues.insert({port, readOp});
} else {
return mlir::emitError(loc)
@ -312,7 +310,7 @@ struct ModuleVisitor : public BaseVisitor {
// No need to express unconnected behavior for output port, skip to the
// next iteration of the loop.
case slang::ast::ArgumentDirection::Out:
case ArgumentDirection::Out:
continue;
// TODO: Mark Inout port as unsupported and it will be supported later.
@ -333,7 +331,7 @@ struct ModuleVisitor : public BaseVisitor {
// 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 == slang::ast::ArgumentDirection::In)
auto value = (port->direction == ArgumentDirection::In)
? context.convertRvalueExpression(*expr)
: context.convertLvalueExpression(*expr);
if (!value)
@ -365,9 +363,9 @@ struct ModuleVisitor : public BaseVisitor {
Value slice = builder.create<moore::ExtractRefOp>(
loc, moore::RefType::get(cast<moore::UnpackedType>(sliceType)),
value, offset);
// Read to map to rvalue for input ports.
if (port->direction == slang::ast::ArgumentDirection::In)
slice = builder.create<moore::ReadOp>(loc, sliceType, slice);
// Create the "ReadOp" for input ports.
if (port->direction == ArgumentDirection::In)
slice = builder.create<moore::ReadOp>(loc, slice);
portValues.insert({port, slice});
offset += width;
}
@ -401,6 +399,13 @@ struct ModuleVisitor : public BaseVisitor {
value =
builder.create<moore::ConversionOp>(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());
@ -409,6 +414,12 @@ struct ModuleVisitor : public BaseVisitor {
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)
@ -450,9 +461,8 @@ struct ModuleVisitor : public BaseVisitor {
return failure();
Value assignment;
if (netNode.getInitializer()) {
assignment = context.convertRvalueExpression(*netNode.getInitializer(),
loweredType);
if (const auto *init = netNode.getInitializer()) {
assignment = context.convertRvalueExpression(*init, loweredType);
if (!assignment)
return failure();
}
@ -562,6 +572,12 @@ struct ModuleVisitor : public BaseVisitor {
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.
@ -671,6 +687,9 @@ Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
// 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);
@ -681,13 +700,15 @@ Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
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 != slang::ast::ArgumentDirection::In)
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();
@ -707,6 +728,27 @@ Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
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
@ -746,8 +788,17 @@ Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
OpBuilder::InsertionGuard g(builder);
builder.setInsertionPointToEnd(lowering.op.getBody());
// Convert the body of the module.
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))))
@ -775,9 +826,7 @@ Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
// 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 = builder.create<moore::ReadOp>(
value.getLoc(),
cast<moore::RefType>(value.getType()).getNestedType(), value);
value = builder.create<moore::ReadOp>(value.getLoc(), value);
outputs.push_back(value);
continue;
}
@ -786,13 +835,18 @@ Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
// of that port.
Value portArg = port.arg;
if (port.ast.direction != slang::ast::ArgumentDirection::In)
portArg = builder.create<moore::ReadOp>(
port.loc, cast<moore::RefType>(value.getType()).getNestedType(),
port.arg);
portArg = builder.create<moore::ReadOp>(port.loc, port.arg);
builder.create<moore::ContinuousAssignOp>(port.loc, value, portArg);
}
builder.create<moore::OutputOp>(lowering.op.getLoc(), outputs);
// 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);
builder.create<moore::OutputOp>(lowering.op.getLoc(), outputs);
return success();
}

View File

@ -0,0 +1,68 @@
// RUN: circt-translate --import-verilog %s | FileCheck %s
// RUN: circt-verilog --ir-moore %s
// REQUIRES: slang
// CHECK-LABEL: moore.module @Foo()
module Foo;
int r;
// CHECK: %subA.subB.y, %subA.subB.x = moore.instance "subA" @SubA(Foo.r: %r: !moore.ref<i32>) -> (subB.y: !moore.ref<i32>, subB.x: !moore.ref<i32>)
SubA subA();
// CHECK: [[RD_SA_SB_Y:%.+]] = moore.read %subA.subB.y : <i32>
int s = subA.subB.y;
// CHECK: [[RD_R:%.+]] = moore.read %r : <i32>
// CHECK: moore.assign %subA.subB.x, [[RD_R]] : i32
assign subA.subB.x = r;
endmodule
// CHECK-LABEL: moore.module private @SubA(in %Foo.r : !moore.ref<i32>, out subB.y : !moore.ref<i32>, out subB.x : !moore.ref<i32>)
module SubA;
int a;
// CHECK: %subB.y, %subB.x = moore.instance "subB" @SubB(Foo.r: %Foo.r: !moore.ref<i32>, subA.a: %a: !moore.ref<i32>) -> (y: !moore.ref<i32>, x: !moore.ref<i32>)
SubB subB();
// CHECK: [[RD_SB_Y:%.+]] = moore.read %subB.y : <i32>
// CHECK: moore.assign %a, [[RD_SB_Y]] : i32
assign a = subB.y;
// CHECK: moore.output %subB.y, %subB.x : !moore.ref<i32>, !moore.ref<i32>
endmodule
// CHECK-LABEL: moore.module private @SubB(in %Foo.r : !moore.ref<i32>, in %subA.a : !moore.ref<i32>, out y : !moore.ref<i32>, out x : !moore.ref<i32>)
module SubB;
int x, y, z;
// CHECK: [[RD_FOO_R:%.+]] = moore.read %Foo.r : <i32>
// CHECK: moore.assign %y, [[RD_FOO_R]] : i32
// CHECK: [[RD_SA_A:%.+]] = moore.read %subA.a : <i32>
// CHECK: moore.assign %z, [[RD_SA_A]] : i32
assign y = Foo.r;
assign z = Foo.subA.a;
// CHECK: moore.output %y, %x : !moore.ref<i32>, !moore.ref<i32>
endmodule
// -----
// CHECK-LABEL: moore.module @Bar(in %a : !moore.l1, in %b : !moore.l1, out c : !moore.l1)
module Bar(input a, b,
output c);
// CHECK: %subC1.c, %subC1.subD.z = moore.instance "subC1" @SubC(a: %0: !moore.l1, b: %1: !moore.l1) -> (c: !moore.l1, subD.z: !moore.ref<i32>)
SubC subC1(a, b, c);
// CHECK: %subC2.c, %subC2.subD.z = moore.instance "subC2" @SubC(a: %2: !moore.l1, b: %3: !moore.l1) -> (c: !moore.l1, subD.z: !moore.ref<i32>)
SubC subC2(a, b, c);
// CHECK: [[RD_SC1_SD_Z:%.+]] = moore.read %subC1.subD.z : <i32>
// CHECK: moore.variable [[RD_SC1_SD_Z]] : <i32>
int u = subC1.subD.z;
endmodule
// CHECK-LABEL: moore.module private @SubC(in %a : !moore.l1, in %b : !moore.l1, out c : !moore.l1, out subD.z : !moore.ref<i32>)
module SubC(input a, b,
output c);
// CHECK: %subD.z = moore.instance "subD" @SubD() -> (z: !moore.ref<i32>)
SubD subD();
endmodule
// CHECK-LABEL: moore.module private @SubD(out z : !moore.ref<i32>)
module SubD;
int z;
int w;
// CHECK: [[RD_Z:%.+]] = moore.read %z : <i32>
// CHECK: moore.assign %w, [[RD_Z]] : i32
assign SubD.w = SubD.z;
endmodule