From 1c4c6c9e9d3a6dd7a685941d1bfc081fbf95bfd1 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 17 Sep 2026 23:11:26 +0200 Subject: [PATCH] Improve V3Split (#8385) Various cleanups, simplifications and improvements: - Build the output bottom up, eliminating AstSplitPlaceholder - Replace the 'ignore step' pruning with removing the vertices and edges - Treat an NBA written variable as a block input, enabling more splits - Move instead of clone leaf statements Overall V3Split is faster, uses less memory, can do more splits, is simpler algorithmically, and has half the lines of code in V3Split Most notably this can now be split, which could not be before: ```systemverilog always @(posedge clk) begin a <= !a; if (a) b <= c; end ``` t_x_rand_mt_stability* pin $random values, but there is one random seed per C thread, so they depend on which thread runs the block. More splitting moved that block onto a different thread, so force a single MTask in those tests to keep them stable. Some property/assert test changed hit counts due to races, but are now closer to what might be expected. --- src/V3AstNodeStmt.h | 7 - src/V3Split.cpp | 1013 ++++++----------- test_regress/t/t_always_reorder.v | 5 +- test_regress/t/t_assert_always_unbounded.v | 11 +- .../t/t_interface_virtual_controlflow.py | 2 +- .../t/{t_always_nosplit.py => t_opt_split.py} | 2 +- .../t/{t_always_split.v => t_opt_split.v} | 71 +- ...ways_split_cond.py => t_opt_split_cond.py} | 0 ...always_split_cond.v => t_opt_split_cond.v} | 0 .../{t_always_split.py => t_opt_split_no.py} | 10 +- .../{t_always_nosplit.v => t_opt_split_no.v} | 65 +- ..._always_splitord.py => t_opt_split_ord.py} | 0 ...{t_always_splitord.v => t_opt_split_ord.v} | 0 ...always_split_rst.py => t_opt_split_rst.py} | 2 +- ...t_always_split_rst.v => t_opt_split_rst.v} | 0 test_regress/t/t_prop_always.v | 22 +- test_regress/t/t_prop_always_wide.v | 12 +- test_regress/t/t_prop_followed_by.v | 4 +- test_regress/t/t_prop_s_always_liveness.v | 2 +- test_regress/t/t_property_if_else.v | 2 +- test_regress/t/t_property_nfa_counts.py | 3 +- ...property_nfa_stop_error_limit_crossing.out | 1 - test_regress/t/t_protect_ids_key.out | 4 - test_regress/t/t_x_rand_mt_stability.out | 4 +- test_regress/t/t_x_rand_mt_stability.py | 6 +- test_regress/t/t_x_rand_mt_stability_add.out | 4 +- test_regress/t/t_x_rand_mt_stability_add.py | 5 +- .../t/t_x_rand_mt_stability_add_trace.out | 4 +- .../t/t_x_rand_mt_stability_add_trace.py | 5 +- .../t/t_x_rand_mt_stability_trace.out | 4 +- test_regress/t/t_x_rand_mt_stability_trace.py | 5 +- .../t/t_x_rand_mt_stability_zeros.out | 4 +- test_regress/t/t_x_rand_mt_stability_zeros.py | 5 +- 33 files changed, 533 insertions(+), 751 deletions(-) rename test_regress/t/{t_always_nosplit.py => t_opt_split.py} (98%) rename test_regress/t/{t_always_split.v => t_opt_split.v} (50%) rename test_regress/t/{t_always_split_cond.py => t_opt_split_cond.py} (100%) rename test_regress/t/{t_always_split_cond.v => t_opt_split_cond.v} (100%) rename test_regress/t/{t_always_split.py => t_opt_split_no.py} (80%) rename test_regress/t/{t_always_nosplit.v => t_opt_split_no.v} (71%) rename test_regress/t/{t_always_splitord.py => t_opt_split_ord.py} (100%) rename test_regress/t/{t_always_splitord.v => t_opt_split_ord.v} (100%) rename test_regress/t/{t_always_split_rst.py => t_opt_split_rst.py} (98%) rename test_regress/t/{t_always_split_rst.v => t_opt_split_rst.v} (100%) diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index e220e1fa6..c69f71a98 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -1221,13 +1221,6 @@ public: ASTGEN_MEMBERS_AstSetuphold; bool sameNode(const AstNode* /*samep*/) const override { return true; } }; -class AstSplitPlaceholder final : public AstNodeStmt { -public: - // Dummy node used within V3Split; never exists outside of V3Split. - explicit AstSplitPlaceholder(FileLine* fl) - : ASTGEN_SUPER_SplitPlaceholder(fl) {} - ASTGEN_MEMBERS_AstSplitPlaceholder; -}; class AstStackTraceT final : public AstNodeStmt { // $stacktrace used as task public: diff --git a/src/V3Split.cpp b/src/V3Split.cpp index 230459a75..371bdfc36 100644 --- a/src/V3Split.cpp +++ b/src/V3Split.cpp @@ -1,6 +1,6 @@ // -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* -// DESCRIPTION: Verilator: Break always into separate statements to reduce temps +// DESCRIPTION: Verilator: Break always into separate statements // // Code available from: https://verilator.org // @@ -15,23 +15,10 @@ //************************************************************************* // V3Split transformation: // -// splitAll() splits large always blocks into smaller always blocks -// when possible (but does not change the order of statements relative -// to one another.) -// -// The scoreboard tracks data deps as follows: -// -// ALWAYS -// ASSIGN ({var} <= {cons}) -// Record as generating var_DLY (independent of use of var), consumers -// ASSIGN ({var} = {cons} -// Record generator and consumer -// Any var that is only consumed can be ignored. -// Then we split into separate ALWAYS blocks. -// -// The scoreboard includes innards of if/else nodes also. Splitting is no -// longer limited to top-level statements, we can split within if-else -// blocks. We want to be able to split this: +// splitAll() splits large always blocks into smaller always blocks when +// possible, without changing the order of dependent statements relative to +// one another. Splitting is not limited to top-level statements, if-else +// blocks can also be split, so that: // // always @ (...) begin // if (reset) begin @@ -46,10 +33,31 @@ // end // end // -// ...into a separate block for each of a, b, and so on. Even though this -// requires duplicating the conditional many times, it's usually -// better. Later modules (V3Gate, V3Order) run faster if they aren't -// handling enormous blocks with long lists of inputs and outputs. +// becomes a separate block for each of a, b, and so on. Even though this +// requires duplicating the conditional many times, it's usually better as it +// reduces ordering constraints, and later optimizations can merge +// conditionals. +// +// To find what must stay together, a graph is built per always block, holding +// a vertex per 'leaf' and 'if' statement, and up to two vertices per variable. +// Statements in the same connected component must stay in one block, and each +// component then becomes a block of its own. The edges are: +// +// - Blocking write: variable -> statement. Such a write is observable within +// the block, so the readers of the variable stay with the writer. +// - Non-blocking write: a separate 'post' vertex of the variable -> statement. +// All writers of a variable stay together, but the readers, which see the +// value from before the NBA commits, are not held together with them. +// - Read: statement -> variable. For an 'if', only the reads in its own +// condition count, not those in its branches. +// - Impure statement: statement -> a vertex shared by all of them, so that +// $display, DPI calls, etc stay in one block, in order. +// +// A variable with no blocking write is an input to the block, so its vertex is +// removed, and with it the dependencies on it, as two statements both reading +// an input need not stay together. An 'if' left with no dependencies of its +// own is removed likewise, so that the statements under it can separate, each +// taking a copy of the condition. // //************************************************************************* @@ -60,14 +68,11 @@ #include "V3Graph.h" #include "V3Stats.h" -#include -#include +#include #include VL_DEFINE_DEBUG_FUNCTIONS; -namespace { - //###################################################################### // Support classes @@ -79,738 +84,374 @@ protected: SplitNodeVertex(V3Graph* graphp, AstNode* nodep) : V3GraphVertex{graphp} , m_nodep{nodep} {} - ~SplitNodeVertex() override = default; // ACCESSORS - // Do not make accessor for nodep(), It may change due to - // reordering a lower block, but we don't repair it - string name() const override { return cvtToHex(m_nodep) + ' ' + m_nodep->prettyTypeName(); } - FileLine* fileline() const override { return nodep()->fileline(); } + std::string name() const override { + return cvtToHex(m_nodep) + ' ' + m_nodep->prettyTypeName(); + } public: - virtual AstNode* nodep() const { return m_nodep; } + AstNode* nodep() const { return m_nodep; } }; -class SplitPliVertex final : public SplitNodeVertex { - VL_RTTI_IMPL(SplitPliVertex, SplitNodeVertex) +class SplitImpureVertex final : public SplitNodeVertex { + VL_RTTI_IMPL(SplitImpureVertex, SplitNodeVertex) + + std::string name() const override { return "*IMPURE*"; } + std::string dotColor() const override { return "green"; } + public: - explicit SplitPliVertex(V3Graph* graphp, AstNode* nodep) + explicit SplitImpureVertex(V3Graph* graphp, AstNode* nodep) : SplitNodeVertex{graphp, nodep} {} - ~SplitPliVertex() override = default; - string name() const override VL_MT_STABLE { return "*PLI*"; } - string dotColor() const override { return "green"; } }; -class SplitLogicVertex final : public SplitNodeVertex { - VL_RTTI_IMPL(SplitLogicVertex, SplitNodeVertex) +class SplitStmtVertex final : public SplitNodeVertex { + VL_RTTI_IMPL(SplitStmtVertex, SplitNodeVertex) + + std::string dotColor() const override { return "yellow"; } + public: - SplitLogicVertex(V3Graph* graphp, AstNode* nodep) + SplitStmtVertex(V3Graph* graphp, AstNode* nodep) : SplitNodeVertex{graphp, nodep} {} - ~SplitLogicVertex() override = default; - string dotColor() const override { return "yellow"; } }; class SplitVarStdVertex final : public SplitNodeVertex { VL_RTTI_IMPL(SplitVarStdVertex, SplitNodeVertex) + + std::string dotColor() const override { return "skyblue"; } + public: - SplitVarStdVertex(V3Graph* graphp, AstNode* nodep) - : SplitNodeVertex{graphp, nodep} {} - ~SplitVarStdVertex() override = default; - string dotColor() const override { return "skyblue"; } + SplitVarStdVertex(V3Graph* graphp, AstVarScope* vscp) + : SplitNodeVertex{graphp, vscp} {} }; class SplitVarPostVertex final : public SplitNodeVertex { VL_RTTI_IMPL(SplitVarPostVertex, SplitNodeVertex) -public: - SplitVarPostVertex(V3Graph* graphp, AstNode* nodep) - : SplitNodeVertex{graphp, nodep} {} - ~SplitVarPostVertex() override = default; - string name() const override { return "POST "s + SplitNodeVertex::name(); } - string dotColor() const override { return "CadetBlue"; } -}; -//###################################################################### -// Edge types - -class SplitEdge VL_NOT_FINAL : public V3GraphEdge { - VL_RTTI_IMPL(SplitEdge, V3GraphEdge) - uint32_t m_ignoreInStep = 0; // Step number that if set to, causes this edge to be ignored - static uint32_t s_stepNum; // Global step number -protected: - static constexpr int WEIGHT_NORMAL = 10; - SplitEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, int weight, - bool cutable = CUTABLE) - : V3GraphEdge{graphp, fromp, top, weight, cutable} {} - ~SplitEdge() override = default; + std::string name() const override { return "POST "s + SplitNodeVertex::name(); } + std::string dotColor() const override { return "CadetBlue"; } public: - // Iterator for graph functions - static void incrementStep() { ++s_stepNum; } - bool ignoreThisStep() const { return m_ignoreInStep == s_stepNum; } - void setIgnoreThisStep() { m_ignoreInStep = s_stepNum; } - virtual bool followScoreboard() const = 0; - static bool followScoreboard(const V3GraphEdge* edgep) { - const SplitEdge* const oedgep = static_cast(edgep); - if (oedgep->ignoreThisStep()) return false; - return oedgep->followScoreboard(); - } - static bool followCyclic(const V3GraphEdge* edgep) { - const SplitEdge* const oedgep = static_cast(edgep); - return (!oedgep->ignoreThisStep()); - } - string dotStyle() const override { - return ignoreThisStep() ? "dotted" : V3GraphEdge::dotStyle(); - } -}; -uint32_t SplitEdge::s_stepNum = 0; - -class SplitPostEdge final : public SplitEdge { - VL_RTTI_IMPL(SplitPostEdge, SplitEdge) -public: - SplitPostEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) - : SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {} - ~SplitPostEdge() override = default; - bool followScoreboard() const override { return false; } - string dotColor() const override { return "khaki"; } + SplitVarPostVertex(V3Graph* graphp, AstVarScope* vscp) + : SplitNodeVertex{graphp, vscp} {} }; -class SplitLVEdge final : public SplitEdge { - VL_RTTI_IMPL(SplitLVEdge, SplitEdge) -public: - SplitLVEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) - : SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {} - ~SplitLVEdge() override = default; - bool followScoreboard() const override { return true; } - string dotColor() const override { return "yellowGreen"; } -}; +class SplitVisitor final : public VNVisitor { + // NODE STATE - Only under AstAlways + // AstVarScope::user1p -> SplitVarStdVertex*: Regular program-flow variable vertex + // AstVarScope::user2p -> SplitVarPostVertex*: NBA written delayed variable vertex + // Ast{StmtIsh}::user1p -> SplitStmtVertex* -class SplitRVEdge final : public SplitEdge { - VL_RTTI_IMPL(SplitRVEdge, SplitEdge) -public: - SplitRVEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) - : SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {} - ~SplitRVEdge() override = default; - bool followScoreboard() const override { return true; } - string dotColor() const override { return "green"; } -}; - -class SplitScorebdEdge final : public SplitEdge { - VL_RTTI_IMPL(SplitScorebdEdge, SplitEdge) -public: - SplitScorebdEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) - : SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {} - ~SplitScorebdEdge() override = default; - bool followScoreboard() const override { return true; } - string dotColor() const override { return "blue"; } -}; - -class SplitStrictEdge final : public SplitEdge { - VL_RTTI_IMPL(SplitStrictEdge, SplitEdge) - // A strict order, based on the original statement order in the graph - // The only non-cutable edge type -public: - SplitStrictEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) - : SplitEdge{graphp, fromp, top, WEIGHT_NORMAL, NOT_CUTABLE} {} - ~SplitStrictEdge() override = default; - bool followScoreboard() const override { return true; } - string dotColor() const override { return "blue"; } -}; - -using ColorSet = std::unordered_set; -using AlwaysVec = std::vector; - -class IfColorVisitor final : public VNVisitorConst { - // MEMBERS - ColorSet m_colors; // All colors in the original always block - - std::vector m_ifStack; // Stack of nested if-statements we're currently processing - - std::unordered_map - m_ifColors; // Map each if-statement to the set of colors (split blocks) - // that will get a copy of that if-statement - - // CONSTRUCTORS -public: - // Visit through *nodep and map each AstNodeIf within to the set of - // colors it will participate in. Also find the whole set of colors. - explicit IfColorVisitor(AstAlways* nodep) { iterateConst(nodep); } - ~IfColorVisitor() override = default; - - // METHODS - const ColorSet& colors() const { return m_colors; } - const ColorSet& colors(AstNodeIf* nodep) const { - const auto it = m_ifColors.find(nodep); - UASSERT_OBJ(it != m_ifColors.end(), nodep, "Node missing from split color() map"); - return it->second; - } - -private: - void trackNode(AstNode* nodep) { - if (nodep->user3p()) { - const SplitLogicVertex* const vertexp - = reinterpret_cast(nodep->user3p()); - const uint32_t color = vertexp->color(); - m_colors.insert(color); - UINFO(8, " SVL " << vertexp << " has color " << color); - - // Record that all containing ifs have this color. - for (auto it = m_ifStack.cbegin(); it != m_ifStack.cend(); ++it) { - m_ifColors[*it].insert(color); - } - } - } - -protected: - void visit(AstNodeIf* nodep) override { - m_ifStack.push_back(nodep); - trackNode(nodep); - iterateChildrenConst(nodep); - m_ifStack.pop_back(); - } - void visit(AstNode* nodep) override { - trackNode(nodep); - iterateChildrenConst(nodep); - } - -private: - VL_UNCOPYABLE(IfColorVisitor); -}; - -class EmitSplitVisitor final : public VNVisitor { - // MEMBERS - const AstAlways* const m_origAlwaysp; // Block that *this will split - const IfColorVisitor* const m_ifColorp; // Digest of results of prior coloring - - // Map each color to our current place within the color's new always - std::unordered_map m_addAfter; - - AlwaysVec* const m_newBlocksp; // Split always blocks we have generated - - // CONSTRUCTORS -public: - // EmitSplitVisitor visits through always block *nodep - // and generates its split blocks, writing the split blocks - // into *newBlocksp. - EmitSplitVisitor(AstAlways* nodep, const IfColorVisitor* ifColorp, AlwaysVec* newBlocksp) - : m_origAlwaysp{nodep} - , m_ifColorp{ifColorp} - , m_newBlocksp{newBlocksp} { - UINFO(6, " splitting always " << nodep); - } - - ~EmitSplitVisitor() override = default; - - // METHODS - void go() { - // Create a new always for each color - const ColorSet& colors = m_ifColorp->colors(); - for (const unsigned int color : colors) { - // We don't need to clone m_origAlwaysp->sensesp() here; - // V3Activate already moved it to a parent node. - AstAlways* const alwaysp - = new AstAlways{m_origAlwaysp->fileline(), VAlwaysKwd::ALWAYS, nullptr, nullptr}; - // Put a placeholder node into stmtp to track our position. - // We'll strip these out after the blocks are fully cloned. - AstSplitPlaceholder* const placeholderp = makePlaceholderp(); - alwaysp->addStmtsp(placeholderp); - m_addAfter[color] = placeholderp; - m_newBlocksp->push_back(alwaysp); - } - // Scan the body of the always. We'll handle if/else - // specially, everything else is a leaf node that we can - // just clone into one of the split always blocks. - iterateAndNextNull(m_origAlwaysp->stmtsp()); - } - -protected: - AstSplitPlaceholder* makePlaceholderp() { - return new AstSplitPlaceholder{m_origAlwaysp->fileline()}; - } - - void visit(AstNode* nodep) override { - // Anything that's not an if/else we assume is a leaf - // (that is, something we won't split.) Don't visit further - // into the leaf. - // - // A leaf might contain another if, for example a WHILE loop - // could contain an if. We can't split WHILE loops, so we - // won't split its nested if either. Just treat it as part - // of the leaf; do not visit further; do not reach visit(AstNodeIf*) - // for such an embedded if. - - // Each leaf must have a user3p - UASSERT_OBJ(nodep->user3p(), nodep, "null user3p in V3Split leaf"); - - // Clone the leaf into its new always block - const SplitLogicVertex* const vxp = reinterpret_cast(nodep->user3p()); - const uint32_t color = vxp->color(); - AstNode* const clonedp = nodep->cloneTree(false); - m_addAfter[color]->addNextHere(clonedp); - m_addAfter[color] = clonedp; - } - - void visit(AstNodeIf* nodep) override { - const ColorSet& colors = m_ifColorp->colors(nodep); - using CloneMap = std::unordered_map; - CloneMap clones; - - for (const unsigned int color : colors) { - // Clone this if into its set of split blocks - AstSplitPlaceholder* const if_placeholderp = makePlaceholderp(); - AstSplitPlaceholder* const else_placeholderp = makePlaceholderp(); - // We check for condition isPure earlier, but may still clone a - // non-pure to separate from other pure statements. - AstIf* const clonep = new AstIf{nodep->fileline(), nodep->condp()->cloneTree(true), - if_placeholderp, else_placeholderp}; - const AstIf* const origp = VN_CAST(nodep, If); - if (origp) { - // Preserve pragmas from unique if's - // so assertions work properly - clonep->uniquePragma(origp->uniquePragma()); - clonep->unique0Pragma(origp->unique0Pragma()); - clonep->priorityPragma(origp->priorityPragma()); - } - clones[color] = clonep; - m_addAfter[color]->addNextHere(clonep); - m_addAfter[color] = if_placeholderp; - } - - iterateAndNextNull(nodep->thensp()); - - for (const auto& color : colors) m_addAfter[color] = clones[color]->elsesp(); - - iterateAndNextNull(nodep->elsesp()); - - for (const auto& color : colors) m_addAfter[color] = clones[color]; - } - -private: - VL_UNCOPYABLE(EmitSplitVisitor); -}; - -class RemovePlaceholdersVisitor final : public VNVisitor { - // MEMBERS - bool m_isPure = true; - int m_emptyAlways = 0; - - // CONSTRUCTORS - RemovePlaceholdersVisitor() = default; - ~RemovePlaceholdersVisitor() override = default; - - // VISITORS - void visit(AstSplitPlaceholder* nodep) override { pushDeletep(nodep->unlinkFrBack()); } - void visit(AstNodeIf* nodep) override { - VL_RESTORER(m_isPure); - m_isPure = true; - iterateChildren(nodep); - if (!nodep->thensp() && !nodep->elsesp() && m_isPure) pushDeletep(nodep->unlinkFrBack()); - } - void visit(AstAlways* nodep) override { - VL_RESTORER(m_isPure); - m_isPure = true; - iterateChildren(nodep); - if (m_isPure) { - bool emptyOrCommentOnly = true; - for (AstNode* bodysp = nodep->stmtsp(); bodysp; bodysp = bodysp->nextp()) { - // If this always block contains only AstComment, remove here. - // V3Gate will remove anyway. - if (!VN_IS(bodysp, Comment)) { - emptyOrCommentOnly = false; - break; - } - } - if (emptyOrCommentOnly) { - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - ++m_emptyAlways; - } - } - } - void visit(AstNode* nodep) override { - m_isPure &= nodep->isPure(); - iterateChildren(nodep); // must visit regardless of m_isPure to remove placeholders - } - - VL_UNCOPYABLE(RemovePlaceholdersVisitor); - -public: - static int exec(AstAlways* nodep) { - RemovePlaceholdersVisitor visitor; - visitor.iterate(nodep); - return visitor.m_emptyAlways; - } -}; - -//###################################################################### -// Split class functions - -class SplitReorderBaseVisitor VL_NOT_FINAL : public VNVisitor { // NODE STATE - // AstVarScope::user1p -> Var SplitNodeVertex* for usage var, 0=not set yet - // AstVarScope::user2p -> Var SplitNodeVertex* for delayed assignment var, 0=not set yet - // Ast*::user3p -> Statement SplitLogicVertex* (temporary only) - // Ast*::user4 -> Current ordering number (reorderBlock usage) - const VNUser1InUse m_inuser1; - const VNUser2InUse m_inuser2; + // AstAlways::user3 -> bool: Block created by splitting, needs no further splitting const VNUser3InUse m_inuser3; - const VNUser4InUse m_inuser4; -protected: // STATE - string m_noReorderWhy; // Reason we can't reorder - std::vector m_stmtStackps; // Current statements being tracked - SplitPliVertex* m_pliVertexp; // Element specifying PLI ordering - V3Graph m_graph; // Scoreboard of var usages/dependencies - bool m_inDly; // Inside ASSIGNDLY - - // CONSTRUCTORS -public: - SplitReorderBaseVisitor() { scoreboardClear(); } - ~SplitReorderBaseVisitor() override = default; - - // METHODS -protected: - void scoreboardClear() { - // VV***** We reset user1p() and user2p on each block!!! - m_inDly = false; - m_graph.clear(); - m_stmtStackps.clear(); - m_pliVertexp = nullptr; - m_noReorderWhy = ""; - AstNode::user1ClearTree(); - AstNode::user2ClearTree(); - AstNode::user3ClearTree(); - AstNode::user4ClearTree(); - } - -private: - void scoreboardPli(AstNode* nodep) { - // Order all PLI statements with other PLI statements - // This ensures $display's and such remain in proper order - // We don't prevent splitting out other non-pli statements, however. - if (!m_pliVertexp) { - m_pliVertexp = new SplitPliVertex{&m_graph, nodep}; // m_graph.clear() will delete it - } - for (const auto& vtxp : m_stmtStackps) { - // Both ways... - new SplitScorebdEdge{&m_graph, vtxp, m_pliVertexp}; - new SplitScorebdEdge{&m_graph, m_pliVertexp, vtxp}; - } - } - void scoreboardPushStmt(AstNode* nodep) { - // UINFO(9, " push " << nodep); - SplitLogicVertex* const vertexp = new SplitLogicVertex{&m_graph, nodep}; - m_stmtStackps.push_back(vertexp); - UASSERT_OBJ(!nodep->user3p(), nodep, "user3p should not be used; cleared in processBlock"); - nodep->user3p(vertexp); - } - void scoreboardPopStmt() { - // UINFO(9, " pop"); - UASSERT(!m_stmtStackps.empty(), "Stack underflow"); - m_stmtStackps.pop_back(); - } - -protected: - void scanBlock(AstNode* nodep) { - // Iterate across current block, making the scoreboard - for (AstNode* nextp = nodep; nextp; nextp = nextp->nextp()) { - scoreboardPushStmt(nextp); - iterate(nextp); - scoreboardPopStmt(); - } - } - - void pruneDepsOnInputs() { - for (V3GraphVertex& vertex : m_graph.vertices()) { - if (vertex.outEmpty() && vertex.is()) { - if (debug() >= 9) { - const SplitVarStdVertex& sVtx = static_cast(vertex); - UINFO(0, "Will prune deps on var " << sVtx.nodep()); - sVtx.nodep()->dumpTree("- "); - } - for (V3GraphEdge& edge : vertex.inEdges()) { - SplitEdge& oedge = static_cast(edge); - oedge.setIgnoreThisStep(); - } - } - } - } - - virtual void makeRvalueEdges(SplitVarStdVertex* vstdp) = 0; - - // VISITORS - void visit(AstAlways* nodep) override = 0; - void visit(AstNodeIf* nodep) override = 0; - - // We don't do AstLoop, due to the standard question of what is before vs. after - - void visit(AstExprStmt* nodep) override { - VL_RESTORER(m_inDly); - m_inDly = false; - iterateChildren(nodep); - } - void visit(AstAssignDly* nodep) override { - UINFO(4, " ASSIGNDLY " << nodep); - iterate(nodep->rhsp()); - VL_RESTORER(m_inDly); - m_inDly = true; - iterate(nodep->lhsp()); - } - void visit(AstVarRef* nodep) override { - if (!m_stmtStackps.empty()) { - AstVarScope* const vscp = nodep->varScopep(); - UASSERT_OBJ(vscp, nodep, "Not linked"); - if (!nodep->varp()->isConst()) { // Constant lookups can be ignored - // --- - // NOTE: Formerly at this location we would avoid - // splitting or reordering if the variable is public. - // - // However, it should be perfectly safe to split an - // always block containing a public variable. - // Neither operation should perturb PLI's view of - // the variable. - // - // Former code: - // - // if (nodep->varp()->isSigPublic()) { - // // Public signals shouldn't be changed, - // // pli code might be messing with them - // scoreboardPli(nodep); - // } - // --- - - // Create vertexes for variable - if (!vscp->user1p()) { - SplitVarStdVertex* const vstdp = new SplitVarStdVertex{&m_graph, vscp}; - vscp->user1p(vstdp); - } - SplitVarStdVertex* const vstdp - = reinterpret_cast(vscp->user1p()); - - // SPEEDUP: We add duplicate edges, that should be fixed - if (m_inDly && nodep->access().isWriteOrRW()) { - UINFO(4, " VARREFDLY: " << nodep); - // Delayed variable is different from non-delayed variable - if (!vscp->user2p()) { - SplitVarPostVertex* const vpostp = new SplitVarPostVertex{&m_graph, vscp}; - vscp->user2p(vpostp); - new SplitPostEdge{&m_graph, vstdp, vpostp}; - } - SplitVarPostVertex* const vpostp - = reinterpret_cast(vscp->user2p()); - // Add edges - for (SplitLogicVertex* vxp : m_stmtStackps) { - new SplitLVEdge{&m_graph, vpostp, vxp}; - } - } else { // Nondelayed assignment - if (nodep->access().isWriteOrRW()) { - // Non-delay; need to maintain existing ordering - // with all consumers of the signal - UINFO(4, " VARREFLV: " << nodep); - for (SplitLogicVertex* ivxp : m_stmtStackps) { - new SplitLVEdge{&m_graph, vstdp, ivxp}; - } - } else { - UINFO(4, " VARREF: " << nodep); - makeRvalueEdges(vstdp); - } - } - } - } - } - - void visit(AstJumpGo* nodep) override { - // Jumps will disable reordering at all levels - // This is overly pessimistic; we could treat jumps as barriers, and - // reorder everything between jumps/labels, however jumps are rare - // in always, so the performance gain probably isn't worth the work. - UINFO(9, " NoReordering " << nodep); - m_noReorderWhy = "JumpGo"; - iterateChildren(nodep); - } - - //-------------------- - // Default - void visit(AstNode* nodep) override { - // **** SPECIAL default type that sets PLI_ORDERING - if (!m_stmtStackps.empty() && !nodep->isPure()) { - UINFO(9, " NotSplittable " << nodep); - scoreboardPli(nodep); - } - if (nodep->isTimingControl()) { - UINFO(9, " NoReordering " << nodep); - m_noReorderWhy = "TimingControl"; - } - iterateChildren(nodep); - } - -private: - VL_UNCOPYABLE(SplitReorderBaseVisitor); -}; - -class SplitVisitor final : public SplitReorderBaseVisitor { - // Keys are original always blocks pending delete, - // values are newly split always blocks pending insertion - // at the same position as the originals: - std::unordered_map m_replaceBlocks; - - // AstNodeIf* whose condition we're currently visiting - const AstNode* m_curIfConditional = nullptr; + V3Graph* m_graphp = nullptr; // Dependency graph to analyze statement connectivity + std::vector m_stmtStackps; // Current statements being tracked + SplitImpureVertex* m_impureVtxp = nullptr; // Vertex connecting impure statements + const char* m_noSplitWhy = nullptr; // Reason current block cannot be split + bool m_inDly = false; // Inside AstAssignDly Lhs + const AstIf* m_currIfp = nullptr; // The AstIf whose condition is currently visited VDouble0 m_statSplits; // Statistic tracking - // CONSTRUCTORS -public: - explicit SplitVisitor(AstNetlist* nodep) { - iterate(nodep); - - // Splice newly-split blocks into the tree. Remove placeholders - // from newly-split blocks. Delete the original always blocks - // that we're replacing. - for (auto it = m_replaceBlocks.begin(); it != m_replaceBlocks.end(); ++it) { - AstAlways* const origp = it->first; - for (AlwaysVec::iterator addme = it->second.begin(); addme != it->second.end(); - ++addme) { - origp->addNextHere(*addme); - const int numRemoved = RemovePlaceholdersVisitor::exec(*addme); - m_statSplits -= numRemoved; - } - origp->unlinkFrBack(); // Without next - VL_DO_DANGLING(origp->deleteTree(), origp); - } - } - - ~SplitVisitor() override { V3Stats::addStat("Optimizations, Split always", m_statSplits); } - // METHODS -protected: - void makeRvalueEdges(SplitVarStdVertex* vstdp) override { - // Each 'if' depends on rvalues in its own conditional ONLY, - // not rvalues in the if/else bodies. - for (auto it = m_stmtStackps.cbegin(); it != m_stmtStackps.cend(); ++it) { - const AstNodeIf* const ifNodep = VN_CAST((*it)->nodep(), NodeIf); - if (ifNodep && (m_curIfConditional != ifNodep)) continue; - new SplitRVEdge{&m_graph, *it, vstdp}; + void addEdge(V3GraphVertex* fromp, V3GraphVertex* top) { + new V3GraphEdge{m_graphp, fromp, top, 1}; + } + + // Iterate the given list of statements, building the dependency graph + void scanBlock(AstNode* stmtsp) { + if (m_noSplitWhy) return; + for (AstNode* stmtp = stmtsp; stmtp; stmtp = stmtp->nextp()) { + // Skip comments. They have no dependencies at all, so would always + // form an independent component, and hence a split block, of their + // own, which would be subsequently deleted as it does nothing. + if (VN_IS(stmtp, Comment)) continue; + UASSERT_OBJ(!stmtp->user1p(), stmtp, "user1p should not be set"); + SplitStmtVertex* const vtxp = new SplitStmtVertex{m_graphp, stmtp}; + stmtp->user1p(vtxp); + m_stmtStackps.push_back(vtxp); + iterate(stmtp); + m_stmtStackps.pop_back(); } } - void colorAlwaysGraph() { - // Color the graph to indicate subsets, each of which - // we can split into its own always block. - m_graph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue); + // Remove unnecessary edges, then color to find weakly connected components + uint32_t colorAlwaysGraph() { + if (dumpGraphLevel() >= 9) m_graphp->dumpDotFilePrefixed("splitg_built", false); - // Some vars are primary inputs to the always block; prune - // edges on those vars. Reasoning: if two statements both depend - // on primary input A, it's ok to split these statements. Whereas - // if they both depend on locally-generated variable B, the statements - // must be kept together. - SplitEdge::incrementStep(); - pruneDepsOnInputs(); + // Prune duplicate edges. Not necessary for correctness, but simplifies dumps. + m_graphp->removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue); + if (dumpGraphLevel() >= 9) m_graphp->dumpDotFilePrefixed("splitg_nodup", false); - // For any 'if' node whose deps have all been pruned - // (meaning, its conditional expression only looks at primary - // inputs) prune all edges that depend on the 'if'. - for (V3GraphVertex& vertex : m_graph.vertices()) { - SplitLogicVertex* const logicp = vertex.cast(); - if (!logicp) continue; + // Remove variable vertices that are not written in the block. + // These are input-only to the block, so carry no dependency. + for (V3GraphVertex* const vtxp : m_graphp->vertices().unlinkable()) { + SplitVarStdVertex* const vstdp = vtxp->cast(); + if (!vstdp || !vstdp->outEmpty()) continue; + UINFOTREE(9, vstdp->nodep(), "", "Will remove deps on block input var:"); + vstdp->nodep()->user1p(nullptr); // Don't leave a dangling pointer behind + VL_DO_DANGLING(vstdp->unlinkDelete(m_graphp), vstdp); + } + if (dumpGraphLevel() >= 9) m_graphp->dumpDotFilePrefixed("splitg_noinputs", false); - const AstNodeIf* const ifNodep = VN_CAST(logicp->nodep(), NodeIf); - if (!ifNodep) continue; + // A statement under an 'if' also has an edge to the 'if' itself, from + // each variable it writes, so an 'if' holds its whole body together. + // An 'if' has out edges only for what its own condition reads. If + // after the pruning of block inputs above, an 'if' has no remaining + // out edges (dependencies of its condition), then it constrains + // nothing. If so, then remove the 'if' statement vertex, so + // its contents can split apart, each part taking a copy of the + // condition. This is what allows splitting within an if/else at all, + // and is what breaks up the reset tree in the example at the top of + // this file. + for (V3GraphVertex* const vtxp : m_graphp->vertices().unlinkable()) { + SplitStmtVertex* const stmtVtxp = vtxp->cast(); + if (!stmtVtxp || !VN_IS(stmtVtxp->nodep(), If)) continue; + // Can't remove if dependent on a variable written in the block + if (!stmtVtxp->outEmpty()) continue; + // Depends only on block inputs, so can be split. Remove the vertex. + stmtVtxp->nodep()->user1p(nullptr); + stmtVtxp->unlinkDelete(m_graphp); + } + if (dumpGraphLevel() >= 9) m_graphp->dumpDotFilePrefixed("splitg_nofreeifs", false); - bool pruneMe = true; - for (const V3GraphEdge& edge : logicp->outEdges()) { - const SplitEdge& oedge = static_cast(edge); - if (!oedge.ignoreThisStep()) { - // This if conditional depends on something we can't - // prune -- a variable generated in the current block. - pruneMe = false; + // Weak coloring to determine what must stay together in a single block + const uint32_t numColors = m_graphp->weaklyConnected(&V3GraphEdge::followAlwaysTrue); + if (dumpGraphLevel() >= 9) m_graphp->dumpDotFilePrefixed("splitg_colored", false); + return numColors; + } - // When we can't prune dependencies on the conditional, - // give a hint about why... - if (debug() >= 9) { - V3GraphVertex* vxp = oedge.top(); - const SplitNodeVertex* const nvxp - = static_cast(vxp); - UINFO(0, "Cannot prune if-node due to edge " - << &oedge << " pointing to node " << nvxp->nodep()); - nvxp->nodep()->dumpTree("- "); - } + // Take the statements of the given list, and return them distributed into one list per color. + static std::vector splitStatements(AstNode* stmtsp, uint32_t numColors) { + std::vector result{numColors, nullptr}; + for (AstNode *stmtp = stmtsp, *nextp = nullptr; stmtp; stmtp = nextp) { + nextp = stmtp->nextp(); // 'stmtp' is unlinked below - break; + // Comments are dropped if the block is split + if (VN_IS(stmtp, Comment)) continue; + + // Pick up the statement vertex, which might be nullptr after pruning during analysis + const SplitStmtVertex* const vtxp = stmtp->user1u().to(); + + // If statements are duplicated for each split branch + if (AstIf* const ifp = VN_CAST(stmtp, If)) { + const auto thens = splitStatements(ifp->thensp(), numColors); + const auto elses = splitStatements(ifp->elsesp(), numColors); + FileLine* const flp = ifp->fileline(); + // Rebuild the 'if' for each color present in either branch + bool empty = true; + for (uint32_t color = 0; color < numColors; ++color) { + if (!thens[color] && !elses[color]) continue; + empty = false; + // The condition is cloned for each color. An impure condition + // keeps the 'if' and all it holds in one component, so just once. + AstIf* const clonep = new AstIf{flp, ifp->condp()->cloneTree(true), + thens[color], elses[color]}; + // Preserve pragmas from unique if's so assertions work properly + clonep->uniquePragma(ifp->uniquePragma()); + clonep->unique0Pragma(ifp->unique0Pragma()); + clonep->priorityPragma(ifp->priorityPragma()); + result[color] = AstNode::addNext(result[color], clonep); } + // There is nothing under the 'if' to guard. If its vertex was + // removed as having no dependencies at all, then its + // condition reads only block inputs and is pure, so the whole + // 'if' can go. Otherwise the condition might have a side + // effect, so keep just the condition, evaluated as a + // statement, under the color of the 'if' itself. + if (empty && vtxp) { + const uint32_t color = vtxp->color(); + AstNodeExpr* const condp = ifp->condp(); + condp->unlinkFrBack(); + result[color] = AstNode::addNext(result[color], new AstStmtExpr{flp, condp}); + } + continue; } - if (!pruneMe) continue; - - // This if can be split; prune dependencies on it. - for (V3GraphEdge& edge : logicp->inEdges()) { - SplitEdge& oedge = static_cast(edge); - oedge.setIgnoreThisStep(); - } + // Move the leaf into its color's list + const uint32_t color = vtxp->color(); + result[color] = AstNode::addNext(result[color], stmtp->unlinkFrBack()); } - - if (dumpGraphLevel() >= 9) m_graph.dumpDotFilePrefixed("splitg_nodup", false); - - // Weak coloring to determine what needs to remain grouped - // in a single always. This follows all edges excluding: - // - those we pruned above - // - PostEdges, which are done later - m_graph.weaklyConnected(&SplitEdge::followScoreboard); - if (dumpGraphLevel() >= 9) m_graph.dumpDotFilePrefixed("splitg_colored", false); + return result; } + // VISITORS void visit(AstAlways* nodep) override { - // build the scoreboard - scoreboardClear(); + // Skip blocks created below + if (nodep->user3()) return; + + UASSERT_OBJ(!m_graphp, nodep, "AstAlways should not nest"); + VL_RESTORER(m_graphp); + VL_RESTORER(m_impureVtxp); + VL_RESTORER(m_noSplitWhy); + VL_RESTORER(m_inDly); + V3Graph graph; + m_graphp = &graph; + m_impureVtxp = nullptr; + m_noSplitWhy = nullptr; + m_inDly = false; + UASSERT_OBJ(m_stmtStackps.empty(), nodep, "Statement stack not empty"); + + // Build the graph + const VNUser1InUse user1InUse; + const VNUser2InUse user2InUse; scanBlock(nodep->stmtsp()); - if (m_noReorderWhy != "") { - // We saw a jump or something else rare that we don't handle. - UINFO(9, " NoSplitBlock because " << m_noReorderWhy); + // We might have to give up + if (m_noSplitWhy) { + UINFO(9, " NoSplitBlock because " << m_noSplitWhy); return; } - // Look across the entire tree of if/else blocks in the always, - // and color regions that must be kept together. - UINFO(5, "SplitVisitor @ " << nodep); - colorAlwaysGraph(); + // Color the graph to identify separable statements + const uint32_t numColors = colorAlwaysGraph(); + // If the whole block is one component (or empty), then nothing to split + if (numColors <= 1) return; - // Map each AstNodeIf to the set of colors (split always blocks) - // it must participate in. Also find the whole set of colors. - const IfColorVisitor ifColor{nodep}; + UINFO(6, " splitting always " << nodep); - if (ifColor.colors().size() > 1) { - // Counting original always blocks rather than newly-split - // always blocks makes it a little easier to use this stat to - // check the result of the t_alw_split test: - m_statSplits += ifColor.colors().size() - 1; // -1 for the original always + // Count the number of new blocks inserted into the Ast: '1 -> n' split, so 'n - 1' extra + m_statSplits += numColors - 1; - // Visit through the original always block one more time, - // and emit the split always blocks into m_replaceBlocks: - EmitSplitVisitor emitSplit{nodep, &ifColor, &(m_replaceBlocks[nodep])}; - emitSplit.go(); + // Unpick the statements out of the original block, into one list per color + const auto lists = splitStatements(nodep->stmtsp(), numColors); + UASSERT_OBJ(lists.size() == numColors, nodep, "Inconsistent split"); + + // Whatever 'splitStatements' did not take, (comments, empty ifs) is not needed any more + if (AstNode* const restp = nodep->stmtsp()) { + VL_DO_DANGLING(restp->unlinkFrBackWithNext()->deleteTree(), restp); + } + + // Every color has a statement in it. Reuse the original block for the + // first color, and add a new block after it for each of the rest. + UASSERT_OBJ(lists.front(), nodep, "Color with no statements"); + nodep->addStmtsp(lists.front()); + AstNode* lastp = nodep; + FileLine* const flp = nodep->fileline(); + const VAlwaysKwd kwd = nodep->keyword(); + for (size_t i = 1; i < numColors; ++i) { + AstNode* const stmtsp = lists[i]; + UASSERT_OBJ(stmtsp, nodep, "Color with no statements"); + AstAlways* const newp = new AstAlways{flp, kwd, nullptr, stmtsp}; + newp->user3(1); // Do not split again + lastp->addNextHere(newp); + lastp = newp; } } - void visit(AstNodeIf* nodep) override { - UINFO(4, " IF " << nodep); - if (!nodep->condp()->isPure()) m_noReorderWhy = "Impure IF condition"; + + void visit(AstIf* nodep) override { + if (!m_graphp || m_noSplitWhy) return; { - VL_RESTORER(m_curIfConditional); - m_curIfConditional = nodep; + VL_RESTORER(m_currIfp); + m_currIfp = nodep; iterateAndNextNull(nodep->condp()); } scanBlock(nodep->thensp()); scanBlock(nodep->elsesp()); } -private: - VL_UNCOPYABLE(SplitVisitor); -}; + void visit(AstExprStmt* nodep) override { + if (!m_graphp || m_noSplitWhy) return; + VL_RESTORER(m_inDly); + m_inDly = false; + iterateChildren(nodep); + } -} //namespace + void visit(AstAssignDly* nodep) override { + if (!m_graphp || m_noSplitWhy) return; + iterate(nodep->rhsp()); + VL_RESTORER(m_inDly); + m_inDly = true; + iterate(nodep->lhsp()); + } + + void visit(AstJumpGo*) override { + if (!m_graphp || m_noSplitWhy) return; + m_noSplitWhy = "JumpGo"; + } + + void visit(AstVarRef* nodep) override { + if (!m_graphp || m_noSplitWhy) return; + UASSERT_OBJ(!m_stmtStackps.empty(), nodep, "Not under a statement"); + + // Constant lookups can be ignored + if (nodep->varp()->isConst()) return; + + AstVarScope* const vscp = nodep->varScopep(); + + // SPEEDUP: We add duplicate edges, that should be fixed + if (m_inDly && nodep->access().isWriteOrRW()) { + // Delayed variable: is different from non-delayed variable, writes to it + // are not observable while executing this block (NBA not yet committed), + // so add only a write edge to a separate 'post' vertex. + if (!vscp->user2p()) vscp->user2p(new SplitVarPostVertex{m_graphp, vscp}); + SplitVarPostVertex* const vpostp = vscp->user2u().to(); + for (SplitStmtVertex* const vtxp : m_stmtStackps) addEdge(vpostp, vtxp); + } else if (nodep->access().isWriteOrRW()) { + // Regular (non-blocking) write: Need to maintain program-flow order + if (!vscp->user1p()) vscp->user1p(new SplitVarStdVertex{m_graphp, vscp}); + SplitVarStdVertex* const vstdp = vscp->user1u().to(); + for (SplitStmtVertex* const vtxp : m_stmtStackps) addEdge(vstdp, vtxp); + } else { + // Regular (non-blocking) read: Need to maintain program-flow order + if (!vscp->user1p()) vscp->user1p(new SplitVarStdVertex{m_graphp, vscp}); + SplitVarStdVertex* const vstdp = vscp->user1u().to(); + for (SplitStmtVertex* const vtxp : m_stmtStackps) { + // If this is an if statement it only depends on refs in its + // own condition only (not those in its branches). For other + // statements, just record the referene as normal. + if (const AstIf* const ifp = VN_CAST(vtxp->nodep(), If)) { + if (ifp != m_currIfp) continue; + } + addEdge(vtxp, vstdp); + } + } + } + + void visit(AstNode* nodep) override { + // Outside AstAlways, just descend + if (!m_graphp) { + iterateChildren(nodep); + return; + } + // Early exit if decided not to split + if (m_noSplitWhy) return; + + UASSERT_OBJ(!m_stmtStackps.empty(), nodep, "Not under a statement"); + + // Timing control prevents splitting + if (nodep->isTimingControl()) { + m_noSplitWhy = "TimingControl"; + return; + } + + // All impure statements must be grouped together. + if (!nodep->isPure()) { + if (!m_impureVtxp) m_impureVtxp = new SplitImpureVertex{m_graphp, nodep}; + // One edge is enough to find the weakly connected components, but + // it must point at the impure vertex, so it is an out edge (input + // dependency) of any enclosing 'if' to prevent pruning. + for (SplitStmtVertex* const vtxp : m_stmtStackps) addEdge(vtxp, m_impureVtxp); + } + + iterateChildren(nodep); + } + + // CONSTRUCTORS + explicit SplitVisitor(AstNetlist* nodep) { iterate(nodep); } + ~SplitVisitor() override { V3Stats::addStat("Optimizations, Split always", m_statSplits); } + VL_UNCOPYABLE(SplitVisitor); + +public: + static void apply(AstNetlist* nodep) { SplitVisitor{nodep}; } +}; //###################################################################### // Split class functions void V3Split::splitAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); - { SplitVisitor{nodep}; } // Destruct before checking + SplitVisitor::apply(nodep); V3Global::dumpCheckGlobalTree("split", 0, dumpTreeEitherLevel() >= 3); } diff --git a/test_regress/t/t_always_reorder.v b/test_regress/t/t_always_reorder.v index f5ca037b8..88e77fe3a 100644 --- a/test_regress/t/t_always_reorder.v +++ b/test_regress/t/t_always_reorder.v @@ -18,10 +18,13 @@ module t ( reg [15:0] v3; integer nosplit; + // Write needed so that V3Dead doesn't kill v0..v3. In its own block, as it only reads + // them, so would split out of the block below anyway. always @(posedge clk) begin - // write needed so that V3Dead doesn't kill v0..v3 $write(" values %x %x %x\n", v1, v2, v3); + end + always @(posedge clk) begin // Locally-set 'nosplit' will prevent the if from splitting // in splitAlwaysAll(). This whole always block should still be // intact when we call splitReorderAll() which is the subject diff --git a/test_regress/t/t_assert_always_unbounded.v b/test_regress/t/t_assert_always_unbounded.v index 1602bd74b..4c5a45ed9 100644 --- a/test_regress/t/t_assert_always_unbounded.v +++ b/test_regress/t/t_assert_always_unbounded.v @@ -46,12 +46,13 @@ module t ( cyc <= cyc + 1; if (cyc >= 4) a_drop <= 1'b0; if (cyc == 19) begin - // Counts pinned to Verilator (NFA per-cycle reject). For all-fail windows - // others are one lower (it does not fire the end-of-sim tick); see the sva - // lessons "multi-cycle end-of-simulation offset" note. + // Counts pinned to Verilator (NFA per-cycle reject). The all-fail windows do not + // include the end-of-simulation tick, as the $finish below takes effect before the + // properties are sampled for this cycle; see the sva lessons "multi-cycle + // end-of-simulation offset" note. `checkd(high_fail_q.size(), 0); - `checkd(low0_fail_q.size(), 20); // All others: 19 - `checkd(low2_fail_q.size(), 18); // All others: 17 + `checkd(low0_fail_q.size(), 19); + `checkd(low2_fail_q.size(), 17); `checkd(drop_fail_q[0], 5); // All others: 6; first fail tick: a_drop sampled low from cyc 5 $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_interface_virtual_controlflow.py b/test_regress/t/t_interface_virtual_controlflow.py index ab012d5ce..28cc70f94 100755 --- a/test_regress/t/t_interface_virtual_controlflow.py +++ b/test_regress/t/t_interface_virtual_controlflow.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--binary -fno-reorder"]) +test.compile(verilator_flags2=["--binary -fno-reorder -fno-split"]) test.execute(expect_filename=test.golden_filename) diff --git a/test_regress/t/t_always_nosplit.py b/test_regress/t/t_opt_split.py similarity index 98% rename from test_regress/t/t_always_nosplit.py rename to test_regress/t/t_opt_split.py index d33d40f72..480d210b3 100755 --- a/test_regress/t/t_always_nosplit.py +++ b/test_regress/t/t_opt_split.py @@ -14,7 +14,7 @@ test.scenarios('simulator') test.compile(verilator_flags2=["--stats"]) if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, Split always\s+(\d+)', 0) + test.file_grep(test.stats, r'Optimizations, Split always\s+(\d+)', 10) test.execute() diff --git a/test_regress/t/t_always_split.v b/test_regress/t/t_opt_split.v similarity index 50% rename from test_regress/t/t_always_split.v rename to test_regress/t/t_opt_split.v index 057538c52..7909c7382 100644 --- a/test_regress/t/t_always_split.v +++ b/test_regress/t/t_opt_split.v @@ -55,7 +55,60 @@ module t ( l_split_1 <= l_split_2 | m_din; end - // (The checker block is an exception, it won't split.) + reg [15:0] p_split_1, p_split_2; + always @(posedge clk) begin + if (m_din != 16'h0) begin + $write(""); + p_split_1 <= m_din; + end + p_split_2 <= ~m_din; + end + + reg [15:0] q_split_1, q_split_2; + always @(posedge clk) begin + if (m_din[0]) q_split_1 <= 16'h1; + else q_split_2 <= 16'h2; + end + + reg [15:0] r_split_mem[0:3]; + reg [1:0] r_split_idx = 2'd0; + always @(posedge clk) begin + r_split_mem[r_split_idx] <= m_din; + r_split_idx <= r_split_idx + 2'd1; + end + + // Inlining this leaves only its comment behind, as its body is dead + task automatic dead_task; + automatic integer loc; + begin + loc = 1; + end + endtask + + integer seed = 1; + reg [15:0] t_split_1, t_split_2; + always @(posedge clk) begin + // Nothing is left under the 'if', but the impure condition keeps its + // vertex, so the condition alone is kept as a statement + if ($random(seed) != 0) begin + dead_task(); + end + t_split_1 <= m_din; + t_split_2 <= ~m_din; + end + + reg [15:0] u_split_1, u_split_2; + always @(posedge clk) begin + // The same, but this condition reads only block inputs, so the vertex is + // pruned and the 'if' goes altogether + if (m_din[2]) begin + dead_task(); + end + u_split_1 <= m_din; + u_split_2 <= ~m_din; + end + + // The checker block won't split. always @(posedge clk) begin if (cyc != 0) begin cyc <= cyc + 1; @@ -70,18 +123,34 @@ module t ( if (!(a_split_1 == 16'hfeed && a_split_2 == 16'hfeed)) $stop; if (!(d_split_1 == 16'h0112 && d_split_2 == 16'h0112)) $stop; if (!(h_split_1 == 16'hfeed && h_split_2 == 16'h0112)) $stop; + if (!(p_split_1 == 16'hfeed && p_split_2 == 16'h0112)) $stop; + if (!(q_split_1 == 16'h1)) $stop; + if (!(r_split_idx == 2'd3)) $stop; + if (!(r_split_mem[1] == 16'hfeed && r_split_mem[2] == 16'hfeed)) $stop; + if (!(t_split_1 == 16'hfeed && t_split_2 == 16'h0112)) $stop; + if (!(u_split_1 == 16'hfeed && u_split_2 == 16'h0112)) $stop; end if (cyc == 5) begin m_din <= 16'he22e; if (!(a_split_1 == 16'he11e && a_split_2 == 16'he11e)) $stop; if (!(d_split_1 == 16'h0112 && d_split_2 == 16'h0112)) $stop; if (!(h_split_1 == 16'hfeed && h_split_2 == 16'h0112)) $stop; + if (!(p_split_1 == 16'hfeed && p_split_2 == 16'h0112)) $stop; + if (!(q_split_1 == 16'h1)) $stop; + if (!(r_split_idx == 2'd0 && r_split_mem[3] == 16'hfeed)) $stop; + if (!(t_split_1 == 16'hfeed && t_split_2 == 16'h0112)) $stop; + if (!(u_split_1 == 16'hfeed && u_split_2 == 16'h0112)) $stop; end if (cyc == 6) begin m_din <= 16'he33e; if (!(a_split_1 == 16'he22e && a_split_2 == 16'he22e)) $stop; if (!(d_split_1 == 16'h1ee1 && d_split_2 == 16'h0112)) $stop; if (!(h_split_1 == 16'he11e && h_split_2 == 16'h1ee1)) $stop; + if (!(p_split_1 == 16'he11e && p_split_2 == 16'h1ee1)) $stop; + if (!(q_split_1 == 16'h1 && q_split_2 == 16'h2)) $stop; + if (!(r_split_idx == 2'd1 && r_split_mem[0] == 16'he11e)) $stop; + if (!(t_split_1 == 16'he11e && t_split_2 == 16'h1ee1)) $stop; + if (!(u_split_1 == 16'he11e && u_split_2 == 16'h1ee1)) $stop; end if (cyc == 7) begin $write("*-* All Finished *-*\n"); diff --git a/test_regress/t/t_always_split_cond.py b/test_regress/t/t_opt_split_cond.py similarity index 100% rename from test_regress/t/t_always_split_cond.py rename to test_regress/t/t_opt_split_cond.py diff --git a/test_regress/t/t_always_split_cond.v b/test_regress/t/t_opt_split_cond.v similarity index 100% rename from test_regress/t/t_always_split_cond.v rename to test_regress/t/t_opt_split_cond.v diff --git a/test_regress/t/t_always_split.py b/test_regress/t/t_opt_split_no.py similarity index 80% rename from test_regress/t/t_always_split.py rename to test_regress/t/t_opt_split_no.py index f5ececdbb..991e6c42a 100755 --- a/test_regress/t/t_always_split.py +++ b/test_regress/t/t_opt_split_no.py @@ -11,10 +11,16 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=[ + "--stats", + "--timing", + # To fill code coverage + "--dumpi-V3Split", + "9", +]) if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, Split always\s+(\d+)', 4) + test.file_grep(test.stats, r'Optimizations, Split always\s+(\d+)', 0) test.execute() diff --git a/test_regress/t/t_always_nosplit.v b/test_regress/t/t_opt_split_no.v similarity index 71% rename from test_regress/t/t_always_nosplit.v rename to test_regress/t/t_opt_split_no.v index a59912177..dce223ea4 100644 --- a/test_regress/t/t_always_nosplit.v +++ b/test_regress/t/t_opt_split_no.v @@ -8,8 +8,8 @@ module t ( input clk ); - integer cyc; - initial cyc = 1; + integer cyc = 1; + always @(posedge clk) cyc <= cyc + 1; reg [15:0] m_din; @@ -101,10 +101,57 @@ module t ( end end // always @ (posedge clk) + reg [15:0] n_split_1, n_split_2; always @(posedge clk) begin - if (cyc != 0) begin - cyc <= cyc + 1; + @(negedge clk); + n_split_1 <= m_din; + n_split_2 <= ~m_din; + end + + reg [15:0] o_split_1, o_split_2; + reg [15:0] o_cnt; + always @(posedge clk) begin + o_cnt = 16'h0; + while (o_cnt < m_din) begin + if (m_din[0]) break; + o_cnt = o_cnt + 16'h1; end + o_split_1 <= m_din; + if (m_din[1]) o_split_2 <= ~m_din; + end + + reg [15:0] p_split_1; + reg [15:0] p_cnt; + always @(posedge clk) begin : pblk + p_split_1 <= m_din; + p_cnt = 16'h0; + while (p_cnt < m_din) begin + @(negedge clk); + disable pblk; + end + end + + // The same constructs, but outside of any always block + function automatic [15:0] fret(input [15:0] a); + fret = 16'h0; + if (a[0]) return 16'hbeef; + fret = a; + endfunction + reg [15:0] q_init_1, q_init_2, q_init_3; + reg [15:0] q_cnt; + initial begin : qblk + if (m_din == 16'h0) q_init_1 = 16'h1; + q_init_1 <= 16'h2; + q_init_2 = fret(m_din | 16'h1); + q_cnt = 16'h0; + while (q_cnt < (m_din | 16'h1)) begin + q_cnt = q_cnt + 16'h1; + q_init_3 = q_cnt; + disable qblk; + end + end + + always @(posedge clk) begin if (cyc == 1) begin m_din <= 16'hfeed; end @@ -116,6 +163,10 @@ module t ( if (!(f_split_1 == 16'hfeed && f_split_2 == 16'hfeed)) $stop; if (!(m_split_1 == 16'hfeed && m_split_2 == 16'h0000)) $stop; if (!(z_split_1 == 16'h0112 && z_split_2 == 16'h0112)) $stop; + if (!(n_split_1 == 16'hfeed && n_split_2 == 16'h0112)) $stop; + if (!(o_split_1 == 16'hfeed)) $stop; + if (!(p_split_1 == 16'hfeed)) $stop; + if (!(q_init_1 == 16'h2 && q_init_2 == 16'hbeef && q_init_3 == 16'h1)) $stop; end if (cyc == 5) begin m_din <= 16'he22e; @@ -128,6 +179,9 @@ module t ( $stop; if (!(m_split_1 == 16'hfeed && m_split_2 == 16'h0000)) $stop; if (!(z_split_1 == 16'h0112 && z_split_2 == 16'h0112)) $stop; + if (!(n_split_1 == 16'he11e && n_split_2 == 16'h1ee1)) $stop; + if (!(o_split_1 == 16'hfeed)) $stop; + if (!(p_split_1 == 16'hfeed)) $stop; end if (cyc == 6) begin m_din <= 16'he33e; @@ -140,6 +194,9 @@ module t ( $stop; if (!(m_split_1 == 16'he11e && m_split_2 == 16'h0000)) $stop; if (!(z_split_1 == 16'h1ee1 && z_split_2 == 16'h0112)) $stop; + if (!(n_split_1 == 16'he22e && n_split_2 == 16'h1dd1)) $stop; + if (!(o_split_1 == 16'he11e && o_split_2 == 16'h1ee1)) $stop; + if (!(p_split_1 == 16'he11e)) $stop; end if (cyc == 7) begin $write("*-* All Finished *-*\n"); diff --git a/test_regress/t/t_always_splitord.py b/test_regress/t/t_opt_split_ord.py similarity index 100% rename from test_regress/t/t_always_splitord.py rename to test_regress/t/t_opt_split_ord.py diff --git a/test_regress/t/t_always_splitord.v b/test_regress/t/t_opt_split_ord.v similarity index 100% rename from test_regress/t/t_always_splitord.v rename to test_regress/t/t_opt_split_ord.v diff --git a/test_regress/t/t_always_split_rst.py b/test_regress/t/t_opt_split_rst.py similarity index 98% rename from test_regress/t/t_always_split_rst.py rename to test_regress/t/t_opt_split_rst.py index 5afd83272..e30695459 100755 --- a/test_regress/t/t_always_split_rst.py +++ b/test_regress/t/t_opt_split_rst.py @@ -14,7 +14,7 @@ test.scenarios('simulator') test.compile(verilator_flags2=["--stats", test.wno_unopthreads_for_few_cores]) if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, Split always\s+(\d+)', 12) + test.file_grep(test.stats, r'Optimizations, Split always\s+(\d+)', 13) test.execute() diff --git a/test_regress/t/t_always_split_rst.v b/test_regress/t/t_opt_split_rst.v similarity index 100% rename from test_regress/t/t_always_split_rst.v rename to test_regress/t/t_opt_split_rst.v diff --git a/test_regress/t/t_prop_always.v b/test_regress/t/t_prop_always.v index a7ca60a20..5982d4eb0 100644 --- a/test_regress/t/t_prop_always.v +++ b/test_regress/t/t_prop_always.v @@ -77,22 +77,22 @@ module t ( cyc <= cyc + 1; crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; if (cyc == 19) begin - // Constant-true window [0:3]: K=0..16 succeed at cyc K+3 = 3..19. - `checkd(high_bounded_pass_q.size(), 17); // Other sims: 16 + // Constant-true window [0:3]: K=0..15 succeed at cyc K+3 = 3..18. + `checkd(high_bounded_pass_q.size(), 16); `checkd(high_bounded_pass_q[0], 3); // Other sims: 4 - `checkd(high_bounded_pass_q[$], 19); - // Degenerate [0:0]: K=0..19 succeed at cyc K = 0..19. - `checkd(high_degenerate_pass_q.size(), 20); // Other sims: 19 - `checkd(high_degenerate_pass_q[0], 0); // Other sims: 0, 1 - `checkd(high_degenerate_pass_q[$], 19); + `checkd(high_bounded_pass_q[$], 18); + // Degenerate [0:0]: K=0..18 succeed at cyc K = 0..18. + `checkd(high_degenerate_pass_q.size(), 19); + `checkd(high_degenerate_pass_q[0], 0); // Some other sims: 1 + `checkd(high_degenerate_pass_q[$], 18); // Constant-false: every attempt fails immediately. - `checkd(low_bounded_fail_q.size(), 20); // Other sims: 19 - `checkd(low_degenerate_fail_q.size(), 20); // Other sims: 19 + `checkd(low_bounded_fail_q.size(), 19); + `checkd(low_degenerate_fail_q.size(), 19); // CRC + disable streams `checkd(rand_bounded_pass_q.size(), 0); - `checkd(rand_bounded_fail_q.size(), 20); // Other sims: 19, 11 + `checkd(rand_bounded_fail_q.size(), 19); // Some other sims: 11 `checkd(disable_bounded_pass_q.size(), 0); - `checkd(disable_bounded_fail_q.size(), 8); // Other sims: 5, 6 + `checkd(disable_bounded_fail_q.size(), 7); // Other sims: 5, 6 $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_prop_always_wide.v b/test_regress/t/t_prop_always_wide.v index 13479dbe4..bc2cabd82 100644 --- a/test_regress/t/t_prop_always_wide.v +++ b/test_regress/t/t_prop_always_wide.v @@ -51,16 +51,16 @@ module t ( always @(posedge clk) begin cyc <= cyc + 1; if (cyc == 49) begin - // Constant-true [1:33]: K=0..16 succeed at cyc K+33 = 33..49. - `checkd(wide_pass_q.size(), 17); + // Constant-true [1:33]: K=0..15 succeed at cyc K+33 = 33..48. + `checkd(wide_pass_q.size(), 16); `checkd(wide_pass_q[0], 33); - `checkd(wide_pass_q[$], 49); + `checkd(wide_pass_q[$], 48); end if (cyc == 1041) begin - // Constant-true [1:1025]: K=0..16 succeed at cyc K+1025 = 1025..1041. - `checkd(wide_ring_pass_q.size(), 17); + // Constant-true [1:1025]: K=0..15 succeed at cyc K+1025 = 1025..1040. + `checkd(wide_ring_pass_q.size(), 16); `checkd(wide_ring_pass_q[0], 1025); - `checkd(wide_ring_pass_q[$], 1041); + `checkd(wide_ring_pass_q[$], 1040); `checkd(wide_fail_q.size(), 1025); `checkd(wide_fail_q[0], 1025); `checkd(wide_fail_q[$], 1025); diff --git a/test_regress/t/t_prop_followed_by.v b/test_regress/t/t_prop_followed_by.v index a1267bf7c..290cf37ba 100644 --- a/test_regress/t/t_prop_followed_by.v +++ b/test_regress/t/t_prop_followed_by.v @@ -55,8 +55,8 @@ module t ( // ovl/novl deltas vs Verilator are 1-cycle preponed-sampling differences. $display("ovl=%0d novl=%0d impl=%0d nimp=%0d wide=%0d", ovl_f, novl_f, impl_f, nimp_f, wide_f); - `checkd(ovl_f, 29); // Other sims: 28, one other sim: 5 - `checkd(novl_f, 20); // Other sims: 19 + `checkd(ovl_f, 28); // One other sim: 5 + `checkd(novl_f, 19); `checkd(impl_f, 9); `checkd(nimp_f, 0); `checkd(wide_f, 0); diff --git a/test_regress/t/t_prop_s_always_liveness.v b/test_regress/t/t_prop_s_always_liveness.v index c7158d79a..e4e920cb6 100644 --- a/test_regress/t/t_prop_s_always_liveness.v +++ b/test_regress/t/t_prop_s_always_liveness.v @@ -36,7 +36,7 @@ module t ( cyc <= cyc + 1; if (cyc == 10) begin `checkd(low_s_fail_q.size(), low_w_fail_q.size()); - `checkd(low_w_fail_q.size(), 9); + `checkd(low_w_fail_q.size(), 8); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_property_if_else.v b/test_regress/t/t_property_if_else.v index 9368c24b1..0e009b639 100644 --- a/test_regress/t/t_property_if_else.v +++ b/test_regress/t/t_property_if_else.v @@ -26,7 +26,7 @@ module t ( always @(posedge clk) begin cyc <= cyc + 1; if (cyc == 10) begin - `checkd(vacuous_passes, 10); + `checkd(vacuous_passes, 9); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_property_nfa_counts.py b/test_regress/t/t_property_nfa_counts.py index dfaf37edb..7d26f16e2 100755 --- a/test_regress/t/t_property_nfa_counts.py +++ b/test_regress/t/t_property_nfa_counts.py @@ -11,7 +11,8 @@ import vltest_bootstrap test.scenarios('vlt') -test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing', '--coverage-user']) +test.compile(timing_loop=True, + verilator_flags2=['--assert', '--timing', '--coverage-user', '-Wno-UNOPTTHREADS']) test.execute() diff --git a/test_regress/t/t_property_nfa_stop_error_limit_crossing.out b/test_regress/t/t_property_nfa_stop_error_limit_crossing.out index 7d42f0a44..6f68a6d7f 100644 --- a/test_regress/t/t_property_nfa_stop_error_limit_crossing.out +++ b/test_regress/t/t_property_nfa_stop_error_limit_crossing.out @@ -1,3 +1,2 @@ -ACTION_RAN %Error: t/t_property_nfa_stop_error_limit_crossing.v:23: Verilog $stop Aborting... diff --git a/test_regress/t/t_protect_ids_key.out b/test_regress/t/t_protect_ids_key.out index 6417d1d28..ae8b6431c 100644 --- a/test_regress/t/t_protect_ids_key.out +++ b/test_regress/t/t_protect_ids_key.out @@ -8,8 +8,6 @@ - - @@ -19,12 +17,10 @@ - - diff --git a/test_regress/t/t_x_rand_mt_stability.out b/test_regress/t/t_x_rand_mt_stability.out index cf0f962d9..eaa53dd1f 100644 --- a/test_regress/t/t_x_rand_mt_stability.out +++ b/test_regress/t/t_x_rand_mt_stability.out @@ -7,11 +7,11 @@ top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d -rand = 0x5fa24450 rand = 0x24800459 rand = 0xfd8d9d77 rand = 0xb722072d rand = 0x244113f3 +rand = 0x776efb08 x_assigned = 0x08507ff6 -Last rand = 0x776efb08 +Last rand = 0x8b3a9df4 *-* All Finished *-* diff --git a/test_regress/t/t_x_rand_mt_stability.py b/test_regress/t/t_x_rand_mt_stability.py index 820119492..8d0b3e3ed 100755 --- a/test_regress/t/t_x_rand_mt_stability.py +++ b/test_regress/t/t_x_rand_mt_stability.py @@ -13,7 +13,11 @@ import glob test.scenarios("vltmt") test.top_filename = "t/t_x_rand_stability.v" -test.compile(verilator_flags2=["--x-initial unique"]) +# $random has one seed per C thread, so the values printed depend on which thread runs the +# block. Force a single MTask, which then runs on the main thread, to make this stable +# against changes in partitioning. +test.compile( + verilator_flags2=["--x-initial unique", "--threads-max-mtasks 1", "-Wno-UNOPTTHREADS"]) test.execute(all_run_flags=["+verilator+rand+reset+2"], expect_filename=test.golden_filename) diff --git a/test_regress/t/t_x_rand_mt_stability_add.out b/test_regress/t/t_x_rand_mt_stability_add.out index cf0f962d9..eaa53dd1f 100644 --- a/test_regress/t/t_x_rand_mt_stability_add.out +++ b/test_regress/t/t_x_rand_mt_stability_add.out @@ -7,11 +7,11 @@ top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d -rand = 0x5fa24450 rand = 0x24800459 rand = 0xfd8d9d77 rand = 0xb722072d rand = 0x244113f3 +rand = 0x776efb08 x_assigned = 0x08507ff6 -Last rand = 0x776efb08 +Last rand = 0x8b3a9df4 *-* All Finished *-* diff --git a/test_regress/t/t_x_rand_mt_stability_add.py b/test_regress/t/t_x_rand_mt_stability_add.py index 0bcc359a5..c330dd755 100755 --- a/test_regress/t/t_x_rand_mt_stability_add.py +++ b/test_regress/t/t_x_rand_mt_stability_add.py @@ -12,7 +12,10 @@ import vltest_bootstrap test.scenarios("vltmt") test.top_filename = "t/t_x_rand_stability.v" -test.compile(verilator_flags2=["--x-initial unique", "-DADD_SIGNAL"]) +# See t_x_rand_mt_stability.py for why a single MTask is forced. +test.compile(verilator_flags2=[ + "--x-initial unique", "-DADD_SIGNAL", "--threads-max-mtasks 1", "-Wno-UNOPTTHREADS" +]) test.execute(all_run_flags=["+verilator+rand+reset+2"], expect_filename=test.golden_filename) diff --git a/test_regress/t/t_x_rand_mt_stability_add_trace.out b/test_regress/t/t_x_rand_mt_stability_add_trace.out index cf0f962d9..eaa53dd1f 100644 --- a/test_regress/t/t_x_rand_mt_stability_add_trace.out +++ b/test_regress/t/t_x_rand_mt_stability_add_trace.out @@ -7,11 +7,11 @@ top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d -rand = 0x5fa24450 rand = 0x24800459 rand = 0xfd8d9d77 rand = 0xb722072d rand = 0x244113f3 +rand = 0x776efb08 x_assigned = 0x08507ff6 -Last rand = 0x776efb08 +Last rand = 0x8b3a9df4 *-* All Finished *-* diff --git a/test_regress/t/t_x_rand_mt_stability_add_trace.py b/test_regress/t/t_x_rand_mt_stability_add_trace.py index 278edc8c2..1c8ed6d1f 100755 --- a/test_regress/t/t_x_rand_mt_stability_add_trace.py +++ b/test_regress/t/t_x_rand_mt_stability_add_trace.py @@ -12,7 +12,10 @@ import vltest_bootstrap test.scenarios("vltmt") test.top_filename = "t/t_x_rand_stability.v" -test.compile(verilator_flags2=["--x-initial unique", "-DADD_SIGNAL", "--trace"]) +# See t_x_rand_mt_stability.py for why a single MTask is forced. +test.compile(verilator_flags2=[ + "--x-initial unique", "-DADD_SIGNAL", "--trace", "--threads-max-mtasks 1", "-Wno-UNOPTTHREADS" +]) test.execute(all_run_flags=["+verilator+rand+reset+2"], expect_filename=test.golden_filename) diff --git a/test_regress/t/t_x_rand_mt_stability_trace.out b/test_regress/t/t_x_rand_mt_stability_trace.out index cf0f962d9..eaa53dd1f 100644 --- a/test_regress/t/t_x_rand_mt_stability_trace.out +++ b/test_regress/t/t_x_rand_mt_stability_trace.out @@ -7,11 +7,11 @@ top.t.the_sub_yes_inline_2 no_init 0xa36c65459f4b9f46 top.t.the_sub_yes_inline_1 no_init 0x842cd8b1033a58 top.t.the_sub_no_inline_1 no_init 0x42d55205d10c58f8 top.t.the_sub_no_inline_2 no_init 0xac98037e5042d96d -rand = 0x5fa24450 rand = 0x24800459 rand = 0xfd8d9d77 rand = 0xb722072d rand = 0x244113f3 +rand = 0x776efb08 x_assigned = 0x08507ff6 -Last rand = 0x776efb08 +Last rand = 0x8b3a9df4 *-* All Finished *-* diff --git a/test_regress/t/t_x_rand_mt_stability_trace.py b/test_regress/t/t_x_rand_mt_stability_trace.py index 042bf344a..b7c01fcb9 100755 --- a/test_regress/t/t_x_rand_mt_stability_trace.py +++ b/test_regress/t/t_x_rand_mt_stability_trace.py @@ -12,7 +12,10 @@ import vltest_bootstrap test.scenarios("vltmt") test.top_filename = "t/t_x_rand_stability.v" -test.compile(verilator_flags2=["--x-initial unique", "--trace"]) +# See t_x_rand_mt_stability.py for why a single MTask is forced. +test.compile(verilator_flags2=[ + "--x-initial unique", "--trace", "--threads-max-mtasks 1", "-Wno-UNOPTTHREADS" +]) test.execute(all_run_flags=["+verilator+rand+reset+2"], expect_filename=test.golden_filename) diff --git a/test_regress/t/t_x_rand_mt_stability_zeros.out b/test_regress/t/t_x_rand_mt_stability_zeros.out index eaac43644..c6dc6b6ab 100644 --- a/test_regress/t/t_x_rand_mt_stability_zeros.out +++ b/test_regress/t/t_x_rand_mt_stability_zeros.out @@ -7,11 +7,11 @@ top.t.the_sub_yes_inline_2 no_init 0x0 top.t.the_sub_yes_inline_1 no_init 0x0 top.t.the_sub_no_inline_1 no_init 0x0 top.t.the_sub_no_inline_2 no_init 0x0 -rand = 0x5fa24450 rand = 0x24800459 rand = 0xfd8d9d77 rand = 0xb722072d rand = 0x244113f3 +rand = 0x776efb08 x_assigned = 0x08507ff6 -Last rand = 0x776efb08 +Last rand = 0x8b3a9df4 *-* All Finished *-* diff --git a/test_regress/t/t_x_rand_mt_stability_zeros.py b/test_regress/t/t_x_rand_mt_stability_zeros.py index 8a062d04d..d3ba0b397 100755 --- a/test_regress/t/t_x_rand_mt_stability_zeros.py +++ b/test_regress/t/t_x_rand_mt_stability_zeros.py @@ -12,7 +12,10 @@ import vltest_bootstrap test.scenarios("vltmt") test.top_filename = "t/t_x_rand_stability.v" -test.compile(verilator_flags2=["--x-initial unique", "-DNOT_RAND"]) +# See t_x_rand_mt_stability.py for why a single MTask is forced. +test.compile(verilator_flags2=[ + "--x-initial unique", "-DNOT_RAND", "--threads-max-mtasks 1", "-Wno-UNOPTTHREADS" +]) test.execute(all_run_flags=["+verilator+rand+reset+0"], expect_filename=test.golden_filename)