diff --git a/include/slang/numeric/ConstantValue.h b/include/slang/numeric/ConstantValue.h index 693348f25..6076215f6 100644 --- a/include/slang/numeric/ConstantValue.h +++ b/include/slang/numeric/ConstantValue.h @@ -311,6 +311,9 @@ struct SLANG_EXPORT ConstantRange { int32_t left = 0; int32_t right = 0; + ConstantRange() = default; + ConstantRange(int32_t left, int32_t right) : left(left), right(right) {} + /// Gets the width of the range, regardless of the order in which /// the bounds are specified. bitwidth_t width() const { @@ -345,6 +348,9 @@ struct SLANG_EXPORT ConstantRange { /// Determines whether the given point is within the range. bool containsPoint(int32_t index) const; + /// Determines whether the given range is wholly contained within this one. + bool contains(ConstantRange other) const; + /// Determines whether the given range overlaps with this one /// (including cases where one is wholly contained in the other). bool overlaps(ConstantRange other) const; diff --git a/source/numeric/ConstantValue.cpp b/source/numeric/ConstantValue.cpp index 25b7c7502..806c9f035 100644 --- a/source/numeric/ConstantValue.cpp +++ b/source/numeric/ConstantValue.cpp @@ -650,6 +650,10 @@ bool ConstantRange::containsPoint(int32_t index) const { return index >= lower() && index <= upper(); } +bool ConstantRange::contains(ConstantRange other) const { + return other.lower() >= lower() && other.upper() <= upper(); +} + bool ConstantRange::overlaps(ConstantRange other) const { return lower() <= other.upper() && upper() >= other.lower(); } diff --git a/tools/netlist/CMakeLists.txt b/tools/netlist/CMakeLists.txt index 6ff5284eb..9add88f87 100644 --- a/tools/netlist/CMakeLists.txt +++ b/tools/netlist/CMakeLists.txt @@ -2,7 +2,8 @@ # SPDX-FileCopyrightText: Michael Popoloski # SPDX-License-Identifier: MIT # ~~~ -add_executable(slang_netlist netlist.cpp) + +add_executable(slang_netlist netlist.cpp source/Netlist.cpp) add_executable(slang::netlist ALIAS slang_netlist) target_link_libraries( diff --git a/tools/netlist/TODO.md b/tools/netlist/TODO.md index f72c05800..03cf4eeda 100644 --- a/tools/netlist/TODO.md +++ b/tools/netlist/TODO.md @@ -1,6 +1,8 @@ To dos ====== +- Support descending ranges in split variable type handling, eg [0:3]. +- Dumping of a dot file outputs random characters at the end. - Support for more procedural statements, the full list is: InvalidStatement @@ -36,7 +38,8 @@ To dos RandSequenceStatement ProceduralCheckerStatement -- Dumping of a dot file outputs random characters at the end. +- Optimise lookups of nodes in the netlist by adding tables for variable + declarations, variable references, ports etc. - Reporting of variables in the netlist (by type, matching patterns). - Infer sequential elements in the netlist (ie non-blocking assignment and sensitive to a clock edge). diff --git a/tools/netlist/include/Netlist.h b/tools/netlist/include/Netlist.h index 915dcc2d8..202df52a1 100644 --- a/tools/netlist/include/Netlist.h +++ b/tools/netlist/include/Netlist.h @@ -15,6 +15,7 @@ #include #include "slang/ast/ASTVisitor.h" +#include "slang/ast/Expression.h" #include "slang/ast/symbols/CompilationUnitSymbols.h" #include "slang/diagnostics/TextDiagnosticClient.h" #include "slang/syntax/SyntaxTree.h" @@ -46,6 +47,11 @@ struct VariableSelectorBase { virtual ~VariableSelectorBase() = default; virtual std::string toString() const = 0; + bool isElementSelect() const { return kind == VariableSelectorKind::ElementSelect; } + bool isRangeSelect() const { return kind == VariableSelectorKind::RangeSelect; } + bool isMemberAccess() const { return kind == VariableSelectorKind::MemberAccess; } + bool isArraySelect() const { return isElementSelect() || isRangeSelect(); } + template T& as() { SLANG_ASSERT(T::isKind(kind)); @@ -61,58 +67,98 @@ struct VariableSelectorBase { /// A variable selector representing an element selector. struct VariableElementSelect : public VariableSelectorBase { + const ast::Expression& expr; ConstantValue index; - VariableElementSelect(ConstantValue index) : - VariableSelectorBase(VariableSelectorKind::ElementSelect), index(std::move(index)) {} + VariableElementSelect(ast::Expression const& expr, ConstantValue index) : + VariableSelectorBase(VariableSelectorKind::ElementSelect), expr(expr), + index(std::move(index)) {} static bool isKind(VariableSelectorKind otherKind) { return otherKind == VariableSelectorKind::ElementSelect; } - size_t getIndexInt() const { - auto intValue = index.integer().as(); - SLANG_ASSERT(intValue && "could not convert index to size_t"); + bool indexIsConstant() const { return !index.bad(); } + + int32_t getIndexInt() const { + auto intValue = index.integer().as(); + SLANG_ASSERT(intValue && "could not convert index to int32_t"); return *intValue; } - std::string toString() const override { return fmt::format("[{}]", index.toString()); } + std::string toString() const override { + if (indexIsConstant()) { + return fmt::format("[{}]", index.toString()); + } + else { + return fmt::format("[{}]", expr.syntax->toString()); + } + } }; /// A variable selector representing a range selector. struct VariableRangeSelect : public VariableSelectorBase { + const ast::RangeSelectExpression& expr; ConstantValue leftIndex, rightIndex; - VariableRangeSelect(ConstantValue leftIndex, ConstantValue rightIndex) : - VariableSelectorBase(VariableSelectorKind::RangeSelect), leftIndex(std::move(leftIndex)), - rightIndex(std::move(rightIndex)) {} + VariableRangeSelect(ast::RangeSelectExpression const& expr, ConstantValue leftIndex, + ConstantValue rightIndex) : + VariableSelectorBase(VariableSelectorKind::RangeSelect), + expr(expr), leftIndex(std::move(leftIndex)), rightIndex(std::move(rightIndex)) {} static bool isKind(VariableSelectorKind otherKind) { return otherKind == VariableSelectorKind::RangeSelect; } - size_t getLeftIndexInt() const { - auto intValue = leftIndex.integer().as(); - SLANG_ASSERT(intValue && "could not convert left index to size_t"); + bool leftIndexIsConstant() const { return !leftIndex.bad(); } + + bool rightIndexIsConstant() const { return !rightIndex.bad(); } + + int32_t getLeftIndexInt() const { + auto intValue = leftIndex.integer().as(); + SLANG_ASSERT(intValue && "could not convert left index to int32_t"); return *intValue; } - size_t getRightIndexInt() const { - auto intValue = rightIndex.integer().as(); - SLANG_ASSERT(intValue && "could not convert right index to size_t"); + int32_t getRightIndexInt() const { + auto intValue = rightIndex.integer().as(); + SLANG_ASSERT(intValue && "could not convert right index to int32_t"); return *intValue; } std::string toString() const override { - return fmt::format("[{}:{}]", leftIndex.toString(), rightIndex.toString()); + std::string left; + if (leftIndexIsConstant()) { + left = leftIndex.toString(); + } + else { + left = expr.left().syntax->toString(); + } + std::string right; + if (rightIndexIsConstant()) { + right = rightIndex.toString(); + } + else { + right = expr.right().syntax->toString(); + } + switch (expr.getSelectionKind()) { + case ast::RangeSelectionKind::Simple: + return fmt::format("[{}:{}]", left, right); + case ast::RangeSelectionKind::IndexedUp: + return fmt::format("[{}+:{}]", left, right); + case ast::RangeSelectionKind::IndexedDown: + return fmt::format("[{}-:{}]", left, right); + default: + SLANG_UNREACHABLE; + } } }; /// A variable selector representing member access of a structure. struct VariableMemberAccess : public VariableSelectorBase { - std::string_view name; + const std::string_view name; - VariableMemberAccess(std::string_view name) : + VariableMemberAccess(const std::string_view name) : VariableSelectorBase(VariableSelectorKind::MemberAccess), name(name) {} static bool isKind(VariableSelectorKind otherKind) { @@ -176,8 +222,6 @@ class NetlistNode : public Node { static size_t nextID; }; -size_t NetlistNode::nextID = 0; - /// A class representing a port declaration. class NetlistPortDeclaration : public NetlistNode { public: @@ -224,12 +268,15 @@ class NetlistVariableReference : public NetlistNode { NetlistNode(NodeKind::VariableReference, symbol), expression(expr), leftOperand(leftOperand) {} - void addElementSelect(const ConstantValue& index) { - selectors.emplace_back(std::make_unique(index)); + void addElementSelect(ast::ElementSelectExpression const& expr, const ConstantValue& index) { + selectors.emplace_back(std::make_unique(expr.selector(), index)); } - void addRangeSelect(const ConstantValue& leftIndex, const ConstantValue& rightIndex) { - selectors.emplace_back(std::make_unique(leftIndex, rightIndex)); + + void addRangeSelect(ast::RangeSelectExpression const& expr, const ConstantValue& leftIndex, + const ConstantValue& rightIndex) { + selectors.emplace_back(std::make_unique(expr, leftIndex, rightIndex)); } + void addMemberAccess(std::string_view name) { selectors.emplace_back(std::make_unique(name)); } @@ -249,7 +296,14 @@ class NetlistVariableReference : public NetlistNode { } /// Return a string representation of this variable reference. - std::string toString() const { return fmt::format("{}{}", getName(), selectorString()); } + std::string toString() const { + if (selectors.empty()) { + return fmt::format("{}", getName()); + } + else { + return fmt::format("{}{}", getName(), selectorString()); + } + } public: /// The expression containing the variable reference. @@ -310,26 +364,37 @@ class Netlist : public DirectedGraph { return node; } + /// Find a port declaration node in the netlist by hierarchical path. + NetlistPortDeclaration* lookupPort(std::string_view hierarchicalPath) { + auto compareNode = [&hierarchicalPath](const std::unique_ptr& node) { + return node->kind == NodeKind::PortDeclaration && + node->as().hierarchicalPath == hierarchicalPath; + }; + auto it = std::ranges::find_if(*this, compareNode); + return it != end() ? &it->get()->as() : nullptr; + } + /// Find a variable declaration node in the netlist by hierarchical path. - /// TODO? Optimise this lookup by maintaining a list of declaration nodes. - NetlistNode* lookupVariable(const std::string& hierarchicalPath) { + /// Note that this does not lookup alias nodes. + NetlistVariableDeclaration* lookupVariable(std::string_view hierarchicalPath) { auto compareNode = [&hierarchicalPath](const std::unique_ptr& node) { return node->kind == NodeKind::VariableDeclaration && node->as().hierarchicalPath == hierarchicalPath; }; auto it = std::ranges::find_if(*this, compareNode); - return it != end() ? it->get() : nullptr; + return it != end() ? &it->get()->as() : nullptr; } - /// Find a port declaration node in the netlist by hierarchical path. - /// TODO? Optimise this lookup by maintaining a list of port nodes. - NetlistNode* lookupPort(const std::string& hierarchicalPath) { - auto compareNode = [&hierarchicalPath](const std::unique_ptr& node) { - return node->kind == NodeKind::PortDeclaration && - node->as().hierarchicalPath == hierarchicalPath; + /// Find a variable reference node in the netlist by its syntax. + /// Note that this does not include the hierarchical path, which is only + /// associated with the corresponding variable declaration nodes. + NetlistVariableReference* lookupVariableReference(std::string_view syntax) { + auto compareNode = [&syntax](const std::unique_ptr& node) { + return node->kind == NodeKind::VariableReference && + node->as().toString() == syntax; }; auto it = std::ranges::find_if(*this, compareNode); - return it != end() ? it->get() : nullptr; + return it != end() ? &it->get()->as() : nullptr; } }; diff --git a/tools/netlist/include/NetlistVisitor.h b/tools/netlist/include/NetlistVisitor.h index e0c624408..1e9b142d6 100644 --- a/tools/netlist/include/NetlistVisitor.h +++ b/tools/netlist/include/NetlistVisitor.h @@ -13,6 +13,7 @@ #include "Netlist.h" #include "fmt/color.h" #include "fmt/format.h" +#include #include #include "slang/ast/ASTContext.h" @@ -38,20 +39,18 @@ static std::string getSymbolHierPath(const ast::Symbol& symbol) { return buffer; } -static void connectDeclToVar(Netlist& netlist, NetlistNode& varNode, +static void connectDeclToVar(Netlist& netlist, NetlistNode& declNode, const std::string& hierarchicalPath) { - auto* variableNode = netlist.lookupVariable(hierarchicalPath); - netlist.addEdge(*variableNode, varNode); - DEBUG_PRINT( - fmt::format("Edge decl {} to ref {}\n", variableNode->getName(), varNode.getName())); + auto* varNode = netlist.lookupVariable(hierarchicalPath); + netlist.addEdge(*varNode, declNode); + DEBUG_PRINT(fmt::format("Edge decl {} to ref {}\n", varNode->getName(), declNode.getName())); } static void connectVarToDecl(Netlist& netlist, NetlistNode& varNode, const std::string& hierarchicalPath) { - auto* portNode = netlist.lookupVariable(hierarchicalPath); - netlist.addEdge(varNode, *portNode); - DEBUG_PRINT( - fmt::format("Edge ref {} to port ref {}\n", varNode.getName(), portNode->getName())); + auto* declNode = netlist.lookupVariable(hierarchicalPath); + netlist.addEdge(varNode, *declNode); + DEBUG_PRINT(fmt::format("Edge ref {} to decl {}\n", varNode.getName(), declNode->getName())); } static void connectVarToVar(Netlist& netlist, NetlistNode& sourceVarNode, @@ -71,23 +70,30 @@ class VariableReferenceVisitor : public ast::ASTVisitorkind == ast::ExpressionKind::ElementSelect) { - auto index = selector->as().selector().eval(evalCtx); - node.addElementSelect(index); + const auto& expr = selector->as(); + auto index = expr.selector().eval(evalCtx); + node.addElementSelect(expr, index); } else if (selector->kind == ast::ExpressionKind::RangeSelect) { - auto& rangeSelectExpr = selector->as(); - auto leftIndex = rangeSelectExpr.left().eval(evalCtx); - auto rightIndex = rangeSelectExpr.right().eval(evalCtx); - node.addRangeSelect(leftIndex, rightIndex); + const auto& expr = selector->as(); + auto leftIndex = expr.left().eval(evalCtx); + auto rightIndex = expr.right().eval(evalCtx); + node.addRangeSelect(expr, leftIndex, rightIndex); } else if (selector->kind == ast::ExpressionKind::MemberAccess) { node.addMemberAccess(selector->as().member.name); } } + std::reverse(node.selectors.begin(), node.selectors.end()); selectors.clear(); } diff --git a/tools/netlist/include/SplitVariables.h b/tools/netlist/include/SplitVariables.h index 611ebb8bf..22b1bc85f 100644 --- a/tools/netlist/include/SplitVariables.h +++ b/tools/netlist/include/SplitVariables.h @@ -13,11 +13,394 @@ #include "fmt/format.h" #include +#include "slang/ast/Symbol.h" +#include "slang/ast/types/AllTypes.h" #include "slang/ast/types/Type.h" +#include "slang/numeric/ConstantValue.h" +#include "slang/numeric/SVInt.h" #include "slang/util/Util.h" namespace netlist { +class AnalyseVariableReference { +private: + NetlistVariableReference& node; + NetlistVariableReference::SelectorsListType::iterator selectorsIt; + +public: + static AnalyseVariableReference create(NetlistVariableReference& node) { + return AnalyseVariableReference(node); + } + + AnalyseVariableReference(NetlistVariableReference& node) : + node(node), selectorsIt(node.selectors.begin()) {} + + std::pair getTypeBitWidthImpl(slang::ast::Type const& type) { + size_t fixedSize = type.getBitWidth(); + if (fixedSize > 0) { + return {1, fixedSize}; + } + + size_t multiplier = 0; + const auto& ct = type.getCanonicalType(); + if (ct.kind == slang::ast::SymbolKind::FixedSizeUnpackedArrayType) { + auto [multiplierElem, fixedSizeElem] = getTypeBitWidthImpl(*type.getArrayElementType()); + auto rw = ct.as().range.width(); + return {multiplierElem * rw, fixedSizeElem}; + } + + SLANG_UNREACHABLE; + } + + /// Return the bit width of a slang type, treating unpacked arrays as + /// as if they were packed. + int32_t getTypeBitWidth(slang::ast::Type const& type) { + auto [multiplierElem, fixedSizeElem] = getTypeBitWidthImpl(type); + return (int32_t)(multiplierElem * fixedSizeElem); + } + + /// Given a scope, return the type of the named field. + slang::ast::Type const& getScopeFieldType(const slang::ast::Scope& scope, + const std::string_view name) const { + auto* symbol = scope.find(name); + SLANG_ASSERT(symbol != nullptr); + return symbol->getDeclaredType()->getType(); + } + + /// Given a packed struct type, return the bit position of the named field. + ConstantRange getStructFieldRange(const slang::ast::PackedStructType& packedStruct, + const std::string_view fieldName) const { + int32_t offset = 0; + for (auto& member : packedStruct.members()) { + int32_t fieldWidth = member.getDeclaredType()->getType().getBitWidth(); + if (member.name == fieldName) { + return {offset, offset + fieldWidth - 1}; + }; + offset += fieldWidth; + } + SLANG_UNREACHABLE; + } + + /// Given a packed union type, return the bit position of the named field. + ConstantRange getUnionFieldRange(const slang::ast::PackedUnionType& packedUnion, + const std::string_view fieldName) const { + auto* symbol = packedUnion.find(fieldName); + SLANG_ASSERT(symbol != nullptr); + int32_t fieldWidth = symbol->getDeclaredType()->getType().getBitWidth(); + return {0, fieldWidth - 1}; + } + + /// Given an enumeration type, return the bit position of the named field. + ConstantRange getEnumRange(const slang::ast::EnumType& enumeration) { + auto fieldRange = enumeration.getBitVectorRange(); + return {0, (int32_t)fieldRange.width() - 1}; + } + + /// Given an array type, return the range from which the array is indexed. + ConstantRange getArrayRange(const slang::ast::Type& type) { + if (type.kind == slang::ast::SymbolKind::PackedArrayType) { + auto& arrayType = type.as(); + return arrayType.range; + } + else if (type.kind == slang::ast::SymbolKind::FixedSizeUnpackedArrayType) { + auto& arrayType = type.as(); + return arrayType.range; + } + else { + SLANG_UNREACHABLE; + } + } + + ConstantRange handleScalarElementSelect(const slang::ast::Type& type, ConstantRange range) { + const auto& elementSelector = selectorsIt->get()->as(); + if (!elementSelector.indexIsConstant()) { + // If the selector is not a constant, then return the whole scalar as + // the range. No further selectors expected. + SLANG_ASSERT(std::next(selectorsIt) == node.selectors.end()); + return {range.lower(), range.lower() + (int32_t)type.getBitWidth() - 1}; + } + // Create a new range. + int32_t index = range.lower() + elementSelector.getIndexInt(); + SLANG_ASSERT(range.containsPoint(index)); + return {index, index}; + } + + ConstantRange handleScalarRangeSelect(const slang::ast::Type& type, ConstantRange range) { + const auto& rangeSelector = selectorsIt->get()->as(); + int32_t rightIndex = rangeSelector.getRightIndexInt(); + int32_t leftIndex = rangeSelector.getLeftIndexInt(); + // Left and right indices must be constant. + SLANG_ASSERT(rangeSelector.leftIndexIsConstant()); + SLANG_ASSERT(rangeSelector.rightIndexIsConstant()); + // Create a new range. + ConstantRange newRange = {range.lower() + rightIndex, range.lower() + leftIndex}; + SLANG_ASSERT(range.contains(newRange)); + if (std::next(selectorsIt) != node.selectors.end()) { + selectorsIt++; + return getBitRangeImpl(type, newRange); + } + else { + return newRange; + } + } + + ConstantRange handleScalarRangeSelectIncr(const slang::ast::Type& type, ConstantRange range, + bool isUp) { + const auto& rangeSelector = selectorsIt->get()->as(); + if (!rangeSelector.leftIndexIsConstant()) { + // If the selector base is not constant, then return the whole scalar + // as the range and halt analysis of any further selectors. + selectorsIt = node.selectors.end(); + return {range.lower(), range.lower() + (int32_t)type.getBitWidth() - 1}; + } + // Right index must be constant. + SLANG_ASSERT(rangeSelector.rightIndexIsConstant()); + int32_t rightIndex = rangeSelector.getRightIndexInt(); + int32_t leftIndex = rangeSelector.getLeftIndexInt(); + // Create a new range. + auto rangeEnd = isUp ? rightIndex + leftIndex : rightIndex - leftIndex; + ConstantRange newRange = {range.lower() + rightIndex, range.lower() + rangeEnd}; + SLANG_ASSERT(range.contains(newRange)); + if (std::next(selectorsIt) != node.selectors.end()) { + selectorsIt++; + return getBitRangeImpl(type, newRange); + } + else { + return newRange; + } + } + + ConstantRange handleArrayElementSelect(const slang::ast::Type& type, ConstantRange range) { + const auto& elementSelector = selectorsIt->get()->as(); + if (!elementSelector.indexIsConstant()) { + // If the selector is not a constant, then return the whole scalar as + // the range and halt analysis of any further selectors. + selectorsIt = node.selectors.end(); + return {range.lower(), range.lower() + getTypeBitWidth(type) - 1}; + } + int32_t index = elementSelector.getIndexInt(); + auto arrayRange = getArrayRange(type); + SLANG_ASSERT(arrayRange.containsPoint(index)); + // Adjust for non-zero array indexing. + index -= arrayRange.lower(); + // Create a new range. + auto* elementType = type.getArrayElementType(); + ConstantRange newRange = {range.lower() + (index * getTypeBitWidth(*elementType)), + range.lower() + ((index + 1) * getTypeBitWidth(*elementType)) - + 1}; + SLANG_ASSERT(range.contains(newRange)); + if (std::next(selectorsIt) != node.selectors.end()) { + selectorsIt++; + return getBitRangeImpl(*elementType, newRange); + } + else { + return newRange; + } + } + + ConstantRange handleArrayRangeSelect(const slang::ast::Type& type, ConstantRange range) { + const auto& rangeSelector = selectorsIt->get()->as(); + int32_t leftIndex = rangeSelector.getLeftIndexInt(); + int32_t rightIndex = rangeSelector.getRightIndexInt(); + auto arrayRange = getArrayRange(type); + // Left and right indices must be constant. + SLANG_ASSERT(rangeSelector.leftIndexIsConstant()); + SLANG_ASSERT(rangeSelector.rightIndexIsConstant()); + // Adjust for non-zero array indexing. + leftIndex -= arrayRange.lower(); + rightIndex -= arrayRange.lower(); + // Create a new range. + auto* elementType = type.getArrayElementType(); + ConstantRange newRange = {range.lower() + (rightIndex * getTypeBitWidth(*elementType)), + range.lower() + + ((leftIndex + 1) * getTypeBitWidth(*elementType)) - 1}; + SLANG_ASSERT(range.contains(newRange)); + if (std::next(selectorsIt) != node.selectors.end()) { + selectorsIt++; + return getBitRangeImpl(type, newRange); + } + else { + return newRange; + } + } + + ConstantRange handleArrayRangeSelectIncr(const slang::ast::Type& type, ConstantRange range, + bool isUp) { + const auto& rangeSelector = selectorsIt->get()->as(); + auto* elementType = type.getArrayElementType(); + auto arrayRange = getArrayRange(type); + if (!rangeSelector.leftIndexIsConstant()) { + // If the selector base is not constant, then return the whole array + // as the range and halt analysis of any further selectors. + selectorsIt = node.selectors.end(); + return {range.lower(), + range.lower() + (getTypeBitWidth(*elementType) * (int32_t)arrayRange.width()) - + 1}; + } + // Right index must be constant. + SLANG_ASSERT(rangeSelector.rightIndexIsConstant()); + int32_t rightIndex = rangeSelector.getRightIndexInt(); + int32_t leftIndex = rangeSelector.getLeftIndexInt(); + // Adjust for non-zero array indexing. + leftIndex -= arrayRange.lower(); + rightIndex -= arrayRange.lower(); + // Create a new range. + ConstantRange newRange = {range.lower() + (rightIndex * getTypeBitWidth(*elementType)), + range.lower() + + ((leftIndex + 1) * getTypeBitWidth(*elementType)) - 1}; + SLANG_ASSERT(range.contains(newRange)); + if (std::next(selectorsIt) != node.selectors.end()) { + selectorsIt++; + return getBitRangeImpl(type, newRange); + } + else { + return newRange; + } + } + + ConstantRange handleStructMemberAccess(const slang::ast::Type& type, ConstantRange range) { + const auto& memberAccessSelector = selectorsIt->get()->as(); + const auto& packedStruct = type.getCanonicalType().as(); + auto fieldRange = getStructFieldRange(packedStruct, memberAccessSelector.name); + // Create a new range. + ConstantRange newRange = {range.lower() + fieldRange.lower(), + range.lower() + fieldRange.upper()}; + SLANG_ASSERT(range.contains(newRange)); + if (std::next(selectorsIt) != node.selectors.end()) { + selectorsIt++; + const auto& fieldType = getScopeFieldType(packedStruct, memberAccessSelector.name); + return getBitRangeImpl(fieldType, newRange); + } + else { + return newRange; + } + } + + ConstantRange handleUnionMemberAccess(const slang::ast::Type& type, ConstantRange range) { + const auto& memberAccessSelector = selectorsIt->get()->as(); + const auto& packedUnion = type.getCanonicalType().as(); + auto fieldRange = getUnionFieldRange(packedUnion, memberAccessSelector.name); + // Create a new range. + ConstantRange newRange = {range.lower() + fieldRange.lower(), + range.lower() + fieldRange.upper()}; + SLANG_ASSERT(range.contains(newRange)); + if (std::next(selectorsIt) != node.selectors.end()) { + selectorsIt++; + const auto& fieldType = getScopeFieldType(packedUnion, memberAccessSelector.name); + return getBitRangeImpl(fieldType, newRange); + } + else { + return newRange; + } + } + + ConstantRange handleEnumMemberAccess(const slang::ast::Type& type, ConstantRange range) { + const auto& memberAccessSelector = selectorsIt->get()->as(); + const auto& enumeration = type.getCanonicalType().as(); + auto fieldRange = getEnumRange(enumeration); + // Create a new range. + ConstantRange newRange = {range.lower() + fieldRange.lower(), + range.lower() + fieldRange.upper()}; + SLANG_ASSERT(range.contains(newRange)); + SLANG_ASSERT(std::next(selectorsIt) == node.selectors.end()); + return newRange; + } + + // Multiple range selectors have only the effect of the last one. + // Eg x[3:0][2:1] <=> x[2:1] or x[2:1][2] <=> x[2]. + inline bool ignoreSelector() { + return selectorsIt->get()->isRangeSelect() && + std::next(selectorsIt) != node.selectors.end() && + std::next(selectorsIt)->get()->isArraySelect(); + } + + /// Given a variable reference with zero or more selectors, determine the + /// bit range that is accessed. + ConstantRange getBitRangeImpl(const slang::ast::Type& type, ConstantRange range) { + // No selectors + if (node.selectors.empty()) { + return {0, getTypeBitWidth(node.symbol.getDeclaredType()->getType()) - 1}; + } + // Simple vector + if (type.isPredefinedInteger() || type.isScalar() || + (type.isStruct() && !type.isUnpackedStruct()) || type.isPackedUnion() || + type.isEnum()) { + + if (ignoreSelector()) { + selectorsIt++; + return getBitRangeImpl(type, range); + } + + if (selectorsIt->get()->isElementSelect()) { + return handleScalarElementSelect(type, range); + } + else if (selectorsIt->get()->isRangeSelect()) { + switch (selectorsIt->get()->as().expr.getSelectionKind()) { + case ast::RangeSelectionKind::Simple: + return handleScalarRangeSelect(type, range); + case ast::RangeSelectionKind::IndexedUp: + return handleScalarRangeSelectIncr(type, range, true); + case ast::RangeSelectionKind::IndexedDown: + return handleScalarRangeSelectIncr(type, range, false); + default: + SLANG_UNREACHABLE; + } + } + else if (selectorsIt->get()->isMemberAccess()) { + if (type.isStruct()) { + return handleStructMemberAccess(type, range); + } + else if (type.isPackedUnion()) { + return handleUnionMemberAccess(type, range); + } + else if (type.isEnum()) { + return handleEnumMemberAccess(type, range); + } + else { + SLANG_ASSERT(0 && "unsupported member selector"); + } + } + else { + SLANG_ASSERT(0 && "unsupported scalar selector"); + } + } + // Packed or unpacked array + else if (type.isArray()) { + if (ignoreSelector()) { + selectorsIt++; + return getBitRangeImpl(type, range); + } + if (selectorsIt->get()->isElementSelect()) { + return handleArrayElementSelect(type, range); + } + else if (selectorsIt->get()->isRangeSelect()) { + switch (selectorsIt->get()->as().expr.getSelectionKind()) { + case ast::RangeSelectionKind::Simple: + return handleArrayRangeSelect(type, range); + case ast::RangeSelectionKind::IndexedUp: + return handleArrayRangeSelectIncr(type, range, true); + case ast::RangeSelectionKind::IndexedDown: + return handleArrayRangeSelectIncr(type, range, false); + default: + SLANG_UNREACHABLE; + } + } + else { + SLANG_ASSERT(0 && "unsupported array selector"); + } + } + else { + SLANG_ASSERT(0 && "unhandled type in getBitRangeImpl"); + } + } + + /// Return a range indicating the bits of the variable that are accessed. + ConstantRange getBitRange() { + auto& variableType = node.symbol.getDeclaredType()->getType(); + return getBitRangeImpl(variableType, {0, getTypeBitWidth(variableType) - 1}); + } +}; + /// A class to perform a transformation on the netlist to split variable /// declaration nodes of structured types into multiple parts based on the /// types of the incoming and outgoing edges. @@ -26,54 +409,13 @@ class SplitVariables { SplitVariables(Netlist& netlist) : netlist(netlist) { split(); } private: - /// Given two ranges [end1:start1] and [end2:start2], return true if there is - /// any overlap in values between them. - inline bool rangesOverlap(size_t start1, size_t end1, size_t start2, size_t end2) const { - return start1 <= end2 && start2 <= end1; - } - /// Return true if the selection made by the target node intersects with the /// selection made by the source node. bool isIntersectingSelection(NetlistVariableReference& sourceNode, NetlistVariableReference& targetNode) const { - bool match = true; - size_t selectorDepth = 0; - while (match) { - // Terminate the loop if either variable reference has no further - // selectors. - if (selectorDepth >= sourceNode.selectors.size() || - selectorDepth >= targetNode.selectors.size()) { - break; - } - auto& sourceSelector = sourceNode.selectors[selectorDepth]; - auto& targetSelector = targetNode.selectors[selectorDepth]; - SLANG_ASSERT(sourceSelector->kind == targetSelector->kind && "selectors do not match"); - switch (sourceSelector->kind) { - case VariableSelectorKind::ElementSelect: - // Matching selectors if the index is the same. - match = sourceSelector->as().getIndexInt() == - targetSelector->as().getIndexInt(); - break; - case VariableSelectorKind::RangeSelect: { - // Matching selectors if there is any overlap in the two ranges. - auto sourceRangeSel = sourceSelector->as(); - auto targetRangeSel = targetSelector->as(); - auto srcLeft = sourceRangeSel.getLeftIndexInt(); - auto srcRight = sourceRangeSel.getRightIndexInt(); - auto tgtLeft = targetRangeSel.getLeftIndexInt(); - auto tgtRight = targetRangeSel.getRightIndexInt(); - match = rangesOverlap(srcRight, srcLeft, tgtRight, tgtLeft); - break; - } - case VariableSelectorKind::MemberAccess: - // Matching selectors if the member names match. - match = sourceSelector->as().name == - targetSelector->as().name; - break; - } - selectorDepth++; - } - return match; + return AnalyseVariableReference(sourceNode) + .getBitRange() + .overlaps(AnalyseVariableReference(targetNode).getBitRange()); } void split() { diff --git a/tools/netlist/source/Netlist.cpp b/tools/netlist/source/Netlist.cpp new file mode 100644 index 000000000..60fc25ea6 --- /dev/null +++ b/tools/netlist/source/Netlist.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Michael Popoloski +// SPDX-License-Identifier: MIT + +#include "Netlist.h" + +size_t netlist::NetlistNode::nextID = 0; diff --git a/tools/netlist/tests/CMakeLists.txt b/tools/netlist/tests/CMakeLists.txt index 54fba2085..0afc4a9e3 100644 --- a/tools/netlist/tests/CMakeLists.txt +++ b/tools/netlist/tests/CMakeLists.txt @@ -5,8 +5,14 @@ add_executable( netlist_unittests - ../../../tests/unittests/main.cpp ../../../tests/unittests/Test.cpp - DepthFirstSearchTests.cpp DirectedGraphTests.cpp NetlistTests.cpp) + ../../../tests/unittests/main.cpp + ../../../tests/unittests/Test.cpp + ../source/Netlist.cpp + DepthFirstSearchTests.cpp + DirectedGraphTests.cpp + NameTests.cpp + PathTests.cpp + VariableSelectorsTests.cpp) target_link_libraries( netlist_unittests diff --git a/tools/netlist/tests/NameTests.cpp b/tools/netlist/tests/NameTests.cpp new file mode 100644 index 000000000..841ff83db --- /dev/null +++ b/tools/netlist/tests/NameTests.cpp @@ -0,0 +1,57 @@ +//------------------------------------------------------------------------------ +//! @file VariableSelectorsTests.cpp +//! @brief Tests for handling of variable selectors. +// +// SPDX-FileCopyrightText: Michael Popoloski +// SPDX-License-Identifier: MIT +//------------------------------------------------------------------------------ + +#include "NetlistTest.h" + +//===---------------------------------------------------------------------===// +// Tests for name resolution. +//===---------------------------------------------------------------------===// + +TEST_CASE("Unused modules") { + // Test that unused modules are not visited by the netlist builder. + // See Issue #793. + auto tree = SyntaxTree::fromText(R"( +module test (input i1, + input i2, + output o1 + ); + cell_a i_cell_a(.d1(i1), + .d2(i2), + .c(o1)); +endmodule + +module cell_a(input d1, + input d2, + output c); + assign c = d1 + d2; +endmodule + +// unused +module cell_b(input a, + input b, + output z); + assign z = a || b; +endmodule + +// unused +module cell_c(input a, + input b, + output z); + assign z = (!a) && b; +endmodule +)"); + CompilationOptions coptions; + coptions.topModules.emplace("test"sv); + Bag options; + options.set(coptions); + Compilation compilation(options); + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(netlist.numNodes() > 0); +} diff --git a/tools/netlist/tests/NetlistTest.h b/tools/netlist/tests/NetlistTest.h new file mode 100644 index 000000000..f92b6a88f --- /dev/null +++ b/tools/netlist/tests/NetlistTest.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Michael Popoloski +// SPDX-License-Identifier: MIT + +#include "Netlist.h" +#include "NetlistVisitor.h" +#include "PathFinder.h" +#include "SplitVariables.h" +#include "Test.h" +#include + +using namespace netlist; + +inline Netlist createNetlist(Compilation& compilation) { + Netlist netlist; + NetlistVisitor visitor(compilation, netlist); + compilation.getRoot().visit(visitor); + SplitVariables splitVariables(netlist); + return netlist; +} + +inline bool pathExists(Netlist& netlist, const std::string& startName, const std::string& endName) { + PathFinder pathFinder(netlist); + auto* startNode = netlist.lookupPort(startName); + auto* endNode = netlist.lookupPort(endName); + return !pathFinder.find(*startNode, *endNode).empty(); +} diff --git a/tools/netlist/tests/NetlistTests.cpp b/tools/netlist/tests/PathTests.cpp similarity index 86% rename from tools/netlist/tests/NetlistTests.cpp rename to tools/netlist/tests/PathTests.cpp index a9ac24ab3..d96729095 100644 --- a/tools/netlist/tests/NetlistTests.cpp +++ b/tools/netlist/tests/PathTests.cpp @@ -6,21 +6,7 @@ // SPDX-License-Identifier: MIT //------------------------------------------------------------------------------ -#include "Netlist.h" -#include "NetlistVisitor.h" -#include "PathFinder.h" -#include "SplitVariables.h" -#include "Test.h" - -using namespace netlist; - -Netlist createNetlist(Compilation& compilation) { - Netlist netlist; - NetlistVisitor visitor(compilation, netlist); - compilation.getRoot().visit(visitor); - SplitVariables splitVariables(netlist); - return netlist; -} +#include "NetlistTest.h" //===---------------------------------------------------------------------===// // Basic tests @@ -109,69 +95,6 @@ endmodule CHECK(*path.findVariable("chain_vars.e") == 9); } -//===---------------------------------------------------------------------===// -// Tests for module instance connectivity. -//===---------------------------------------------------------------------===// - -TEST_CASE("Signal passthrough with a nested module") { - // Test that a nested module is connected correctly. - auto tree = SyntaxTree::fromText(R"( -module passthrough(input logic i_value, output logic o_value); - assign o_value = i_value; -endmodule - -module nested_passthrough(input logic i_value, output logic o_value); - passthrough foo( - .i_value(i_value), - .o_value(o_value)); -endmodule -)"); - Compilation compilation; - compilation.addSyntaxTree(tree); - NO_COMPILATION_ERRORS; - auto netlist = createNetlist(compilation); - PathFinder pathFinder(netlist); - auto path = pathFinder.find(*netlist.lookupPort("nested_passthrough.i_value"), - *netlist.lookupPort("nested_passthrough.o_value")); - CHECK(*path.findVariable("nested_passthrough.foo.i_value") == 1); - CHECK(*path.findVariable("nested_passthrough.foo.o_value") == 2); -} - -TEST_CASE("Signal passthrough with a chain of two nested modules") { - // Test that two nested module are connected correctly. - auto tree = SyntaxTree::fromText(R"( -module passthrough(input logic i_value, output logic o_value); - assign o_value = i_value; -endmodule - -module nested_passthrough(input logic i_value, output logic o_value); - logic value; - passthrough foo_a( - .i_value(i_value), - .o_value(value)); - passthrough foo_b( - .i_value(value), - .o_value(o_value)); -endmodule -)"); - Compilation compilation; - compilation.addSyntaxTree(tree); - NO_COMPILATION_ERRORS; - auto netlist = createNetlist(compilation); - PathFinder pathFinder(netlist); - auto path = pathFinder.find(*netlist.lookupPort("nested_passthrough.i_value"), - *netlist.lookupPort("nested_passthrough.o_value")); - CHECK(path.size() == 8); - CHECK(*path.findVariable("nested_passthrough.foo_a.i_value") == 1); - CHECK(*path.findVariable("nested_passthrough.foo_a.o_value") == 2); - CHECK(*path.findVariable("nested_passthrough.foo_b.i_value") == 5); - CHECK(*path.findVariable("nested_passthrough.foo_b.o_value") == 6); -} - -//===---------------------------------------------------------------------===// -// Tests for variable splitting -//===---------------------------------------------------------------------===// - TEST_CASE("Chain of assignments in a sequence using a vector") { // As above but this time using a packed array. auto tree = SyntaxTree::fromText(R"( @@ -206,7 +129,43 @@ endmodule CHECK(*path.findVariable("chain_array.x[4]") == 9); } -TEST_CASE("Passthrough two signals via a shared structure") { +TEST_CASE("Passthrough two signals via ranges in a shared vector") { + auto tree = SyntaxTree::fromText(R"( +module passthrough_ranges ( + input logic [1:0] i_value_a, + input logic [1:0] i_value_b, + output logic [1:0] o_value_a, + output logic [1:0] o_value_b +); + + logic [3:0] foo; + + assign foo[1:0] = i_value_a; + assign foo[3:2] = i_value_b; + + assign o_value_a = foo[1:0]; + assign o_value_b = foo[3:2]; + +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + auto* inPortA = netlist.lookupPort("passthrough_ranges.i_value_a"); + auto* inPortB = netlist.lookupPort("passthrough_ranges.i_value_b"); + auto* outPortA = netlist.lookupPort("passthrough_ranges.o_value_a"); + auto* outPortB = netlist.lookupPort("passthrough_ranges.o_value_b"); + PathFinder pathFinder(netlist); + // Valid paths. + CHECK(pathFinder.find(*inPortA, *outPortA).size() == 4); + CHECK(pathFinder.find(*inPortB, *outPortB).size() == 4); + // Invalid paths. + CHECK(pathFinder.find(*inPortA, *outPortB).empty()); + CHECK(pathFinder.find(*inPortB, *outPortA).empty()); +} + +TEST_CASE("Passthrough two signals via a shared struct") { auto tree = SyntaxTree::fromText(R"( module passthrough_member_access ( input logic i_value_a, @@ -245,22 +204,27 @@ endmodule CHECK(pathFinder.find(*inPortB, *outPortA).empty()); } -TEST_CASE("Passthrough two signals via ranges in a shared vector") { +TEST_CASE("Passthrough two signals via a shared union") { auto tree = SyntaxTree::fromText(R"( -module passthrough_ranges ( - input logic [1:0] i_value_a, - input logic [1:0] i_value_b, - output logic [1:0] o_value_a, - output logic [1:0] o_value_b +module passthrough_member_access ( + input logic i_value_a, + input logic i_value_b, + output logic o_value_a, + output logic o_value_b, + output logic o_value_c ); - logic [3:0] foo; + union packed { + logic [1:0] a; + logic [1:0] b; + } foo; - assign foo[1:0] = i_value_a; - assign foo[3:2] = i_value_b; + assign foo.a[0] = i_value_a; + assign foo.b[1] = i_value_b; - assign o_value_a = foo[1:0]; - assign o_value_b = foo[3:2]; + assign o_value_a = foo.a[0]; + assign o_value_b = foo.b[1]; + assign o_value_c = foo.b[0]; // Overlapping with a in union. endmodule )"); @@ -268,19 +232,134 @@ endmodule compilation.addSyntaxTree(tree); NO_COMPILATION_ERRORS; auto netlist = createNetlist(compilation); - auto* inPortA = netlist.lookupPort("passthrough_ranges.i_value_a"); - auto* inPortB = netlist.lookupPort("passthrough_ranges.i_value_b"); - auto* outPortA = netlist.lookupPort("passthrough_ranges.o_value_a"); - auto* outPortB = netlist.lookupPort("passthrough_ranges.o_value_b"); + auto* inPortA = netlist.lookupPort("passthrough_member_access.i_value_a"); + auto* inPortB = netlist.lookupPort("passthrough_member_access.i_value_b"); + auto* outPortA = netlist.lookupPort("passthrough_member_access.o_value_a"); + auto* outPortB = netlist.lookupPort("passthrough_member_access.o_value_b"); + auto* outPortC = netlist.lookupPort("passthrough_member_access.o_value_c"); PathFinder pathFinder(netlist); // Valid paths. CHECK(pathFinder.find(*inPortA, *outPortA).size() == 4); CHECK(pathFinder.find(*inPortB, *outPortB).size() == 4); + CHECK(pathFinder.find(*inPortA, *outPortC).size() == 4); // Extra path. // Invalid paths. CHECK(pathFinder.find(*inPortA, *outPortB).empty()); CHECK(pathFinder.find(*inPortB, *outPortA).empty()); } +//===---------------------------------------------------------------------===// +// Tests for module instance connectivity. +//===---------------------------------------------------------------------===// + +TEST_CASE("Signal passthrough with a nested module") { + // Test that a nested module is connected correctly. + auto tree = SyntaxTree::fromText(R"( +module passthrough(input logic i_value, output logic o_value); + assign o_value = i_value; +endmodule + +module nested_passthrough(input logic i_value, output logic o_value); + passthrough foo( + .i_value(i_value), + .o_value(o_value)); +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + PathFinder pathFinder(netlist); + auto path = pathFinder.find(*netlist.lookupPort("nested_passthrough.i_value"), + *netlist.lookupPort("nested_passthrough.o_value")); + CHECK(*path.findVariable("nested_passthrough.foo.i_value") == 1); + CHECK(*path.findVariable("nested_passthrough.foo.o_value") == 2); +} + +TEST_CASE("Signal passthrough with a chain of two nested modules") { + // Test that two nested module are connected correctly. + auto tree = SyntaxTree::fromText(R"( +module passthrough(input logic i_value, output logic o_value); + assign o_value = i_value; +endmodule + +module nested_passthrough(input logic i_value, output logic o_value); + logic value; + passthrough foo_a( + .i_value(i_value), + .o_value(value)); + passthrough foo_b( + .i_value(value), + .o_value(o_value)); +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + PathFinder pathFinder(netlist); + auto path = pathFinder.find(*netlist.lookupPort("nested_passthrough.i_value"), + *netlist.lookupPort("nested_passthrough.o_value")); + CHECK(path.size() == 8); + CHECK(*path.findVariable("nested_passthrough.foo_a.i_value") == 1); + CHECK(*path.findVariable("nested_passthrough.foo_a.o_value") == 2); + CHECK(*path.findVariable("nested_passthrough.foo_b.i_value") == 5); + CHECK(*path.findVariable("nested_passthrough.foo_b.o_value") == 6); +} + +//===---------------------------------------------------------------------===// +// Tests for conditional variables in procedural blocks. +//===---------------------------------------------------------------------===// + +TEST_CASE("Mux") { + // Test that the variable in a conditional block is correctly added as a + // dependency on the output variable controlled by that block. + auto tree = SyntaxTree::fromText(R"( +module mux(input a, input b, input sel, output reg f); + always @(*) begin + if (sel == 1'b0) begin + f = a; + end else begin + f = b; + end + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + PathFinder pathFinder(netlist); + CHECK(!pathFinder.find(*netlist.lookupPort("mux.sel"), *netlist.lookupPort("mux.f")).empty()); +} + +TEST_CASE("Nested muxing") { + // Test that the variables in multiple nested levels of conditions are + // correctly added as dependencies of the output variable. + auto tree = SyntaxTree::fromText(R"( +module mux(input a, input b, input c, + input sel_a, input sel_b, + output reg f); + always @(*) begin + if (sel_a == 1'b0) begin + if (sel_b == 1'b0) + f = a; + else + f = b; + end else begin + f = c; + end + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + PathFinder pathFinder(netlist); + CHECK(!pathFinder.find(*netlist.lookupPort("mux.sel_a"), *netlist.lookupPort("mux.f")).empty()); + CHECK(!pathFinder.find(*netlist.lookupPort("mux.sel_b"), *netlist.lookupPort("mux.f")).empty()); +} + //===---------------------------------------------------------------------===// // Tests for loop unrolling //===---------------------------------------------------------------------===// @@ -412,103 +491,37 @@ endmodule } //===---------------------------------------------------------------------===// -// Tests for conditional variables in procedural blocks. +// Test case for #792 (bus expression in ports) //===---------------------------------------------------------------------===// -TEST_CASE("Mux") { - // Test that the variable in a conditional block is correctly added as a - // dependency on the output variable controlled by that block. +TEST_CASE("Test case for #792 (bus expression in ports)") { auto tree = SyntaxTree::fromText(R"( -module mux(input a, input b, input sel, output reg f); - always @(*) begin - if (sel == 1'b0) begin - f = a; - end else begin - f = b; - end - end -endmodule -)"); - Compilation compilation; - compilation.addSyntaxTree(tree); - NO_COMPILATION_ERRORS; - auto netlist = createNetlist(compilation); - PathFinder pathFinder(netlist); - CHECK(!pathFinder.find(*netlist.lookupPort("mux.sel"), *netlist.lookupPort("mux.f")).empty()); -} +module test (input [1:0] in_i, + output [1:0] out_o); -TEST_CASE("Nested muxing") { - // Test that the variables in multiple nested levels of conditions are - // correctly added as dependencies of the output variable. - auto tree = SyntaxTree::fromText(R"( -module mux(input a, input b, input c, - input sel_a, input sel_b, - output reg f); - always @(*) begin - if (sel_a == 1'b0) begin - if (sel_b == 1'b0) - f = a; - else - f = b; - end else begin - f = c; - end - end -endmodule -)"); - Compilation compilation; - compilation.addSyntaxTree(tree); - NO_COMPILATION_ERRORS; - auto netlist = createNetlist(compilation); - PathFinder pathFinder(netlist); - CHECK(!pathFinder.find(*netlist.lookupPort("mux.sel_a"), *netlist.lookupPort("mux.f")).empty()); - CHECK(!pathFinder.find(*netlist.lookupPort("mux.sel_b"), *netlist.lookupPort("mux.f")).empty()); -} - -//===---------------------------------------------------------------------===// -// Tests for name resolution -//===---------------------------------------------------------------------===// + wire [1:0] in_s; -TEST_CASE("Unused modules") { - // Test that unused modules are not visited by the netlist builder. - // See Issue #793. - auto tree = SyntaxTree::fromText(R"( -module test (input i1, - input i2, - output o1 - ); - cell_a i_cell_a(.d1(i1), - .d2(i2), - .c(o1)); -endmodule + assign in_s = in_i; -module cell_a(input d1, - input d2, - output c); - assign c = d1 + d2; + nop i_nop(.in_i(in_s[1:0]), // ok: in_s, in_i, {in_i[1], in_i[0]} + .out_o(out_o)); endmodule -// unused -module cell_b(input a, - input b, - output z); - assign z = a || b; -endmodule +module nop (input [1:0] in_i, + output [1:0] out_o); -// unused -module cell_c(input a, - input b, - output z); - assign z = (!a) && b; + // individual bits access; ok: out_o = in_i; + assign out_o[0] = in_i[0]; + assign out_o[1] = in_i[1]; endmodule )"); - CompilationOptions coptions; - coptions.topModules.emplace("test"sv); - Bag options; - options.set(coptions); - Compilation compilation(options); + Compilation compilation; compilation.addSyntaxTree(tree); NO_COMPILATION_ERRORS; auto netlist = createNetlist(compilation); - CHECK(netlist.numNodes() > 0); + auto* inPort = netlist.lookupPort("test.in_i"); + auto* outPort = netlist.lookupPort("test.out_o"); + PathFinder pathFinder(netlist); + // Valid paths. + CHECK(!pathFinder.find(*inPort, *outPort).empty()); } diff --git a/tools/netlist/tests/VariableSelectorsTests.cpp b/tools/netlist/tests/VariableSelectorsTests.cpp new file mode 100644 index 000000000..d17f87084 --- /dev/null +++ b/tools/netlist/tests/VariableSelectorsTests.cpp @@ -0,0 +1,578 @@ +//------------------------------------------------------------------------------ +//! @file VariableSelectorsTests.cpp +//! @brief Tests for handling of variable selectors. +// +// SPDX-FileCopyrightText: Michael Popoloski +// SPDX-License-Identifier: MIT +//------------------------------------------------------------------------------ + +#include "NetlistTest.h" +#include "SplitVariables.h" +#include + +#include "slang/util/Util.h" + +/// Helper method to extract a variable reference from a netlist and return the +/// bit range associated with it. +ConstantRange getBitRange(Netlist& netlist, std::string_view variableSyntax) { + auto* node = netlist.lookupVariableReference(variableSyntax); + if (node == nullptr) { + SLANG_THROW(std::runtime_error(fmt::format("Could not find node {}", variableSyntax))); + } + return AnalyseVariableReference::create(*node).getBitRange(); +} + +//===---------------------------------------------------------------------===// +// Scalar selectors. +//===---------------------------------------------------------------------===// + +TEST_CASE("Scalar element and range") { + auto tree = SyntaxTree::fromText(R"( +module m (input int a); + int foo; + always_comb begin + foo = 0; + foo[0] = 0; + foo[1] = 0; + foo[7:7] = 0; + foo[1:0] = 0; + foo[3:1] = 0; + foo[7:4] = 0; + foo[3:1][2:1] = 0; + foo[7:4][6:5] = 0; + foo[3:1][2:1][1] = 0; + foo[7:4][6:5][5] = 0; + foo[a] = 0; + foo[a+:1] = 0; + foo[a-:1] = 0; + foo[a+:1][a] = 0; + foo[a-:1][a] = 0; + foo[a+:1][a-:1] = 0; + foo[a+:1][a-:1][a] = 0; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo[0]") == ConstantRange(0, 0)); + CHECK(getBitRange(netlist, "foo[1]") == ConstantRange(1, 1)); + CHECK(getBitRange(netlist, "foo[7:7]") == ConstantRange(7, 7)); + CHECK(getBitRange(netlist, "foo[1:0]") == ConstantRange(0, 1)); + CHECK(getBitRange(netlist, "foo[3:1]") == ConstantRange(1, 3)); + CHECK(getBitRange(netlist, "foo[7:4]") == ConstantRange(4, 7)); + CHECK(getBitRange(netlist, "foo[3:1][2:1]") == ConstantRange(1, 2)); + CHECK(getBitRange(netlist, "foo[7:4][6:5]") == ConstantRange(5, 6)); + CHECK(getBitRange(netlist, "foo[3:1][2:1][1]") == ConstantRange(1, 1)); + CHECK(getBitRange(netlist, "foo[7:4][6:5][5]") == ConstantRange(5, 5)); + // Dynamic indices. + CHECK(getBitRange(netlist, "foo[a]") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo[a+:1]") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo[a-:1]") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo[a+:1][a]") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo[a-:1][a]") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo[a+:1][a-:1]") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo[a+:1][a-:1][a]") == ConstantRange(0, 31)); +} + +//===---------------------------------------------------------------------===// +// Packed array selectors. +//===---------------------------------------------------------------------===// + +TEST_CASE("Packed 1D array element and range") { + auto tree = SyntaxTree::fromText(R"( +module m (input int a); + logic [3:0] foo; + always_comb begin + foo = 0; + foo[0] = 0; + foo[1] = 0; + foo[2] = 0; + foo[3] = 0; + foo[3:3] = 0; + foo[1:0] = 0; + foo[3:0] = 0; + foo[2:1] = 0; + foo[3:1][2:1][1] = 0; + foo[a] = 0; + foo[a+:1] = 0; + foo[a-:1] = 0; + foo[a+:1][a] = 0; + foo[a-:1][a] = 0; + foo[a+:1][a-:1] = 0; + foo[a+:1][a-:1][a] = 0; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[0]") == ConstantRange(0, 0)); + CHECK(getBitRange(netlist, "foo[1]") == ConstantRange(1, 1)); + CHECK(getBitRange(netlist, "foo[2]") == ConstantRange(2, 2)); + CHECK(getBitRange(netlist, "foo[3]") == ConstantRange(3, 3)); + CHECK(getBitRange(netlist, "foo[3:3]") == ConstantRange(3, 3)); + CHECK(getBitRange(netlist, "foo[1:0]") == ConstantRange(0, 1)); + CHECK(getBitRange(netlist, "foo[3:0]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[2:1]") == ConstantRange(1, 2)); + CHECK(getBitRange(netlist, "foo[3:1][2:1][1]") == ConstantRange(1, 1)); + // Dynamic indices. + CHECK(getBitRange(netlist, "foo[a]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[a+:1]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[a-:1]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[a+:1][a]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[a-:1][a]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[a+:1][a-:1]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[a+:1][a-:1][a]") == ConstantRange(0, 3)); +} + +TEST_CASE("Packed 1D array element and range non-zero indexed") { + auto tree = SyntaxTree::fromText(R"( +module m (input int a); + logic [7:4] foo; + always_comb begin + foo = 0; + foo[4] = 0; + foo[5] = 0; + foo[6] = 0; + foo[7] = 0; + foo[7:7] = 0; + foo[5:4] = 0; + foo[7:4] = 0; + foo[6:5] = 0; + foo[7:4][6:5][5] = 0; + foo[a] = 0; + foo[a+:1] = 0; + foo[a-:1] = 0; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[4]") == ConstantRange(0, 0)); + CHECK(getBitRange(netlist, "foo[5]") == ConstantRange(1, 1)); + CHECK(getBitRange(netlist, "foo[6]") == ConstantRange(2, 2)); + CHECK(getBitRange(netlist, "foo[7]") == ConstantRange(3, 3)); + CHECK(getBitRange(netlist, "foo[7:7]") == ConstantRange(3, 3)); + CHECK(getBitRange(netlist, "foo[5:4]") == ConstantRange(0, 1)); + CHECK(getBitRange(netlist, "foo[7:4]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[6:5]") == ConstantRange(1, 2)); + CHECK(getBitRange(netlist, "foo[7:4][6:5][5]") == ConstantRange(1, 1)); + // Dynamic indices. + CHECK(getBitRange(netlist, "foo[a]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[a+:1]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[a-:1]") == ConstantRange(0, 3)); +} + +TEST_CASE("Packed 2D array element and range") { + auto tree = SyntaxTree::fromText(R"( +module m (input int a); + logic [3:0] [1:0] foo; + always_comb begin + foo = 0; + foo[0] = 0; + foo[1] = 0; + foo[2] = 0; + foo[3] = 0; + foo[0][1] = 0; + foo[1][1] = 0; + foo[2][1] = 0; + foo[3][1] = 0; + foo[1:0] = 0; + foo[3:2] = 0; + foo[3:0][2:1] = 0; + foo[3:0][2:1][1] = 0; + foo[a] = 0; + foo[a][1] = 0; + foo[a][a] = 0; + foo[a+:1] = 0; + foo[a-:1] = 0; + foo[1][a] = 0; + foo[1][a+:1] = 0; + foo[1][a-:1] = 0; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[0]") == ConstantRange(0, 1)); + CHECK(getBitRange(netlist, "foo[1]") == ConstantRange(2, 3)); + CHECK(getBitRange(netlist, "foo[2]") == ConstantRange(4, 5)); + CHECK(getBitRange(netlist, "foo[3]") == ConstantRange(6, 7)); + CHECK(getBitRange(netlist, "foo[0][1]") == ConstantRange(1, 1)); + CHECK(getBitRange(netlist, "foo[1][1]") == ConstantRange(3, 3)); + CHECK(getBitRange(netlist, "foo[2][1]") == ConstantRange(5, 5)); + CHECK(getBitRange(netlist, "foo[3][1]") == ConstantRange(7, 7)); + CHECK(getBitRange(netlist, "foo[1:0]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[3:2]") == ConstantRange(4, 7)); + CHECK(getBitRange(netlist, "foo[3:0][2:1]") == ConstantRange(2, 5)); + CHECK(getBitRange(netlist, "foo[3:0][2:1][1]") == ConstantRange(2, 3)); + // Dynamic indices. + CHECK(getBitRange(netlist, "foo[a]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[a][1]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[a][a]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[a+:1]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[a-:1]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[1][a]") == ConstantRange(2, 3)); + CHECK(getBitRange(netlist, "foo[1][a+:1]") == ConstantRange(2, 3)); + CHECK(getBitRange(netlist, "foo[1][a-:1]") == ConstantRange(2, 3)); +} + +TEST_CASE("Packed 2D array element and range, non-zero indexing") { + auto tree = SyntaxTree::fromText(R"( +module m (input int a); + logic [7:4] [3:2] foo; + always_comb begin + foo = 0; + foo[4] = 0; + foo[4][3] = 0; + foo[5:4] = 0; + foo[7:4][6:5] = 0; + foo[7:5][6:5][5] = 0; + foo[a] = 0; + foo[a+:1] = 0; + foo[a-:1] = 0; + foo[5][a] = 0; + foo[5][a+:1] = 0; + foo[5][a-:1] = 0; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[4]") == ConstantRange(0, 1)); + CHECK(getBitRange(netlist, "foo[4][3]") == ConstantRange(1, 1)); + CHECK(getBitRange(netlist, "foo[5:4]") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[7:4][6:5]") == ConstantRange(2, 5)); + CHECK(getBitRange(netlist, "foo[7:5][6:5][5]") == ConstantRange(2, 3)); + // Dynamic indices. + CHECK(getBitRange(netlist, "foo[a]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[a+:1]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[a-:1]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[5][a]") == ConstantRange(2, 3)); + CHECK(getBitRange(netlist, "foo[5][a+:1]") == ConstantRange(2, 3)); + CHECK(getBitRange(netlist, "foo[5][a-:1]") == ConstantRange(2, 3)); +} + +//===---------------------------------------------------------------------===// +// Unpacked array selectors. +//===---------------------------------------------------------------------===// + +TEST_CASE("Unpacked 1D array element") { + auto tree = SyntaxTree::fromText(R"( +module m (input int a); + logic foo [1:0]; + logic bar [1:0]; + always_comb begin + foo = bar; + foo[0] = 0; + foo[1] = 0; + foo[a] = 0; + foo[a+:1] = '{0}; + foo[a-:2] = '{0, 0}; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 1)); + CHECK(getBitRange(netlist, "foo[0]") == ConstantRange(0, 0)); + CHECK(getBitRange(netlist, "foo[1]") == ConstantRange(1, 1)); + // Dynamic indices. + CHECK(getBitRange(netlist, "foo[a]") == ConstantRange(0, 1)); + CHECK(getBitRange(netlist, "foo[a+:1]") == ConstantRange(0, 1)); + CHECK(getBitRange(netlist, "foo[a-:2]") == ConstantRange(0, 1)); +} + +TEST_CASE("Unpacked 2D array element and range") { + auto tree = SyntaxTree::fromText(R"( +module m (input int a); + logic foo [3:0] [1:0]; + logic bar [1:0]; + always_comb begin + foo[0] = bar; + foo[1] = bar; + foo[2] = bar; + foo[3] = bar; + foo[0][1] = 0; + foo[1][1] = 0; + foo[2][1] = 0; + foo[3][1] = 0; + foo[a] = bar; + foo[a][1] = 0; + foo[a][a] = 0; + foo[a+:1] = '{'{0, 0}}; + foo[a-:2] = '{'{0, 0}, '{0, 0}}; + foo[1][a] = 0; + foo[1][a+:1] = '{0}; + foo[1][a-:2] = '{0, 0}; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo[0]") == ConstantRange(0, 1)); + CHECK(getBitRange(netlist, "foo[1]") == ConstantRange(2, 3)); + CHECK(getBitRange(netlist, "foo[0][1]") == ConstantRange(1, 1)); + CHECK(getBitRange(netlist, "foo[1][1]") == ConstantRange(3, 3)); + CHECK(getBitRange(netlist, "foo[2][1]") == ConstantRange(5, 5)); + CHECK(getBitRange(netlist, "foo[3][1]") == ConstantRange(7, 7)); + // Dynamic indices. + CHECK(getBitRange(netlist, "foo[a]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[a][1]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[a][a]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[a+:1]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[a-:2]") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[1][a]") == ConstantRange(2, 3)); + CHECK(getBitRange(netlist, "foo[1][a+:1]") == ConstantRange(2, 3)); + CHECK(getBitRange(netlist, "foo[1][a-:2]") == ConstantRange(2, 3)); +} + +//===---------------------------------------------------------------------===// +// Struct, union and enum selectors. +//===---------------------------------------------------------------------===// + +TEST_CASE("Struct member") { + auto tree = SyntaxTree::fromText(R"( +module m; + struct packed { + logic a; + int b; + longint c; + } foo; + always_comb begin + foo = 0; + foo.a = 0; + foo.b = 0; + foo.c = 0; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 96)); + CHECK(getBitRange(netlist, "foo.a") == ConstantRange(0, 0)); + CHECK(getBitRange(netlist, "foo.b") == ConstantRange(1, 32)); + CHECK(getBitRange(netlist, "foo.c") == ConstantRange(33, 96)); +} + +TEST_CASE("Union member") { + auto tree = SyntaxTree::fromText(R"( +module m; + union packed { + int a, b, c; + } foo; + always_comb begin + foo = 0; + foo.a = 0; + foo.b = 0; + foo.c = 0; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo.a") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo.b") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo.c") == ConstantRange(0, 31)); +} + +TEST_CASE("Enum member") { + auto tree = SyntaxTree::fromText(R"( +module m; + typedef enum logic [7:0] { A, B, C } foo_t; + foo_t foo; + assign foo = A; +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 7)); +} + +//===---------------------------------------------------------------------===// +// Combine selection of types with arrays, structs, unions and enums. +//===---------------------------------------------------------------------===// + +TEST_CASE("Struct with packed array members") { + // Test recursion from packed struct. + auto tree = SyntaxTree::fromText(R"( +module m; + struct packed { + logic [3:0] a, b; + } foo; + always_comb begin + foo = 0; + foo[1] = 0; + foo[5:1] = 0; + foo.a[2:1] = 0; + foo.b[2:1] = 0; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 7)); + CHECK(getBitRange(netlist, "foo[1]") == ConstantRange(1, 1)); + CHECK(getBitRange(netlist, "foo[5:1]") == ConstantRange(1, 5)); + CHECK(getBitRange(netlist, "foo.a[2:1]") == ConstantRange(1, 2)); + CHECK(getBitRange(netlist, "foo.b[2:1]") == ConstantRange(5, 6)); +} + +TEST_CASE("Packed struct with packed union and enum members") { + // Test recursion from packed struct. + auto tree = SyntaxTree::fromText(R"( +module m; + typedef enum int { A, B, C } enum_t; + struct packed { + union packed { + logic [3:0] a, b; + } u; + enum_t c; + } foo; + always_comb begin + foo = 0; + foo[1] = 0; + foo.u = 0; + foo.u[2:1] = 0; + foo.u.a[2:1] = 0; + foo.u.b[2:1] = 0; + foo.c = A; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 35)); + CHECK(getBitRange(netlist, "foo[1]") == ConstantRange(1, 1)); + CHECK(getBitRange(netlist, "foo.u") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo.u[2:1]") == ConstantRange(1, 2)); + CHECK(getBitRange(netlist, "foo.u.a[2:1]") == ConstantRange(1, 2)); + CHECK(getBitRange(netlist, "foo.u.b[2:1]") == ConstantRange(1, 2)); + CHECK(getBitRange(netlist, "foo.c") == ConstantRange(4, 35)); +} + +TEST_CASE("Packed arrays of structs etc") { + // Test recursion from packed packed array, packed struct, packed union. + auto tree = SyntaxTree::fromText(R"( +module m; + typedef enum int { A, B, C } enum_t; + typedef struct packed { + union packed { + logic [3:0] a, b; + } u; + logic [1:0] c; + enum_t d; + } foo_t; + foo_t [3:0] [1:0] foo; + always_comb begin + foo = 0; + foo[0] = 0; + foo[1] = 0; + foo[0][0] = 0; + foo[0][1] = 0; + foo[0][0].u.a = 0; + foo[0][1].u.a = 0; + foo[0][0].u.b = 0; + foo[0][1].u.b = 0; + foo[0][0].c = 0; + foo[0][1].c = 0; + foo[0][0].d = A; + foo[0][1].d = A; + foo[3][1] = 0; + foo[3][1].u.a = 0; + foo[3][1].u.b = 0; + foo[3][1].c = 0; + foo[3][1].d = A; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 303)); + CHECK(getBitRange(netlist, "foo[0]") == ConstantRange(0, 75)); + CHECK(getBitRange(netlist, "foo[1]") == ConstantRange(76, 151)); + CHECK(getBitRange(netlist, "foo[0][0]") == ConstantRange(0, 37)); + CHECK(getBitRange(netlist, "foo[0][1]") == ConstantRange(38, 75)); + CHECK(getBitRange(netlist, "foo[0][0].u.a") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[0][1].u.a") == ConstantRange(38, 41)); + CHECK(getBitRange(netlist, "foo[0][0].u.b") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[0][1].u.b") == ConstantRange(38, 41)); + CHECK(getBitRange(netlist, "foo[0][0].c") == ConstantRange(4, 5)); + CHECK(getBitRange(netlist, "foo[0][1].c") == ConstantRange(42, 43)); + CHECK(getBitRange(netlist, "foo[0][0].d") == ConstantRange(6, 37)); + CHECK(getBitRange(netlist, "foo[0][1].d") == ConstantRange(44, 75)); + CHECK(getBitRange(netlist, "foo[3][1]") == ConstantRange(266, 303)); + CHECK(getBitRange(netlist, "foo[3][1].u.a") == ConstantRange(266, 269)); + CHECK(getBitRange(netlist, "foo[3][1].u.b") == ConstantRange(266, 269)); + CHECK(getBitRange(netlist, "foo[3][1].c") == ConstantRange(270, 271)); + CHECK(getBitRange(netlist, "foo[3][1].d") == ConstantRange(272, 303)); +} + +TEST_CASE("Union with packed struct members") { + // Test recursion from packed union, packed struct. + auto tree = SyntaxTree::fromText(R"( +module m; + typedef enum int { A, B, C } enum_t; + union packed { + struct packed { + logic [3:0] a, b; + } x, y; + } [3:0] foo; + always_comb begin + foo = 0; + foo[0].x.a = 0; + foo[0].x.b = 0; + foo[0].y.a = 0; + foo[0].y.b = 0; + foo[3].x.a = 0; + foo[3].x.b = 0; + foo[3].y.a = 0; + foo[3].y.b = 0; + end +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + CHECK(getBitRange(netlist, "foo") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo[0].x.a") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[0].x.b") == ConstantRange(4, 7)); + CHECK(getBitRange(netlist, "foo[0].y.a") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[0].y.b") == ConstantRange(4, 7)); + CHECK(getBitRange(netlist, "foo[3].x.a") == ConstantRange(24, 27)); + CHECK(getBitRange(netlist, "foo[3].x.b") == ConstantRange(28, 31)); + CHECK(getBitRange(netlist, "foo[3].y.a") == ConstantRange(24, 27)); + CHECK(getBitRange(netlist, "foo[3].y.b") == ConstantRange(28, 31)); +}