From 4a73177e9fff0a95e1d4dc9f97d55e2631405c01 Mon Sep 17 00:00:00 2001 From: Hailong Sun Date: Fri, 6 Dec 2024 12:03:08 +0800 Subject: [PATCH] [ImportVerilog] Add HierarchicalNames.cpp to support hierarchical names. (#7382) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [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 --- lib/Conversion/ImportVerilog/CMakeLists.txt | 1 + lib/Conversion/ImportVerilog/Expressions.cpp | 44 +++- .../ImportVerilog/HierarchicalNames.cpp | 191 ++++++++++++++++++ .../ImportVerilog/ImportVerilogInternals.h | 29 +++ lib/Conversion/ImportVerilog/Structure.cpp | 98 +++++++-- .../ImportVerilog/hierarchical-names.sv | 68 +++++++ 6 files changed, 405 insertions(+), 26 deletions(-) create mode 100644 lib/Conversion/ImportVerilog/HierarchicalNames.cpp create mode 100644 test/Conversion/ImportVerilog/hierarchical-names.sv 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