diff --git a/docs/gen/ex_DIDNOTCONVERGE_msg.rst b/docs/gen/ex_DIDNOTCONVERGE_msg.rst index 86af7245a..bd055184b 100644 --- a/docs/gen/ex_DIDNOTCONVERGE_msg.rst +++ b/docs/gen/ex_DIDNOTCONVERGE_msg.rst @@ -1,5 +1,5 @@ .. comment: generated by t_lint_didnotconverge_bad .. code-block:: - -V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] a) + -V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] b) %Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge. diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 945d944d3..58e0e7839 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -564,6 +564,10 @@ Summary: Any :code:`$VAR`, :code:`$(VAR)`, or :code:`${VAR}` will be replaced with the specified environment variable. +.. option:: -fdfg-synthesize-all + + Rarely needed. Attempt to synthesize all combinational logic in DFG. + .. option:: -FI Force include of the specified C++ header file. All generated C++ files diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bd7b97654..ccb25ffef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,7 @@ set(HEADERS V3CUse.h V3Case.h V3Cast.h + V3Cfg.h V3Class.h V3Clean.h V3Clock.h @@ -213,6 +214,9 @@ set(COMMON_SOURCES V3CUse.cpp V3Case.cpp V3Cast.cpp + V3Cfg.cpp + V3CfgBuilder.cpp + V3CfgLiveVariables.cpp V3Class.cpp V3Clean.cpp V3Clock.cpp @@ -238,6 +242,7 @@ set(COMMON_SOURCES V3DfgPasses.cpp V3DfgPeephole.cpp V3DfgRegularize.cpp + V3DfgSynthesize.cpp V3DiagSarif.cpp V3DupFinder.cpp V3EmitCBase.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index ed9767907..98d06b6f6 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -226,6 +226,9 @@ RAW_OBJS_PCH_ASTNOMT = \ V3CUse.o \ V3Case.o \ V3Cast.o \ + V3Cfg.o \ + V3CfgBuilder.o \ + V3CfgLiveVariables.o \ V3Class.o \ V3Clean.o \ V3Clock.o \ @@ -249,6 +252,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3DfgPasses.o \ V3DfgPeephole.o \ V3DfgRegularize.o \ + V3DfgSynthesize.o \ V3DiagSarif.o \ V3DupFinder.o \ V3EmitCMain.o \ diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 76e15c560..bf18a7049 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1873,6 +1873,7 @@ class AstVar final : public AstNode { bool m_ignorePostRead : 1; // Ignore reads in 'Post' blocks during ordering bool m_ignorePostWrite : 1; // Ignore writes in 'Post' blocks during ordering bool m_ignoreSchedWrite : 1; // Ignore writes in scheduling (for special optimizations) + bool m_dfgMultidriven : 1; // Singal is multidriven, used by DFG to avoid repeat processing void init() { m_ansi = false; @@ -1921,6 +1922,7 @@ class AstVar final : public AstNode { m_ignorePostRead = false; m_ignorePostWrite = false; m_ignoreSchedWrite = false; + m_dfgMultidriven = false; m_attrClocker = VVarAttrClocker::CLOCKER_UNKNOWN; } @@ -2084,6 +2086,8 @@ public: void setIgnorePostWrite() { m_ignorePostWrite = true; } bool ignoreSchedWrite() const { return m_ignoreSchedWrite; } void setIgnoreSchedWrite() { m_ignoreSchedWrite = true; } + bool dfgMultidriven() const { return m_dfgMultidriven; } + void setDfgMultidriven() { m_dfgMultidriven = true; } // METHODS void name(const string& name) override { m_name = name; } diff --git a/src/V3Cfg.cpp b/src/V3Cfg.cpp new file mode 100644 index 000000000..54ec09ae8 --- /dev/null +++ b/src/V3Cfg.cpp @@ -0,0 +1,90 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Control flow graph (CFG) implementation +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2025 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// Control flow graph (CFG) implementation +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Cfg.h" + +#include "V3Ast.h" +#include "V3EmitV.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +//###################################################################### +// ControlFlowGraph method definitions + +bool ControlFlowGraph::containsLoop() const { + for (const V3GraphVertex& vtx : vertices()) { + const BasicBlock& current = static_cast(vtx); + for (const V3GraphEdge& edge : current.outEdges()) { + const BasicBlock& successor = *static_cast(edge.top()); + // IDs are the reverse post-order numbering, so easy to check for a back-edge + if (successor.id() < current.id()) return true; + } + } + return false; +} + +//###################################################################### +// BasicBlock method definitions + +std::string BasicBlock::name() const { + std::stringstream ss; + ss << "BB " + std::to_string(id()) + ":\n"; + for (AstNode* nodep : m_stmtps) { + if (const AstIf* const ifp = VN_CAST(nodep, If)) { + ss << "if ("; + V3EmitV::debugVerilogForTree(ifp->condp(), ss); + ss << ") ..."; + } else if (const AstWhile* const whilep = VN_CAST(nodep, While)) { + ss << "while ("; + V3EmitV::debugVerilogForTree(whilep->condp(), ss); + ss << ") ..."; + } else { + V3EmitV::debugVerilogForTree(nodep, ss); + } + } + std::string text = VString::replaceSubstr(ss.str(), "\n", "\\l "); + if (inEmpty()) text = "**ENTER**\n" + text; + if (outEmpty()) text = text + "\n**EXIT**"; + return text; +} +std::string BasicBlock::dotShape() const { return "rect"; } +std::string BasicBlock::dotRank() const { + if (inEmpty()) return "source"; + if (outEmpty()) return "sink"; + return ""; +} + +//###################################################################### +// ControlFlowEdge method definitions + +std::string ControlFlowEdge::dotLabel() const { + std::string label = "E" + std::to_string(id()); + const BasicBlock& source = *fromp()->as(); + const ControlFlowEdge* const untknp = source.untknEdgep(); + if (this == untknp) { + label += " / F"; + } else if (untknp) { + label += " / T"; + } + return label; +} diff --git a/src/V3Cfg.h b/src/V3Cfg.h new file mode 100644 index 000000000..2eba9514e --- /dev/null +++ b/src/V3Cfg.h @@ -0,0 +1,270 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Control flow graph (CFG) +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2025 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// Control flow graph (CFG) and related data structures. +// +// A control flow graph is a directed graph with with basic blocks as +// vertices connected by control flow edges. A basic block is a sequence +// of statements that are always executed as a unit (there are no branches +// targeting the middle of a basic block). The last statement in a basic +// block is called the terminator statemet. The terminator statements are +// the control flow transfer statements (branches) in the program. In this +// implementation, an unconditoinal jump (goto) is implicit and not stored +// in the basic block. A consequence of the implicit jump is that a basic +// block might be empty (not contain any statements). Conditional branches +// are represented by storing the corresponding conditional AstNodeStmt as +// the last statement in the basic block. Most importantly, only the actual +// condition check (e.g.: the 'condp' part of an AstIf) and branch is +// executed in the host basic block, but not any of the body statements of +// the conditoinal. The control flow graph has 2 unique basic blocks. The +// 'enter' block is the unique entry point of the represented program. It +// has no predecessors and itself dominates all other basic blocks. The +// 'exit' block is the unique exit point. It has no successors, and itself +// post-dominates all other basic blocks. +// +// The current implementation is designed to be immutable after +// construction. This can be relaxed in the future if necessary, however +// it is unlikely to be necessary as we cannot as of now convert a control +// flow graph back to Ast or any other form of output. We can also only +// represent 2-way conditionals, therefore all basic blocks have up to 2 +// successors. The exit block has 0, blocks terminated by an unconditional +// implicit jump have exactly 1, and blocks terminated by a conditional +// branch have exactly 2 successors. +// +// Basic blocks have a unique ID, these are assigned based on the reverse +// post-ordering of the basic blocks within the control flow graph, and +// therefore they define a topological ordering of the basic blocks. The +// basic blocks within the graph are stored in this order. This means that +// in control flow graphs without back edges (loops), a basic block is +// always stored after its predecessors. +// +//************************************************************************* + +#ifndef VERILATOR_V3CFG_H_ +#define VERILATOR_V3CFG_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3Graph.h" + +#include +#include +#include +#include + +//###################################################################### +// DenseMap - This can be made generic if needed + +// Map from unique non-negative integers (IDs) to generic values. As opposed +// to a std::map/std::unordered_map, in DenseMap, all entries are value +// initialized at construction, and the map is always dense. This can be +// improved if necessary but is sufficient for our current purposes. +template +class DenseMap final { + std::vector m_map; // The map, stored as a vector + +public: + // CONSTRUCTOR + explicit DenseMap(size_t size) + : m_map{size} {} + + T_Value& operator[](const T_Key& key) { return m_map.at((key.*Index)()); } + const T_Value& operator[](const T_Key& key) const { return m_map.at((key.*Index)()); } +}; + +//###################################################################### +// ControlFlowGraph data structure + +class ControlFlowGraph; +class BasicBlock; +class ControlFlowEdge; + +// A basic block (verticies of the control flow graph) +class BasicBlock final : public V3GraphVertex { + VL_RTTI_IMPL(BasicBlock, V3GraphVertex) + friend class CfgBuilder; + + // STATE - Immutable after construction, set by CfgBuilder + // V3GraphEdge::user() is set to the unique id by CfgBuilder + std::vector m_stmtps; // statements in this BasicBlock + + // CONSTRUCTOR - via CfgBuilder only + inline explicit BasicBlock(ControlFlowGraph* graphp); + ~BasicBlock() override = default; + +public: + // METHODS - all const + + // Statements in this BasicBlock + const std::vector& stmtps() const { return m_stmtps; } + // Unique ID of this BasicBlock - defines topological ordering + size_t id() const { return V3GraphVertex::user(); } + + // The edge corresponding to the terminator branch being taken (including unonditoinal goto) + inline const ControlFlowEdge* takenEdgep() const; + // The edge corresponding to the terminator branch being not taken (or nullptr if goto) + inline const ControlFlowEdge* untknEdgep() const; + // Same as takenpEdgep/untknEdgep but returns the successor basic blocks + inline const BasicBlock* takenSuccessorp() const; + inline const BasicBlock* untknSuccessorp() const; + + void forEachSuccessor(std::function f) const { + for (const V3GraphEdge& edge : outEdges()) f(*edge.top()->as()); + } + + void forEachPredecessor(std::function f) const { + for (const V3GraphEdge& edge : inEdges()) f(*edge.fromp()->as()); + } + + FileLine* fileline() const override { + return !m_stmtps.empty() ? m_stmtps.front()->fileline() : nullptr; + } + + // For Graphviz dumps only + std::string name() const override; + std::string dotShape() const override; + std::string dotRank() const override; +}; + +// A control flow graph edge +class ControlFlowEdge final : public V3GraphEdge { + VL_RTTI_IMPL(ControlFlowEdge, V3GraphEdge) + friend class CfgBuilder; + + // STATE - Immutable after construction, set by CfgBuilder + // V3GraphEdge::user() is set to the unique id by CfgBuilder + + // CONSTRUCTOR - via CfgBuilder only + inline ControlFlowEdge(ControlFlowGraph* graphp, BasicBlock* srcp, BasicBlock* dstp); + ~ControlFlowEdge() override = default; + +public: + // METHODS - all const + + // Unique ID of this ControlFlowEdge - no particular meaning + size_t id() const { return user(); } + + // Source/destination BasicBlock + const BasicBlock& src() const { return *static_cast(fromp()); } + const BasicBlock& dst() const { return *static_cast(top()); } + + // For Graphviz dumps only + std::string dotLabel() const override; +}; + +template +using BasicBlockMap = DenseMap; + +template +using ControlFlowEdgeMap = DenseMap; + +// The control flow graph +class ControlFlowGraph final : public V3Graph { + friend class CfgBuilder; + + // STATE - Immutable after construction, set by CfgBuilder + BasicBlock* m_enterp = nullptr; // The singular entry vertex + BasicBlock* m_exitp = nullptr; // The singular exit vertex + size_t m_nBasicBlocks = 0; // Number of BasicBlocks in this ControlFlowGraph + size_t m_nEdges = 0; // Number of ControlFlowEdges in this ControlFlowGraph + + // CONSTRUCTOR - via CfgBuilder only + ControlFlowGraph() = default; + +public: + ~ControlFlowGraph() override = default; + + // METHODS + void foreach(std::function f) const { + for (const V3GraphVertex& vtx : vertices()) f(*vtx.as()); + } + + // Accessors + const BasicBlock& enter() const { return *m_enterp; } + const BasicBlock& exit() const { return *m_exitp; } + + // Number of basic blocks in this graph + size_t nBasicBlocks() const { return m_nBasicBlocks; } + // Number of control flow edges in this graph + size_t nEdges() const { return m_nEdges; } + + // Create a BasicBlock map for this graph + template + BasicBlockMap makeBasicBlockMap() const { + return BasicBlockMap{nBasicBlocks()}; + } + // Create a ControlFlowEdgeMap map for this graph + template + ControlFlowEdgeMap makeEdgeMap() const { + return ControlFlowEdgeMap{nEdges()}; + } + + // Returns true iff the graph contains a loop (back-edge) + bool containsLoop() const; +}; + +//###################################################################### +// Inline method definitions + +BasicBlock::BasicBlock(ControlFlowGraph* cfgp) + : V3GraphVertex{cfgp} {} + +const ControlFlowEdge* BasicBlock::takenEdgep() const { + // It's always the first edge + const V3GraphEdge* const frontp = outEdges().frontp(); + return static_cast(frontp); +} + +const ControlFlowEdge* BasicBlock::untknEdgep() const { + // It's always the second (last) edge + const V3GraphEdge* const frontp = outEdges().frontp(); + const V3GraphEdge* const backp = outEdges().backp(); + return backp != frontp ? static_cast(backp) : nullptr; +} + +const BasicBlock* BasicBlock::takenSuccessorp() const { + const ControlFlowEdge* const edgep = takenEdgep(); + return edgep ? static_cast(edgep->top()) : nullptr; +} + +const BasicBlock* BasicBlock::untknSuccessorp() const { + const ControlFlowEdge* const edgep = untknEdgep(); + return edgep ? static_cast(edgep->top()) : nullptr; +} + +ControlFlowEdge::ControlFlowEdge(ControlFlowGraph* graphp, BasicBlock* srcp, BasicBlock* dstp) + : V3GraphEdge{graphp, srcp, dstp, 1, false} {} + +//###################################################################### +// ControlFlowGraph functions + +namespace V3Cfg { +// Build control flow graph for given node +std::unique_ptr build(const AstNodeProcedure*); + +// Compute AstVars live on entry to given CFG. That is, variables that might +// be read before wholly assigned in the CFG. Returns nullptr if the analysis +// failed due to unhandled statements or data types involved in the CFG. +// On success, returns a vector of AstVar or AstVarScope nodes live on entry. +std::unique_ptr> liveVars(const ControlFlowGraph&); + +// Same as liveVars, but return AstVarScopes insted +std::unique_ptr> liveVarScopes(const ControlFlowGraph&); +} //namespace V3Cfg + +#endif // VERILATOR_V3CFG_H_ diff --git a/src/V3CfgBuilder.cpp b/src/V3CfgBuilder.cpp new file mode 100644 index 000000000..a4b315b99 --- /dev/null +++ b/src/V3CfgBuilder.cpp @@ -0,0 +1,291 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Control flow graph (CFG) builder +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2025 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// Control flow graph (CFG) builder +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3Cfg.h" +#include "V3EmitV.h" + +#include +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +class CfgBuilder final : VNVisitorConst { + // STATE + // The graph being built, or nullptr if failed to build one + std::unique_ptr m_cfgp{new ControlFlowGraph}; + // Current basic block to add statements to + BasicBlock* m_currBBp = nullptr; + // Continuation block for given JumpBlock + std::unordered_map m_jumpBlockContp; + + // METHODS + + // Create new Basicblock block in the CFG + BasicBlock& addBasicBlock() { return *new BasicBlock{m_cfgp.get()}; } + + // Create new taken (or unconditional) ControlFlowEdge in the CFG + void addTakenEdge(BasicBlock& src, BasicBlock& dst) { + UASSERT_OBJ(src.outEmpty(), &src, "Taken edge should be added first"); + new ControlFlowEdge{m_cfgp.get(), &src, &dst}; + } + + // Create new untaken ControlFlowEdge in the CFG + void addUntknEdge(BasicBlock& src, BasicBlock& dst) { + UASSERT_OBJ(src.outSize1(), &src, "Untaken edge shold be added second"); + UASSERT_OBJ(src.outEdges().frontp()->top() != &dst, &src, + "Untaken branch targets the same block as the taken branch"); + new ControlFlowEdge{m_cfgp.get(), &src, &dst}; + } + + // Add the given statement to the current BasicBlock + void addStmt(AstNodeStmt* nodep) { m_currBBp->m_stmtps.emplace_back(nodep); } + + // Used to handle statements not representable in the CFG + void nonRepresentable(AstNodeStmt*) { + if (!m_cfgp) return; + m_cfgp.reset(); + } + + // Used to handle simple (non-branching) statements in the CFG + void simpleStatement(AstNodeStmt* nodep, bool representable = true) { + if (!m_cfgp) return; + // If non-representable, reset graph + if (!representable) { + m_cfgp.reset(); + return; + } + // Just add to current block + addStmt(nodep); + } + + // VISITORS + + // Eventually we should handle all procedural statements, however, what + // is a procedural statemen is a bit unclear (#6280), so in the first + // instance we will only handle select statemetns that cover the requied + // use cases, and in the base case we conservatively assume the statement + // is non-representable. More visits can be added case by case if needed. + void visit(AstNode* nodep) override { + if (!m_cfgp) return; + UINFO(9, "Unhandled AstNode type " << nodep->typeName()); + m_cfgp.reset(); + } + + // Non-representable statements + void visit(AstAssignDly* nodep) override { nonRepresentable(nodep); } + void visit(AstCase* nodep) override { nonRepresentable(nodep); } // V3Case will eliminate + void visit(AstCReset* nodep) override { nonRepresentable(nodep); } + void visit(AstDelay* nodep) override { nonRepresentable(nodep); } + + // Representable non control-flow statements + void visit(AstAssign* nodep) override { simpleStatement(nodep, !nodep->timingControlp()); } + void visit(AstComment*) override {} // ignore entirely + void visit(AstDisplay* nodep) override { simpleStatement(nodep); } + void visit(AstFinish* nodep) override { simpleStatement(nodep); } + void visit(AstStmtExpr* nodep) override { simpleStatement(nodep); } + void visit(AstStop* nodep) override { simpleStatement(nodep); } + + // Representable control flow statements + void visit(AstIf* nodep) override { + if (!m_cfgp) return; + + // Add terminator statement to current block - semantically the condition check only ... + addStmt(nodep); + + // Create then/else/continuation blocks + BasicBlock& thenBB = addBasicBlock(); + BasicBlock& elseBB = addBasicBlock(); + BasicBlock& contBB = addBasicBlock(); + addTakenEdge(*m_currBBp, thenBB); + addUntknEdge(*m_currBBp, elseBB); + + // Build then branch + m_currBBp = &thenBB; + iterateAndNextConstNull(nodep->thensp()); + if (!m_cfgp) return; + if (m_currBBp) addTakenEdge(*m_currBBp, contBB); + + // Build else branch + m_currBBp = &elseBB; + iterateAndNextConstNull(nodep->elsesp()); + if (!m_cfgp) return; + if (m_currBBp) addTakenEdge(*m_currBBp, contBB); + + // Set continuation + m_currBBp = &contBB; + } + void visit(AstWhile* nodep) override { + if (!m_cfgp) return; + + // Create the header block + BasicBlock& headBB = addBasicBlock(); + addTakenEdge(*m_currBBp, headBB); + + // The While goes in the header block - semantically the condition check only ... + m_currBBp = &headBB; + addStmt(nodep); + + // Create the body/continuation blocks + BasicBlock& bodyBB = addBasicBlock(); + BasicBlock& contBB = addBasicBlock(); + addTakenEdge(headBB, bodyBB); + addUntknEdge(headBB, contBB); + + // Build the body + m_currBBp = &bodyBB; + iterateAndNextConstNull(nodep->stmtsp()); + iterateAndNextConstNull(nodep->incsp()); + if (!m_cfgp) return; + if (m_currBBp) addTakenEdge(*m_currBBp, headBB); + + // Set continuation + m_currBBp = &contBB; + } + void visit(AstJumpBlock* nodep) override { + if (!m_cfgp) return; + + // Don't acutally need to add this 'nodep' to any block - but we could later if needed + + // Create continuation block + BasicBlock& contBB = addBasicBlock(); + const bool newEntry = m_jumpBlockContp.emplace(nodep, &contBB).second; + UASSERT_OBJ(newEntry, nodep, "AstJumpBlock visited twice"); + + // Build the body + iterateAndNextConstNull(nodep->stmtsp()); + if (!m_cfgp) return; + if (m_currBBp) addTakenEdge(*m_currBBp, contBB); + + // Set continuation + m_currBBp = &contBB; + } + void visit(AstJumpGo* nodep) override { + if (!m_cfgp) return; + + // Non-representable if not last in statement list (V3Const will fix this later) + if (nodep->nextp()) { + m_cfgp.reset(); + return; + } + + // Don't acutally need to add this 'nodep' to any block - but we could later if needed + + // Make current block go to the continuation of the JumpBlock + addTakenEdge(*m_currBBp, *m_jumpBlockContp.at(nodep->blockp())); + + // There should be no statements after a JumpGo! + m_currBBp = nullptr; + } + + // CONSTRUCTOR + explicit CfgBuilder(const AstNodeProcedure* nodep) { + // Build the graph, starting from the entry block + m_currBBp = &addBasicBlock(); + m_cfgp->m_enterp = m_currBBp; + // Visit each statement to build the control flow graph + iterateAndNextConstNull(nodep->stmtsp()); + if (!m_cfgp) return; + // The final block is the exit block + m_cfgp->m_exitp = m_currBBp; + + // Remove empty blocks - except enter/exit + for (V3GraphVertex* const vtxp : m_cfgp->vertices().unlinkable()) { + if (vtxp == &m_cfgp->enter()) continue; + if (vtxp == &m_cfgp->exit()) continue; + BasicBlock* const bbp = vtxp->as(); + if (!bbp->stmtps().empty()) continue; + UASSERT(bbp->outSize1(), "Empty block should have a single successor"); + BasicBlock* const succp = const_cast(bbp->takenSuccessorp()); + for (V3GraphEdge* const edgep : bbp->inEdges().unlinkable()) edgep->relinkTop(succp); + vtxp->unlinkDelete(m_cfgp.get()); + } + // Remove redundant entry block + while (m_cfgp->enter().stmtps().empty() && m_cfgp->enter().outSize1()) { + BasicBlock* const succp = m_cfgp->enter().outEdges().frontp()->top()->as(); + if (!succp->inSize1()) break; + m_cfgp->m_enterp->unlinkDelete(m_cfgp.get()); + m_cfgp->m_enterp = succp; + } + // Remove redundant exit block + while (m_cfgp->exit().stmtps().empty() && m_cfgp->exit().inSize1()) { + BasicBlock* const prep = m_cfgp->exit().inEdges().frontp()->fromp()->as(); + if (!prep->outSize1()) break; + m_cfgp->m_exitp->unlinkDelete(m_cfgp.get()); + m_cfgp->m_exitp = prep; + } + + // Compute reverse post-order enumeration and sort blocks, assign IDs + { + // Simple recursive algorith will do ... + std::vector postOrderEnumeration; + std::unordered_set visited; + const std::function visitBasicBlock = [&](BasicBlock* bbp) { + // Mark and skip if already visited + if (!visited.emplace(bbp).second) return; + // Visit successors + for (const V3GraphEdge& e : bbp->outEdges()) { + visitBasicBlock(static_cast(e.top())); + } + // Add to post order enumeration + postOrderEnumeration.emplace_back(bbp); + }; + visitBasicBlock(m_cfgp->m_enterp); + const uint32_t n = postOrderEnumeration.size(); + UASSERT_OBJ(n == m_cfgp->vertices().size(), nodep, "Inconsistent enumeration size"); + + // Set size in graph + m_cfgp->m_nBasicBlocks = n; + + // Assign ids equal to the reverse post order number and sort vertices + for (uint32_t i = 0; i < postOrderEnumeration.size(); ++i) { + BasicBlock* const bbp = postOrderEnumeration[n - 1 - i]; + bbp->user(i); + m_cfgp->vertices().unlink(bbp); + m_cfgp->vertices().linkBack(bbp); + } + } + + // Assign IDs to edges + { + size_t nEdges = 0; + for (V3GraphVertex& v : m_cfgp->vertices()) { + for (V3GraphEdge& e : v.outEdges()) e.user(nEdges++); + } + // Set size in graph + m_cfgp->m_nEdges = nEdges; + } + + if (dumpGraphLevel() >= 9) m_cfgp->dumpDotFilePrefixed("cfgbuilder"); + } + +public: + static std::unique_ptr apply(const AstNodeProcedure* nodep) { + return std::move(CfgBuilder{nodep}.m_cfgp); + } +}; + +std::unique_ptr V3Cfg::build(const AstNodeProcedure* nodep) { + return CfgBuilder::apply(nodep); +} diff --git a/src/V3CfgLiveVariables.cpp b/src/V3CfgLiveVariables.cpp new file mode 100644 index 000000000..790ee7fee --- /dev/null +++ b/src/V3CfgLiveVariables.cpp @@ -0,0 +1,240 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: CFG liveness analysis +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2025 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// Classical data flow analysis computing live variables input to a CFG +// https://en.wikipedia.org/wiki/Live-variable_analysis +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3Cfg.h" + +#include +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +template +class CfgLiveVariables final : VNVisitorConst { + // TYPES + using Variable = std::conditional_t; + + // State associted with each basic block + struct BlockState final { + // Variables used in block, before a complete assignment in the same block + std::unordered_set m_gen; + // Variables that are assigned a complete value in the basic block + std::unordered_set m_kill; + std::unordered_set m_liveIn; // Variables live on entry to the block + std::unordered_set m_liveOut; // Variables live on exit from the block + bool m_isOnWorkList = false; // Block is on work list + bool m_wasProcessed = false; // Already processed at least once + }; + + // STATE + const ControlFlowGraph& m_cfg; // The CFG beign analysed + // State for each block + BasicBlockMap m_blockState = m_cfg.makeBasicBlockMap(); + BlockState* m_currp = nullptr; // State of current block being analysed + bool m_abort = false; // Abort analysis - unhandled construct + + // METHODS + static Variable* getTarget(const AstNodeVarRef* refp) { + // TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works + if VL_CONSTEXPR_CXX17 (T_Scoped) { + return reinterpret_cast(refp->varScopep()); + } else { + return reinterpret_cast(refp->varp()); + } + } + + // This is to match DFG, but can be extended independently - eventually should handle all + static bool isSupportedPackedDType(const AstNodeDType* dtypep) { + dtypep = dtypep->skipRefp(); + if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) { + return typep->keyword().isIntNumeric(); + } + if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) { + return isSupportedPackedDType(typep->subDTypep()); + } + if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) { + return typep->packed(); + } + return false; + } + + // Check and return if variable is incompatible + bool incompatible(Variable* varp) { + if (!isSupportedPackedDType(varp->dtypep())) return true; + AstVar* astVarp = nullptr; + // TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works + if VL_CONSTEXPR_CXX17 (T_Scoped) { + astVarp = reinterpret_cast(varp)->varp(); + } else { + astVarp = reinterpret_cast(varp); + } + if (astVarp->ignoreSchedWrite()) return true; + return false; + } + + void updateGen(AstNode* nodep) { + UASSERT_OBJ(!m_abort, nodep, "Doing useless work"); + m_abort = nodep->exists([&](const AstNodeVarRef* refp) { + // Cross reference is ambiguous + if (VN_IS(refp, VarXRef)) return true; + // Only care about reads + if (refp->access().isWriteOnly()) return false; + // Grab referenced variable + Variable* const tgtp = getTarget(refp); + // Bail if not a compatible type + if (incompatible(tgtp)) return true; + // Add to gen set, if not killed - assume whole variable read + if (m_currp->m_kill.count(tgtp)) return false; + m_currp->m_gen.emplace(tgtp); + return false; + }); + } + + void updateKill(AstNode* nodep) { + UASSERT_OBJ(!m_abort, nodep, "Doing useless work"); + m_abort = nodep->exists([&](const AstNodeVarRef* refp) { + // Cross reference is ambiguous + if (VN_IS(refp, VarXRef)) return true; + // Only care about writes + if (refp->access().isReadOnly()) return false; + // Grab referenced variable + Variable* const tgtp = getTarget(refp); + // Bail if not a compatible type + if (incompatible(tgtp)) return true; + // If whole written, add to kill set + if (refp->nextp()) return false; + if (VN_IS(refp->abovep(), Sel)) return false; + m_currp->m_kill.emplace(tgtp); + return false; + }); + } + + void single(AstNode* nodep) { + // Assume all reads hapen before any writes + if (m_abort) return; + updateGen(nodep); + if (m_abort) return; + updateKill(nodep); + } + + // Apply transfer function of block, return true if changed + bool transfer(const BasicBlock& bb) { + BlockState& state = m_blockState[bb]; + + // liveIn = gen union (liveOut - kill) + std::unordered_set liveIn = state.m_gen; + for (Variable* const varp : state.m_liveOut) { + if (state.m_kill.count(varp)) continue; + liveIn.insert(varp); + } + if (liveIn == state.m_liveIn) return false; + std::swap(liveIn, state.m_liveIn); + return true; + } + + // VISIT + void visit(AstNode* nodep) override { // + UASSERT_OBJ(!m_abort, nodep, "Repeat traversal after abort"); + m_abort = true; + UINFO(9, "Unhandled AstNode type " << nodep->typeName()); + } + + void visit(AstAssign* nodep) override { single(nodep); } + void visit(AstDisplay* nodep) override { single(nodep); } + void visit(AstFinish* nodep) override { single(nodep); } + void visit(AstStmtExpr* nodep) override { single(nodep); } + void visit(AstStop* nodep) override { single(nodep); } + + // Only the condition check belongs to the terminated basic block + void visit(AstIf* nodep) override { single(nodep->condp()); } + void visit(AstWhile* nodep) override { single(nodep->condp()); } + + // CONSTRUCTOR + explicit CfgLiveVariables(const ControlFlowGraph& cfg) + : m_cfg{cfg} { + // For each basic block, compute the gen and kill set via visit + cfg.foreach([&](const BasicBlock& bb) { + if (m_abort) return; + VL_RESTORER(m_currp); + m_currp = &m_blockState[bb]; + for (AstNode* const stmtp : bb.stmtps()) iterateConst(stmtp); + }); + if (m_abort) return; + + // Perform the flow analysis + std::deque workList; + const auto enqueue = [&](const BasicBlock& bb) { + BlockState& state = m_blockState[bb]; + if (state.m_isOnWorkList) return; + state.m_isOnWorkList = true; + workList.emplace_back(&bb); + }; + + enqueue(cfg.exit()); + + while (!workList.empty()) { + const BasicBlock* const currp = workList.front(); + workList.pop_front(); + BlockState& state = m_blockState[*currp]; + state.m_isOnWorkList = false; + + // Compute meet (liveOut = union liveIn of successors) + currp->forEachSuccessor([&](const BasicBlock& bb) { + auto& liveIn = m_blockState[bb].m_liveIn; + state.m_liveOut.insert(liveIn.begin(), liveIn.end()); + }); + + // Apply transfer function of block + const bool changed = transfer(*currp); + // Enqueue predecessors + if (changed || !state.m_wasProcessed) currp->forEachPredecessor(enqueue); + // Mark as done with first visit + state.m_wasProcessed = true; + } + } + +public: + static std::unique_ptr> apply(const ControlFlowGraph& cfg) { + CfgLiveVariables analysis{cfg}; + // If failed, return nullptr + if (analysis.m_abort) return nullptr; + // Gather variables live in to the entry blockstd::unique_ptr> + const std::unordered_set& lin = analysis.m_blockState[cfg.enter()].m_liveIn; + std::vector* const resultp = new std::vector{lin.begin(), lin.end()}; + // Sort for stability + std::stable_sort(resultp->begin(), resultp->end(), [](Variable* ap, Variable* bp) { // + return ap->name() < bp->name(); + }); + return std::unique_ptr>{resultp}; + } +}; + +std::unique_ptr> V3Cfg::liveVars(const ControlFlowGraph& cfg) { + return CfgLiveVariables::apply(cfg); +} + +std::unique_ptr> V3Cfg::liveVarScopes(const ControlFlowGraph& cfg) { + return CfgLiveVariables::apply(cfg); +} diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index ca744c99e..3311c3f81 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -18,6 +18,7 @@ #include "V3Dfg.h" +#include "V3EmitV.h" #include "V3File.h" VL_DEFINE_DEBUG_FUNCTIONS; @@ -77,6 +78,8 @@ std::unique_ptr DfgGraph::clone() const { break; } } + + if (AstNode* const tmpForp = vp->tmpForp()) cp->tmpForp(tmpForp); } // Clone operation vertices for (const DfgVertex& vtx : m_opVertices) { @@ -88,6 +91,11 @@ std::unique_ptr DfgGraph::clone() const { vtxp2clonep.emplace(&vtx, cp); break; } + case VDfgType::atUnitArray: { + DfgUnitArray* const cp = new DfgUnitArray{*clonep, vtx.fileline(), vtx.dtypep()}; + vtxp2clonep.emplace(&vtx, cp); + break; + } case VDfgType::atMux: { DfgMux* const cp = new DfgMux{*clonep, vtx.fileline(), vtx.dtypep()}; vtxp2clonep.emplace(&vtx, cp); @@ -103,6 +111,16 @@ std::unique_ptr DfgGraph::clone() const { vtxp2clonep.emplace(&vtx, cp); break; } + case VDfgType::atLogic: { + vtx.v3fatalSrc("DfgLogic cannot be cloned"); + VL_UNREACHABLE; + break; + } + case VDfgType::atUnresolved: { + vtx.v3fatalSrc("DfgUnresolved cannot be cloned"); + VL_UNREACHABLE; + break; + } default: { vtx.v3fatalSrc("Unhandled operation vertex type: " + vtx.typeName()); VL_UNREACHABLE; @@ -130,7 +148,7 @@ std::unique_ptr DfgGraph::clone() const { vp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) { if (DfgVertex* const srcp = edge.sourcep()) { cp->addDriver(vp->driverFileLine(i), // - vp->driverIndex(i), // + vp->driverLo(i), // vtxp2clonep.at(srcp)); } }); @@ -140,10 +158,14 @@ std::unique_ptr DfgGraph::clone() const { const DfgSplicePacked* const vp = vtx.as(); DfgSplicePacked* const cp = vtxp2clonep.at(vp)->as(); vp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) { - if (DfgVertex* const srcp = edge.sourcep()) { - cp->addDriver(vp->driverFileLine(i), // - vp->driverLsb(i), // - vtxp2clonep.at(srcp)); + if (DfgVertex* const srcVp = edge.sourcep()) { + DfgVertex* const srcCp = vtxp2clonep.at(srcVp); + UASSERT_OBJ(!srcCp->is(), srcCp, "Cannot clone DfgLogic"); + if (srcVp == vp->defaultp()) { + cp->defaultp(srcCp); + } else { + cp->addDriver(vp->driverFileLine(i), vp->driverLo(i), srcCp); + } } }); break; @@ -268,8 +290,11 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { AstNode* const nodep = varVtxp->nodep(); AstVar* const varp = varVtxp->varp(); os << toDotId(vtx); - os << " [label=\"" << nodep->name() << '\n'; + os << " [label=\"" << nodep->prettyName() << '\n'; os << cvtToHex(varVtxp) << '\n'; + if (AstNode* const tmpForp = varVtxp->tmpForp()) { + os << "temporary for: " << tmpForp->prettyName() << "\n"; + } varVtxp->dtypep()->dumpSmall(os); os << " / F" << varVtxp->fanout() << '"'; @@ -285,6 +310,8 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { os << ", shape=box, style=filled, fillcolor=darkorange1"; // Orange } else if (varVtxp->hasDfgRefs()) { os << ", shape=box, style=filled, fillcolor=gold2"; // Yellow + } else if (varVtxp->tmpForp()) { + os << ", shape=box, style=filled, fillcolor=gray80"; } else { os << ", shape=box"; } @@ -296,8 +323,11 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { AstNode* const nodep = arrVtxp->nodep(); AstVar* const varp = arrVtxp->varp(); os << toDotId(vtx); - os << " [label=\"" << nodep->name() << '\n'; + os << " [label=\"" << nodep->prettyName() << '\n'; os << cvtToHex(arrVtxp) << '\n'; + if (AstNode* const tmpForp = arrVtxp->tmpForp()) { + os << "temporary for: " << tmpForp->prettyName() << "\n"; + } arrVtxp->dtypep()->dumpSmall(os); os << " / F" << arrVtxp->fanout() << '"'; if (varp->direction() == VDirection::INPUT) { @@ -312,6 +342,8 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { os << ", shape=box3d, style=filled, fillcolor=darkorange1"; // Orange } else if (arrVtxp->hasDfgRefs()) { os << ", shape=box3d, style=filled, fillcolor=gold2"; // Yellow + } else if (arrVtxp->tmpForp()) { + os << ", shape=box3d, style=filled, fillcolor=gray80"; } else { os << ", shape=box3d"; } @@ -354,7 +386,7 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { return; } - if (vtx.is()) { + if (vtx.is() || vtx.is() || vtx.is()) { os << toDotId(vtx); os << " [label=\"" << vtx.typeName() << '\n'; os << cvtToHex(&vtx) << '\n'; @@ -369,6 +401,19 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { return; } + if (const DfgLogic* const logicp = vtx.cast()) { + os << toDotId(vtx); + std::stringstream ss; + V3EmitV::debugVerilogForTree(logicp->nodep(), ss); + os << " [label=\""; + os << VString::replaceSubstr(VString::replaceSubstr(ss.str(), "\n", "\\l"), "\"", "\\\""); + os << "\\n" << cvtToHex(&vtx); + os << "\"\n"; + os << ", shape=box, style=\"rounded,filled\", fillcolor=cornsilk, nojustify=true"; + os << "]\n"; + return; + } + os << toDotId(vtx); os << " [label=\"" << vtx.typeName() << '\n'; os << cvtToHex(&vtx) << '\n'; @@ -384,7 +429,7 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { // Dump one DfgEdge in Graphviz format static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, size_t idx) { - if (!edge.sourcep()) return; + UASSERT(edge.sourcep(), "Can't dump unconnected DfgEdge"); const DfgVertex& sink = *edge.sinkp(); // sink is never nullptr os << toDotId(*edge.sourcep()) << " -> " << toDotId(sink); if (sink.arity() > 1 || sink.is()) { @@ -399,30 +444,47 @@ void DfgGraph::dumpDot(std::ostream& os, const std::string& label, // Header os << "digraph dfg {\n"; - os << "graph [label=\"" << name(); - if (!label.empty()) os << "-" << label; - os << "\", labelloc=t, labeljust=l]\n"; - os << "graph [rankdir=LR]\n"; + os << "rankdir=LR\n"; - if (!p) { - // Emit all vertices and edges - forEachVertex([&](const DfgVertex& vtx) { - dumpDotVertex(os, vtx); - vtx.forEachSourceEdge([&](const DfgEdge& e, size_t i) { dumpDotEdge(os, e, i); }); + // If predicate not given, dump everything + if (!p) p = [](const DfgVertex&) { return true; }; + + std::unordered_set emitted; + // Emit all vertices associated with a DfgLogic + forEachVertex([&](const DfgVertex& vtx) { + const DfgLogic* const logicp = vtx.cast(); + if (!logicp) return; + if (logicp->synth().empty()) return; + if (!p(vtx)) return; + os << "subgraph cluster_" << cvtToHex(logicp) << " {\n"; + dumpDotVertex(os, *logicp); + emitted.insert(logicp); + for (DfgVertex* const vtxp : logicp->synth()) { + if (!p(*vtxp)) continue; + dumpDotVertex(os, *vtxp); + emitted.insert(vtxp); + } + os << "}\n"; + }); + // Emit all remaining vertices + forEachVertex([&](const DfgVertex& vtx) { + if (emitted.count(&vtx)) return; + if (!p(vtx)) return; + dumpDotVertex(os, vtx); + }); + // Emit all edges + forEachVertex([&](const DfgVertex& vtx) { // + if (!p(vtx)) return; + vtx.forEachSourceEdge([&](const DfgEdge& e, size_t i) { // + if (!e.sourcep() || !p(*e.sourcep())) return; + dumpDotEdge(os, e, i); }); - } else { - // Emit vertices that satify the predicate 'p' - forEachVertex([&](const DfgVertex& vtx) { - if (!p(vtx)) return; - dumpDotVertex(os, vtx); - vtx.forEachSourceEdge([&](const DfgEdge& e, size_t i) { - if (!e.sourcep() || !p(*e.sourcep())) return; - dumpDotEdge(os, e, i); - }); - }); - } + }); // Footer + os << "label=\"" << name() + (label.empty() ? "" : "-" + label) << "\"\n"; + os << "labelloc=t\n"; + os << "labeljust=l\n"; os << "}\n"; } @@ -448,51 +510,65 @@ void DfgGraph::dumpDotFilePrefixed(const std::string& label, dumpDotFile(v3Global.debugFilename(filename) + ".dot", label, p); } -// LCOV_EXCL_START // Debug functions for developer use only -void DfgGraph::dumpDotUpstreamCone(const std::string& fileName, const DfgVertex& vtx, - const std::string& name) const { - // Open output file - const std::unique_ptr os{V3File::new_ofstream(fileName)}; - if (os->fail()) v3fatal("Can't write file: " << fileName); - - // Header - *os << "digraph dfg {\n"; - if (!name.empty()) *os << "graph [label=\"" << name << "\", labelloc=t, labeljust=l]\n"; - *os << "graph [rankdir=LR]\n"; - - // Work queue for depth first traversal starting from this vertex - std::vector queue{&vtx}; - +template +static std::unique_ptr> +dfgGraphCollectCone(const std::vector vtxps) { + // Work queue for traversal starting from all the seed vertices + std::vector queue = vtxps; // Set of already visited vertices - std::unordered_set visited; - + std::unordered_set* const resp = new std::unordered_set{}; // Depth first traversal while (!queue.empty()) { // Pop next work item const DfgVertex* const vtxp = queue.back(); queue.pop_back(); - - // Mark vertex as visited - const bool isFirstEncounter = visited.insert(vtxp).second; - - // If we have already visited this vertex during the traversal, then move on. - if (!isFirstEncounter) continue; - - // Enqueue all sources of this vertex. - vtxp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); }); - - // Emit this vertex and all of its source edges - dumpDotVertex(*os, *vtxp); - vtxp->forEachSourceEdge([&](const DfgEdge& e, size_t i) { dumpDotEdge(*os, e, i); }); + // Mark vertex as visited, move on if already visited + if (!resp->insert(vtxp).second) continue; + // Enqueue all siblings of this vertex. + if VL_CONSTEXPR_CXX17 (T_SinksNotSources) { + vtxp->forEachSink([&](const DfgVertex& sink) { queue.push_back(&sink); }); + } else { + vtxp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); }); + } } - - // Footer - *os << "}\n"; - // Done - os->close(); + return std::unique_ptr>{resp}; +} + +std::unique_ptr> +DfgGraph::sourceCone(const std::vector vtxps) const { + return dfgGraphCollectCone(vtxps); +} + +std::unique_ptr> +DfgGraph::sinkCone(const std::vector vtxps) const { + return dfgGraphCollectCone(vtxps); +} + +// predicate for supported data types +static bool dfgGraphIsSupportedDTypePacked(const AstNodeDType* dtypep) { + dtypep = dtypep->skipRefp(); + if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) { + return typep->keyword().isIntNumeric(); + } + if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) { + return dfgGraphIsSupportedDTypePacked(typep->subDTypep()); + } + if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) { + return typep->packed(); + } + return false; +} + +bool DfgGraph::isSupported(const AstNodeDType* dtypep) { + dtypep = dtypep->skipRefp(); + // Support 1 dimensional unpacked arrays of packed types + if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) { + return dfgGraphIsSupportedDTypePacked(typep->subDTypep()); + } + // Support packed types + return dfgGraphIsSupportedDTypePacked(dtypep); } -// LCOV_EXCL_STOP //------------------------------------------------------------------------------ // DfgEdge @@ -658,6 +734,11 @@ DfgVertexVar* DfgVertex::getResultVar() { if (!resp->hasModRdRefs()) resp = varp; return; } + // Prefer real variabels over temporaries + if (!resp->tmpForp() != !varp->tmpForp()) { + if (resp->tmpForp()) resp = varp; + return; + } // Prefer the earlier one in source order const FileLine& oldFlp = *(resp->fileline()); const FileLine& newFlp = *(varp->fileline()); @@ -742,39 +823,26 @@ bool DfgSel::selfEquals(const DfgVertex& that) const { return lsb() == that.as(); +bool DfgVertexSplice::selfEquals(const DfgVertex& that) const { + const DfgVertexSplice* const thatp = that.as(); + if (!defaultp() != !thatp->defaultp()) return false; const size_t arity = this->arity(); for (size_t i = 0; i < arity; ++i) { - if (driverIndex(i) != thatp->driverIndex(i)) return false; + if (i == 0 && defaultp()) continue; + if (driverLo(i) != thatp->driverLo(i)) return false; } return true; } -V3Hash DfgSpliceArray::selfHash() const { +V3Hash DfgVertexSplice::selfHash() const { V3Hash hash; const size_t arity = this->arity(); - for (size_t i = 0; i < arity; ++i) hash += driverIndex(i); - return hash; -} - -// DfgSplicePacked ---------- - -bool DfgSplicePacked::selfEquals(const DfgVertex& that) const { - const DfgSplicePacked* const thatp = that.as(); - const size_t arity = this->arity(); for (size_t i = 0; i < arity; ++i) { - if (driverLsb(i) != thatp->driverLsb(i)) return false; + if (i == 0 && defaultp()) continue; + hash += driverLo(i); } - return true; -} - -V3Hash DfgSplicePacked::selfHash() const { - V3Hash hash; - const size_t arity = this->arity(); - for (size_t i = 0; i < arity; ++i) hash += driverLsb(i); return hash; } diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 2ee391d9f..2936dffa8 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -34,6 +34,7 @@ #include "verilatedos.h" #include "V3Ast.h" +#include "V3Cfg.h" #include "V3Error.h" #include "V3Global.h" #include "V3Hash.h" @@ -140,7 +141,7 @@ protected: DfgGraph* m_graphp; // The containing DfgGraph const VDfgType m_type; // Vertex type tag uint32_t m_userCnt = 0; // User data generation number - UserDataStorage m_userDataStorage; // User data storage + UserDataStorage m_userDataStorage = nullptr; // User data storage // CONSTRUCTOR DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep) VL_MT_DISABLED; @@ -168,59 +169,15 @@ private: virtual V3Hash selfHash() const VL_MT_DISABLED; public: - // Supported packed types - static bool isSupportedPackedDType(const AstNodeDType* dtypep) { - dtypep = dtypep->skipRefp(); - if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) { - return typep->keyword().isIntNumeric(); - } - if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) { - return isSupportedPackedDType(typep->subDTypep()); - } - if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) { - return typep->packed(); - } - return false; - } + // The data type of the result of the vertex + AstNodeDType* dtypep() const { return m_dtypep; } - // Returns true if an AstNode with the given 'dtype' can be represented as a DfgVertex - static bool isSupportedDType(const AstNodeDType* dtypep) { - dtypep = dtypep->skipRefp(); - // Support unpacked arrays of packed types - if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) { - return isSupportedPackedDType(typep->subDTypep()); - } - // Support packed types - return isSupportedPackedDType(dtypep); - } - - // Return data type used to represent any packed value of the given 'width'. All packed types - // of a given width use the same canonical data type, as the only interesting information is - // the total width. - static AstNodeDType* dtypeForWidth(uint32_t width) { - return v3Global.rootp()->typeTablep()->findLogicDType(width, width, VSigning::UNSIGNED); - } - - // Return data type used to represent the type of 'nodep' when converted to a DfgVertex - static AstNodeDType* dtypeFor(const AstNode* nodep) { - const AstNodeDType* const dtypep = nodep->dtypep()->skipRefp(); - UDEBUGONLY(UASSERT_OBJ(isSupportedDType(dtypep), nodep, "Unsupported dtype");); - // For simplicity, all packed types are represented with a fixed type - if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) { - AstNodeDType* const adtypep = new AstUnpackArrayDType{ - typep->fileline(), dtypeForWidth(typep->subDTypep()->width()), - typep->rangep()->cloneTree(false)}; - v3Global.rootp()->typeTablep()->addTypesp(adtypep); - return adtypep; - } - return dtypeForWidth(dtypep->width()); - } + // Is it a packed type (instead of an array) + bool isPacked() const { return VN_IS(dtypep(), BasicDType); } // Source location FileLine* fileline() const { return m_filelinep; } - // The data type of the result of the nodes - AstNodeDType* dtypep() const { return m_dtypep; } - void dtypep(AstNodeDType* nodep) { m_dtypep = nodep; } + // The type of this vertex VDfgType type() const { return m_type; } @@ -244,10 +201,16 @@ public: // Width of result uint32_t width() const { - UASSERT_OBJ(VN_IS(dtypep(), BasicDType), this, "non-packed has no 'width()'"); + UASSERT_OBJ(isPacked(), this, "non-packed has no 'width()'"); return dtypep()->width(); } + // Number of sub-elements in result vertex + uint32_t size() const { + if (isPacked()) return dtypep()->width(); + return VN_AS(dtypep(), UnpackArrayDType)->elementsConst(); + } + // Cache type for 'equals' below using EqualsCache = std::unordered_map, uint8_t>; @@ -307,6 +270,9 @@ public: return m_sinksp && !m_sinksp->m_nextp ? m_sinksp->m_sinkp : nullptr; } + // First sink of the vertex, if any, otherwise nullptr + DfgVertex* firtsSinkp() const { return m_sinksp ? m_sinksp->m_sinkp : nullptr; } + // Unlink from container (graph or builder), then delete this vertex void unlinkDelete(DfgGraph& dfg) VL_MT_DISABLED; @@ -598,12 +564,20 @@ protected: m_srcCnt = 0; } -public: + void clearSources() { + for (uint32_t i = 0; i < m_srcCnt; ++i) { + UASSERT_OBJ(m_srcsp[i].sourcep(), this, "Unconnected source"); + m_srcsp[i].unlinkSource(); + } + m_srcCnt = 0; + } + ASTGEN_MEMBERS_DfgVertexVariadic; DfgEdge* sourceEdge(size_t idx) const { return &m_srcsp[idx]; } DfgVertex* source(size_t idx) const { return m_srcsp[idx].sourcep(); } +public: std::pair sourceEdges() override { return {m_srcsp, m_srcCnt}; } std::pair sourceEdges() const override { return {m_srcsp, m_srcCnt}; } }; @@ -744,6 +718,9 @@ public: std::vector> extractCyclicComponents(std::string label) VL_MT_DISABLED; + //----------------------------------------------------------------------- + // Debug dumping + // Dump graph in Graphviz format into the given stream 'os'. 'label' is added to the name of // the graph which is included in the output. // If the predicate function 'p' is provided, only those vertices are dumped that satifty it. @@ -763,10 +740,70 @@ public: void dumpDotFilePrefixed(const std::string& label, std::function p = {}) const VL_MT_DISABLED; - // Dump upstream (source) logic cone starting from given vertex into a file with the given - // 'filename'. 'name' is the name of the graph, which is included in the output. - void dumpDotUpstreamCone(const std::string& filename, const DfgVertex& vtx, - const std::string& name = "") const VL_MT_DISABLED; + // Returns the set of vertices in the upstream cones of the given vertices + std::unique_ptr> + sourceCone(const std::vector) const VL_MT_DISABLED; + // Returns the set of vertices in the downstream cones of the given vertices + std::unique_ptr> + sinkCone(const std::vector) const VL_MT_DISABLED; + + //----------------------------------------------------------------------- + // Static methods for data types + + // Some data types are interned, in order to facilitate type comparison + // via pointer compariosn. These are functoins to construct the canonical + // DFG data types + + // Returns data type used to represent any packed value of the given 'width'. + static AstNodeDType* dtypePacked(uint32_t width) { + return v3Global.rootp()->typeTablep()->findLogicDType(width, width, VSigning::UNSIGNED); + } + + // Returns data type used to represent any array with the given type and number of elements. + static AstNodeDType* dtypeArray(AstNodeDType* subDtypep, uint32_t size) { + UASSERT_OBJ(isSupported(subDtypep), subDtypep, "Unsupported element type"); + FileLine* const flp = subDtypep->fileline(); + AstRange* const rangep = new AstRange{flp, static_cast(size - 1), 0}; + AstNodeDType* const dtypep = new AstUnpackArrayDType{flp, subDtypep, rangep}; + v3Global.rootp()->typeTablep()->addTypesp(dtypep); + return dtypep; + } + + // Return data type used to represent the type of 'nodep' when converted to a DfgVertex + static AstNodeDType* toDfgDType(const AstNodeDType* dtypep) { + dtypep = dtypep->skipRefp(); + UASSERT_OBJ(isSupported(dtypep), dtypep, "Unsupported dtype"); + // For simplicity, all packed types are represented with a fixed type + if (const AstUnpackArrayDType* const uatp = VN_CAST(dtypep, UnpackArrayDType)) { + return dtypeArray(toDfgDType(uatp->subDTypep()), uatp->elementsConst()); + } + return dtypePacked(dtypep->width()); + } + + //----------------------------------------------------------------------- + // Static methods for compatibility tests + + // Returns true if the given data type can be represented in the graph + static bool isSupported(const AstNodeDType* dtypep) VL_MT_DISABLED; + + // Returns true if variable can be represented in the graph + static bool isSupported(const AstVar* varp) { + if (varp->isIfaceRef()) return false; // Cannot handle interface references + if (varp->delayp()) return false; // Cannot handle delayed variables + if (varp->isSc()) return false; // SystemC variables are special and rare, we can ignore + if (varp->dfgMultidriven()) return false; // Discovered as multidriven on earlier DFG run + return isSupported(varp->dtypep()); + } + + // Returns true if variable can be represented in the graph + static bool isSupported(const AstVarScope* vscp) { + // If the variable is not in a regular module, then we do not support it. + // This is especially needed for variabels in interfaces which might be + // referenced via virtual intefaces, which cannot be resovled statically. + if (!VN_IS(vscp->scopep()->modp(), Module)) return false; + // Check the AstVar + return isSupported(vscp->varp()); + } }; // Specializations of privateTypeTest @@ -921,12 +958,41 @@ bool DfgVertex::isOnes() const { return false; } +//------------------------------------------------------------------------------ +// Inline method definitions - for DfgConst +//------------------------------------------------------------------------------ + +DfgConst::DfgConst(DfgGraph& dfg, FileLine* flp, const V3Number& num) + : DfgVertex{dfg, dfgType(), flp, DfgGraph::dtypePacked(num.width())} + , m_num{num} {} +DfgConst::DfgConst(DfgGraph& dfg, FileLine* flp, uint32_t width, uint32_t value) + : DfgVertex{dfg, dfgType(), flp, DfgGraph::dtypePacked(width)} + , m_num{flp, static_cast(width), value} {} + +//------------------------------------------------------------------------------ +// Inline method definitions - for DfgVertexSplice +//------------------------------------------------------------------------------ + +DfgVertex* DfgVertexSplice::wholep() const { + if (defaultp()) return nullptr; + if (arity() != 1) return nullptr; + if (driverLo(0) != 0) return nullptr; + DfgVertex* const srcp = DfgVertexVariadic::source(1); + if (srcp->size() != size()) return nullptr; + if (DfgUnitArray* const uap = srcp->cast()) { + if (DfgVertexSplice* sp = uap->srcp()->cast()) { + if (!sp->wholep()) return nullptr; + } + } + return srcp; +} + //------------------------------------------------------------------------------ // Inline method definitions - for DfgVertexVar //------------------------------------------------------------------------------ DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp) - : DfgVertexUnary{dfg, type, varp->fileline(), dtypeFor(varp)} + : DfgVertexUnary{dfg, type, varp->fileline(), DfgGraph::toDfgDType(varp->dtypep())} , m_varp{varp} , m_varScopep{nullptr} { UASSERT_OBJ(dfg.modulep(), varp, "Un-scoped DfgVertexVar created in scoped DfgGraph"); @@ -936,7 +1002,7 @@ DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp) UASSERT_OBJ((varp->user1() >> 4) > 0, varp, "Reference count overflow"); } DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVarScope* vscp) - : DfgVertexUnary{dfg, type, vscp->fileline(), dtypeFor(vscp)} + : DfgVertexUnary{dfg, type, vscp->fileline(), DfgGraph::toDfgDType(vscp->varp()->dtypep())} , m_varp{vscp->varp()} , m_varScopep{vscp} { UASSERT_OBJ(!dfg.modulep(), vscp, "Scoped DfgVertexVar created in un-scoped DfgGraph"); diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index 147bd2b1a..ec18a8212 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -14,20 +14,16 @@ // //************************************************************************* // -// Convert and AstModule to a DfgGraph. We proceed by visiting convertible logic blocks (e.g.: -// AstAssignW of appropriate type and with no delays), recursively constructing DfgVertex instances -// for the expressions that compose the subject logic block. If all expressions in the current -// logic block can be converted, then we delete the logic block (now represented in the DfgGraph), -// and connect the corresponding DfgVertex instances appropriately. If some of the expressions were -// not convertible in the current logic block, we revert (delete) the DfgVertex instances created -// for the logic block, and leave the logic block in the AstModule. Any variable reference from -// non-converted logic blocks (or other constructs under the AstModule) are marked as being -// referenced in the AstModule, which is relevant for later optimization. +// Convert and AstModule (before V3Scope), or the entire AstNetlist +// (after V3Scope) to an initial DfgGraph composed onlyof DfgLogic, +// DfgUnresolved and DfgVertexVar vertices. This will later be synthesized +// into primitive operations by V3DfgPasses::synthesize. // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT +#include "V3Cfg.h" #include "V3Const.h" #include "V3Dfg.h" #include "V3DfgPasses.h" @@ -36,42 +32,20 @@ VL_DEFINE_DEBUG_FUNCTIONS; -namespace { - -// Create a DfgVertex out of a AstNodeExpr. For most AstNodeExpr subtypes, this can be done -// automatically. For the few special cases, we provide specializations below -template -T_Vertex* makeVertex(const T_Node* nodep, DfgGraph& dfg) { - return new T_Vertex{dfg, nodep->fileline(), DfgVertex::dtypeFor(nodep)}; -} - -template <> -DfgArraySel* makeVertex(const AstArraySel* nodep, DfgGraph& dfg) { - // Some earlier passes create malformed ArraySels, just bail on those... - // See t_bitsel_wire_array_bad - if (VN_IS(nodep->fromp(), Const)) return nullptr; - if (!VN_IS(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) return nullptr; - return new DfgArraySel{dfg, nodep->fileline(), DfgVertex::dtypeFor(nodep)}; -} - -} //namespace - -// Visitor that can convert combinational Ast logic constructs/assignments to Dfg template -class AstToDfgConverter final : public VNVisitor { +class AstToDfgVisitor final : public VNVisitor { // NODE STATE - // AstNodeExpr/AstVar/AstVarScope::user2p -> DfgVertex* for this Node + // AstVar/AstVarScope::user2() -> DfgVertexVar* : the corresponding variable vertex + // AstVar/AstVarScope::user3() -> bool : Already gathered - used fine grained below + const VNUser2InUse m_user2InUse; // TYPES + using RootType = std::conditional_t; using Variable = std::conditional_t; // STATE - DfgGraph& m_dfg; // The graph being built V3DfgAstToDfgContext& m_ctx; // The context for stats - bool m_foundUnhandled = false; // Found node not implemented as DFG or not implemented 'visit' - bool m_converting = false; // We are trying to convert some logic at the moment - std::vector m_uncommittedSpliceps; // New splices made during convertLValue // METHODS static Variable* getTarget(const AstVarRef* refp) { @@ -83,9 +57,33 @@ class AstToDfgConverter final : public VNVisitor { } } - DfgVertexVar* getNet(Variable* varp) { + std::unique_ptr> getLiveVariables(const ControlFlowGraph& cfg) { + // TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works + if VL_CONSTEXPR_CXX17 (T_Scoped) { + std::unique_ptr> result = V3Cfg::liveVarScopes(cfg); + const auto resultp = reinterpret_cast*>(result.release()); + return std::unique_ptr>{resultp}; + } else { + std::unique_ptr> result = V3Cfg::liveVars(cfg); + const auto resultp = reinterpret_cast*>(result.release()); + return std::unique_ptr>{resultp}; + } + } + + // Mark variables referenced under node + static void markReferenced(const AstNode* nodep) { + nodep->foreach([](const AstVarRef* refp) { + Variable* const tgtp = getTarget(refp); + // Mark as read from non-DFG logic + if (refp->access().isReadOrRW()) DfgVertexVar::setHasModRdRefs(tgtp); + // Mark as written from non-DFG logic + if (refp->access().isWriteOrRW()) DfgVertexVar::setHasModWrRefs(tgtp); + }); + } + + DfgVertexVar* getVarVertex(Variable* varp) { if (!varp->user2p()) { - AstNodeDType* const dtypep = varp->dtypep()->skipRefp(); + const AstNodeDType* const dtypep = varp->dtypep()->skipRefp(); DfgVertexVar* const vtxp = VN_IS(dtypep, UnpackArrayDType) ? static_cast(new DfgVarArray{m_dfg, varp}) @@ -95,861 +93,137 @@ class AstToDfgConverter final : public VNVisitor { return varp->user2u().template to(); } - // Returns true if the expression cannot (or should not) be represented by DFG - bool unhandled(AstNodeExpr* nodep) { - // Short-circuiting if something was already unhandled - if (!m_foundUnhandled) { - // Impure nodes cannot be represented - if (!nodep->isPure()) { - m_foundUnhandled = true; - ++m_ctx.m_nonRepImpure; - } - // Check node has supported dtype - if (!DfgVertex::isSupportedDType(nodep->dtypep())) { - m_foundUnhandled = true; - ++m_ctx.m_nonRepDType; - } - } - return m_foundUnhandled; - } - - bool isSupported(const AstVar* varp) { - if (varp->isIfaceRef()) return false; // Cannot handle interface references - if (varp->delayp()) return false; // Cannot handle delayed variables - if (varp->isSc()) return false; // SystemC variables are special and rare, we can ignore - return DfgVertex::isSupportedDType(varp->dtypep()); - } - - bool isSupported(const AstVarScope* vscp) { - // Check the Var fist - if (!isSupported(vscp->varp())) return false; - // If the variable is not in a regular module, then do not convert it. - // This is especially needed for variabels in interfaces which might be - // referenced via virtual intefaces, which cannot be resovled statically. - if (!VN_IS(vscp->scopep()->modp(), Module)) return false; - // Otherwise OK - return true; - } - - bool isSupported(const AstVarRef* nodep) { - // Cannot represent cross module references - if (nodep->classOrPackagep()) return false; - // Check target - return isSupported(getTarget(nodep)); - } - - // Given an RValue expression, return the equivalent Vertex, or nullptr if not representable. - DfgVertex* convertRValue(AstNodeExpr* nodep) { - UASSERT_OBJ(!m_converting, nodep, "'convertingRValue' should not be called recursively"); - VL_RESTORER(m_converting); - VL_RESTORER(m_foundUnhandled); - m_converting = true; - m_foundUnhandled = false; - - // Convert the expression - iterate(nodep); - - // If falied to convert, return nullptr - if (m_foundUnhandled) return nullptr; - - // Traversal set user2p to the equivalent vertex - DfgVertex* const vtxp = nodep->user2u().to(); - UASSERT_OBJ(vtxp, nodep, "Missing Dfg vertex after covnersion"); - return vtxp; - } - - // Given an LValue expression, return the splice node that writes the - // destination, together with the index to use for splicing in the value. - // Returns {nullptr, 0}, if the given LValue expression is not supported. - std::pair convertLValue(AstNodeExpr* nodep) { - if (AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) { - if (!isSupported(vrefp)) { - ++m_ctx.m_nonRepLhs; - return {nullptr, 0}; - } - // Get the variable vertex - DfgVertexVar* const vtxp = getNet(getTarget(vrefp)); - // Ensure the Splice driver exists for this variable - if (!vtxp->srcp()) { - FileLine* const flp = vtxp->fileline(); - AstNodeDType* const dtypep = vtxp->dtypep(); - if (vtxp->is()) { - DfgSplicePacked* const newp = new DfgSplicePacked{m_dfg, flp, dtypep}; - m_uncommittedSpliceps.emplace_back(newp); - vtxp->srcp(newp); - } else if (vtxp->is()) { - DfgSpliceArray* const newp = new DfgSpliceArray{m_dfg, flp, dtypep}; - m_uncommittedSpliceps.emplace_back(newp); - vtxp->srcp(newp); - } else { - nodep->v3fatalSrc("Unhandled DfgVertexVar sub-type"); // LCOV_EXCL_LINE - } - } - // Return the Splice driver - return {vtxp->srcp()->as(), 0}; - } - - if (AstSel* selp = VN_CAST(nodep, Sel)) { - // Only handle constant selects - const AstConst* const lsbp = VN_CAST(selp->lsbp(), Const); - if (!lsbp) { - ++m_ctx.m_nonRepLhs; - return {nullptr, 0}; - } - uint32_t lsb = lsbp->toUInt(); - - // Convert the 'fromp' sub-expression - const auto pair = convertLValue(selp->fromp()); - if (!pair.first) return {nullptr, 0}; - DfgSplicePacked* const splicep = pair.first->template as(); - // Adjust index. - lsb += pair.second; - - // AstSel doesn't change type kind (array vs packed), so we can use - // the existing splice driver with adjusted lsb - return {splicep, lsb}; - } - - if (AstArraySel* const aselp = VN_CAST(nodep, ArraySel)) { - // Only handle constant selects - const AstConst* const indexp = VN_CAST(aselp->bitp(), Const); - if (!indexp) { - ++m_ctx.m_nonRepLhs; - return {nullptr, 0}; - } - uint32_t index = indexp->toUInt(); - - // Convert the 'fromp' sub-expression - const auto pair = convertLValue(aselp->fromp()); - if (!pair.first) return {nullptr, 0}; - DfgSpliceArray* const splicep = pair.first->template as(); - // Adjust index. Note pair.second is always 0, but we might handle array slices later.. - index += pair.second; - - // Ensure the Splice driver exists for this element - if (!splicep->driverAt(index)) { - FileLine* const flp = nodep->fileline(); - AstNodeDType* const dtypep = DfgVertex::dtypeFor(nodep); - if (VN_IS(dtypep, BasicDType)) { - DfgSplicePacked* const newp = new DfgSplicePacked{m_dfg, flp, dtypep}; - m_uncommittedSpliceps.emplace_back(newp); - splicep->addDriver(flp, index, newp); - } else if (VN_IS(dtypep, UnpackArrayDType)) { - DfgSpliceArray* const newp = new DfgSpliceArray{m_dfg, flp, dtypep}; - m_uncommittedSpliceps.emplace_back(newp); - splicep->addDriver(flp, index, newp); - } else { - nodep->v3fatalSrc("Unhandled AstNodeDType sub-type"); // LCOV_EXCL_LINE - } - } - - // Return the splice driver - return {splicep->driverAt(index)->as(), 0}; - } - - ++m_ctx.m_nonRepLhs; - return {nullptr, 0}; - } - - // Given the LHS of an assignment, and the vertex representing the RHS, - // connect up the RHS to drive the targets. - // Returns true on success, false if the LHS is not representable. - bool convertAssignment(FileLine* flp, AstNodeExpr* lhsp, DfgVertex* vtxp) { - // Represents a DFG assignment contributed by the AST assignment with the above 'lhsp'. - // There might be multiple of these if 'lhsp' is a concatenation. - struct Assignment final { - DfgVertexSplice* m_lhsp; - uint32_t m_idx; - DfgVertex* m_rhsp; - Assignment() = delete; - Assignment(DfgVertexSplice* lhsp, uint32_t idx, DfgVertex* rhsp) - : m_lhsp{lhsp} - , m_idx{idx} - , m_rhsp{rhsp} {} - }; - - // Convert each concatenation LHS separately, gather all assignments - // we need to do into 'assignments', return true if all LValues - // converted successfully. - std::vector assignments; - const std::function convertAllLValues - = [&](AstNodeExpr* lhsp, DfgVertex* vtxp) -> bool { - // Simplify the LHS, to get rid of things like SEL(CONCAT(_, _), _) - lhsp = VN_AS(V3Const::constifyExpensiveEdit(lhsp), NodeExpr); - - // Concatenation on the LHS, convert each parts - if (AstConcat* const concatp = VN_CAST(lhsp, Concat)) { - AstNodeExpr* const cLhsp = concatp->lhsp(); - AstNodeExpr* const cRhsp = concatp->rhsp(); - // Convert Left of concat - FileLine* const lFlp = cLhsp->fileline(); - DfgSel* const lVtxp = new DfgSel{m_dfg, lFlp, DfgVertex::dtypeFor(cLhsp)}; - lVtxp->fromp(vtxp); - lVtxp->lsb(cRhsp->width()); - if (!convertAllLValues(cLhsp, lVtxp)) return false; - // Convert Rigth of concat - FileLine* const rFlp = cRhsp->fileline(); - DfgSel* const rVtxp = new DfgSel{m_dfg, rFlp, DfgVertex::dtypeFor(cRhsp)}; - rVtxp->fromp(vtxp); - rVtxp->lsb(0); - return convertAllLValues(cRhsp, rVtxp); - } - - // Non-concatenation, convert the LValue - const auto pair = convertLValue(lhsp); - if (!pair.first) return false; - assignments.emplace_back(pair.first, pair.second, vtxp); - return true; - }; - // Convert the given LHS assignment, give up if any LValues failed to convert - if (!convertAllLValues(lhsp, vtxp)) { - for (DfgVertexSplice* const splicep : m_uncommittedSpliceps) { - VL_DO_DANGLING(splicep->unlinkDelete(m_dfg), splicep); - } - m_uncommittedSpliceps.clear(); + // Gather variables written by the given logic node. + // Return nullptr if any are not supported. + std::unique_ptr> gatherWritten(const AstNode* nodep) { + const VNUser3InUse user3InUse; + std::unique_ptr> resp{new std::vector{}}; + // We can ignore AstVarXRef here. The only thing we can do with DfgLogic is + // synthesize it into regular vertices, which will fail on a VarXRef at that point. + const bool abort = nodep->exists([&](const AstNodeVarRef* vrefp) -> bool { + if (VN_IS(vrefp, VarXRef)) return true; + if (vrefp->access().isReadOnly()) return false; + Variable* const varp = getTarget(VN_AS(vrefp, VarRef)); + if (!DfgGraph::isSupported(varp)) return true; + if (!varp->user3SetOnce()) resp->emplace_back(getVarVertex(varp)); return false; + }); + if (abort) { + ++m_ctx.m_nonRepVar; + return nullptr; } - m_uncommittedSpliceps.clear(); - - // All successful, connect the drivers - for (const Assignment& a : assignments) { - if (DfgSplicePacked* const spp = a.m_lhsp->template cast()) { - spp->addDriver(flp, a.m_idx, a.m_rhsp); - } else if (DfgSpliceArray* const sap = a.m_lhsp->template cast()) { - sap->addDriver(flp, a.m_idx, a.m_rhsp); - } else { - a.m_lhsp->v3fatalSrc("Unhandled DfgVertexSplice sub-type"); // LCOV_EXCL_LINE - } - } - return true; + return resp; } - // Convert the assignment with the given LHS and RHS into DFG. - // Returns true on success, false if not representable. - bool convertEquation(FileLine* flp, AstNodeExpr* lhsp, AstNodeExpr* rhsp) { - // Check data types are compatible. - if (!DfgVertex::isSupportedDType(lhsp->dtypep()) - || !DfgVertex::isSupportedDType(rhsp->dtypep())) { - ++m_ctx.m_nonRepDType; + // Gather variables read by the given logic node. + // Return nullptr if any are not supported. + std::unique_ptr> gatherRead(const AstNode* nodep) { + const VNUser3InUse user3InUse; + std::unique_ptr> resp{new std::vector{}}; + // We can ignore AstVarXRef here. The only thing we can do with DfgLogic is + // synthesize it into regular vertices, which will fail on a VarXRef at that point. + const bool abort = nodep->exists([&](const AstNodeVarRef* vrefp) -> bool { + if (VN_IS(vrefp, VarXRef)) return true; + if (vrefp->access().isWriteOnly()) return false; + Variable* const varp = getTarget(VN_AS(vrefp, VarRef)); + if (!DfgGraph::isSupported(varp)) return true; + if (!varp->user3SetOnce()) resp->emplace_back(getVarVertex(varp)); return false; + }); + if (abort) { + ++m_ctx.m_nonRepVar; + return nullptr; + } + return resp; + } + + // Gather variables live in to the given CFG. + // Return nullptr if any are not supported. + std::unique_ptr> gatherLive(const ControlFlowGraph& cfg) { + // Run analysis + std::unique_ptr> varps = getLiveVariables(cfg); + if (!varps) { + ++m_ctx.m_nonRepLive; + return nullptr; } - // For now, only direct array assignment is supported (e.g. a = b, but not a = _ ? b : c) - if (VN_IS(rhsp->dtypep()->skipRefp(), UnpackArrayDType) && !VN_IS(rhsp, VarRef)) { - ++m_ctx.m_nonRepDType; - return false; + // Convert to vertics + const VNUser3InUse user3InUse; + std::unique_ptr> resp{new std::vector{}}; + resp->reserve(varps->size()); + for (Variable* const varp : *varps) { + if (!DfgGraph::isSupported(varp)) { + ++m_ctx.m_nonRepVar; + return nullptr; + } + UASSERT_OBJ(!varp->user3SetOnce(), varp, "Live variables should be unique"); + resp->emplace_back(getVarVertex(varp)); } + return resp; + } - // Cannot handle mismatched widths. Mismatched assignments should have been fixed up in - // earlier passes anyway, so this should never be hit, but being paranoid just in case. - if (lhsp->width() != rhsp->width()) { // LCOV_EXCL_START - ++m_ctx.m_nonRepWidth; - return false; - } // LCOV_EXCL_STOP + // Connect inputs and outputs of a DfgLogic + void connect(DfgLogic& vtx, const std::vector& iVarps, + const std::vector& oVarps) { + // Connect inputs + for (DfgVertexVar* const iVarp : iVarps) vtx.addInput(iVarp); + // Connect outputs + for (DfgVertexVar* const oVarp : oVarps) { + if (!oVarp->srcp()) oVarp->srcp(new DfgUnresolved{m_dfg, oVarp}); + oVarp->srcp()->as()->addDriver(&vtx); + } + } - // Convert the RHS expression - DfgVertex* const rVtxp = convertRValue(rhsp); - if (!rVtxp) return false; + // Convert AstAssignW to DfgLogic, return true if successful. + bool convert(AstAssignW* nodep) { + // Cannot handle assignment with timing control + if (nodep->timingControlp()) return false; - // Connect the RHS vertex to the LHS targets - if (!convertAssignment(flp, lhsp, rVtxp)) return false; - - // All good + // Potentially convertible block + ++m_ctx.m_inputs; + // Gather written variables, give up if any are not supported + const std::unique_ptr> oVarpsp = gatherWritten(nodep); + if (!oVarpsp) return false; + // Gather read variables, give up if any are not supported + const std::unique_ptr> iVarpsp = gatherRead(nodep); + if (!iVarpsp) return false; + // Create the DfgLogic + DfgLogic* const logicp = new DfgLogic{m_dfg, nodep}; + // Connect it up + connect(*logicp, *iVarpsp, *oVarpsp); + // Done ++m_ctx.m_representable; return true; } - // Convert an AstNodeAssign (AstAssign or AstAssignW) - bool convertNodeAssign(AstNodeAssign* nodep) { - UASSERT_OBJ(VN_IS(nodep, AssignW) || VN_IS(nodep, Assign), nodep, "Invalid subtype"); - ++m_ctx.m_inputEquations; - - // Cannot handle assignment with timing control yet - if (nodep->timingControlp()) { - ++m_ctx.m_nonRepTiming; - return false; - } - - return convertEquation(nodep->fileline(), nodep->lhsp(), nodep->rhsp()); - } - - // Convert special simple form Always block into DFG. - // Returns true on success, false if not representable/not simple. - bool convertSimpleAlways(AstAlways* nodep) { - // Only consider single statement block - if (!nodep->isJustOneBodyStmt()) return false; - - AstNode* const stmtp = nodep->stmtsp(); - - if (AstAssign* const assignp = VN_CAST(stmtp, Assign)) { - return convertNodeAssign(assignp); - } - - if (AstIf* const ifp = VN_CAST(stmtp, If)) { - // Will only handle single assignments to the same LHS in both branches - AstAssign* const thenp = VN_CAST(ifp->thensp(), Assign); - AstAssign* const elsep = VN_CAST(ifp->elsesp(), Assign); - if (!thenp || !elsep || thenp->nextp() || elsep->nextp() - || !thenp->lhsp()->sameTree(elsep->lhsp())) { - return false; - } - - ++m_ctx.m_inputEquations; - if (thenp->timingControlp() || elsep->timingControlp()) { - ++m_ctx.m_nonRepTiming; - return false; - } - - // Create a conditional for the rhs by borrowing the components from the AstIf - AstCond* const rhsp = new AstCond{ifp->fileline(), // - ifp->condp()->unlinkFrBack(), // - thenp->rhsp()->unlinkFrBack(), // - elsep->rhsp()->unlinkFrBack()}; - const bool success = convertEquation(ifp->fileline(), thenp->lhsp(), rhsp); - // Put the AstIf back together - ifp->condp(rhsp->condp()->unlinkFrBack()); - thenp->rhsp(rhsp->thenp()->unlinkFrBack()); - elsep->rhsp(rhsp->elsep()->unlinkFrBack()); - // Delete the auxiliary conditional - VL_DO_DANGLING(rhsp->deleteTree(), rhsp); - return success; - } - - return false; - } - - // VISITORS - - // Unhandled node - void visit(AstNode* nodep) override { - if (!m_foundUnhandled && m_converting) ++m_ctx.m_nonRepUnknown; - m_foundUnhandled = true; - } - - // Expressions - mostly auto generated, but a few special ones - void visit(AstVarRef* nodep) override { - UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); - UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); - if (unhandled(nodep)) return; - // This visit method is only called on RValues, where only read refs are supportes - if (!nodep->access().isReadOnly() || !isSupported(nodep)) { - m_foundUnhandled = true; - ++m_ctx.m_nonRepVarRef; - return; - } - nodep->user2p(getNet(getTarget(nodep))); - } - void visit(AstConst* nodep) override { - UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); - UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); - if (unhandled(nodep)) return; - DfgVertex* const vtxp = new DfgConst{m_dfg, nodep->fileline(), nodep->num()}; - nodep->user2p(vtxp); - } - void visit(AstSel* nodep) override { - UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); - UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); - if (unhandled(nodep)) return; - - iterate(nodep->fromp()); - if (m_foundUnhandled) return; - - FileLine* const flp = nodep->fileline(); - DfgVertex* vtxp = nullptr; - if (AstConst* const constp = VN_CAST(nodep->lsbp(), Const)) { - DfgSel* const selp = new DfgSel{m_dfg, flp, DfgVertex::dtypeFor(nodep)}; - selp->fromp(nodep->fromp()->user2u().to()); - selp->lsb(constp->toUInt()); - vtxp = selp; - } else { - iterate(nodep->lsbp()); - if (m_foundUnhandled) return; - DfgMux* const muxp = new DfgMux{m_dfg, flp, DfgVertex::dtypeFor(nodep)}; - muxp->fromp(nodep->fromp()->user2u().to()); - muxp->lsbp(nodep->lsbp()->user2u().to()); - vtxp = muxp; - } - nodep->user2p(vtxp); - } -// The rest of the visit methods for expressions are generated by 'astgen' -#include "V3Dfg__gen_ast_to_dfg.h" - -public: - // PUBLIC METHODS - - // Convert AstAssignW to Dfg, return true if successful. - bool convert(AstAssignW* nodep) { - if (convertNodeAssign(nodep)) { - // Remove node from Ast. Now represented by the Dfg. - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - return true; - } - - return false; - } - - // Convert AstAlways to Dfg, return true if successful. + // Convert AstAlways to DfgLogic, return true if successful. bool convert(AstAlways* nodep) { - // Ignore sequential logic + // Can only handle combinational logic + if (nodep->sentreep()) return false; const VAlwaysKwd kwd = nodep->keyword(); - if (nodep->sentreep() || (kwd != VAlwaysKwd::ALWAYS && kwd != VAlwaysKwd::ALWAYS_COMB)) { + if (kwd != VAlwaysKwd::ALWAYS && kwd != VAlwaysKwd::ALWAYS_COMB) return false; + + // Potentially convertible block + ++m_ctx.m_inputs; + // Attempt to build CFG of AstAlways, give up if failed + std::unique_ptr cfgp = V3Cfg::build(nodep); + if (!cfgp) { + ++m_ctx.m_nonRepCfg; return false; } - - // Attemp to convert special forms - if (convertSimpleAlways(nodep)) { - // Remove node from Ast. Now represented by the Dfg. - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - return true; - } - - return false; - } - - // CONSTRUCTOR - AstToDfgConverter(DfgGraph& dfg, V3DfgAstToDfgContext& ctx) - : m_dfg{dfg} - , m_ctx{ctx} {} -}; - -// Resolves multiple drivers (keep only the first one), -// and ensures drivers are stored in ascending index order -class AstToDfgNormalizeDrivers final { - // TYPES - struct Driver final { - FileLine* m_flp; // Location of driver in source - uint32_t m_low; // Low index of driven range - DfgVertex* m_vtxp; // Driving vertex - Driver() = delete; - Driver(FileLine* flp, uint32_t low, DfgVertex* vtxp) - : m_flp{flp} - , m_low{low} - , m_vtxp{vtxp} {} - }; - - // STATE - DfgGraph& m_dfg; // The graph being processed - DfgVertexVar& m_var; // The variable being normalzied - - // METHODS - - // Normalize packed driver - void normalizePacked(const std::string& sub, DfgSplicePacked* const splicep) { - UASSERT_OBJ(splicep->arity() >= 1, splicep, "Undriven DfgSplicePacked"); - - // The drivers of 'splicep' - std::vector drivers; - drivers.reserve(splicep->arity()); - - // Sometime assignment ranges are coalesced by V3Const, - // so we unpack concatenations for better error reporting. - const std::function gather - = [&](FileLine* flp, uint32_t lsb, DfgVertex* vtxp) -> void { - if (DfgConcat* const concatp = vtxp->cast()) { - DfgVertex* const rhsp = concatp->rhsp(); - auto const rhs_width = rhsp->width(); - gather(rhsp->fileline(), lsb, rhsp); - DfgVertex* const lhsp = concatp->lhsp(); - gather(lhsp->fileline(), lsb + rhs_width, lhsp); - concatp->unlinkDelete(m_dfg); - } else { - drivers.emplace_back(flp, lsb, vtxp); - } - }; - - // Gather and unlink all drivers - splicep->forEachSourceEdge([&](DfgEdge& edge, size_t i) { - DfgVertex* const driverp = edge.sourcep(); - UASSERT(driverp, "Should not have created undriven sources"); - UASSERT_OBJ(!driverp->is(), splicep, "Should not be DfgVertexSplice"); - gather(splicep->driverFileLine(i), splicep->driverLsb(i), driverp); - edge.unlinkSource(); - }); - splicep->resetSources(); - - const auto cmp = [](const Driver& a, const Driver& b) { - if (a.m_low != b.m_low) return a.m_low < b.m_low; - return a.m_flp->operatorCompare(*b.m_flp) < 0; - }; - - // Sort drivers by LSB - std::stable_sort(drivers.begin(), drivers.end(), cmp); - - // Fix multiply driven ranges - for (auto it = drivers.begin(); it != drivers.end();) { - Driver& a = *it++; - const uint32_t aWidth = a.m_vtxp->width(); - const uint32_t aEnd = a.m_low + aWidth; - while (it != drivers.end()) { - Driver& b = *it; - // If no overlap, then nothing to do - if (b.m_low >= aEnd) break; - - const uint32_t bWidth = b.m_vtxp->width(); - const uint32_t bEnd = b.m_low + bWidth; - const uint32_t overlapEnd = std::min(aEnd, bEnd) - 1; - - // Loop index often abused, so suppress - if (!m_var.varp()->isUsedLoopIdx()) { - AstNode* const nodep = m_var.nodep(); - nodep->v3warn( // - MULTIDRIVEN, - "Bits [" // - << overlapEnd << ":" << b.m_low << "] of signal '" - << nodep->prettyName() << sub - << "' have multiple combinational drivers\n" - << a.m_flp->warnOther() << "... Location of first driver\n" - << a.m_flp->warnContextPrimary() << '\n' - << b.m_flp->warnOther() << "... Location of other driver\n" - << b.m_flp->warnContextSecondary() << nodep->warnOther() - << "... Only the first driver will be respected"); - } - - // If the first driver completely covers the range of the second driver, - // we can just delete the second driver completely, otherwise adjust the - // second driver to apply from the end of the range of the first driver. - if (aEnd >= bEnd) { - it = drivers.erase(it); - } else { - const auto dtypep = DfgVertex::dtypeForWidth(bEnd - aEnd); - DfgSel* const selp = new DfgSel{m_dfg, b.m_vtxp->fileline(), dtypep}; - selp->fromp(b.m_vtxp); - selp->lsb(aEnd - b.m_low); - b.m_low = aEnd; - b.m_vtxp = selp; - std::stable_sort(it, drivers.end(), cmp); - } - } - } - - // Reinsert drivers in order - for (const Driver& d : drivers) splicep->addDriver(d.m_flp, d.m_low, d.m_vtxp); - } - - // Normalize array driver - void normalizeArray(const std::string& sub, DfgSpliceArray* const splicep) { - UASSERT_OBJ(splicep->arity() >= 1, splicep, "Undriven DfgSpliceArray"); - - // The drivers of 'splicep' - std::vector drivers; - drivers.reserve(splicep->arity()); - - // Normalize, gather, and unlink all drivers - splicep->forEachSourceEdge([&](DfgEdge& edge, size_t i) { - DfgVertex* const driverp = edge.sourcep(); - UASSERT(driverp, "Should not have created undriven sources"); - const uint32_t idx = splicep->driverIndex(i); - // Normalize - if (DfgSplicePacked* const splicePackedp = driverp->cast()) { - normalizePacked(sub + "[" + std::to_string(idx) + "]", splicePackedp); - } else if (DfgSpliceArray* const spliceArrayp = driverp->cast()) { - normalizeArray(sub + "[" + std::to_string(idx) + "]", spliceArrayp); - } else if (driverp->is()) { - driverp->v3fatalSrc("Unhandled DfgVertexSplice sub-type"); // LCOV_EXCL_LINE - } - // Gather - drivers.emplace_back(splicep->driverFileLine(i), idx, driverp); - // Unlink - edge.unlinkSource(); - }); - splicep->resetSources(); - - const auto cmp = [](const Driver& a, const Driver& b) { - if (a.m_low != b.m_low) return a.m_low < b.m_low; - return a.m_flp->operatorCompare(*b.m_flp) < 0; - }; - - // Sort drivers by index - std::stable_sort(drivers.begin(), drivers.end(), cmp); - - // Fix multiply driven ranges - for (auto it = drivers.begin(); it != drivers.end();) { - Driver& a = *it++; - AstUnpackArrayDType* aArrayDTypep = VN_CAST(a.m_vtxp->dtypep(), UnpackArrayDType); - const uint32_t aElements = aArrayDTypep ? aArrayDTypep->elementsConst() : 1; - const uint32_t aEnd = a.m_low + aElements; - while (it != drivers.end()) { - Driver& b = *it; - // If no overlap, then nothing to do - if (b.m_low >= aEnd) break; - - AstUnpackArrayDType* bArrayDTypep = VN_CAST(b.m_vtxp->dtypep(), UnpackArrayDType); - const uint32_t bElements = bArrayDTypep ? bArrayDTypep->elementsConst() : 1; - const uint32_t bEnd = b.m_low + bElements; - const uint32_t overlapEnd = std::min(aEnd, bEnd) - 1; - - AstNode* const nodep = m_var.nodep(); - nodep->v3warn( // - MULTIDRIVEN, - "Elements [" // - << overlapEnd << ":" << b.m_low << "] of signal '" << nodep->prettyName() - << sub << "' have multiple combinational drivers\n" - << a.m_flp->warnOther() << "... Location of first driver\n" - << a.m_flp->warnContextPrimary() << '\n' - << b.m_flp->warnOther() << "... Location of other driver\n" - << b.m_flp->warnContextSecondary() << nodep->warnOther() - << "... Only the first driver will be respected"); - - // If the first driver completely covers the range of the second driver, - // we can just delete the second driver completely, otherwise adjust the - // second driver to apply from the end of the range of the first driver. - if (aEnd >= bEnd) { - it = drivers.erase(it); - } else { - const auto distance = std::distance(drivers.begin(), it); - DfgVertex* const bVtxp = b.m_vtxp; - FileLine* const flp = b.m_vtxp->fileline(); - AstNodeDType* const elemDtypep = DfgVertex::dtypeFor( - VN_AS(splicep->dtypep(), UnpackArrayDType)->subDTypep()); - // Remove this driver - it = drivers.erase(it); - // Add missing items element-wise - for (uint32_t i = aEnd; i < bEnd; ++i) { - DfgArraySel* const aselp = new DfgArraySel{m_dfg, flp, elemDtypep}; - aselp->fromp(bVtxp); - aselp->bitp(new DfgConst{m_dfg, flp, 32, i}); - drivers.emplace_back(flp, i, aselp); - } - it = drivers.begin(); - std::advance(it, distance); - std::stable_sort(it, drivers.end(), cmp); - } - } - } - - // Reinsert drivers in order - for (const Driver& d : drivers) splicep->addDriver(d.m_flp, d.m_low, d.m_vtxp); - } - - // CONSTRUCTOR - AstToDfgNormalizeDrivers(DfgGraph& dfg, DfgVertexVar& var) - : m_dfg{dfg} - , m_var{var} { - // Nothing to do for un-driven (input) variables - if (!var.srcp()) return; - - // The driver of a variable must always be a splice vertex, normalize it - if (DfgSpliceArray* const sArrayp = var.srcp()->cast()) { - normalizeArray("", sArrayp); - } else if (DfgSplicePacked* const sPackedp = var.srcp()->cast()) { - normalizePacked("", sPackedp); - } else { - var.v3fatalSrc("Unhandled DfgVertexSplice sub-type"); // LCOV_EXCL_LINE - } - } - -public: - // Normalize drivers of given variable - static void apply(DfgGraph& dfg, DfgVertexVar& var) { AstToDfgNormalizeDrivers{dfg, var}; } -}; - -// Coalesce contiguous driver ranges, -// and remove redundant splice vertices (when the variable is driven whole) -class AstToDfgCoalesceDrivers final { - // TYPES - struct Driver final { - FileLine* m_flp; // Location of driver in source - uint32_t m_low; // Low index of driven range - DfgVertex* m_vtxp; // Driving vertex - Driver() = delete; - Driver(FileLine* flp, uint32_t low, DfgVertex* vtxp) - : m_flp{flp} - , m_low{low} - , m_vtxp{vtxp} {} - }; - - // STATE - DfgGraph& m_dfg; // The graph being processed - V3DfgAstToDfgContext& m_ctx; // The context for stats - - // METHODS - - // Coalesce packed driver - return the coalesced vertex and location for 'splicep' - std::pair coalescePacked(DfgSplicePacked* const splicep) { - UASSERT_OBJ(splicep->arity() >= 1, splicep, "Undriven DfgSplicePacked"); - - // The drivers of 'splicep' - std::vector drivers; - drivers.reserve(splicep->arity()); - - // Gather and unlink all drivers - int64_t prevHigh = -1; // High index of previous driven range - splicep->forEachSourceEdge([&](DfgEdge& edge, size_t i) { - DfgVertex* const driverp = edge.sourcep(); - UASSERT_OBJ(driverp, splicep, "Should not have created undriven sources"); - UASSERT_OBJ(!driverp->is(), splicep, "Should not be DfgVertexSplice"); - const uint32_t low = splicep->driverLsb(i); - UASSERT_OBJ(static_cast(low) > prevHigh, splicep, - "Drivers should have been normalized"); - prevHigh = low + driverp->width() - 1; - // Gather - drivers.emplace_back(splicep->driverFileLine(i), low, driverp); - // Unlink - edge.unlinkSource(); - }); - splicep->resetSources(); - - // Coalesce adjacent ranges - if (drivers.size() > 1) { - size_t mergeInto = 0; - size_t mergeFrom = 1; - do { - Driver& into = drivers[mergeInto]; - Driver& from = drivers[mergeFrom]; - const uint32_t intoWidth = into.m_vtxp->width(); - const uint32_t fromWidth = from.m_vtxp->width(); - - if (into.m_low + intoWidth == from.m_low) { - // Adjacent ranges, coalesce - const auto dtypep = DfgVertex::dtypeForWidth(intoWidth + fromWidth); - DfgConcat* const concatp = new DfgConcat{m_dfg, into.m_flp, dtypep}; - concatp->rhsp(into.m_vtxp); - concatp->lhsp(from.m_vtxp); - into.m_vtxp = concatp; - from.m_vtxp = nullptr; // Mark as moved - ++m_ctx.m_coalescedAssignments; - } else { - // There is a gap - future merges go into the next position - ++mergeInto; - // Move 'from' into the next position, unless it's already there - if (mergeFrom != mergeInto) { - Driver& next = drivers[mergeInto]; - UASSERT_OBJ(!next.m_vtxp, next.m_flp, "Should have been marked moved"); - next = from; - from.m_vtxp = nullptr; // Mark as moved - } - } - - // Consider next driver - ++mergeFrom; - } while (mergeFrom < drivers.size()); - // Rightsize vector - drivers.erase(drivers.begin() + (mergeInto + 1), drivers.end()); - } - - // If the variable is driven whole, we can just use that driver - if (drivers.size() == 1 // - && drivers[0].m_low == 0 // - && drivers[0].m_vtxp->width() == splicep->width()) { - VL_DO_DANGLING(splicep->unlinkDelete(m_dfg), splicep); - // Use the driver directly - return {drivers[0].m_vtxp, drivers[0].m_flp}; - } - - // Reinsert drivers in order - for (const Driver& d : drivers) splicep->addDriver(d.m_flp, d.m_low, d.m_vtxp); - // Use the original splice - return {splicep, splicep->fileline()}; - } - - // Coalesce array driver - return the coalesced vertex and location for 'splicep' - std::pair coalesceArray(DfgSpliceArray* const splicep) { - UASSERT_OBJ(splicep->arity() >= 1, splicep, "Undriven DfgSpliceArray"); - - // The drivers of 'splicep' - std::vector drivers; - drivers.reserve(splicep->arity()); - - // Coalesce, gather and unlink all drivers - int64_t prevHigh = -1; // High index of previous driven range - splicep->forEachSourceEdge([&](DfgEdge& edge, size_t i) { - DfgVertex* driverp = edge.sourcep(); - UASSERT_OBJ(driverp, splicep, "Should not have created undriven sources"); - const uint32_t low = splicep->driverIndex(i); - UASSERT_OBJ(static_cast(low) > prevHigh, splicep, - "Drivers should have been normalized"); - prevHigh = low; - FileLine* flp = splicep->driverFileLine(i); - // Coalesce - if (DfgSplicePacked* const spp = driverp->cast()) { - std::tie(driverp, flp) = coalescePacked(spp); - } else if (DfgSpliceArray* const sap = driverp->cast()) { - std::tie(driverp, flp) = coalesceArray(sap); - } else if (driverp->is()) { - driverp->v3fatalSrc("Unhandled DfgVertexSplice sub-type"); // LCOV_EXCL_LINE - } - // Gather - drivers.emplace_back(flp, low, driverp); - // Unlink - edge.unlinkSource(); - }); - splicep->resetSources(); - - // If the variable is driven whole, we can just use that driver - if (drivers.size() == 1 // - && drivers[0].m_low == 0 // - && drivers[0].m_vtxp->dtypep()->isSame(splicep->dtypep())) { - VL_DO_DANGLING(splicep->unlinkDelete(m_dfg), splicep); - // Use the driver directly - return {drivers[0].m_vtxp, drivers[0].m_flp}; - } - - // Reinsert drivers in order - for (const Driver& d : drivers) splicep->addDriver(d.m_flp, d.m_low, d.m_vtxp); - // Use the original splice - return {splicep, splicep->fileline()}; - } - - // CONSTRUCTOR - AstToDfgCoalesceDrivers(DfgGraph& dfg, DfgVertexVar& var, V3DfgAstToDfgContext& ctx) - : m_dfg{dfg} - , m_ctx{ctx} { - // Nothing to do for un-driven (input) variables - if (!var.srcp()) return; - - // The driver of a variable must always be a splice vertex, coalesce it - std::pair normalizedDriver; - if (DfgSpliceArray* const sArrayp = var.srcp()->cast()) { - normalizedDriver = coalesceArray(sArrayp); - } else if (DfgSplicePacked* const sPackedp = var.srcp()->cast()) { - normalizedDriver = coalescePacked(sPackedp); - } else { - var.v3fatalSrc("Unhandled DfgVertexSplice sub-type"); // LCOV_EXCL_LINE - } - var.srcp(normalizedDriver.first); - var.driverFileLine(normalizedDriver.second); - } - -public: - // Coalesce drivers of given variable - static void apply(DfgGraph& dfg, DfgVertexVar& var, V3DfgAstToDfgContext& ctx) { - AstToDfgCoalesceDrivers{dfg, var, ctx}; - } -}; - -// Visitor that converts a whole module (when T_Scoped is false), -// or the whole netlist (when T_Scoped is true). -template -class AstToDfgVisitor final : public VNVisitor { - // NODE STATE - const VNUser2InUse m_user2InUse; // Used by AstToDfgConverter - - // TYPES - using RootType = std::conditional_t; - using Variable = std::conditional_t; - - // STATE - AstToDfgConverter m_converter; // The convert instance to use for each construct - - // METHODS - static Variable* getTarget(const AstVarRef* refp) { - // TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works - if VL_CONSTEXPR_CXX17 (T_Scoped) { - return reinterpret_cast(refp->varScopep()); - } else { - return reinterpret_cast(refp->varp()); - } - } - - // Mark variables referenced under node - static void markReferenced(AstNode* nodep) { - nodep->foreach([](const AstVarRef* refp) { - Variable* const tgtp = getTarget(refp); - // Mark as read from non-DFG logic - if (refp->access().isReadOrRW()) DfgVertexVar::setHasModRdRefs(tgtp); - // Mark as written from non-DFG logic - if (refp->access().isWriteOrRW()) DfgVertexVar::setHasModWrRefs(tgtp); - }); + // Gather written variables, give up if any are not supported + const std::unique_ptr> oVarpsp = gatherWritten(nodep); + if (!oVarpsp) return false; + // Gather read variables, give up if any are not supported + const std::unique_ptr> iVarpsp = gatherLive(*cfgp); + if (!iVarpsp) return false; + // Create the DfgLogic + DfgLogic* const logicp = new DfgLogic{m_dfg, nodep, std::move(cfgp)}; + // Connect it up + connect(*logicp, *iVarpsp, *oVarpsp); + // Done + ++m_ctx.m_representable; + return true; } // VISITORS @@ -975,15 +249,16 @@ class AstToDfgVisitor final : public VNVisitor { // Potentially representable constructs void visit(AstAssignW* nodep) override { - if (!m_converter.convert(nodep)) markReferenced(nodep); + if (!convert(nodep)) markReferenced(nodep); } void visit(AstAlways* nodep) override { - if (!m_converter.convert(nodep)) markReferenced(nodep); + if (!convert(nodep)) markReferenced(nodep); } // CONSTRUCTOR AstToDfgVisitor(DfgGraph& dfg, RootType& root, V3DfgAstToDfgContext& ctx) - : m_converter{dfg, ctx} { + : m_dfg{dfg} + , m_ctx{ctx} { iterate(&root); } @@ -991,16 +266,10 @@ public: static void apply(DfgGraph& dfg, RootType& root, V3DfgAstToDfgContext& ctx) { // Convert all logic under 'root' AstToDfgVisitor{dfg, root, ctx}; - if (dumpDfgLevel() >= 9) dfg.dumpDotFilePrefixed(ctx.prefix() + "ast2dfg-conv"); - // Normalize and coalesce all variable drivers - for (DfgVertexVar& var : dfg.varVertices()) { - AstToDfgNormalizeDrivers::apply(dfg, var); - AstToDfgCoalesceDrivers::apply(dfg, var, ctx); + // Remove unread and undriven variables (created when something failed to convert) + for (DfgVertexVar* const varp : dfg.varVertices().unlinkable()) { + if (!varp->srcp() && !varp->hasSinks()) VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); } - if (dumpDfgLevel() >= 9) dfg.dumpDotFilePrefixed(ctx.prefix() + "ast2dfg-norm"); - // Remove all unused vertices - V3DfgPasses::removeUnused(dfg); - if (dumpDfgLevel() >= 9) dfg.dumpDotFilePrefixed(ctx.prefix() + "ast2dfg-prun"); } }; diff --git a/src/V3DfgBreakCycles.cpp b/src/V3DfgBreakCycles.cpp index cf9d718cb..9d0d852d5 100644 --- a/src/V3DfgBreakCycles.cpp +++ b/src/V3DfgBreakCycles.cpp @@ -20,6 +20,7 @@ #include "V3DfgPasses.h" #include "V3Hash.h" +#include #include #include #include @@ -116,7 +117,7 @@ class TraceDriver final : public DfgVisitor { // Vertex is DfgConst, in which case this code is unreachable ... using Vtx = typename std::conditional::value, DfgSel, Vertex>::type; - AstNodeDType* const dtypep = DfgVertex::dtypeForWidth(width); + AstNodeDType* const dtypep = DfgGraph::dtypePacked(width); Vtx* const vtxp = new Vtx{m_dfg, refp->fileline(), dtypep}; vtxp->template setUser(0); m_newVtxps.emplace_back(vtxp); @@ -281,18 +282,94 @@ class TraceDriver final : public DfgVisitor { } void visit(DfgSplicePacked* vtxp) override { - // Proceed with the driver that wholly covers the searched bits + struct Driver final { + DfgVertex* m_vtxp; + uint32_t m_lsb; // LSB of driven range (internal, not Verilog) + uint32_t m_msb; // MSB of driven range (internal, not Verilog) + Driver() = delete; + Driver(DfgVertex* vtxp, uint32_t lsb, uint32_t msb) + : m_vtxp{vtxp} + , m_lsb{lsb} + , m_msb{msb} {} + }; + std::vector drivers; + DfgVertex* const defaultp = vtxp->defaultp(); + + // Look at all the drivers, one might cover the whole range, but also gathe all drivers const auto pair = vtxp->sourceEdges(); + bool tryWholeDefault = defaultp; for (size_t i = 0; i < pair.second; ++i) { DfgVertex* const srcp = pair.first[i].sourcep(); - const uint32_t lsb = vtxp->driverLsb(i); + if (srcp == defaultp) continue; + + const uint32_t lsb = vtxp->driverLo(i); const uint32_t msb = lsb + srcp->width() - 1; - // If it does not cover the searched bit range, move on + drivers.emplace_back(srcp, lsb, msb); + // Check if this driver covers any of the bits, then we can't use whole default + if (m_msb >= lsb && msb >= m_lsb) tryWholeDefault = false; + // If it does not cover the whole searched bit range, move on if (m_lsb < lsb || msb < m_msb) continue; - // Trace this driver + // Driver covers whole search range, trace that and we are done SET_RESULT(trace(srcp, m_msb - lsb, m_lsb - lsb)); return; } + + // Trace the default driver if no other drivers cover the searched range + if (defaultp && tryWholeDefault) { + SET_RESULT(trace(defaultp, m_msb, m_lsb)); + return; + } + + // Hard case: We need to combine multiple drivers to produce the searched bit range + + // Sort ragnes (they are non-overlapping) + std::sort(drivers.begin(), drivers.end(), + [](const Driver& a, const Driver& b) { return a.m_lsb < b.m_lsb; }); + + // Gather terms + std::vector termps; + for (const Driver& driver : drivers) { + // Driver is below the searched LSB, move on + if (m_lsb > driver.m_msb) continue; + // Driver is above the searched MSB, done + if (driver.m_lsb > m_msb) break; + // Gap below this driver, trace default to fill it + if (driver.m_lsb > m_lsb) { + if (!defaultp) return; + DfgVertex* const termp = trace(defaultp, driver.m_lsb - 1, m_lsb); + if (!termp) return; + termps.emplace_back(termp); + m_lsb = driver.m_lsb; + } + // Driver covers searched range, pick the needed/available bits + uint32_t lim = std::min(m_msb, driver.m_msb); + DfgVertex* const termp + = trace(driver.m_vtxp, lim - driver.m_lsb, m_lsb - driver.m_lsb); + if (!termp) return; + termps.emplace_back(termp); + m_lsb = lim + 1; + } + if (m_msb >= m_lsb) { + if (!defaultp) return; + DfgVertex* const termp = trace(defaultp, m_msb, m_lsb); + if (!termp) return; + termps.emplace_back(termp); + } + + // The earlier cheks cover the case when either a whole driver or the default covers + // the whole range, so there should be at least 2 terms required here. + UASSERT_OBJ(termps.size() >= 2, vtxp, "Should have returned in special cases"); + + // Concatenate all terms and set result + DfgVertex* resp = termps.front(); + for (size_t i = 1; i < termps.size(); ++i) { + DfgVertex* const termp = termps[i]; + DfgConcat* const catp = make(termp, resp->width() + termp->width()); + catp->rhsp(resp); + catp->lhsp(termp); + resp = catp; + } + SET_RESULT(resp); } void visit(DfgVarPacked* vtxp) override { @@ -315,12 +392,24 @@ class TraceDriver final : public DfgVisitor { } // Find driver if (!varp->srcp()) return; - DfgSpliceArray* const splicep = varp->srcp()->cast(); - if (!splicep) return; - DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT()); - if (!driverp) return; + + // Driver might be a splice + if (DfgSpliceArray* const splicep = varp->srcp()->cast()) { + DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT()); + if (!driverp) return; + DfgUnitArray* const uap = driverp->cast(); + if (!uap) return; + // Trace the driver + SET_RESULT(trace(uap->srcp(), m_msb, m_lsb)); + return; + } + + // Or a unit array + DfgUnitArray* const uap = varp->srcp()->cast(); + if (!uap) return; // Trace the driver - SET_RESULT(trace(driverp, m_msb, m_lsb)); + UASSERT_OBJ(idxp->toSizeT() == 0, vtxp, "Array Index out of range"); + SET_RESULT(trace(uap->srcp(), m_msb, m_lsb)); } void visit(DfgConcat* vtxp) override { @@ -629,9 +718,12 @@ class IndependentBits final : public DfgVisitor { void visit(DfgSplicePacked* vtxp) override { // Combine the masks of all drivers V3Number& m = MASK(vtxp); + DfgVertex* const defaultp = vtxp->defaultp(); + if (defaultp) m = MASK(defaultp); vtxp->forEachSourceEdge([&](DfgEdge& edge, size_t i) { const DfgVertex* const srcp = edge.sourcep(); - m.opSelInto(MASK(srcp), vtxp->driverLsb(i), srcp->width()); + if (srcp == defaultp) return; + m.opSelInto(MASK(srcp), vtxp->driverLo(i), srcp->width()); }); } @@ -656,8 +748,10 @@ class IndependentBits final : public DfgVisitor { if (!splicep) return; DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT()); if (!driverp) return; + DfgUnitArray* const uap = driverp->cast(); + if (!uap) return; // Update mask - MASK(vtxp) = MASK(driverp); + MASK(vtxp) = MASK(uap->srcp()); } void visit(DfgConcat* vtxp) override { @@ -829,7 +923,9 @@ class IndependentBits final : public DfgVisitor { if (VN_IS(currp->dtypep(), UnpackArrayDType)) { // For an unpacked array vertex, just enque it's sinks. // (There can be no loops through arrays directly) - currp->forEachSink([&](DfgVertex& vtx) { workList.emplace_back(&vtx); }); + currp->forEachSink([&](DfgVertex& vtx) { + if (vtx.getUser() == m_component) workList.emplace_back(&vtx); + }); continue; } @@ -843,7 +939,9 @@ class IndependentBits final : public DfgVisitor { // If mask changed, enqueue sinks if (!prevMask.isCaseEq(maskCurr)) { - currp->forEachSink([&](DfgVertex& vtx) { workList.emplace_back(&vtx); }); + currp->forEachSink([&](DfgVertex& vtx) { + if (vtx.getUser() == m_component) workList.emplace_back(&vtx); + }); // Check the mask only ever contrects (no bit goes 0 -> 1) if (VL_UNLIKELY(v3Global.opt.debugCheck())) { @@ -998,7 +1096,7 @@ class FixUpIndependentRanges final { } // Fall back on using the part of the variable (if dependent, or trace failed) if (!termp) { - AstNodeDType* const dtypep = DfgVertex::dtypeForWidth(width); + AstNodeDType* const dtypep = DfgGraph::dtypePacked(width); DfgSel* const selp = new DfgSel{dfg, vtxp->fileline(), dtypep}; // Same component as 'vtxp', as reads 'vtxp' and will replace 'vtxp' selp->setUser(vtxp->getUser()); @@ -1079,7 +1177,7 @@ class FixUpIndependentRanges final { for (size_t i = 1; i < termps.size(); ++i) { DfgVertex* const termp = termps[i]; const uint32_t catWidth = replacementp->width() + termp->width(); - AstNodeDType* const dtypep = DfgVertex::dtypeForWidth(catWidth); + AstNodeDType* const dtypep = DfgGraph::dtypePacked(catWidth); DfgConcat* const catp = new DfgConcat{dfg, flp, dtypep}; catp->rhsp(replacementp); catp->lhsp(termp); diff --git a/src/V3DfgContext.h b/src/V3DfgContext.h index 87487ad56..9fad9e2ee 100644 --- a/src/V3DfgContext.h +++ b/src/V3DfgContext.h @@ -69,45 +69,23 @@ class V3DfgAstToDfgContext final : public V3DfgSubContext { public: // STATE - VDouble0 m_coalescedAssignments; // Number of partial assignments coalesced - VDouble0 m_inputEquations; // Number of input combinational equations + VDouble0 m_inputs; // Number of input processes (logic constructs) VDouble0 m_representable; // Number of combinational equations representable - VDouble0 m_nonRepDType; // Equations non-representable due to data type - VDouble0 m_nonRepImpure; // Equations non-representable due to impure node - VDouble0 m_nonRepTiming; // Equations non-representable due to timing control - VDouble0 m_nonRepLhs; // Equations non-representable due to lhs - VDouble0 m_nonRepNode; // Equations non-representable due to node type - VDouble0 m_nonRepUnknown; // Equations non-representable due to unknown node - VDouble0 m_nonRepVarRef; // Equations non-representable due to variable reference - VDouble0 m_nonRepWidth; // Equations non-representable due to width mismatch + VDouble0 m_nonRepCfg; // Non-representable due to failing to build CFG + VDouble0 m_nonRepLive; // Non-representable due to failing liveness analysis + VDouble0 m_nonRepVar; // Non-representable due to unsupported variable properties private: V3DfgAstToDfgContext(V3DfgContext& ctx, const std::string& label) : V3DfgSubContext{ctx, label, "AstToDfg"} {} ~V3DfgAstToDfgContext() { - addStat("coalesced assignments", m_coalescedAssignments); - addStat("input equations", m_inputEquations); + addStat("input processes", m_inputs); addStat("representable", m_representable); - addStat("non-representable (dtype)", m_nonRepDType); - addStat("non-representable (impure)", m_nonRepImpure); - addStat("non-representable (timing)", m_nonRepTiming); - addStat("non-representable (lhs)", m_nonRepLhs); - addStat("non-representable (node)", m_nonRepNode); - addStat("non-representable (unknown)", m_nonRepUnknown); - addStat("non-representable (var ref)", m_nonRepVarRef); - addStat("non-representable (width)", m_nonRepWidth); - + addStat("non-representable (cfg)", m_nonRepCfg); + addStat("non-representable (live)", m_nonRepLive); + addStat("non-representable (var)", m_nonRepVar); // Check the stats are consistent - UASSERT(m_representable // - + m_nonRepDType // - + m_nonRepImpure // - + m_nonRepTiming // - + m_nonRepLhs // - + m_nonRepNode // - + m_nonRepUnknown // - + m_nonRepVarRef // - + m_nonRepWidth // - == m_inputEquations, + UASSERT(m_representable + m_nonRepCfg + m_nonRepLive + m_nonRepVar == m_inputs, "Inconsistent statistics"); } }; @@ -167,12 +145,18 @@ class V3DfgDfgToAstContext final : public V3DfgSubContext { public: // STATE + VDouble0 m_outputVariables; // Number of output variables + VDouble0 m_outputVariablesWithDefault; // Number of outptu variables with a default driver VDouble0 m_resultEquations; // Number of result combinational equations private: V3DfgDfgToAstContext(V3DfgContext& ctx, const std::string& label) : V3DfgSubContext{ctx, label, "DfgToAst"} {} - ~V3DfgDfgToAstContext() { addStat("result equations", m_resultEquations); } + ~V3DfgDfgToAstContext() { + addStat("output variables", m_outputVariables); + addStat("output variables with default driver", m_outputVariablesWithDefault); + addStat("result equations", m_resultEquations); + } }; class V3DfgEliminateVarsContext final : public V3DfgSubContext { // Only V3DfgContext can create an instance @@ -223,6 +207,115 @@ private: : V3DfgSubContext{ctx, label, "Regularize"} {} ~V3DfgRegularizeContext() { addStat("temporaries introduced", m_temporariesIntroduced); } }; +class V3DfgSynthesisContext final : public V3DfgSubContext { + // Only V3DfgContext can create an instance + friend class V3DfgContext; + +public: + // STATE + + // Stats for conversion + struct { + // Inputs + VDouble0 inputAssignments; // Number of input assignments + VDouble0 inputExpressions; // Number of input equations + // Successful + VDouble0 representable; // Number of representable constructs + // Unsuccessful + VDouble0 nonRepImpure; // Non representable: impure + VDouble0 nonRepDType; // Non representable: unsupported data type + VDouble0 nonRepLValue; // Non representable: unsupported LValue form + VDouble0 nonRepVarRef; // Non representable: unsupported var reference + VDouble0 nonRepNode; // Non representable: unsupported AstNode type + VDouble0 nonRepUnknown; // Non representable: unhandled AstNode type + } m_conv; + + // Stats for synthesis + struct { + // Inputs + VDouble0 inputAlways; // Number of always blocks attempted + VDouble0 inputAssign; // Number of continuous assignments attempted + // Successful + VDouble0 synthAlways; // Number of always blocks successfully synthesized + VDouble0 synthAssign; // Number of continuous assignments successfully synthesized + // Unsuccessful + VDouble0 nonSynConv; // Non synthesizable: non representable (above) + VDouble0 nonSynExtWrite; // Non synthesizable: has externally written variable + VDouble0 nonSynLoop; // Non synthesizable: loop in CFG + VDouble0 nonSynStmt; // Non synthesizable: unsupported statement + VDouble0 nonSynMultidrive; // Non synthesizable: multidriven value within statement + VDouble0 nonSynArray; // Non synthesizable: array type unhandled + VDouble0 nonSynLatch; // Non synthesizable: maybe latch + VDouble0 nonSynJoinInput; // Non synthesizable: needing to join input variable + VDouble0 nonSynFalseWrite; // Non synthesizable: does not write output + // Reverted + VDouble0 revertNonSyn; // Reverted due to being driven from non-synthesizable vertex + VDouble0 revertMultidrive; // Reverted due to multiple drivers + + } m_synt; + +private: + V3DfgSynthesisContext(V3DfgContext& ctx, const std::string& label) + : V3DfgSubContext{ctx, label, "Synthesis"} {} + ~V3DfgSynthesisContext() { + // Conversion statistics + addStat("conv / input assignments", m_conv.inputAssignments); + addStat("conv / input expressions", m_conv.inputExpressions); + addStat("conv / representable inputs", m_conv.representable); + addStat("conv / non-representable (impure)", m_conv.nonRepImpure); + addStat("conv / non-representable (dtype)", m_conv.nonRepDType); + addStat("conv / non-representable (lhs)", m_conv.nonRepLValue); + addStat("conv / non-representable (varref)", m_conv.nonRepVarRef); + addStat("conv / non-representable (node)", m_conv.nonRepNode); + addStat("conv / non-representable (unknown)", m_conv.nonRepUnknown); + VDouble0 nConvNonRep; + nConvNonRep += m_conv.nonRepImpure; + nConvNonRep += m_conv.nonRepDType; + nConvNonRep += m_conv.nonRepLValue; + nConvNonRep += m_conv.nonRepVarRef; + nConvNonRep += m_conv.nonRepNode; + nConvNonRep += m_conv.nonRepUnknown; + VDouble0 nConvExpect; + nConvExpect += m_conv.inputAssignments; + nConvExpect += m_conv.inputExpressions; + nConvExpect -= m_conv.representable; + UASSERT(nConvNonRep == nConvExpect, "Inconsistent statistics / conv"); + + // Synthesis statistics + addStat("synt / always blocks considered", m_synt.inputAlways); + addStat("synt / always blocks synthesized", m_synt.synthAlways); + addStat("synt / continuous assignments considered", m_synt.inputAssign); + addStat("synt / continuous assignments synthesized", m_synt.synthAssign); + addStat("synt / non-synthesizable (conv)", m_synt.nonSynConv); + addStat("synt / non-synthesizable (ext write)", m_synt.nonSynExtWrite); + addStat("synt / non-synthesizable (loop)", m_synt.nonSynLoop); + addStat("synt / non-synthesizable (stmt)", m_synt.nonSynStmt); + addStat("synt / non-synthesizable (multidrive)", m_synt.nonSynMultidrive); + addStat("synt / non-synthesizable (array)", m_synt.nonSynArray); + addStat("synt / non-synthesizable (latch)", m_synt.nonSynLatch); + addStat("synt / non-synthesizable (join input)", m_synt.nonSynJoinInput); + addStat("synt / non-synthesizable (false write)", m_synt.nonSynFalseWrite); + addStat("synt / reverted (non-synthesizable)", m_synt.revertNonSyn); + addStat("synt / reverted (multidrive)", m_synt.revertMultidrive); + + VDouble0 nSyntNonSyn; + nSyntNonSyn += m_synt.nonSynConv; + nSyntNonSyn += m_synt.nonSynExtWrite; + nSyntNonSyn += m_synt.nonSynLoop; + nSyntNonSyn += m_synt.nonSynStmt; + nSyntNonSyn += m_synt.nonSynMultidrive; + nSyntNonSyn += m_synt.nonSynArray; + nSyntNonSyn += m_synt.nonSynLatch; + nSyntNonSyn += m_synt.nonSynJoinInput; + nSyntNonSyn += m_synt.nonSynFalseWrite; + VDouble0 nSyntExpect; + nSyntExpect += m_synt.inputAlways; + nSyntExpect += m_synt.inputAssign; + nSyntExpect -= m_synt.synthAlways; + nSyntExpect -= m_synt.synthAssign; + UASSERT(nSyntNonSyn == nSyntExpect, "Inconsistent statistics / synt"); + } +}; ////////////////////////////////////////////////////////////////////////////// // Top level V3DfgContext @@ -248,6 +341,7 @@ public: V3DfgEliminateVarsContext m_eliminateVarsContext{*this, m_label}; V3DfgPeepholeContext m_peepholeContext{*this, m_label}; V3DfgRegularizeContext m_regularizeContext{*this, m_label}; + V3DfgSynthesisContext m_synthContext{*this, m_label}; // Node pattern collector V3DfgPatternStats m_patternStats; diff --git a/src/V3DfgDecomposition.cpp b/src/V3DfgDecomposition.cpp index 214dd13c8..8de979b4b 100644 --- a/src/V3DfgDecomposition.cpp +++ b/src/V3DfgDecomposition.cpp @@ -199,6 +199,7 @@ class ExtractCyclicComponents final { } UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexVar' sub-type"); clonep->setUser(component); + clonep->tmpForp(vtx.tmpForp()); } return *clonep; } diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index de5f5c125..09df3a63f 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -93,12 +93,23 @@ class DfgToAstVisitor final : DfgVisitor { // TYPES using VariableType = std::conditional_t; + struct Assignment final { + FileLine* m_flp; + AstNodeExpr* m_lhsp; + AstNodeExpr* m_rhsp; + Assignment() = delete; + Assignment(FileLine* flp, AstNodeExpr* lhsp, AstNodeExpr* m_rhsp) + : m_flp{flp} + , m_lhsp{lhsp} + , m_rhsp{m_rhsp} {} + }; // STATE - AstModule* const m_modp; // The parent/result module - This is nullptr when T_Scoped V3DfgDfgToAstContext& m_ctx; // The context for stats AstNodeExpr* m_resultp = nullptr; // The result node of the current traversal + std::vector m_assignments; // Assignments to currently rendered variable + std::vector m_defaults; // Default assignments to currently rendered variable // METHODS @@ -147,57 +158,69 @@ class DfgToAstVisitor final : DfgVisitor { return resultp; } - void convertDriver(AstScope* scopep, FileLine* flp, AstNodeExpr* lhsp, DfgVertex* driverp) { - if (!driverp->is()) { - // Base case: assign vertex to current lhs - AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(driverp); - AstAssignW* const assignp = new AstAssignW{flp, lhsp, rhsp}; - lhsp->foreach([flp](AstNode* nodep) { nodep->fileline(flp); }); - if VL_CONSTEXPR_CXX17 (T_Scoped) { - // Add it to the scope holding the target variable - getCombActive(scopep)->addStmtsp(assignp); - } else { - // Add it to the parend module of the DfgGraph - m_modp->addStmtsp(assignp); - } - ++m_ctx.m_resultEquations; - return; - } - + void convertDriver(std::vector& assignments, FileLine* flp, AstNodeExpr* lhsp, + DfgVertex* driverp) { if (DfgSplicePacked* const sPackedp = driverp->cast()) { - // Partial assignment of packed value + // Render defaults first + if (DfgVertex* const defaultp = sPackedp->defaultp()) { + convertDriver(m_defaults, flp, lhsp, defaultp); + } + // Render partial assignments of packed value sPackedp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) { - UASSERT_OBJ(edge.sourcep(), sPackedp, "Should have removed undriven sources"); + DfgVertex* const srcp = edge.sourcep(); + if (srcp == sPackedp->defaultp()) return; // Create Sel FileLine* const dflp = sPackedp->driverFileLine(i); - AstConst* const lsbp = new AstConst{dflp, sPackedp->driverLsb(i)}; - const int width = static_cast(edge.sourcep()->width()); + AstConst* const lsbp = new AstConst{dflp, sPackedp->driverLo(i)}; + const int width = static_cast(srcp->width()); AstSel* const nLhsp = new AstSel{dflp, lhsp->cloneTreePure(false), lsbp, width}; // Convert source - convertDriver(scopep, dflp, nLhsp, edge.sourcep()); - // Delete Sel if not consumed - if (!nLhsp->backp()) VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp); + convertDriver(assignments, dflp, nLhsp, srcp); + // Delete Sel - was cloned + VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp); }); return; } if (DfgSpliceArray* const sArrayp = driverp->cast()) { + UASSERT_OBJ(!sArrayp->defaultp(), flp, "Should not have a default assignment yet"); // Partial assignment of array variable sArrayp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) { - UASSERT_OBJ(edge.sourcep(), sArrayp, "Should have removed undriven sources"); + DfgVertex* const driverp = edge.sourcep(); + UASSERT_OBJ(driverp, sArrayp, "Should have removed undriven sources"); + UASSERT_OBJ(driverp->size() == 1, driverp, "We only handle single elements"); // Create ArraySel FileLine* const dflp = sArrayp->driverFileLine(i); - AstConst* const idxp = new AstConst{dflp, sArrayp->driverIndex(i)}; + AstConst* const idxp = new AstConst{dflp, sArrayp->driverLo(i)}; AstArraySel* const nLhsp = new AstArraySel{dflp, lhsp->cloneTreePure(false), idxp}; // Convert source - convertDriver(scopep, dflp, nLhsp, edge.sourcep()); - // Delete ArraySel if not consumed - if (!nLhsp->backp()) VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp); + if (DfgUnitArray* const uap = driverp->cast()) { + convertDriver(assignments, dflp, nLhsp, uap->srcp()); + } else { + convertDriver(assignments, dflp, nLhsp, driverp); + } + // Delete ArraySel - was cloned + VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp); }); return; } - driverp->v3fatalSrc("Unhandled DfgVertexSplice sub-type"); // LCOV_EXCL_LINE + if (DfgUnitArray* const uap = driverp->cast()) { + // Single element array being assigned a unit array. Needs an ArraySel. + AstConst* const idxp = new AstConst{flp, 0}; + AstArraySel* const nLhsp = new AstArraySel{flp, lhsp->cloneTreePure(false), idxp}; + // Convert source + convertDriver(assignments, flp, nLhsp, uap->srcp()); + // Delete ArraySel - was cloned + VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp); + return; + } + + // Base case: assign vertex to current lhs + AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(driverp); + assignments.emplace_back(flp, lhsp->cloneTreePure(false), rhsp); + ++m_ctx.m_resultEquations; + return; } // VISITORS @@ -245,13 +268,53 @@ class DfgToAstVisitor final : DfgVisitor { // If there is no driver (this vertex is an input to the graph), then nothing to do. if (!vtx.srcp()) continue; + ++m_ctx.m_outputVariables; + // Render variable assignments FileLine* const flp = vtx.driverFileLine() ? vtx.driverFileLine() : vtx.fileline(); - AstScope* const scopep = T_Scoped ? vtx.varScopep()->scopep() : nullptr; AstVarRef* const lhsp = new AstVarRef{flp, getNode(&vtx), VAccess::WRITE}; - convertDriver(scopep, flp, lhsp, vtx.srcp()); - // convetDriver clones and might not use up the original lhsp - if (!lhsp->backp()) VL_DO_DANGLING(lhsp->deleteTree(), lhsp); + convertDriver(m_assignments, flp, lhsp, vtx.srcp()); + // convetDriver always clones lhsp + VL_DO_DANGLING(lhsp->deleteTree(), lhsp); + + if (m_defaults.empty()) { + // If there are no default assignments, render each driver as an AssignW + for (const Assignment& a : m_assignments) { + AstAssignW* const assignp = new AstAssignW{a.m_flp, a.m_lhsp, a.m_rhsp}; + a.m_lhsp->foreach([&a](AstNode* nodep) { nodep->fileline(a.m_flp); }); + if VL_CONSTEXPR_CXX17 (T_Scoped) { + // Add it to the scope holding the target variable + getCombActive(vtx.varScopep()->scopep())->addStmtsp(assignp); + } else { + // Add it to the parent module of the DfgGraph + m_modp->addStmtsp(assignp); + } + } + } else { + ++m_ctx.m_outputVariablesWithDefault; + // If there are default assignments, render all drivers under an AstAlways + AstAlways* const alwaysp + = new AstAlways{vtx.fileline(), VAlwaysKwd::ALWAYS_COMB, nullptr, nullptr}; + if VL_CONSTEXPR_CXX17 (T_Scoped) { + // Add it to the scope holding the target variable + getCombActive(vtx.varScopep()->scopep())->addStmtsp(alwaysp); + } else { + // Add it to the parent module of the DfgGraph + m_modp->addStmtsp(alwaysp); + } + for (const Assignment& a : m_defaults) { + AstAssign* const assignp = new AstAssign{a.m_flp, a.m_lhsp, a.m_rhsp}; + a.m_lhsp->foreach([&a](AstNode* nodep) { nodep->fileline(a.m_flp); }); + alwaysp->addStmtsp(assignp); + } + for (const Assignment& a : m_assignments) { + AstAssign* const assignp = new AstAssign{a.m_flp, a.m_lhsp, a.m_rhsp}; + a.m_lhsp->foreach([&a](AstNode* nodep) { nodep->fileline(a.m_flp); }); + alwaysp->addStmtsp(assignp); + } + } + m_assignments.clear(); + m_defaults.clear(); } } diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index 5f7e34ced..3a12470fb 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -252,7 +252,11 @@ class DataflowOptimize final { V3DfgContext m_ctx; // The context holding values that need to persist across multiple graphs void optimize(DfgGraph& dfg) { - if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "whole-input"); + if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "dfg-in"); + + // Synthesize DfgLogic vertices + V3DfgPasses::synthesize(dfg, m_ctx); + if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "synth"); // Extract the cyclic sub-graphs. We do this because a lot of the optimizations assume a // DAG, and large, mostly acyclic graphs could not be optimized due to the presence of @@ -310,7 +314,7 @@ class DataflowOptimize final { // Merge back under the main DFG (we will convert everything back in one go) dfg.mergeGraphs(std::move(cyclicComponents)); - if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "whole-optimized"); + if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "dfg-out"); } DataflowOptimize(AstNetlist* netlistp, const string& label) diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index a6aad4c37..0ea612634 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -103,7 +103,7 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) { DfgVertex* const sentinelp = reinterpret_cast(&dfg); DfgVertex* workListp = sentinelp; - // Add all unused vertices to the work list. This also allocates all DfgVertex::user. + // Add all unused operation vertices to the work list. This also allocates all DfgVertex::user. for (DfgVertex& vtx : dfg.opVertices()) { if (vtx.hasSinks()) { // This vertex is used. Allocate user, but don't add to work list. @@ -115,6 +115,19 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) { } } + // Also add all unused temporaries created during synthesis + for (DfgVertexVar& vtx : dfg.varVertices()) { + if (!vtx.tmpForp()) continue; + if (vtx.hasSinks() || vtx.hasDfgRefs()) { + // This vertex is used. Allocate user, but don't add to work list. + vtx.setUser(nullptr); + } else { + // This vertex is unused. Add to work list. + vtx.setUser(workListp); + workListp = &vtx; + } + } + // Process the work list while (workListp != sentinelp) { // Pick up the head @@ -123,12 +136,23 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) { workListp = vtxp->getUser(); // Prefetch next item VL_PREFETCH_RW(workListp); + // This item is now off the work list + vtxp->setUser(nullptr); + // DfgLogic should have been synthesized or removed + UASSERT_OBJ(!vtxp->is(), vtxp, "Should not be DfgLogic"); // If used, then nothing to do, so move on if (vtxp->hasSinks()) continue; + // If temporary used in another graph, we need to keep it + if (const DfgVertexVar* const varp = vtxp->cast()) { + UASSERT_OBJ(varp->tmpForp(), varp, "Non-temporary variable should not be visited"); + if (varp->hasDfgRefs()) continue; + } // Add sources of unused vertex to work list vtxp->forEachSource([&](DfgVertex& src) { - // We only remove actual operation vertices in this loop - if (src.is() || src.is()) return; + // We only remove actual operation vertices and synthesis temporaries in this loop + if (src.is()) return; + const DfgVertexVar* const varp = src.cast(); + if (varp && !varp->tmpForp()) return; // If already in work list then nothing to do if (src.getUser()) return; // Actually add to work list. @@ -282,7 +306,7 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) { // Required data types AstNodeDType* const idxDTypep = srcp->dtypep(); - AstNodeDType* const bitDTypep = DfgVertex::dtypeForWidth(1); + AstNodeDType* const bitDTypep = DfgGraph::dtypePacked(1); AstUnpackArrayDType* const tabDTypep = new AstUnpackArrayDType{ flp, bitDTypep, new AstRange{flp, static_cast(nBits - 1), 0}}; v3Global.rootp()->typeTablep()->addTypesp(tabDTypep); @@ -453,8 +477,13 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) { DfgVarPacked* const varp = vtxp->cast(); if (!varp) continue; - // Can't remove if it has external drivers - if (!varp->isDrivenFullyByDfg()) continue; + if (!varp->tmpForp()) { + // Can't remove regular variable if it has external drivers + if (!varp->isDrivenFullyByDfg()) continue; + } else { + // Can't remove partially driven used temporaries + if (!varp->isDrivenFullyByDfg() && varp->hasSinks()) continue; + } // Can't remove if referenced external to the module/netlist if (varp->hasExtRefs()) continue; diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index 98ce5e196..93febfa68 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -38,6 +38,10 @@ std::unique_ptr astToDfg(AstModule&, V3DfgContext&) VL_MT_DISABLED; // Same as above, but for the entire netlist, after V3Scope std::unique_ptr astToDfg(AstNetlist&, V3DfgContext&) VL_MT_DISABLED; +// Synthesize DfgLogic vertices into primitive operations. +// Removes all DfgLogic (even those that were not synthesized). +void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; + // Attempt to make the given cyclic graph into an acyclic, or "less cyclic" // equivalent. If the returned pointer is null, then no improvement was // possible on the input graph. Otherwise the returned graph is an improvement diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index deadbf96f..634009af8 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -131,7 +131,7 @@ class V3DfgPeephole final : public DfgVisitor { // STATE DfgGraph& m_dfg; // The DfgGraph being visited V3DfgPeepholeContext& m_ctx; // The config structure - AstNodeDType* const m_bitDType = DfgVertex::dtypeForWidth(1); // Common, so grab it up front + AstNodeDType* const m_bitDType = DfgGraph::dtypePacked(1); // Common, so grab it up front // Head of work list. Note that we want all next pointers in the list to be non-zero (including // that of the last element). This allows as to do two important things: detect if an element // is in the list by checking for a non-zero next pointer, and easy prefetching without @@ -205,7 +205,7 @@ class V3DfgPeephole final : public DfgVisitor { } // Shorthand - static AstNodeDType* dtypeForWidth(uint32_t width) { return DfgVertex::dtypeForWidth(width); } + static AstNodeDType* dtypePacked(uint32_t width) { return DfgGraph::dtypePacked(width); } // Create a 32-bit DfgConst vertex DfgConst* makeI32(FileLine* flp, uint32_t val) { return new DfgConst{m_dfg, flp, 32, val}; } @@ -365,7 +365,7 @@ class V3DfgPeephole final : public DfgVisitor { // Concatenation dtypes need to be fixed up, other associative nodes preserve // types if VL_CONSTEXPR_CXX17 (std::is_same::value) { - childDtyptp = dtypeForWidth(bp->width() + cp->width()); + childDtyptp = dtypePacked(bp->width() + cp->width()); } Vertex* const childp = make(vtxp->fileline(), childDtyptp, bp, cp); @@ -820,10 +820,10 @@ class V3DfgPeephole final : public DfgVisitor { const uint32_t lSelWidth = width - rSelWidth; // The new Lhs vertex - DfgSel* const newLhsp = make(flp, dtypeForWidth(lSelWidth), lhsp, 0U); + DfgSel* const newLhsp = make(flp, dtypePacked(lSelWidth), lhsp, 0U); // The new Rhs vertex - DfgSel* const newRhsp = make(flp, dtypeForWidth(rSelWidth), rhsp, lsb); + DfgSel* const newRhsp = make(flp, dtypePacked(rSelWidth), rhsp, lsb); // The replacement Concat vertex DfgConcat* const newConcat @@ -912,6 +912,38 @@ class V3DfgPeephole final : public DfgVisitor { } } } + + // Sel from a partial temporary + if (DfgVarPacked* const varp = fromp->cast()) { + if (varp->tmpForp() && varp->srcp()) { + // Must be a splice, otherwise it would have been inlined + DfgSplicePacked* const splicep = varp->srcp()->as(); + + const auto pair = splicep->sourceEdges(); + for (size_t i = 0; i < pair.second; ++i) { + DfgVertex* const driverp = pair.first[i].sourcep(); + // Ignore default, we won't select into that for now ... + if (driverp == splicep->defaultp()) continue; + + const uint32_t dLsb = splicep->driverLo(i); + const uint32_t dMsb = dLsb + driverp->width() - 1; + // If it does not cover the whole searched bit range, move on + if (lsb < dLsb || dMsb < msb) continue; + + // Replace with sel from driver + APPLYING(PUSH_SEL_THROUGH_SPLICE) { + DfgSel* const replacementp = make(vtxp, driverp, lsb - dLsb); + replace(vtxp, replacementp); + // Special case just for this pattern: delete temporary if became unsued + if (!varp->hasSinks() && !varp->hasDfgRefs()) { + addToWorkList(splicep); // So it can be delete itself if unused + VL_DO_DANGLING(varp->unlinkDelete(m_dfg), varp); // Delete it + } + return; + } + } + } + } } //========================================================================= @@ -1179,20 +1211,40 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgArraySel* vtxp) override { - if (DfgConst* const idxp = vtxp->bitp()->cast()) { - if (DfgVarArray* const varp = vtxp->fromp()->cast()) { - if (varp->srcp() && !varp->varp()->isForced()) { - if (DfgSpliceArray* const splicep = varp->srcp()->cast()) { - if (DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT())) { - if (!driverp->is()) { - APPLYING(INLINE_ARRAYSEL) { - replace(vtxp, driverp); - return; - } - } - } - } - } + DfgConst* const idxp = vtxp->bitp()->cast(); + if (!idxp) return; + DfgVarArray* const varp = vtxp->fromp()->cast(); + if (!varp) return; + if (varp->varp()->isForced()) return; + DfgVertex* const srcp = varp->srcp(); + if (!srcp) return; + + if (DfgSpliceArray* const splicep = srcp->cast()) { + DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT()); + if (!driverp) return; + DfgUnitArray* const uap = driverp->cast(); + if (!uap) return; + if (uap->srcp()->is()) return; + // If driven by a variable that had a Driver in DFG, it is partial + if (DfgVertexVar* const dvarp = uap->srcp()->cast()) { + if (dvarp->srcp()) return; + } + APPLYING(INLINE_ARRAYSEL_SPLICE) { + replace(vtxp, uap->srcp()); + return; + } + } + + if (DfgUnitArray* const uap = srcp->cast()) { + UASSERT_OBJ(idxp->toSizeT() == 0, vtxp, "Array index out of range"); + if (uap->srcp()->is()) return; + // If driven by a variable that had a Driver in DFG, it is partial + if (DfgVertexVar* const dvarp = uap->srcp()->cast()) { + if (dvarp->srcp()) return; + } + APPLYING(INLINE_ARRAYSEL_UNIT) { + replace(vtxp, uap->srcp()); + return; } } } @@ -1257,8 +1309,7 @@ class V3DfgPeephole final : public DfgVisitor { if (lSelp->lsb() == rSelp->lsb() + rSelp->width()) { // Two consecutive Sels, make a single Sel. const uint32_t width = lSelp->width() + rSelp->width(); - return make(flp, dtypeForWidth(width), rSelp->fromp(), - rSelp->lsb()); + return make(flp, dtypePacked(width), rSelp->fromp(), rSelp->lsb()); } } return nullptr; diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index e3fe5c3d0..d1e254bc0 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -33,7 +33,8 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_SEL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_UNARY) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL_SPLICE) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL_UNIT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_REDUCTION) \ @@ -47,6 +48,7 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_NOT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_REPLICATE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SHIFTL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SPLICE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_AND_WITH_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_AND_WITH_SELF) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_CONCAT_OF_ADJOINING_SELS) \ diff --git a/src/V3DfgRegularize.cpp b/src/V3DfgRegularize.cpp index 64eef883a..e050e8dc8 100644 --- a/src/V3DfgRegularize.cpp +++ b/src/V3DfgRegularize.cpp @@ -45,15 +45,11 @@ class DfgRegularize final { // Ensure intermediate values used multiple times are written to variables for (DfgVertex& vtx : m_dfg.opVertices()) { const bool needsIntermediateVariable = [&]() { - // Splice vertices represent partial assignments, so they need a variable - // iff and only if they have a non-variable sink. - if (vtx.is()) { - const bool hasNonVarSink - = vtx.findSink([](const DfgVertex& snk) { // - return !snk.is() && !snk.is(); - }); - return hasNonVarSink; - } + // Splice vertices represent partial assignments. The must flow + // into variables, so they should never need a temporary. + if (vtx.is()) return false; + // Smilarly to splice, UnitArray should never need one eitehr + if (vtx.is()) return false; // Operations without multiple sinks need no variables if (!vtx.hasMultipleSinks()) return false; // Array selects need no variables, they are just memory references diff --git a/src/V3DfgSynthesize.cpp b/src/V3DfgSynthesize.cpp new file mode 100644 index 000000000..4f16a52fe --- /dev/null +++ b/src/V3DfgSynthesize.cpp @@ -0,0 +1,1696 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Convert DfgLogic into primitive operations +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2025 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// Synthesize DfgLogic vertices in as a graph, as created by V3DfgAstToDfg +// into primitive vertices. +// +//************************************************************************* + +#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT + +#include "V3Cfg.h" +#include "V3Const.h" +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3EmitV.h" +#include "V3Os.h" + +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +namespace { + +// Create a DfgVertex out of a AstNodeExpr. For most AstNodeExpr subtypes, this can be done +// automatically. For the few special cases, we provide specializations below +template +T_Vertex* makeVertex(const T_Node* nodep, DfgGraph& dfg) { + return new T_Vertex{dfg, nodep->fileline(), DfgGraph::toDfgDType(nodep->dtypep())}; +} + +template <> +DfgArraySel* makeVertex(const AstArraySel* nodep, DfgGraph& dfg) { + // Some earlier passes create malformed ArraySels, just bail on those... + // See t_bitsel_wire_array_bad + if (VN_IS(nodep->fromp(), Const)) return nullptr; + if (!VN_IS(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) return nullptr; + return new DfgArraySel{dfg, nodep->fileline(), DfgGraph::toDfgDType(nodep->dtypep())}; +} + +} // namespace + +// Visitor that can convert Ast statements and expressions in Dfg vertices +template +class AstToDfgConverter final : public VNVisitor { + // NODE STATE + // AstNodeExpr/AstVar/AstVarScope::user2p -> DfgVertex* for this Node + // AstVar::user3() -> int temporary counter for variable + const VNUser3InUse m_user3InUse; + + // TYPES + using Variable = std::conditional_t; + + // STATE + DfgGraph& m_dfg; // The graph being built + V3DfgSynthesisContext& m_ctx; // The context for stats + + // Current logic vertex we are synthesizing + DfgLogic* m_logicp = nullptr; + // Variable updates produced by currently converted statement. This almost + // always have a single element, so a vector is ok + std::vector>* m_updatesp = nullptr; + + bool m_foundUnhandled = false; // Found node not implemented as DFG or not implemented 'visit' + bool m_converting = false; // We are trying to convert some logic at the moment + + // METHODS + static Variable* getTarget(const AstVarRef* refp) { + // TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works + if VL_CONSTEXPR_CXX17 (T_Scoped) { + return reinterpret_cast(refp->varScopep()); + } else { + return reinterpret_cast(refp->varp()); + } + } + + // Allocate a new non-variable vertex, add it to the currently synthesized logic + template + Vertex* make(Args&&... args) { + static_assert(!std::is_base_of::value, "Do not use for variables"); + static_assert(std::is_base_of::value, "'Vertex' must be a 'DfgVertex'"); + Vertex* const vtxp = new Vertex{m_dfg, std::forward(args)...}; + m_logicp->synth().emplace_back(vtxp); + return vtxp; + } + + // Returns true if the expression cannot (or should not) be represented by DFG + bool unhandled(AstNodeExpr* nodep) { + // Short-circuiting if something was already unhandled + if (!m_foundUnhandled) { + // Impure nodes cannot be represented + if (!nodep->isPure()) { + m_foundUnhandled = true; + ++m_ctx.m_conv.nonRepImpure; + } + // Check node has supported dtype + if (!DfgGraph::isSupported(nodep->dtypep())) { + m_foundUnhandled = true; + ++m_ctx.m_conv.nonRepDType; + } + } + return m_foundUnhandled; + } + + bool isSupported(const AstVarRef* nodep) { + // Cannot represent cross module references + if (nodep->classOrPackagep()) return false; + // Check target + return DfgGraph::isSupported(getTarget(nodep)); + } + + // Given an RValue expression, return the equivalent Vertex, or nullptr if not representable. + DfgVertex* convertRValue(AstNodeExpr* nodep) { + UASSERT_OBJ(!m_converting, nodep, "'convertingRValue' should not be called recursively"); + VL_RESTORER(m_converting); + VL_RESTORER(m_foundUnhandled); + m_converting = true; + m_foundUnhandled = false; + + // Convert the expression + iterate(nodep); + + // If falied to convert, return nullptr + if (m_foundUnhandled) return nullptr; + + // Traversal set user2p to the equivalent vertex + DfgVertex* const vtxp = nodep->user2u().to(); + UASSERT_OBJ(vtxp, nodep, "Missing Dfg vertex after covnersion"); + return vtxp; + } + + // Given an LValue expression, return the splice node that writes the + // destination, together with the index to use for splicing in the value. + // Returns {nullptr, 0}, if the given LValue expression is not supported. + std::pair convertLValue(AstNodeExpr* nodep) { + if (const AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) { + if (!isSupported(vrefp)) { + ++m_ctx.m_conv.nonRepLValue; + return {nullptr, 0}; + } + + // Get (or create a new) temporary for this variable + DfgVertexVar* const vtxp = [&]() -> DfgVertexVar* { + // The variable being assigned + Variable* const tgtp = getTarget(vrefp); + + // Find existing one, if any + for (const auto& pair : *m_updatesp) { + if (pair.first == tgtp) return pair.second; + } + + // Create new one + DfgVertexVar* const newp = createTmp(m_dfg, *m_logicp, tgtp, "SynthAssign"); + m_updatesp->emplace_back(tgtp, newp); + + // Create the Splice driver for the new temporary + if (newp->is()) { + newp->srcp(make(newp->fileline(), newp->dtypep())); + } else if (newp->is()) { + newp->srcp(make(newp->fileline(), newp->dtypep())); + } else { + nodep->v3fatalSrc("Unhandled DfgVertexVar sub-type"); // LCOV_EXCL_LINE + } + + // Use new temporary + return newp; + }(); + + // Return the Splice driver + return {vtxp->srcp()->as(), 0}; + } + + if (const AstSel* selp = VN_CAST(nodep, Sel)) { + // Only handle constant selects + const AstConst* const lsbp = VN_CAST(selp->lsbp(), Const); + if (!lsbp) { + ++m_ctx.m_conv.nonRepLValue; + return {nullptr, 0}; + } + uint32_t lsb = lsbp->toUInt(); + + // Convert the 'fromp' sub-expression + const auto pair = convertLValue(selp->fromp()); + if (!pair.first) return {nullptr, 0}; + DfgSplicePacked* const splicep = pair.first->template as(); + // Adjust index. + lsb += pair.second; + + // AstSel doesn't change type kind (array vs packed), so we can use + // the existing splice driver with adjusted lsb + return {splicep, lsb}; + } + + if (const AstArraySel* const aselp = VN_CAST(nodep, ArraySel)) { + // Only handle constant selects + const AstConst* const indexp = VN_CAST(aselp->bitp(), Const); + if (!indexp) { + ++m_ctx.m_conv.nonRepLValue; + return {nullptr, 0}; + } + uint32_t index = indexp->toUInt(); + + // Convert the 'fromp' sub-expression + const auto pair = convertLValue(aselp->fromp()); + if (!pair.first) return {nullptr, 0}; + DfgSpliceArray* const splicep = pair.first->template as(); + // Adjust index. Note pair.second is always 0, but we might handle array slices later.. + index += pair.second; + + // Ensure the Splice driver exists for this element + if (!splicep->driverAt(index)) { + FileLine* const flp = nodep->fileline(); + AstNodeDType* const dtypep = DfgGraph::toDfgDType(nodep->dtypep()); + if (VN_IS(dtypep, BasicDType)) { + DfgSplicePacked* const newp = make(flp, dtypep); + AstNodeDType* const uaDtypep = DfgGraph::dtypeArray(dtypep, 1); + DfgUnitArray* const uap = make(flp, uaDtypep); + uap->srcp(newp); + splicep->addDriver(flp, index, uap); + } else if (VN_IS(dtypep, UnpackArrayDType)) { + DfgSpliceArray* const newp = make(flp, dtypep); + splicep->addDriver(flp, index, newp); + } else { + nodep->v3fatalSrc("Unhandled AstNodeDType sub-type"); // LCOV_EXCL_LINE + } + } + + // Return the splice driver + DfgVertex* driverp = splicep->driverAt(index); + if (DfgUnitArray* const uap = driverp->cast()) driverp = uap->srcp(); + return {driverp->as(), 0}; + } + + ++m_ctx.m_conv.nonRepLValue; + return {nullptr, 0}; + } + + // Given the LHS of an assignment, and the vertex representing the RHS, + // connect up the RHS to drive the targets. + // Returns true on success, false if the LHS is not representable. + bool convertAssignment(FileLine* flp, AstNodeExpr* lhsp, DfgVertex* vtxp) { + // Represents a DFG assignment contributed by the AST assignment with the above 'lhsp'. + // There might be multiple of these if 'lhsp' is a concatenation. + struct Assignment final { + DfgVertexSplice* m_lhsp; + uint32_t m_idx; + DfgVertex* m_rhsp; + Assignment() = delete; + Assignment(DfgVertexSplice* lhsp, uint32_t idx, DfgVertex* rhsp) + : m_lhsp{lhsp} + , m_idx{idx} + , m_rhsp{rhsp} {} + }; + + // Convert each concatenation LHS separately, gather all assignments + // we need to do into 'assignments', return true if all LValues + // converted successfully. + std::vector assignments; + const std::function convertAllLValues + = [&](AstNodeExpr* lhsp, DfgVertex* vtxp) -> bool { + // Simplify the LHS, to get rid of things like SEL(CONCAT(_, _), _) + lhsp = VN_AS(V3Const::constifyExpensiveEdit(lhsp), NodeExpr); + + // Concatenation on the LHS, convert each parts + if (AstConcat* const concatp = VN_CAST(lhsp, Concat)) { + AstNodeExpr* const cLhsp = concatp->lhsp(); + AstNodeExpr* const cRhsp = concatp->rhsp(); + // Convert Left of concat + FileLine* const lFlp = cLhsp->fileline(); + AstNodeDType* const lDtp = DfgGraph::toDfgDType(cLhsp->dtypep()); + DfgSel* const lVtxp = make(lFlp, lDtp); + lVtxp->fromp(vtxp); + lVtxp->lsb(cRhsp->width()); + if (!convertAllLValues(cLhsp, lVtxp)) return false; + // Convert Rigth of concat + FileLine* const rFlp = cRhsp->fileline(); + AstNodeDType* const rDtp = DfgGraph::toDfgDType(cRhsp->dtypep()); + DfgSel* const rVtxp = make(rFlp, rDtp); + rVtxp->fromp(vtxp); + rVtxp->lsb(0); + return convertAllLValues(cRhsp, rVtxp); + } + + // Non-concatenation, convert the LValue + const auto pair = convertLValue(lhsp); + if (!pair.first) return false; + assignments.emplace_back(pair.first, pair.second, vtxp); + return true; + }; + + // Convert the given LHS assignment, give up if any LValues failed to convert + if (!convertAllLValues(lhsp, vtxp)) return false; + + // All successful, connect the drivers + for (const Assignment& item : assignments) { + if (DfgSplicePacked* const spp = item.m_lhsp->template cast()) { + spp->addDriver(flp, item.m_idx, item.m_rhsp); + } else if (DfgSpliceArray* const sap = item.m_lhsp->template cast()) { + AstUnpackArrayDType* const lDtp = VN_AS(sap->dtypep(), UnpackArrayDType); + const AstNodeDType* const lEleDtp = lDtp->subDTypep(); + AstNodeDType* const rDtp = item.m_rhsp->dtypep(); + if (lEleDtp->isSame(rDtp)) { + // RHS is assigning an element of this array. Need a DfgUnitArray adapter. + AstNodeDType* const uaDtp = DfgGraph::dtypeArray(rDtp, 1); + DfgUnitArray* const uap = make(flp, uaDtp); + uap->srcp(item.m_rhsp); + sap->addDriver(flp, item.m_idx, uap); + } else { + // RHS is assigning an array (or array slice). Should be the same element type. + const AstNodeDType* const rEleDtp = VN_AS(rDtp, UnpackArrayDType)->subDTypep(); + UASSERT_OBJ(lEleDtp->isSame(rEleDtp), item.m_rhsp, "Mismatched array types"); + sap->addDriver(flp, item.m_idx, item.m_rhsp); + } + } else { + item.m_lhsp->v3fatalSrc("Unhandled DfgVertexSplice sub-type"); // LCOV_EXCL_LINE + } + } + + return true; + } + + // VISITORS + + // Unhandled node + void visit(AstNode* nodep) override { + if (!m_foundUnhandled && m_converting) ++m_ctx.m_conv.nonRepUnknown; + m_foundUnhandled = true; + } + + // Expressions - mostly auto generated, but a few special ones + void visit(AstVarRef* nodep) override { + UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); + UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); + if (unhandled(nodep)) return; + // This visit method is only called on RValues, where only read refs are supported + if (!nodep->access().isReadOnly() || !isSupported(nodep)) { + m_foundUnhandled = true; + ++m_ctx.m_conv.nonRepVarRef; + return; + } + + // Variable should have been bound before starting conversion + DfgVertex* const vtxp = getTarget(nodep)->user2u().template to(); + UASSERT_OBJ(vtxp, nodep, "Referenced variable has no associated DfgVertexVar"); + nodep->user2p(vtxp); + } + void visit(AstConst* nodep) override { + UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); + UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); + if (unhandled(nodep)) return; + DfgVertex* const vtxp = make(nodep->fileline(), nodep->num()); + nodep->user2p(vtxp); + } + void visit(AstSel* nodep) override { + UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); + UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); + if (unhandled(nodep)) return; + + iterate(nodep->fromp()); + if (m_foundUnhandled) return; + + FileLine* const flp = nodep->fileline(); + DfgVertex* vtxp = nullptr; + if (const AstConst* const constp = VN_CAST(nodep->lsbp(), Const)) { + DfgSel* const selp = make(flp, DfgGraph::toDfgDType(nodep->dtypep())); + selp->fromp(nodep->fromp()->user2u().to()); + selp->lsb(constp->toUInt()); + vtxp = selp; + } else { + iterate(nodep->lsbp()); + if (m_foundUnhandled) return; + DfgMux* const muxp = make(flp, DfgGraph::toDfgDType(nodep->dtypep())); + muxp->fromp(nodep->fromp()->user2u().to()); + muxp->lsbp(nodep->lsbp()->user2u().to()); + vtxp = muxp; + } + nodep->user2p(vtxp); + } +// The rest of the visit methods for expressions are generated by 'astgen' +#include "V3Dfg__gen_ast_to_dfg.h" + +public: + // PUBLIC METHODS + + // Create a new temporary variable capable of holding 'varp' + static DfgVertexVar* createTmp(DfgGraph& dfg, DfgLogic& logic, Variable* varp, + const std::string& prefix) { + AstVar* const astVarp = T_Scoped ? reinterpret_cast(varp)->varp() + : reinterpret_cast(varp); + const std::string prfx = prefix + "_" + astVarp->name(); + const std::string name = dfg.makeUniqueName(prfx, astVarp->user3Inc()); + AstNodeDType* const dtypep = DfgGraph::toDfgDType(astVarp->dtypep()); + AstScope* const scp = T_Scoped ? reinterpret_cast(varp)->scopep() : nullptr; + DfgVertexVar* const vtxp = dfg.makeNewVar(astVarp->fileline(), name, dtypep, scp); + logic.synth().emplace_back(vtxp); + vtxp->varp()->isInternal(true); + vtxp->tmpForp(varp); + return vtxp; + } + + // Convert AstAssign to Dfg, return true if successful. + // Fills 'updates' with bindings for assigned variables. + bool convert(std::vector>& updates, DfgLogic& vtx, + AstAssign* nodep) { + UASSERT_OBJ(updates.empty(), nodep, "'updates' should be empty"); + VL_RESTORER(m_updatesp); + VL_RESTORER(m_logicp); + m_updatesp = &updates; + m_logicp = &vtx; + // Assignment with timing control shouldn't make it this far + UASSERT_OBJ(!nodep->timingControlp(), nodep, "Shouldn't make it this far"); + // Convert it + ++m_ctx.m_conv.inputAssignments; + AstNodeExpr* const lhsp = nodep->lhsp(); + AstNodeExpr* const rhsp = nodep->rhsp(); + // Check data types are compatible. + if (!DfgGraph::isSupported(lhsp->dtypep()) || !DfgGraph::isSupported(rhsp->dtypep())) { + ++m_ctx.m_conv.nonRepDType; + return false; + } + // For now, only direct array assignment is supported (e.g. a = b, but not a = _ ? b : c) + if (VN_IS(rhsp->dtypep()->skipRefp(), UnpackArrayDType) && !VN_IS(rhsp, VarRef)) { + ++m_ctx.m_conv.nonRepDType; + return false; + } + // Widths should match at this point + UASSERT_OBJ(lhsp->width() == rhsp->width(), nodep, "Mismatched width reached DFG"); + // Convert the RHS expression + DfgVertex* const rVtxp = convertRValue(rhsp); + if (!rVtxp) return false; + // Connect the RHS vertex to the LHS targets + const bool success = convertAssignment(nodep->fileline(), lhsp, rVtxp); + if (success) ++m_ctx.m_conv.representable; + return success; + } + + // Convert RValue expression to Dfg. Returns nullptr if failed. + DfgVertex* convert(DfgLogic& vtx, AstNodeExpr* nodep) { + VL_RESTORER(m_logicp); + m_logicp = &vtx; + // Convert it + ++m_ctx.m_conv.inputExpressions; + DfgVertex* const vtxp = convertRValue(nodep); + if (vtxp) ++m_ctx.m_conv.representable; + return vtxp; + } + + // CONSTRUCTOR + AstToDfgConverter(DfgGraph& dfg, V3DfgSynthesisContext& ctx) + : m_dfg{dfg} + , m_ctx{ctx} {} +}; + +// For debugging, we can stop synthesizing after a certain number of vertices. +// for this we need a global counter (inside the template makes multiple copies) +static size_t s_dfgSynthDebugCount = 0; +// The number of vertices we stop after can be passed in through the environment +// you can then use a bisection search over this value and look at the dumps +// produced with the lowest failing value +static const size_t s_dfgSynthDebugLimit + = std::stoull(V3Os::getenvStr("VERILATOR_DFG_SYNTH_DEBUG", "0")); + +template +class AstToDfgSynthesize final { + // NODE STATE + // AstNodeExpr/AstVar/AstVarScope::user2p -> DfgVertex* for this Node + + // TYPES + using Variable = std::conditional_t; + using SymTab = std::unordered_map; + + // Represents a [potentially partial] driver of a variable + struct Driver final { + FileLine* m_flp = nullptr; // Location of driver in source + uint32_t m_lo = 0; // Low index of driven range (internal, not Verilog) + uint32_t m_hi = 0; // High index of driven range (internal, not Verilog) + DfgVertex* m_vtxp = nullptr; // Driving vertex + + Driver() = default; + Driver(FileLine* flp, uint32_t lo, DfgVertex* vtxp) + : m_flp{flp} + , m_lo{lo} + , m_hi{lo + vtxp->size() - 1} + , m_vtxp{vtxp} {} + operator bool() const { return m_vtxp != nullptr; } + + bool operator<(const Driver& other) const { + if (m_lo != other.m_lo) return m_lo < other.m_lo; + if (m_hi != other.m_hi) return m_hi < other.m_hi; + return m_flp->operatorCompare(*other.m_flp) < 0; + } + + bool operator<=(const Driver& other) const { return !(other < *this); } + }; + + // STATE + DfgGraph& m_dfg; // The graph being built + V3DfgSynthesisContext& m_ctx; // The context for stats + AstToDfgConverter m_converter; // The convert instance to use for each construct + DfgLogic* m_logicp = nullptr; // Current logic vertex we are synthesizing + + // Some debug aid: We stop after synthesizing s_dfgSynthDebugLimit vertices (if non-zero). + // This is the problematic logic (last one we synthesize), assuming a bisection search + // over s_dfgSynthDebugLimit. + DfgLogic* m_debugLogicp = nullptr; + // Source (upstream) cone of outputs of m_debugLogicp + std::unique_ptr> m_debugOSrcConep{nullptr}; + + // METHODS + + // Dump current graph for debugging ... + void debugDump(const char* name) { + // If we have the debugged logic, compute the vertices feeding its outputs + if (VL_UNLIKELY(m_debugLogicp)) { + std::vector outputs; + m_debugLogicp->forEachSink([&outputs](const DfgVertex& v) { // + outputs.emplace_back(v.singleSink()->as()); + }); + m_debugOSrcConep = m_dfg.sourceCone(outputs); + } + + if (VL_UNLIKELY(dumpDfgLevel() >= 9 || m_debugOSrcConep)) { + const auto label = m_ctx.prefix() + name; + m_dfg.dumpDotFilePrefixed(label); + if (m_debugOSrcConep) { + // Dump only the subgraph involving the inputs and outputs of the bad vertex + m_dfg.dumpDotFilePrefixed(label + "-min", [&](const DfgVertex& v) -> bool { + return m_debugOSrcConep->count(&v); + }); + } + } + } + + static AstVar* getAstVar(Variable* vp) { + // TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works + if VL_CONSTEXPR_CXX17 (T_Scoped) { + return reinterpret_cast(vp)->varp(); + } else { + return reinterpret_cast(vp); + } + } + + // Allocate a new non-variable vertex, add it to the currently synthesized logic + template + Vertex* make(Args&&... args) { + static_assert(!std::is_base_of::value, "Do not use for variables"); + static_assert(std::is_base_of::value, "'Vertex' must be a 'DfgVertex'"); + Vertex* const vtxp = new Vertex{m_dfg, std::forward(args)...}; + if (m_logicp) m_logicp->synth().emplace_back(vtxp); + return vtxp; + } + + // Gather all drivers of a resolved variable + static std::pair, DfgVertex*> gatherDrivers(const DfgVertexSplice* vtxp) { + // Collect them all, check if they are sorted + std::vector drivers; + drivers.reserve(vtxp->arity()); + DfgVertex* const defaultp = vtxp->defaultp(); + bool sorted = true; + vtxp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) { + DfgVertex* const driverp = edge.sourcep(); + UASSERT_OBJ(driverp, vtxp, "Should not have created undriven sources"); + // Ignore default driver + if (driverp == defaultp) return; + // Collect the driver + drivers.emplace_back(vtxp->driverFileLine(i), vtxp->driverLo(i), driverp); + // Check if drivers are sorted - most often they are + const size_t n = drivers.size(); + if (n >= 2 && drivers[n - 1] < drivers[n - 2]) sorted = false; + }); + + // Sort if unsorted + if (!sorted) std::stable_sort(drivers.begin(), drivers.end()); + + // Done + return {std::move(drivers), defaultp}; + } + + // Gather all synthesized drivers of an unresolved variable + static std::vector gatherDriversUnresolved(const DfgUnresolved* vtxp) { + std::vector drivers; + drivers.reserve(vtxp->arity()); + + // For better locations in error reporting, we unpick concatenations + // which are sometimes introduced by combinint assignments in V3Const. + const std::function gather + = [&](FileLine* flp, uint32_t lo, DfgVertex* vtxp) -> void { + if (DfgConcat* const concatp = vtxp->cast()) { + DfgVertex* const rhsp = concatp->rhsp(); + gather(rhsp->fileline(), lo, rhsp); + DfgVertex* const lhsp = concatp->lhsp(); + gather(lhsp->fileline(), lo + rhsp->width(), lhsp); + return; + } + drivers.emplace_back(flp, lo, vtxp); + }; + + // Gather all synthesized drivers + vtxp->forEachSource([&](const DfgVertex& src) { + // Can ignore the original DfgLogic + if (src.is()) return; + + // Synthesized drivers must be a splice at this point + const DfgVertexSplice* const splicep = src.as(); + splicep->forEachSourceEdge([&](const DfgEdge& edge, size_t i) { + DfgVertex* const driverp = edge.sourcep(); + UASSERT_OBJ(driverp, splicep, "Should not have created undriven sources"); + // Ignore the default driver, it's the feedback in circular logic + if (driverp == splicep->defaultp()) return; + // Collect the driver + gather(splicep->driverFileLine(i), splicep->driverLo(i), driverp); + }); + }); + + // Sort the drivers + std::stable_sort(drivers.begin(), drivers.end()); + + // Done + return drivers; + } + + // Given two drivers, combine the driven sub-ranges into the first one if + // possible. First bool returned indicates successfully combined and there + // are no multi-driven bits. Second bool returned indicates we warned + // already about multi-driven bits. + std::pair combineDrivers(DfgVertexVar& var, const std::string& sub, // + Driver& a, const Driver& b) { + // We can only combine array drivers ... + if (a.m_vtxp->isPacked()) return {false, false}; + // ... that both drive a single element ... + if (a.m_lo != b.m_lo) return {false, false}; + const DfgUnitArray* const aUap = a.m_vtxp->template cast(); + if (!aUap) return {false, false}; + const DfgUnitArray* const bUap = b.m_vtxp->template cast(); + if (!bUap) return {false, false}; + // ... and are themeselves partial + const DfgSplicePacked* const aSp = aUap->srcp()->template cast(); + if (!aSp) return {false, false}; + const DfgSplicePacked* const bSp = bUap->srcp()->template cast(); + if (!bSp) return {false, false}; + UASSERT_OBJ(aSp->dtypep()->isSame(bSp->dtypep()), &var, "DTypes should match"); + + // Gather drivers of a + std::vector aDrivers; + DfgVertex* aDefaultp = nullptr; + std::tie(aDrivers, aDefaultp) = gatherDrivers(aSp); + UASSERT_OBJ(!aDefaultp, aSp, "Should not have default driver here"); + + // Gather drivers of b + std::vector bDrivers; + DfgVertex* bDefaultp = nullptr; + std::tie(bDrivers, bDefaultp) = gatherDrivers(bSp); + UASSERT_OBJ(!bDefaultp, bSp, "Should not have default driver here"); + + // Merge them + std::vector abDrivers; + abDrivers.reserve(aDrivers.size() + bDrivers.size()); + std::merge( // + aDrivers.begin(), aDrivers.end(), // + bDrivers.begin(), bDrivers.end(), // + std::back_inserter(abDrivers) // + ); + + // Attempt to resolve them + if (!normalizeDrivers(var, abDrivers, sub + "[" + std::to_string(a.m_lo) + "]")) { + return {false, true}; + } + + // Successfully resolved. Needs a new splice and unit. + FileLine* const flp = var.fileline(); + DfgSplicePacked* const splicep = make(flp, aSp->dtypep()); + for (const Driver& d : abDrivers) splicep->addDriver(d.m_flp, d.m_lo, d.m_vtxp); + DfgUnitArray* const uap = make(flp, aUap->dtypep()); + uap->srcp(splicep); + a.m_vtxp = uap; + return {true, false}; + } + + // Combine and coalesce the given drivers. + // Returns true iff no multi-driven bits are present. + bool normalizeDrivers(DfgVertexVar& var, std::vector& drivers, + const std::string& sub = "") { + if (drivers.empty()) return true; + + // What type of values are we combining + const bool isPacked = drivers[0].m_vtxp->isPacked(); + + // Found a multidriven part ? + bool multiDriven = false; + + // Iterate through the sorted drivers. Index 'i' is the driver we are + // resolving driver 'j' agains, and if required, we merge 'j' into 'i'. + size_t i = 0; + for (size_t j = 1; j < drivers.size();) { + UASSERT_OBJ(i < j, &var, "Invalid iteration"); + Driver& iD = drivers[i]; + Driver& jD = drivers[j]; + + // If 'j' was moved, step forward + if (!jD) { + ++j; + continue; + } + // If 'i' was moved, move 'j' in it's place + if (!iD) { + iD = jD; + jD = Driver{}; + ++j; + continue; + } + + // We have 2 valid drivers now + UASSERT_OBJ(iD <= jD, &var, "Should always be sorted"); + UASSERT_OBJ(jD.m_vtxp->isPacked() == isPacked, &var, "Mixed type drivers"); + + // If no overlap, consider next pair + if (iD.m_hi < jD.m_lo) { + ++i; + if (i == j) ++j; + continue; + } + + // There is an overlap. Attempt to combine them. + bool combined = false; + bool warned = false; + std::tie(combined, warned) = combineDrivers(var, sub, iD, jD); + + // If sucessfully combined, 'j' is no longer needed, it was combined into 'i' + if (combined) { + jD = Driver{}; + ++j; + continue; + } + + // Found overlap that cannot be resolved + multiDriven = true; + // Compare next driver + ++j; + + // Do not warn again if we warned during 'combineDrivers' + if (warned) continue; + + // The variable to warn on + AstNode* const nodep = var.tmpForp() ? var.tmpForp() : var.nodep(); + Variable* const varp = reinterpret_cast(nodep); + + // Loop index often abused, so suppress + if (getAstVar(varp)->isUsedLoopIdx()) continue; + + // Warn the user now + const std::string lo = std::to_string(jD.m_lo); + const std::string hi = std::to_string(std::min(iD.m_hi, jD.m_hi)); + const std::string kind = isPacked ? "Bit" : "Element"; + const std::string part = hi == lo ? (" [" + lo + "]") : ("s [" + hi + ":" + lo + "]"); + + varp->v3warn( // + MULTIDRIVEN, // + kind << part << " of signal '" << varp->prettyName() << sub << "'" + << " have multiple combinational drivers." + << " This can cause performance degradation.\n" + << iD.m_flp->warnOther() << "... Location of offending driver\n" + << iD.m_flp->warnContextPrimary() << '\n' + << jD.m_flp->warnOther() << "... Location of offending driver\n" + << jD.m_flp->warnContextSecondary()); + } + // Rightsize vector + drivers.resize(i + 1); + + // Coalesce adjacent drivers + if (!multiDriven && isPacked) coalesceDrivers(drivers); + + return !multiDriven; + } + + // Coalesce adjacent drivers into single ones + void coalesceDrivers(std::vector& drivers) { + UASSERT(!drivers.empty(), "Can't coalesce 0 drivers"); + UASSERT_OBJ(drivers[0].m_vtxp->isPacked(), drivers[0].m_vtxp, "Can only coalesce packed"); + + size_t i = 0; + for (size_t j = 1; j < drivers.size();) { + UASSERT(i < j, "Invalid iteration"); + Driver& iD = drivers[i]; + Driver& jD = drivers[j]; + + // If 'j' was moved, step forward + if (!jD) { + ++j; + continue; + } + // If 'i' was moved, move 'j' in it's place + if (!iD) { + iD = jD; + jD = Driver{}; + ++j; + continue; + } + + // We have 2 valid drivers now + UASSERT(iD <= jD, "Should always be sorted"); + + // If not adjacent, move on + if (iD.m_hi + 1 != jD.m_lo) { + ++i; + if (i == j) ++j; + continue; + } + + // Coalesce Adjacent ranges, + const auto dtypep = DfgGraph::dtypePacked(iD.m_vtxp->width() + jD.m_vtxp->width()); + DfgConcat* const concatp = make(iD.m_flp, dtypep); + concatp->rhsp(iD.m_vtxp); + concatp->lhsp(jD.m_vtxp); + iD.m_vtxp = concatp; + iD.m_hi = jD.m_hi; + jD = Driver{}; + + // Consider next driver + ++j; + } + // Rightsize vector + drivers.resize(i + 1); + } + + // Make a new splice with the given drivers + DfgVertexSplice* makeSplice(DfgVertexVar& var, const std::vector& newDrivers) { + UASSERT_OBJ(!newDrivers.empty(), &var, "'makeSplice' called with no new drivers"); + // Create new driver + DfgVertexSplice* splicep = nullptr; + if (var.is()) { + splicep = make(var.fileline(), var.dtypep()); + } else if (var.is()) { + splicep = make(var.fileline(), var.dtypep()); + } else { + var.v3fatalSrc("Unhandled DfgVertexVar sub-type"); // LCOV_EXCL_LINE + } + for (const Driver& d : newDrivers) splicep->addDriver(d.m_flp, d.m_lo, d.m_vtxp); + return splicep; + } + + // If any written variables are forced or otherwise udpated from outside, + // we generally cannot synthesie the construct, as we will likely need to + // introduce intermediate values that would not be updated. + static bool hasExternallyWrittenVariable(DfgLogic& vtx) { + return vtx.findSink([](const DfgVertex& sink) -> bool { + // 'sink' is a splice (for which 'vtxp' is an unresolved driver), + // which drives the target variable. + const DfgVertexVar* varp = sink.singleSink()->as(); + if (varp->hasXRefs()) return true; // Target of a hierarchical reference + const AstVar* const astVarp = varp->varp(); + if (astVarp->isForced()) return true; // Forced + if (astVarp->isSigPublic()) return true; // Public + return false; + }); + } + + // Initialzie input symbol table of entry BasicBlock + void initializeEntrySymbolTable(SymTab& iSymTab) { + m_logicp->forEachSource([&](DfgVertex& src) { + DfgVertexVar* const vvp = src.as(); + Variable* const varp = reinterpret_cast(vvp->nodep()); + iSymTab[varp] = vvp; + }); + } + + // Join variable drivers across a control flow confluence (insert muxes ...) + DfgVertexVar* joinDrivers(Variable* varp, DfgVertex* predicatep, // + DfgVertexVar* thenp, DfgVertexVar* elsep) { + UASSERT_OBJ(!predicatep->is(), predicatep, "joinDrivers with cons predicate"); + + AstNode* const thenVarp = thenp->tmpForp() ? thenp->tmpForp() : thenp->nodep(); + AstNode* const elseVarp = elsep->tmpForp() ? elsep->tmpForp() : elsep->nodep(); + UASSERT_OBJ(thenVarp == elseVarp, varp, "Attempting to join unrelated variables"); + + // If both bindings are the the same (variable not updated through either path), + // then there is nothing to do, canuse the same binding + if (thenp == elsep) return thenp; + + // We can't join the input variable just yet, so bail + if (thenp->nodep() == varp) { + ++m_ctx.m_synt.nonSynJoinInput; + return nullptr; + } + if (elsep->nodep() == varp) { + ++m_ctx.m_synt.nonSynJoinInput; + return nullptr; + } + + // Can't do arrays yet + if (VN_IS(thenp->dtypep(), UnpackArrayDType)) { + ++m_ctx.m_synt.nonSynArray; + return nullptr; + } + + // Gather drivers of 'thenp' - only if 'thenp' is not an input to the synthesized block + std::vector tDrivers; + DfgVertex* tDefaultp = nullptr; + std::tie(tDrivers, tDefaultp) = gatherDrivers(thenp->srcp()->as()); + + // Gather drivers of 'elsep' - only if 'thenp' is not an input to the synthesized block + std::vector eDrivers; + DfgVertex* eDefaultp = nullptr; + std::tie(eDrivers, eDefaultp) = gatherDrivers(elsep->srcp()->as()); + + // Default drivers should be the same or not present on either + UASSERT_OBJ(tDefaultp == eDefaultp, varp, "Different default drivers"); + + // Location to use for the join vertices + FileLine* const flp = predicatep->fileline(); + + // Create a fresh temporary for the joined value + DfgVertexVar* const joinp = m_converter.createTmp(m_dfg, *m_logicp, varp, "SynthJoin"); + DfgVertexSplice* const joinSplicep = make(flp, joinp->dtypep()); + joinp->srcp(joinSplicep); + + // If both paths are fully driven, just create a simple conditional + if (tDrivers.size() == 1 // + && tDrivers[0].m_lo == 0 // + && tDrivers[0].m_hi == thenp->width() - 1 // + && eDrivers.size() == 1 // + && eDrivers[0].m_lo == 0 // + && eDrivers[0].m_hi == elsep->width() - 1) { + UASSERT_OBJ(!tDefaultp, varp, "Fully driven variable have default driver"); + + DfgCond* const condp = make(flp, joinp->dtypep()); + condp->condp(predicatep); + condp->thenp(thenp); + condp->elsep(elsep); + joinSplicep->addDriver(tDrivers[0].m_flp, 0, condp); + + // Done + return joinp; + } + + // Otherwise we need to merge them part by part + + // If different bits are driven, then some might not have been assigned.. Latch? + if (tDrivers.size() != eDrivers.size()) { + ++m_ctx.m_synt.nonSynLatch; + return nullptr; + } + + for (size_t i = 0; i < tDrivers.size(); ++i) { + const Driver& tDriver = tDrivers[i]; + const Driver& eDriver = eDrivers[i]; + // If different bits are driven, then some might not have been assigned.. Latch? + if (tDriver.m_lo != eDriver.m_lo || tDriver.m_hi != eDriver.m_hi) { + ++m_ctx.m_synt.nonSynLatch; + return nullptr; + } + + AstNodeDType* const dtypep = DfgGraph::dtypePacked(tDriver.m_hi - tDriver.m_lo + 1); + DfgCond* const condp = make(flp, dtypep); + condp->condp(predicatep); + + // We actally need to select the bits from the joined variables, not use the drivers + DfgSel* const thenSelp = make(flp, tDriver.m_vtxp->dtypep()); + thenSelp->lsb(tDriver.m_lo); + thenSelp->fromp(thenp); + condp->thenp(thenSelp); + + // Same for the 'else' part + DfgSel* const elseSelp = make(flp, eDriver.m_vtxp->dtypep()); + elseSelp->lsb(eDriver.m_lo); + elseSelp->fromp(elsep); + condp->elsep(elseSelp); + + // Add it as a driver to the join + joinSplicep->addDriver(tDriver.m_flp, tDriver.m_lo, condp); + } + + // If there was a default driver, add it to te join + if (tDefaultp) joinSplicep->defaultp(tDefaultp); + + // Done + return joinp; + } + + // Combine the output symbol tables of the predecessors of the given + // BasicBlock to compute the input symtol table for the given block. + bool createInputSymbolTable(SymTab& joined, const BasicBlock& bb, + const BasicBlockMap& bbToOSymTab, + const ControlFlowEdgeMap& edgeToPredicatep) { + // Input symbol table of entry block was previously initialzied + if (bb.inEmpty()) return true; + + // We will fill it in here + UASSERT(joined.empty(), "Unresolved input symbol table should be empty"); + + // Fast path if there is only one predecessor + if (bb.inSize1()) { + joined = bbToOSymTab[*(bb.inEdges().frontp()->fromp()->as())]; + return true; + } + + // Gather predecessors and the path predicates + struct Predecessor final { + const BasicBlock* m_bbp; + DfgVertex* m_predicatep; + Predecessor() = delete; + Predecessor(const BasicBlock* bbp, DfgVertex* predicatep) + : m_bbp{bbp} + , m_predicatep{predicatep} {} + }; + + const std::vector predecessors = [&]() { + std::vector res; + for (const V3GraphEdge& edge : bb.inEdges()) { + const ControlFlowEdge& cfgEdge = static_cast(edge); + res.emplace_back(&cfgEdge.src(), edgeToPredicatep[cfgEdge]); + } + // Sort predecessors topologically. This way later blocks will come + // after earlier blocks, and the entry block will be first if present. + std::sort(res.begin(), res.end(), [](const Predecessor& a, const Predecessor& b) { // + return a.m_bbp->id() < b.m_bbp->id(); + }); + return res; + }(); + + // Start by copying the bindings from the oldest predecessor + joined = bbToOSymTab[*predecessors[0].m_bbp]; + // Join over all other predecessors + for (size_t i = 1; i < predecessors.size(); ++i) { + DfgVertex* const predicatep = predecessors[i].m_predicatep; + const SymTab& oSymTab = bbToOSymTab[*predecessors[i].m_bbp]; + // Give up if something is not assigned on all paths ... Latch? + if (joined.size() != oSymTab.size()) { + ++m_ctx.m_synt.nonSynLatch; + return false; + } + // Join each symbol + for (auto& pair : joined) { + Variable* const varp = pair.first; + // Find same variable on other path + auto it = oSymTab.find(varp); + // Give up if something is not assigned on all paths ... Latch? + if (it == oSymTab.end()) { + ++m_ctx.m_synt.nonSynLatch; + return false; + } + // Join paths with the block predicate + DfgVertexVar* const thenp = it->second; + DfgVertexVar* const elsep = pair.second; + DfgVertexVar* const newp = joinDrivers(varp, predicatep, thenp, elsep); + if (!newp) return false; + pair.second = newp; + } + } + + return true; + } + + // Gieven the drivers of a variable after converting a single statement + // 'newp', add drivers from 'oldp' that were not reassigned be drivers + // in newp. This computes the total result of all previous assignments. + bool incorporatePreviousValue(Variable* varp, const DfgVertexVar* newp, DfgVertexVar* oldp) { + UASSERT_OBJ(newp->srcp(), varp, "Assigned variable has no driver"); + + // Easy if there is no old value... + if (!oldp) return true; + + // New driver was not yet coalesced, so should always be a splice + DfgVertexSplice* const nSplicep = newp->srcp()->as(); + + // If the old value is the real variable we just computed the new value for, + // then it is the circular feedback into the synthesized block, add it as default driver. + if (oldp->nodep() == varp) { + if (!nSplicep->wholep()) nSplicep->defaultp(oldp); + return true; + } + + UASSERT_OBJ(oldp->srcp(), varp, "Previously assigned variable has no driver"); + + // Can't do arrays yet + if (VN_IS(newp->dtypep(), UnpackArrayDType)) { + ++m_ctx.m_synt.nonSynArray; + return false; + } + + // Gather drivers of 'newp' - they are in incresing range order with no overlaps + std::vector nDrivers; + DfgVertex* nDefaultp = nullptr; + std::tie(nDrivers, nDefaultp) = gatherDrivers(newp->srcp()->as()); + UASSERT_OBJ(!nDrivers.empty(), varp, "Should have a proper driver"); + UASSERT_OBJ(!nDefaultp, varp, "Should not have a default after conversion"); + + // Gather drivers of 'oldp' - they are in incresing range order with no overlaps + std::vector oDrivers; + DfgVertex* oDefaultp = nullptr; + std::tie(oDrivers, oDefaultp) = gatherDrivers(oldp->srcp()->as()); + UASSERT_OBJ(!oDrivers.empty(), varp, "Should have a proper driver"); + + // Additional drivers of 'newp' propagated from 'oldp' + std::vector pDrivers; + + // Add bits between 'msb' and 'lsb' from 'oldp' to 'pDrivers' + const auto addToPDriver = [&](FileLine* const flp, uint32_t msb, uint32_t lsb) { + UASSERT_OBJ(pDrivers.empty() || lsb > pDrivers.back().m_hi, flp, "Non ascending"); + DfgSel* const selp = make(flp, DfgGraph::dtypePacked(msb - lsb + 1)); + selp->lsb(lsb); + selp->fromp(oldp); + pDrivers.emplace_back(flp, lsb, selp); + }; + + // Incorporate old drivers + for (const Driver& oDriver : oDrivers) { + FileLine* const flp = oDriver.m_flp; + // Range to consider inserting, we will adjust oldLo as we process drivers + uint32_t oldLo = oDriver.m_lo; + const uint32_t oldHi = oDriver.m_hi; + + // Loop for now, can move to bisection search if this is a problem, shouldn't be ... + for (const Driver& nDriver : nDrivers) { + UASSERT_OBJ(oldHi >= oldLo, flp, "Should have stopped iteration"); + // If new driver is entirely below old driver, move on to + if (nDriver.m_hi < oldLo) continue; + // If new driver is entirely above old driver, we can stop + if (oldHi < nDriver.m_lo) break; + + // There is an overlap between 'oDriver' and 'nDriver'. + // Insert the low bits and adjust the insertion range. + // The rest will take care of itself on subsequent iterations. + if (oldLo < nDriver.m_lo) addToPDriver(flp, nDriver.m_lo - 1, oldLo); + oldLo = nDriver.m_hi + 1; + + // Stop if no more bits remaining in the old driver + if (oldLo > oldHi) break; + } + + // Insert remaining bits if any + if (oldHi >= oldLo) addToPDriver(flp, oldHi, oldLo); + } + + if (!pDrivers.empty()) { + // Need to merge propagated sources, so unlink and reset the splice + nSplicep->forEachSourceEdge([](DfgEdge& edge, size_t) { edge.unlinkSource(); }); + nSplicep->resetSources(); + // Merge drivers - they are both sorted and non-overlapping + std::vector drivers; + drivers.reserve(nDrivers.size() + pDrivers.size()); + std::merge(nDrivers.begin(), nDrivers.end(), pDrivers.begin(), pDrivers.end(), + std::back_inserter(drivers)); + // Coalesce adjacent ranges + coalesceDrivers(drivers); + // Reinsert drivers in order + for (const Driver& d : drivers) nSplicep->addDriver(d.m_flp, d.m_lo, d.m_vtxp); + } + + // If the old had a default, add to the new one too, unless redundant + if (oDefaultp && !nSplicep->wholep()) nSplicep->defaultp(oDefaultp); + + // Done + return true; + } + + // Synthesize the given statements with the given input symbol table. + // Returnt true if successfolly synthesized. + // Populates the given output symbol table. + // Populates the given reference with the condition of the terminator branch, if any. + bool synthesizeBasicBlock(SymTab& oSymTab, DfgVertex*& condpr, + const std::vector& stmtps, const SymTab& iSymTab) { + // Use fresh set of vertices in m_converter + const VNUser2InUse user2InUse; + + // Initialize Variable -> Vertex bindings available in this block + for (const auto& pair : iSymTab) { + Variable* const varp = pair.first; + DfgVertexVar* const vtxp = pair.second; + varp->user2p(vtxp); + oSymTab[varp] = vtxp; + } + + // Synthesize each statement one after the other + std::vector> updates; + for (AstNodeStmt* const stmtp : stmtps) { + // Regular statements + if (AstAssign* const ap = VN_CAST(stmtp, Assign)) { + // Convert this assignment + if (!m_converter.convert(updates, *m_logicp, ap)) { + ++m_ctx.m_synt.nonSynConv; + return false; + } + // Apply variable updates from this statement + for (const auto& pair : updates) { + // The target variable that was assigned to + Variable* const varp = pair.first; + // The new, potentially partially assigned value + DfgVertexVar* const newp = pair.second; + // Normalize drivers within this statement, bail if multidriven + std::vector drivers; + DfgVertex* dfltp = nullptr; + std::tie(drivers, dfltp) = gatherDrivers(newp->srcp()->as()); + UASSERT_OBJ(!dfltp, varp, "Conversion should not add default driver"); + const bool single = drivers.size() == 1; + if (!normalizeDrivers(*newp, drivers)) { + getAstVar(varp)->setDfgMultidriven(); + ++m_ctx.m_synt.nonSynMultidrive; + return false; + } + // If there were more than one driver (often not), replace in case coalesced + if (!single) newp->srcp(makeSplice(*newp, drivers)); + // The old value, if any + DfgVertexVar* const oldp = varp->user2u().template to(); + // Inncorporate old value into the new value + if (!incorporatePreviousValue(varp, newp, oldp)) return false; + // Update binding of target variable + varp->user2p(newp); + // Update output symbol table of this block + oSymTab[varp] = newp; + } + updates.clear(); + continue; + } + + // Terminator branches + if (AstIf* const ifp = VN_CAST(stmtp, If)) { + UASSERT_OBJ(ifp == stmtps.back(), ifp, "Branch should be last statement"); + // Convert condition, give up if failed + DfgVertex* const condp = m_converter.convert(*m_logicp, ifp->condp()); + if (!condp) { + ++m_ctx.m_synt.nonSynConv; + return false; + } + // + if (condp->width() == 1) { + // Single bit condition can be use directly + condpr = condp; + } else { + // Multi bit condition: use 'condp != 0' + FileLine* const flp = condp->fileline(); + DfgNeq* const neqp = make(flp, DfgGraph::dtypePacked(1)); + neqp->lhsp(make(flp, condp->width(), 0U)); + neqp->rhsp(condp); + condpr = neqp; + } + continue; + } + + // Unhandled + ++m_ctx.m_synt.nonSynStmt; + return false; + } + + return true; + } + + // Given a basic block, and the condition of the terminating branch (if any), + // assign perdicates to the block's outgoing control flow edges. + void assignSuccessorPredicates(ControlFlowEdgeMap& edgeToPredicatep, + const BasicBlock& bb, DfgVertex* condp) { + // Nothing to do for the exit block + if (bb.outEmpty()) return; + + // Get the predicate of this block + DfgVertex* const predp = [&]() -> DfgVertex* { + // Entry block has no predecessors, use constant true + if (bb.inEmpty()) return make(m_logicp->fileline(), 1U, 1U); + + // For any other block, 'or' together all the incoming predicates + const auto& inEdges = bb.inEdges(); + auto it = inEdges.begin(); + DfgVertex* resp = edgeToPredicatep[static_cast(*it)]; + while (++it != inEdges.end()) { + DfgOr* const orp = make(resp->fileline(), resp->dtypep()); + orp->rhsp(resp); + orp->lhsp(edgeToPredicatep[static_cast(*it)]); + resp = orp; + } + return resp; + }(); + + if (!condp) { + // There should be 1 successors for a block with an unconditional terminator + UASSERT_OBJ(!bb.untknEdgep(), predp, "Expecting 1 successor for BasicBlock"); + // Successor predicate edge is the same + edgeToPredicatep[*bb.takenEdgep()] = predp; + } else { + // There should be 2 successors for a block with an conditional terminator + UASSERT_OBJ(bb.untknEdgep(), predp, "Expecting 2 successors for BasicBlock"); + FileLine* const flp = condp->fileline(); + AstNodeDType* const dtypep = condp->dtypep(); // Single bit + + // Predicate for taken branch: 'predp & condp' + DfgAnd* const takenPredp = make(flp, dtypep); + takenPredp->lhsp(predp); + takenPredp->rhsp(condp); + edgeToPredicatep[*bb.takenEdgep()] = takenPredp; + + // Predicate for untaken branch: 'predp & ~condp' + DfgAnd* const untknPredp = make(flp, dtypep); + untknPredp->lhsp(predp); + DfgNot* const notp = make(flp, dtypep); + notp->srcp(condp); + untknPredp->rhsp(notp); + edgeToPredicatep[*bb.untknEdgep()] = untknPredp; + } + } + + // Add the synthesized values as drivers to the output variables of the current DfgLogic + bool addSynthesizedOutput(SymTab& oSymTab) { + // It's possible we think a variable is written by the DfgLogic when + // it actauly isn't, e.g.: '{a[0], b[0]}[1] = ...' does not write 'b'. + // These LHS forms can happen after some earlier tranforms. We + // should just run V3Const on them earleir, but we will do belt and + // braces and check here too. We can't touch any output variables if so. + const bool missing = m_logicp->findSink([&](const DfgVertex& sink) -> bool { + const DfgUnresolved* const unresolvedp = sink.as(); + AstNode* const tgtp = unresolvedp->singleSink()->as()->nodep(); + Variable* const varp = reinterpret_cast(tgtp); + return !oSymTab.count(varp); + }); + if (missing) { + ++m_ctx.m_synt.nonSynFalseWrite; + return false; + } + + // Add sinks to read the computed values for the target variables + m_logicp->forEachSink([&](DfgVertex& sink) { + DfgUnresolved* const unresolvedp = sink.as(); + AstNode* const tgtp = unresolvedp->singleSink()->as()->nodep(); + Variable* const varp = reinterpret_cast(tgtp); + DfgVertexVar* const resp = oSymTab.at(varp); + UASSERT_OBJ(resp->srcp(), resp, "Undriven result"); + unresolvedp->addDriver(resp->srcp()->as()); + }); + return true; + } + + // Synthesize the given AstAssignW. Returns true on success. + bool synthesizeAssignW(AstAssignW* nodep) { + ++m_ctx.m_synt.inputAssign; + + // Construct an equivalent AstAssign + AstNodeExpr* const lhsp = nodep->lhsp()->cloneTree(false); + AstNodeExpr* const rhsp = nodep->rhsp()->cloneTree(false); + AstAssign* const assignp = new AstAssign{nodep->fileline(), lhsp, rhsp}; + + // The input and output symbol tables + SymTab iSymTab; + SymTab oSymTab; + + // Initialzie input symbol table + initializeEntrySymbolTable(iSymTab); + + // Synthesize as if it was in a single BasicBlock CFG + DfgVertex* condp = nullptr; + const bool success = synthesizeBasicBlock(oSymTab, condp, {assignp}, iSymTab); + UASSERT_OBJ(!condp, nodep, "Conditional AstAssignW ???"); + // Delete auxiliary AstAssign + VL_DO_DANGLING(assignp->deleteTree(), assignp); + if (!success) return false; + + // Add resolved output variable drivers + return addSynthesizedOutput(oSymTab); + } + + // Synthesize the given AstAlways. Returns true on success. + bool synthesizeCfg(const ControlFlowGraph& cfg) { + ++m_ctx.m_synt.inputAlways; + + if (hasExternallyWrittenVariable(*m_logicp)) { + ++m_ctx.m_synt.nonSynExtWrite; + return false; + } + + // If there is a backward edge (loop), we can't synthesize it + if (cfg.containsLoop()) { + ++m_ctx.m_synt.nonSynLoop; + return false; + } + + // Maps from BasicBlock to its input and output symbol tables + BasicBlockMap bbToISymTab = cfg.makeBasicBlockMap(); + BasicBlockMap bbToOSymTab = cfg.makeBasicBlockMap(); + + // Map from ControlFlowGraphEdge to its predicate + ControlFlowEdgeMap edgeToPredicatep = cfg.makeEdgeMap(); + + // Initialzie input symbol table of entry block + initializeEntrySymbolTable(bbToISymTab[cfg.enter()]); + + // Synthesize all blocks + for (const V3GraphVertex& cfgVtx : cfg.vertices()) { + const BasicBlock& bb = *cfgVtx.as(); + // Symbol tables of the block + SymTab& iSymTab = bbToISymTab[bb]; + SymTab& oSymTab = bbToOSymTab[bb]; + // Join symbol tables from predecessor blocks + if (!createInputSymbolTable(iSymTab, bb, bbToOSymTab, edgeToPredicatep)) return false; + // Condition of the terminating branch, if any + DfgVertex* condp = nullptr; + // Synthesize the block + if (!synthesizeBasicBlock(oSymTab, condp, bb.stmtps(), iSymTab)) return false; + // Set the predicates on the successor edges + assignSuccessorPredicates(edgeToPredicatep, bb, condp); + } + + // Add resolved output variable drivers + return addSynthesizedOutput(bbToOSymTab[cfg.exit()]); + } + + // Synthesize a DfgLogic into regular vertices. Returns ture on success. + bool synthesize(DfgLogic& vtx) { + VL_RESTORER(m_logicp); + m_logicp = &vtx; + + if (AstAssignW* const nodep = VN_CAST(vtx.nodep(), AssignW)) { + if (!synthesizeAssignW(nodep)) return false; + ++m_ctx.m_synt.synthAssign; + return true; + } + + if (!synthesizeCfg(vtx.cfg())) return false; + ++m_ctx.m_synt.synthAlways; + return true; + } + + // Revert synthesis of the given DfgLogic + void revert(DfgLogic& vtx) { + for (DfgVertex* const p : vtx.synth()) VL_DO_DANGLING(p->unlinkDelete(m_dfg), p); + vtx.synth().clear(); + } + + // Revert all logic driving the given unresolved driver, delete it, + // and transitively the same for variables driven by the reverted logic. + void revertTransivelyAndRemove(DfgUnresolved* vtxp, VDouble0& statCountr) { + // The result variable will be driven from Ast code, mark as such + vtxp->singleSink()->as()->setHasModWrRefs(); + + // Gather all logic driving this unresolved driver + std::vector logicps; + logicps.reserve(vtxp->arity()); + vtxp->forEachSource([&](DfgVertex& src) { + if (DfgLogic* const p = src.cast()) logicps.emplace_back(p); + }); + + // Delete the unresolved driver + VL_DO_DANGLING(vtxp->unlinkDelete(m_dfg), vtxp); + + // Transitively for the rest + for (DfgLogic* const logicp : logicps) { + if (!logicp->synth().empty()) { + ++statCountr; + revert(*logicp); + } + while (DfgVertex* const sinkp = logicp->firtsSinkp()) { + revertTransivelyAndRemove(sinkp->as(), statCountr); + } + } + } + + // Synthesize all of the given vertices + void main(const std::vector& logicps) { + //------------------------------------------------------------------- + UINFO(5, "Step 1: Attempting to synthesize each of the given DfgLogic"); + for (DfgLogic* const logicp : logicps) { + // Debug aid + if (VL_UNLIKELY(s_dfgSynthDebugLimit)) { + if (s_dfgSynthDebugCount == s_dfgSynthDebugLimit) break; + ++s_dfgSynthDebugCount; + if (s_dfgSynthDebugCount == s_dfgSynthDebugLimit) { + // This is the breaking logic + m_debugLogicp = logicp; + // Dump it + UINFOTREE(0, logicp->nodep(), "Problematic DfgLogic: " << logicp, " "); + V3EmitV::debugVerilogForTree(logicp->nodep(), std::cout); + debugDump("synth-lastok"); + } + } + + // Synthesize it, revert partial construction if failed + if (!synthesize(*logicp)) revert(*logicp); + } + debugDump("synth-converted"); + + //------------------------------------------------------------------- + UINFO(5, "Step 2: Revert drivers of variables with unsynthesizeable drivers"); + // We do this as the variables might be multi-driven, we just can't know at this point + for (DfgVertexVar& var : m_dfg.varVertices()) { + if (!var.srcp()) continue; + DfgUnresolved* const unresolvedp = var.srcp()->cast(); + if (!unresolvedp) break; // Stop when reached the synthesized temporaries + + // Check if any driver have failed to synthesize + const bool failed = unresolvedp->findSourceEdge([&](const DfgEdge& e, size_t) -> bool { + DfgLogic* const driverp = e.sourcep()->cast(); + return driverp && driverp->synth().empty(); + }); + // Revert all logic involved + if (failed) revertTransivelyAndRemove(unresolvedp, m_ctx.m_synt.revertNonSyn); + } + debugDump("synth-reverted"); + + //------------------------------------------------------------------- + UINFO(5, "Step 3: Resolve synthesized drivers of original (non-temporary) variables"); + // List of multi-driven variables + std::vector multidrivenps; + // Map from variable to its resolved driver + std::unordered_map resolvedDrivers; + // Compute resolved drivers of all variablees + for (DfgVertexVar& var : m_dfg.varVertices()) { + if (!var.srcp()) continue; + const DfgUnresolved* const unresolvedp = var.srcp()->cast(); + if (!unresolvedp) break; // Stop when reached the synthesized temporaries + + // Resolve the synthesized drivers + DfgVertexSplice* const resolvedp = [&]() -> DfgVertexSplice* { + // All synthesized drivers were normalized already, + // so if there is only one, it can be used directly + if (const auto p = unresolvedp->singleSource()) return p->as(); + // Otherwise gather the synthesized drivers + std::vector drivers = gatherDriversUnresolved(unresolvedp); + // Normalize them, make resolved driver if all good + if (normalizeDrivers(var, drivers)) return makeSplice(var, drivers); + // If mutlidriven, record and ignore + multidrivenps.emplace_back(&var); + return nullptr; + }(); + // Bail if multidriven + if (!resolvedp) continue; + // Add to map for next loop + const bool newEntry = resolvedDrivers.emplace(&var, resolvedp).second; + UASSERT_OBJ(newEntry, &var, "Dupliacte driver"); + } + // Revert and remove drivers of multi-driven variables + for (DfgVertexVar* const vtxp : multidrivenps) { + // Mark as multidriven for future DFG runs - here, so we get all warning before + vtxp->varp()->setDfgMultidriven(); + // Might not have a driver if transitively removed on an earlier iteration + if (!vtxp->srcp()) continue; + // Revert all logic involved + DfgUnresolved* const unresolvedp = vtxp->srcp()->as(); + revertTransivelyAndRemove(unresolvedp, m_ctx.m_synt.revertMultidrive); + } + // Replace all DfgUnresolved with the resolved drivers + for (DfgVertexVar& var : m_dfg.varVertices()) { + if (!var.srcp()) continue; + DfgUnresolved* const srcp = var.srcp()->cast(); + if (!srcp) break; // Stop when reached the synthesized temporaries + + // Replace it + srcp->replaceWith(resolvedDrivers.at(&var)); + VL_DO_DANGLING(srcp->unlinkDelete(m_dfg), srcp); + } + debugDump("synth-resolved"); + + //------------------------------------------------------------------- + UINFO(5, "Step 4: Remove all DfgLogic and DfgUnresolved"); + for (DfgVertex* const vtxp : m_dfg.opVertices().unlinkable()) { + // Previous step should have removed all DfgUnresolved + UASSERT_OBJ(!vtxp->is(), vtxp, "DfgUnresolved remains"); + + // Process only DfgLogic + DfgLogic* const logicp = vtxp->cast(); + if (!logicp) continue; + + // Earlier pass should have removed all sinks + UASSERT_OBJ(!logicp->hasSinks(), logicp, "DfgLogic sink remains"); + + if (!logicp->synth().empty()) { + // If synthesized, delete the corresponding AstNode. It is now in Dfg. + logicp->nodep()->unlinkFrBack()->deleteTree(); + } else { + // Not synthesized. Logic stays in Ast. Mark source variables + //as read in module. Outputs already marked by revertTransivelyAndRemove. + logicp->forEachSource([](DfgVertex& src) { // + src.as()->setHasModRdRefs(); + }); + } + + // Delete this DfgLogic + VL_DO_DANGLING(logicp->unlinkDelete(m_dfg), logicp); + } + // Reset the debug pointer, we have deleted it in the loop above ... + m_debugLogicp = nullptr; + debugDump("synth-rmlogics"); + + //------------------------------------------------------------------- + UINFO(5, "Step 5: Remove unnecessary splices"); + for (DfgVertex* const vtxp : m_dfg.opVertices().unlinkable()) { + DfgVertexSplice* const splicep = vtxp->cast(); + if (!splicep) continue; + + // Might not have a sink if the driving logic was revered, remove + if (!splicep->hasSinks()) { + VL_DO_DANGLING(splicep->unlinkDelete(m_dfg), splicep); + continue; + } + + // It should alway have drivers + UASSERT_OBJ(splicep->arity(), splicep, "Splice with no drivers"); + + // If redundant, remove it + if (DfgVertex* const wholep = splicep->wholep()) { + if (DfgVertexVar* const varp = splicep->singleSink()->cast()) { + varp->driverFileLine(splicep->driverFileLine(0)); + } + splicep->replaceWith(wholep); + VL_DO_DANGLING(splicep->unlinkDelete(m_dfg), splicep); + } + } + debugDump("synth-rmsplice"); + + //------------------------------------------------------------------- + UINFO(5, "Step 6: Remove all unused vertices"); + V3DfgPasses::removeUnused(m_dfg); + debugDump("synth-rmunused"); + } + + // CONSTRUCTOR + AstToDfgSynthesize(DfgGraph& dfg, const std::vector& logicps, + V3DfgSynthesisContext& ctx) + : m_dfg{dfg} + , m_ctx{ctx} + , m_converter{dfg, ctx} {} + +public: + static void apply(DfgGraph& dfg, const std::vector& logicps, + V3DfgSynthesisContext& ctx) { + AstToDfgSynthesize{dfg, logicps, ctx}.main(logicps); + } +}; + +void V3DfgPasses::synthesize(DfgGraph& dfg, V3DfgContext& ctx) { + // The vertices to synthesize + std::vector logicps; + + if (v3Global.opt.fDfgSynthesizeAll()) { + // If we are told to synthesize everything, we will do so ... + for (DfgVertex& vtx : dfg.opVertices()) { + if (DfgLogic* const logicp = vtx.cast()) logicps.emplace_back(logicp); + } + } else { + // Otherwise figure out which vertices are worth synthesizing. + + // Find cycles + const auto userDataInUse = dfg.userDataInUse(); + V3DfgPasses::colorStronglyConnectedComponents(dfg); + + // First, gather variables, we will then attempt to synthesize all their drivers + std::vector varps; + for (DfgVertexVar& var : dfg.varVertices()) { + // Can ignore variables with no drivers + if (!var.srcp()) continue; + + // Circular variable - synthesize + if (var.getUser()) { + varps.emplace_back(&var); + continue; + } + + // Must be driven from a DfgUnresolved at this point, pick it up + const DfgUnresolved* const unresolvedp = var.srcp()->as(); + + // Inspect drivers to figure out if we should synthesize them + const bool doIt = unresolvedp->findSourceEdge([](const DfgEdge& edge, size_t) -> bool { + const DfgLogic* const logicp = edge.sourcep()->as(); + // Synthesize continuous assignments (this is the earlier behaviour) + if (VN_IS(logicp->nodep(), AssignW)) return true; + // Synthesize always blocks with no more than 4 basic blocks and 4 edges + // These are usually simple branches (if (rst) ... else ...), or close to it + return logicp->cfg().nBasicBlocks() <= 4 && logicp->cfg().nEdges() <= 4; + }); + if (doIt) varps.emplace_back(&var); + } + + // Gather all drivers of the selected variables + const VNUser2InUse user2InUse; // AstNode (logic) -> bool: already collected + for (const DfgVertexVar* const varp : varps) { + varp->srcp()->as()->forEachSource([&](DfgVertex& source) { + DfgLogic* const logicp = source.as(); + if (!logicp->nodep()->user2Inc()) logicps.emplace_back(logicp); + }); + } + } + + // Synthesize them - also removes un-synthesized DfgLogic, so must run even if logicps.empty() + if (dfg.modulep()) { + AstToDfgSynthesize::apply(dfg, logicps, ctx.m_synthContext); + } else { + AstToDfgSynthesize::apply(dfg, logicps, ctx.m_synthContext); + } +} diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index bb6306f26..f3ef92c65 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -43,6 +43,8 @@ class DfgVertexVar VL_NOT_FINAL : public DfgVertexUnary { AstVarScope* const m_varScopep; // The AstVarScope associated with this vertex (not owned) // Location of driver of this variable. Only used for converting back to Ast. Might be nullptr. FileLine* m_driverFileLine = nullptr; + // If this DfgVertexVar is a synthesized temporary, this is the Var/VarScope it stands for. + AstNode* m_tmpForp = nullptr; bool selfEquals(const DfgVertex& that) const final VL_MT_DISABLED; V3Hash selfHash() const final VL_MT_DISABLED; @@ -64,6 +66,9 @@ public: FileLine* driverFileLine() const { return m_driverFileLine; } void driverFileLine(FileLine* flp) { m_driverFileLine = flp; } + AstNode* tmpForp() const { return m_tmpForp; } + void tmpForp(AstNode* nodep) { m_tmpForp = nodep; } + bool isDrivenFullyByDfg() const { return srcp() && !srcp()->is() && !varp()->isForced(); } @@ -100,10 +105,104 @@ public: } }; class DfgVertexSplice VL_NOT_FINAL : public DfgVertexVariadic { +protected: + struct DriverData final { + FileLine* m_flp; // Location of this driver + uint32_t m_lo; // Low index of range driven by this driver + DriverData() = delete; + DriverData(FileLine* flp, uint32_t lo) + : m_flp{flp} + , m_lo{lo} {} + }; + std::vector m_driverData; // Additional data associated with each driver + + bool selfEquals(const DfgVertex& that) const override VL_MT_DISABLED; + V3Hash selfHash() const override VL_MT_DISABLED; + public: DfgVertexSplice(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep) - : DfgVertexVariadic{dfg, type, flp, dtypep, 1u} {} + : DfgVertexVariadic{dfg, type, flp, dtypep, 2u} { + // Add optional source for 'defaultp' + addSource(); + } ASTGEN_MEMBERS_DfgVertexSplice; + + std::pair sourceEdges() const override { + const std::pair pair = DfgVertexVariadic::sourceEdges(); + UASSERT_OBJ(pair.second > 0, this, "default driver edge is missing"); + // If it has a default driver that's it + if (pair.first->sourcep()) return pair; + // Otherwise there is one less source + return {pair.first + 1, pair.second - 1}; + } + std::pair sourceEdges() override { + const auto pair = const_cast(this)->sourceEdges(); + return {const_cast(pair.first), pair.second}; + } + + // Named getter/setter for optional default driver + DfgVertex* defaultp() const { return DfgVertexVariadic::source(0); } + void defaultp(DfgVertex* vtxp) { + UASSERT_OBJ(!vtxp->is(), vtxp, "default driver can't be a DfgLogic"); + const bool found = findSourceEdge([vtxp](const DfgEdge& e, size_t) -> bool { // + return e.sourcep() == vtxp; + }); + UASSERT_OBJ(!found, this, "adding existing driver as default"); + DfgVertexVariadic::sourceEdge(0)->relinkSource(vtxp); + } + + // Add resolved driver + void addDriver(FileLine* flp, uint32_t lo, DfgVertex* vtxp) { + UASSERT_OBJ(!vtxp->is(), vtxp, "addDriver called with DfgLogic"); + UASSERT_OBJ(vtxp != defaultp(), this, "adding default driver as resolved"); + m_driverData.emplace_back(flp, lo); + DfgVertexVariadic::addSource()->relinkSource(vtxp); + } + + FileLine* driverFileLine(size_t idx) const { + UASSERT_OBJ(!defaultp() || idx > 0, this, "'driverFileLine' called on default driver"); + if (defaultp()) --idx; + return m_driverData.at(idx).m_flp; + } + + uint32_t driverLo(size_t idx) const { + UASSERT_OBJ(!defaultp() || idx > 0, this, "'driverLo' called on default driver"); + if (defaultp()) --idx; + const DriverData& dd = m_driverData.at(idx); + return dd.m_lo; + } + + DfgVertex* driverAt(size_t idx) const { + const DfgEdge* const edgep = findSourceEdge([this, idx](const DfgEdge& e, size_t i) { // + // Don't pick the default driver + if (i == 0 && defaultp()) return false; + return driverLo(i) == idx; + }); + return edgep ? edgep->sourcep() : nullptr; + } + + // If drives the whole result explicitly (not through defaultp), this is + // the actual driver this DfgVertexSplice can be replaced with. + inline DfgVertex* wholep() const; + + void resetSources() { + m_driverData.clear(); + // Unlink default driver + DfgVertex* const dp = defaultp(); + DfgVertexVariadic::sourceEdge(0)->unlinkSource(); + // Reset DfgVertexVariadic sources + DfgVertexVariadic::resetSources(); + // Add back the default driver if present + DfgEdge* const edgep = DfgVertexVariadic::addSource(); + if (dp) edgep->relinkSource(dp); + } + + const std::string srcName(size_t idx) const override { + if (idx == 0 && defaultp()) return "default"; + const uint32_t lo = driverLo(idx); + const uint32_t hi = lo + DfgVertexVariadic::source(idx + !defaultp())->size() - 1; + return '[' + std::to_string(hi) + ':' + std::to_string(lo) + ']'; + } }; // === Concrete node types ===================================================== @@ -119,12 +218,8 @@ class DfgConst final : public DfgVertex { V3Hash selfHash() const override VL_MT_DISABLED; public: - DfgConst(DfgGraph& dfg, FileLine* flp, const V3Number& num) - : DfgVertex{dfg, dfgType(), flp, dtypeForWidth(num.width())} - , m_num{num} {} - DfgConst(DfgGraph& dfg, FileLine* flp, uint32_t width, uint32_t value = 0) - : DfgVertex{dfg, dfgType(), flp, dtypeForWidth(width)} - , m_num{flp, static_cast(width), value} {} + inline DfgConst(DfgGraph& dfg, FileLine* flp, const V3Number& num); + inline DfgConst(DfgGraph& dfg, FileLine* flp, uint32_t width, uint32_t value = 0); ASTGEN_MEMBERS_DfgConst; V3Number& num() { return m_num; } @@ -174,6 +269,7 @@ public: }; // === DfgVertexUnary === + class DfgSel final : public DfgVertexUnary { // AstSel is ternary, but the 'widthp' is always constant and is hence redundant, and // 'lsbp' is very often constant. As AstSel is fairly common, we special case as a DfgSel for @@ -196,6 +292,20 @@ public: const string srcName(size_t) const override { return "fromp"; } }; +class DfgUnitArray final : public DfgVertexUnary { + // This is a type adapter for modeling arrays. It's a single element array, + // with the value of the single element being the source operand. +public: + DfgUnitArray(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep) + : DfgVertexUnary{dfg, dfgType(), flp, dtypep} { + UASSERT_OBJ(this->dtypep(), flp, "Non array DfgUnitArray"); + UASSERT_OBJ(this->size() == 1, flp, "DfgUnitArray must have a single element"); + } + ASTGEN_MEMBERS_DfgUnitArray; + + const std::string srcName(size_t) const override { return ""; } +}; + // === DfgVertexVar === class DfgVarArray final : public DfgVertexVar { friend class DfgVertex; @@ -228,94 +338,78 @@ public: ASTGEN_MEMBERS_DfgVarPacked; }; +// === DfgVertexVariadic === +class DfgLogic final : public DfgVertexVariadic { + // Generic vertex representing a whole combinational process + AstNode* const m_nodep; // The Ast logic represented by this vertex + const std::unique_ptr m_cfgp; + // Vertices this logic was synthesized into. Excluding variables + std::vector m_synth; + +public: + DfgLogic(DfgGraph& dfg, AstAssignW* nodep) + : DfgVertexVariadic{dfg, dfgType(), nodep->fileline(), nullptr, 1u} + , m_nodep{nodep} + , m_cfgp{nullptr} {} + + DfgLogic(DfgGraph& dfg, AstAlways* nodep, std::unique_ptr cfgp) + : DfgVertexVariadic{dfg, dfgType(), nodep->fileline(), nullptr, 1u} + , m_nodep{nodep} + , m_cfgp{std::move(cfgp)} {} + + ASTGEN_MEMBERS_DfgLogic; + + void addInput(DfgVertexVar* varp) { addSource()->relinkSource(varp); } + + AstNode* nodep() const { return m_nodep; } + const ControlFlowGraph& cfg() const { return *m_cfgp; } + std::vector& synth() { return m_synth; } + const std::vector& synth() const { return m_synth; } + + const std::string srcName(size_t) const override { return ""; } +}; + +class DfgUnresolved final : public DfgVertexVariadic { + // Represents a collection of unresolved variable drivers before synthesis + +public: + DfgUnresolved(DfgGraph& dfg, DfgVertexVar* vtxp) + : DfgVertexVariadic{dfg, dfgType(), vtxp->fileline(), vtxp->dtypep(), 1u} {} + ASTGEN_MEMBERS_DfgUnresolved; + + // Can only be driven by DfgLogic or DfgVertexSplice + void addDriver(DfgLogic* vtxp) { addSource()->relinkSource(vtxp); } + void addDriver(DfgVertexSplice* vtxp) { addSource()->relinkSource(vtxp); } + + void clearSources() { DfgVertexVariadic::clearSources(); } + + DfgVertex* singleSource() const { return arity() == 1 ? source(0) : nullptr; } + + const std::string srcName(size_t) const override { return ""; } +}; + // === DfgVertexSplice === class DfgSpliceArray final : public DfgVertexSplice { friend class DfgVertex; friend class DfgVisitor; - struct DriverData final { - FileLine* m_flp; // Location of this driver - uint32_t m_index; // Array index driven by this driver (or low index of range) - DriverData() = delete; - DriverData(FileLine* flp, uint32_t index) - : m_flp{flp} - , m_index{index} {} - }; - - std::vector m_driverData; // Additional data associated with each driver - - bool selfEquals(const DfgVertex& that) const override VL_MT_DISABLED; - V3Hash selfHash() const override VL_MT_DISABLED; - public: DfgSpliceArray(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep) : DfgVertexSplice{dfg, dfgType(), flp, dtypep} { UASSERT_OBJ(VN_IS(dtypep, UnpackArrayDType), flp, "Non array DfgSpliceArray"); } ASTGEN_MEMBERS_DfgSpliceArray; - - void addDriver(FileLine* flp, uint32_t index, DfgVertex* vtxp) { - m_driverData.emplace_back(flp, index); - DfgVertexVariadic::addSource()->relinkSource(vtxp); - } - - void resetSources() { - m_driverData.clear(); - DfgVertexVariadic::resetSources(); - } - - FileLine* driverFileLine(size_t i) const { return m_driverData.at(i).m_flp; } - uint32_t driverIndex(size_t i) const { return m_driverData.at(i).m_index; } - - DfgVertex* driverAt(size_t idx) const { - const DfgEdge* const edgep = findSourceEdge([this, idx](const DfgEdge&, size_t i) { // - return driverIndex(i) == idx; - }); - return edgep ? edgep->sourcep() : nullptr; - } - - const std::string srcName(size_t idx) const override { - return std::to_string(driverIndex(idx)); - } }; class DfgSplicePacked final : public DfgVertexSplice { friend class DfgVertex; friend class DfgVisitor; - struct DriverData final { - FileLine* m_flp; // Location of this driver - uint32_t m_lsb; // LSB of range driven by this driver - DriverData() = delete; - DriverData(FileLine* flp, uint32_t lsb) - : m_flp{flp} - , m_lsb{lsb} {} - }; - std::vector m_driverData; // Additional data associated with each driver - - bool selfEquals(const DfgVertex& that) const override VL_MT_DISABLED; - V3Hash selfHash() const override VL_MT_DISABLED; - public: DfgSplicePacked(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep) : DfgVertexSplice{dfg, dfgType(), flp, dtypep} { UASSERT_OBJ(!VN_IS(dtypep, UnpackArrayDType), flp, "Array DfgSplicePacked"); } ASTGEN_MEMBERS_DfgSplicePacked; - - void addDriver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp) { - m_driverData.emplace_back(flp, lsb); - DfgVertexVariadic::addSource()->relinkSource(vtxp); - } - - void resetSources() { - m_driverData.clear(); - DfgVertexVariadic::resetSources(); - } - - FileLine* driverFileLine(size_t i) const { return m_driverData.at(i).m_flp; } - uint32_t driverLsb(size_t i) const { return m_driverData.at(i).m_lsb; } - - const std::string srcName(size_t idx) const override { return std::to_string(driverLsb(idx)); } }; #endif diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 5608c19d9..2765564b0 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -1018,8 +1018,8 @@ class EmitVStreamVisitor final : public EmitVBaseVisitorConst { void putqs(AstNode*, const string& str) override { putbs(str); } public: - EmitVStreamVisitor(const AstNode* nodep, std::ostream& os, bool tracking) - : EmitVBaseVisitorConst{false, false} + EmitVStreamVisitor(const AstNode* nodep, std::ostream& os, bool tracking, bool suppressUnknown) + : EmitVBaseVisitorConst{false, suppressUnknown} , m_os{os, V3OutFormatter::LA_VERILOG} , m_tracking{tracking} { iterateConst(const_cast(nodep)); @@ -1031,7 +1031,11 @@ public: // EmitV class functions void V3EmitV::verilogForTree(const AstNode* nodep, std::ostream& os) { - { EmitVStreamVisitor{nodep, os, /* tracking: */ false}; } + { EmitVStreamVisitor{nodep, os, /* tracking: */ false, false}; } +} + +void V3EmitV::debugVerilogForTree(const AstNode* nodep, std::ostream& os) { + { EmitVStreamVisitor{nodep, os, /* tracking: */ true, true}; } } void V3EmitV::emitvFiles() { diff --git a/src/V3EmitV.h b/src/V3EmitV.h index 1da5bc2e8..d284cfa80 100644 --- a/src/V3EmitV.h +++ b/src/V3EmitV.h @@ -28,6 +28,7 @@ class AstSenTree; class V3EmitV final { public: static void verilogForTree(const AstNode* nodep, std::ostream& os = std::cout); + static void debugVerilogForTree(const AstNode* nodep, std::ostream& os); static void emitvFiles(); static void debugEmitV(const string& filename); }; diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 0d2bafc62..2aac40cc3 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1347,6 +1347,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-fdfg-pre-inline", FOnOff, &m_fDfgPreInline); DECL_OPTION("-fdfg-post-inline", FOnOff, &m_fDfgPostInline); DECL_OPTION("-fdfg-scoped", FOnOff, &m_fDfgScoped); + DECL_OPTION("-fdfg-synthesize-all", FOnOff, &m_fDfgSynthesizeAll); DECL_OPTION("-fexpand", FOnOff, &m_fExpand); DECL_OPTION("-ffunc-opt", CbFOnOff, [this](bool flag) { // m_fFuncSplitCat = flag; diff --git a/src/V3Options.h b/src/V3Options.h index 39f8e9a4b..12dcdd295 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -425,6 +425,7 @@ private: bool m_fDfgPreInline; // main switch: -fno-dfg-pre-inline and -fno-dfg bool m_fDfgPostInline; // main switch: -fno-dfg-post-inline and -fno-dfg bool m_fDfgScoped; // main switch: -fno-dfg-scoped and -fno-dfg + bool m_fDfgSynthesizeAll = false; // main switch: -fdfg-synthesize-all bool m_fDeadAssigns; // main switch: -fno-dead-assigns: remove dead assigns bool m_fDeadCells; // main switch: -fno-dead-cells: remove dead cells bool m_fExpand; // main switch: -fno-expand: expansion of C macros @@ -741,6 +742,7 @@ public: bool fDfgPreInline() const { return m_fDfgPreInline; } bool fDfgPostInline() const { return m_fDfgPostInline; } bool fDfgScoped() const { return m_fDfgScoped; } + bool fDfgSynthesizeAll() const { return m_fDfgSynthesizeAll; } bool fDfgPeepholeEnabled(const std::string& name) const { return !m_fDfgPeepholeDisabled.count(name); } diff --git a/src/astgen b/src/astgen index 9af3a1746..f59e84865 100755 --- a/src/astgen +++ b/src/astgen @@ -1266,9 +1266,10 @@ def write_dfg_ast_to_dfg(filename): " Dfg{t}* const vtxp = makeVertex(nodep, m_dfg);\n".format(t=node.name)) fh.write(" if (!vtxp) {\n") fh.write(" m_foundUnhandled = true;\n") - fh.write(" ++m_ctx.m_nonRepNode;\n") + fh.write(" ++m_ctx.m_conv.nonRepNode;\n") fh.write(" return;\n") fh.write(" }\n\n") + fh.write(" m_logicp->synth().emplace_back(vtxp);") for i in range(node.arity): fh.write( " vtxp->relinkSource<{i}>(nodep->op{j}p()->user2u().to());\n". diff --git a/test_regress/t/t_balance_cats.v b/test_regress/t/t_balance_cats.v index 4562ca1e5..7befe35ce 100644 --- a/test_regress/t/t_balance_cats.v +++ b/test_regress/t/t_balance_cats.v @@ -4,8 +4,6 @@ // any use, without warranty, 2024 by Wilson Snyder. // SPDX-License-Identifier: CC0-1.0 -// verilator lint_off UNOPTFLAT - module t(i, o); localparam N = 2000; // Deliberately not multiple of 32 diff --git a/test_regress/t/t_clk_condflop.v b/test_regress/t/t_clk_condflop.v index a28335121..5563b1b82 100644 --- a/test_regress/t/t_clk_condflop.v +++ b/test_regress/t/t_clk_condflop.v @@ -15,9 +15,7 @@ module t (clk); wire [2:0] q3; wire [7:0] q8; - // verilator lint_off UNOPTFLAT reg ena; - // verilator lint_on UNOPTFLAT condff #(12) condff (.clk(clk), .sen(1'b0), .ena(ena), diff --git a/test_regress/t/t_comb_input_1.py b/test_regress/t/t_comb_input_1.py index 74e183dab..4f9f6115e 100755 --- a/test_regress/t/t_comb_input_1.py +++ b/test_regress/t/t_comb_input_1.py @@ -14,7 +14,7 @@ test.scenarios('vlt_all') test.compile( #make_top_shell = False, make_main=False, - v_flags2=["--exe", test.pli_filename]) + v_flags2=["--exe", test.pli_filename, "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_comb_input_2.py b/test_regress/t/t_comb_input_2.py index 74e183dab..4f9f6115e 100755 --- a/test_regress/t/t_comb_input_2.py +++ b/test_regress/t/t_comb_input_2.py @@ -14,7 +14,7 @@ test.scenarios('vlt_all') test.compile( #make_top_shell = False, make_main=False, - v_flags2=["--exe", test.pli_filename]) + v_flags2=["--exe", test.pli_filename, "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_comb_loop_through_unpacked_array.py b/test_regress/t/t_comb_loop_through_unpacked_array.py index 41b8d5138..d16f85d79 100755 --- a/test_regress/t/t_comb_loop_through_unpacked_array.py +++ b/test_regress/t/t_comb_loop_through_unpacked_array.py @@ -11,6 +11,6 @@ import vltest_bootstrap test.scenarios('vlt_all') -test.compile(verilator_flags2=["-Wno-UNOPTFLAT"]) +test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "-fno-dfg"]) test.passes() diff --git a/test_regress/t/t_detectarray_1.py b/test_regress/t/t_detectarray_1.py index c37bc018e..51e61aa39 100755 --- a/test_regress/t/t_detectarray_1.py +++ b/test_regress/t/t_detectarray_1.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["-Wno-UNOPTFLAT"]) +test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_detectarray_2.py b/test_regress/t/t_detectarray_2.py index c37bc018e..51e61aa39 100755 --- a/test_regress/t/t_detectarray_2.py +++ b/test_regress/t/t_detectarray_2.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["-Wno-UNOPTFLAT"]) +test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_detectarray_3.py b/test_regress/t/t_detectarray_3.py index bf411aade..979bfb85d 100755 --- a/test_regress/t/t_detectarray_3.py +++ b/test_regress/t/t_detectarray_3.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["-Wno-UNOPTFLAT -Wno-WIDTH"]) +test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "-Wno-WIDTH", "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_dfg_break_cycles.py b/test_regress/t/t_dfg_break_cycles.py index 198c4cd48..48fe8d28f 100755 --- a/test_regress/t/t_dfg_break_cycles.py +++ b/test_regress/t/t_dfg_break_cycles.py @@ -9,6 +9,8 @@ import vltest_bootstrap +import os + test.scenarios('vlt_all') test.sim_time = 2000000 @@ -65,8 +67,6 @@ test.compile(verilator_flags2=[ "--stats", "--build", "-fno-dfg-break-cycles", - "-fno-dfg-post-inline", - "-fno-dfg-scoped", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_ref", "--prefix", "Vref", @@ -100,6 +100,9 @@ coveredLines = set() def readCovered(fileName): + if not os.path.exists(fileName): + test.error_keep_going("Missing coverage file: " + fileName) + return with open(fileName, 'r', encoding="utf8") as fd: for line in fd: coveredLines.add(int(line.strip())) diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index 538e4abdc..ad794c052 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -194,4 +194,37 @@ module t ( `signal(COND_COND, 3); // UNOPTFLAT assign COND_COND = {rand_a[0], (COND_COND >> 2) == 3'b001 ? rand_b[3:2] : rand_b[1:0]}; + // verilator lint_off ALWCOMBORDER + logic [3:0] always_0; + always_comb begin + always_0[3] = ~always_0[1]; + always_0[2] = always_0[1]; + always_0[0] = rand_a[0]; + end + assign always_0[1] = ~always_0[0]; + `signal(ALWAYS_0, 4); // UNOPTFLAT + assign ALWAYS_0 = always_0; + // verilator lint_on ALWCOMBORDER + + // verilator lint_off ALWCOMBORDER + logic [4:0] always_1; + always_comb begin + always_1[4] = always_1[0]; + always_1[0] = rand_a[0]; + always_1[3:2] = always_1[1:0]; + end + assign always_1[1] = always_1[0]; + `signal(ALWAYS_1, 5); // UNOPTFLAT + assign ALWAYS_1 = always_1; + // verilator lint_on ALWCOMBORDER + + // verilator lint_off ALWCOMBORDER + logic [3:0] always_2; + always_comb begin + always_2[2:0] = 3'((always_2 << 1) | 4'(rand_a[0])); + always_2[3] = rand_a[0]; + end + `signal(ALWAYS_2, 4); // UNOPTFLAT + assign ALWAYS_2 = always_2; + // verilator lint_on ALWCOMBORDER endmodule diff --git a/test_regress/t/t_dfg_multidriver_dfg_bad.out b/test_regress/t/t_dfg_multidriver_dfg_bad.out index 07a2fa37e..92a73f8a3 100644 --- a/test_regress/t/t_dfg_multidriver_dfg_bad.out +++ b/test_regress/t/t_dfg_multidriver_dfg_bad.out @@ -1,75 +1,120 @@ -%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [3:1] of signal 'a' have multiple combinational drivers +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:45:18: Bit [1] of signal 'y' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_dfg_multidriver_dfg_bad.v:17:19: ... Location of first driver - 17 | assign a[3:0] = i[3:0]; - | ^ - t/t_dfg_multidriver_dfg_bad.v:18:19: ... Location of other driver - 18 | assign a[4:1] = ~i[4:1]; - | ^ - t/t_dfg_multidriver_dfg_bad.v:16:18: ... Only the first driver will be respected + t/t_dfg_multidriver_dfg_bad.v:48:24: ... Location of offending driver + 48 | {y[1:0], y[2:1]} = i[3:0] + 4'd5; + | ^ + t/t_dfg_multidriver_dfg_bad.v:48:24: ... Location of offending driver + 48 | {y[1:0], y[2:1]} = i[3:0] + 4'd5; + | ^ ... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest ... Use "/* verilator lint_off MULTIDRIVEN */" and lint_on around source to disable this message. -%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [3:3] of signal 'a' have multiple combinational drivers +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [3:1] of signal 'a' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_dfg_multidriver_dfg_bad.v:17:19: ... Location of first driver + t/t_dfg_multidriver_dfg_bad.v:17:19: ... Location of offending driver 17 | assign a[3:0] = i[3:0]; | ^ - t/t_dfg_multidriver_dfg_bad.v:19:17: ... Location of other driver + t/t_dfg_multidriver_dfg_bad.v:18:19: ... Location of offending driver + 18 | assign a[4:1] = ~i[4:1]; + | ^ +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bit [3] of signal 'a' have multiple combinational drivers. This can cause performance degradation. + : ... note: In instance 't' + t/t_dfg_multidriver_dfg_bad.v:17:19: ... Location of offending driver + 17 | assign a[3:0] = i[3:0]; + | ^ + t/t_dfg_multidriver_dfg_bad.v:19:17: ... Location of offending driver 19 | assign a[3] = ~i[3]; | ^ - t/t_dfg_multidriver_dfg_bad.v:16:18: ... Only the first driver will be respected -%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [7:6] of signal 'a' have multiple combinational drivers +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [7:6] of signal 'a' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_dfg_multidriver_dfg_bad.v:20:19: ... Location of first driver + t/t_dfg_multidriver_dfg_bad.v:20:19: ... Location of offending driver 20 | assign a[8:5] = i[8:5]; | ^ - t/t_dfg_multidriver_dfg_bad.v:21:19: ... Location of other driver + t/t_dfg_multidriver_dfg_bad.v:21:19: ... Location of offending driver 21 | assign a[7:6] = ~i[7:6]; | ^ - t/t_dfg_multidriver_dfg_bad.v:16:18: ... Only the first driver will be respected -%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bits [9:9] of signal 'a' have multiple combinational drivers +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:16:18: Bit [9] of signal 'a' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_dfg_multidriver_dfg_bad.v:22:17: ... Location of first driver + t/t_dfg_multidriver_dfg_bad.v:22:17: ... Location of offending driver 22 | assign a[9] = i[9]; | ^ - t/t_dfg_multidriver_dfg_bad.v:23:19: ... Location of other driver + t/t_dfg_multidriver_dfg_bad.v:23:17: ... Location of offending driver 23 | assign a[9] = ~i[9]; - | ^ - t/t_dfg_multidriver_dfg_bad.v:16:18: ... Only the first driver will be respected -%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:26:18: Elements [3:0] of signal 'u' have multiple combinational drivers + | ^ +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:26:18: Elements [3:0] of signal 'u' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_dfg_multidriver_dfg_bad.v:27:14: ... Location of first driver + t/t_dfg_multidriver_dfg_bad.v:27:14: ... Location of offending driver 27 | assign u = j; | ^ - t/t_dfg_multidriver_dfg_bad.v:28:14: ... Location of other driver + t/t_dfg_multidriver_dfg_bad.v:28:14: ... Location of offending driver 28 | assign u = k; | ^ - t/t_dfg_multidriver_dfg_bad.v:26:18: ... Only the first driver will be respected -%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:30:18: Elements [1:1] of signal 'v' have multiple combinational drivers +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:30:18: Element [1] of signal 'v' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_dfg_multidriver_dfg_bad.v:31:14: ... Location of first driver + t/t_dfg_multidriver_dfg_bad.v:31:14: ... Location of offending driver 31 | assign v = j; | ^ - t/t_dfg_multidriver_dfg_bad.v:32:13: ... Location of other driver + t/t_dfg_multidriver_dfg_bad.v:32:13: ... Location of offending driver 32 | assign v[1] = i; | ^ - t/t_dfg_multidriver_dfg_bad.v:30:18: ... Only the first driver will be respected -%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:34:18: Elements [0:0] of signal 'w' have multiple combinational drivers +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:34:18: Element [0] of signal 'w' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_dfg_multidriver_dfg_bad.v:35:13: ... Location of first driver + t/t_dfg_multidriver_dfg_bad.v:35:13: ... Location of offending driver 35 | assign w[0] = i; | ^ - t/t_dfg_multidriver_dfg_bad.v:36:14: ... Location of other driver + t/t_dfg_multidriver_dfg_bad.v:36:14: ... Location of offending driver 36 | assign w = j; | ^ - t/t_dfg_multidriver_dfg_bad.v:34:18: ... Only the first driver will be respected -%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:38:18: Bits [3:2] of signal 'x[3]' have multiple combinational drivers +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:38:18: Bits [3:2] of signal 'x[3]' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_dfg_multidriver_dfg_bad.v:39:17: ... Location of first driver + t/t_dfg_multidriver_dfg_bad.v:39:17: ... Location of offending driver 39 | assign x[3] = i; | ^ - t/t_dfg_multidriver_dfg_bad.v:40:22: ... Location of other driver + t/t_dfg_multidriver_dfg_bad.v:40:22: ... Location of offending driver 40 | assign x[3][3:2] = ~i[1:0]; | ^ - t/t_dfg_multidriver_dfg_bad.v:38:18: ... Only the first driver will be respected +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:51:18: Bits [2:1] of signal 'z' have multiple combinational drivers. This can cause performance degradation. + : ... note: In instance 't' + t/t_dfg_multidriver_dfg_bad.v:53:14: ... Location of offending driver + 53 | z[2:0] = i[2:0]; + | ^ + t/t_dfg_multidriver_dfg_bad.v:58:17: ... Location of offending driver + 58 | z[3:1] = i[3:1]; + | ^ +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:51:18: Bits [6:5] of signal 'z' have multiple combinational drivers. This can cause performance degradation. + : ... note: In instance 't' + t/t_dfg_multidriver_dfg_bad.v:57:14: ... Location of offending driver + 57 | z[6:4] = i[6:4]; + | ^ + t/t_dfg_multidriver_dfg_bad.v:54:14: ... Location of offending driver + 54 | z[7:5] = i[7:5]; + | ^ +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:51:18: Bit [7] of signal 'z' have multiple combinational drivers. This can cause performance degradation. + : ... note: In instance 't' + t/t_dfg_multidriver_dfg_bad.v:54:14: ... Location of offending driver + 54 | z[7:5] = i[7:5]; + | ^ + t/t_dfg_multidriver_dfg_bad.v:60:20: ... Location of offending driver + 60 | assign z[10:7] = i[10:7]; + | ^ +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:74:18: Bits [5:2] of signal 't.sub_1.a' have multiple combinational drivers. This can cause performance degradation. + t/t_dfg_multidriver_dfg_bad.v:63:20: ... Location of offending driver + 63 | assign sub_1.a = i; + | ^ + t/t_dfg_multidriver_dfg_bad.v:75:19: ... Location of offending driver + 75 | assign a[5:2] = i[5:2]; + | ^ +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:74:18: Bits [3:2] of signal 't.sub_2.a' have multiple combinational drivers. This can cause performance degradation. + t/t_dfg_multidriver_dfg_bad.v:67:25: ... Location of offending driver + 67 | assign sub_2.a[3:0] = i[3:0]; + | ^ + t/t_dfg_multidriver_dfg_bad.v:75:19: ... Location of offending driver + 75 | assign a[5:2] = i[5:2]; + | ^ +%Warning-MULTIDRIVEN: t/t_dfg_multidriver_dfg_bad.v:74:18: Bit [5] of signal 't.sub_2.a' have multiple combinational drivers. This can cause performance degradation. + t/t_dfg_multidriver_dfg_bad.v:75:19: ... Location of offending driver + 75 | assign a[5:2] = i[5:2]; + | ^ + t/t_dfg_multidriver_dfg_bad.v:66:26: ... Location of offending driver + 66 | assign sub_2.a[10:5] = i[10:5]; + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_dfg_multidriver_dfg_bad.py b/test_regress/t/t_dfg_multidriver_dfg_bad.py index e33e10acf..cdd7fb345 100755 --- a/test_regress/t/t_dfg_multidriver_dfg_bad.py +++ b/test_regress/t/t_dfg_multidriver_dfg_bad.py @@ -11,6 +11,8 @@ import vltest_bootstrap test.scenarios('vlt') -test.lint(fails=True, expect_filename=test.golden_filename) +test.lint(verilator_flags2=["-fdfg-synthesize-all", "-fno-const-before-dfg"], + fails=True, + expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_dfg_multidriver_dfg_bad.v b/test_regress/t/t_dfg_multidriver_dfg_bad.v index 84659c923..d42e66fa1 100644 --- a/test_regress/t/t_dfg_multidriver_dfg_bad.v +++ b/test_regress/t/t_dfg_multidriver_dfg_bad.v @@ -42,6 +42,35 @@ module t( assign x[2][3:2] = ~i[1:0]; assign x[2][1:0] = ~i[1:0]; - assign o = a ^ u[3] ^ v[3] ^ w[3] ^ x[3]; + logic [10:0] y; + always_comb begin + y = i; + {y[1:0], y[2:1]} = i[3:0] + 4'd5; + end + + logic [10:0] z; + always_comb begin + z[2:0] = i[2:0]; + z[7:5] = i[7:5]; + end + always_comb begin + z[6:4] = i[6:4]; + z[3:1] = i[3:1]; + end + assign z[10:7] = i[10:7]; + + sub sub_1(i); + assign sub_1.a = i; + + sub sub_2(i); + assign sub_2.a[10:5] = i[10:5]; + assign sub_2.a[3:0] = i[3:0]; + + assign o = a ^ u[3] ^ v[3] ^ w[3] ^ x[3] ^ y ^ z ^ sub_1.a ^ sub_2.a; endmodule + +module sub(input wire [10:0] i); + logic [10:0] a; + assign a[5:2] = i[5:2]; +endmodule diff --git a/test_regress/t/t_dfg_peephole.py b/test_regress/t/t_dfg_peephole.py index ffb1819e9..204b59116 100755 --- a/test_regress/t/t_dfg_peephole.py +++ b/test_regress/t/t_dfg_peephole.py @@ -82,6 +82,7 @@ test.compile(verilator_flags2=[ "-Mdir", test.obj_dir + "/obj_opt", "--prefix", "Vopt", "-fno-const-before-dfg", # Otherwise V3Const makes testing painful + "-fdfg-synthesize-all", "--dump-dfg", # To fill code coverage "-CFLAGS \"-I .. -I ../obj_ref\"", "../obj_ref/Vref__ALL.a", diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 6392ded77..73d3bd635 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -36,12 +36,17 @@ module t ( wire logic signed [63:0] sconst_a; wire logic signed [63:0] sconst_b; logic [63:0] array [3:0]; + logic [63:0] unitArrayWhole [0:0]; + logic [63:0] unitArrayParts [0:0]; assign array[0] = (rand_a << 32) | (rand_a >> 32); assign array[1] = (rand_a << 16) | (rand_a >> 48); assign array[2][3:0] = rand_a[3:0]; always @(rand_b) begin // Intentional non-combinational partial driver array[2][7:4] = rand_a[7:4]; end + assign unitArrayWhole[0] = rand_a; + assign unitArrayParts[0][1] = rand_a[1]; + assign unitArrayParts[0][9] = rand_a[9]; `signal(FOLD_UNARY_LogNot, !const_a[0]); `signal(FOLD_UNARY_Negate, -const_a); @@ -188,8 +193,10 @@ module t ( `signal(REPLACE_COND_WITH_THEN_BRANCH_ONES, rand_a[0] ? 1'd1 : rand_a[1]); `signal(REPLACE_COND_WITH_ELSE_BRANCH_ZERO, rand_a[0] ? rand_a[1] : 1'd0); `signal(REPLACE_COND_WITH_ELSE_BRANCH_ONES, rand_a[0] ? rand_a[1] : 1'd1); - `signal(INLINE_ARRAYSEL, array[0]); - `signal(NO_INLINE_ARRAYSEL_PARTIAL, array[2]); + `signal(INLINE_ARRAYSEL_SPLICE, array[0]); + `signal(NO_INLINE_ARRAYSEL_SPLICE_PARTIAL, array[2]); + `signal(INLINE_ARRAYSEL_UNIT, unitArrayWhole[0]); + `signal(NO_INLINE_ARRAYSEL_UNIT_PARTIAL, unitArrayParts[0]); `signal(PUSH_BITWISE_THROUGH_REDUCTION_AND, (&(rand_a + 64'd105)) & (&(rand_b + 64'd108))); `signal(PUSH_BITWISE_THROUGH_REDUCTION_OR, (|(rand_a + 64'd106)) | (|(rand_b + 64'd109))); `signal(PUSH_BITWISE_THROUGH_REDUCTION_XOR, (^(rand_a + 64'd107)) ^ (^(rand_b + 64'd110))); @@ -229,6 +236,15 @@ module t ( `signal(PUSH_SEL_THROUGH_SHIFTL, sel_from_shiftl[20:0]); `signal(REPLACE_SEL_FROM_SEL, sel_from_sel[4:3]); + logic [2:0] sel_from_partial_tmp;; + always_comb begin + sel_from_partial_tmp[1:0] = 2'd0; + if (rand_a[0]) begin + sel_from_partial_tmp[0] = rand_b[0]; + end + end + `signal(PUSH_SEL_THROUGH_SPLICE, sel_from_partial_tmp[1:0]); + // Asscending ranges `signal(ASCENDNG_SEL, arand_a[0:4]); // verilator lint_off ASCRANGE diff --git a/test_regress/t/t_dfg_synthesis.cpp b/test_regress/t/t_dfg_synthesis.cpp new file mode 100644 index 000000000..0664397e1 --- /dev/null +++ b/test_regress/t/t_dfg_synthesis.cpp @@ -0,0 +1,60 @@ +// +// DESCRIPTION: Verilator: DFG optimizer equivalence testing +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 +// + +#include +#include + +#include +#include +#include + +void rngUpdate(uint64_t& x) { + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; +} + +int main(int, char**) { + // Create contexts + VerilatedContext ctx; + + // Create models + Vref ref{&ctx}; + Vopt opt{&ctx}; + + uint64_t rand_a = 0x5aef0c8dd70a4497; + uint64_t rand_b = 0xf0c0a8dd75ae4497; + uint64_t srand_a = 0x00fa8dcc7ae4957; + uint64_t srand_b = 0x0fa8dc7ae3c9574; + + for (size_t n = 0; n < 200000; ++n) { + // Update rngs + rngUpdate(rand_a); + rngUpdate(rand_b); + rngUpdate(srand_a); + rngUpdate(srand_b); + + // Assign inputs + ref.rand_a = opt.rand_a = rand_a; + ref.rand_b = opt.rand_b = rand_b; + ref.srand_a = opt.srand_a = srand_a; + ref.srand_b = opt.srand_b = srand_b; + + // Evaluate both models + ref.eval(); + opt.eval(); + + // Check equivalence +#include "checks.h" + + // increment time + ctx.timeInc(1); + } + + std::cout << "*-* All Finished *-*\n"; +} diff --git a/test_regress/t/t_dfg_synthesis.py b/test_regress/t/t_dfg_synthesis.py new file mode 100755 index 000000000..bd229b216 --- /dev/null +++ b/test_regress/t/t_dfg_synthesis.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') +test.sim_time = 2000000 + +root = ".." + +if not os.path.exists(root + "/.git"): + test.skip("Not in a git repository") + +# Generate the equivalence checks and declaration boilerplate +rdFile = test.top_filename +plistFile = test.obj_dir + "/portlist.vh" +pdeclFile = test.obj_dir + "/portdecl.vh" +checkFile = test.obj_dir + "/checks.h" +nAlwaysSynthesized = 0 +nAlwaysNotSynthesized = 0 +nAlwaysReverted = 0 +with open(rdFile, 'r', encoding="utf8") as rdFh, \ + open(plistFile, 'w', encoding="utf8") as plistFh, \ + open(pdeclFile, 'w', encoding="utf8") as pdeclFh, \ + open(checkFile, 'w', encoding="utf8") as checkFh: + for line in rdFh: + if re.search(r'^\s*always.*//\s*nosynth$', line): + nAlwaysNotSynthesized += 1 + elif re.search(r'^\s*always.*//\s*revert$', line): + nAlwaysReverted += 1 + elif re.search(r'^\s*always', line): + nAlwaysSynthesized += 1 + line = line.split("//")[0] + m = re.search(r'`signal\((\w+),', line) + if not m: + continue + sig = m.group(1) + plistFh.write(sig + ",\n") + pdeclFh.write("output " + sig + ";\n") + checkFh.write("if (ref." + sig + " != opt." + sig + ") {\n") + checkFh.write(" std::cout << \"Mismatched " + sig + "\" << std::endl;\n") + checkFh.write(" std::cout << \"Ref: 0x\" << std::hex << (ref." + sig + + " + 0) << std::endl;\n") + checkFh.write(" std::cout << \"Opt: 0x\" << std::hex << (opt." + sig + + " + 0) << std::endl;\n") + checkFh.write(" std::exit(1);\n") + checkFh.write("}\n") + +# Compile un-optimized +test.compile(verilator_flags2=[ + "--stats", + "--build", + "-fno-dfg", + "+incdir+" + test.obj_dir, + "-Mdir", test.obj_dir + "/obj_ref", + "--prefix", "Vref", + "-Wno-UNOPTFLAT" +]) # yapf:disable + +test.file_grep_not(test.obj_dir + "/obj_ref/Vref__stats.txt", r'DFG.*Synthesis') + +# Compile optimized - also builds executable +test.compile(verilator_flags2=[ + "--stats", + "--build", + "--fdfg-synthesize-all", + "-fno-dfg-post-inline", + "-fno-dfg-scoped", + "--exe", + "+incdir+" + test.obj_dir, + "-Mdir", test.obj_dir + "/obj_opt", + "--prefix", "Vopt", + "-fno-const-before-dfg", # Otherwise V3Const makes testing painful + "--debug", "--debugi", "0", "--dumpi-tree", "0", + "-CFLAGS \"-I .. -I ../obj_ref\"", + "../obj_ref/Vref__ALL.a", + "../../t/" + test.name + ".cpp" +]) # yapf:disable + +test.file_grep(test.obj_dir + "/obj_opt/Vopt__stats.txt", + r'DFG pre inline Synthesis, synt / always blocks considered\s+(\d+)$', + nAlwaysSynthesized + nAlwaysReverted + nAlwaysNotSynthesized) +test.file_grep(test.obj_dir + "/obj_opt/Vopt__stats.txt", + r'DFG pre inline Synthesis, synt / always blocks synthesized\s+(\d+)$', + nAlwaysSynthesized + nAlwaysReverted) +test.file_grep(test.obj_dir + "/obj_opt/Vopt__stats.txt", + r'DFG pre inline Synthesis, synt / reverted \(multidrive\)\s+(\d)$', + nAlwaysReverted) + +# Execute test to check equivalence +test.execute(executable=test.obj_dir + "/obj_opt/Vopt") + +test.passes() diff --git a/test_regress/t/t_dfg_synthesis.v b/test_regress/t/t_dfg_synthesis.v new file mode 100644 index 000000000..46f3e298b --- /dev/null +++ b/test_regress/t/t_dfg_synthesis.v @@ -0,0 +1,228 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +`define signal(name, expr) wire [$bits(expr)-1:0] ``name = expr + +module t ( +`include "portlist.vh" // Boilerplate generated by t_dfg_break_cycles.py + rand_a, rand_b, srand_a, srand_b +); + +`include "portdecl.vh" // Boilerplate generated by t_dfg_break_cycles.py + + input rand_a; + input rand_b; + input srand_a; + input srand_b; + wire logic [63:0] rand_a; + wire logic [63:0] rand_b; + wire logic signed [63:0] srand_a; + wire logic signed [63:0] srand_b; + + ////////////////////////////////////////////////////////////////////////// + + logic [2:0] simple; + always_comb begin + simple[0] = rand_a[0]; + simple[1] = rand_a[1]; + simple[2] = rand_a[2]; + end + `signal(SIMPLE, simple); + + logic [1:0] reassign; + always_comb begin + reassign[0] = rand_a[0]; + reassign[0] = ~rand_a[0]; + reassign[1] = rand_a[1]; + reassign[1] = ~rand_a[1]; + end + `signal(REASSIGN, reassign); + + logic [1:0] use_intermediate_a; + logic [1:0] use_intermediate_b; + always_comb begin + use_intermediate_a[0] = rand_a[0]; + use_intermediate_b[0] = ~use_intermediate_a[0]; + use_intermediate_a[1] = rand_a[1]; + use_intermediate_a[0] = ~rand_a[0]; + use_intermediate_b[1] = ~use_intermediate_a[1]; + use_intermediate_a[1] = ~rand_a[1]; + end + `signal(USE_INTERMEDIATE, {use_intermediate_a, use_intermediate_b}); + + logic [2:0] self_circular; + always_comb begin + self_circular[0] = rand_a[0]; + self_circular[1] = ~self_circular[0]; + self_circular[2] = ~self_circular[1]; + end + `signal(SELF_CIRCULAR, self_circular); + + logic [2:0] part_circular; + always_comb begin + part_circular[0] = rand_a[0]; + part_circular[1] = ~part_circular[0]; + end + // part_circular[2] deliberately undriven! + `signal(PART_CIRCULAR, part_circular); + + logic [3:0] split_circular; + always_comb begin + split_circular[0] = rand_a[0]; + split_circular[2] = rand_a[1]; + end + always_comb begin + split_circular[1] = ~split_circular[0]; + split_circular[3] = ~split_circular[2]; + end + `signal(SPLIT_CIRCULAR, split_circular); + + logic [3:0] conditional_a; + always_comb begin + conditional_a = 4'd0; + if (rand_a[0]) begin + conditional_a = rand_b[3:0]; + end else begin + conditional_a = ~rand_b[3:0]; + end + end + `signal(CONDITONAL_A, conditional_a); + + logic [3:0] conditional_b; + always_comb begin + conditional_b = 4'd0; + if (rand_a[0]) begin + conditional_b = rand_b[3:0]; + end + end + `signal(CONDITONAL_B, conditional_b); + + // verilator lint_off LATCH + logic [3:0] conditional_c; + always_comb begin // nosynth + if (rand_a[0]) begin + conditional_c = rand_b[3:0]; + end + if (~rand_a[0]) begin + conditional_c = ~rand_b[3:0]; + end + end + `signal(CONDITONAL_C, conditional_c); + // verilator lint_on LATCH + + logic [3:0] conditional_d; + always_comb begin + if (rand_a[0]) begin + conditional_d = rand_b[3:0]; + end else if (rand_a[1]) begin + conditional_d = ~rand_b[3:0]; + end else begin + conditional_d = rand_b[7:4]; + end + end + `signal(CONDITONAL_D, conditional_d); + + logic [3:0] conditional_e; + always_comb begin + conditional_e = 4'd0; + if (rand_a[0]) begin + conditional_e = rand_b[3:0]; + end else begin + if (rand_a[1]) begin + conditional_e = rand_b[3:0]; + end else begin + conditional_e = rand_b[7:4]; + end + conditional_e = ~conditional_e; + end + end + `signal(CONDITONAL_E, conditional_e); + + logic condigional_f; + always_comb begin + if (rand_b[0]) begin + condigional_f = 1'h1; + if (rand_b[1]) begin + condigional_f = rand_a[0]; + end + end else begin + condigional_f = 1'b0; + end + end + `signal(CONDITONAL_F, condigional_f); + + logic [7:0] partial_conditional_a; + always_comb begin + partial_conditional_a[1:0] = 2'd0; + if (rand_a[0]) begin + partial_conditional_a[0] = rand_b[0]; + end else begin + partial_conditional_a[1] = rand_b[1]; + end + partial_conditional_a[4:3] = rand_b[4:3]; + end + `signal(PARTIAL_CONDITONAL_A, partial_conditional_a); + + logic [3:0] partial_conditional_b; + always_comb begin + partial_conditional_b[1:0] = 2'd0; + if (rand_a[0]) begin + partial_conditional_b[0] = rand_b[0]; + end + if (rand_a[1]) begin + partial_conditional_b[1] = rand_b[1]; + end + end + `signal(PARTIAL_CONDITONAL_B, partial_conditional_b); + + logic [3:0] becomes_full; + always_comb begin + becomes_full[2:0] = rand_a[2:0]; + becomes_full[3] = ~rand_a[3]; + if (rand_b[0]) begin + becomes_full = ~becomes_full; + end + end + `signal(BECOMES_FULL, becomes_full); + + // verilator lint_off LATCH + logic [3:0] latch_a; + logic [3:0] latch_b; + always_comb begin // nosynth + if (rand_b[0]) begin + latch_a[3:1] = ~rand_a[3:1]; + end + latch_b = latch_a; + end + assign latch_a[0] = rand_a[0]; + `signal(LATCH, latch_b); + // verilator lint_on LATCH + + // verilator lint_off MULTIDRIVEN + logic static_temporary_a; + logic static_temporary_b; + logic static_temporary_tmp; + always_comb begin // revert + static_temporary_tmp = rand_a[0]; + static_temporary_a = ~static_temporary_tmp; + end + always_comb begin // revert + static_temporary_tmp = static_temporary_a; + static_temporary_b = ~static_temporary_tmp; + end + // verilator lint_on MULTIDRIVEN + `signal(STATIC_TEMPORARY, {static_temporary_tmp, static_temporary_b, static_temporary_a, rand_a[0]}); + + logic [2:0] partial_temporary_a; + logic [2:0] partial_temporary_tmp; + always_comb begin + partial_temporary_tmp[2] = rand_a[3]; + partial_temporary_tmp[1] = rand_a[2]; + partial_temporary_tmp[0] = rand_a[1]; + partial_temporary_a = partial_temporary_tmp; + end + `signal(PARTIAL_TEMPORARY, partial_temporary_a); +endmodule diff --git a/test_regress/t/t_dist_docs_summary.py b/test_regress/t/t_dist_docs_summary.py index d75cc5ce2..c8576c6f5 100755 --- a/test_regress/t/t_dist_docs_summary.py +++ b/test_regress/t/t_dist_docs_summary.py @@ -17,6 +17,7 @@ Waivers = [ '+verilator+prof+threads+file+', # Deprecated '+verilator+prof+threads+start+', # Deprecated '+verilator+prof+threads+window+', # Deprecated + '-fdfg-synthesize-all', # Mostly used for testing '-fno-', # Documented differently '-no-lineno', # Deprecated '-no-order-clock-delay', # Deprecated diff --git a/test_regress/t/t_dpi_qw.py b/test_regress/t/t_dpi_qw.py index 6e3f18c06..b9e84b1c1 100755 --- a/test_regress/t/t_dpi_qw.py +++ b/test_regress/t/t_dpi_qw.py @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('simulator') test.compile(v_flags2=["t/t_dpi_qw_c.cpp"], - verilator_flags2=["-Wall -Wno-DECLFILENAME -Wno-UNOPTFLAT -no-l2name"]) + verilator_flags2=["-Wall -Wno-DECLFILENAME -no-l2name"]) test.execute() diff --git a/test_regress/t/t_func_crc.py b/test_regress/t/t_func_crc.py index 0304e7ed5..d6276df18 100755 --- a/test_regress/t/t_func_crc.py +++ b/test_regress/t/t_func_crc.py @@ -18,6 +18,6 @@ test.compile( test.execute() if test.vlt: - test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 3888) + test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 3434) test.passes() diff --git a/test_regress/t/t_hier_block.v b/test_regress/t/t_hier_block.v index aaf0016f3..d21be731f 100644 --- a/test_regress/t/t_hier_block.v +++ b/test_regress/t/t_hier_block.v @@ -35,12 +35,10 @@ module t (/*AUTOARG*/ `ifdef PROTLIB_TOP secret i_secred(.clk(clk)); `else - /* verilator lint_off UNOPTFLAT */ wire [7:0] out0; wire [7:0] out1; wire [7:0] out2; wire [7:0] out3; - /* verilator lint_on UNOPTFLAT */ wire [7:0] out3_2; wire [7:0] out5; wire [7:0] out6; diff --git a/test_regress/t/t_hier_block_chained.py b/test_regress/t/t_hier_block_chained.py index 553bf8ea4..b443056a4 100755 --- a/test_regress/t/t_hier_block_chained.py +++ b/test_regress/t/t_hier_block_chained.py @@ -24,7 +24,6 @@ test.compile( benchmarksim=1, v_flags2=[ config_file, "+define+SIM_CYCLES=" + str(test.cycles), "--hierarchical", "--stats", - "-Wno-UNOPTFLAT", (f"-DWORKERS={HIER_BLOCK_THREADS}" if test.vltmt and HIER_BLOCK_THREADS > 1 else ""), (f"--hierarchical-threads {HIER_THREADS}" if test.vltmt and HIER_THREADS > 1 else "") ], @@ -33,9 +32,9 @@ test.compile( if test.vltmt: test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule count\s+(\d+)', 2) + r'Optimizations, Thread schedule count\s+(\d+)', 3) test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule total tasks\s+(\d+)', 3) + r'Optimizations, Thread schedule total tasks\s+(\d+)', 4) test.execute() diff --git a/test_regress/t/t_lint_always_comb_multidriven_bad.out b/test_regress/t/t_lint_always_comb_multidriven_bad.out index 83d6cb364..25b66bffc 100644 --- a/test_regress/t/t_lint_always_comb_multidriven_bad.out +++ b/test_regress/t/t_lint_always_comb_multidriven_bad.out @@ -48,40 +48,36 @@ t/t_lint_always_comb_multidriven_bad.v:40:16: ... Location of other write 40 | always_comb out6 = d; | ^~~~ -%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:17:15: Bits [0:0] of signal 'out2' have multiple combinational drivers +%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:17:15: Bit [0] of signal 'out2' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_lint_always_comb_multidriven_bad.v:28:16: ... Location of first driver + t/t_lint_always_comb_multidriven_bad.v:28:16: ... Location of offending driver 28 | assign out2 = d; | ^ - t/t_lint_always_comb_multidriven_bad.v:29:21: ... Location of other driver + t/t_lint_always_comb_multidriven_bad.v:29:21: ... Location of offending driver 29 | always_comb out2 = 1'b0; | ^ - t/t_lint_always_comb_multidriven_bad.v:17:15: ... Only the first driver will be respected -%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:19:15: Bits [0:0] of signal 'out4' have multiple combinational drivers +%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:19:15: Bit [0] of signal 'out4' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_lint_always_comb_multidriven_bad.v:34:21: ... Location of first driver + t/t_lint_always_comb_multidriven_bad.v:34:21: ... Location of offending driver 34 | always_comb out4 = 1'b0; | ^ - t/t_lint_always_comb_multidriven_bad.v:35:16: ... Location of other driver + t/t_lint_always_comb_multidriven_bad.v:35:16: ... Location of offending driver 35 | assign out4 = d; | ^ - t/t_lint_always_comb_multidriven_bad.v:19:15: ... Only the first driver will be respected -%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:20:15: Bits [0:0] of signal 'out5' have multiple combinational drivers +%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:20:15: Bit [0] of signal 'out5' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_lint_always_comb_multidriven_bad.v:37:21: ... Location of first driver + t/t_lint_always_comb_multidriven_bad.v:37:21: ... Location of offending driver 37 | always_comb out5 = 1'b0; | ^ - t/t_lint_always_comb_multidriven_bad.v:38:21: ... Location of other driver + t/t_lint_always_comb_multidriven_bad.v:38:21: ... Location of offending driver 38 | always_comb out5 = d; | ^ - t/t_lint_always_comb_multidriven_bad.v:20:15: ... Only the first driver will be respected -%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:21:15: Bits [0:0] of signal 'out6' have multiple combinational drivers +%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:21:15: Bit [0] of signal 'out6' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_lint_always_comb_multidriven_bad.v:40:21: ... Location of first driver + t/t_lint_always_comb_multidriven_bad.v:40:21: ... Location of offending driver 40 | always_comb out6 = d; | ^ - t/t_lint_always_comb_multidriven_bad.v:41:21: ... Location of other driver + t/t_lint_always_comb_multidriven_bad.v:41:21: ... Location of offending driver 41 | always_comb out6 = 1'b0; | ^ - t/t_lint_always_comb_multidriven_bad.v:21:15: ... Only the first driver will be respected %Error: Exiting due to diff --git a/test_regress/t/t_lint_always_comb_multidriven_compile_public_flat_bad.out b/test_regress/t/t_lint_always_comb_multidriven_compile_public_flat_bad.out new file mode 100644 index 000000000..0a5648357 --- /dev/null +++ b/test_regress/t/t_lint_always_comb_multidriven_compile_public_flat_bad.out @@ -0,0 +1,51 @@ +%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:26:16: Variable written to in always_comb also written by other process (IEEE 1800-2023 9.2.2.2): 'out1' + : ... note: In instance 't' + t/t_lint_always_comb_multidriven_bad.v:26:16: + 26 | always_comb out1 = d; + | ^~~~ + t/t_lint_always_comb_multidriven_bad.v:25:11: ... Location of other write + 25 | assign out1 = 1'b0; + | ^~~~ + ... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest + ... Use "/* verilator lint_off MULTIDRIVEN */" and lint_on around source to disable this message. +%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:29:16: Variable written to in always_comb also written by other process (IEEE 1800-2023 9.2.2.2): 'out2' + : ... note: In instance 't' + t/t_lint_always_comb_multidriven_bad.v:29:16: + 29 | always_comb out2 = 1'b0; + | ^~~~ + t/t_lint_always_comb_multidriven_bad.v:28:11: ... Location of other write + 28 | assign out2 = d; + | ^~~~ +%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:32:11: Variable also written to in always_comb (IEEE 1800-2023 9.2.2.2): 'out3' + : ... note: In instance 't' + t/t_lint_always_comb_multidriven_bad.v:32:11: + 32 | assign out3 = 1'b0; + | ^~~~ + t/t_lint_always_comb_multidriven_bad.v:31:16: ... Location of always_comb write + 31 | always_comb out3 = d; + | ^~~~ +%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:35:11: Variable also written to in always_comb (IEEE 1800-2023 9.2.2.2): 'out4' + : ... note: In instance 't' + t/t_lint_always_comb_multidriven_bad.v:35:11: + 35 | assign out4 = d; + | ^~~~ + t/t_lint_always_comb_multidriven_bad.v:34:16: ... Location of always_comb write + 34 | always_comb out4 = 1'b0; + | ^~~~ +%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:38:16: Variable written to in always_comb also written by other process (IEEE 1800-2023 9.2.2.2): 'out5' + : ... note: In instance 't' + t/t_lint_always_comb_multidriven_bad.v:38:16: + 38 | always_comb out5 = d; + | ^~~~ + t/t_lint_always_comb_multidriven_bad.v:37:16: ... Location of other write + 37 | always_comb out5 = 1'b0; + | ^~~~ +%Warning-MULTIDRIVEN: t/t_lint_always_comb_multidriven_bad.v:41:16: Variable written to in always_comb also written by other process (IEEE 1800-2023 9.2.2.2): 'out6' + : ... note: In instance 't' + t/t_lint_always_comb_multidriven_bad.v:41:16: + 41 | always_comb out6 = 1'b0; + | ^~~~ + t/t_lint_always_comb_multidriven_bad.v:40:16: ... Location of other write + 40 | always_comb out6 = d; + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_lint_always_comb_multidriven_compile_public_flat_bad.py b/test_regress/t/t_lint_always_comb_multidriven_compile_public_flat_bad.py index 58bab8994..07d3c7396 100755 --- a/test_regress/t/t_lint_always_comb_multidriven_compile_public_flat_bad.py +++ b/test_regress/t/t_lint_always_comb_multidriven_compile_public_flat_bad.py @@ -14,6 +14,6 @@ test.top_filename = "t/t_lint_always_comb_multidriven_bad.v" test.lint(verilator_flags2=['--public-flat-rw --lint-only'], fails=True, - expect_filename="t/t_lint_always_comb_multidriven_bad.out") + expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_lint_didnotconverge_bad.out b/test_regress/t/t_lint_didnotconverge_bad.out index f72c08997..526a34911 100644 --- a/test_regress/t/t_lint_didnotconverge_bad.out +++ b/test_regress/t/t_lint_didnotconverge_bad.out @@ -1,3 +1,3 @@ --V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] a) +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] b) %Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_lint_inherit.py b/test_regress/t/t_lint_inherit.py index c3bd7274f..5484b6a2d 100755 --- a/test_regress/t/t_lint_inherit.py +++ b/test_regress/t/t_lint_inherit.py @@ -11,6 +11,6 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=["-fno-dfg"]) test.passes() diff --git a/test_regress/t/t_order.v b/test_regress/t/t_order.v index 9a54fa524..14276f6ba 100644 --- a/test_regress/t/t_order.v +++ b/test_regress/t/t_order.v @@ -62,9 +62,7 @@ module t (/*AUTOARG*/ end reg sepassign_in; - // verilator lint_off UNOPTFLAT wire [3:0] sepassign; - // verilator lint_on UNOPTFLAT // verilator lint_off UNOPT assign #0.1 sepassign[0] = 0, diff --git a/test_regress/t/t_order_clkinst.py b/test_regress/t/t_order_clkinst.py index 3f7b706e5..9e12d32c2 100755 --- a/test_regress/t/t_order_clkinst.py +++ b/test_regress/t/t_order_clkinst.py @@ -17,7 +17,7 @@ test.scenarios('simulator') # closely enough to pass the same test? # If not -- probably we should switch this to be vlt-only. -test.compile(verilator_flags2=["--trace-vcd"]) +test.compile(verilator_flags2=["--trace-vcd", "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_order_comboclkloop.py b/test_regress/t/t_order_comboclkloop.py index d4f986441..dc6cab445 100755 --- a/test_regress/t/t_order_comboclkloop.py +++ b/test_regress/t/t_order_comboclkloop.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=["-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_order_comboloop.py b/test_regress/t/t_order_comboloop.py index d4f986441..dc6cab445 100755 --- a/test_regress/t/t_order_comboloop.py +++ b/test_regress/t/t_order_comboloop.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=["-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_order_doubleloop.py b/test_regress/t/t_order_doubleloop.py index d4f986441..dc6cab445 100755 --- a/test_regress/t/t_order_doubleloop.py +++ b/test_regress/t/t_order_doubleloop.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=["-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_order_first.v b/test_regress/t/t_order_first.v index 79c3516e8..eaf1403ee 100644 --- a/test_regress/t/t_order_first.v +++ b/test_regress/t/t_order_first.v @@ -31,9 +31,7 @@ module t_netlist (/*AUTOARG*/ // This entire module should optimize to nearly nothing... - // verilator lint_off UNOPTFLAT reg [4:0] a,a2,b,c,d,e; - // verilator lint_on UNOPTFLAT initial a=5'd1; @@ -42,13 +40,11 @@ module t_netlist (/*AUTOARG*/ c <= b+5'd1; // Better for ordering if this moves before previous statement end - // verilator lint_off UNOPT always @ (d or /*AS*/a or c) begin e = d+5'd1; a2 = a+5'd1; // This can be pulled out of the middle of the always d = c+5'd1; // Better for ordering if this moves before previous statement end - // verilator lint_on UNOPT always @ (posedge also_fastclk) begin if (_mode==5) begin diff --git a/test_regress/t/t_order_multialways.v b/test_regress/t/t_order_multialways.v index f38590db8..46c67d672 100644 --- a/test_regress/t/t_order_multialways.v +++ b/test_regress/t/t_order_multialways.v @@ -23,7 +23,6 @@ module t (/*AUTOARG*/ h = {g[15:0], g[31:16]}; end - // verilator lint_off UNOPTFLAT reg [31:0] e2,f2,g2,h2; always @ (/*AS*/f2, g2) begin h2 = {g2[15:0], g2[31:16]}; @@ -33,7 +32,6 @@ module t (/*AUTOARG*/ f2 = {e2[15:0], e2[31:16]}; e2 = in_a; end - // verilator lint_on UNOPTFLAT integer cyc; initial cyc=1; always @ (posedge clk) begin diff --git a/test_regress/t/t_order_quad.py b/test_regress/t/t_order_quad.py index f37ad07c8..94dfdc72f 100755 --- a/test_regress/t/t_order_quad.py +++ b/test_regress/t/t_order_quad.py @@ -11,7 +11,9 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(make_top_shell=False, make_main=False, verilator_flags2=["--exe", test.pli_filename]) +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=["--exe", test.pli_filename, "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_order_wireloop.py b/test_regress/t/t_order_wireloop.py index 2a9b7aa62..a96c62185 100755 --- a/test_regress/t/t_order_wireloop.py +++ b/test_regress/t/t_order_wireloop.py @@ -11,14 +11,14 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(fails=test.vlt_all) +test.compile(fails=test.vlt_all, verilator_flags2=["-fno-dfg"]) # Used to be %Error: t/t_order_wireloop.v:\d+: Wire inputs its own output, creating circular logic .wire x=x. # However we no longer gate optimize this # Can't use expect_filename here as unstable output test.file_grep( test.compile_log_filename, - r"%Warning-UNOPTFLAT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Circular combinational logic: \'bar\'" + r"%Warning-UNOPTFLAT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Circular combinational logic: \'t.foo\'" ) test.passes() diff --git a/test_regress/t/t_split_var_0.py b/test_regress/t/t_split_var_0.py index 24ed1b20d..d9c2c6068 100755 --- a/test_regress/t/t_split_var_0.py +++ b/test_regress/t/t_split_var_0.py @@ -14,7 +14,7 @@ test.scenarios('simulator') # CI environment offers 2 VCPUs, 2 thread setting causes the following warning. # %Warning-UNOPTTHREADS: Thread scheduler is unable to provide requested parallelism; consider asking for fewer threads. # So use 6 threads here though it's not optimal in performance, but ok. -test.compile(verilator_flags2=['--stats', test.t_dir + "/t_split_var_0.vlt"], +test.compile(verilator_flags2=['--stats', test.t_dir + "/t_split_var_0.vlt", "-fno-dfg"], threads=(6 if test.vltmt else 1)) test.execute() diff --git a/test_regress/t/t_split_var_0.v b/test_regress/t/t_split_var_0.v index c9efe9e76..65fd4cde6 100644 --- a/test_regress/t/t_split_var_0.v +++ b/test_regress/t/t_split_var_0.v @@ -351,13 +351,10 @@ module unpack2pack #(parameter WIDTH = 8) return tmp; endfunction - /* verilator lint_off UNOPTFLAT*/ task automatic to_packed1(input logic in[1:0] /*verilator split_var*/, output logic [1:0] out /*verilator split_var*/); out[1] = in[1]; out[0] = in[0]; endtask - /* verilator lint_on UNOPTFLAT*/ - generate for (genvar i = 4; i < WIDTH; i += 4) begin diff --git a/test_regress/t/t_split_var_1_bad.py b/test_regress/t/t_split_var_1_bad.py index 699eafaf5..fed583da0 100755 --- a/test_regress/t/t_split_var_1_bad.py +++ b/test_regress/t/t_split_var_1_bad.py @@ -11,6 +11,8 @@ import vltest_bootstrap test.scenarios('linter') -test.lint(fails=True, verilator_flags2=['--stats'], expect_filename=test.golden_filename) +test.lint(fails=True, + verilator_flags2=['--stats', "-fno-dfg"], + expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_split_var_2_trace.py b/test_regress/t/t_split_var_2_trace.py index 7a9eac173..0fcaf3d8c 100755 --- a/test_regress/t/t_split_var_2_trace.py +++ b/test_regress/t/t_split_var_2_trace.py @@ -15,8 +15,9 @@ test.top_filename = "t/t_split_var_0.v" # CI environment offers 2 VCPUs, 2 thread setting causes the following warning. # %Warning-UNOPTTHREADS: Thread scheduler is unable to provide requested parallelism; consider asking for fewer threads. # So use 6 threads here though it's not optimal in performance, but ok. -test.compile(verilator_flags2=['--cc --trace-vcd --stats +define+TEST_ATTRIBUTES'], - threads=(6 if test.vltmt else 1)) +test.compile( + verilator_flags2=['--cc', '--trace-vcd', '--stats', '+define+TEST_ATTRIBUTES', "-fno-dfg"], + threads=(6 if test.vltmt else 1)) test.execute() diff --git a/test_regress/t/t_split_var_3_wreal.py b/test_regress/t/t_split_var_3_wreal.py index 524a7b18d..8860c1644 100755 --- a/test_regress/t/t_split_var_3_wreal.py +++ b/test_regress/t/t_split_var_3_wreal.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=['--stats']) +test.compile(verilator_flags2=['--stats', "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_split_var_4.py b/test_regress/t/t_split_var_4.py index 57b43d1a0..4a461e645 100755 --- a/test_regress/t/t_split_var_4.py +++ b/test_regress/t/t_split_var_4.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=['--stats', '-DENABLE_SPLIT_VAR=1']) +test.compile(verilator_flags2=['--stats', '-DENABLE_SPLIT_VAR=1', "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_split_var_5.py b/test_regress/t/t_split_var_5.py index 1cfb7e245..12d5eaabe 100755 --- a/test_regress/t/t_split_var_5.py +++ b/test_regress/t/t_split_var_5.py @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('simulator') test.top_filename = "t/t_split_var_4.v" -test.compile(verilator_flags2=['--stats']) +test.compile(verilator_flags2=['--stats', "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_timing_debug1.out b/test_regress/t/t_timing_debug1.out index eefec27ca..4a8b8f770 100644 --- a/test_regress/t/t_timing_debug1.out +++ b/test_regress/t/t_timing_debug1.out @@ -11,7 +11,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_initial__TOP__Vtiming__1 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 -V{t#,#}+ Vt_timing_debug1___024root___eval_initial__TOP__Vtiming__2 --V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:50 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:48 -V{t#,#}+ Vt_timing_debug1___024root___eval_initial__TOP__Vtiming__3 -V{t#,#}+ Vt_timing_debug1___024root___eval_settle -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__stl @@ -24,14 +24,12 @@ -V{t#,#} 'stl' region trigger index 4 is active: @([hybrid] t.clk2) -V{t#,#} 'stl' region trigger index 5 is active: @([hybrid] __VassignWtmp_h########__0) -V{t#,#} 'stl' region trigger index 6 is active: @([hybrid] __VassignWgen_h########__0) --V{t#,#} 'stl' region trigger index 7 is active: @([hybrid] t.c1) -V{t#,#}+ Vt_timing_debug1___024root___eval_stl -V{t#,#}+ Vt_timing_debug1___024root___stl_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1____Vfork_2__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__stl -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__stl -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__stl @@ -57,18 +55,16 @@ -V{t#,#} 'act' region trigger index 3 is active: @([hybrid] t.clk2) -V{t#,#} 'act' region trigger index 4 is active: @([hybrid] __VassignWtmp_h########__0) -V{t#,#} 'act' region trigger index 5 is active: @([hybrid] __VassignWgen_h########__0) --V{t#,#} 'act' region trigger index 6 is active: @([hybrid] t.c1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk1): -V{t#,#} - Process waiting at t/t_timing_sched.v:18 -V{t#,#} - Process waiting at t/t_timing_sched.v:17 -V{t#,#} Committing processes waiting for @(posedge t.clk2): --V{t#,#} - Process waiting at t/t_timing_sched.v:50 +-V{t#,#} - Process waiting at t/t_timing_sched.v:48 -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -90,13 +86,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 3: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -105,7 +101,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -121,7 +117,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -141,6 +136,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -156,14 +152,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 6: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 7: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -208,14 +204,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 7: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 9: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 @@ -245,13 +241,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 9: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -260,7 +256,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -272,7 +268,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -290,6 +285,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -305,14 +301,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Awaiting time 12: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 13: Process waiting at t/t_timing_sched.v:17 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -321,35 +317,32 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @([hybrid] t.clk2) --V{t#,#} 'act' region trigger index 9 is active: @(posedge t.clk2) +-V{t#,#} 'act' region trigger index 8 is active: @(posedge t.clk2) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk2): --V{t#,#} - Process waiting at t/t_timing_sched.v:50 +-V{t#,#} - Process waiting at t/t_timing_sched.v:48 -V{t#,#} Ready processes waiting for @(posedge t.clk2): -V{t#,#} - Process waiting at t/t_timing_sched.v:18 -V{t#,#} Resuming processes waiting for @(posedge t.clk2) --V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:48 -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1____Vfork_2__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 4 is active: @([hybrid] __VassignWtmp_h########__0) -V{t#,#} 'act' region trigger index 5 is active: @([hybrid] __VassignWgen_h########__0) --V{t#,#} 'act' region trigger index 6 is active: @([hybrid] t.c1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk1): -V{t#,#} - Process waiting at t/t_timing_sched.v:18 -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -357,6 +350,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -372,19 +366,19 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 12: Process waiting at t/t_timing_sched.v:10 --V{t#,#} Awaiting time 12: Process waiting at t/t_timing_sched.v:50 +-V{t#,#} Awaiting time 12: Process waiting at t/t_timing_sched.v:48 -V{t#,#} Awaiting time 13: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 --V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:50 --V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:48 -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act @@ -393,7 +387,7 @@ -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk2): --V{t#,#} - Process waiting at t/t_timing_sched.v:50 +-V{t#,#} - Process waiting at t/t_timing_sched.v:48 -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 @@ -429,14 +423,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 13: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 15: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 @@ -466,13 +460,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 15: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -481,7 +475,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -497,7 +491,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -517,6 +510,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -532,14 +526,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 18: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 19: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -584,14 +578,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 19: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 21: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 @@ -621,13 +615,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 21: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -636,7 +630,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -648,7 +642,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -666,6 +659,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -681,14 +675,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Awaiting time 24: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 25: Process waiting at t/t_timing_sched.v:17 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -733,14 +727,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 24: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 25: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -785,14 +779,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 25: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 27: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 @@ -822,13 +816,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 27: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -837,7 +831,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -849,7 +843,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -867,6 +860,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -882,14 +876,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 30: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 31: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -934,14 +928,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 31: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 @@ -971,13 +965,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 @@ -988,8 +982,8 @@ -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) -V{t#,#} 'act' region trigger index 3 is active: @([hybrid] t.clk2) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) --V{t#,#} 'act' region trigger index 9 is active: @(posedge t.clk2) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 8 is active: @(posedge t.clk2) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -997,11 +991,11 @@ -V{t#,#} Resuming processes waiting for @(posedge t.clk1) -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Ready processes waiting for @(posedge t.clk2): --V{t#,#} - Process waiting at t/t_timing_sched.v:50 +-V{t#,#} - Process waiting at t/t_timing_sched.v:48 -V{t#,#} Ready processes waiting for @(posedge t.clk2): -V{t#,#} - Process waiting at t/t_timing_sched.v:18 -V{t#,#} Resuming processes waiting for @(posedge t.clk2) --V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:48 -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1011,7 +1005,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1____Vfork_2__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -1019,7 +1012,6 @@ -V{t#,#} 'act' region trigger index 2 is active: @([hybrid] __VassignWgen_h########__0) -V{t#,#} 'act' region trigger index 4 is active: @([hybrid] __VassignWtmp_h########__0) -V{t#,#} 'act' region trigger index 5 is active: @([hybrid] __VassignWgen_h########__0) --V{t#,#} 'act' region trigger index 6 is active: @([hybrid] t.c1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk1): -V{t#,#} - Process waiting at t/t_timing_sched.v:18 @@ -1027,7 +1019,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -1036,6 +1027,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -1051,18 +1043,18 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: --V{t#,#} Awaiting time 34: Process waiting at t/t_timing_sched.v:50 +-V{t#,#} Awaiting time 34: Process waiting at t/t_timing_sched.v:48 -V{t#,#} Awaiting time 36: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 37: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes --V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:50 --V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:48 -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act @@ -1071,7 +1063,7 @@ -V{t#,#} No triggers active -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk2): --V{t#,#} - Process waiting at t/t_timing_sched.v:50 +-V{t#,#} - Process waiting at t/t_timing_sched.v:48 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 @@ -1089,14 +1081,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 36: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 37: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1141,14 +1133,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 37: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 39: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 @@ -1178,13 +1170,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 39: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1193,7 +1185,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -1209,7 +1201,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -1229,6 +1220,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -1244,14 +1236,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 42: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 43: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1296,14 +1288,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 43: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Awaiting time 45: Process waiting at t/t_timing_sched.v:10 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 @@ -1333,13 +1325,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Awaiting time 45: Process waiting at t/t_timing_sched.v:10 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1384,13 +1376,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 45: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1399,7 +1391,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -1411,7 +1403,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -1429,6 +1420,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -1444,14 +1436,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 48: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 49: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1496,14 +1488,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 49: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 51: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 @@ -1533,13 +1525,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 51: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1548,7 +1540,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -1560,7 +1552,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -1578,6 +1569,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -1593,14 +1585,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 54: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:17 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1645,14 +1637,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 57: Process waiting at t/t_timing_sched.v:10 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 @@ -1663,37 +1655,34 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @([hybrid] t.clk2) --V{t#,#} 'act' region trigger index 9 is active: @(posedge t.clk2) +-V{t#,#} 'act' region trigger index 8 is active: @(posedge t.clk2) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk1): -V{t#,#} - Process waiting at t/t_timing_sched.v:17 -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk2): --V{t#,#} - Process waiting at t/t_timing_sched.v:50 +-V{t#,#} - Process waiting at t/t_timing_sched.v:48 -V{t#,#} Ready processes waiting for @(posedge t.clk2): -V{t#,#} - Process waiting at t/t_timing_sched.v:18 -V{t#,#} Resuming processes waiting for @(posedge t.clk2) --V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:48 -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1____Vfork_2__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 4 is active: @([hybrid] __VassignWtmp_h########__0) -V{t#,#} 'act' region trigger index 5 is active: @([hybrid] __VassignWgen_h########__0) --V{t#,#} 'act' region trigger index 6 is active: @([hybrid] t.c1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk1): -V{t#,#} - Process waiting at t/t_timing_sched.v:18 -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -1701,6 +1690,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -1716,17 +1706,17 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: --V{t#,#} Awaiting time 56: Process waiting at t/t_timing_sched.v:50 +-V{t#,#} Awaiting time 56: Process waiting at t/t_timing_sched.v:48 -V{t#,#} Awaiting time 57: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes --V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:50 --V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:48 -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act @@ -1735,7 +1725,7 @@ -V{t#,#} No triggers active -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk2): --V{t#,#} - Process waiting at t/t_timing_sched.v:50 +-V{t#,#} - Process waiting at t/t_timing_sched.v:48 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 @@ -1753,13 +1743,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 57: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1768,7 +1758,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -1784,7 +1774,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -1804,6 +1793,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -1819,14 +1809,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 60: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 61: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1871,14 +1861,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 61: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 63: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 @@ -1908,13 +1898,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 63: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -1923,7 +1913,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -1935,7 +1925,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -1953,6 +1942,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -1968,14 +1958,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 67: Process waiting at t/t_timing_sched.v:17 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 @@ -2027,14 +2017,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 67: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 69: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 @@ -2064,13 +2054,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 69: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -2079,7 +2069,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -2091,7 +2081,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -2109,6 +2098,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -2124,14 +2114,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 72: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 73: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -2176,14 +2166,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 73: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 75: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 @@ -2213,13 +2203,13 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 75: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 -V{t#,#}+ Vt_timing_debug1___024root___eval_act @@ -2228,7 +2218,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([hybrid] t.clk1) --V{t#,#} 'act' region trigger index 7 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 6 is active: @(posedge t.clk1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk1): @@ -2240,7 +2230,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0____Vfork_1__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -2258,6 +2247,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba -V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -2273,12 +2263,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: -V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:13 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 -V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 79: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Resuming delayed processes @@ -2289,35 +2279,32 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @([hybrid] t.clk2) --V{t#,#} 'act' region trigger index 9 is active: @(posedge t.clk2) +-V{t#,#} 'act' region trigger index 8 is active: @(posedge t.clk2) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Ready processes waiting for @(posedge t.clk2): --V{t#,#} - Process waiting at t/t_timing_sched.v:50 +-V{t#,#} - Process waiting at t/t_timing_sched.v:48 -V{t#,#} Ready processes waiting for @(posedge t.clk2): -V{t#,#} - Process waiting at t/t_timing_sched.v:18 -V{t#,#} Resuming processes waiting for @(posedge t.clk2) --V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:48 -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 -V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1____Vfork_2__0 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 4 is active: @([hybrid] __VassignWtmp_h########__0) -V{t#,#} 'act' region trigger index 5 is active: @([hybrid] __VassignWgen_h########__0) --V{t#,#} 'act' region trigger index 6 is active: @([hybrid] t.c1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk1): -V{t#,#} - Process waiting at t/t_timing_sched.v:18 -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -2325,6 +2312,7 @@ -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__nba -V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act @@ -2340,20 +2328,20 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 8 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#} 'act' region trigger index 7 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#} Delayed processes: --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:52 --V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 -V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:50 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:48 -V{t#,#} Awaiting time 79: Process waiting at t/t_timing_sched.v:17 -V{t#,#} Awaiting time 88: Process waiting at t/t_timing_sched.v:13 -V{t#,#} Resuming delayed processes --V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:52 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:50 *-* All Finished *-* -V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 --V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:50 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:48 -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_phase__act diff --git a/test_regress/t/t_timing_sched.v b/test_regress/t/t_timing_sched.v index 6bb015fb9..596616a26 100644 --- a/test_regress/t/t_timing_sched.v +++ b/test_regress/t/t_timing_sched.v @@ -37,14 +37,12 @@ module t; `endif end - // verilator lint_off UNOPTFLAT int c1 = 0; int c2 = 0; always @(b2, c1) begin c2 = c1 >> 3; c1 = b2 << 3; end - // verilator lint_on UNOPTFLAT always @(posedge clk1) if (a2 != a1 << 1) $stop; always @(posedge clk2) #1 if (b2 != b1 << 2) $stop; diff --git a/test_regress/t/t_unopt_array.py b/test_regress/t/t_unopt_array.py index c37bc018e..51e61aa39 100755 --- a/test_regress/t/t_unopt_array.py +++ b/test_regress/t/t_unopt_array.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["-Wno-UNOPTFLAT"]) +test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_unopt_array_csplit.py b/test_regress/t/t_unopt_array_csplit.py index ce0e4bae6..ec16097b5 100755 --- a/test_regress/t/t_unopt_array_csplit.py +++ b/test_regress/t/t_unopt_array_csplit.py @@ -12,7 +12,10 @@ import vltest_bootstrap test.scenarios('vlt_all') test.top_filename = "t/t_unopt_array.v" -test.compile(v_flags2=["--trace-vcd --output-split 1 --output-split-cfuncs 1 -Wno-UNOPTFLAT"]) +test.compile(v_flags2=[ + "--trace-vcd", "--output-split", "1", "--output-split-cfuncs", "1", "-Wno-UNOPTFLAT", + "-fno-dfg" +]) test.execute() diff --git a/test_regress/t/t_unopt_array_typedef.py b/test_regress/t/t_unopt_array_typedef.py index 809e92873..fb7da0974 100755 --- a/test_regress/t/t_unopt_array_typedef.py +++ b/test_regress/t/t_unopt_array_typedef.py @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('simulator') test.top_filename = "t/t_unopt_array.v" -test.compile(verilator_flags2=["-Wno-UNOPTFLAT +define+USE_TYPEDEF"]) +test.compile(verilator_flags2=["-Wno-UNOPTFLAT", "+define+USE_TYPEDEF", "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_unopt_bound.py b/test_regress/t/t_unopt_bound.py index d4f986441..dc6cab445 100755 --- a/test_regress/t/t_unopt_bound.py +++ b/test_regress/t/t_unopt_bound.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=["-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_unopt_combo.py b/test_regress/t/t_unopt_combo.py index 64265ff66..9027b0470 100755 --- a/test_regress/t/t_unopt_combo.py +++ b/test_regress/t/t_unopt_combo.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(v_flags2=['+define+ALLOW_UNOPT']) +test.compile(v_flags2=['+define+ALLOW_UNOPT', "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_unopt_combo_bad.out b/test_regress/t/t_unopt_combo_bad.out index bee8bc003..8cd58f59f 100644 --- a/test_regress/t/t_unopt_combo_bad.out +++ b/test_regress/t/t_unopt_combo_bad.out @@ -4,7 +4,7 @@ ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest ... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message. t/t_unopt_combo.v:23:25: Example path: t.b - t/t_unopt_combo.v:137:14: Example path: ASSIGNW + t/t_unopt_combo.v:124:4: Example path: ALWAYS t/t_unopt_combo.v:24:25: Example path: t.c t/t_unopt_combo.v:81:4: Example path: ALWAYS t/t_unopt_combo.v:23:25: Example path: t.b diff --git a/test_regress/t/t_unopt_combo_bad.py b/test_regress/t/t_unopt_combo_bad.py index 1592b6d48..c659a17b7 100755 --- a/test_regress/t/t_unopt_combo_bad.py +++ b/test_regress/t/t_unopt_combo_bad.py @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('simulator') test.top_filename = "t/t_unopt_combo.v" -test.compile(v_flags2=['+define+ATTRIBUTES'], +test.compile(v_flags2=['+define+ATTRIBUTES', "-fno-dfg"], fails=test.vlt_all, expect_filename=test.golden_filename) diff --git a/test_regress/t/t_unopt_combo_isolate.py b/test_regress/t/t_unopt_combo_isolate.py index 641974899..fa5b040b2 100755 --- a/test_regress/t/t_unopt_combo_isolate.py +++ b/test_regress/t/t_unopt_combo_isolate.py @@ -14,7 +14,7 @@ test.top_filename = "t/t_unopt_combo.v" out_filename = test.obj_dir + "/V" + test.name + ".tree.json" -test.compile(verilator_flags2=["--no-json-edit-nums +define+ISOLATE --stats"]) +test.compile(verilator_flags2=["--no-json-edit-nums", "+define+ISOLATE", "--stats", "-fno-dfg"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+3') diff --git a/test_regress/t/t_unopt_combo_isolate_vlt.py b/test_regress/t/t_unopt_combo_isolate_vlt.py index 4f3a4988c..70a35c8c2 100755 --- a/test_regress/t/t_unopt_combo_isolate_vlt.py +++ b/test_regress/t/t_unopt_combo_isolate_vlt.py @@ -14,8 +14,9 @@ test.top_filename = "t/t_unopt_combo.v" out_filename = test.obj_dir + "/V" + test.name + ".tree.json" -test.compile( - verilator_flags2=["--no-json-edit-nums --stats", test.t_dir + "/t_unopt_combo_isolate.vlt"]) +test.compile(verilator_flags2=[ + "--no-json-edit-nums", "--stats", test.t_dir + "/t_unopt_combo_isolate.vlt", "-fno-dfg" +]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+3') diff --git a/test_regress/t/t_unopt_combo_waive.py b/test_regress/t/t_unopt_combo_waive.py index 508a70141..34a421ec1 100755 --- a/test_regress/t/t_unopt_combo_waive.py +++ b/test_regress/t/t_unopt_combo_waive.py @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('vlt_all') test.top_filename = "t/t_unopt_combo.v" -test.compile(v_flags2=['+define+ATTRIBUTES', "t/t_unopt_combo.vlt"], +test.compile(v_flags2=['+define+ATTRIBUTES', "t/t_unopt_combo.vlt", "-fno-dfg"], # Passes, as we waived ) diff --git a/test_regress/t/t_unopt_converge_initial_run_bad.py b/test_regress/t/t_unopt_converge_initial_run_bad.py index ebc9607d7..eaa85d927 100755 --- a/test_regress/t/t_unopt_converge_initial_run_bad.py +++ b/test_regress/t/t_unopt_converge_initial_run_bad.py @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('simulator') test.top_filename = "t/t_unopt_converge_initial.v" -test.compile(v_flags2=['+define+ALLOW_UNOPT --output-split 0']) +test.compile(v_flags2=['+define+ALLOW_UNOPT', '--output-split 0', "-fno-dfg"]) if test.vlt_all: test.execute(fails=True, expect_filename=test.golden_filename) diff --git a/test_regress/t/t_unopt_converge_ndbg_bad.py b/test_regress/t/t_unopt_converge_ndbg_bad.py index 65b34b60c..fbc3822fe 100755 --- a/test_regress/t/t_unopt_converge_ndbg_bad.py +++ b/test_regress/t/t_unopt_converge_ndbg_bad.py @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('simulator') test.top_filename = "t/t_unopt_converge.v" -test.compile(v_flags2=['+define+ALLOW_UNOPT'], make_flags=['CPPFLAGS_ADD=-UVL_DEBUG']) +test.compile(v_flags2=['+define+ALLOW_UNOPT', "-fno-dfg"], make_flags=['CPPFLAGS_ADD=-UVL_DEBUG']) if test.vlt_all: test.execute(fails=True, expect_filename=test.golden_filename) diff --git a/test_regress/t/t_unopt_converge_print_bad.py b/test_regress/t/t_unopt_converge_print_bad.py index d38fcc4a0..ac1bfd49d 100755 --- a/test_regress/t/t_unopt_converge_print_bad.py +++ b/test_regress/t/t_unopt_converge_print_bad.py @@ -13,7 +13,7 @@ test.scenarios('simulator') test.top_filename = "t/t_unopt_converge.v" #test.verilated_debug = 1 -test.compile(v_flags2=['+define+ALLOW_UNOPT --output-split 0'], +test.compile(v_flags2=['+define+ALLOW_UNOPT', '--output-split 0', "-fno-dfg"], make_flags=['CPPFLAGS_ADD=-DVL_DEBUG']) if test.vlt_all: diff --git a/test_regress/t/t_unopt_converge_run_bad.py b/test_regress/t/t_unopt_converge_run_bad.py index 846df0093..4d3251a15 100755 --- a/test_regress/t/t_unopt_converge_run_bad.py +++ b/test_regress/t/t_unopt_converge_run_bad.py @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('simulator') test.top_filename = "t/t_unopt_converge.v" -test.compile(v_flags2=['+define+ALLOW_UNOPT --output-split 0']) +test.compile(v_flags2=['+define+ALLOW_UNOPT', '--output-split 0', "-fno-dfg"]) if test.vlt_all: test.execute(fails=True, expect_filename=test.golden_filename) diff --git a/test_regress/t/t_unopt_converge_unopt_bad.out b/test_regress/t/t_unopt_converge_unopt_bad.out index 7aa0e9039..ad410cbb3 100644 --- a/test_regress/t/t_unopt_converge_unopt_bad.out +++ b/test_regress/t/t_unopt_converge_unopt_bad.out @@ -4,6 +4,6 @@ ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest ... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message. t/t_unopt_converge.v:19:11: Example path: x - t/t_unopt_converge.v:23:9: Example path: ASSIGNW + t/t_unopt_converge.v:22:4: Example path: ALWAYS t/t_unopt_converge.v:19:11: Example path: x %Error: Exiting due to diff --git a/test_regress/t/t_unopt_converge_unopt_bad.py b/test_regress/t/t_unopt_converge_unopt_bad.py index 23a3f47d2..75751ea27 100755 --- a/test_regress/t/t_unopt_converge_unopt_bad.py +++ b/test_regress/t/t_unopt_converge_unopt_bad.py @@ -12,6 +12,6 @@ import vltest_bootstrap test.scenarios('vlt_all') test.top_filename = "t/t_unopt_converge.v" -test.compile(fails=True, expect_filename=test.golden_filename) +test.compile(verilator_flags2=["-fno-dfg"], fails=True, expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_unoptflat_simple_2_bad.out b/test_regress/t/t_unoptflat_simple_2_bad.out index 49c85ef34..5bcb5a581 100644 --- a/test_regress/t/t_unoptflat_simple_2_bad.out +++ b/test_regress/t/t_unoptflat_simple_2_bad.out @@ -4,11 +4,11 @@ ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest ... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message. t/t_unoptflat_simple_2.v:16:15: Example path: t.x - t/t_unoptflat_simple_2.v:13:10: Example path: ASSIGNW + t/t_unoptflat_simple_2.v:19:18: Example path: ASSIGNW t/t_unoptflat_simple_2.v:16:15: Example path: t.x ... Widest variables candidate to splitting: - t/t_unoptflat_simple_2.v:16:15: t.x, width 3, circular fanout 1, can split_var + t/t_unoptflat_simple_2.v:16:15: t.x, width 3, circular fanout 2, can split_var ... Candidates with the highest fanout: - t/t_unoptflat_simple_2.v:16:15: t.x, width 3, circular fanout 1, can split_var + t/t_unoptflat_simple_2.v:16:15: t.x, width 3, circular fanout 2, can split_var ... Suggest add /*verilator split_var*/ or /*verilator isolate_assignments*/ to appropriate variables above. %Error: Exiting due to diff --git a/test_regress/t/t_unoptflat_simple_2_bad.py b/test_regress/t/t_unoptflat_simple_2_bad.py index a87556b80..6ed146135 100755 --- a/test_regress/t/t_unoptflat_simple_2_bad.py +++ b/test_regress/t/t_unoptflat_simple_2_bad.py @@ -14,7 +14,7 @@ test.top_filename = "t/t_unoptflat_simple_2.v" # Compile only test.compile(verilator_flags3=[], - verilator_flags2=["--report-unoptflat", "-fno-dfg-break-cycles"], + verilator_flags2=["--report-unoptflat", "-fno-dfg"], fails=True, expect_filename=test.golden_filename) diff --git a/test_regress/t/t_unoptflat_simple_3_bad.py b/test_regress/t/t_unoptflat_simple_3_bad.py index 7c3da3bf6..02fe09491 100755 --- a/test_regress/t/t_unoptflat_simple_3_bad.py +++ b/test_regress/t/t_unoptflat_simple_3_bad.py @@ -13,6 +13,6 @@ test.scenarios('simulator') test.top_filename = "t/t_unoptflat_simple_3.v" # Compile only -test.compile(fails=True) +test.compile(verilator_flags2=["-fno-dfg"], fails=True) test.passes() diff --git a/test_regress/t/t_unoptflat_simple_bad.py b/test_regress/t/t_unoptflat_simple_bad.py index 85da89728..6440259f5 100755 --- a/test_regress/t/t_unoptflat_simple_bad.py +++ b/test_regress/t/t_unoptflat_simple_bad.py @@ -13,6 +13,6 @@ test.scenarios('simulator') test.top_filename = "t/t_unoptflat_simple.v" # Compile only -test.compile(fails=True) +test.compile(verilator_flags2=["-fno-dfg"], fails=True) test.passes()