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)