From 636a6b8cd2c5cb361df2cf32132eb3892c71dfab Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 19 Aug 2025 15:06:38 +0100 Subject: [PATCH] Optimize complex combinational logic in DFG (#6298) This patch adds DfgLogic, which is a vertex that represents a whole, arbitrarily complex combinational AstAlways or AstAssignW in the DfgGraph. Implementing this requires computing the variables live at entry to the AstAlways (variables read by the block), so there is a new ControlFlowGraph data structure and a classical data-flow analysis based live variable analysis to do that at the variable level (as opposed to bit/element level). The actual CFG construction and live variable analysis is best effort, and might fail for currently unhandled constructs or data types. This can be extended later. V3DfgAstToDfg is changed to convert the Ast into an initial DfgGraph containing only DfgLogic, DfgVertexSplice and DfgVertexVar vertices. The DfgLogic are then subsequently synthesized into primitive operations by the new V3DfgSynthesize pass, which is a combination of the old V3DfgAstToDfg conversion and new code to handle AstAlways blocks with complex flow control. V3DfgSynthesize by default will synthesize roughly the same constructs as V3DfgAstToDfg used to handle before, plus any logic that is part of a combinational cycle within the DfgGraph. This enables breaking up these cycles, for which there are extensions to V3DfgBreakCycles in this patch as well. V3DfgSynthesize will then delete all non synthesized or non synthesizable DfgLogic vertices and the rest of the Dfg pipeline is identical, with minor changes to adjust for the changed representation. Because with this change we can now eliminate many more UNOPTFLAT, DFG has been disabled in all the tests that specifically target testing the scheduling and reporting of circular combinational logic. --- docs/gen/ex_DIDNOTCONVERGE_msg.rst | 2 +- docs/guide/exe_verilator.rst | 4 + src/CMakeLists.txt | 5 + src/Makefile_obj.in | 4 + src/V3AstNodeOther.h | 4 + src/V3Cfg.cpp | 90 + src/V3Cfg.h | 270 +++ src/V3CfgBuilder.cpp | 291 +++ src/V3CfgLiveVariables.cpp | 240 +++ src/V3Dfg.cpp | 242 ++- src/V3Dfg.h | 182 +- src/V3DfgAstToDfg.cpp | 1041 ++-------- src/V3DfgBreakCycles.cpp | 130 +- src/V3DfgContext.h | 158 +- src/V3DfgDecomposition.cpp | 1 + src/V3DfgDfgToAst.cpp | 133 +- src/V3DfgOptimizer.cpp | 8 +- src/V3DfgPasses.cpp | 41 +- src/V3DfgPasses.h | 4 + src/V3DfgPeephole.cpp | 93 +- src/V3DfgPeepholePatterns.h | 4 +- src/V3DfgRegularize.cpp | 14 +- src/V3DfgSynthesize.cpp | 1696 +++++++++++++++++ src/V3DfgVertices.h | 240 ++- src/V3EmitV.cpp | 10 +- src/V3EmitV.h | 1 + src/V3Options.cpp | 1 + src/V3Options.h | 2 + src/astgen | 3 +- test_regress/t/t_balance_cats.v | 2 - test_regress/t/t_clk_condflop.v | 2 - test_regress/t/t_comb_input_1.py | 2 +- test_regress/t/t_comb_input_2.py | 2 +- .../t/t_comb_loop_through_unpacked_array.py | 2 +- test_regress/t/t_detectarray_1.py | 2 +- test_regress/t/t_detectarray_2.py | 2 +- test_regress/t/t_detectarray_3.py | 2 +- test_regress/t/t_dfg_break_cycles.py | 7 +- test_regress/t/t_dfg_break_cycles.v | 33 + test_regress/t/t_dfg_multidriver_dfg_bad.out | 119 +- test_regress/t/t_dfg_multidriver_dfg_bad.py | 4 +- test_regress/t/t_dfg_multidriver_dfg_bad.v | 31 +- test_regress/t/t_dfg_peephole.py | 1 + test_regress/t/t_dfg_peephole.v | 20 +- test_regress/t/t_dfg_synthesis.cpp | 60 + test_regress/t/t_dfg_synthesis.py | 99 + test_regress/t/t_dfg_synthesis.v | 228 +++ test_regress/t/t_dist_docs_summary.py | 1 + test_regress/t/t_dpi_qw.py | 2 +- test_regress/t/t_func_crc.py | 2 +- test_regress/t/t_hier_block.v | 2 - test_regress/t/t_hier_block_chained.py | 5 +- .../t/t_lint_always_comb_multidriven_bad.out | 28 +- ...mb_multidriven_compile_public_flat_bad.out | 51 + ...omb_multidriven_compile_public_flat_bad.py | 2 +- test_regress/t/t_lint_didnotconverge_bad.out | 2 +- test_regress/t/t_lint_inherit.py | 2 +- test_regress/t/t_order.v | 2 - test_regress/t/t_order_clkinst.py | 2 +- test_regress/t/t_order_comboclkloop.py | 2 +- test_regress/t/t_order_comboloop.py | 2 +- test_regress/t/t_order_doubleloop.py | 2 +- test_regress/t/t_order_first.v | 4 - test_regress/t/t_order_multialways.v | 2 - test_regress/t/t_order_quad.py | 4 +- test_regress/t/t_order_wireloop.py | 4 +- test_regress/t/t_split_var_0.py | 2 +- test_regress/t/t_split_var_0.v | 3 - test_regress/t/t_split_var_1_bad.py | 4 +- test_regress/t/t_split_var_2_trace.py | 5 +- test_regress/t/t_split_var_3_wreal.py | 2 +- test_regress/t/t_split_var_4.py | 2 +- test_regress/t/t_split_var_5.py | 2 +- test_regress/t/t_timing_debug1.out | 304 ++- test_regress/t/t_timing_sched.v | 2 - test_regress/t/t_unopt_array.py | 2 +- test_regress/t/t_unopt_array_csplit.py | 5 +- test_regress/t/t_unopt_array_typedef.py | 2 +- test_regress/t/t_unopt_bound.py | 2 +- test_regress/t/t_unopt_combo.py | 2 +- test_regress/t/t_unopt_combo_bad.out | 2 +- test_regress/t/t_unopt_combo_bad.py | 2 +- test_regress/t/t_unopt_combo_isolate.py | 2 +- test_regress/t/t_unopt_combo_isolate_vlt.py | 5 +- test_regress/t/t_unopt_combo_waive.py | 2 +- .../t/t_unopt_converge_initial_run_bad.py | 2 +- test_regress/t/t_unopt_converge_ndbg_bad.py | 2 +- test_regress/t/t_unopt_converge_print_bad.py | 2 +- test_regress/t/t_unopt_converge_run_bad.py | 2 +- test_regress/t/t_unopt_converge_unopt_bad.out | 2 +- test_regress/t/t_unopt_converge_unopt_bad.py | 2 +- test_regress/t/t_unoptflat_simple_2_bad.out | 6 +- test_regress/t/t_unoptflat_simple_2_bad.py | 2 +- test_regress/t/t_unoptflat_simple_3_bad.py | 2 +- test_regress/t/t_unoptflat_simple_bad.py | 2 +- 95 files changed, 4511 insertions(+), 1518 deletions(-) create mode 100644 src/V3Cfg.cpp create mode 100644 src/V3Cfg.h create mode 100644 src/V3CfgBuilder.cpp create mode 100644 src/V3CfgLiveVariables.cpp create mode 100644 src/V3DfgSynthesize.cpp create mode 100644 test_regress/t/t_dfg_synthesis.cpp create mode 100755 test_regress/t/t_dfg_synthesis.py create mode 100644 test_regress/t/t_dfg_synthesis.v create mode 100644 test_regress/t/t_lint_always_comb_multidriven_compile_public_flat_bad.out 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()