diff --git a/lib/Conversion/ImportVerilog/CMakeLists.txt b/lib/Conversion/ImportVerilog/CMakeLists.txt index d813fa908ce7..c4393ecfcb85 100644 --- a/lib/Conversion/ImportVerilog/CMakeLists.txt +++ b/lib/Conversion/ImportVerilog/CMakeLists.txt @@ -31,6 +31,7 @@ endif () add_circt_translation_library(CIRCTImportVerilog Expressions.cpp FormatStrings.cpp + HierarchicalNames.cpp ImportVerilog.cpp Statements.cpp Structure.cpp diff --git a/lib/Conversion/ImportVerilog/Expressions.cpp b/lib/Conversion/ImportVerilog/Expressions.cpp index b8ca5470d0ea..8a0d9b41ca43 100644 --- a/lib/Conversion/ImportVerilog/Expressions.cpp +++ b/lib/Conversion/ImportVerilog/Expressions.cpp @@ -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(value.getType())) { + auto readOp = builder.create(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(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(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 operands; @@ -1012,7 +1048,7 @@ struct LvalueExprVisitor { cast(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(loc, operands).getResult(); diff --git a/lib/Conversion/ImportVerilog/HierarchicalNames.cpp b/lib/Conversion/ImportVerilog/HierarchicalNames.cpp new file mode 100644 index 000000000000..f12995ea02db --- /dev/null +++ b/lib/Conversion/ImportVerilog/HierarchicalNames.cpp @@ -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(); + + // 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 + 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 + 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(); + + // 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 + LogicalResult visit(T &&node) { + return success(); + } +}; +}; // namespace + +LogicalResult Context::traverseInstanceBody(const slang::ast::Symbol &symbol) { + if (auto *instBodySymbol = symbol.as_if()) + for (auto &member : instBodySymbol->members()) { + auto loc = convertLocation(member.location); + if (failed(member.visit(InstBodyVisitor(*this, loc)))) + return failure(); + } + return success(); +} diff --git a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h index b045ffe00713..431710ca935c 100644 --- a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h +++ b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h @@ -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 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> + 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 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 diff --git a/lib/Conversion/ImportVerilog/Structure.cpp b/lib/Conversion/ImportVerilog/Structure.cpp index c7b5548c92d1..fe351d7e8501 100644 --- a/lib/Conversion/ImportVerilog/Structure.cpp +++ b/lib/Conversion/ImportVerilog/Structure.cpp @@ -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(context.convertType(port->getType()))); @@ -290,8 +290,7 @@ struct ModuleVisitor : public BaseVisitor { auto netOp = builder.create( loc, refType, StringAttr::get(builder.getContext(), net->name), convertNetKind(net->netType.netKind), nullptr); - auto readOp = builder.create( - loc, refType.getNestedType(), netOp); + auto readOp = builder.create(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( loc, refType, StringAttr::get(builder.getContext(), var->name), nullptr); - auto readOp = builder.create( - loc, refType.getNestedType(), varOp); + auto readOp = builder.create(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()) { // 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( loc, moore::RefType::get(cast(sliceType)), value, offset); - // Read to map to rvalue for input ports. - if (port->direction == slang::ast::ArgumentDirection::In) - slice = builder.create(loc, sliceType, slice); + // Create the "ReadOp" for input ports. + if (port->direction == ArgumentDirection::In) + slice = builder.create(loc, slice); portValues.insert({port, slice}); offset += width; } @@ -401,6 +399,13 @@ struct ModuleVisitor : public BaseVisitor { value = builder.create(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 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(); SmallVector 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(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(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(value.getType())) - value = builder.create( - value.getLoc(), - cast(value.getType()).getNestedType(), value); + value = builder.create(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( - port.loc, cast(value.getType()).getNestedType(), - port.arg); + portArg = builder.create(port.loc, port.arg); builder.create(port.loc, value, portArg); } - builder.create(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(lowering.op.getLoc(), outputs); return success(); } diff --git a/test/Conversion/ImportVerilog/hierarchical-names.sv b/test/Conversion/ImportVerilog/hierarchical-names.sv new file mode 100644 index 000000000000..129fef09dafc --- /dev/null +++ b/test/Conversion/ImportVerilog/hierarchical-names.sv @@ -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) -> (subB.y: !moore.ref, subB.x: !moore.ref) + SubA subA(); + // CHECK: [[RD_SA_SB_Y:%.+]] = moore.read %subA.subB.y : + int s = subA.subB.y; + // CHECK: [[RD_R:%.+]] = moore.read %r : + // 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, out subB.y : !moore.ref, out subB.x : !moore.ref) +module SubA; + int a; + // CHECK: %subB.y, %subB.x = moore.instance "subB" @SubB(Foo.r: %Foo.r: !moore.ref, subA.a: %a: !moore.ref) -> (y: !moore.ref, x: !moore.ref) + SubB subB(); + // CHECK: [[RD_SB_Y:%.+]] = moore.read %subB.y : + // CHECK: moore.assign %a, [[RD_SB_Y]] : i32 + assign a = subB.y; + // CHECK: moore.output %subB.y, %subB.x : !moore.ref, !moore.ref +endmodule + +// CHECK-LABEL: moore.module private @SubB(in %Foo.r : !moore.ref, in %subA.a : !moore.ref, out y : !moore.ref, out x : !moore.ref) +module SubB; + int x, y, z; + // CHECK: [[RD_FOO_R:%.+]] = moore.read %Foo.r : + // CHECK: moore.assign %y, [[RD_FOO_R]] : i32 + // CHECK: [[RD_SA_A:%.+]] = moore.read %subA.a : + // CHECK: moore.assign %z, [[RD_SA_A]] : i32 + assign y = Foo.r; + assign z = Foo.subA.a; + // CHECK: moore.output %y, %x : !moore.ref, !moore.ref +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) + 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) + SubC subC2(a, b, c); + // CHECK: [[RD_SC1_SD_Z:%.+]] = moore.read %subC1.subD.z : + // CHECK: moore.variable [[RD_SC1_SD_Z]] : + 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) +module SubC(input a, b, + output c); + // CHECK: %subD.z = moore.instance "subD" @SubD() -> (z: !moore.ref) + SubD subD(); +endmodule + +// CHECK-LABEL: moore.module private @SubD(out z : !moore.ref) +module SubD; + int z; + int w; + // CHECK: [[RD_Z:%.+]] = moore.read %z : + // CHECK: moore.assign %w, [[RD_Z]] : i32 + assign SubD.w = SubD.z; +endmodule