From 75f760b8f2c6f65bec646e64db03c3a6cd3764c5 Mon Sep 17 00:00:00 2001 From: Udi Finkelstein Date: Fri, 17 May 2024 03:14:52 +0300 Subject: [PATCH] slang-netlist new feature: Detect combinatorial loops (#984) --- tools/netlist/CMakeLists.txt | 3 +- tools/netlist/include/CombLoops.h | 243 ++++++++++++++ tools/netlist/include/Netlist.h | 16 +- tools/netlist/include/NetlistPath.h | 1 + tools/netlist/include/NetlistVisitor.h | 61 +++- tools/netlist/netlist.cpp | 41 ++- tools/netlist/source/CombLoops.cpp | 428 +++++++++++++++++++++++++ tools/netlist/tests/CMakeLists.txt | 2 + tools/netlist/tests/CombLoopsTests.cpp | 167 ++++++++++ 9 files changed, 944 insertions(+), 18 deletions(-) create mode 100644 tools/netlist/include/CombLoops.h create mode 100644 tools/netlist/source/CombLoops.cpp create mode 100644 tools/netlist/tests/CombLoopsTests.cpp diff --git a/tools/netlist/CMakeLists.txt b/tools/netlist/CMakeLists.txt index 9add88f87..264341fbd 100644 --- a/tools/netlist/CMakeLists.txt +++ b/tools/netlist/CMakeLists.txt @@ -3,7 +3,8 @@ # SPDX-License-Identifier: MIT # ~~~ -add_executable(slang_netlist netlist.cpp source/Netlist.cpp) +add_executable(slang_netlist netlist.cpp source/Netlist.cpp + source/CombLoops.cpp) add_executable(slang::netlist ALIAS slang_netlist) target_link_libraries( diff --git a/tools/netlist/include/CombLoops.h b/tools/netlist/include/CombLoops.h new file mode 100644 index 000000000..9813ba45c --- /dev/null +++ b/tools/netlist/include/CombLoops.h @@ -0,0 +1,243 @@ +#ifndef COMBLOOPS_H +#define COMBLOOPS_H + +/* + * This code is derived from C++ code created by me (Udi finkelstein), + * which is a translation of the Java code at https://github.com/josch/cycles_johnson_meyer + * Due to this reason, I include the original author's license file. + */ + +/* + * (BSD-2 license) + * + * Copyright (c) 2012, Frank Meyer + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "Netlist.h" +#include +#include +#include + +using namespace netlist; +using ID_type = int; + +template +bool find_vec(const std::vector& vec, const T& val) { + return std::find(vec.cbegin(), vec.cend(), val) != vec.end(); +} + +template +bool find_vec_if(const std::vector& vec, Predicate predicate) { + return std::find_if(vec.cbegin(), vec.cend(), predicate) != vec.end(); +} +template +int count_vec_if(const std::vector& vec, Predicate predicate) { + return std::count_if(vec.cbegin(), vec.cend(), predicate); +} + +class SCCResult { +private: + std::vector> adjList; + ID_type lowestNodeId; + +public: + SCCResult(std::vector>& adjList, ID_type lowestNodeId) : + adjList(adjList), lowestNodeId(lowestNodeId) {} + SCCResult(std::vector>& adjList) : adjList(adjList), lowestNodeId(-1) {} + SCCResult() : lowestNodeId(-1) {} + inline std::vector>& getAdjListForWrite() { return adjList; } + inline const std::vector>& getAdjList() const { return adjList; } + inline const ID_type getLowestNodeId() const { return lowestNodeId; } + inline void setLowestNodeId(ID_type node_id) { lowestNodeId = node_id; } + inline bool isEmpty() const { return lowestNodeId == -1; } +}; + +/** + * This is a helpclass for the search of all elementary cycles in a graph + * with the algorithm of Johnson. For this it searches for strong connected + * components, using the algorithm of Tarjan. The constructor gets an + * adjacency-list of a graph. Based on this graph, it gets a nodenumber s, + * for which it calculates the subgraph, containing all nodes + * {s, s + 1, ..., n}, where n is the highest nodenumber in the original + * graph (e.g. it builds a subgraph with all nodes with higher or same + * nodenumbers like the given node s). It returns the strong connected + * component of this subgraph which contains the lowest nodenumber of all + * nodes in the subgraph.

+ * + * For a description of the algorithm for calculating the strong connected + * components see:
+ * Robert Tarjan: Depth-first search and linear graph algorithms. In: SIAM + * Journal on Computing. Volume 1, Nr. 2 (1972), pp. 146-160.
+ * For a description of the algorithm for searching all elementary cycles in + * a directed graph see:
+ * Donald B. Johnson: Finding All the Elementary Circuits of a Directed Graph. + * SIAM Journal on Computing. Volumne 4, Nr. 1 (1975), pp. 77-84.

+ * + * @author Frank Meyer, web_at_normalisiert_dot_de + * @version 1.1, 22.03.2009 + * + */ + +class StrongConnectedComponents { +private: + /** Adjacency-list of original graph */ + std::vector>& adjListOriginal; + + /** Adjacency-list of currently viewed subgraph */ + /* node IDs are the same as the original, but some of the nodes will be filtered, below a + * specific node ID*/ + std::vector> adjList; + + /** Helpattribute for finding scc's */ + std::vector visited; + + /** Helpattribute for finding scc's */ + std::vector stack; + + /** Helpattribute for finding scc's */ + std::vector lowlink; + + /** Helpattribute for finding scc's */ + std::vector number; + + /** Helpattribute for finding scc's */ + int sccCounter = 0; + + /** Helpattribute for finding scc's */ + std::vector> currentSCCs; + + // we only need one object at a time + SCCResult sccr_current; + +public: + static SCCResult sccr_dummy; + /** + * Constructor. + * + * @param adjList adjacency-list of the graph + */ + StrongConnectedComponents(std::vector>& adjList) : + adjListOriginal(adjList) {}; + + /** + * This method returns the adjacency-structure of the strong connected + * component with the least vertex in a subgraph of the original graph + * induced by the nodes {s, s + 1, ..., n}, where s is a given node. Note + * that trivial strong connected components with just one node will not + * be returned. + * + * @param node node s + * @return SCCResult with adjacency-structure of the strong + * connected component; null, if no such component exists + */ + SCCResult& getAdjacencyList(ID_type node); + +private: + void makeAdjListSubgraph(const ID_type node); + const std::vector* getLowestIdComponent() const; + void buildAdjList(const std::vector& nodes, SCCResult& sccr) const; + void getStrongConnectedComponents(ID_type root); +}; + +/** + * Searchs all elementary cycles in a given directed graph. The implementation + * is independent from the concrete objects that represent the graphnodes, it + * just needs an array of the objects representing the nodes the graph + * and an adjacency-matrix of type boolean, representing the edges of the + * graph. It then calculates based on the adjacency-matrix the elementary + * cycles and returns a list, which contains lists itself with the objects of the + * concrete graphnodes-implementation. Each of these lists represents an + * elementary cycle.

+ * + * The implementation uses the algorithm of Donald B. Johnson for the search of + * the elementary cycles. For a description of the algorithm see:
+ * Donald B. Johnson: Finding All the Elementary Circuits of a Directed Graph. + * SIAM Journal on Computing. Volumne 4, Nr. 1 (1975), pp. 77-84.

+ * + * The algorithm of Johnson is based on the search for strong connected + * components in a graph. For a description of this part see:
+ * Robert Tarjan: Depth-first search and linear graph algorithms. In: SIAM + * Journal on Computing. Volume 1, Nr. 2 (1972), pp. 146-160.
+ * + * @author Frank Meyer, web_at_normalisiert_dot_de + * @version 1.2, 22.03.2009 + * + */ +using CycleListType = std::vector; + +class ElementaryCyclesSearch { +private: + /** List of cycles */ + std::vector cycles; + + /** Adjacency-list of graph */ + std::vector> adjList; + + /** Blocked nodes, used by the algorithm of Johnson */ + std::vector blocked; + + /** B-Lists, used by the algorithm of Johnson */ + std::vector> B; + + /** Stack for nodes, used by the algorithm of Johnson */ + std::vector stack; + +public: + /** + * Constructor. + * + * @param matrix adjacency-matrix of the graph + * @param netlist pointer to the full netlist + */ + ElementaryCyclesSearch(Netlist& netlist); + /** + * Returns List::List::Object with the Lists of nodes of all elementary + * cycles in the graph. + * + * @return List::List::Object with the Lists of the elementary cycles. + */ + std::vector* getElementaryCycles(); + /** + * Dumps the cycles found + */ + static void getHierName(NetlistNode& node, std::string& buffer); + void dumpAdjList(Netlist& netlist); + +private: + /** + * Calculates the cycles containing a given node in a strongly connected + * component. The method calls itself recursivly. + * + * @param v + * @param s + * @param adjList adjacency-list with the subgraph of the strongly + * connected component s is part of. + * @return true, if cycle found; false otherwise + */ + bool findCycles(ID_type v, ID_type s, const std::vector>& adjList); + /** + * Unblocks recursivly all blocked nodes, starting with a given node. + * + * @param node node to unblock + */ + void unblock(ID_type node); +}; + +#endif // COMBLOOPS_H diff --git a/tools/netlist/include/Netlist.h b/tools/netlist/include/Netlist.h index cc8b5d50b..fff17838b 100644 --- a/tools/netlist/include/Netlist.h +++ b/tools/netlist/include/Netlist.h @@ -191,7 +191,9 @@ class NetlistEdge : public DirectedEdge { class NetlistNode : public Node { public: NetlistNode(NodeKind kind, const ast::Symbol& symbol) : - ID(++nextID), kind(kind), symbol(symbol) {}; + ID(++nextID), kind(kind), symbol(symbol) { + edgeKind = ast::EdgeKind::None; + }; ~NetlistNode() override = default; template @@ -223,6 +225,8 @@ class NetlistNode : public Node { size_t ID; NodeKind kind; const ast::Symbol& symbol; + ast::EdgeKind edgeKind; + bool blocked{}; private: static size_t nextID; @@ -372,6 +376,16 @@ class Netlist : public DirectedGraph { return node; } + // without this, the base class method will be hidden, + // even when calling netlist.addEdge with only 2 parameters + using DirectedGraph::addEdge; + + NetlistEdge& addEdge(NetlistNode& sourceNode, NetlistNode& targetNode, ast::EdgeKind edgeKind) { + auto& edge = DirectedGraph::addEdge(sourceNode, targetNode); + targetNode.edgeKind = edgeKind; + return edge; + } + /// 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) { diff --git a/tools/netlist/include/NetlistPath.h b/tools/netlist/include/NetlistPath.h index 5c076cea5..9e07b9051 100644 --- a/tools/netlist/include/NetlistPath.h +++ b/tools/netlist/include/NetlistPath.h @@ -38,6 +38,7 @@ class NetlistPath { size_t size() const { return nodes.size(); } bool empty() const { return nodes.empty(); } + void clear() { nodes.clear(); } static std::string getSymbolHierPath(const ast::Symbol& symbol) { std::string buffer; diff --git a/tools/netlist/include/NetlistVisitor.h b/tools/netlist/include/NetlistVisitor.h index d513d7f8d..82a13ea98 100644 --- a/tools/netlist/include/NetlistVisitor.h +++ b/tools/netlist/include/NetlistVisitor.h @@ -21,6 +21,7 @@ #include "slang/ast/Compilation.h" #include "slang/ast/EvalContext.h" #include "slang/ast/SemanticFacts.h" +#include "slang/ast/Statements.h" #include "slang/ast/Symbol.h" #include "slang/ast/symbols/BlockSymbols.h" #include "slang/ast/symbols/CompilationUnitSymbols.h" @@ -78,9 +79,10 @@ static void connectVarToDecl(Netlist& netlist, NetlistNode& varNode, } static void connectVarToVar(Netlist& netlist, NetlistNode& sourceVarNode, - NetlistNode& targetVarNode) { - netlist.addEdge(sourceVarNode, targetVarNode); - DEBUG_PRINT("Edge ref {} to ref {}\n", sourceVarNode.getName(), targetVarNode.getName()); + NetlistNode& targetVarNode, ast::EdgeKind edgeKind) { + auto& edge = netlist.addEdge(sourceVarNode, targetVarNode, edgeKind); + DEBUG_PRINT("Edge ref {} to ref {} by edge {}\n", sourceVarNode.getName(), + targetVarNode.getName(), toString(edgeKind)); } /// An AST visitor to identify variable references with selectors in @@ -199,8 +201,8 @@ class VariableReferenceVisitor : public ast::ASTVisitor { public: explicit AssignmentVisitor(Netlist& netlist, ast::EvalContext& evalCtx, - SmallVector& condVars) : - netlist(netlist), evalCtx(evalCtx), condVars(condVars) {} + SmallVector& condVars, ast::EdgeKind edgeKind) : + netlist(netlist), evalCtx(evalCtx), condVars(condVars), edgeKind(edgeKind) {} void handle(const ast::AssignmentExpression& expr) { // Collect variable references on the left-hand side of the assignment. @@ -216,7 +218,7 @@ class AssignmentVisitor : public ast::ASTVisitor // Add edge from variable declaration to RHS variable reference. connectDeclToVar(netlist, *rightNode, rightNode->symbol); // Add edge from RHS expression term to LHS expression terms. - connectVarToVar(netlist, *rightNode, *leftNode); + connectVarToVar(netlist, *rightNode, *leftNode, edgeKind); } } for (auto* condNode : condVars) { @@ -224,7 +226,7 @@ class AssignmentVisitor : public ast::ASTVisitor connectDeclToVar(netlist, *condNode, condNode->symbol); for (auto* leftNode : visitorLHS.getVars()) { // Add edge from conditional variable to the LHS variable. - connectVarToVar(netlist, *condNode, *leftNode); + connectVarToVar(netlist, *condNode, *leftNode, edgeKind); } } } @@ -233,6 +235,7 @@ class AssignmentVisitor : public ast::ASTVisitor Netlist& netlist; ast::EvalContext& evalCtx; SmallVector& condVars; + ast::EdgeKind edgeKind; }; /// An AST visitor for proceural blocks that performs loop unrolling. @@ -240,9 +243,10 @@ class ProceduralBlockVisitor : public ast::ASTVisitor condVarsStack; + ast::EdgeKind edgeKind; }; /// A visitor that traverses the AST and builds a netlist representation. @@ -597,23 +602,51 @@ class NetlistVisitor : public ast::ASTVisitor { /// Procedural block. void handle(const ast::ProceduralBlockSymbol& symbol) { - ProceduralBlockVisitor visitor(compilation, netlist); + ast::EdgeKind edgeKind = ast::EdgeKind::None; + if (symbol.procedureKind == ast::ProceduralBlockKind::AlwaysFF || + symbol.procedureKind == ast::ProceduralBlockKind::Always) { + auto tck = symbol.getBody().as().timing.kind; + if (tck == ast::TimingControlKind::SignalEvent) { + edgeKind = symbol.getBody() + .as() + .timing.as() + .edge; + } + else if (tck == ast::TimingControlKind::EventList) { + auto& events = symbol.getBody() + .as() + .timing.as() + .events; + // We need to decide if this has the potential for combinatorial loops + // The most strict test is if for any unique signal on the event list only one edge + // (pos or neg) appears e.g. "@(posedge x or negedge x)" is potentially + // combinatorial At the moment we'll settle for no signal having "None" edge. + for (auto e : events) { + edgeKind = e->as().edge; + if (edgeKind == ast::EdgeKind::None) + break; + } + // if we got here, edgeKind is not "None" which is all we care about + } + } + ProceduralBlockVisitor visitor(compilation, netlist, edgeKind); symbol.visit(visitor); } /// Generate block. void handle(const ast::GenerateBlockSymbol& symbol) { if (!symbol.isUninstantiated) { - ProceduralBlockVisitor visitor(compilation, netlist); + ProceduralBlockVisitor visitor(compilation, netlist, ast::EdgeKind::None); symbol.visit(visitor); } } /// Continuous assignment statement. void handle(const ast::ContinuousAssignSymbol& symbol) { + ast::EdgeKind edgeKind = ast::EdgeKind::None; ast::EvalContext evalCtx(ast::ASTContext(compilation.getRoot(), ast::LookupLocation::max)); SmallVector condVars; - AssignmentVisitor visitor(netlist, evalCtx, condVars); + AssignmentVisitor visitor(netlist, evalCtx, condVars, edgeKind); symbol.visit(visitor); } diff --git a/tools/netlist/netlist.cpp b/tools/netlist/netlist.cpp index f3181f2e2..4d5fcb6dc 100644 --- a/tools/netlist/netlist.cpp +++ b/tools/netlist/netlist.cpp @@ -7,6 +7,7 @@ //------------------------------------------------------------------------------ #include "Netlist.h" +#include "CombLoops.h" #include "NetlistVisitor.h" #include "PathFinder.h" #include "fmt/color.h" @@ -86,8 +87,14 @@ void printDOT(const Netlist& netlist, const std::string& fileName) { } case NodeKind::VariableReference: { auto& varRef = node->as(); - buffer.format(" N{} [label=\"{}\\n{}\"]\n", node->ID, varRef.toString(), - varRef.isLeftOperand() ? "[Assigned to]" : ""); + if (!varRef.isLeftOperand()) + buffer.format(" N{} [label=\"{}\\n\"]\n", node->ID, varRef.toString()); + else if (node->edgeKind == EdgeKind::None) + buffer.format(" N{} [label=\"{}\\n[Assigned to]\"]\n", node->ID, + varRef.toString()); + else + buffer.format(" N{} [label=\"{}\\n[Assigned to @({})]\"]\n", node->ID, + varRef.toString(), toString(node->edgeKind)); break; } default: @@ -135,6 +142,29 @@ void reportPath(Compilation& compilation, const NetlistPath& path) { } } +void dumpCyclesList(Compilation& compilation, Netlist& netlist, + std::vector* cycles) { + auto s = cycles->size(); + if (!s) { + OS::print("No combinatorial loops detected\n"); + return; + } + OS::print(fmt::format("Detected {} combinatorial loop{}:\n", s, (s > 1) ? "s" : "")); + NetlistPath path; + for (int i = 0; i < s; i++) { + auto si = (*cycles)[i].size(); + for (int j = 0; j < si; j++) { + auto& node = netlist.getNode((*cycles)[i][j]); + if (node.kind == NodeKind::VariableReference) { + path.add(node); + } + } + OS::print(fmt::format("Path length: {}\n", path.size())); + reportPath(compilation, path); + path.clear(); + } +} + int main(int argc, char** argv) { OS::setupConsole(); @@ -145,10 +175,12 @@ int main(int argc, char** argv) { std::optional showVersion; std::optional quiet; std::optional debug; + std::optional combLoops; driver.cmdLine.add("-h,--help", showHelp, "Display available options"); driver.cmdLine.add("--version", showVersion, "Display version information and exit"); driver.cmdLine.add("-q,--quiet", quiet, "Suppress non-essential output"); driver.cmdLine.add("-d,--debug", debug, "Output debugging information"); + driver.cmdLine.add("-c,--comb-loops", combLoops, "Detect combinatorial loops"); std::optional astJsonFile; driver.cmdLine.add( @@ -232,6 +264,11 @@ int main(int argc, char** argv) { return 0; } + if (combLoops == true) { + ElementaryCyclesSearch ecs(netlist); + std::vector* cycles = ecs.getElementaryCycles(); + dumpCyclesList(*compilation, netlist, cycles); + } // Find a point-to-point path in the netlist. if (fromPointName.has_value() && toPointName.has_value()) { if (!fromPointName.has_value()) { diff --git a/tools/netlist/source/CombLoops.cpp b/tools/netlist/source/CombLoops.cpp new file mode 100644 index 000000000..b196a65af --- /dev/null +++ b/tools/netlist/source/CombLoops.cpp @@ -0,0 +1,428 @@ +/* + * This code is derived from C++ code created by me (Udi finkelstein), + * which is a translation of the Java code at https://github.com/josch/cycles_johnson_meyer + * Due to this reason, I include the original author's license file. + */ + +/* + * (BSD-2 license) + * + * Copyright (c) 2012, Frank Meyer + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "CombLoops.h" + +#include "NetlistPath.h" + +#include "slang/ast/SemanticFacts.h" + +/** + * This is a helpclass for the search of all elementary cycles in a graph + * with the algorithm of Johnson. For this it searches for strong connected + * components, using the algorithm of Tarjan. The constructor gets an + * adjacency-list of a graph. Based on this graph, it gets a nodenumber s, + * for which it calculates the subgraph, containing all nodes + * {s, s + 1, ..., n}, where n is the highest nodenumber in the original + * graph (e.g. it builds a subgraph with all nodes with higher or same + * nodenumbers like the given node s). It returns the strong connected + * component of this subgraph which contains the lowest nodenumber of all + * nodes in the subgraph.

+ * + * For a description of the algorithm for calculating the strong connected + * components see:
+ * Robert Tarjan: Depth-first search and linear graph algorithms. In: SIAM + * Journal on Computing. Volume 1, Nr. 2 (1972), pp. 146-160.
+ * For a description of the algorithm for searching all elementary cycles in + * a directed graph see:
+ * Donald B. Johnson: Finding All the Elementary Circuits of a Directed Graph. + * SIAM Journal on Computing. Volumne 4, Nr. 1 (1975), pp. 77-84.

+ * + * @author Frank Meyer, web_at_normalisiert_dot_de + * @version 1.1, 22.03.2009 + * + */ + +SCCResult StrongConnectedComponents::sccr_dummy; + +/** + * This method returns the adjacency-structure of the strong connected + * component with the least vertex in a subgraph of the original graph + * induced by the nodes {s, s + 1, ..., n}, where s is a given node. Note + * that trivial strong connected components with just one node will not + * be returned. + * + * @param node node s + * @return SCCResult with adjacency-structure of the strong + * connected component; null, if no such component exists + */ +SCCResult& StrongConnectedComponents::getAdjacencyList(int node) { + auto adjListOriginal_s = adjListOriginal.size(); + lowlink.resize(adjListOriginal_s); + number.resize(adjListOriginal_s); + visited.resize(adjListOriginal_s); + + while (true) { + std::fill(visited.begin(), visited.end(), false); + stack.clear(); + currentSCCs.clear(); + + makeAdjListSubgraph(node); + for (int i = node; i < adjListOriginal_s; i++) { + if (!visited[i]) { + getStrongConnectedComponents(i); + const std::vector* nodes = getLowestIdComponent(); + if (nodes != nullptr && !nodes->empty() && !find_vec(*nodes, node) && + !find_vec(*nodes, node + 1)) { + node++; + continue; + } + else if (nodes != nullptr) { + buildAdjList(*nodes, sccr_current); + auto& adjacencyList = sccr_current.getAdjListForWrite(); + if (!adjacencyList.empty()) { + for (int j = 0; j < adjListOriginal_s; j++) { + if (!adjacencyList[j].empty()) { + sccr_current.setLowestNodeId(j); + return sccr_current; + } + } + } + } + } + } + return sccr_dummy; + } +} + +/** + * Builds the adjacency-list for a subgraph containing just nodes + * >= a given index. + * + * @param node Node with lowest index in the subgraph + */ +void StrongConnectedComponents::makeAdjListSubgraph(const int node) { + adjList.clear(); + const int adjListSize = adjListOriginal.size(); + adjList.resize(adjListSize); + + for (int i = node; i < adjListSize; i++) { + const int adjListOriginalISize = adjListOriginal[i].size(); + for (int j = 0; j < adjListOriginalISize; j++) { + const int currentNode = adjListOriginal[i][j]; + if (currentNode >= node) { + adjList[i].push_back(currentNode); + } + } + } +} + +/** + * Calculates the strong connected component out of a set of scc's, that + * contains the node with the lowest index. + * + * @return Vector::Integer of the scc containing the lowest nodenumber + */ +const std::vector* StrongConnectedComponents::getLowestIdComponent() const { + int min = adjList.size(); + const std::vector* currScc = nullptr; + + for (int i = 0; i < currentSCCs.size(); i++) { + for (int j = 0; j < currentSCCs[i].size(); j++) { + const int node = currentSCCs[i][j]; + if (node < min) { + currScc = ¤tSCCs[i]; + min = node; + } + } + } + + return currScc; +} + +/** + * Fills SCCResult with adjacency list representing the adjacency-structure + * of the strong connected component with least vertex in the currently + * viewed subgraph + */ +void StrongConnectedComponents::buildAdjList(const std::vector& nodes, SCCResult& sccr) const { + std::vector>& lowestIdAdjacencyList = sccr.getAdjListForWrite(); + auto nodes_s = nodes.size(); + lowestIdAdjacencyList.clear(); + lowestIdAdjacencyList.resize(adjList.size()); + if (!nodes.empty()) { + for (int i = 0; i < nodes_s; i++) { + int node = nodes[i]; + std::vector& sccr_adjlist_node = lowestIdAdjacencyList[node]; + auto& adjList_node = adjList[node]; + sccr_adjlist_node.clear(); + const int ns = adjList_node.size(); + for (int j = 0; j < ns; j++) { + int succ = adjList_node[j]; + if (find_vec(nodes, succ)) { + sccr_adjlist_node.push_back(succ); + } + } + } + } +} + +/** + * Searchs for strong connected components reachable from a given node. + * + * @param root node to start from. + */ +void StrongConnectedComponents::getStrongConnectedComponents(int root) { + sccCounter++; + lowlink[root] = sccCounter; + number[root] = sccCounter; + visited[root] = true; + stack.push_back(root); + + for (int i = 0; i < adjList[root].size(); i++) { + int w = adjList[root][i]; + if (!visited[w]) { + getStrongConnectedComponents(w); + lowlink[root] = std::min(lowlink[root], lowlink[w]); + } + else if (number[w] < number[root]) { + if (find_vec(stack, w)) { + lowlink[root] = std::min(lowlink[root], number[w]); + } + } + } + + if ((lowlink[root] == number[root]) && !stack.empty()) { + int next; + std::vector scc; + + do { + next = stack.back(); + stack.pop_back(); + scc.push_back(next); + } while (number[next] > number[root]); + + if (scc.size() > 1) { + currentSCCs.push_back(scc); + } + } +} + +/** + * Constructor. + * + * Go over the node list in the Netlist object, skipping any nodes + * driven on edge (pos or neg), and any edges terminating on such nodes. + * + * @param matrix adjacency-matrix of the graph + * @param netlist pointer to the full netlist + */ +ElementaryCyclesSearch::ElementaryCyclesSearch(Netlist& netlist) { + int nodes_num = netlist.numNodes(); + auto node_0 = netlist.getNode(0).ID; + adjList.resize(nodes_num); + auto net_nodes = nodes_num; + DEBUG_PRINT("Nodes: {}\n", nodes_num); + for (size_t i = 0; i < nodes_num; i++) { + auto& node = netlist.getNode(i); + if (node.edgeKind != slang::ast::EdgeKind::None) { + DEBUG_PRINT("skipped node {}\n", node.ID); + net_nodes--; + continue; + } + int node_edges_num = node.getEdges().size(); + adjList[i].clear(); + for (int j = 0; j < node_edges_num; j++) { + auto& edge = *(node.getEdges()[j]); + if (edge.disabled) + continue; + auto& tnode = edge.getTargetNode(); + if (tnode.edgeKind != slang::ast::EdgeKind::None) { + DEBUG_PRINT("skipped tnode {}\n", tnode.ID); + continue; + } + adjList[i].push_back(tnode.ID - node_0); + } + } + DEBUG_PRINT("Actual active Nodes: {}\n", net_nodes); +} + +/** + * Returns List::List::Object with the Lists of nodes of all elementary + * cycles in the graph. + * + * @return List::List::Object with the Lists of the elementary cycles. + */ +std::vector* ElementaryCyclesSearch::getElementaryCycles() { + cycles.clear(); + blocked.resize(adjList.size(), false); + B.resize(adjList.size()); + stack.clear(); + StrongConnectedComponents sccs(adjList); + ID_type s = 0; + + while (true) { + const SCCResult& sccResult = sccs.getAdjacencyList(s); + if (!sccResult.isEmpty() && !sccResult.getAdjList().empty()) { + const std::vector>& scc = sccResult.getAdjList(); + s = sccResult.getLowestNodeId(); + for (int j = 0; j < scc.size(); j++) { + if (!scc[j].empty()) { + blocked[j] = false; + B[j].clear(); + } + } + + findCycles(s, s, scc); + s++; + } + else { + break; + } + } + + return &(this->cycles); +} + +#ifdef DEBUG +/** + * These are useful when debugging but are not needed otherwise + */ +void ElementaryCyclesSearch::getHierName(NetlistNode& node, std::string& buffer) { + switch (node.kind) { + case NodeKind::PortDeclaration: { + auto& portDecl = node.as(); + buffer = "Port declaration: "; + buffer.append(portDecl.hierarchicalPath); + break; + } + case NodeKind::VariableDeclaration: { + auto& varDecl = node.as(); + buffer = "Variable declaration: "; + buffer.append(varDecl.hierarchicalPath); + break; + } + case NodeKind::VariableAlias: { + auto& varAlias = node.as(); + buffer = "Variable alias "; + buffer.append(varAlias.hierarchicalPath); + break; + } + case NodeKind::VariableReference: { + auto& varRef = node.as(); + buffer = varRef.toString(); + if (!varRef.isLeftOperand()) + break; + if (node.edgeKind == slang::ast::EdgeKind::None) + buffer.append(" (Assigned to)"); + else { + buffer.append(" (Assigned to @("); + buffer.append(toString(node.edgeKind)); + buffer.append(")"); + } + break; + } + default: + SLANG_UNREACHABLE; + } +} + +void ElementaryCyclesSearch::dumpAdjList(Netlist& netlist) { + std::string buffer_s; + std::string buffer_d; + for (int i = 0; i < adjList.size(); i++) { + auto l = adjList[i]; + auto ls = l.size(); + ElementaryCyclesSearch::getHierName(netlist.getNode(i), buffer_s); + for (int j = 0; j < ls; j++) { + ElementaryCyclesSearch::getHierName(netlist.getNode(l[j]), buffer_d); + std::cout << buffer_s << " => " << buffer_d << std::endl; + } + } +} +#endif + +/** + * Calculates the cycles containing a given node in a strongly connected + * component. The method calls itself recursivly. + * + * @param v + * @param s + * @param adjList adjacency-list with the subgraph of the strongly + * connected component s is part of. + * @return true, if cycle found; false otherwise + */ +bool ElementaryCyclesSearch::findCycles(ID_type v, ID_type s, + const std::vector>& adjList) { + bool f = false; + stack.push_back(v); + blocked[v] = true; + + for (int i = 0; i < adjList[v].size(); i++) { + ID_type w = adjList[v][i]; + + if (w == s) { + CycleListType cycle; + for (int j = 0; j < stack.size(); j++) { + ID_type index = stack[j]; + cycle.push_back(index); + } + cycles.push_back(cycle); + f = true; + } + else if (!this->blocked[w]) { + if (this->findCycles(w, s, adjList)) { + f = true; + } + } + } + + if (f) { + this->unblock(v); + } + else { + for (int i = 0; i < adjList[v].size(); i++) { + ID_type w = adjList[v][i]; + if (!find_vec(B[w], v)) { + B[w].push_back(v); + } + } + } + + stack.erase(remove(stack.begin(), stack.end(), v), stack.end()); + return f; +} + +/** + * Unblocks recursivly all blocked nodes, starting with a given node. + * + * @param node node to unblock + */ +void ElementaryCyclesSearch::unblock(ID_type node) { + this->blocked[node] = false; + std::vector Bnode = this->B[node]; + while (!Bnode.empty()) { + ID_type w = Bnode.front(); + Bnode.erase(Bnode.begin()); + if (this->blocked[w]) { + this->unblock(w); + } + } +} diff --git a/tools/netlist/tests/CMakeLists.txt b/tools/netlist/tests/CMakeLists.txt index 0afc4a9e3..30c6c4f1e 100644 --- a/tools/netlist/tests/CMakeLists.txt +++ b/tools/netlist/tests/CMakeLists.txt @@ -8,6 +8,8 @@ add_executable( ../../../tests/unittests/main.cpp ../../../tests/unittests/Test.cpp ../source/Netlist.cpp + ../source/CombLoops.cpp + CombLoopsTests.cpp DepthFirstSearchTests.cpp DirectedGraphTests.cpp NameTests.cpp diff --git a/tools/netlist/tests/CombLoopsTests.cpp b/tools/netlist/tests/CombLoopsTests.cpp new file mode 100644 index 000000000..c0c50600c --- /dev/null +++ b/tools/netlist/tests/CombLoopsTests.cpp @@ -0,0 +1,167 @@ +//------------------------------------------------------------------------------ +//! @file CombLoopsTests.cpp +//! @brief CombLooops unit tests. +// +// SPDX-FileCopyrightText: Michael Popoloski +// SPDX-License-Identifier: MIT +//------------------------------------------------------------------------------ + +#include "CombLoops.h" +#include "NetlistTest.h" + +//===---------------------------------------------------------------------===// +// Basic tests +//===---------------------------------------------------------------------===// + +TEST_CASE("A simple combinatorial loop") { + auto tree = SyntaxTree::fromText(R"( +module test; +wire a; +wire b; + +t2 t2( + .x(a), + .y(b) +); +assign a = b; +endmodule + +module t2(input x, output y); +assign y = x; +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + ElementaryCyclesSearch ecs(netlist); + std::vector* cycles_ptr = ecs.getElementaryCycles(); + std::vector& cycles = *cycles_ptr; + CHECK(cycles.size() == 1); + CHECK(cycles[0].size() == 10); + CHECK(count_vec_if(cycles[0], [&netlist](int node) { + return (netlist.getNode(node).kind == NodeKind::VariableReference); + }) == 6); +} + +TEST_CASE("A combinatorial loop with a single posedge DFF path") { + // Test that a DFF in a closed path prevents + // the loop from being counted as combinatorial + auto tree = SyntaxTree::fromText(R"( +module test (input clk); +wire a; +wire b; +wire c; +wire d; + +t2 t2( + .clk(clk), + .x(a), + .y(b), + .z(c) +); +assign a = b & c; +endmodule + +module t2(input clk, input x, output y, output reg z); +assign y = x; +always @(posedge clk) + z <= x; +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + ElementaryCyclesSearch ecs(netlist); + std::vector* cycles_ptr = ecs.getElementaryCycles(); + std::vector& cycles = *cycles_ptr; + CHECK(cycles.size() == 1); + CHECK(cycles[0].size() == 10); + CHECK(count_vec_if(cycles[0], [&netlist](int node) { + return (netlist.getNode(node).kind == NodeKind::VariableReference); + }) == 6); +} + +TEST_CASE("A combinatorial loop with multiple edges DFF path") { + // Test that a DFF in a closed path prevents + // the loop from being counted as combinatorial + auto tree = SyntaxTree::fromText(R"( +module test (input clk, input rst); +wire a; +wire b; +wire c; +wire d; + +t2 t2( + .clk(clk), + .rst(rst), + .x(a), + .y(b), + .z(c) +); +assign a = b & c; +endmodule + +module t2(input clk, input rst, input x, output y, output reg z); +assign y = x; +always @(posedge clk or negedge rst) + if (!rst) + z <= 1'b0; + else + z <= x; +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + ElementaryCyclesSearch ecs(netlist); + std::vector* cycles_ptr = ecs.getElementaryCycles(); + std::vector& cycles = *cycles_ptr; + CHECK(cycles.size() == 1); + CHECK(cycles[0].size() == 10); + CHECK(count_vec_if(cycles[0], [&netlist](int node) { + return (netlist.getNode(node).kind == NodeKind::VariableReference); + }) == 6); +} + +TEST_CASE("A combinatorial loop with a combinatorial event list") { + // Test that a csensitivity list with a nonedge signal in the + // sensitivity list is detected as a ccombinatorial loop + auto tree = SyntaxTree::fromText(R"( +module test (input clk, input rst); +wire a; +wire b; +wire c; +wire d; + +t2 t2( + .clk(clk), + .rst(rst), + .x(a), + .y(b), + .z(c) +); +assign a = b & c; +endmodule + +module t2(input clk, input rst, input x, output y, output reg z); +assign y = x; +always @(posedge clk or x) + z <= x; +endmodule +)"); + Compilation compilation; + compilation.addSyntaxTree(tree); + NO_COMPILATION_ERRORS; + auto netlist = createNetlist(compilation); + ElementaryCyclesSearch ecs(netlist); + std::vector* cycles_ptr = ecs.getElementaryCycles(); + std::vector& cycles = *cycles_ptr; + CHECK(cycles.size() == 2); + CHECK(cycles[0].size() == 10); + CHECK(count_vec_if(cycles[0], [&netlist](int node) { + return (netlist.getNode(node).kind == NodeKind::VariableReference); + }) == 6); +}