From 6d4445ebfabad2fc327f3f5fe33bbb53a6947f64 Mon Sep 17 00:00:00 2001 From: James Hanlon Date: Sat, 28 Oct 2023 17:03:38 +0100 Subject: [PATCH] [slang-netlist] Refactor to use ValueDriver::getBounds (#839) --- tools/netlist/include/Config.h | 3 +- tools/netlist/include/Debug.h | 32 +- tools/netlist/include/Netlist.h | 76 ++- tools/netlist/include/NetlistVisitor.h | 65 ++- tools/netlist/include/SplitVariables.h | 481 ------------------ tools/netlist/netlist.cpp | 10 +- tools/netlist/tests/NetlistTest.h | 3 +- .../netlist/tests/VariableSelectorsTests.cpp | 99 ++-- 8 files changed, 215 insertions(+), 554 deletions(-) delete mode 100644 tools/netlist/include/SplitVariables.h diff --git a/tools/netlist/include/Config.h b/tools/netlist/include/Config.h index 1d0c4622c..ffe83245c 100644 --- a/tools/netlist/include/Config.h +++ b/tools/netlist/include/Config.h @@ -12,7 +12,8 @@ namespace netlist { /// A singleton to hold global configuration options. class Config { public: - bool debugEnabled{}; + bool debugEnabled{false}; + bool quietEnabled{false}; Config() = default; diff --git a/tools/netlist/include/Debug.h b/tools/netlist/include/Debug.h index fd73b9939..ed2c78402 100644 --- a/tools/netlist/include/Debug.h +++ b/tools/netlist/include/Debug.h @@ -1,6 +1,6 @@ //------------------------------------------------------------------------------ //! @file Debug.h -//! @brief Provide a debug printing macro. +//! @brief Provide debug printing macros. // // SPDX-FileCopyrightText: Michael Popoloski // SPDX-License-Identifier: MIT @@ -8,13 +8,33 @@ #pragma once #include "Config.h" -#include +#include + +namespace netlist { + +template +void DebugMessage(const char* filename, const int line, fmt::format_string fmt, T&&... args) { + fmt::print("{}:{}: ", filename, line); + fmt::print(fmt, std::forward(args)...); +} + +template +void InfoMessage(fmt::format_string fmt, T&&... args) { + fmt::print(fmt, std::forward(args)...); +} + +} // namespace netlist #ifdef DEBUG -# define DEBUG_PRINT(x) \ - if (netlist::Config::getInstance().debugEnabled) { \ - std::cerr << x; \ +# define DEBUG_PRINT(str, ...) \ + if (netlist::Config::getInstance().debugEnabled) { \ + DebugMessage(__FILE__, __LINE__, str __VA_OPT__(, ) __VA_ARGS__); \ } #else -# define DEBUG_PRINT(x) +# define DEBUG_PRINT(str, ...) #endif + +#define INFO_PRINT(str, ...) \ + if (!Config::getInstance().quietEnabled) { \ + InfoMessage(str __VA_OPT__(, ) __VA_ARGS__); \ + } diff --git a/tools/netlist/include/Netlist.h b/tools/netlist/include/Netlist.h index 202df52a1..7544641d4 100644 --- a/tools/netlist/include/Netlist.h +++ b/tools/netlist/include/Netlist.h @@ -13,11 +13,17 @@ #include "fmt/color.h" #include "fmt/format.h" #include +#include #include "slang/ast/ASTVisitor.h" #include "slang/ast/Expression.h" +#include "slang/ast/Symbol.h" #include "slang/ast/symbols/CompilationUnitSymbols.h" +#include "slang/ast/types/AllTypes.h" +#include "slang/ast/types/Type.h" #include "slang/diagnostics/TextDiagnosticClient.h" +#include "slang/numeric/ConstantValue.h" +#include "slang/numeric/SVInt.h" #include "slang/syntax/SyntaxTree.h" #include "slang/syntax/SyntaxVisitor.h" #include "slang/util/Util.h" @@ -313,6 +319,8 @@ class NetlistVariableReference : public NetlistNode { bool leftOperand; /// Selectors applied to the variable reference. SelectorsListType selectors; + // Access bounds. + ConstantRange bounds; }; /// A class representing the design netlist. @@ -328,7 +336,7 @@ class Netlist : public DirectedGraph { SLANG_ASSERT(lookupPort(nodePtr->hierarchicalPath) == nullptr && "Port declaration already exists"); nodes.push_back(std::move(nodePtr)); - DEBUG_PRINT("Add port decl " << node.hierarchicalPath << "\n"); + DEBUG_PRINT("Add port decl {}\n", node.hierarchicalPath); return node; } @@ -340,7 +348,7 @@ class Netlist : public DirectedGraph { SLANG_ASSERT(lookupVariable(nodePtr->hierarchicalPath) == nullptr && "Variable declaration already exists"); nodes.push_back(std::move(nodePtr)); - DEBUG_PRINT("Add var decl " << node.hierarchicalPath << "\n"); + DEBUG_PRINT("Add var decl {}\n", node.hierarchicalPath); return node; } @@ -350,7 +358,7 @@ class Netlist : public DirectedGraph { auto& node = nodePtr->as(); symbol.getHierarchicalPath(node.hierarchicalPath); nodes.push_back(std::move(nodePtr)); - DEBUG_PRINT("Add var alias " << node.hierarchicalPath << "\n"); + DEBUG_PRINT("Add var alias {}\n", node.hierarchicalPath); return node; } @@ -360,7 +368,7 @@ class Netlist : public DirectedGraph { auto nodePtr = std::make_unique(symbol, expr, leftOperand); auto& node = nodePtr->as(); nodes.push_back(std::move(nodePtr)); - DEBUG_PRINT("Add var ref " << symbol.name << "\n"); + DEBUG_PRINT("Add var ref ", symbol.name); return node; } @@ -396,6 +404,66 @@ class Netlist : public DirectedGraph { auto it = std::ranges::find_if(*this, compareNode); return it != end() ? &it->get()->as() : nullptr; } + + /// Perform a transformation on the netlist graph to split variable / + /// declaration nodes into multiple parts corresponding to / the access ranges of + /// references to the variable incoming (l-values) and outgoing edges (r-values). + void split() { + std::vector> + modifications; + // Find each variable declaration nodes in the graph that has multiple + // outgoing edges. + for (auto& node : nodes) { + if (node->kind == NodeKind::VariableDeclaration && node->outDegree() > 1) { + auto& varDeclNode = node->as(); + auto& varType = varDeclNode.symbol.getDeclaredType()->getType(); + DEBUG_PRINT("Variable {} has type {}\n", varDeclNode.hierarchicalPath, + varType.toString()); + std::vector inEdges; + getInEdgesToNode(*node, inEdges); + // Find pairs of input and output edges that are attached to variable + // refertence nodes. Eg. + // var ref -> var decl -> var ref + // If the variable references select the same part of a structured + // variable, then transform them into: + // var ref -> var alias -> var ref + // And mark the original edges as disabled. + for (auto* inEdge : inEdges) { + for (auto& outEdge : *node) { + if (inEdge->getSourceNode().kind == NodeKind::VariableReference && + outEdge->getTargetNode().kind == NodeKind::VariableReference) { + auto& sourceVarRef = + inEdge->getSourceNode().as(); + auto& targetVarRef = + outEdge->getTargetNode().as(); + // Match if the selection made by the target node intersects with the + // selection made by the source node. + auto match = sourceVarRef.bounds.overlaps(targetVarRef.bounds); + if (match) { + DEBUG_PRINT("New dependency through variable {} -> {}\n", + sourceVarRef.toString(), targetVarRef.toString()); + modifications.emplace_back(&varDeclNode, inEdge, outEdge.get()); + } + } + } + } + } + } + // Apply the operations to the graph. + for (auto& modification : modifications) { + auto* varDeclNode = std::get<0>(modification); + auto* inEdge = std::get<1>(modification); + auto* outEdge = std::get<2>(modification); + // Disable the existing edges. + inEdge->disable(); + outEdge->disable(); + // Create a new node that aliases the variable declaration. + auto& varAliasNode = addVariableAlias(varDeclNode->symbol); + // Create edges through the new node. + inEdge->getSourceNode().addEdge(varAliasNode); + varAliasNode.addEdge(outEdge->getTargetNode()); + } + } }; } // namespace netlist diff --git a/tools/netlist/include/NetlistVisitor.h b/tools/netlist/include/NetlistVisitor.h index 1e9b142d6..4c280e45d 100644 --- a/tools/netlist/include/NetlistVisitor.h +++ b/tools/netlist/include/NetlistVisitor.h @@ -24,6 +24,7 @@ #include "slang/ast/Symbol.h" #include "slang/ast/symbols/BlockSymbols.h" #include "slang/ast/symbols/CompilationUnitSymbols.h" +#include "slang/ast/symbols/ValueSymbol.h" #include "slang/diagnostics/TextDiagnosticClient.h" #include "slang/syntax/SyntaxTree.h" #include "slang/syntax/SyntaxVisitor.h" @@ -43,21 +44,20 @@ static void connectDeclToVar(Netlist& netlist, NetlistNode& declNode, const std::string& hierarchicalPath) { auto* varNode = netlist.lookupVariable(hierarchicalPath); netlist.addEdge(*varNode, declNode); - DEBUG_PRINT(fmt::format("Edge decl {} to ref {}\n", varNode->getName(), declNode.getName())); + DEBUG_PRINT("Edge decl {} to ref {}\n", varNode->getName(), declNode.getName()); } static void connectVarToDecl(Netlist& netlist, NetlistNode& varNode, const std::string& hierarchicalPath) { auto* declNode = netlist.lookupVariable(hierarchicalPath); netlist.addEdge(varNode, *declNode); - DEBUG_PRINT(fmt::format("Edge ref {} to decl {}\n", varNode.getName(), declNode->getName())); + DEBUG_PRINT("Edge ref {} to decl {}\n", varNode.getName(), declNode->getName()); } static void connectVarToVar(Netlist& netlist, NetlistNode& sourceVarNode, NetlistNode& targetVarNode) { netlist.addEdge(sourceVarNode, targetVarNode); - DEBUG_PRINT( - fmt::format("Edge ref {} to ref {}\n", sourceVarNode.getName(), targetVarNode.getName())); + DEBUG_PRINT("Edge ref {} to ref {}\n", sourceVarNode.getName(), targetVarNode.getName()); } /// An AST visitor to identify variable references with selectors in @@ -70,11 +70,14 @@ class VariableReferenceVisitor : public ast::ASTVisitoras().member.name); } } + + // Reverse the selectors. std::reverse(node.selectors.begin(), node.selectors.end()); + + // Determine the access range to the variable. + if (!selectors.empty()) { + SmallVector> + prefixes; + selectors.front()->getLongestStaticPrefixes(prefixes, evalCtx); + SLANG_ASSERT(prefixes.size() == 1); + auto [prefixSymbol, prefixExpr] = prefixes.back(); + auto bounds = slang::ast::ValueDriver::getBounds(*prefixExpr, evalCtx, + prefixSymbol->getType()); + node.bounds = {static_cast(bounds->first), + static_cast(bounds->second)}; + } + else { + node.bounds = {0, getTypeBitWidth(expr.symbol.getType()) - 1}; + } + DEBUG_PRINT("Variable reference: {} bounds {}:{}\n", node.toString(), node.bounds.lower(), + node.bounds.upper()); + + // Clear the selectors for the next named value. selectors.clear(); } @@ -121,6 +146,30 @@ class VariableReferenceVisitor : public ast::ASTVisitor varList; std::vector selectors; + + 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); + } }; /// An AST visitor to create dependencies between occurrances of variables @@ -145,7 +194,7 @@ class AssignmentVisitor : public ast::ASTVisitor for (auto* rightNode : visitorRHS.getVars()) { // Add edge from variable declaration to RHS variable reference. connectDeclToVar(netlist, *rightNode, getSymbolHierPath(rightNode->symbol)); - // Add edge form RHS expression term to LHS expression terms. + // Add edge from RHS expression term to LHS expression terms. connectVarToVar(netlist, *rightNode, *leftNode); } } @@ -371,7 +420,7 @@ class NetlistVisitor : public ast::ASTVisitor { /// Instance. void handle(const ast::InstanceSymbol& symbol) { - DEBUG_PRINT(fmt::format("Instance {}\n", symbol.name)); + DEBUG_PRINT("Instance {}\n", symbol.name); if (symbol.name.empty()) { // An instance without a name has been excluded from the design. // This can happen when the --top option is used and there is an diff --git a/tools/netlist/include/SplitVariables.h b/tools/netlist/include/SplitVariables.h deleted file mode 100644 index 22b1bc85f..000000000 --- a/tools/netlist/include/SplitVariables.h +++ /dev/null @@ -1,481 +0,0 @@ -//------------------------------------------------------------------------------ -//! @file SplitVariables.h -//! @brief Transform netlist variable nodes to reflect connections with -/// structured variables. -// -// SPDX-FileCopyrightText: Michael Popoloski -// SPDX-License-Identifier: MIT -//------------------------------------------------------------------------------ -#pragma once - -#include "Netlist.h" -#include "fmt/color.h" -#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. -class SplitVariables { -public: - SplitVariables(Netlist& netlist) : netlist(netlist) { split(); } - -private: - /// 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 { - return AnalyseVariableReference(sourceNode) - .getBitRange() - .overlaps(AnalyseVariableReference(targetNode).getBitRange()); - } - - void split() { - std::vector> - modifications; - // Find each variable declaration nodes in the graph that has multiple - // outgoing edges. - for (auto& node : netlist) { - if (node->kind == NodeKind::VariableDeclaration && node->outDegree() > 1) { - auto& varDeclNode = node->as(); - auto& varType = varDeclNode.symbol.getDeclaredType()->getType(); - DEBUG_PRINT(fmt::format("Variable {} has type {}\n", varDeclNode.hierarchicalPath, - varType.toString())); - std::vector inEdges; - netlist.getInEdgesToNode(*node, inEdges); - // Find pairs of input and output edges that are attached to variable - // refertence nodes. Eg. - // var ref -> var decl -> var ref - // If the variable references select the same part of a structured - // variable, then transform them into: - // var ref -> var alias -> var ref - // And mark the original edges as disabled. - for (auto* inEdge : inEdges) { - for (auto& outEdge : *node) { - if (inEdge->getSourceNode().kind == NodeKind::VariableReference && - outEdge->getTargetNode().kind == NodeKind::VariableReference) { - auto& sourceVarRef = - inEdge->getSourceNode().as(); - auto& targetVarRef = - outEdge->getTargetNode().as(); - auto match = isIntersectingSelection(sourceVarRef, targetVarRef); - if (match) { - DEBUG_PRINT( - fmt::format("New dependency through variable {} -> {}\n", - sourceVarRef.toString(), targetVarRef.toString())); - modifications.emplace_back(&varDeclNode, inEdge, outEdge.get()); - } - } - } - } - } - } - // Apply the operations to the graph. - for (auto& modification : modifications) { - auto* varDeclNode = std::get<0>(modification); - auto* inEdge = std::get<1>(modification); - auto* outEdge = std::get<2>(modification); - // Disable the existing edges. - inEdge->disable(); - outEdge->disable(); - // Create a new node that aliases the variable declaration. - auto& varAliasNode = netlist.addVariableAlias(varDeclNode->symbol); - // Create edges through the new node. - inEdge->getSourceNode().addEdge(varAliasNode); - varAliasNode.addEdge(outEdge->getTargetNode()); - } - } - -private: - Netlist& netlist; -}; - -} // namespace netlist diff --git a/tools/netlist/netlist.cpp b/tools/netlist/netlist.cpp index f52b54c77..ede0b855f 100644 --- a/tools/netlist/netlist.cpp +++ b/tools/netlist/netlist.cpp @@ -9,7 +9,6 @@ #include "NetlistVisitor.h" #include "PathFinder.h" -#include "SplitVariables.h" #include "fmt/color.h" #include "fmt/format.h" #include @@ -200,6 +199,10 @@ int main(int argc, char** argv) { Config::getInstance().debugEnabled = true; } + if (quiet) { + Config::getInstance().quietEnabled = true; + } + SLANG_TRY { bool ok = driver.parseAllSources(); @@ -220,9 +223,8 @@ int main(int argc, char** argv) { Netlist netlist; NetlistVisitor visitor(*compilation, netlist); compilation->getRoot().visit(visitor); - SplitVariables splitVariables(netlist); - DEBUG_PRINT(fmt::format("Netlist has {} nodes and {} edges\n", netlist.numNodes(), - netlist.numEdges())); + netlist.split(); + DEBUG_PRINT("Netlist has {} nodes and {} edges\n", netlist.numNodes(), netlist.numEdges()); // Output a DOT file of the netlist. if (netlistDotFile) { diff --git a/tools/netlist/tests/NetlistTest.h b/tools/netlist/tests/NetlistTest.h index f92b6a88f..917333913 100644 --- a/tools/netlist/tests/NetlistTest.h +++ b/tools/netlist/tests/NetlistTest.h @@ -4,7 +4,6 @@ #include "Netlist.h" #include "NetlistVisitor.h" #include "PathFinder.h" -#include "SplitVariables.h" #include "Test.h" #include @@ -14,7 +13,7 @@ inline Netlist createNetlist(Compilation& compilation) { Netlist netlist; NetlistVisitor visitor(compilation, netlist); compilation.getRoot().visit(visitor); - SplitVariables splitVariables(netlist); + netlist.split(); return netlist; } diff --git a/tools/netlist/tests/VariableSelectorsTests.cpp b/tools/netlist/tests/VariableSelectorsTests.cpp index d17f87084..281942d7f 100644 --- a/tools/netlist/tests/VariableSelectorsTests.cpp +++ b/tools/netlist/tests/VariableSelectorsTests.cpp @@ -7,7 +7,6 @@ //------------------------------------------------------------------------------ #include "NetlistTest.h" -#include "SplitVariables.h" #include #include "slang/util/Util.h" @@ -19,7 +18,7 @@ ConstantRange getBitRange(Netlist& netlist, std::string_view variableSyntax) { if (node == nullptr) { SLANG_THROW(std::runtime_error(fmt::format("Could not find node {}", variableSyntax))); } - return AnalyseVariableReference::create(*node).getBitRange(); + return node->bounds; } //===---------------------------------------------------------------------===// @@ -274,12 +273,13 @@ endmodule TEST_CASE("Unpacked 1D array element") { auto tree = SyntaxTree::fromText(R"( module m (input int a); - logic foo [1:0]; - logic bar [1:0]; + logic foo [2:0]; + logic bar [2:0]; always_comb begin foo = bar; foo[0] = 0; foo[1] = 0; + foo[2] = 0; foo[a] = 0; foo[a+:1] = '{0}; foo[a-:2] = '{0, 0}; @@ -290,13 +290,14 @@ endmodule 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") == ConstantRange(0, 2)); + CHECK(getBitRange(netlist, "foo[0]") == ConstantRange(2, 2)); CHECK(getBitRange(netlist, "foo[1]") == ConstantRange(1, 1)); + CHECK(getBitRange(netlist, "foo[2]") == ConstantRange(0, 0)); // 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)); + CHECK(getBitRange(netlist, "foo[a]") == ConstantRange(0, 2)); + CHECK(getBitRange(netlist, "foo[a+:1]") == ConstantRange(0, 2)); + CHECK(getBitRange(netlist, "foo[a-:2]") == ConstantRange(0, 2)); } TEST_CASE("Unpacked 2D array element and range") { @@ -328,21 +329,23 @@ endmodule 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)); + CHECK(getBitRange(netlist, "foo[0]") == ConstantRange(6, 7)); + CHECK(getBitRange(netlist, "foo[1]") == ConstantRange(4, 5)); + CHECK(getBitRange(netlist, "foo[2]") == ConstantRange(2, 3)); + CHECK(getBitRange(netlist, "foo[3]") == ConstantRange(0, 1)); + CHECK(getBitRange(netlist, "foo[0][1]") == ConstantRange(6, 6)); + CHECK(getBitRange(netlist, "foo[1][1]") == ConstantRange(4, 4)); + CHECK(getBitRange(netlist, "foo[2][1]") == ConstantRange(2, 2)); + CHECK(getBitRange(netlist, "foo[3][1]") == ConstantRange(0, 0)); // 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)); + CHECK(getBitRange(netlist, "foo[1][a]") == ConstantRange(4, 5)); + CHECK(getBitRange(netlist, "foo[1][a+:1]") == ConstantRange(4, 5)); + CHECK(getBitRange(netlist, "foo[1][a-:2]") == ConstantRange(4, 5)); } //===---------------------------------------------------------------------===// @@ -370,9 +373,9 @@ endmodule 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)); + CHECK(getBitRange(netlist, "foo.a") == ConstantRange(96, 96)); + CHECK(getBitRange(netlist, "foo.b") == ConstantRange(64, 95)); + CHECK(getBitRange(netlist, "foo.c") == ConstantRange(0, 63)); } TEST_CASE("Union member") { @@ -441,8 +444,8 @@ endmodule 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)); + CHECK(getBitRange(netlist, "foo.a[2:1]") == ConstantRange(5, 6)); + CHECK(getBitRange(netlist, "foo.b[2:1]") == ConstantRange(1, 2)); } TEST_CASE("Packed struct with packed union and enum members") { @@ -473,11 +476,11 @@ endmodule 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)); + CHECK(getBitRange(netlist, "foo.u") == ConstantRange(32, 35)); + CHECK(getBitRange(netlist, "foo.u[2:1]") == ConstantRange(33, 34)); + CHECK(getBitRange(netlist, "foo.u.a[2:1]") == ConstantRange(33, 34)); + CHECK(getBitRange(netlist, "foo.u.b[2:1]") == ConstantRange(33, 34)); + CHECK(getBitRange(netlist, "foo.c") == ConstantRange(0, 31)); } TEST_CASE("Packed arrays of structs etc") { @@ -524,19 +527,19 @@ endmodule 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[0][0].u.a") == ConstantRange(34, 37)); + CHECK(getBitRange(netlist, "foo[0][1].u.a") == ConstantRange(72, 75)); + CHECK(getBitRange(netlist, "foo[0][0].u.b") == ConstantRange(34, 37)); + CHECK(getBitRange(netlist, "foo[0][1].u.b") == ConstantRange(72, 75)); + CHECK(getBitRange(netlist, "foo[0][0].c") == ConstantRange(32, 33)); + CHECK(getBitRange(netlist, "foo[0][1].c") == ConstantRange(70, 71)); + CHECK(getBitRange(netlist, "foo[0][0].d") == ConstantRange(0, 31)); + CHECK(getBitRange(netlist, "foo[0][1].d") == ConstantRange(38, 69)); 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)); + CHECK(getBitRange(netlist, "foo[3][1].u.a") == ConstantRange(300, 303)); + CHECK(getBitRange(netlist, "foo[3][1].u.b") == ConstantRange(300, 303)); + CHECK(getBitRange(netlist, "foo[3][1].c") == ConstantRange(298, 299)); + CHECK(getBitRange(netlist, "foo[3][1].d") == ConstantRange(266, 297)); } TEST_CASE("Union with packed struct members") { @@ -567,12 +570,12 @@ endmodule 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)); + CHECK(getBitRange(netlist, "foo[0].x.a") == ConstantRange(4, 7)); + CHECK(getBitRange(netlist, "foo[0].x.b") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[0].y.a") == ConstantRange(4, 7)); + CHECK(getBitRange(netlist, "foo[0].y.b") == ConstantRange(0, 3)); + CHECK(getBitRange(netlist, "foo[3].x.a") == ConstantRange(28, 31)); + CHECK(getBitRange(netlist, "foo[3].x.b") == ConstantRange(24, 27)); + CHECK(getBitRange(netlist, "foo[3].y.a") == ConstantRange(28, 31)); + CHECK(getBitRange(netlist, "foo[3].y.b") == ConstantRange(24, 27)); }