From dc1e9a8ec65d5549f8b029e724c27051bf517981 Mon Sep 17 00:00:00 2001 From: Tracy Narine <76846523+tmnarine@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:18:48 -0400 Subject: [PATCH 01/89] Internals: Fix gcc bison warnings (#7719) (#7756) Fixes #7719. --- src/bisonpre | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/bisonpre b/src/bisonpre index 9be647f39..36db4b48f 100755 --- a/src/bisonpre +++ b/src/bisonpre @@ -145,6 +145,25 @@ def clean_output(filename: str, outname: str, is_output: bool, is_c: bool) -> No out.append(line) lines = out out = [] + # Code updates for the .c + if outname.endswith(".c"): + for line in lines: + modify = "YYSTACK_FREE (yyss);" in line and "if (yyss != yyssa)" in out[-1] + ifdefined = "#if defined __GNUC__ && ! defined __ICC\n" + endif = "#endif\n" + if modify: + out.insert(-1, ifdefined) + out.insert(-1, " _Pragma (\"GCC diagnostic push\")\n") + out.insert(-1, + " _Pragma (\"GCC diagnostic ignored \\\"-Wfree-nonheap-object\\\"\")\n") + out.insert(-1, endif) + out.append(line) + if modify: + out.append(ifdefined) + out.append(" _Pragma (\"GCC diagnostic pop\")\n") + out.append(endif) + lines = out + out = [] with open(outname, "w", encoding="utf-8") as fh: tmpy = re.escape(tmp_prefix() + ".y") From 48008a2fc96a94caa498c0038b4b7ecbe8dccded Mon Sep 17 00:00:00 2001 From: github action Date: Wed, 10 Jun 2026 16:19:51 +0000 Subject: [PATCH 02/89] Apply 'make format' [ci skip] --- src/bisonpre | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bisonpre b/src/bisonpre index 36db4b48f..5db061a5a 100755 --- a/src/bisonpre +++ b/src/bisonpre @@ -154,7 +154,8 @@ def clean_output(filename: str, outname: str, is_output: bool, is_c: bool) -> No if modify: out.insert(-1, ifdefined) out.insert(-1, " _Pragma (\"GCC diagnostic push\")\n") - out.insert(-1, + out.insert( + -1, " _Pragma (\"GCC diagnostic ignored \\\"-Wfree-nonheap-object\\\"\")\n") out.insert(-1, endif) out.append(line) From e2e1bfe8dd96bbf1437dae3fffacdcec61c95515 Mon Sep 17 00:00:00 2001 From: Shashvat Date: Wed, 10 Jun 2026 23:18:38 +0530 Subject: [PATCH 03/89] Internals: Remove unused V3TSP (#7194) (#7757) --- docs/CONTRIBUTORS | 1 + src/CMakeLists.txt | 2 - src/Makefile_obj.in | 1 - src/V3EmitCFunc.cpp | 2 - src/V3TSP.cpp | 701 ---------------------------------- src/V3TSP.h | 57 --- src/Verilator.cpp | 2 - src/cppcheck-suppressions.txt | 2 - 8 files changed, 1 insertion(+), 767 deletions(-) delete mode 100644 src/V3TSP.cpp delete mode 100644 src/V3TSP.h diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index ef3746a76..dae170a70 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -326,3 +326,4 @@ Yogish Sekhar Zubin Jain Muzaffer Kal Yilin Li +Shashvat Prabhu diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ad2178203..b4f4be9aa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -183,7 +183,6 @@ set(HEADERS V3String.h V3Subst.h V3SymTable.h - V3TSP.h V3Table.h V3Task.h V3ThreadPool.h @@ -358,7 +357,6 @@ set(COMMON_SOURCES V3StatsReport.cpp V3String.cpp V3Subst.cpp - V3TSP.cpp V3Table.cpp V3Task.cpp V3ThreadPool.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 08c8899fc..dcd78a431 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -337,7 +337,6 @@ RAW_OBJS_PCH_ASTNOMT = \ V3SplitVar.o \ V3StackCount.o \ V3Subst.o \ - V3TSP.o \ V3Table.o \ V3Task.o \ V3Timing.o \ diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 8eb82b31f..11f2ac14a 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -18,8 +18,6 @@ #include "V3EmitCFunc.h" -#include "V3TSP.h" - #include #include diff --git a/src/V3TSP.cpp b/src/V3TSP.cpp deleted file mode 100644 index 4244a5676..000000000 --- a/src/V3TSP.cpp +++ /dev/null @@ -1,701 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Implementation of Christofides algorithm to -// approximate the solution to the traveling salesman problem. -// -// ISSUES: This isn't exactly Christofides algorithm; see the TODO -// in perfectMatching(). True minimum-weight perfect matching -// would produce a better result. How much better is TBD. -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT - -#include "V3TSP.h" - -#include "V3File.h" -#include "V3Graph.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -VL_DEFINE_DEBUG_FUNCTIONS; - -//###################################################################### -// Support classes - -namespace V3TSP { -static uint32_t s_edgeIdNext = 0; - -static void selfTestStates(); -static void selfTestString(); -} // namespace V3TSP - -// Vertex that tracks a per-vertex key -template -class TspVertexTmpl final : public V3GraphVertex { - VL_RTTI_IMPL(TspVertexTmpl, V3GraphVertex) -private: - const T_Key m_key; - -public: - TspVertexTmpl(V3Graph* graphp, const T_Key& k) - : V3GraphVertex{graphp} - , m_key{k} {} - ~TspVertexTmpl() override = default; - const T_Key& key() const { return m_key; } - -private: - VL_UNCOPYABLE(TspVertexTmpl); -}; - -// TspGraphTmpl represents a complete graph, templatized to work with -// different T_Key types. -template -class TspGraphTmpl final : public V3Graph { -public: - // TYPES - using Vertex = TspVertexTmpl; - - enum VertexState : uint32_t { CLEAR = 0, MST_VISITED = 1, UNMATCHED_ODD = 2 }; - - // MEMBERS - std::unordered_map m_vertices; // T_Key to Vertex lookup map - - // CONSTRUCTORS - TspGraphTmpl() - : V3Graph{} {} - ~TspGraphTmpl() override = default; - - // METHODS - void addVertex(const T_Key& key) { - const bool newEntry = m_vertices.emplace(key, new Vertex{this, key}).second; - UASSERT(newEntry, "Vertex already exists with same key"); - } - - // For purposes of TSP, we are using non-directional graphs. - // Map that onto the normally-directional V3Graph by creating - // a matched pairs of opposite-directional edges to represent - // each non-directional edge: - void addEdge(const T_Key& from, const T_Key& to, int cost) { -#if VL_DEBUG // Hot, so only in debug - UASSERT(from != to, "Adding edge would form a loop"); - UASSERT(cost >= 0, "Negative weight edge"); -#endif - Vertex* const fp = findVertex(from); - Vertex* const tp = findVertex(to); - - // No need to dedup edges. - // The only time we may create duplicate edges is when - // combining the MST with the perfect-matched pairs, - // and in that case, we want to permit duplicate edges. - const uint32_t edgeId = ++V3TSP::s_edgeIdNext; - - // We want to be able to compare edges quickly for a total - // ordering, so pre-compute a sorting key and store it in - // the edge user field. We also want easy access to the 'id' - // which uniquely identifies a single bidir edge. Luckily we - // can do both efficiently. - // cppcheck-suppress badBitmaskCheck - const uint64_t userValue = (static_cast(cost) << 32) | edgeId; - (new V3GraphEdge{this, fp, tp, cost})->user(userValue); - (new V3GraphEdge{this, tp, fp, cost})->user(userValue); - } - - static uint32_t getEdgeId(const V3GraphEdge* edgep) { - return static_cast(edgep->user()); - } - - // cppcheck-suppress duplInheritedMember - bool empty() const { return m_vertices.empty(); } - - const std::list keysToVertexList(const std::vector& odds) { - std::list vertices; - for (unsigned i = 0; i < odds.size(); ++i) vertices.push_back(findVertex(odds.at(i))); - return vertices; - } - -private: - // We will keep sorted lists of edges as vectors - using EdgeList = std::vector; - - static bool edgeCmp(const V3GraphEdge* ap, const V3GraphEdge* bp) { - // We pre-computed these when adding the edge to sort first by cost, then by identity - return ap->user() > bp->user(); - } - - struct EdgeListCmp final { - bool operator()(const EdgeList* ap, const EdgeList* bp) const { - // Compare heads - return edgeCmp(bp->back(), ap->back()); - } - }; - - static Vertex* castVertexp(V3GraphVertex* vxp) { return static_cast(vxp); } - static const Vertex* castVertexp(const V3GraphVertex* vxp) { - return static_cast(vxp); - } - -public: - // From *this, populate *mstp with the minimum spanning tree. - // *mstp must be initially empty. - void makeMinSpanningTree(TspGraphTmpl* mstp) { - UASSERT(mstp->empty(), "Output graph must start empty"); - - // Use Prim's algorithm to efficiently construct the MST. - - uint32_t vertCount = 0; - for (V3GraphVertex& vtx : vertices()) { - mstp->addVertex(castVertexp(&vtx)->key()); - vertCount++; - } - - // Allocate storage for per vertex edge lists up front. - std::vector allocatedEdgeLists{vertCount}; - - // Index of vertex in visitation order (used for indexing allocatedEdgeLists) - uint32_t vertIdx = 0; - - // We keep pending edges as a sorted set of sorted vectors. This allows us to find the - // lowest cost edge quickly, while also reducing the cost of inserting batches of new - // edges, which is what we need in this algorithm. - std::set pendingEdgeListps; - - const auto visit = [&](V3GraphVertex* vtxp) { -#ifdef VL_DEBUG // Very hot, so only in debug - UASSERT(vtxp->user() == VertexState::CLEAR, "Vertex visited twice"); -#endif - // Mark vertex as visited - vtxp->user(VertexState::MST_VISITED); - // Allocate new edge list - EdgeList* const newEdgesp = &allocatedEdgeLists[vertIdx++]; - // Gather out edges of this vertex - for (V3GraphEdge& edge : vtxp->outEdges()) { - // Don't add edges leading to vertices we already visited. This is a highly - // connected graph, so this greatly reduces the cost of maintaining the pending - // set. - if (edge.top()->user() == VertexState::MST_VISITED) continue; - newEdgesp->push_back(&edge); - } - // If no relevant out edges, then we are done - if (newEdgesp->empty()) return; - // Sort new edge list - std::sort(newEdgesp->begin(), newEdgesp->end(), edgeCmp); - // Add edge list to pending set - pendingEdgeListps.insert(newEdgesp); - }; - - // To start, choose an arbitrary vertex and visit it. - visit(vertices().frontp()); - - // Repeatedly find the least costly edge in the pending set. - // If it connects to an unvisited node, visit that node and update - // the pending edge set. If it connects to an already visited node, - // discard it and repeat again. - while (!pendingEdgeListps.empty()) { - // Grab lowest cost edge list - auto it = pendingEdgeListps.begin(); - - // Grab lowest cost edge - EdgeList* const bestEdgeListp = *it; - const V3GraphEdge* const bestEdgep = bestEdgeListp->back(); - - // Remove the lowest cost edge list. We will remove its lowest cost element, and either - // we are done with (if it had a single element) it in which case it will be discarded, - // or the cost of the new head element might be different, so we will need to re-insert - // it in the right place. In either case, it needs to be removed. - pendingEdgeListps.erase(it); - - // If the lowest cost edge list is not a singleton list, then pop the lowest cost - // edge and re-insert the remaining edge list into the pending set. - if (bestEdgeListp->size() > 1) { - bestEdgeListp->pop_back(); - pendingEdgeListps.insert(bestEdgeListp); - } - - // Grab the target vertex - Vertex* const neighborp = castVertexp(bestEdgep->top()); - - // If the neighbour is not yet visited - if (neighborp->user() == VertexState::CLEAR) { - // Visit it - visit(neighborp); - - // Create the edge in our output MST graph - Vertex* const from_vertexp = castVertexp(bestEdgep->fromp()); - mstp->addEdge(from_vertexp->key(), neighborp->key(), bestEdgep->weight()); - -#if VL_DEBUG // Very hot loop, so only in debug - UASSERT(from_vertexp->user() == MST_VISITED, - "bestEdgep->fromp() should be already seen"); -#endif - } - } - - UASSERT(vertIdx == vertCount, "Should have visited all vertices"); - } - - // Populate *outp with a minimal perfect matching of *this. - // *outp must be initially empty. - void perfectMatching(const std::vector& oddKeys, TspGraphTmpl* outp) { - UASSERT(outp->empty(), "Output graph must start empty"); - - const std::list& odds = keysToVertexList(oddKeys); - UASSERT(odds.size() % 2 == 0, "number of odd-order nodes should be even"); - - for (Vertex* const vtxp : odds) { - outp->addVertex(vtxp->key()); - vtxp->user(VertexState::UNMATCHED_ODD); - } - - // TODO: The true Christofides algorithm calls for minimum-weight - // perfect matching. Instead, we have a simple greedy algorithm - // which might get close to the minimum, maybe, with luck? - // - // TODO: Revisit this. It's possible to compute the true minimum in - // N*N*log(N) time using variants of the Blossom algorithm. - // Implementing Blossom looks hard, maybe we can use an existing - // open source implementation -- for example the "LEMON" library - // which has a TSP solver. - - // ----- - - // Gather and sort all edges. We use a vector then sort, because this is faster than a - // sorted set. Reuse the comparator from Prim's routine (note it a 'greater', not a - // 'lesser' comparator). The logic is the same here. - // - // Note that there are two V3GraphEdge's representing a single bidir edge. While we could - // just add both to the pending list and get the same result, we will only add one (based - // on fast pointer comparison - this still yields deterministic results), in order to - // reduce the size of the working set. - std::vector pendingEdges; - - for (Vertex* const fromp : odds) { - for (V3GraphEdge& edge : fromp->outEdges()) { - Vertex* const top = castVertexp(edge.top()); - // There are two edges (in both directions) between these two vertices. Keep one. - if (fromp > top) continue; - // We only care about edges between the odd-order vertices - if (top->user() != VertexState::UNMATCHED_ODD) continue; - // Add to candidate list - pendingEdges.push_back(&edge); - } - } - - // Sort reverse iterators. This yields ascending order with a 'greater' comparator. - std::sort(pendingEdges.rbegin(), pendingEdges.rend(), edgeCmp); - - // Iterate over all edges, in order from low to high cost. - // For any edge whose ends are both odd-order vertices which - // haven't been matched yet, match them. - for (V3GraphEdge* const edgep : pendingEdges) { - Vertex* const fromp = castVertexp(edgep->fromp()); - Vertex* const top = castVertexp(edgep->top()); - if (fromp->user() == VertexState::UNMATCHED_ODD - && top->user() == VertexState::UNMATCHED_ODD) { - outp->addEdge(fromp->key(), top->key(), edgep->weight()); - fromp->user(VertexState::CLEAR); - top->user(VertexState::CLEAR); - } - } - } - - void combineGraph(const TspGraphTmpl& g) { - std::unordered_set edges_done; - for (const V3GraphVertex& vtx : g.vertices()) { - const Vertex* const fromp = castVertexp(&vtx); - for (const V3GraphEdge& edge : fromp->outEdges()) { - const Vertex* const top = castVertexp(edge.top()); - if (edges_done.insert(getEdgeId(&edge)).second) { - addEdge(fromp->key(), top->key(), edge.weight()); - } - } - } - } - - void findEulerTourRecurse(std::unordered_set* markedEdgesp, Vertex* startp, - std::vector* sortedOutp) { - Vertex* cur_vertexp = startp; - - // Go on a random tour. Fun! - std::vector tour; - do { - UINFO(6, "Adding " << cur_vertexp->key() << " to tour."); - tour.push_back(cur_vertexp); - - // Look for an arbitrary edge we've not yet marked - for (V3GraphEdge& edge : cur_vertexp->outEdges()) { - const uint32_t edgeId = getEdgeId(&edge); - if (markedEdgesp->end() == markedEdgesp->find(edgeId)) { - // This edge is not yet marked, so follow it. - markedEdgesp->insert(edgeId); - Vertex* const neighborp = castVertexp(edge.top()); - UINFO(6, "following edge " << edgeId << " from " << cur_vertexp->key() - << " to " << neighborp->key()); - cur_vertexp = neighborp; - goto found; - } - } - v3fatalSrc("No unmarked edges found in tour"); - found:; - } while (cur_vertexp != startp); - UINFO(6, "stopped, got back to start of tour @ " << cur_vertexp->key()); - - // Look for nodes on the tour that still have - // un-marked edges. If we find one, recurse. - for (Vertex* vxp : tour) { - bool recursed; - do { - recursed = false; - // Look for an arbitrary edge at vxp we've not yet marked - for (V3GraphEdge& edge : vxp->outEdges()) { - const uint32_t edgeId = getEdgeId(&edge); - if (markedEdgesp->end() == markedEdgesp->find(edgeId)) { - UINFO(6, "Recursing."); - findEulerTourRecurse(markedEdgesp, vxp, sortedOutp); - recursed = true; - goto recursed; - } - } - recursed:; - } while (recursed); - sortedOutp->push_back(vxp->key()); - } - - if (debug() >= 6) { - UINFO(0, "Tour was:"); - for (const Vertex* vxp : tour) std::cout << "- " << vxp->key() << '\n'; - std::cout << "-\n"; - } - } - - void dumpGraph(std::ostream& os, const string& nameComment) const { - // UINFO(0) as controlled by caller - os << "At " << nameComment << ", dumping graph. Keys:\n"; - for (const V3GraphVertex& vtx : vertices()) { - const Vertex* const tspvp = castVertexp(&vtx); - os << " " << tspvp->key() << '\n'; - for (const V3GraphEdge& edge : tspvp->outEdges()) { - const Vertex* const neighborp = castVertexp(edge.top()); - os << " has edge " << getEdgeId(&edge) << " to " << neighborp->key() << '\n'; - } - } - } - void dumpGraphFilePrefixed(const string& nameComment) const { - if (dumpLevel()) { - const string filename = v3Global.debugFilename(nameComment) + ".txt"; - const std::unique_ptr logp{V3File::new_ofstream(filename)}; - if (logp->fail()) v3fatal("Can't write file: " << filename); - dumpGraph(*logp, nameComment); - } - } - - void findEulerTour(std::vector* sortedOutp) { - UASSERT(sortedOutp->empty(), "Output graph must start empty"); - if (::dumpGraphLevel() >= 6) dumpDotFilePrefixed("findEulerTour"); - std::unordered_set markedEdges; - // Pick a start node - Vertex* const start_vertexp = castVertexp(vertices().frontp()); - findEulerTourRecurse(&markedEdges, start_vertexp, sortedOutp); - } - - std::vector getOddDegreeKeys() const { - std::vector result; - for (const V3GraphVertex& vtx : vertices()) { - const Vertex* const tspvp = castVertexp(&vtx); - const uint32_t degree = vtx.outEdges().size(); - if (degree & 1) result.push_back(tspvp->key()); - } - return result; - } - -private: - Vertex* findVertex(const T_Key& key) const { - const auto it = m_vertices.find(key); - UASSERT(it != m_vertices.end(), "Vertex not found"); - return it->second; - } - VL_UNCOPYABLE(TspGraphTmpl); -}; - -//###################################################################### -// Main algorithm - -void V3TSP::tspSort(const V3TSP::StateVec& states, V3TSP::StateVec* resultp) VL_MT_SAFE { - UASSERT(resultp->empty(), "Output graph must start empty"); - - // Make this TSP implementation work for graphs of size 0 or 1 - // which, unfortunately, is a special case as the following - // code assumes >= 2 nodes. - if (states.empty()) return; - if (states.size() == 1) { - resultp->push_back(*(states.begin())); - return; - } - - // Build the initial graph from the starting state set. - using Graph = TspGraphTmpl; - Graph graph; - for (const auto& state : states) graph.addVertex(state); - for (V3TSP::StateVec::const_iterator it = states.begin(); it != states.end(); ++it) { - for (V3TSP::StateVec::const_iterator jt = it; jt != states.end(); ++jt) { - if (it == jt) continue; - graph.addEdge(*it, *jt, (*it)->cost(*jt)); - } - } - - // Create the minimum spanning tree - Graph minGraph; - graph.makeMinSpanningTree(&minGraph); - if (dumpGraphLevel() >= 6) minGraph.dumpGraphFilePrefixed("minGraph"); - - const std::vector oddDegree = minGraph.getOddDegreeKeys(); - Graph matching; - graph.perfectMatching(oddDegree, &matching); - if (dumpGraphLevel() >= 6) matching.dumpGraphFilePrefixed("matching"); - - // Adds edges to minGraph, the resulting graph will have even number of - // edge counts at every vertex: - minGraph.combineGraph(matching); - - V3TSP::StateVec prelim_result; - minGraph.findEulerTour(&prelim_result); - - UASSERT(prelim_result.size() >= states.size(), "Algorithm size error"); - - // Discard duplicate nodes that the Euler tour might contain. - { - std::unordered_set seen; - for (V3TSP::StateVec::iterator it = prelim_result.begin(); it != prelim_result.end(); - ++it) { - const TspStateBase* const elemp = *it; - const auto itFoundPair = seen.insert(elemp); - if (itFoundPair.second) resultp->push_back(elemp); - } - } - - UASSERT(resultp->size() == states.size(), "Algorithm size error"); - - // Find the most expensive arc and rotate the list so that the most - // expensive arc connects the last and first elements. (Since we're not - // modeling something that actually cycles back, we don't need to pay - // that cost at all.) - { - unsigned max_cost = 0; - unsigned max_cost_idx = 0; - for (unsigned i = 0; i < resultp->size(); ++i) { - const TspStateBase* const ap = (*resultp)[i]; - const TspStateBase* const bp - = (i + 1 == resultp->size()) ? (*resultp)[0] : (*resultp)[i + 1]; - const unsigned cost = ap->cost(bp); - if (cost > max_cost) { - max_cost = cost; - max_cost_idx = i; - } - } - - if (max_cost_idx == resultp->size() - 1) { - // List is already rotated for minimum cost. stop. - return; - } - - V3TSP::StateVec new_result; - unsigned i = max_cost_idx + 1; - UASSERT(i < resultp->size(), "Algorithm size error"); - while (i != max_cost_idx) { - new_result.push_back((*resultp)[i]); - i++; - if (i >= resultp->size()) i = 0; - } - new_result.push_back((*resultp)[i]); - - UASSERT(resultp->size() == new_result.size(), "Algorithm size error"); - *resultp = new_result; - } -} - -//###################################################################### -// Self Tests - -class TspTestState final : public V3TSP::TspStateBase { -public: - TspTestState(unsigned xpos, unsigned ypos) - : m_xpos{xpos} - , m_ypos{ypos} - , m_serial{++s_serialNext} {} - ~TspTestState() override = default; - int cost(const TspStateBase* otherp) const override VL_MT_SAFE { - return cost(dynamic_cast(otherp)); - } - static unsigned diff(unsigned a, unsigned b) VL_PURE { - if (a > b) return a - b; - return b - a; - } - int cost(const TspTestState* otherp) const VL_PURE { - // For test purposes, each TspTestState is merely a point - // on the Cartesian plane; cost is the linear distance - // between two points. - unsigned xabs; - unsigned yabs; - xabs = diff(otherp->m_xpos, m_xpos); - yabs = diff(otherp->m_ypos, m_ypos); - return std::lround(std::sqrt(xabs * xabs + yabs * yabs)); - } - unsigned xpos() const { return m_xpos; } - unsigned ypos() const { return m_ypos; } - - bool operator<(const TspStateBase& other) const override { - return operator<(dynamic_cast(other)); - } - bool operator<(const TspTestState& other) const { return m_serial < other.m_serial; } - -private: - const unsigned m_xpos; - const unsigned m_ypos; - const unsigned m_serial; - static unsigned s_serialNext; -}; - -unsigned TspTestState::s_serialNext = 0; - -void V3TSP::selfTestStates() { - // Linear test -- coords all along the x-axis - { - V3TSP::StateVec states; - const TspTestState s10{10, 0}; - const TspTestState s60{60, 0}; - const TspTestState s20{20, 0}; - const TspTestState s100{100, 0}; - const TspTestState s5{5, 0}; - states.push_back(&s10); - states.push_back(&s60); - states.push_back(&s20); - states.push_back(&s100); - states.push_back(&s5); - - V3TSP::StateVec result; - tspSort(states, &result); - - V3TSP::StateVec expect; - expect.push_back(&s100); - expect.push_back(&s60); - expect.push_back(&s20); - expect.push_back(&s10); - expect.push_back(&s5); - if (VL_UNCOVERABLE(expect != result)) { - for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) { - const TspTestState* const statep = dynamic_cast(*it); - cout << statep->xpos() << " "; - } - cout << endl; - v3fatalSrc("TSP linear self-test fail. Result (above) did not match expectation."); - } - } - - // Second test. Coords are distributed in 2D space. - // Test that tspSort() will rotate the list for minimum cost. - { - V3TSP::StateVec states; - const TspTestState a{0, 0}; - const TspTestState b{100, 0}; - const TspTestState c{200, 0}; - const TspTestState d{200, 100}; - const TspTestState e{150, 150}; - const TspTestState f{0, 150}; - const TspTestState g{0, 100}; - - states.push_back(&a); - states.push_back(&b); - states.push_back(&c); - states.push_back(&d); - states.push_back(&e); - states.push_back(&f); - states.push_back(&g); - - V3TSP::StateVec result; - tspSort(states, &result); - - V3TSP::StateVec expect; - expect.push_back(&f); - expect.push_back(&g); - expect.push_back(&a); - expect.push_back(&b); - expect.push_back(&c); - expect.push_back(&d); - expect.push_back(&e); - - if (VL_UNCOVERABLE(expect != result)) { - for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) { - const TspTestState* const statep = dynamic_cast(*it); - cout << statep->xpos() << "," << statep->ypos() << " "; - } - cout << endl; - v3fatalSrc( - "TSP 2d cycle=false self-test fail. Result (above) did not match expectation."); - } - } -} - -void V3TSP::selfTestString() { - using Graph = TspGraphTmpl; - Graph graph; - graph.addVertex("0"); - graph.addVertex("1"); - graph.addVertex("2"); - graph.addVertex("3"); - - graph.addEdge("0", "1", 3943); - graph.addEdge("0", "2", 3456); - graph.addEdge("0", "3", 4920); - graph.addEdge("1", "2", 2730); - graph.addEdge("1", "3", 8199); - graph.addEdge("2", "3", 4130); - - Graph minGraph; - graph.makeMinSpanningTree(&minGraph); - if (dumpGraphLevel() >= 6) minGraph.dumpGraphFilePrefixed("minGraph"); - - const std::vector oddDegree = minGraph.getOddDegreeKeys(); - Graph matching; - graph.perfectMatching(oddDegree, &matching); - if (dumpGraphLevel() >= 6) matching.dumpGraphFilePrefixed("matching"); - - minGraph.combineGraph(matching); - - std::vector result; - minGraph.findEulerTour(&result); - - std::vector expect; - expect.emplace_back("0"); - expect.emplace_back("2"); - expect.emplace_back("1"); - expect.emplace_back("2"); - expect.emplace_back("3"); - - if (VL_UNCOVERABLE(expect != result)) { - for (const string& i : result) cout << i << " "; - cout << endl; - v3fatalSrc("TSP string self-test fail. Result (above) did not match expectation."); - } -} - -void V3TSP::selfTest() { - selfTestString(); - selfTestStates(); -} diff --git a/src/V3TSP.h b/src/V3TSP.h deleted file mode 100644 index 840f5fa96..000000000 --- a/src/V3TSP.h +++ /dev/null @@ -1,57 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Implementation of Christofides algorithm to -// approximate the solution to the traveling salesman problem. -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#ifndef VERILATOR_V3TSP_H_ -#define VERILATOR_V3TSP_H_ - -#include "config_build.h" -#include "verilatedos.h" - -#include "V3Error.h" - -#include - -namespace V3TSP { -// Perform a "Traveling Salesman Problem" optimizing sort -// on any type you like -- so long as inherits from TspStateBase. - -class TspStateBase VL_NOT_FINAL { -public: - // This is the cost function that the TSP sort will minimize. - // All costs in V3TSP are int, chosen to match the type of - // V3GraphEdge::weight() which will reflect each edge's cost. - virtual int cost(const TspStateBase* otherp) const VL_MT_SAFE = 0; - - // This operator< must place a meaningless, arbitrary, but - // stable order on all TspStateBase's. It's used only to - // key maps so that iteration is stable, without relying - // on pointer values that could lead to nondeterminism. - virtual bool operator<(const TspStateBase& otherp) const = 0; - - virtual ~TspStateBase() = default; -}; - -using StateVec = std::vector; - -// Given an unsorted set of TspState's, sort them to minimize -// the transition cost for walking the sorted list. -void tspSort(const StateVec& states, StateVec* resultp) VL_MT_SAFE; - -void selfTest() VL_MT_DISABLED; -} // namespace V3TSP - -#endif // Guard diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 13bb95c6f..f94c8655d 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -103,7 +103,6 @@ #include "V3Stats.h" #include "V3String.h" #include "V3Subst.h" -#include "V3TSP.h" #include "V3Table.h" #include "V3Task.h" #include "V3ThreadPool.h" @@ -734,7 +733,6 @@ static bool verilate(const string& argString) { VHashSha256::selfTest(); VSpellCheck::selfTest(); V3Graph::selfTest(); - V3TSP::selfTest(); V3ScoreboardBase::selfTest(); V3Order::selfTestParallel(); V3ExecGraph::selfTest(); diff --git a/src/cppcheck-suppressions.txt b/src/cppcheck-suppressions.txt index 754ef3c4b..6d9ab321d 100644 --- a/src/cppcheck-suppressions.txt +++ b/src/cppcheck-suppressions.txt @@ -137,8 +137,6 @@ constParameterPointer:src/V3Tristate.cpp constParameterReference:src/V3Tristate.cpp constVariablePointer:src/V3Tristate.cpp constVariableReference:src/V3Tristate.cpp -constVariablePointer:src/V3TSP.cpp -constVariableReference:src/V3TSP.cpp constParameterPointer:src/V3Undriven.cpp constVariablePointer:src/V3Undriven.cpp constParameterPointer:src/V3Unroll.cpp From 901909d3c71014956d54314a00f9b413b9b17a49 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 11 Jun 2026 10:52:43 +0100 Subject: [PATCH 04/89] Optimize conditional patterns sharing common MBSs/LSBs in DfgPeephole (#7760) Replace 3 DfgCond patterns with 2 more general ones that convert DfgCond with common MSBs/LSBs in both branches into a DfgConcat with a narrower DfgCond. This pattern arises frequently with Dfg synthesis. --- src/V3DfgDataType.h | 1 + src/V3DfgPeephole.cpp | 190 ++++++++++++++++++++++++-------- src/V3DfgPeepholePatterns.h | 5 +- src/V3DfgVertices.h | 1 + test_regress/t/t_dfg_peephole.v | 15 ++- 5 files changed, 162 insertions(+), 50 deletions(-) diff --git a/src/V3DfgDataType.h b/src/V3DfgDataType.h index 5af232b84..3011d254f 100644 --- a/src/V3DfgDataType.h +++ b/src/V3DfgDataType.h @@ -139,6 +139,7 @@ public: // Returns a Packed type of the given width static const DfgDataType& packed(uint32_t width) { + UASSERT(width > 0, "Width must be positive"); // Find or create the right sized packed type const DfgDataType*& entryr = s_packedTypes[width]; if (!entryr) entryr = new DfgDataType{width}; diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index f89285eb7..34c1ac5a6 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -1188,6 +1188,102 @@ class V3DfgPeephole final : public DfgVisitor { return {fromp, lsb}; } + // Given a pair of vertices, returns a vertex representing the common LSBs of the two, + // and the number of common LSBs. Returns {nullptr, 0} if no common LSBs are found. + std::pair commonLSBs(DfgVertex* ap, DfgVertex* bp) { + if (ap == bp) return {ap, ap->width()}; + + // If both constants, check LSBs + if (DfgConst* const aConstp = ap->cast()) { + if (DfgConst* const bConstp = bp->cast()) { + const V3Number& aNum = aConstp->num(); + const V3Number& bNum = bConstp->num(); + // Max match is the shorter constant + const uint32_t maxMatch = std::min(aConstp->width(), bConstp->width()); + // Check all bits + uint32_t matchWidth = 0; + for (; matchWidth < maxMatch; ++matchWidth) { + if (aNum.bitIs0(matchWidth) != bNum.bitIs0(matchWidth)) break; + } + // Will always return the shorter constant in case it can be used directly + DfgConst* const shorterp = aConstp->width() < bConstp->width() ? aConstp : bConstp; + return {matchWidth ? shorterp : nullptr, matchWidth}; + } + } + + // If Concat, check against the RHS + if (DfgConcat* const catp = ap->cast()) return commonLSBs(catp->rhsp(), bp); + if (DfgConcat* const catp = bp->cast()) return commonLSBs(ap, catp->rhsp()); + + // If selecting the LSBs, check against the source of the Sel + if (DfgSel* const selp = ap->cast()) { + if (selp->lsb() == 0) { + DfgVertex* const fromp = selp->fromp(); + const std::pair common = commonLSBs(fromp, bp); + return {common.first, std::min(common.second, selp->width())}; + } + } + if (DfgSel* const selp = bp->cast()) { + if (selp->lsb() == 0) { + DfgVertex* const fromp = selp->fromp(); + const std::pair common = commonLSBs(ap, fromp); + return {common.first, std::min(common.second, selp->width())}; + } + } + + // Otherwise no common LSBs + return {nullptr, 0}; + } + + // Given a pair of vertices, returns a vertex representing the common MSBs of the two, + // and the number of common LSBs. Returns {nullptr, 0} if no common LSBs are found. + std::pair commonMSBs(DfgVertex* ap, DfgVertex* bp) { + if (ap == bp) return {ap, ap->width()}; + + // If both constants, check MSBs + if (DfgConst* const aConstp = ap->cast()) { + if (DfgConst* const bConstp = bp->cast()) { + const uint32_t aMsb = aConstp->width() - 1; + const uint32_t bMsb = bConstp->width() - 1; + const V3Number& aNum = aConstp->num(); + const V3Number& bNum = bConstp->num(); + // Max match is the shorter constant + const uint32_t maxMatch = std::min(aConstp->width(), bConstp->width()); + // Check all bits + uint32_t matchWidth = 0; + for (; matchWidth < maxMatch; ++matchWidth) { + if (aNum.bitIs0(aMsb - matchWidth) != bNum.bitIs0(bMsb - matchWidth)) break; + } + // Will always return the shorter constant in case it can be used directly + DfgConst* const shorterp = aMsb < bMsb ? aConstp : bConstp; + return {matchWidth ? shorterp : nullptr, matchWidth}; + } + } + + // If Concat, check against the LHS + if (DfgConcat* const catp = ap->cast()) return commonMSBs(catp->lhsp(), bp); + if (DfgConcat* const catp = bp->cast()) return commonMSBs(ap, catp->lhsp()); + + // If selecting the MSBs, check against the source of the Sel + if (DfgSel* const selp = ap->cast()) { + DfgVertex* const fromp = selp->fromp(); + if (selp->msb() == fromp->width() - 1) { + const std::pair common = commonMSBs(fromp, bp); + return {common.first, std::min(common.second, selp->width())}; + } + } + if (DfgSel* const selp = bp->cast()) { + DfgVertex* const fromp = selp->fromp(); + if (selp->msb() == fromp->width() - 1) { + const std::pair common = commonMSBs(ap, fromp); + return {common.first, std::min(common.second, selp->width())}; + } + } + + // Otherwise no common MSBs + return {nullptr, 0}; + } + // VISIT methods void visit(DfgVertex*) override {} @@ -2834,56 +2930,62 @@ class V3DfgPeephole final : public DfgVisitor { } } - if (DfgConcat* const tConcatp = thenp->cast()) { - if (DfgConcat* const eConcatp = elsep->cast()) { - DfgVertex* const tRhsp = tConcatp->rhsp(); - DfgVertex* const tLhsp = tConcatp->lhsp(); - DfgVertex* const eRhsp = eConcatp->rhsp(); - DfgVertex* const eLhsp = eConcatp->lhsp(); - - if (isSame(tRhsp, eRhsp)) { - APPLYING(REPLACE_COND_SAME_CAT_RHS) { - DfgCond* const newCondp - = make(flp, tLhsp->dtype(), condp, tLhsp, eLhsp); - replace(make(vtxp, newCondp, tRhsp)); - return; - } - } - - if (isSame(tLhsp, eLhsp)) { - APPLYING(REPLACE_COND_SAME_CAT_LHS) { - DfgCond* const newCondp - = make(flp, tRhsp->dtype(), condp, tRhsp, eRhsp); - replace(make(vtxp, tLhsp, newCondp)); + if (!thenp->is() && !elsep->is()) { + const std::pair cLSBs = commonLSBs(thenp, elsep); + if (cLSBs.first) { + APPLYING(REPLACE_COND_COMMON_LSBS) { + // Create new RHS + const uint32_t rWidth = cLSBs.second; + DfgVertex* rhsp = cLSBs.first; + if (rWidth != rhsp->width()) { + const DfgDataType& rDtype = DfgDataType::packed(rWidth); + rhsp = make(flp, rDtype, rhsp, 0U); + } + // If it's all the same, just replace + if (rhsp->dtype() == vtxp->dtype()) { + // Note this branch can only be hit if rules run in the right order, + // it might have missing code coverage after a refactor. + replace(rhsp); return; } + // Create new LHS + const uint32_t lWidth = vtxp->width() - rWidth; + const DfgDataType& lDtype = DfgDataType::packed(lWidth); + DfgVertex* const lThenp = make(flp, lDtype, thenp, rWidth); + DfgVertex* const lElsep = make(flp, lDtype, elsep, rWidth); + DfgVertex* const lhsp = make(flp, lDtype, condp, lThenp, lElsep); + // Replace with concat + replace(make(vtxp, lhsp, rhsp)); + return; } } - if (!tConcatp->hasMultipleSinks()) { - if (DfgConcat* const tRCatp = tConcatp->rhsp()->cast()) { - if (!tRCatp->hasMultipleSinks()) { - if (DfgSel* const tLSelp = tConcatp->lhsp()->cast()) { - if (DfgSel* const tRRSelp = tRCatp->rhsp()->cast()) { - if (tLSelp->lsb() == tRCatp->width() // - && tRRSelp->lsb() == 0 // - && isSame(tLSelp->fromp(), elsep) // - && isSame(tRRSelp->fromp(), elsep)) { - APPLYING(REPLACE_COND_INSERT) { - DfgVertex* const newTp = tRCatp->lhsp(); - DfgVertex* const newEp = make( - flp, newTp->dtype(), elsep, tRRSelp->width()); - DfgCond* const newCp = make(flp, newTp->dtype(), - condp, newTp, newEp); - replace(make( - vtxp, tLSelp, - make(tRCatp, newCp, tRRSelp))); - return; - } - } - } - } + const std::pair cMSBs = commonMSBs(thenp, elsep); + if (cMSBs.first) { + APPLYING(REPLACE_COND_COMMON_MSBS) { + // Create new LHS + const uint32_t lWidth = cMSBs.second; + DfgVertex* lhsp = cMSBs.first; + if (lWidth != lhsp->width()) { + const DfgDataType& lDtype = DfgDataType::packed(lWidth); + lhsp = make(flp, lDtype, lhsp, lhsp->width() - lWidth); } + // If it's all the same, just replace + if (lhsp->dtype() == vtxp->dtype()) { + // Note this branch can only be hit if rules run in the right order, + // it might have missing code coverage after a refactor. + replace(lhsp); + return; + } + // Create new RHS + const uint32_t rWidth = vtxp->width() - lWidth; + const DfgDataType& rDtype = DfgDataType::packed(rWidth); + DfgVertex* const rThenp = make(flp, rDtype, thenp, 0U); + DfgVertex* const rElsep = make(flp, rDtype, elsep, 0U); + DfgVertex* const rhsp = make(flp, rDtype, condp, rThenp, rElsep); + // Replace with concat + replace(make(vtxp, lhsp, rhsp)); + return; } } } diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index d8d295b2d..4da947664 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -112,17 +112,16 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SAME_REP_ON_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_LSBS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_MSBS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONES_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONE_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INC) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INSERT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_RHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_LHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_ELSE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_THEN) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_THEN_OR_LHS) \ diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index a3d3adf8e..f8ca8e9c8 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -299,6 +299,7 @@ public: void fromp(DfgVertex* vtxp) { srcp(vtxp); } uint32_t lsb() const { return m_lsb; } void lsb(uint32_t value) { m_lsb = value; } + uint32_t msb() const { return m_lsb + width() - 1; } }; class DfgUnitArray final : public DfgVertexUnary { diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 3c9ce7ae8..2d679a8de 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -283,10 +283,20 @@ module t ( `signal(REPLACE_COND_CONST_ZERO_ONAE, rand_a[0] ? 80'b0 : -80'b1); `signal(REPLACE_COND_CAT_LHS_CONST_ONE_ZERO, rand_a[0] ? {8'b1, rand_b[0]} : {8'b0, rand_b[1]}); `signal(REPLACE_COND_CAT_LHS_CONST_ZERO_ONE, rand_a[0] ? {8'b0, rand_b[0]} : {8'b1, rand_b[1]}); - `signal(REPLACE_COND_SAME_CAT_LHS, rand_a[0] ? {8'd0, rand_b[0]} : {8'd0, rand_b[1]}); - `signal(REPLACE_COND_SAME_CAT_RHS, rand_a[0] ? {rand_b[0], 8'd0} : {rand_b[1], 8'd0}); `signal(REPLACE_COND_SAM_COND_THEN, rand_a[0] ? (rand_a[0] ? rand_b[1:0] : rand_b[3:2]) : rand_b[5:4]); `signal(REPLACE_COND_SAM_COND_ELSE, rand_a[0] ? rand_b[1:0] : (rand_a[0] ? rand_b[3:2] : rand_b[5:4])); + + `signal(REPLACE_COND_COMMON_MSBS_A, rand_a[0] ? {8'd0, rand_b[0]} : {8'd0, rand_b[1]}); + `signal(REPLACE_COND_COMMON_MSBS_B, rand_a[0] ? {8'hf0, rand_b[1:0]} : {9'h1e2, rand_b[1]}); + `signal(REPLACE_COND_COMMON_MSBS_C, rand_a[0] ? {rand_a[63 -: 3] , rand_b[0]} : {rand_a[63 -: 2], rand_b[2:1]}); + `signal(REPLACE_COND_COMMON_LSBS_A, rand_a[0] ? {rand_b[0], 8'd0} : {rand_b[1], 8'd0}); + `signal(REPLACE_COND_COMMON_LSBS_B, rand_a[0] ? {rand_b[2:1], 8'h0f} : {rand_b[1], 9'h08f}); + `signal(REPLACE_COND_COMMON_LSBS_C, rand_a[0] ? {rand_b[0], rand_a[3:0]} : {rand_b[1:0], rand_a[2:0]}); + wire [5:0] tmp_REPLACE_COND_COMMON_LSBS_D = rand_b[5:0]; + wire [5:0] tmp_REPLACE_COND_COMMON_MSBS_D = rand_b[63:58]; + `signal(REPLACE_COND_COMMON_LSBS_D, rand_a[0] ? rand_b[4:0] : tmp_REPLACE_COND_COMMON_LSBS_D[4:0]); + `signal(REPLACE_COND_COMMON_MSBS_D, rand_a[0] ? rand_b[63:59] : tmp_REPLACE_COND_COMMON_MSBS_D[5:1]); + `signal(REMOVE_SHIFTL_ZERO, rand_a << 0); `signal(REPLACE_SHIFTL_OVER, rand_a << 64); `signal(REPLACE_SHIFTL_SEL, rand_a[27:0] << 4); @@ -352,7 +362,6 @@ module t ( `signal(REMOVE_EQ_BIT_1, 1'b1 == rand_a[0]); `signal(REMOVE_NEQ_BIT_0, 1'b0 != rand_a[0]); `signal(REPLACE_NEQ_BIT_1, 1'b1 != rand_a[0]); - `signal(REPLACE_COND_INSERT, rand_a[0] ? {rand_b[63:40], {1'd0, rand_b[38:0]}} : rand_b); `signal(REPLACE_REP_REP, {2{({3{rand_a[0]}})}}); // Operators that should work wiht mismatched widths From 394c9bc9b2ef5c5ecb8350f56d33d56383441018 Mon Sep 17 00:00:00 2001 From: Adam Kostrzewski Date: Thu, 11 Jun 2026 14:37:23 +0200 Subject: [PATCH 05/89] Fix FSM detect unchecked casts and variable redeclaration (#7758) --- docs/CONTRIBUTORS | 1 + src/V3AstNodes.cpp | 6 + src/V3Coverage.cpp | 4 +- src/V3FsmDetect.cpp | 35 ++-- test_regress/t/t_cover_fsm_concat_unsup.out | 6 + test_regress/t/t_cover_fsm_concat_unsup.py | 16 ++ test_regress/t/t_cover_fsm_concat_unsup.v | 18 ++ test_regress/t/t_cover_fsm_sel.out | 99 ++++++++++ test_regress/t/t_cover_fsm_sel.py | 30 +++ test_regress/t/t_cover_fsm_sel.v | 88 +++++++++ test_regress/t/t_cover_fsm_sel_assign.out | 89 +++++++++ test_regress/t/t_cover_fsm_sel_assign.py | 30 +++ test_regress/t/t_cover_fsm_sel_assign.v | 78 ++++++++ .../t/t_cover_fsm_sel_togglevar_unsup.out | 11 ++ .../t/t_cover_fsm_sel_togglevar_unsup.py | 16 ++ .../t/t_cover_fsm_sel_togglevar_unsup.v | 31 ++++ test_regress/t/t_fsm_duplicate.py | 18 ++ test_regress/t/t_fsm_duplicate.v | 173 ++++++++++++++++++ 18 files changed, 736 insertions(+), 13 deletions(-) create mode 100644 test_regress/t/t_cover_fsm_concat_unsup.out create mode 100755 test_regress/t/t_cover_fsm_concat_unsup.py create mode 100644 test_regress/t/t_cover_fsm_concat_unsup.v create mode 100644 test_regress/t/t_cover_fsm_sel.out create mode 100755 test_regress/t/t_cover_fsm_sel.py create mode 100644 test_regress/t/t_cover_fsm_sel.v create mode 100644 test_regress/t/t_cover_fsm_sel_assign.out create mode 100755 test_regress/t/t_cover_fsm_sel_assign.py create mode 100644 test_regress/t/t_cover_fsm_sel_assign.v create mode 100644 test_regress/t/t_cover_fsm_sel_togglevar_unsup.out create mode 100755 test_regress/t/t_cover_fsm_sel_togglevar_unsup.py create mode 100644 test_regress/t/t_cover_fsm_sel_togglevar_unsup.v create mode 100755 test_regress/t/t_fsm_duplicate.py create mode 100644 test_regress/t/t_fsm_duplicate.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index dae170a70..b78b4f101 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -9,6 +9,7 @@ Please see the Verilator manual for 200+ additional contributors. Thanks to all. 404allen404 Adam Bagley +Adam Kostrzewski Adrian Sampson Adrien Le Masle أحمد المحمودي (Ahmed El-Mahmoudy) diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 3bd3f66f6..e98c9bb89 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1322,6 +1322,12 @@ AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) { } else if (VN_IS(nodep, Sel)) { nodep = VN_AS(nodep, Sel)->fromp(); continue; + } else if (VN_IS(nodep, AssocSel)) { + nodep = VN_AS(nodep, AssocSel)->fromp(); + continue; + } else if (VN_IS(nodep, WildcardSel)) { + nodep = VN_AS(nodep, WildcardSel)->fromp(); + continue; } else if (overMembers && VN_IS(nodep, MemberSel)) { nodep = VN_AS(nodep, MemberSel)->fromp(); continue; diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index 63cb65b8a..6d1ff5bc1 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -530,8 +530,10 @@ class CoverageVisitor final : public VNVisitor { newent.cleanup(); } } - } else if (VN_IS(dtypep, QueueDType)) { + } else if (VN_IS(dtypep, QueueDType) || VN_IS(dtypep, AssocArrayDType) + || VN_IS(dtypep, WildcardArrayDType)) { // Not covered + varp->v3warn(COVERIGN, "Coverage ignored for type " << dtypep->prettyTypeName()); } else { dtypep->v3fatalSrc("Unexpected node data type in toggle coverage generation: " << dtypep->prettyTypeName()); diff --git a/src/V3FsmDetect.cpp b/src/V3FsmDetect.cpp index 4a2ccf8af..5ffee9e4c 100644 --- a/src/V3FsmDetect.cpp +++ b/src/V3FsmDetect.cpp @@ -30,6 +30,7 @@ #include "V3Ast.h" #include "V3Control.h" #include "V3Graph.h" +#include "V3UniqueNames.h" #include #include @@ -987,14 +988,23 @@ class FsmDetectVisitor final : public VNVisitor { return assp; } + static AstVarRef* tryExtractVarRef(AstNodeExpr* const exprp) { + AstVarRef* const varp = VN_CAST(AstArraySel::baseFromp(exprp, true), VarRef); + if (!varp) { + exprp->v3warn(COVERIGN, + "Ignoring unsupported: FSM coverage with " << exprp->prettyTypeName()); + return nullptr; + } + return varp; + } + static AstNodeAssign* nodeStateVarAssign(AstNode* nodep, AstVarScope*& stateVscp, AstVarScope*& fromVscp) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); - UASSERT_OBJ(lhsp, assp, "register commit lhs should be normalized to a VarRef"); + AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp()); AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef); - if (!rhsp) return nullptr; + if (!rhsp || !lhsp) return nullptr; stateVscp = lhsp->varScopep(); fromVscp = rhsp->varScopep(); return assp; @@ -1006,11 +1016,9 @@ class FsmDetectVisitor final : public VNVisitor { FsmStateValue& resetValue) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); - UASSERT_OBJ(lhsp, assp, - "conditional register commit lhs should be normalized to a VarRef"); + AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp()); AstCond* const rhsp = VN_CAST(assp->rhsp(), Cond); - if (!rhsp) return nullptr; + if (!rhsp || !lhsp) return nullptr; if (AstVarRef* const elsep = VN_CAST(rhsp->elsep(), VarRef)) { if (constValueStatus(rhsp->thenp(), resetValue) != ConstValueStatus::OK) return nullptr; @@ -1033,7 +1041,7 @@ class FsmDetectVisitor final : public VNVisitor { FsmStateValue& value) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); + AstVarRef* const lhsp = VN_CAST(AstArraySel::baseFromp(assp->lhsp(), true), VarRef); UASSERT_OBJ(lhsp, assp, "direct constant state assignment lhs should be normalized to a VarRef"); if (constValueStatus(assp->rhsp(), value) != ConstValueStatus::OK) return nullptr; @@ -1220,7 +1228,8 @@ class FsmDetectVisitor final : public VNVisitor { AstVarRef* vrefp = VN_CAST(eqp->lhsp(), VarRef); AstNodeExpr* valuep = eqp->rhsp(); if (!vrefp) { - vrefp = VN_AS(eqp->rhsp(), VarRef); + vrefp = tryExtractVarRef(eqp->rhsp()); + if (!vrefp) { return false; } valuep = eqp->lhsp(); } @@ -2082,6 +2091,7 @@ public: class FsmLowerVisitor final { // STATE - across all visitors const FsmState& m_state; + V3UniqueNames m_fsmBuildNames; // METHODS // Rebuild a state-typed constant using the tracked state variable @@ -2133,8 +2143,8 @@ class FsmLowerVisitor final { AstNodeModule* const modp = scopep->modp(); AstNodeDType* const prevDTypep = scopep->findLogicDType( sampleVscp->width(), sampleVscp->width(), sampleVscp->dtypep()->numeric()); - AstVarScope* const prevVscp - = scopep->createTemp("__Vfsmcov_prev__" + stateVscp->varp()->shortName(), prevDTypep); + const std::string tmpName = m_fsmBuildNames.get(stateVscp->varp()->shortName()); + AstVarScope* const prevVscp = scopep->createTemp(tmpName, prevDTypep); // The saved previous-state temp crosses the scheduler's pre/post split // in the same way as Verilator's built-in NBA shadow variables, so keep // both vars marked as post-life participants for stable MT ordering. @@ -2283,7 +2293,8 @@ public: // concrete coverage instrumentation while the saved scoped pointers are // still valid in the same pass. explicit FsmLowerVisitor(const FsmState& state) - : m_state{state} { + : m_state{state} + , m_fsmBuildNames{"__Vfsmcov_prev"} { for (const DetectedFsm& fsm : m_state.fsms()) { buildOne(*fsm.graphp); } } }; diff --git a/test_regress/t/t_cover_fsm_concat_unsup.out b/test_regress/t/t_cover_fsm_concat_unsup.out new file mode 100644 index 000000000..85da4d440 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.out @@ -0,0 +1,6 @@ +%Warning-COVERIGN: t/t_cover_fsm_concat_unsup.v:12:17: Ignoring unsupported: FSM coverage with CONCAT + 12 | assign c = ({a, b} == 8'h00); + | ^ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_concat_unsup.py b/test_regress/t/t_cover_fsm_concat_unsup.py new file mode 100755 index 000000000..4a63bc600 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage concat as unsupported operation test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename, verilator_flags2=['--coverage']) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_concat_unsup.v b/test_regress/t/t_cover_fsm_concat_unsup.v new file mode 100644 index 000000000..1f9cee77a --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.v @@ -0,0 +1,18 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input logic[6:0] a, + input logic b, + output logic c +); + assign c = ({a, b} == 8'h00); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_cover_fsm_sel.out b/test_regress/t/t_cover_fsm_sel.out new file mode 100644 index 000000000..4913f5502 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.out @@ -0,0 +1,99 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: Verilog Test module + // + // This file ONLY is placed under the Creative Commons Public Domain + // SPDX-FileCopyrightText: 2026 Antmicro + // SPDX-License-Identifier: CC0-1.0 + + package P; + typedef struct packed{ + logic [7:0] vs; + } C; + typedef struct packed{ + C a; int b; + } B; + typedef struct packed{ + B a; + } A; + endpackage + + module t ( +%000009 input clk + ); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + +%000001 logic rst; +%000001 logic start; + integer cyc; +%000001 state_t state /*verilator fsm_reset_arc*/; +%000002 P::A a; +%000001 logic done; + + logic [7:0] va[int]; + logic [7:0] va2d[int][int]; + +%000001 logic b; +%000001 logic c; +%000001 logic d; + + assign b = (a.a.a.vs == 8'h0); + assign c = (va[0] == 8'h0); + assign d = (va2d[0][0] == 8'h0); + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000009 always @(posedge clk) begin +%000009 cyc <= cyc + 1; +%000008 if (cyc == 1) rst <= 1'b0; +%000008 if (cyc == 2) start <= 1'b1; +%000008 if (cyc == 3) start <= 1'b0; +%000008 if (cyc == 8) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + +%000009 always_ff @(posedge clk) begin +%000007 if (rst) begin +%000002 state <= S_IDLE; + end +%000007 else begin +%000007 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %] +%000002 // [fsm_arc t.state::S_DONE->S_DONE] +%000003 // [fsm_arc t.state::S_IDLE->S_IDLE] +%000001 // [fsm_arc t.state::S_IDLE->S_RUN] +%000001 // [fsm_state t.state::S_DONE] +%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED *** +%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED *** +%000001 // [fsm_state t.state::S_RUN] +%000002 S_IDLE: +%000001 if (start) state <= S_RUN; +%000001 else state <= S_IDLE; +%000003 S_RUN: begin +%000003 a.a.a.vs <= a.a.a.vs + 1; +%000003 done <= (a.a.a.vs == 8'h1); +%000002 if (done) begin +%000001 state <= S_DONE; +%000002 end else begin +%000002 state <= S_RUN; + end + end +%000002 S_DONE: state <= S_DONE; +%000000 default: state <= S_ERR; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_sel.py b/test_regress/t/t_cover_fsm_sel.py new file mode 100755 index 000000000..9d066e89e --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage array sel test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import os + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage"]) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", + test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel.v b/test_regress/t/t_cover_fsm_sel.v new file mode 100644 index 000000000..a06275b03 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.v @@ -0,0 +1,88 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +package P; + typedef struct packed{ + logic [7:0] vs; + } C; + typedef struct packed{ + C a; int b; + } B; + typedef struct packed{ + B a; + } A; +endpackage + +module t ( + input clk +); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + + logic rst; + logic start; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + P::A a; + logic done; + + logic [7:0] va[int]; + logic [7:0] va2d[int][int]; + + logic b; + logic c; + logic d; + + assign b = (a.a.a.vs == 8'h0); + assign c = (va[0] == 8'h0); + assign d = (va2d[0][0] == 8'h0); + + initial begin + rst = 1'b1; + start = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) start <= 1'b1; + if (cyc == 3) start <= 1'b0; + if (cyc == 8) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S_IDLE; + end + else begin + case (state) + S_IDLE: + if (start) state <= S_RUN; + else state <= S_IDLE; + S_RUN: begin + a.a.a.vs <= a.a.a.vs + 1; + done <= (a.a.a.vs == 8'h1); + if (done) begin + state <= S_DONE; + end else begin + state <= S_RUN; + end + end + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_sel_assign.out b/test_regress/t/t_cover_fsm_sel_assign.out new file mode 100644 index 000000000..c033ed2ef --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.out @@ -0,0 +1,89 @@ +// // verilator_coverage annotation + // DESCRIPTION: Verilator: Verilog Test module + // + // This file ONLY is placed under the Creative Commons Public Domain + // SPDX-FileCopyrightText: 2026 Antmicro + // SPDX-License-Identifier: CC0-1.0 + + module t #( + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 + ) ( +%000009 input clk + ); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + +%000001 logic rst; +%000001 logic start; + integer cyc; +%000001 state_t state /*verilator fsm_reset_arc*/; +%000001 logic [1:0] done_arr; + +%000001 logic [W-1:0] a; +%000000 logic [BW-1:0] b; + begin +%000001 logic [D-1:0][W-1:0] s; + begin +%000009 always_ff @(posedge clk) +%000009 s[b] <= a; + end + end + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000009 always @(posedge clk) begin +%000009 cyc <= cyc + 1; +%000008 if (cyc == 1) rst <= 1'b0; +%000008 if (cyc == 2) start <= 1'b1; +%000008 if (cyc == 3) start <= 1'b0; +%000008 if (cyc == 4) a[0] = 1'b1; +%000008 if (cyc == 8) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + +%000009 always_ff @(posedge clk) begin +%000007 if (rst) begin +%000002 state <= S_IDLE; + end +%000007 else begin +%000007 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %] +%000002 // [fsm_arc t.state::S_DONE->S_DONE] +%000003 // [fsm_arc t.state::S_IDLE->S_IDLE] +%000001 // [fsm_arc t.state::S_IDLE->S_RUN] +%000001 // [fsm_state t.state::S_DONE] +%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED *** +%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED *** +%000001 // [fsm_state t.state::S_RUN] +%000002 S_IDLE: +%000001 if (start) state <= S_RUN; +%000001 else state <= S_IDLE; +%000003 S_RUN: begin; +%000003 done_arr[0] <= (a[0] == 1'b1); +%000002 if (done_arr[0]) begin +%000001 state <= S_DONE; +%000002 end else begin +%000002 state <= S_RUN; + end + end +%000002 S_DONE: state <= S_DONE; +%000000 default: state <= S_ERR; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_sel_assign.py b/test_regress/t/t_cover_fsm_sel_assign.py new file mode 100755 index 000000000..728658c89 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage assignment test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import os + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage"]) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", + test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel_assign.v b/test_regress/t/t_cover_fsm_sel_assign.v new file mode 100644 index 000000000..293fa4692 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.v @@ -0,0 +1,78 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t #( + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 +) ( + input clk +); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + + logic rst; + logic start; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + logic [1:0] done_arr; + + logic [W-1:0] a; + logic [BW-1:0] b; + begin + logic [D-1:0][W-1:0] s; + begin + always_ff @(posedge clk) + s[b] <= a; + end + end + + initial begin + rst = 1'b1; + start = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) start <= 1'b1; + if (cyc == 3) start <= 1'b0; + if (cyc == 4) a[0] = 1'b1; + if (cyc == 8) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S_IDLE; + end + else begin + case (state) + S_IDLE: + if (start) state <= S_RUN; + else state <= S_IDLE; + S_RUN: begin; + done_arr[0] <= (a[0] == 1'b1); + if (done_arr[0]) begin + state <= S_DONE; + end else begin + state <= S_RUN; + end + end + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out new file mode 100644 index 000000000..9883741c3 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out @@ -0,0 +1,11 @@ +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:20:14: Coverage ignored for type ASSOCARRAYDTYPE + : ... note: In instance 't' + 20 | input P::A a, + | ^ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:20:14: Coverage ignored for type WILDCARDARRAYDTYPE + : ... note: In instance 't' + 20 | input P::A a, + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py new file mode 100755 index 000000000..59e566c59 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage ignores associative array selection operators +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename, verilator_flags2=['--coverage']) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v new file mode 100644 index 000000000..547a14d85 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v @@ -0,0 +1,31 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +package P; + typedef struct { + logic [7:0] va[int]; + logic [7:0] vw[*]; + } C; + typedef struct { + C a; int b; + } B; + typedef struct { + B a; + } A; +endpackage +module t ( + input P::A a, + output logic b, + output logic c +); + assign b = (a.a.a.va[0] == 8'h0); + assign c = (a.a.a.vw[0] == 8'h0); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_fsm_duplicate.py b/test_regress/t/t_fsm_duplicate.py new file mode 100755 index 000000000..7f4205522 --- /dev/null +++ b/test_regress/t/t_fsm_duplicate.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM no duplicate variables test +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage -Wno-PINMISSING"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fsm_duplicate.v b/test_regress/t/t_fsm_duplicate.v new file mode 100644 index 000000000..20d7fd62e --- /dev/null +++ b/test_regress/t/t_fsm_duplicate.v @@ -0,0 +1,173 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module rr +#( +) ( + input logic clk, + input logic rst, + input logic [7:0] data, + input logic data_q +); + logic a; + logic [15:0] dcnt; + typedef enum logic [7:0] { + S0, + S1, + S2, + S3 + } state_t; + state_t state_d, state_q; + always_ff @(posedge clk or negedge rst) + if (!rst) state_q <= S0; + always_ff @(posedge clk) + unique case (state_q) + S1: if (a) dcnt[7:0] <= data; + S2: if (a) dcnt[15:8] <= data; + S3: if (data_q) dcnt <= dcnt - 1; + default: dcnt <= dcnt; + endcase +endmodule +module re +#( +) ( + input logic clk, + input logic rst, + output logic o, + input unused0, /* block optimizations */ + input unused1, + input unused2, + input unused3, + input unused4, + input unused5, + input unused6, + input unused7, + input unused8, + input unused9, + input unused10, + input unused11, + input unused12, + input unused13, + input unused14, + input unused15, + input unused16, + input unused17, + input unused18, + input unused19, + input unused20, + input unused21, + input unused22, + input unused23, + input unused24, + input unused25, + input unused26, + input unused27, + input unused28, + input unused29, + input unused30, + input unused31, + input unused32, + input unused33, + input unused34, + input unused35, + input unused36, + input unused37, + input unused38, + input unused39, + input unused40 +); + logic [15:0] dcnt; + typedef enum logic [7:0] { + S0, + S1 + } state_t; + state_t state_d, state_q; + always_ff @(posedge clk or negedge rst) + if (!rst) state_q <= S0; + always_ff @(posedge clk) + unique case (state_q) + S1: o <= dcnt[0]; + default: o <= '0; + endcase + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule +module rh +#( +) ( + input logic clk +); + logic [7:0] a; + logic b; + logic c; + logic d; + logic rst; + rr xrr ( + .clk, + .rst(rst), + .data (a), + .data_q (b & c) + ); + re xre ( + .clk, + .rst(rst), + .o (d) + ); +endmodule +module U +#( +) ( + input clk, + input rst +); + rh xrh ( + .clk (clk) + ); +endmodule +module C #( +) ( + input clk, + input rst +); + U U ( + .clk, + .rst + ); +endmodule +module A #( +) ( +); + logic clk; + logic rst; + C c0 ( + .clk, + .rst + ); + C c1 ( + .clk, + .rst + ); +endmodule +module B #( +) ( +); + logic clk; + logic rst; + C xC ( + .clk, + .rst + ); +endmodule +module t #( +) ( +); + B b ( + ); + A a ( + ); +endmodule From c6caa94fe07fbf2be2f366d7a6807b12fac799fe Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Thu, 11 Jun 2026 15:04:06 +0200 Subject: [PATCH 06/89] Fix no-scope internal error on virtual interface method calls (#7759) --- src/V3Task.cpp | 7 +++--- .../t/t_interface_virtual_func_module_call.py | 18 ++++++++++++++ .../t/t_interface_virtual_func_module_call.v | 24 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100755 test_regress/t/t_interface_virtual_func_module_call.py create mode 100644 test_regress/t/t_interface_virtual_func_module_call.v diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 5bae5d0aa..0db8d04d3 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -248,9 +248,9 @@ private: UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked task"); TaskFTaskVertex* const taskVtxp = getFTaskVertex(nodep->taskp()); new TaskEdge{&m_callGraph, m_curVxp, taskVtxp}; - if (isVirtualIfaceMethodCall(nodep) && isIfaceFTaskScope(getScope(nodep->taskp()))) { - taskVtxp->needsNonInlineCFunc(true); - } + // Virtual-interface method calls dispatch through a runtime handle and + // must not be inlined. + if (isVirtualIfaceMethodCall(nodep)) taskVtxp->needsNonInlineCFunc(true); // Do we have to disable inlining the function? const V3TaskConnects tconnects = V3Task::taskConnects(nodep, nodep->taskp()->stmtsp()); if (!taskVtxp->noInline()) { // Else short-circuit below @@ -1638,6 +1638,7 @@ class TaskVisitor final : public VNVisitor { // Create cloned statements AstNode* beginp; AstCNew* cnewp = nullptr; + // getScope() is safe here: TaskStateVisitor stamped all FTask scopes before this pass. const bool virtualIfaceCall = TaskStateVisitor::isVirtualIfaceMethodCall(nodep) && TaskStateVisitor::isIfaceFTaskScope(m_statep->getScope(nodep->taskp())); diff --git a/test_regress/t/t_interface_virtual_func_module_call.py b/test_regress/t/t_interface_virtual_func_module_call.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_module_call.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_func_module_call.v b/test_regress/t/t_interface_virtual_func_module_call.v new file mode 100644 index 000000000..7489bead4 --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_module_call.v @@ -0,0 +1,24 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +interface iface; + int cnt = 0; + function void bump(); + cnt++; + endfunction +endinterface + +module t; + iface theIf (); + virtual iface vif; + initial begin + vif = theIf; + vif.bump(); + if (theIf.cnt !== 1) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 4c92c035e7f51b776310f19992654d8c8a93ca2d Mon Sep 17 00:00:00 2001 From: Kornel Uriasz Date: Thu, 11 Jun 2026 15:43:18 +0200 Subject: [PATCH 07/89] Support reduction XOR/AND operations in constraints (#7753) --- docs/CONTRIBUTORS | 1 + src/V3Randomize.cpp | 35 ++++++++ test_regress/t/t_constraint_redops.py | 21 +++++ test_regress/t/t_constraint_redops.v | 123 ++++++++++++++++++++++++++ 4 files changed, 180 insertions(+) create mode 100755 test_regress/t/t_constraint_redops.py create mode 100644 test_regress/t/t_constraint_redops.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index b78b4f101..877011165 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -159,6 +159,7 @@ Kefa Chen Keith Colbert Kevin Kiningham Kevin Nygaard +Kornel Uriasz Kritik Bhimani Krzysztof Bieganski Krzysztof Boronski diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 3fcaf5535..929bbf115 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -1509,6 +1509,41 @@ class ConstraintExprVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->deleteTree(), nodep); iterate(sump); } + void visit(AstRedXor* nodep) override { + if (editFormat(nodep)) return; + + // Build popcount expansion: (extract x 1 1) ^ (extract x 2 2) ^ ... + FileLine* const fl = nodep->fileline(); + AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack(); + + AstNodeExpr* redxorp = new AstSel{fl, argp, 0, 1}; + redxorp->user1(true); + for (int i = 1; i < argp->width(); i++) { + AstSel* const selp = new AstSel{fl, argp->cloneTreePure(false), i, 1}; + selp->user1(true); + + redxorp = new AstXor{fl, redxorp, selp}; + redxorp->user1(true); + } + + nodep->replaceWith(redxorp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + iterate(redxorp); + } + void visit(AstRedAnd* nodep) override { + if (editFormat(nodep)) return; + // Convert to (~x == 0) + FileLine* const fl = nodep->fileline(); + AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack(); + const V3Number numZero{fl, argp->width(), 0}; + AstNodeExpr* const negp = new AstNot{fl, argp}; + negp->user1(true); + AstNodeExpr* const eqp = new AstEq{fl, negp, new AstConst{fl, numZero}}; + eqp->user1(true); + nodep->replaceWith(eqp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + iterate(eqp); + } void visit(AstRedOr* nodep) override { if (editFormat(nodep)) return; // Convert to (x != 0) diff --git a/test_regress/t/t_constraint_redops.py b/test_regress/t/t_constraint_redops.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_redops.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_redops.v b/test_regress/t/t_constraint_redops.v new file mode 100644 index 000000000..e285f8e24 --- /dev/null +++ b/test_regress/t/t_constraint_redops.v @@ -0,0 +1,123 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 by Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// Test case for reducing and in constraint +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define check_rand(cl, field, gotv, expv, count) \ +begin \ + automatic longint prev_result; \ + automatic int ok; \ + if (!bit'(cl.randomize())) $stop; \ + prev_result = longint'(field); \ + `checkd(gotv, expv) \ + repeat(count) begin \ + longint result; \ + if (!bit'(cl.randomize())) $stop; \ + result = longint'(field); \ + `checkd(gotv, expv) \ + if (result != prev_result) ok = 1; \ + prev_result = result; \ + end \ + if (ok != 1) $stop; \ +end +// verilog_format: on + +class test_redops_bitfields #(RANDVAL_BITWIDTH=8); + rand bit [RANDVAL_BITWIDTH-1:0] rand_val; + rand bit redand; + rand bit redxor; + rand bit redor; + + constraint c { + redand == &rand_val; + } + + constraint d { + redxor == ^rand_val; + } + + constraint e { + redor == |rand_val; + } + + function bit calc_redand(); + bit result = 1'b1; + + foreach (rand_val[idx]) begin + result &= rand_val[idx]; + end + + return result; + endfunction + + function bit calc_redxor(); + bit result; + + foreach (rand_val[idx]) begin + result ^= rand_val[idx]; + end + + return result; + endfunction + + function bit calc_redor(); + bit result = 1'b0; + + foreach (rand_val[idx]) begin + result |= rand_val[idx]; + end + + return result; + endfunction + + function void verify(); + //`check_rand(this, this.rand_val, this.redand, this.calc_redand(), 20); + `check_rand(this, this.rand_val, this.redxor, this.calc_redxor(), 20); + //`check_rand(this, this.rand_val, this.redor, this.calc_redor(), 20); + endfunction +endclass + +module t; + test_redops_bitfields #(.RANDVAL_BITWIDTH(1)) redops_1bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(8)) redops_8bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(16)) redops_16bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(32)) redops_32bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(47)) redops_47bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(63)) redops_63bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(64)) redops_64bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(128)) redops_128bit; + + initial begin + redops_1bit = new(); + redops_1bit.verify(); + + redops_8bit = new(); + redops_8bit.verify(); + + redops_16bit = new(); + redops_16bit.verify(); + + redops_32bit = new(); + redops_32bit.verify(); + + redops_47bit = new(); + redops_47bit.verify(); + + redops_63bit = new(); + redops_63bit.verify(); + + redops_64bit = new(); + redops_64bit.verify(); + + redops_128bit = new(); + redops_128bit.verify(); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From c7a262b05d05d866255545994ba114544c8b8019 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 11 Jun 2026 16:00:30 +0100 Subject: [PATCH 08/89] Optimize bit select removal earlier in Dfg (#7762) Add a simple Dfg pass that removes redundant bit selects early. This can significantly cut down on downstream work and remove some temporary variables introduced during synthesis. --- src/V3DfgContext.h | 21 +++++++ src/V3DfgOptimizer.cpp | 7 +++ src/V3DfgPasses.cpp | 72 ++++++++++++++++++++++++ src/V3DfgPasses.h | 2 + test_regress/t/t_dfg_break_cycles.py | 13 +++-- test_regress/t/t_dfg_break_cycles.v | 6 +- test_regress/t/t_dfg_break_cycles_off.py | 16 ++++++ 7 files changed, 130 insertions(+), 7 deletions(-) create mode 100755 test_regress/t/t_dfg_break_cycles_off.py diff --git a/src/V3DfgContext.h b/src/V3DfgContext.h index dc1b54666..21e7af31a 100644 --- a/src/V3DfgContext.h +++ b/src/V3DfgContext.h @@ -234,6 +234,26 @@ private: addStat("temporaries introduced", m_temporariesIntroduced); } }; +class V3DfgRemoveSelectsContext final : public V3DfgSubContext { + // Only V3DfgContext can create an instance + friend class V3DfgContext; + +public: + // STATE + VDouble0 m_removedFullWidth; // Number of full width selects removed + VDouble0 m_replacedWithSelFromFull; // Number of selects replaced with sel from full driver + VDouble0 m_replacedWithSelFromPart; // Number of selects replaced with sel from partial driver + VDouble0 m_replacedWithPart; // Number of selects replaced with part of driver +private: + V3DfgRemoveSelectsContext() + : V3DfgSubContext{"RemoveSelects"} {} + ~V3DfgRemoveSelectsContext() { + addStat("full width selects removed", m_removedFullWidth); + addStat("replaced with sel from full driver", m_replacedWithSelFromFull); + addStat("replaced with sel from partial driver", m_replacedWithSelFromPart); + addStat("replaced with partial driver", m_replacedWithPart); + } +}; class V3DfgRemoveUnobservableContext final : public V3DfgSubContext { // Only V3DfgContext can create an instance friend class V3DfgContext; @@ -399,6 +419,7 @@ public: V3DfgPeepholeContext m_peepholeContext; V3DfgPushDownSelsContext m_pushDownSelsContext; V3DfgRegularizeContext m_regularizeContext; + V3DfgRemoveSelectsContext m_removeSelectsContext; V3DfgRemoveUnobservableContext m_removeUnobservableContext; V3DfgSynthesisContext m_synthContext; diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index b2fa0f9e8..0ad3b8aa9 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -139,6 +139,13 @@ class DataflowOptimize final { dfg.mergeGraphs(std::move(madeAcyclicComponents)); endOfStage("breakCycles", dfg, cyclicComps); + // Remove redundant selects + V3DfgPasses::removeSelects(dfg, m_ctx.m_removeSelectsContext); + for (std::unique_ptr& compp : cyclicComps) { + V3DfgPasses::removeSelects(*compp, m_ctx.m_removeSelectsContext); + } + endOfStage("removeSelects", dfg, cyclicComps); + // Split the acyclic DFG into [weakly] connected components std::vector> acyclicComps = dfg.splitIntoComponents("acyclic"); UASSERT(dfg.size() == 0, "DfgGraph should have become empty"); diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 3e14397c6..afca00589 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -108,6 +108,78 @@ void V3DfgPasses::removeUnobservable(DfgGraph& dfg, V3DfgContext& dfgCtx) { } } +void V3DfgPasses::removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) { + + std::vector selps; + for (DfgVertex& vtx : dfg.opVertices()) { + DfgSel* const selp = vtx.cast(); + if (!selp) continue; + selps.push_back(selp); + } + + for (DfgSel* const selp : selps) { + FileLine* const flp = selp->fileline(); + const DfgDataType& dtype = selp->dtype(); + + // Remove full width selects + if (selp->fromp()->dtype() == dtype) { + ++ctx.m_removedFullWidth; + selp->replaceWith(selp->fromp()); + continue; + } + + // Push selects through synthesis temporaries only + DfgVarPacked* const varp = selp->fromp()->cast(); + if (!varp || !varp->tmpForp()) continue; + DfgVertex* const srcp = varp->srcp(); + if (!srcp) continue; + // Don't inline CReset + if (srcp->is()) continue; + + const uint32_t lsb = selp->lsb(); + const uint32_t msb = lsb + selp->width() - 1; + + // If driven whole, select from the driver + if (!srcp->is()) { + ++ctx.m_replacedWithSelFromFull; + DfgSel* const newSelp = new DfgSel{dfg, flp, dtype}; + newSelp->lsb(lsb); + newSelp->fromp(srcp); + selp->replaceWith(newSelp); + continue; + } + + // Otherwise attemt to select from the partial driver + DfgSplicePacked* const splicep = srcp->as(); + DfgVertex* driverp = nullptr; + uint32_t driverLsb = 0; + splicep->foreachDriver([&](DfgVertex& src, const uint32_t dLsb) { + const uint32_t dMsb = dLsb + src.width() - 1; + // If it does not cover the whole searched bit range, move on + if (lsb < dLsb || dMsb < msb) return false; + // Save the driver + driverp = &src; + driverLsb = dLsb; + return true; + }); + if (!driverp) continue; + + // If partial driver is the whole thing we are looking for, just replace with the driver + if (driverp->dtype() == dtype) { + ++ctx.m_replacedWithPart; + selp->replaceWith(driverp); + continue; + } + + // Otherwise create a new select from the partial driver + ++ctx.m_replacedWithSelFromPart; + DfgSel* const newSelp = new DfgSel{dfg, flp, dtype}; + newSelp->lsb(lsb - driverLsb); + newSelp->fromp(driverp); + selp->replaceWith(newSelp); + } +} + void V3DfgPasses::inlineVars(DfgGraph& dfg) { for (DfgVertexVar& vtx : dfg.varVertices()) { // Nothing to inline it into diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index a7d2b04fb..2984c6bd7 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -41,6 +41,8 @@ void removeUnobservable(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; // Synthesize DfgLogic vertices into primitive operations. // Removes all DfgLogic (even those that were not synthesized). void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; +// Remove redundant selects +void removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) VL_MT_DISABLED; // Attempt to make the given cyclic graph into an acyclic, or "less cyclic" // equivalent. If the returned pointer is null, then no improvement was // possible on the input graph. Otherwise the returned graph is an improvement diff --git a/test_regress/t/t_dfg_break_cycles.py b/test_regress/t/t_dfg_break_cycles.py index 2c35f08f5..ec6c6b339 100755 --- a/test_regress/t/t_dfg_break_cycles.py +++ b/test_regress/t/t_dfg_break_cycles.py @@ -17,6 +17,8 @@ test.sim_time = 2000000 if not os.path.exists(test.root + "/.git"): test.skip("Not in a git repository") +test.top_filename = "t/t_dfg_break_cycles.v" + # Read expected source lines hit expectedLines = set() @@ -65,7 +67,8 @@ with open(rdFile, 'r', encoding="utf8") as rdFh, \ test.compile(verilator_flags2=[ "--stats", "--build", - "-fno-dfg-break-cycles", + "-fno-dfg" if test.name == "t_dfg_break_cycles" else "-fno-dfg-break-cycles", + "-fno-gate", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_ref", "--prefix", "Vref", @@ -73,8 +76,9 @@ test.compile(verilator_flags2=[ ]) # yapf:disable # Check we got the expected number of circular logic warnings -test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt", - r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles) +if test.name == "t_dfg_break_cycles": + test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt", + r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles) # Compile optimized - also builds executable test.compile(verilator_flags2=[ @@ -82,6 +86,7 @@ test.compile(verilator_flags2=[ "--build", "--exe", "-fno-const-before-dfg", + "-fno-gate", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_opt", "--prefix", "Vopt", @@ -90,7 +95,7 @@ test.compile(verilator_flags2=[ "--debug", "--debugi", "0", "--dumpi-tree", "0", "-CFLAGS \"-I .. -I ../obj_ref\"", "../obj_ref/Vref__ALL.a", - "../../t/" + test.name + ".cpp" + "../../t/t_dfg_break_cycles.cpp" ]) # yapf:disable # Execute test to check equivalence diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index 5de4c628b..80c5aad04 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -219,14 +219,14 @@ module t ( `signal(ARRAY_1, 3); // UNOPTFLAT assign ARRAY_1 = array_1[0]; - wire [2:0] array_2a [2]; - wire [2:0] array_2b [2]; + wire [2:0] array_2a [2]; // UNOPTFLAT + wire [2:0] array_2b [2]; // UNOPTFLAT assign array_2a[0][0] = rand_a[0]; assign array_2a[0][1] = array_2b[1][0]; assign array_2a[0][2] = array_2b[1][1]; assign array_2a[1] = array_2a[0]; assign array_2b = array_2a; - `signal(ARRAY_2, 3); // UNOPTFLAT + `signal(ARRAY_2, 3); assign ARRAY_2 = array_2a[0]; wire [2:0] array_3 [2]; diff --git a/test_regress/t/t_dfg_break_cycles_off.py b/test_regress/t/t_dfg_break_cycles_off.py new file mode 100755 index 000000000..bf6af753c --- /dev/null +++ b/test_regress/t/t_dfg_break_cycles_off.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +import runpy + +test.scenarios('vlt_all') + +runpy.run_path("t/t_dfg_break_cycles.py", globals()) From 0ee5cbf5021dcaea2d82f8a3177d98dc2338eabd Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 11 Jun 2026 15:47:48 +0100 Subject: [PATCH 09/89] CI: replace deprecated app-id with client-id --- .github/workflows/coverage.yml | 2 +- .github/workflows/pages.yml | 2 +- .github/workflows/rtlmeter.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 439143c85..e5f6a61ea 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -197,7 +197,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 4d1523ac2..3718abc11 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -76,7 +76,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} permission-actions: write permission-pull-requests: write diff --git a/.github/workflows/rtlmeter.yml b/.github/workflows/rtlmeter.yml index d2880b476..b36646698 100644 --- a/.github/workflows/rtlmeter.yml +++ b/.github/workflows/rtlmeter.yml @@ -292,7 +292,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator-rtlmeter-results @@ -394,7 +394,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator From 0ee25038ac7b089805b9699596baf5afed2f8db3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 11 Jun 2026 20:59:18 +0100 Subject: [PATCH 10/89] Optimize V3Gate inlining heuristic (#7716) V3Gate used to inline too many expensive operations. One particularly bad example is inlining `{<<{wide}}` (bit-reverse of a wide signal), which is a single input node, but is quite expensive to compute, which we always used to inline. Change the heuristic to only inline single input nodes if they are not wide, or a cheap wide operation, otherwise treat them the same as multi-input ops and inline them only if they are used no more than once. --- src/V3Gate.cpp | 109 +++++++----------- test_regress/t/t_gate_chained.py | 2 +- .../t/t_gate_inline_wide_exclude_multiple.py | 4 +- .../t_gate_inline_wide_noexclude_arraysel.py | 4 +- .../t/t_gate_inline_wide_noexclude_const.py | 4 +- ..._gate_inline_wide_noexclude_other_scope.py | 18 --- ...t_gate_inline_wide_noexclude_other_scope.v | 34 ------ .../t/t_gate_inline_wide_noexclude_sel.py | 4 +- ...t_gate_inline_wide_noexclude_small_wide.py | 18 --- .../t_gate_inline_wide_noexclude_small_wide.v | 21 ---- .../t/t_gate_inline_wide_noexclude_varref.py | 4 +- 11 files changed, 52 insertions(+), 170 deletions(-) delete mode 100755 test_regress/t/t_gate_inline_wide_noexclude_other_scope.py delete mode 100644 test_regress/t/t_gate_inline_wide_noexclude_other_scope.v delete mode 100755 test_regress/t/t_gate_inline_wide_noexclude_small_wide.py delete mode 100644 test_regress/t/t_gate_inline_wide_noexclude_small_wide.v diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp index 4875ac75c..b96c892f2 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -543,89 +543,62 @@ class GateInline final { // Logic block with pending substitutions are stored in this map, together with their ordinal std::unordered_map m_hasPending; size_t m_statInlined = 0; // Statistic tracking - signals inlined - size_t m_statRefs = 0; // Statistic tracking - size_t m_statExcluded = 0; // Statistic tracking + size_t m_statNotInlined = 0; // Statistic tracking - signals not inlined due to cost + size_t m_statRefs = 0; // Statistic tracking - number of input variable references replaced // METHODS - static bool isCheapWide(const AstNodeExpr* exprp) { + static bool isCheap(const AstNodeExpr* exprp) { + // Constant is cheap + if (VN_IS(exprp, Const)) return true; + // Variable reference is cheap + if (VN_IS(exprp, NodeVarRef)) return true; + // AstSel is cheap if the fromp is cheap, and not a wide needing bit swizzling if (const AstSel* const selp = VN_CAST(exprp, Sel)) { + if (!isCheap(selp->fromp())) return false; + if (!selp->isWide()) return true; + if (!VN_IS(selp->lsbp(), Const)) return false; if (selp->lsbConst() % VL_EDATASIZE != 0) return false; - exprp = selp->fromp(); + return true; } - if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) exprp = aselp->fromp(); - return VN_IS(exprp, Const) || VN_IS(exprp, NodeVarRef); - } - static bool excludedWide(GateVarVertex* const vVtxp, const AstNodeExpr* const rhsp) { - // Handle wides with logic drivers that are too wide for V3Expand. - if (!vVtxp->varScp()->isWide() // - || vVtxp->varScp()->widthWords() <= v3Global.opt.expandLimit() // - || vVtxp->inEmpty() // - || isCheapWide(rhsp)) - return false; - - const GateLogicVertex* const lVtxp - = vVtxp->inEdges().frontp()->fromp()->as(); - - // Exclude from inlining variables READ multiple times. - // To decouple actives thus simplifying scheduling, exclude only those - // VarRefs that are referenced under the same active as they were assigned. - if (const AstActive* const primaryActivep = lVtxp->activep()) { - size_t reads = 0; - for (const V3GraphEdge& edge : vVtxp->outEdges()) { - const GateLogicVertex* const lvp = edge.top()->as(); - if (lvp->activep() != primaryActivep) continue; - - reads += edge.weight(); - if (reads > 1) return true; - } + // AstArraySel is cheap if the fromp is cheap + if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) { + return isCheap(aselp->fromp()); } + // Otherwise it is not cheap return false; } bool shouldInline(GateVarVertex* vVtxp, GateLogicVertex* lVtxp, size_t nReads, AstNodeExpr* substp, bool allowMultiIn) { - AstVarScope* const vscp = vVtxp->varScp(); - // Always inline constants if (VN_IS(substp, Const)) return true; - // Don't inline non-constant static initializers + // Don't inline non-constant static initializers - these are scheduled differently if (lVtxp->staticInit()) return false; // Inline simple variable references if (VN_IS(substp, VarRef)) return true; // Only inline arrays if a simple variable or constant - if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) return false; - // Inline constant array selects - if (VN_IS(substp, ArraySel) && nReads <= 1) return true; - - // Don't inline expensive wide operations - if (excludedWide(vVtxp, substp)) { - ++m_statExcluded; - UINFO(9, "Gate inline exclude '" << vVtxp->name() << "'"); - vVtxp->clearReducible("Excluded wide"); // Check once. - return false; - } - - if (nReads == 0) { - // Reads no variables, likely unfolded constant expression - return true; - } else if (nReads == 1) { - // Reads one variable - return true; - } else { - // Reads more two or more variables - if (!allowMultiIn) return false; - // Do it if not used, or used only once, ignoring slow code - int n = 0; - for (V3GraphEdge& edge : vVtxp->outEdges()) { - const GateLogicVertex* const dstVtxp = edge.top()->as(); - // Ignore slow code, or if the destination is not used - if (dstVtxp->slow()) continue; - if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue; - n += edge.weight(); - if (n > 1) return false; + if (VN_IS(vVtxp->varScp()->dtypep()->skipRefp(), UnpackArrayDType)) return false; + // Inline if reads no variables - unfolded constant expression, nullary builtin e.g.: $time + if (nReads == 0) return true; + // If it reads one variable, inline if not wide, or if cheap + if (nReads == 1 && (!substp->isWide() || isCheap(substp))) return true; + // Don't inline on first round if reads more than one variable + if (nReads > 1 && !allowMultiIn) return false; + // Reads multiple variables, or is expensive to compute. + // Inline if used only once, ignoring slow code, or dead code that can be deleted. + int n = 0; + for (V3GraphEdge& edge : vVtxp->outEdges()) { + const GateLogicVertex* const dstVtxp = edge.top()->as(); + // Ignore slow code, or if the destination is not used + if (dstVtxp->slow()) continue; + if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue; + n += edge.weight(); + if (n > 1) { + ++m_statNotInlined; + return false; } - return true; } + return true; } void recordSubstitution(AstVarScope* vscp, AstNodeExpr* substp, AstNode* logicp) { @@ -724,7 +697,7 @@ class GateInline final { if (!okVisitor.varAssigned(vVtxp->varScp())) continue; // Expression we are considering to substitute with - AstNodeExpr* const substp = okVisitor.substitutionp(); + AstNodeExpr* const substp = V3Const::constifyEdit(okVisitor.substitutionp()); // Number of variables read by the substitution const size_t nReads = okVisitor.readVscps().size(); @@ -832,9 +805,9 @@ class GateInline final { } ~GateInline() { - V3Stats::addStat("Optimizations, Gate sigs deleted", m_statInlined); - V3Stats::addStat("Optimizations, Gate inputs replaced", m_statRefs); - V3Stats::addStat("Optimizations, Gate excluded wide expressions", m_statExcluded); + V3Stats::addStat("Optimizations, Gate signals inlined", m_statInlined); + V3Stats::addStat("Optimizations, Gate signals not inlined due to cost", m_statNotInlined); + V3Stats::addStat("Optimizations, Gate reads replaced", m_statRefs); } public: diff --git a/test_regress/t/t_gate_chained.py b/test_regress/t/t_gate_chained.py index 7fa18e3a3..ddf8bef62 100755 --- a/test_regress/t/t_gate_chained.py +++ b/test_regress/t/t_gate_chained.py @@ -48,6 +48,6 @@ test.compile( test.execute() # Must be <<9000 above to prove this worked -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 8550) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 8550) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_exclude_multiple.py b/test_regress/t/t_gate_inline_wide_exclude_multiple.py index 76a8991cf..2bf5b1241 100755 --- a/test_regress/t/t_gate_inline_wide_exclude_multiple.py +++ b/test_regress/t/t_gate_inline_wide_exclude_multiple.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 2) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 4) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py index 8c99e4b33..15370ae7c 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-dfg']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_const.py b/test_regress/t/t_gate_inline_wide_noexclude_const.py index 5102631f7..cb6511e21 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_const.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_const.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 2) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py b/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py deleted file mode 100755 index c20d324db..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -# DESCRIPTION: Verilator: Verilog Test driver/expect definition -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of either the GNU Lesser General Public License Version 3 -# or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2024 Wilson Snyder -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -import vltest_bootstrap - -test.scenarios('vlt') - -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) - -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) - -test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v b/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v deleted file mode 100644 index c70022d24..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v +++ /dev/null @@ -1,34 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2024 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -localparam N = 256; // Wider than expand limit. - -module t ( - input wire [N-1:0] i, - output wire [N-1:0] o -); - - // Do not exclude from inlining wides referenced in different scope. - wire [N-1:0] wide = N ~^ i; - - sub sub ( - i, - wide, - o - ); -endmodule - -module sub ( - input wire [N-1:0] i, - input wire [N-1:0] wide, - output logic [N-1:0] o -); - initial begin - for (integer n = 0; n < N; ++n) begin - o[n] = i[N-1-n] | wide[N-1-n]; - end - end -endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_sel.py b/test_regress/t/t_gate_inline_wide_noexclude_sel.py index aa65429ab..41d13c55d 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_sel.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_sel.py @@ -13,8 +13,8 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-var-split']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 1) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 1) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py b/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py deleted file mode 100755 index c20d324db..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -# DESCRIPTION: Verilator: Verilog Test driver/expect definition -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of either the GNU Lesser General Public License Version 3 -# or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2024 Wilson Snyder -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -import vltest_bootstrap - -test.scenarios('vlt') - -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) - -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) - -test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v b/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v deleted file mode 100644 index 1b1231c4e..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v +++ /dev/null @@ -1,21 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2024 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -localparam N = 65; // Wide but narrower than expand limit - -module t ( - input wire [N-1:0] i, - output wire [N-1:0] o -); - - // Do not exclude from inlining wides small enough to be handled by - // V3Expand. - wire [65:0] wide_small = N << i * i / N; - - for (genvar n = 0; n < N; ++n) begin - assign o[n] = i[n] ^ wide_small[n]; - end -endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_varref.py b/test_regress/t/t_gate_inline_wide_noexclude_varref.py index 1d91e6493..282e88b84 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_varref.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_varref.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 0) test.passes() From 4555c8b23ce11072c6430eca084ccb57b6edf634 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 12 Jun 2026 06:38:21 +0100 Subject: [PATCH 11/89] Internals: Cleanup case condition lifting (#7768) V3Begin used to lift impure case expressions. With V3LiftExpr, this is now redundant. --- src/V3Begin.cpp | 32 ++---------------- src/V3Case.cpp | 38 +++------------------- test_regress/t/t_case_call_count.py | 2 +- test_regress/t/t_case_inside_call_count.py | 2 +- 4 files changed, 8 insertions(+), 66 deletions(-) diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp index 36e151053..2ac71c770 100644 --- a/src/V3Begin.cpp +++ b/src/V3Begin.cpp @@ -61,7 +61,6 @@ class BeginVisitor final : public VNVisitor { // NODE STATE // AstCase::user1 -> bool, if already purified - V3UniqueNames m_caseTempNames; // For generating unique temporary variable names used by cases // STATE - across all visitors BeginState* const m_statep; // Current global state @@ -75,7 +74,6 @@ class BeginVisitor final : public VNVisitor { string m_unnamedScope; // Name of begin blocks, including unnamed blocks int m_ifDepth = 0; // Current if depth bool m_keepBegins = false; // True if begins should not be inlined - VDouble0 m_statPurifiedCaseExpr; // Count of purified case expressions // METHODS @@ -132,7 +130,6 @@ class BeginVisitor final : public VNVisitor { } void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); - VL_RESTORER(m_caseTempNames); m_modp = nodep; // Rename it (e.g. class under a generate) if (m_unnamedScope != "") { @@ -171,7 +168,6 @@ class BeginVisitor final : public VNVisitor { VL_RESTORER(m_liftedp); VL_RESTORER(m_namedScope); VL_RESTORER(m_unnamedScope); - VL_RESTORER(m_caseTempNames); m_displayScope = dot(m_displayScope, nodep->name()); m_namedScope = ""; m_unnamedScope = ""; @@ -415,29 +411,7 @@ class BeginVisitor final : public VNVisitor { // any BEGINs, but V3Coverage adds them all under the module itself. iterateChildren(nodep); } - void visit(AstCase* nodep) override { - // Introduce temporary variable for AstCase if needed - it is done here and not in V3Case - // because this phase is before V3Scope and V3Case is not. Doing it before V3Scope ensures - // that V3Scope will take care of a scope creation - if (!nodep->exprp()->isPure() && !nodep->user1SetOnce()) { - ++m_statPurifiedCaseExpr; - FileLine* const fl = nodep->exprp()->fileline(); - AstVar* const varp = new AstVar{fl, VVarType::XTEMP, m_caseTempNames.get(nodep), - nodep->exprp()->dtypep()}; - nodep->exprp(new AstExprStmt{fl, - new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, - nodep->exprp()->unlinkFrBack()}, - new AstVarRef{fl, varp, VAccess::READ}}); - if (m_ftaskp) { - varp->funcLocal(true); - varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - m_ftaskp->stmtsp()->addHereThisAsNext(varp); - } else { - m_modp->stmtsp()->addHereThisAsNext(varp); - } - } - iterateChildren(nodep); - } + void visit(AstCase* nodep) override { iterateChildren(nodep); } // VISITORS - LINT CHECK void visit(AstIf* nodep) override { // not AstNodeIf; other types not covered VL_RESTORER(m_keepBegins); @@ -464,10 +438,8 @@ class BeginVisitor final : public VNVisitor { public: // CONSTRUCTORS BeginVisitor(AstNetlist* nodep, BeginState* statep) - : m_caseTempNames{"__VCase"} - , m_statep{statep} { + : m_statep{statep} { iterate(nodep); - V3Stats::addStatSum("Impure case expressions", m_statPurifiedCaseExpr); } ~BeginVisitor() override = default; }; diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 4eca73e01..6c969c791 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -147,7 +147,6 @@ class CaseVisitor final : public VNVisitor { bool m_caseNoOverlapsAllCovered = false; // Proven to be synopsys parallel_case compliant // For each possible value, the case branch we need std::array m_valueItem; - bool m_needToClearCache = false; // Whether cache needs to be cleared // METHODS //! Determine whether we should check case items are complete @@ -398,14 +397,6 @@ class CaseVisitor final : public VNVisitor { // CASEx(cexpr,.... // -> tree of IF(msb, IF(msb-1, 11, 10) // IF(msb-1, 01, 00)) - AstNodeExpr* cexprp; - AstExprStmt* cexprStmtp = nullptr; - if (nodep->exprp()->isPure()) { - cexprp = nodep->exprp()->unlinkFrBack(); - } else { - cexprStmtp = VN_AS(nodep->exprp()->unlinkFrBack(), ExprStmt); - cexprp = cexprStmtp->resultp()->cloneTreePure(false); - } if (debug() >= 9) { // LCOV_EXCL_START for (uint32_t i = 0; i < (1UL << m_caseWidth); ++i) { @@ -419,14 +410,7 @@ class CaseVisitor final : public VNVisitor { replaceCaseParallel(nodep, m_caseNoOverlapsAllCovered); AstNode::user3ClearTree(); - AstNode* ifrootp = replaceCaseFastRecurse(cexprp, m_caseWidth - 1, 0UL); - if (cexprStmtp) { - cexprStmtp->resultp()->unlinkFrBack()->deleteTree(); - AstIf* const ifp = VN_AS(ifrootp, If); - cexprStmtp->resultp(ifp->condp()->unlinkFrBack()); - ifp->condp(cexprStmtp); - m_needToClearCache = true; - } + AstNode* ifrootp = replaceCaseFastRecurse(nodep->exprp(), m_caseWidth - 1, 0UL); // Case expressions can't be linked twice, so clone them if (ifrootp && !ifrootp->user3()) ifrootp = ifrootp->cloneTree(true); @@ -437,7 +421,6 @@ class CaseVisitor final : public VNVisitor { nodep->unlinkFrBack(); } VL_DO_DANGLING(nodep->deleteTree(), nodep); - VL_DO_DANGLING(cexprp->deleteTree(), cexprp); UINFOTREE(9, ifrootp, "", "_simp"); } @@ -446,14 +429,7 @@ class CaseVisitor final : public VNVisitor { // -> IF((cexpr==icond1),istmts1, // IF((EQ (AND MASK cexpr) (AND MASK icond1) // ,istmts2, istmts3 - AstNodeExpr* cexprp; - AstExprStmt* cexprStmtp = nullptr; - if (nodep->exprp()->isPure()) { - cexprp = nodep->exprp()->unlinkFrBack(); - } else { - cexprStmtp = VN_AS(nodep->exprp(), ExprStmt)->unlinkFrBack(); - cexprp = cexprStmtp->resultp()->cloneTreePure(false); - } + AstNodeExpr* const cexprp = nodep->exprp(); // We'll do this in two stages. First stage, convert the conditions to // the appropriate IF AND terms. UINFOTREE(9, nodep, "", "_comp_IN::"); @@ -518,7 +494,6 @@ class CaseVisitor final : public VNVisitor { itemp->addCondsp(ifexprp); } } - VL_DO_DANGLING(cexprp->deleteTree(), cexprp); if (!hadDefault) { // If there was no default, add a empty one, this greatly simplifies below code // and constant propagation will just eliminate it for us later. @@ -582,12 +557,6 @@ class CaseVisitor final : public VNVisitor { if (grouprootp) { UINFOTREE(9, grouprootp, "", "_new"); nodep->replaceWith(grouprootp); - if (cexprStmtp) { - pushDeletep(cexprStmtp->resultp()->unlinkFrBack()); - cexprStmtp->resultp(grouprootp->condp()->unlinkFrBack()); - grouprootp->condp(cexprStmtp); - m_needToClearCache = true; - } } else { nodep->unlinkFrBack(); } @@ -622,6 +591,8 @@ class CaseVisitor final : public VNVisitor { { CaseLintVisitor{nodep}; } iterateChildren(nodep); UINFOTREE(9, nodep, "", "case_old"); + UASSERT_OBJ(nodep->exprp()->isPure(), nodep, + "Impure case expression should have been removed by V3LiftExpr"); if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) { // It's a simple priority encoder or complete statement // we can make a tree of statements to avoid extra comparisons @@ -648,7 +619,6 @@ public: explicit CaseVisitor(AstNetlist* nodep) { for (auto& itr : m_valueItem) itr = nullptr; iterate(nodep); - if (m_needToClearCache) VIsCached::clearCacheTree(); } ~CaseVisitor() override { V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast); diff --git a/test_regress/t/t_case_call_count.py b/test_regress/t/t_case_call_count.py index 4116217e8..df91d0e3c 100755 --- a/test_regress/t/t_case_call_count.py +++ b/test_regress/t/t_case_call_count.py @@ -15,6 +15,6 @@ test.compile(verilator_flags2=['--stats']) test.execute() -test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2) +test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3) test.passes() diff --git a/test_regress/t/t_case_inside_call_count.py b/test_regress/t/t_case_inside_call_count.py index 4116217e8..df91d0e3c 100755 --- a/test_regress/t/t_case_inside_call_count.py +++ b/test_regress/t/t_case_inside_call_count.py @@ -15,6 +15,6 @@ test.compile(verilator_flags2=['--stats']) test.execute() -test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2) +test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3) test.passes() From 60f729639b882fc1d32df7bf194cad6957598fce Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 12 Jun 2026 07:48:36 +0100 Subject: [PATCH 12/89] Fix 'case (_) inside' with x wildcards (#7766) Found by inspection, case inside used to threat 'x' as a value, not as a wildcard. Per the standard it should behave as '==?' which treats both 'x' and 'z' as wildcards. --- src/V3Case.cpp | 4 +-- test_regress/t/t_case_inside_with_x.py | 18 +++++++++++++ test_regress/t/t_case_inside_with_x.v | 35 ++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100755 test_regress/t/t_case_inside_with_x.py create mode 100644 test_regress/t/t_case_inside_with_x.v diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 6c969c791..1431c8ddf 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -576,8 +576,8 @@ class CaseVisitor final : public VNVisitor { bool neverItem(const AstCase* casep, const AstConst* itemp) { // Xs in case or casez are impossible due to two state simulations - if (casep->casex()) { - } else if (casep->casez() || casep->caseInside()) { + if (casep->casex() || casep->caseInside()) { + } else if (casep->casez()) { if (itemp->num().isAnyX()) return true; } else { if (itemp->num().isFourState()) return true; diff --git a/test_regress/t/t_case_inside_with_x.py b/test_regress/t/t_case_inside_with_x.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_case_inside_with_x.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_case_inside_with_x.v b/test_regress/t/t_case_inside_with_x.v new file mode 100644 index 000000000..51a456c7a --- /dev/null +++ b/test_regress/t/t_case_inside_with_x.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module top; + bit clk = 1'b0; + always #1 clk = ~clk; + + logic [2:0] cyc = 3'd0; + int count = 0; + always @(posedge clk) begin + // verilator lint_off CASEWITHX + case (cyc) inside + 3'b000: begin $display("case inside 000"); ++count; end + 3'b001: begin $display("case inside 001"); ++count; end + // Should match z + 3'b01?: begin $display("case inside 01?"); ++count; end + // Should match x + 3'b1xx: begin $display("case inside 1xx"); ++count; end + endcase + // verilator lint_on CASEWITHX + cyc <= cyc + 3'd1; + if (&cyc) begin + `checkh(count, 8); + $finish; + end + end +endmodule From e6ee6dd106f6db8c0b846222cb3ec19cbb8ac1c6 Mon Sep 17 00:00:00 2001 From: Ryszard Rozak Date: Fri, 12 Jun 2026 12:55:06 +0200 Subject: [PATCH 13/89] Fix bounds checks in expressions with read/write references (#7694) --- src/V3Unknown.cpp | 93 +++++++------------ test_regress/t/t_select_bound4.py | 20 ++++ test_regress/t/t_select_bound4.v | 29 ++++++ test_regress/t/t_select_bound_side_effect.py | 20 ++++ test_regress/t/t_select_bound_side_effect.v | 74 +++++++++++++++ test_regress/t/t_select_bound_timing_intra.py | 20 ++++ test_regress/t/t_select_bound_timing_intra.v | 34 +++++++ 7 files changed, 228 insertions(+), 62 deletions(-) create mode 100755 test_regress/t/t_select_bound4.py create mode 100644 test_regress/t/t_select_bound4.v create mode 100755 test_regress/t/t_select_bound_side_effect.py create mode 100644 test_regress/t/t_select_bound_side_effect.v create mode 100755 test_regress/t/t_select_bound_timing_intra.py create mode 100644 test_regress/t/t_select_bound_timing_intra.v diff --git a/src/V3Unknown.cpp b/src/V3Unknown.cpp index 727e97840..c8851280d 100644 --- a/src/V3Unknown.cpp +++ b/src/V3Unknown.cpp @@ -51,15 +51,14 @@ class UnknownVisitor final : public VNVisitor { static const std::string s_xrandPrefix; // STATE - across all visitors - VDouble0 m_statUnkVars; // Statistic tracking + VDouble0 m_statUnkVars; // Statistic of xrand variable created + VDouble0 m_statElses; // Statistic of else branches created for array selects V3UniqueNames m_lvboundNames; // For generating unique temporary variable names std::unique_ptr m_xrandNames; // For generating unique temporary variable names // STATE - for current visit position (use VL_RESTORER) AstNodeModule* m_modp = nullptr; // Current module AstNodeFTask* m_ftaskp = nullptr; // Current function/task - AstAssignDly* m_assigndlyp = nullptr; // Current assignment - AstNode* m_timingControlp = nullptr; // Current assignment's intra timing control bool m_constXCvt = false; // Convert X's bool m_allowXUnique = true; // Allow unique assignments @@ -69,38 +68,21 @@ class UnknownVisitor final : public VNVisitor { if (m_ftaskp) { varp->funcLocal(true); varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - m_ftaskp->stmtsp()->addHereThisAsNext(varp); + if (m_ftaskp->stmtsp()) + m_ftaskp->stmtsp()->addHereThisAsNext(varp); + else + m_ftaskp->addStmtsp(varp); } else { - m_modp->stmtsp()->addHereThisAsNext(varp); + if (m_modp->stmtsp()) + m_modp->stmtsp()->addHereThisAsNext(varp); + else + m_modp->addStmtsp(varp); } } void replaceBoundLvalue(AstNodeExpr* nodep, AstNodeExpr* condp) { // Spec says a out-of-range LHS SEL results in a NOP. - // This is a PITA. We could: - // 1. IF(...) around an ASSIGN, - // but that would break a "foo[TOO_BIG]=$fopen(...)". - // 2. Hack to extend the size of the output structure - // by one bit, and write to that temporary, but never read it. - // That makes there be two widths() and is likely a bug farm. - // 3. Make a special SEL to choose between the real lvalue - // and a temporary NOP register. - // 4. Assign to a temp, then IF that assignment. - // This is suspected to be nicest to later optimizations. - // 4 seems best but breaks later optimizations. 3 was tried, - // but makes a mess in the emitter as lvalue switching is needed. So 4. - // SEL(...) -> temp - // if (COND(LTE(bit<=maxlsb))) ASSIGN(SEL(...)),temp) - const bool needDly = (m_assigndlyp != nullptr); - if (m_assigndlyp) { - // Delayed assignments become normal assignments, - // then the temp created becomes the delayed assignment - AstNode* const newp = new AstAssign{m_assigndlyp->fileline(), - m_assigndlyp->lhsp()->unlinkFrBackWithNext(), - m_assigndlyp->rhsp()->unlinkFrBackWithNext()}; - m_assigndlyp->replaceWith(newp); - VL_DO_CLEAR(pushDeletep(m_assigndlyp), m_assigndlyp = nullptr); - } + // We wrap the expression into IF AstNodeExpr* prep = nodep; // Scan back to put the condlvalue above all selects (IE top of the lvalue) @@ -121,32 +103,35 @@ class UnknownVisitor final : public VNVisitor { // Already exists; rather than IF(a,... IF(b... optimize to IF(a&&b, // Saves us teaching V3Const how to optimize, and it won't be needed again. if (const AstIf* const ifp = VN_AS(prep->user2p(), If)) { - UASSERT_OBJ(!needDly, prep, "Should have already converted to non-delay"); VNRelinker replaceHandle; AstNodeExpr* const earliercondp = ifp->condp()->unlinkFrBack(&replaceHandle); AstNodeExpr* const newp = new AstLogAnd{condp->fileline(), condp, earliercondp}; UINFO(4, "Edit BOUNDLVALUE " << newp); replaceHandle.relink(newp); } else { - AstVar* const varp - = new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), prep->dtypep()}; - addVar(varp); AstNode* stmtp = prep->backp(); // Grab above point before we replace 'prep' while (!VN_IS(stmtp, NodeStmt)) stmtp = stmtp->backp(); - - prep->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE}); - if (m_timingControlp) m_timingControlp->unlinkFrBack(); - AstIf* const newp = new AstIf{ - fl, condp, - (needDly - ? static_cast(new AstAssignDly{ - fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}) - : static_cast(new AstAssign{ - fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}))}; + VNRelinker replaceHandle; + AstNode* const origStmtp = stmtp->unlinkFrBack(&replaceHandle); + AstNode* elseStmtp = nullptr; + const bool hasSideEffects = origStmtp->exists( + [](AstNode* const np) { return !np->isPure() || np->isTimingControl(); }); + if (hasSideEffects) { + // Copy original statement and replace `prep` with reference to tmp var to make + // sure that side effect will take place + m_statElses++; + elseStmtp = origStmtp->cloneTree(false); + AstVar* const varp = new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), + prep->dtypep()}; + addVar(varp); + AstNode* const prepCopyp = prep->clonep(); + prepCopyp->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE}); + pushDeletep(prepCopyp); + } + AstIf* const newp = new AstIf{fl, condp, origStmtp, elseStmtp}; + replaceHandle.relink(newp); newp->branchPred(VBranchPred::BP_LIKELY); newp->isBoundsCheck(true); - UINFOTREE(9, newp, "", "_new"); - stmtp->addNextHere(newp); prep->user2p(newp); // Save so we may LogAnd it next time } } @@ -208,23 +193,6 @@ class UnknownVisitor final : public VNVisitor { m_ftaskp = nodep; iterateChildren(nodep); } - void visit(AstAssignDly* nodep) override { - VL_RESTORER(m_assigndlyp); - VL_RESTORER(m_timingControlp); - m_assigndlyp = nodep; - m_timingControlp = nodep->timingControlp(); - VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. - } - void visit(AstAssignW* nodep) override { - VL_RESTORER(m_timingControlp); - m_timingControlp = nodep->timingControlp(); - VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. - } - void visit(AstNodeAssign* nodep) override { - VL_RESTORER(m_timingControlp); - m_timingControlp = nodep->timingControlp(); - iterateChildren(nodep); - } void visit(AstCaseItem* nodep) override { VL_RESTORER(m_constXCvt); m_constXCvt = false; // Avoid losing the X's in casex @@ -584,6 +552,7 @@ public: } ~UnknownVisitor() override { // V3Stats::addStat("Unknowns, variables created", m_statUnkVars); + V3Stats::addStat("Unknowns, else branches created", m_statElses); } }; diff --git a/test_regress/t/t_select_bound4.py b/test_regress/t/t_select_bound4.py new file mode 100755 index 000000000..00e3aae74 --- /dev/null +++ b/test_regress/t/t_select_bound4.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_select_bound4.v b/test_regress/t/t_select_bound4.v new file mode 100644 index 000000000..d09a59a52 --- /dev/null +++ b/test_regress/t/t_select_bound4.v @@ -0,0 +1,29 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module t; + int q[2][$]; + + task automatic pop_q(input int qid, input int expected); + int actual; + actual = q[qid].pop_front(); + if (qid < 2 && actual !== expected) $stop; + endtask + + initial begin + for (int i = 0; i < 4; i++) begin + q[i].push_back(i); + end + + for (int i = 0; i < 4; i++) begin + pop_q(i, i); + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_select_bound_side_effect.py b/test_regress/t/t_select_bound_side_effect.py new file mode 100755 index 000000000..d696b53d9 --- /dev/null +++ b/test_regress/t/t_select_bound_side_effect.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_select_bound_side_effect.v b/test_regress/t/t_select_bound_side_effect.v new file mode 100644 index 000000000..5fd21bdc8 --- /dev/null +++ b/test_regress/t/t_select_bound_side_effect.v @@ -0,0 +1,74 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + int arr[5]; + int x = 0; + int y = 0; + int z = 0; + + task automatic incr(input int i, input int expected); + arr[i] = x++; + if (i < 5) `checkh(arr[i], expected); + endtask + + function int get_y; + y++; + return y; + endfunction + + task automatic assign_side_effect(input int i, input int expected); + arr[i] = get_y(); + if (i < 5) `checkh(arr[i], expected); + endtask + + task automatic add_z(inout int a); + a += z; + z++; + endtask + + task automatic assign_side_effect_inout(input int i, input int expected); + if (i < 5) arr[i] = 1; + add_z(arr[i]); + if (i < 5) `checkh(arr[i], expected); + endtask + + initial begin + incr(0, 0); + incr(7, 0); + incr(4, 2); + + assign_side_effect(3, 1); + assign_side_effect(8, 0); + assign_side_effect(9, 0); + assign_side_effect(3, 4); + + assign_side_effect_inout(3, 1); + assign_side_effect_inout(4, 2); + assign_side_effect_inout(5, 0); + assign_side_effect_inout(1, 4); + + y = 0; + for (int i = 0; i < 10; i++) begin + arr[get_y()] = i; + if (y < 5) `checkh(arr[y], i); + `checkh(y, 2 * i + 1); + arr[get_y() % (i + 1)] = i; + if (y % (i + 1) < 5) `checkh(arr[y % (i + 1)], i); + `checkh(y, 2 * (i + 1)); + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_select_bound_timing_intra.py b/test_regress/t/t_select_bound_timing_intra.py new file mode 100755 index 000000000..4fae43d09 --- /dev/null +++ b/test_regress/t/t_select_bound_timing_intra.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary", "--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_select_bound_timing_intra.v b/test_regress/t/t_select_bound_timing_intra.v new file mode 100644 index 000000000..31d30c48c --- /dev/null +++ b/test_regress/t/t_select_bound_timing_intra.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + int arr[5]; + + task automatic intra(input int i); + time t = $time; + arr[i] = #1 i; + #1; + if (i < 5) `checkh(arr[i], i); + `checkh($time, t + 2); + endtask + + initial begin + intra(0); + intra(7); + intra(4); + intra(1); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 384a63fade5375c4b18c5319f20928a065b81896 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 07:13:57 -0400 Subject: [PATCH 14/89] CI: Bump codecov/codecov-action from 6 to 7 in the everything group (#7738) --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index e5f6a61ea..1601a67d7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -90,7 +90,7 @@ jobs: find obj_coverage -type f | paste -sd, | sed "s/^/files=/" >> "$GITHUB_OUTPUT" - name: Upload to codecov.io - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 with: disable_file_fixes: true disable_search: true From 279b425a5775295679d79ef051d581c38ffa5d36 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 12 Jun 2026 14:15:41 +0100 Subject: [PATCH 15/89] Internals: Cleanup V3Case (#7769) --- src/V3Case.cpp | 687 +++++++++++++++++++++++++------------------------ 1 file changed, 348 insertions(+), 339 deletions(-) diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 1431c8ddf..93612026f 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -42,10 +42,6 @@ VL_DEFINE_DEBUG_FUNCTIONS; -#define CASE_OVERLAP_WIDTH 16 // Maximum width we can check for overlaps in -#define CASE_BARF 999999 // Magic width when non-constant -#define CASE_ENCODER_GROUP_DEPTH 8 // Levels of priority to be ORed together in top IF tree - //###################################################################### class CaseLintVisitor final : public VNVisitorConst { @@ -119,21 +115,34 @@ class CaseLintVisitor final : public VNVisitorConst { } void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } -public: // CONSTRUCTORS explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); } explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); } ~CaseLintVisitor() override = default; + +public: + static void apply(AstCase* nodep) { CaseLintVisitor{nodep}; } + static void apply(AstGenCase* nodep) { CaseLintVisitor{nodep}; } }; //###################################################################### // Case state, as a visitor of each AstNode class CaseVisitor final : public VNVisitor { - // NODE STATE - // Cleared each Case - // AstIf::user3() -> bool. Set true to indicate clone not needed - const VNUser3InUse m_inuser3; + // Maximum width we can check for overlaps/exhaustiveness + constexpr static int CASE_OVERLAP_WIDTH = 16; + // Maximum number of case values for exhaustive analysis/optimization + constexpr static int CASE_MAX_VALUES = 1 << CASE_OVERLAP_WIDTH; + // Levels of priority to be ORed together in top IF tree + constexpr static int CASE_ENCODER_GROUP_DEPTH = 8; + + // TYPES + // Record for each case value + struct CaseRecord final { + AstCaseItem* itemp; // Case item that covers value + AstConst* constp; // Expression within 'itemp' that covers value (nullptr for default) + AstNode* stmtsp; // Statements of 'itemp' (might be nullptr if case is empty) + }; // STATE VDouble0 m_statCaseFast; // Statistic tracking @@ -141,229 +150,246 @@ class CaseVisitor final : public VNVisitor { const AstNode* m_alwaysp = nullptr; // Always in which case is located // Per-CASE - int m_caseWidth = 0; // Width of valueItems - int m_caseItems = 0; // Number of caseItem unique values - bool m_caseIncomplete = false; // Proven incomplete - bool m_caseNoOverlapsAllCovered = false; // Proven to be synopsys parallel_case compliant - // For each possible value, the case branch we need - std::array m_valueItem; + bool m_caseExhaustive = false; // Proven exhaustive + bool m_caseNoOverlaps = false; // Proven no overlaps between cases + // Map from value (index) to the CaseRecord that covers this value + std::array m_value2CaseRecord; // METHODS - //! Determine whether we should check case items are complete - //! @return Enum's dtype if should check, nullptr if shouldn't - const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { + // Determine whether we should check case items are complete + // Returns enum's dtype if should check, nullptr if shouldn't + static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return nullptr; const AstEnumDType* const enumDtp = VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType); if (!enumDtp) return nullptr; // Case isn't enum const AstBasicDType* const basicp = enumDtp->subDTypep()->basicp(); if (!basicp) return nullptr; // Not simple type (perhaps IEEE illegal) - if (basicp->width() > 32) return nullptr; return enumDtp; } - //! @return True if case items are complete, false if there are uncovered enums - bool checkCaseEnumComplete(const AstCase* const nodep, const AstEnumDType* const dtype) { - const uint32_t numCases = 1UL << m_caseWidth; - for (AstEnumItem* itemp = dtype->itemsp(); itemp; - itemp = VN_AS(itemp->nextp(), EnumItem)) { - AstConst* const econstp = VN_AS(itemp->valuep(), Const); - V3Number nummask{itemp, econstp->width()}; + + // Check and warn if case items are not complete over the given enum type. + // Returns true iff the case items cover all enum values/patterns. + bool checkExhaustiveEnum(const AstCase* const nodep, const AstEnumDType* const enump) { + const uint32_t numCases = 1UL << nodep->exprp()->width(); + for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) { + AstConst* const econstp = VN_AS(eip->valuep(), Const); + V3Number nummask{eip, econstp->width()}; nummask.opBitsNonX(econstp->num()); - const uint32_t mask = nummask.toUInt(); - V3Number numval{itemp, econstp->width()}; + V3Number numval{eip, econstp->width()}; numval.opBitsOne(econstp->num()); + + const uint32_t mask = nummask.toUInt(); const uint32_t val = numval.toUInt(); + // Check all cases to see if they cover this enum value/pattern for (uint32_t i = 0; i < numCases; ++i) { - if ((i & mask) == val) { - if (!m_valueItem[i]) { - if (!nodep->unique0Pragma()) - nodep->v3warn(CASEINCOMPLETE, "Enum item " - << itemp->prettyNameQ() - << " not covered by case\n"); - m_caseIncomplete = true; - return false; // enum has uncovered value by case items - } + if ((i & mask) != val) continue; // This case is not for this enum value + if (m_value2CaseRecord[i].itemp) continue; // Covered case + // Warn unless unique0 case which allows no-match + if (!nodep->unique0Pragma()) { + nodep->v3warn(CASEINCOMPLETE, + "Enum item " << eip->prettyNameQ() << " not covered by case"); } + // TODO: warn for all uncovered enum values, not just the first + return false; // enum has uncovered value by case items } } return true; // enum is fully covered } + + // Check and warn if case items are not complete over all possible values. + // Returns true iff the case items cover all values of the case expression. + bool checkExhaustivePacked(AstCase* nodep) { + const uint32_t numCases = 1UL << nodep->exprp()->width(); + for (uint32_t i = 0; i < numCases; ++i) { + if (m_value2CaseRecord[i].itemp) continue; // Covered case + if (!nodep->unique0Pragma()) { + nodep->v3warn(CASEINCOMPLETE, + "Case values incompletely covered (example pattern 0x" << std::hex + << i << ")"); + } + // TODO: warn for more than one uncovered case, not just the first + return false; + } + + // It's an exhaustive case statement + return true; + } + + bool checkExhaustive(AstCase* nodep) { + if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) { + return checkExhaustiveEnum(nodep, enump); + } + return checkExhaustivePacked(nodep); + } + bool isCaseTreeFast(AstCase* nodep) { - int width = 0; - bool opaque = false; - m_caseItems = 0; - m_caseNoOverlapsAllCovered = true; - for (AstCaseItem* itemp = nodep->itemsp(); itemp; - itemp = VN_AS(itemp->nextp(), CaseItem)) { - for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { - if (icondp->width() > width) width = icondp->width(); - if (icondp->isDouble()) opaque = true; - if (!VN_IS(icondp, Const)) width = CASE_BARF; // Can't parse; not a constant - m_caseItems++; + m_caseExhaustive = true; // TODO: we haven't proven this yet, but is as was before + m_caseNoOverlaps = false; + + AstNode* const caseExprp = nodep->exprp(); + if (caseExprp->isDouble() || caseExprp->isString()) return false; + + const int caseWidth = caseExprp->width(); + if (!caseWidth) return false; + if (caseWidth > CASE_OVERLAP_WIDTH) return false; + + int caseConditions = 0; + + for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { + // Can't do anything with non-constants + if (!VN_IS(condp, Const)) return false; + // Count conditions + ++caseConditions; } } - m_caseWidth = width; - if (width == 0 || width > CASE_OVERLAP_WIDTH || opaque) { - m_caseNoOverlapsAllCovered = false; - return false; // Too wide for analysis - } + UINFO(8, "Simple case statement: " << nodep); - const uint32_t numCases = 1UL << m_caseWidth; + const uint32_t numCases = 1UL << caseWidth; // Zero list of items for each value - for (uint32_t i = 0; i < numCases; ++i) m_valueItem[i] = nullptr; + for (uint32_t i = 0; i < numCases; ++i) { + m_value2CaseRecord[i].itemp = nullptr; + m_value2CaseRecord[i].constp = nullptr; + m_value2CaseRecord[i].stmtsp = nullptr; + } // Now pick up the values for each assignment // We can cheat and use uint32_t's because we only support narrow case's bool reportedOverlap = false; bool reportedSubcase = false; - bool hasDefaultCase = false; - std::map caseItemMap; // case condition -> case item + bool hasDefault = false; + m_caseNoOverlaps = true; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { - // UINFOTREE(9, icondp, "", "caseitem"); - AstConst* const iconstp = VN_AS(icondp, Const); - UASSERT_OBJ(iconstp, nodep, "above 'can't parse' should have caught this"); - if (neverItem(nodep, iconstp)) { - // X in casez can't ever be executed + + // Default case + if (itemp->isDefault()) { + // Default was moved to be the last item by V3LinkDot. Fill remaining cases + for (uint32_t i = 0; i < numCases; ++i) { + CaseRecord& caseRecord = m_value2CaseRecord[i]; + if (!caseRecord.itemp) { + caseRecord.itemp = itemp; + caseRecord.stmtsp = itemp->stmtsp(); + } + } + hasDefault = true; + continue; + } + + for (AstConst* iconstp = VN_AS(itemp->condsp(), Const); iconstp; + iconstp = VN_AS(iconstp->nextp(), Const)) { + // Some items can never match due to 2-state simulation + if (neverItem(nodep, iconstp)) continue; + + V3Number nummask{itemp, iconstp->width()}; + nummask.opBitsNonX(iconstp->num()); + V3Number numval{itemp, iconstp->width()}; + numval.opBitsOne(iconstp->num()); + + const uint32_t mask = nummask.toUInt(); + const uint32_t val = numval.toUInt(); + + uint32_t firstOverlap = 0; + const AstConst* overlappedCondp = nullptr; + bool foundHit = false; + for (uint32_t i = 0; i < numCases; ++i) { + if ((i & mask) != val) continue; + + CaseRecord& caseRecord = m_value2CaseRecord[i]; + + // If this is the first case that covers this value, record it + if (!caseRecord.itemp) { + caseRecord.itemp = itemp; + caseRecord.constp = iconstp; + caseRecord.stmtsp = itemp->stmtsp(); + foundHit = true; + continue; + } + + // Otherwise record the first overlapping case, + // but overlap within the same CaseItem is legal + if (!overlappedCondp && caseRecord.itemp != itemp) { + firstOverlap = i; + overlappedCondp = caseRecord.constp; + m_caseNoOverlaps = false; + } + } + if (!nodep->priorityPragma()) { + // If this case statement doesn't have the priority + // keyword, we want to warn on any overlap. + if (!reportedOverlap && overlappedCondp) { + std::ostringstream examplePattern; + if (iconstp->num().isAnyXZ()) { + examplePattern << " (example pattern 0x" << std::hex << firstOverlap + << ")"; + } + iconstp->v3warn(CASEOVERLAP, + "Case conditions overlap" + << examplePattern.str() << "\n" + << iconstp->warnContextPrimary() << '\n' + << overlappedCondp->warnOther() + << "... Location of overlapping condition\n" + << overlappedCondp->warnContextSecondary()); + reportedOverlap = true; + } } else { - const bool isCondWildcard = iconstp->num().isAnyXZ(); - V3Number nummask{itemp, iconstp->width()}; - nummask.opBitsNonX(iconstp->num()); - const uint32_t mask = nummask.toUInt(); - V3Number numval{itemp, iconstp->width()}; - numval.opBitsOne(iconstp->num()); - const uint32_t val = numval.toUInt(); - - uint32_t firstOverlap = 0; - const AstNode* overlappedCondp = nullptr; - bool foundHit = false; - for (uint32_t i = 0; i < numCases; ++i) { - if ((i & mask) == val) { - if (!m_valueItem[i]) { - m_valueItem[i] = icondp; - caseItemMap[icondp] = itemp; - foundHit = true; - } else if (!overlappedCondp) { - // Overlapping case item expressions in the - // same case item are legal - if (caseItemMap[m_valueItem[i]] != itemp) { - firstOverlap = i; - overlappedCondp = m_valueItem[i]; - m_caseNoOverlapsAllCovered = false; - } - } - } - } - if (!nodep->priorityPragma()) { - // If this case statement doesn't have the priority - // keyword, we want to warn on any overlap. - if (!reportedOverlap && overlappedCondp) { - std::ostringstream examplePattern; - if (isCondWildcard) { - examplePattern << " (example pattern 0x" << std::hex - << firstOverlap << ")"; - } - icondp->v3warn(CASEOVERLAP, - "Case conditions overlap" - << examplePattern.str() << "\n" - << icondp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of overlapping condition\n" - << overlappedCondp->warnContextSecondary()); - reportedOverlap = true; - } - } else { - // If this is a priority case, we only want to complain - // if every possible value for this item is already hit - // by some other item. This is true if foundHit is - // false. - if (!reportedSubcase && !foundHit) { - icondp->v3warn(CASEOVERLAP, - "Case item ignored: every matching value is covered " - "by an earlier condition\n" - << icondp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of previous condition\n" - << overlappedCondp->warnContextPrimary()); - reportedSubcase = true; - } - } - } - } - // Defaults were moved to last in the caseitem list by V3LinkDot - if (itemp->isDefault()) { // Case statement's default... Fill the table - for (uint32_t i = 0; i < numCases; ++i) { - if (!m_valueItem[i]) m_valueItem[i] = itemp; - } - caseItemMap[itemp] = itemp; - hasDefaultCase = true; - } - } - if (!hasDefaultCase) { - const AstEnumDType* const dtype = getEnumCompletionCheckDType(nodep); - if (dtype) { - if (!checkCaseEnumComplete(nodep, dtype)) { - // checkCaseEnumComplete has already warned of incompletion - m_caseNoOverlapsAllCovered = false; - return false; - } - } else { - for (uint32_t i = 0; i < numCases; ++i) { - if (!m_valueItem[i]) { // has uncovered case - if (!nodep->unique0Pragma()) - nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered " - "(example pattern 0x" - << std::hex << i << ")"); - m_caseIncomplete = true; - m_caseNoOverlapsAllCovered = false; - return false; + // If this is a priority case, we only want to complain + // if every possible value for this item is already hit + // by some other item. This is true if foundHit is + // false. + if (!reportedSubcase && !foundHit) { + iconstp->v3warn(CASEOVERLAP, + "Case item ignored: every matching value is covered " + "by an earlier condition\n" + << iconstp->warnContextPrimary() << '\n' + << overlappedCondp->warnOther() + << "... Location of previous condition\n" + << overlappedCondp->warnContextPrimary()); + reportedSubcase = true; } } } } - if (m_caseItems <= 3 + // If there was no default, check exhaustiveness + m_caseExhaustive = hasDefault || checkExhaustive(nodep); + if (!m_caseExhaustive) { + m_caseNoOverlaps = false; + return false; + } + + if (caseConditions <= 3 // Avoid e.g. priority expanders from going crazy in expansion - || (m_caseWidth >= 8 && (m_caseItems <= (m_caseWidth + 1)))) { + || (caseWidth >= 8 && (caseConditions <= (caseWidth + 1)))) { return false; // Not worth simplifying } - // Convert valueItem from AstCaseItem* to the expression - // Not done earlier, as we may now have a nullptr because it's just a ";" NOP branch - for (uint32_t i = 0; i < numCases; ++i) { - if (AstNode* const condp = m_valueItem[i]) { - const AstCaseItem* const caseItemp = caseItemMap[condp]; - UASSERT_OBJ(caseItemp, condp, "caseItemp should exist"); - m_valueItem[i] = caseItemp->stmtsp(); - } - } return true; // All is fine } + // TODO: should return AstNodeStmt after #6280 AstNode* replaceCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) { - if (msb < 0) { - // There's no space for a IF. We know upperValue is thus down to a specific - // exact value, so just return the tree value - // Note can't clone here, as we're going to check for equivalence above - AstNode* const foundp = m_valueItem[upperValue]; - return foundp; - } else { - // Make left and right subtrees - // cexpr[msb:lsb] == 1 - AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue); - AstNode* tree1p = replaceCaseFastRecurse( - cexprp, msb - 1, upperValue | (1UL << static_cast(msb))); + // Base case: If reached the last bit, upperValue equals an exact value, just return + // the statements from that CaseItem. Note: Not cloning here as the caller will do + // an identity check. + if (msb < 0) return m_value2CaseRecord[upperValue].stmtsp; - if (tree0p == tree1p) { - // Same logic on both sides, so we can just return one of 'em - return tree0p; - } - // We could have a "checkerboard" with A B A B, we can use the same IF on both edges + // Recursive case: + // Make left and right subtrees assuming cexpr[msb] is 0 and 1 respectively + const uint32_t upperValue0 = upperValue; + const uint32_t upperValue1 = upperValue | (1UL << msb); + AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue0); + AstNode* tree1p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue1); + + // If same logic on both sides, we can just return one of them + if (tree0p == tree1p) return tree0p; + + // We could have a "checkerboard" with A B A B, we can use the same IF on both edges + { bool same = true; - for (uint32_t a = upperValue, b = (upperValue | (1UL << msb)); - a < (upperValue | (1UL << msb)); a++, b++) { - if (m_valueItem[a] != m_valueItem[b]) { + for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) { + if (m_value2CaseRecord[a].stmtsp != m_value2CaseRecord[b].stmtsp) { same = false; break; } @@ -372,137 +398,120 @@ class CaseVisitor final : public VNVisitor { VL_DO_DANGLING(tree1p->deleteTree(), tree1p); return tree0p; } - - // Must have differing logic, so make a selection - - // Case expressions can't be linked twice, so clone them - if (tree0p && !tree0p->user3()) tree0p = tree0p->cloneTree(true); - if (tree1p && !tree1p->user3()) tree1p = tree1p->cloneTree(true); - - // Alternate scheme if we ever do multiple bits at a time: - // V3Number nummask (cexprp, cexprp->width(), (1UL<fileline(), cexprp->cloneTreePure(false), - // new AstConst(cexprp->fileline(), nummask)); - AstNodeExpr* const and1p - = new AstSel{cexprp->fileline(), cexprp->cloneTreePure(false), msb, 1}; - AstNodeExpr* const eqp - = new AstNeq{cexprp->fileline(), new AstConst{cexprp->fileline(), 0}, and1p}; - AstIf* const ifp = new AstIf{cexprp->fileline(), eqp, tree1p, tree0p}; - ifp->user3(1); // So we don't bother to clone it - return ifp; } + + // Must have differing logic. Test the bit and convert to an If. + + // Clone if needed + if (tree0p && tree0p->backp()) tree0p = tree0p->cloneTree(true); + if (tree1p && tree1p->backp()) tree1p = tree1p->cloneTree(true); + // Create the If statement + FileLine* const flp = cexprp->fileline(); + AstNodeExpr* const condp = new AstSel{flp, cexprp->cloneTreePure(false), msb, 1}; + AstIf* const ifp = new AstIf{flp, condp, tree1p, tree0p}; + return ifp; } - void replaceCaseFast(AstCase* nodep) { + // TODO: should return AstNodeStmt after #6280 + AstNode* replaceCaseFast(AstCase* nodep) { // CASEx(cexpr,.... // -> tree of IF(msb, IF(msb-1, 11, 10) // IF(msb-1, 01, 00)) - - if (debug() >= 9) { // LCOV_EXCL_START - for (uint32_t i = 0; i < (1UL << m_caseWidth); ++i) { - if (const AstNode* const itemp = m_valueItem[i]) { - UINFO(9, "Value " << std::hex << i << " " << itemp); - } - } - } // LCOV_EXCL_STOP - - // Handle any assertions - replaceCaseParallel(nodep, m_caseNoOverlapsAllCovered); - - AstNode::user3ClearTree(); - AstNode* ifrootp = replaceCaseFastRecurse(nodep->exprp(), m_caseWidth - 1, 0UL); - - // Case expressions can't be linked twice, so clone them - if (ifrootp && !ifrootp->user3()) ifrootp = ifrootp->cloneTree(true); - - if (ifrootp) { - nodep->replaceWith(ifrootp); - } else { - nodep->unlinkFrBack(); - } - VL_DO_DANGLING(nodep->deleteTree(), nodep); - UINFOTREE(9, ifrootp, "", "_simp"); + const int caseWidth = nodep->exprp()->width(); + AstNode* const ifrootp = replaceCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); + return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp; } - void replaceCaseComplicated(AstCase* nodep) { + // TODO: should return AstNodeStmt after #6280 + AstNode* replaceCaseComplicated(AstCase* nodep) { // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) // -> IF((cexpr==icond1),istmts1, // IF((EQ (AND MASK cexpr) (AND MASK icond1) // ,istmts2, istmts3 - AstNodeExpr* const cexprp = nodep->exprp(); - // We'll do this in two stages. First stage, convert the conditions to - // the appropriate IF AND terms. - UINFOTREE(9, nodep, "", "_comp_IN::"); - bool hadDefault = false; + + // We'll do this in two stages. + // First stage, convert the conditions to the appropriate IF AND terms. + bool hasDefault = false; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - if (!itemp->condsp()) { - // Default clause. Just make true, we'll optimize it away later - itemp->addCondsp(new AstConst{itemp->fileline(), AstConst::BitTrue{}}); - hadDefault = true; - } else { - // Expressioned clause - AstNodeExpr* icondNextp = nullptr; - AstNodeExpr* ifexprp = nullptr; // If expression to test - for (AstNodeExpr* icondp = itemp->condsp(); icondp; icondp = icondNextp) { - icondNextp = VN_AS(icondp->nextp(), NodeExpr); - icondp->unlinkFrBack(); - AstNodeExpr* condp = nullptr; // Default is to use and1p/and2p - AstConst* const iconstp = VN_CAST(icondp, Const); - if (iconstp && neverItem(nodep, iconstp)) { - // X in casez can't ever be executed - VL_DO_DANGLING(icondp->deleteTree(), icondp); - VL_DANGLING(iconstp); - // For simplicity, make expression that is not equal, and let later - // optimizations remove it - condp = new AstConst{itemp->fileline(), AstConst::BitFalse{}}; - } else if (AstInsideRange* const irangep = VN_CAST(icondp, InsideRange)) { - // Similar logic in V3Width::visit(AstInside) - condp = irangep->newAndFromInside(cexprp->cloneTreePure(true), - irangep->lhsp()->unlinkFrBack(), - irangep->rhsp()->unlinkFrBack()); - VL_DO_DANGLING2(icondp->deleteTree(), icondp, irangep); - } else if (iconstp && iconstp->num().isFourState() - && (nodep->casex() || nodep->casez() || nodep->caseInside())) { - V3Number nummask{itemp, iconstp->width()}; - nummask.opBitsNonX(iconstp->num()); - V3Number numval{itemp, iconstp->width()}; - numval.opBitsOne(iconstp->num()); - AstNodeExpr* const and1p - = new AstAnd{itemp->fileline(), cexprp->cloneTreePure(false), - new AstConst{itemp->fileline(), nummask}}; - AstNodeExpr* const and2p = new AstAnd{ - itemp->fileline(), new AstConst{itemp->fileline(), numval}, - new AstConst{itemp->fileline(), nummask}}; - VL_DO_DANGLING(icondp->deleteTree(), icondp); - VL_DANGLING(iconstp); - condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); - } else { - // Not a caseX mask, we can build CASEEQ(cexpr icond) - AstNodeExpr* const and1p = cexprp->cloneTreePure(false); - AstNodeExpr* const and2p = icondp; - condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); - } - if (!ifexprp) { - ifexprp = condp; - } else { - ifexprp = new AstLogOr{itemp->fileline(), ifexprp, condp}; - } - } - // Replace expression in tree - itemp->addCondsp(ifexprp); + FileLine* const flp = itemp->fileline(); + + // Default clause. Just make true, we'll optimize it away later + if (itemp->isDefault()) { + itemp->addCondsp(new AstConst{flp, AstConst::BitTrue{}}); + hasDefault = true; + continue; } + + // Regular clause. Construct the condition expression. + AstNodeExpr* newCondp = nullptr; + for (AstNodeExpr *itemExprp = itemp->condsp(), *nextp; itemExprp; itemExprp = nextp) { + nextp = VN_AS(itemExprp->nextp(), NodeExpr); + itemExprp->unlinkFrBack(); + + // If case never matches, ignore it + if (neverItem(nodep, itemExprp)) { + VL_DO_DANGLING(itemExprp->deleteTree(), itemExprp); + continue; + } + + // Compute the term to add to the condition expression + AstNodeExpr* const termp = [&]() -> AstNodeExpr* { + // Will need a copy of the caseExpr regardless + AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false); + + // InsideRange: Similar logic in V3Width::visit(AstInside) + if (AstInsideRange* const itemRangep = VN_CAST(itemExprp, InsideRange)) { + AstNodeExpr* const resultp = itemRangep->newAndFromInside( // + caseExprp, // + itemRangep->lhsp()->unlinkFrBack(), + itemRangep->rhsp()->unlinkFrBack()); + VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemRangep); + return resultp; + } + + // Check if we need to perform a wildcard match, this needs masking + if (AstConst* const itemConstp = VN_CAST(itemExprp, Const)) { + // TODO: 4-state will need to fix this + if (itemConstp->num().isFourState() + && (nodep->casex() || nodep->casez() || nodep->caseInside())) { + // Wildcard match, make 'caseExpr' & 'mask' == 'itemExpr' & 'mask' + V3Number numMask{itemp, itemConstp->width()}; + numMask.opBitsNonX(itemConstp->num()); + V3Number numOne{itemp, itemConstp->width()}; + numOne.opBitsOne(itemConstp->num()); + V3Number numRhs{itemp, itemConstp->width()}; + numRhs.opAnd(numOne, numMask); + VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemConstp); + return AstEq::newTyped( + flp, // + new AstConst{flp, numRhs}, + new AstAnd{flp, caseExprp, new AstConst{flp, numMask}}); + } + } + + // Regular case, use simple equality comparison + return AstEq::newTyped(flp, caseExprp, itemExprp); + }(); + + // 'Or' new term with previous terms + newCondp = newCondp ? new AstLogOr{flp, newCondp, termp} : termp; + } + // Replace expression in tree. Needs to be non-null, so add a constant false if needed + if (!newCondp) newCondp = new AstConst{flp, AstConst::BitFalse{}}; + itemp->addCondsp(newCondp); } - if (!hadDefault) { - // If there was no default, add a empty one, this greatly simplifies below code - // and constant propagation will just eliminate it for us later. + + // If there was no default, add a empty one, this greatly simplifies below code + // and constant propagation will just eliminate it for us later. + if (!hasDefault) { nodep->addItemsp(new AstCaseItem{ nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr}); } - UINFOTREE(9, nodep, "", "_comp_COND"); + // Now build the IF statement tree - // The tree can be quite huge. Pull ever group of 8 out, and make a OR tree. + // The tree can be quite huge. Pull every group of 8 out, and make a OR tree. // This reduces the depth for the bottom elements, at the cost of // some of the top elements. If we ever have profiling data, we // should pull out the most common item from here and instead make @@ -513,8 +522,11 @@ class CaseVisitor final : public VNVisitor { AstIf* itemnextp = nullptr; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - AstNode* const istmtsp = itemp->stmtsp(); // Maybe null -- no action. + + // Grab the statements from this item. May be empty. + AstNode* const istmtsp = itemp->stmtsp(); if (istmtsp) istmtsp->unlinkFrBackWithNext(); + // Expressioned clause AstNodeExpr* const ifexprp = itemp->condsp()->unlinkFrBack(); { // Prepare for next group @@ -550,61 +562,61 @@ class CaseVisitor final : public VNVisitor { itemnextp = newp; } } - UINFOTREE(9, nodep, "", "_comp_TREE"); - // Handle any assertions - replaceCaseParallel(nodep, false); - // Replace the CASE... with IF... - if (grouprootp) { - UINFOTREE(9, grouprootp, "", "_new"); - nodep->replaceWith(grouprootp); - } else { - nodep->unlinkFrBack(); - } - VL_DO_DANGLING(nodep->deleteTree(), nodep); + return grouprootp; } - void replaceCaseParallel(AstCase* nodep, bool noOverlapsAllCovered) { - // Take the notParallelp tree under the case statement created by V3Assert - // If the statement was proven to have no overlaps and all cases - // covered, we're done with it. - // Else, convert to a normal statement parallel with the case statement. - if (nodep->notParallelp() && !noOverlapsAllCovered) { - AstNode* const parp = nodep->notParallelp()->unlinkFrBackWithNext(); - nodep->addNextHere(parp); - } - } - - bool neverItem(const AstCase* casep, const AstConst* itemp) { + bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) { + const AstConst* const constp = VN_CAST(itemExprp, Const); + if (!constp) return false; // Xs in case or casez are impossible due to two state simulations - if (casep->casex() || casep->caseInside()) { - } else if (casep->casez()) { - if (itemp->num().isAnyX()) return true; - } else { - if (itemp->num().isFourState()) return true; - } - return false; + if (casep->casex() || casep->caseInside()) return false; + if (casep->casez()) return constp->num().isAnyX(); + return constp->num().isFourState(); } // VISITORS void visit(AstCase* nodep) override { - VL_RESTORER(m_caseIncomplete); - { CaseLintVisitor{nodep}; } - iterateChildren(nodep); - UINFOTREE(9, nodep, "", "case_old"); UASSERT_OBJ(nodep->exprp()->isPure(), nodep, "Impure case expression should have been removed by V3LiftExpr"); + + CaseLintVisitor::apply(nodep); + + // Convert any children first + iterateChildren(nodep); + + // Convert the case statement + AstNode* replacementp = nullptr; if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) { // It's a simple priority encoder or complete statement // we can make a tree of statements to avoid extra comparisons ++m_statCaseFast; - VL_DO_DANGLING(replaceCaseFast(nodep), nodep); + replacementp = replaceCaseFast(nodep); } else { - // If a case statement is whole, presume signals involved aren't forming a latch - if (m_alwaysp && !m_caseIncomplete) + // If a case statement is exhaustive, presume signals involved aren't forming a latch + // TODO: this is broken, but it is as was before + if (m_alwaysp && m_caseExhaustive) { m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); + } ++m_statCaseSlow; - VL_DO_DANGLING(replaceCaseComplicated(nodep), nodep); + m_caseExhaustive = false; + m_caseNoOverlaps = false; + replacementp = replaceCaseComplicated(nodep); } + + // Take the notParallelp tree under the case statement created by V3Assert + // If the statement was proven to have no overlaps and all cases covered, + // it can be removed. Otherwise insert the assertion after the case statement. + if (nodep->notParallelp() && (!m_caseExhaustive || !m_caseNoOverlaps)) { + nodep->addNextHere(nodep->notParallelp()->unlinkFrBackWithNext()); + } + + // Replace/remove the case statement + if (replacementp) { + nodep->replaceWith(replacementp); + } else { + nodep->unlinkFrBack(); + } + VL_DO_DANGLING(nodep->deleteTree(), nodep); } //-------------------- void visit(AstAlways* nodep) override { @@ -616,10 +628,7 @@ class CaseVisitor final : public VNVisitor { public: // CONSTRUCTORS - explicit CaseVisitor(AstNetlist* nodep) { - for (auto& itr : m_valueItem) itr = nullptr; - iterate(nodep); - } + explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); } ~CaseVisitor() override { V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast); V3Stats::addStat("Optimizations, Cases complex", m_statCaseSlow); @@ -636,5 +645,5 @@ void V3Case::caseAll(AstNetlist* nodep) { } void V3Case::caseLint(AstGenCase* nodep) { UINFO(4, __FUNCTION__ << ": "); - { CaseLintVisitor{nodep}; } + CaseLintVisitor::apply(nodep); } From e0c4c995b9ce0abf099b3ad0b43a39a3d7d289e7 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 12 Jun 2026 12:22:18 +0100 Subject: [PATCH 16/89] Fix crash on overlapping priority case --- src/V3Case.cpp | 66 ++++++++++++----------- test_regress/t/t_case_priority_overlap.py | 18 +++++++ test_regress/t/t_case_priority_overlap.v | 56 +++++++++++++++++++ 3 files changed, 108 insertions(+), 32 deletions(-) create mode 100755 test_regress/t/t_case_priority_overlap.py create mode 100644 test_regress/t/t_case_priority_overlap.v diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 93612026f..9fc489c4d 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -290,9 +290,9 @@ class CaseVisitor final : public VNVisitor { const uint32_t mask = nummask.toUInt(); const uint32_t val = numval.toUInt(); - uint32_t firstOverlap = 0; - const AstConst* overlappedCondp = nullptr; - bool foundHit = false; + bool foundNewCase = false; + const AstConst* firstOverlapConstp = nullptr; + uint32_t firstOverlapValue = 0; for (uint32_t i = 0; i < numCases; ++i) { if ((i & mask) != val) continue; @@ -303,51 +303,53 @@ class CaseVisitor final : public VNVisitor { caseRecord.itemp = itemp; caseRecord.constp = iconstp; caseRecord.stmtsp = itemp->stmtsp(); - foundHit = true; + foundNewCase = true; continue; } - // Otherwise record the first overlapping case, - // but overlap within the same CaseItem is legal - if (!overlappedCondp && caseRecord.itemp != itemp) { - firstOverlap = i; - overlappedCondp = caseRecord.constp; + // There is an overlap. If it's within the same CaseItem, it is legal + if (caseRecord.itemp == itemp) continue; + + // Otherwise record the first overlapping case + if (!firstOverlapConstp) { + firstOverlapConstp = caseRecord.constp; + firstOverlapValue = i; m_caseNoOverlaps = false; } } - if (!nodep->priorityPragma()) { - // If this case statement doesn't have the priority - // keyword, we want to warn on any overlap. - if (!reportedOverlap && overlappedCondp) { + if (nodep->priorityPragma()) { + // If this is a priority case, we only want to complain if every possible value + // for this item is already hit by some other item. This is true if + // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only covering + // item is this item itself, which is legal overlap within one item. + if (!reportedSubcase && !foundNewCase && firstOverlapConstp) { + iconstp->v3warn(CASEOVERLAP, + "Case item ignored: every matching value is covered " + "by an earlier condition\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of previous condition\n" + << firstOverlapConstp->warnContextPrimary()); + reportedSubcase = true; + } + } else { + // If this case statement doesn't have the priority keyword, + // we want to warn on any overlap. + if (!reportedOverlap && firstOverlapConstp) { std::ostringstream examplePattern; if (iconstp->num().isAnyXZ()) { - examplePattern << " (example pattern 0x" << std::hex << firstOverlap - << ")"; + examplePattern << " (example pattern 0x" << std::hex + << firstOverlapValue << ")"; } iconstp->v3warn(CASEOVERLAP, "Case conditions overlap" << examplePattern.str() << "\n" << iconstp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() + << firstOverlapConstp->warnOther() << "... Location of overlapping condition\n" - << overlappedCondp->warnContextSecondary()); + << firstOverlapConstp->warnContextSecondary()); reportedOverlap = true; } - } else { - // If this is a priority case, we only want to complain - // if every possible value for this item is already hit - // by some other item. This is true if foundHit is - // false. - if (!reportedSubcase && !foundHit) { - iconstp->v3warn(CASEOVERLAP, - "Case item ignored: every matching value is covered " - "by an earlier condition\n" - << iconstp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of previous condition\n" - << overlappedCondp->warnContextPrimary()); - reportedSubcase = true; - } } } } diff --git a/test_regress/t/t_case_priority_overlap.py b/test_regress/t/t_case_priority_overlap.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_case_priority_overlap.v b/test_regress/t/t_case_priority_overlap.v new file mode 100644 index 000000000..2de27080a --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.v @@ -0,0 +1,56 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Regression: a `priority` case item whose later condition (INST_B) is fully +// subsumed by an earlier condition (INST_A) of the *same* item. Overlap within +// a single case item is legal, but this previously crashed V3Case with a null +// pointer dereference: the priority "case item ignored" CASEOVERLAP diagnostic +// dereferenced 'overlappedCondp', which is null when the only covering item is +// the item itself. Must compile cleanly and simulate correctly. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [1:0] in; + logic [1:0] out; + + always_comb begin + priority casez (in) + 2'b1?, // fully subsumes 2'b11 below on the same case clause + 2'b11: out = 2'b10; + 2'b0?: out = 2'b01; + endcase + end + + initial begin + in = 2'b00; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b01; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b10; + @(posedge clk); + `checkh(out, 2'b10); + + in = 2'b11; + @(posedge clk); + `checkh(out, 2'b10); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule From dab6889f1e3cdc1bd1c7f0e3c3b0957ac0322ef3 Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Fri, 12 Jun 2026 16:40:38 +0200 Subject: [PATCH 17/89] Support assert property 'default disable iff` (#4848) (#7723) --- src/V3Assert.cpp | 73 ++++++ src/V3Assert.h | 5 + src/V3AssertNfa.cpp | 10 +- src/V3AssertPre.cpp | 14 +- src/V3AstNodeOther.h | 6 + src/Verilator.cpp | 1 + src/verilog.y | 10 +- test_regress/t/t_assert_iff_clk.py | 18 ++ test_regress/t/t_assert_iff_clk.v | 48 ++++ test_regress/t/t_assert_iff_clk_unsup.out | 6 - test_regress/t/t_assert_iff_clk_unsup.v | 22 -- .../t/t_default_disable_iff_gen_multi_bad.out | 6 + ...=> t_default_disable_iff_gen_multi_bad.py} | 6 +- .../t/t_default_disable_iff_gen_multi_bad.v | 20 ++ test_regress/t/t_default_disable_iff_scope.py | 18 ++ test_regress/t/t_default_disable_iff_scope.v | 230 ++++++++++++++++++ 16 files changed, 444 insertions(+), 49 deletions(-) create mode 100755 test_regress/t/t_assert_iff_clk.py create mode 100644 test_regress/t/t_assert_iff_clk.v delete mode 100644 test_regress/t/t_assert_iff_clk_unsup.out delete mode 100644 test_regress/t/t_assert_iff_clk_unsup.v create mode 100644 test_regress/t/t_default_disable_iff_gen_multi_bad.out rename test_regress/t/{t_assert_iff_clk_unsup.py => t_default_disable_iff_gen_multi_bad.py} (70%) create mode 100644 test_regress/t/t_default_disable_iff_gen_multi_bad.v create mode 100755 test_regress/t/t_default_disable_iff_scope.py create mode 100644 test_regress/t/t_default_disable_iff_scope.v diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 0ccf162fa..e785973f1 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -23,6 +23,79 @@ VL_DEFINE_DEBUG_FUNCTIONS; +namespace { + +class DefaultDisableLocalVisitor final : public VNVisitor { + // STATE + AstNode* m_scopep = nullptr; + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + nodep->defaultDisablep(nullptr); + iterateChildren(nodep); + } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + nodep->defaultDisablep(nullptr); + iterateChildren(nodep); + } + void visit(AstDefaultDisable* nodep) override { + UASSERT_OBJ(nodep, m_scopep, + "default disable iff must be inside a module or generate block"); + AstDefaultDisable* defaultp = nullptr; + if (const AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) { + defaultp = modp->defaultDisablep(); + } else { + defaultp = VN_AS(m_scopep, GenBlock)->defaultDisablep(); + } + if (VL_UNLIKELY(defaultp)) { + nodep->v3error("Only one 'default disable iff' allowed per " + << (VN_IS(m_scopep, NodeModule) ? "module" : "generate block") + << " (IEEE 1800-2023 16.15)"); + } else if (AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) { + modp->defaultDisablep(nodep); + } else { + VN_AS(m_scopep, GenBlock)->defaultDisablep(nodep); + } + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit DefaultDisableLocalVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + +class DefaultDisablePropagateVisitor final : public VNVisitor { + // STATE + AstDefaultDisable* m_defaultDisablep = nullptr; + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + if (!nodep->defaultDisablep()) nodep->defaultDisablep(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit DefaultDisablePropagateVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + +} // namespace + +void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) { + { DefaultDisableLocalVisitor{nodep}; } + { DefaultDisablePropagateVisitor{nodep}; } +} + //###################################################################### // AssertDeFutureVisitor // If any AstFuture, then move all non-future varrefs to be one cycle behind, diff --git a/src/V3Assert.h b/src/V3Assert.h index dbaac13bf..f144688b6 100644 --- a/src/V3Assert.h +++ b/src/V3Assert.h @@ -24,6 +24,11 @@ //============================================================================ +class V3AssertCommon final { +public: + static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED; +}; + class V3Assert final { public: static void assertAll(AstNetlist* nodep) VL_MT_DISABLED; diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index bf50e97a1..8db3e7761 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -26,6 +26,7 @@ #include "V3AssertNfa.h" +#include "V3Assert.h" #include "V3Const.h" #include "V3Graph.h" #include "V3Task.h" @@ -2301,7 +2302,7 @@ class AssertNfaVisitor final : public VNVisitor { VL_RESTORER(m_defaultDisablep); m_modp = nodep; m_defaultClockingp = nullptr; - m_defaultDisablep = nullptr; + m_defaultDisablep = nodep->defaultDisablep(); SvaNfaLowering lowering{nodep}; m_loweringp = &lowering; iterateChildren(nodep); @@ -2310,9 +2311,12 @@ class AssertNfaVisitor final : public VNVisitor { if (nodep->isDefault() && !m_defaultClockingp) m_defaultClockingp = nodep; iterateChildren(nodep); } - void visit(AstDefaultDisable* nodep) override { - if (!m_defaultDisablep) m_defaultDisablep = nodep; + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); } + void visit(AstDefaultDisable* nodep) override {} void visit(AstAssert* nodep) override { processAssertion(nodep); } void visit(AstCover* nodep) override { processAssertion(nodep); } void visit(AstRestrict* nodep) override { diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index c61299a66..05fb804fc 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -23,6 +23,7 @@ #include "V3AssertPre.h" +#include "V3Assert.h" #include "V3Const.h" #include "V3Task.h" #include "V3UniqueNames.h" @@ -1438,12 +1439,6 @@ private: } void visit(AstDefaultDisable* nodep) override { - if (m_defaultDisablep) { - nodep->v3error("Only one 'default disable iff' allowed per module" - " (IEEE 1800-2023 16.15)"); - } else { - m_defaultDisablep = nodep; - } VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstInferredDisable* nodep) override { @@ -1565,10 +1560,15 @@ private: VL_RESTORER(m_modp); m_defaultClockingp = nullptr; m_defaultClkEvtVarp = nullptr; - m_defaultDisablep = nullptr; + m_defaultDisablep = nodep->defaultDisablep(); m_modp = nodep; iterateChildren(nodep); } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } void visit(AstProperty* nodep) override { // The body will be visited when will be substituted in place of property reference // (AstFuncRef) diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index f3692d05e..1e17d672c 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -301,6 +301,7 @@ class AstNodeModule VL_NOT_FINAL : public AstNode { VLifetime m_lifetime; // Lifetime VTimescale m_timeunit; // Global time unit VOptionBool m_unconnectedDrive; // State of `unconnected_drive + AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope bool m_modPublic : 1; // Module has public references bool m_modTrace : 1; // Tracing this module @@ -351,6 +352,8 @@ public: string origName() const override { return m_origName; } string someInstanceName() const VL_MT_SAFE { return m_someInstanceName; } void someInstanceName(const string& name) { m_someInstanceName = name; } + AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; } + void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; } bool inLibrary() const { return m_inLibrary; } void inLibrary(bool flag) { m_inLibrary = flag; } void depth(int value) { m_depth = value; } @@ -2810,6 +2813,7 @@ class AstGenBlock final : public AstNodeGen { std::string m_name; // Name of block const bool m_unnamed; // Originally unnamed (name change does not affect this) const bool m_implied; // Not inserted by user + AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope public: AstGenBlock(FileLine* fl, const string& name, AstNode* itemsp, bool implied) @@ -2826,6 +2830,8 @@ public: void name(const std::string& name) override { m_name = name; } bool unnamed() const { return m_unnamed; } bool implied() const { return m_implied; } + AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; } + void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; } }; class AstGenCase final : public AstNodeGen { // Generate 'case' diff --git a/src/Verilator.cpp b/src/Verilator.cpp index f94c8655d..c30f8b28c 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -260,6 +260,7 @@ static void process() { // Assertion insertion // After we've added block coverage, but before other nasty transforms + V3AssertCommon::collectDefaultDisable(v3Global.rootp()); V3AssertNfa::assertNfaAll(v3Global.rootp()); // V3AssertProp removed: NFA subsumes multi-cycle property lowering. // Unsupported constructs fall through to V3AssertPre. diff --git a/src/verilog.y b/src/verilog.y index 663e8a206..510545736 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -6664,14 +6664,8 @@ property_spec: // IEEE: property_spec { $$ = new AstPropSpec{$1, $3, nullptr, $5}; } | '@' senitemVar pexpr { $$ = new AstPropSpec{$1, $2, nullptr, $3}; } - // // Disable applied after the event occurs, - // // so no existing AST can represent this - | yDISABLE yIFF '(' expr ')' '@' '(' senitemEdge ')' pexpr - { $$ = new AstPropSpec{$1, $8, nullptr, new AstLogOr{$1, $4, $10}}; - BBUNSUP($1, "Unsupported: property '(disable iff (...) @ (...)'\n" - + $1->warnMore() - + "... Suggest use property '(@(...) disable iff (...))'"); } - //UNSUP remove above + | yDISABLE yIFF '(' expr ')' '@' '(' senitem ')' pexpr + { $$ = new AstPropSpec{$1, $8, $4, $10}; } | yDISABLE yIFF '(' expr ')' pexpr { $$ = new AstPropSpec{$4->fileline(), nullptr, $4, $6}; } | pexpr { $$ = new AstPropSpec{$1->fileline(), nullptr, nullptr, $1}; } ; diff --git a/test_regress/t/t_assert_iff_clk.py b/test_regress/t/t_assert_iff_clk.py new file mode 100755 index 000000000..35e44000c --- /dev/null +++ b/test_regress/t/t_assert_iff_clk.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_iff_clk.v b/test_regress/t/t_assert_iff_clk.v new file mode 100644 index 000000000..3dfb3458b --- /dev/null +++ b/test_regress/t/t_assert_iff_clk.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro Ltd +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + int cyc; + logic rst = 1'b1; + logic x = 1'b1; + int issue_fail; + int pre_fail; + int post_fail; + int pre_temporal_fail; + int post_temporal_fail; + + a_issue: assert property (disable iff(rst !== 1'b0) @(posedge clk) !x) + else issue_fail++; + + assert property (disable iff (cyc < 5) @(posedge clk) 0) + else pre_fail++; + + assert property (@(posedge clk) disable iff (cyc < 5) 0) + else post_fail++; + + assert property (disable iff (cyc < 5) @(posedge clk) 1 ##1 0) + else pre_temporal_fail++; + + assert property (@(posedge clk) disable iff (cyc < 5) 1 ##1 0) + else post_temporal_fail++; + + always @(posedge clk) begin + cyc <= cyc + 1; + rst <= cyc < 4; + x <= cyc < 4; + if (cyc == 12) begin + if (issue_fail != 0) $stop; + if (pre_fail != post_fail) $stop; + if (pre_temporal_fail != post_temporal_fail) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_assert_iff_clk_unsup.out b/test_regress/t/t_assert_iff_clk_unsup.out deleted file mode 100644 index 3ff98273e..000000000 --- a/test_regress/t/t_assert_iff_clk_unsup.out +++ /dev/null @@ -1,6 +0,0 @@ -%Error-UNSUPPORTED: t/t_assert_iff_clk_unsup.v:20:20: Unsupported: property '(disable iff (...) @ (...)' - : ... Suggest use property '(@(...) disable iff (...))' - 20 | assert property (disable iff (cyc < 5) @(posedge clk) cyc >= 5); - | ^~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to diff --git a/test_regress/t/t_assert_iff_clk_unsup.v b/test_regress/t/t_assert_iff_clk_unsup.v deleted file mode 100644 index efe917a04..000000000 --- a/test_regress/t/t_assert_iff_clk_unsup.v +++ /dev/null @@ -1,22 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2022 Antmicro Ltd -// SPDX-License-Identifier: CC0-1.0 - -module t ( - input clk -); - - input clk; - int cyc = 0; - logic val = 0; - - always @(posedge clk) begin - cyc <= cyc + 1; - val = ~val; - end - - assert property (disable iff (cyc < 5) @(posedge clk) cyc >= 5); - -endmodule diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.out b/test_regress/t/t_default_disable_iff_gen_multi_bad.out new file mode 100644 index 000000000..ed0df293e --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_default_disable_iff_gen_multi_bad.v:15:7: Only one 'default disable iff' allowed per generate block (IEEE 1800-2023 16.15) + : ... note: In instance 't' + 15 | default disable iff (cyc < 7); + | ^~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_assert_iff_clk_unsup.py b/test_regress/t/t_default_disable_iff_gen_multi_bad.py similarity index 70% rename from test_regress/t/t_assert_iff_clk_unsup.py rename to test_regress/t/t_default_disable_iff_gen_multi_bad.py index b5718946c..38cf36b43 100755 --- a/test_regress/t/t_assert_iff_clk_unsup.py +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.py @@ -4,13 +4,13 @@ # This program is free software; you can redistribute it and/or modify it # under the terms of either the GNU Lesser General Public License Version 3 # or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('linter') -test.lint(expect_filename=test.golden_filename, verilator_flags2=['--assert'], fails=True) +test.lint(fails=True, expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.v b/test_regress/t/t_default_disable_iff_gen_multi_bad.v new file mode 100644 index 000000000..61e69830c --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t(input clk); + int cyc; + + default disable iff (cyc < 3); + + generate + begin : g + default disable iff (cyc < 5); + default disable iff (cyc < 7); + + assert property (@(posedge clk) 0); + end + endgenerate +endmodule diff --git a/test_regress/t/t_default_disable_iff_scope.py b/test_regress/t/t_default_disable_iff_scope.py new file mode 100755 index 000000000..35e44000c --- /dev/null +++ b/test_regress/t/t_default_disable_iff_scope.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_default_disable_iff_scope.v b/test_regress/t/t_default_disable_iff_scope.v new file mode 100644 index 000000000..6cdabba82 --- /dev/null +++ b/test_regress/t/t_default_disable_iff_scope.v @@ -0,0 +1,230 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + bit clk; + int cyc; + + always #1 clk = !clk; + + bit rst_mod; + int before_mod_false; + int before_mod_gen_false; + int before_gen_default_false; + int before_gen_child_default_false; + int prog_false_count; + + always_comb rst_mod = (cyc < 3); + + // The default declaration appears later in the module, but still applies here. + assert property (@(posedge clk) 0) + else before_mod_false++; + + generate + begin : g_before_mod_default + // Inherits the module default declaration that appears later in this module. + assert property (@(posedge clk) 0) + else before_mod_gen_false++; + end + + begin : g_before_local_default + // The generate-local default declaration appears later in the block. + assert property (@(posedge clk) 0) + else before_gen_default_false++; + + begin : g_child_inherit + // Inherits the generate-local default declaration that appears later. + assert property (@(posedge clk) 0) + else before_gen_child_default_false++; + end + default disable iff (cyc < 7); + end + endgenerate + + default disable iff (rst_mod); + + generate + begin : g_override + bit rst_gen; + int override_false; + + always_comb rst_gen = (cyc < 5); + + default disable iff (rst_gen); + + assert property (@(posedge clk) 0) + else override_false++; + end + + begin : g_inherit + bit rst_mod; + int inherit_false; + + always_comb rst_mod = (cyc < 8); + + // Inherits the module default, whose rst_mod was resolved in module scope. + assert property (@(posedge clk) 0) + else inherit_false++; + end + endgenerate + + if_scope u_if (.clk(clk), .cyc(cyc)); + p_scope u_prog (.clk(clk), .cyc(cyc), .false_count(prog_false_count)); + examples_with_default_count u_with (.clk(clk), .cyc(cyc)); + examples_without_default_count u_without (.clk(clk), .cyc(cyc)); + examples_with_default u_ieee_with (.a(1'b0), .b(1'b0), .clk(clk), .rst(1'b0), .rst1(1'b0)); + examples_without_default u_ieee_without (.a(1'b0), .b(1'b0), .clk(clk), .rst(1'b0)); + m_override u_m_override (.clk(clk), .cyc(cyc)); + + // The disable iff expression is unsampled, so same-edge updates race in MT simulation. + // Change clk on negedge while the properties are sampled on posedge to avoid races. + always @(negedge clk) begin + cyc++; + if (cyc == 12) begin + `checkd(before_mod_false, 9); + `checkd(before_mod_gen_false, 9); + `checkd(before_gen_default_false, 5); + `checkd(before_gen_child_default_false, 5); + `checkd(g_inherit.inherit_false, 9); + `checkd(g_override.override_false, 7); + `checkd(u_if.false_count, 6); + `checkd(u_if.g_inherit.false_count, 6); + `checkd(prog_false_count, 4); + `checkd(u_with.explicit_assert_false, 5); + `checkd(u_with.explicit_property_false, 5); + `checkd(u_with.inferred_default_false, 9); + `checkd(u_without.explicit_assert_false, 9); + `checkd(u_without.explicit_property_false, 9); + `checkd(u_m_override.false_count, 7); + `checkd(u_m_override.g_inherit_from_module.false_count, 7); + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module examples_with_default (input logic a, b, clk, rst, rst1); +default disable iff rst; +property p1; +disable iff (rst1) a |=> b; +endproperty +// Disable condition is rst1 - explicitly specified within a1 +a1 : assert property (@(posedge clk) disable iff (rst1) a |=> b); +// Disable condition is rst1 - explicitly specified within p1 +a2 : assert property (@(posedge clk) p1); +// Disable condition is rst - no explicit specification, inferred from +// default disable iff declaration +a3 : assert property (@(posedge clk) a |=> b); +// Disable condition is 1'b0. This is the only way to +// cancel the effect of default disable. +a4 : assert property (@(posedge clk) disable iff (1'b0) a |=> b); +endmodule + +module examples_without_default (input logic a, b, clk, rst); +property p2; +disable iff (rst) a |=> b; +endproperty +// Disable condition is rst - explicitly specified within a5 +a5 : assert property (@(posedge clk) disable iff (rst) a |=> b); +// Disable condition is rst - explicitly specified within p2 +a6 : assert property (@ (posedge clk) p2); +// No disable condition +a7 : assert property (@ (posedge clk) a |=> b); +endmodule + +module examples_with_default_count(input bit clk, input int cyc); + int explicit_assert_false; + int explicit_property_false; + int inferred_default_false; + + default disable iff (cyc < 3); + + property p1; + disable iff (cyc < 7) 0; + endproperty + + // Disable condition is explicit in the assertion. + assert property (@(posedge clk) disable iff (cyc < 7) 0) + else explicit_assert_false++; + + // Disable condition is explicit in the property. + assert property (@(posedge clk) p1) + else explicit_property_false++; + + // Disable condition is inferred from the default. + assert property (@(posedge clk) 0) + else inferred_default_false++; + +endmodule + +module examples_without_default_count(input bit clk, input int cyc); + int explicit_assert_false; + int explicit_property_false; + + property p2; + disable iff (cyc < 3) 0; + endproperty + + // Disable condition is explicit in the assertion. + assert property (@(posedge clk) disable iff (cyc < 3) 0) + else explicit_assert_false++; + + // Disable condition is explicit in the property. + assert property (@(posedge clk) p2) + else explicit_property_false++; + +endmodule + +module m_override(input bit clk, input int cyc); + bit rst2; + int false_count; + + always_comb rst2 = (cyc < 5); + default disable iff (rst2); + + assert property (@(posedge clk) 0) + else false_count++; + + generate + begin : g_inherit_from_module + int false_count; + assert property (@(posedge clk) 0) + else false_count++; + end + endgenerate +endmodule + +interface if_scope(input bit clk, input int cyc); + bit rst_if; + int false_count; + + always_comb rst_if = (cyc < 6); + default disable iff (rst_if); + + assert property (@(posedge clk) 0) + else false_count++; + + generate + begin : g_inherit + int false_count; + assert property (@(posedge clk) 0) + else false_count++; + end + endgenerate +endinterface + +program p_scope(input bit clk, input int cyc, + output int false_count); + default disable iff (cyc < 8); + + assert property (@(posedge clk) 0) + else false_count++; +endprogram From 748e48f88144e527f29d5f65385c1c29acbf59d3 Mon Sep 17 00:00:00 2001 From: Nick Brereton <85175726+nbstrike@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:41:56 -0400 Subject: [PATCH 18/89] Fix s_eventually in parameterized interfaces (#7741) --- src/V3Assert.cpp | 2 ++ .../t/t_property_s_eventually_iface_param.py | 18 ++++++++++ .../t/t_property_s_eventually_iface_param.v | 36 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100755 test_regress/t/t_property_s_eventually_iface_param.py create mode 100644 test_regress/t/t_property_s_eventually_iface_param.v diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index e785973f1..160121c0b 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -1080,10 +1080,12 @@ class AssertVisitor final : public VNVisitor { VL_RESTORER(m_modPastNum); VL_RESTORER(m_modStrobeNum); VL_RESTORER(m_modExpr2Sen2DelayedAlwaysp); + VL_RESTORER(m_finalp); m_modp = nodep; m_modPastNum = 0; m_modStrobeNum = 0; m_modExpr2Sen2DelayedAlwaysp.clear(); + m_finalp = nullptr; iterateChildren(nodep); } void visit(AstNodeProcedure* nodep) override { diff --git a/test_regress/t/t_property_s_eventually_iface_param.py b/test_regress/t/t_property_s_eventually_iface_param.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface_param.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_property_s_eventually_iface_param.v b/test_regress/t/t_property_s_eventually_iface_param.v new file mode 100644 index 000000000..8d44f6373 --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface_param.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +interface iface_if #(parameter int W = 8) (input bit clk); + logic [W-1:0] sig = 0; + int passed = 0; + assert property (@(posedge clk) s_eventually (sig == 1)) passed++; +endinterface + +module t; + bit clk = 0; + initial forever #1 clk = ~clk; + int cyc = 0; + + // Two distinct specializations: V3Param clones the interface into two + // modules, each with its own s_eventually tracking. The generated final + // block must stay per-module. + iface_if #(.W(4)) a(.clk(clk)); + iface_if #(.W(8)) b(.clk(clk)); + + always @(posedge clk) begin + ++cyc; + if (cyc == 2) begin + a.sig <= 1; + b.sig <= 1; + end + if (cyc == 5) begin + if (a.passed == 0 || b.passed == 0) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule From 5831cc8d4667dc90bf4918270df9ef48ad01fee3 Mon Sep 17 00:00:00 2001 From: Marco Bartoli Date: Fri, 12 Jun 2026 16:42:32 +0200 Subject: [PATCH 19/89] Fix timed nested fork block with disable (#6720) (#7743) Fixes #6720. --- src/V3LinkJump.cpp | 3 + test_regress/t/t_disable_fork_nested.out | 3 + test_regress/t/t_disable_fork_nested.py | 18 +++++ test_regress/t/t_disable_fork_nested.v | 86 ++++++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 test_regress/t/t_disable_fork_nested.out create mode 100755 test_regress/t/t_disable_fork_nested.py create mode 100644 test_regress/t/t_disable_fork_nested.v diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 21e3cdad9..70115033f 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -262,6 +262,9 @@ class LinkJumpVisitor final : public VNVisitor { if (it != m_beginDisableBegins.end()) return it->second; AstBegin* const beginBodyp = new AstBegin{fl, "", nullptr, false}; + // Disable-by-name rewrites kill this detached block-body process, so mark it as process + // backed to ensure fork/join kill-accounting hooks are always emitted. + beginBodyp->setNeedProcess(); if (beginp->stmtsp()) beginBodyp->addStmtsp(beginp->stmtsp()->unlinkFrBackWithNext()); AstFork* const forkp = new AstFork{fl, VJoinType::JOIN}; diff --git a/test_regress/t/t_disable_fork_nested.out b/test_regress/t/t_disable_fork_nested.out new file mode 100644 index 000000000..dce504813 --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.out @@ -0,0 +1,3 @@ +Fast clock (200ns half-period): o_counter=7 +Slow clock (5400ns half-period): o_counter=3 +*-* All Finished *-* diff --git a/test_regress/t/t_disable_fork_nested.py b/test_regress/t/t_disable_fork_nested.py new file mode 100755 index 000000000..b716266af --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary", "-Wno-ZERODLY"]) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_disable_fork_nested.v b/test_regress/t/t_disable_fork_nested.v new file mode 100644 index 000000000..ad6a4d51e --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.v @@ -0,0 +1,86 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Clock-period detector exercising a named-block `disable` of a block whose +// body contains a nested fork..join. Disabling such a block from a sibling +// process must release the enclosing fork..join so the surrounding `always` +// block keeps iterating. + +module disable_fork ( + input logic i_clk, + output logic [2:0] o_counter +); + time delay1 = 500ns; // min period + time delay2 = 3333ns; // max period + + logic clk_re = 1'b0; // rising edge of the clock + logic [2:0] counter = 3'b000; + + always begin + fork + begin : check1 + #delay1; + #1 disable check2; + fork + begin : check3 + #(delay2 - delay1); + clk_re <= 1'b0; + #1 disable check4; + if (counter < 3'b111) counter <= counter + 3'b001; + end + begin : check4 + @(posedge i_clk); + clk_re <= 1'b1; + counter <= 3'b000; + #1 disable check3; + end + join + end + begin : check2 + @(posedge i_clk); + clk_re <= 1'b0; + #1 disable check1; + if (counter < 3'b111) counter <= counter + 3'b001; + end + join + end + + assign o_counter = counter; +endmodule + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk; + logic [2:0] counter; + + task clk_cycle(input time half_period); + clk = 1'b1; + #half_period; + clk = 1'b0; + #half_period; + endtask : clk_cycle + + initial begin + // Fast clock (period below delay1): every edge arrives before the + // min-period timeout, so the counter saturates at its max. + repeat (100) clk_cycle(200ns); + $display("Fast clock (200ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h7); + // Slow clock (period above delay2): the nested fork path runs, which + // only works if disabling check1 releases the inner fork..join. + repeat (100) clk_cycle(5400ns); + $display("Slow clock (5400ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h3); + $write("*-* All Finished *-*\n"); + $finish; + end + + disable_fork a_inst(.i_clk(clk), .o_counter(counter)); +endmodule From e03fa6c7839ca604fba57942d48363a26eeb7f00 Mon Sep 17 00:00:00 2001 From: Matthew Ballance Date: Fri, 12 Jun 2026 08:40:48 -0700 Subject: [PATCH 20/89] Support covergroup runtime model Phase A1 (#7728) --- include/verilated_cov_model.h | 63 ++++ include/verilated_covergroup.cpp | 78 +++++ include/verilated_covergroup.h | 144 +++++++++ src/V3AstAttr.h | 13 + src/V3Covergroup.cpp | 279 +++++++++++++++--- src/V3EmitCHeaders.cpp | 1 + src/V3EmitCModel.cpp | 1 + src/V3Global.cpp | 1 + test_regress/t/t_covergroup_array_bins.out | 14 +- test_regress/t/t_covergroup_cross.out | 19 ++ test_regress/t/t_covergroup_cross.v | 36 +++ test_regress/t/t_covergroup_default_bins.out | 6 +- test_regress/t/t_covergroup_default_bins.v | 12 +- test_regress/t/t_covergroup_iff.out | 6 +- test_regress/t/t_covergroup_iff.v | 2 +- test_regress/t/t_covergroup_ignore_bins.out | 6 +- test_regress/t/t_covergroup_illegal_bins.out | 4 +- test_regress/t/t_covergroup_trans.out | 2 + test_regress/t/t_covergroup_trans.v | 16 + .../t/t_vlcov_covergroup.annotate.out | 86 +++++- 20 files changed, 720 insertions(+), 69 deletions(-) create mode 100644 include/verilated_cov_model.h create mode 100644 include/verilated_covergroup.cpp create mode 100644 include/verilated_covergroup.h diff --git a/include/verilated_cov_model.h b/include/verilated_cov_model.h new file mode 100644 index 000000000..519fd214c --- /dev/null +++ b/include/verilated_cov_model.h @@ -0,0 +1,63 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage model interfaces +/// +/// Defines interface classes to runtime covergroup coverage-collection classes. +/// These are used to query coverage achievement at runtime, and (future) +/// when writing coverage to the coverage database. +/// +//============================================================================= + +#ifndef VERILATOR_VERILATED_COV_MODEL_H_ +#define VERILATOR_VERILATED_COV_MODEL_H_ + +#include "verilatedos.h" + +#include +#include + +// Per-bin classification. A bin's kind is which set it lives in (structural), +// not a per-bin field. Only Normal feeds coverage(); the rest are recorded. +// Enumerators are 'KIND_'-prefixed because the bare LRM terms collide with +// macros (e.g. IGNORE), which the preprocessor would expand. +enum class VlCovBinKind : uint8_t { + KIND_NORMAL = 0, // Base coverage-collecting bin + KIND_DEFAULT = 1, // Bin declared with 'default' range (which is excluded per LRM) + KIND_IGNORE = 2, // Ignore bin + KIND_ILLEGAL = 3 // Illegal bin +}; + +//============================================================================= +// VlCoverpointIf +/// Read-side view of a coverpoint. The writer queries bins by index; the +/// implementor computes names/kinds on demand. Bounded bin count, so random +/// access by index is the primary usage. + +class VlCoverpointIf VL_NOT_FINAL { +public: + // CONSTRUCTORS + virtual ~VlCoverpointIf() = default; + + // METHODS + // All bins, across every set; index range [0, binCount()) + virtual int binCount() const = 0; + // Bin name in declaration order (e.g. "myBin" or "b[3]") + virtual std::string binName(int i) const = 0; + virtual VlCovBinKind binKind(int i) const = 0; + // Bins covered / effective total (Normal set only) for the coverage calc + virtual void coverageParts(double& covered, double& total) const = 0; +}; + +#endif // Guard diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp new file mode 100644 index 000000000..6dc7006f1 --- /dev/null +++ b/include/verilated_covergroup.cpp @@ -0,0 +1,78 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage collection runtime implementation +/// +/// Compiled and linked when "verilator --coverage" is used with covergroups. +/// +//============================================================================= + +#include "verilatedos.h" + +#include "verilated_covergroup.h" + +#include "verilated_cov.h" + +void VlCoverpoint::init(const char* hier, uint32_t atLeast, int nBins) { + m_hier = hier; + m_atLeast = atLeast; + m_total = nBins; + m_counts.assign(nBins, 0); +} + +void VlCoverpoint::addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name, + const char* file, int line, int col) { + m_namers.emplace_back(set, count, m_nextBase, naming, name, file, line, col); + m_nextBase += count; + if (set == VlCovBinKind::KIND_NORMAL) m_normal += count; +} + +const VlCovNamer& VlCoverpoint::namerFor(int i) const { + // Namers are appended in ascending, contiguous index order covering [0, m_total), + // and i is always a valid bin index, so the matching namer always exists. + for (const VlCovNamer& nm : m_namers) { + if (i < nm.base() + nm.count()) return nm; + } + VL_UNREACHABLE; +} + +std::string VlCoverpoint::binName(int i) const { + const VlCovNamer& nm = namerFor(i); + std::string name = nm.name(); + if (nm.naming() == VlCovBinNaming::Array) name += '[' + std::to_string(i - nm.base()) + ']'; + return name; +} + +void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page) { + for (int i = 0; i < binCount(); ++i) { + const VlCovNamer& nm = namerFor(i); + const VlCovBinKind kind = binKind(i); + const std::string binp = binName(i); + const std::string full = m_hier + "." + binp; + const std::string lineStr = std::to_string(nm.line()); + const std::string colStr = std::to_string(nm.col()); + if (kind == VlCovBinKind::KIND_NORMAL) { + VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename", + nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", + binp.c_str()); + } else { + const char* const binType = kind == VlCovBinKind::KIND_IGNORE ? "ignore" + : kind == VlCovBinKind::KIND_ILLEGAL ? "illegal" + : "default"; + VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename", + nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", + binp.c_str(), "bin_type", binType); + } + } +} diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h new file mode 100644 index 000000000..1ffc98ff7 --- /dev/null +++ b/include/verilated_covergroup.h @@ -0,0 +1,144 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2024-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage collection runtime +/// +/// VlCoverpoint owns per-instance bin-count storage for one coverpoint, +/// computes coverage, builds bin names on demand, and registers bins with the +/// coverage database. It implements the VlCoverpointIf read interface. +/// +/// Generated covergroup code holds one VlCoverpoint per coverpoint, configures +/// it in the constructor (init + add*Namer), increments bins from sample(), +/// and registers via registerBins(). +/// +//============================================================================= + +#ifndef VERILATOR_VERILATED_COVERGROUP_H_ +#define VERILATOR_VERILATED_COVERGROUP_H_ + +#include "verilatedos.h" + +#include "verilated_cov_model.h" + +#include +#include +#include + +class VerilatedCovContext; + +// How a namer builds the names of the bins it covers. +enum class VlCovBinNaming : uint8_t { + Single, // "" one bin + Array, // "[i]" bins b[N] value array +}; + +// Specifies the naming scheme for a range of bins, allowing the +// specific name to be computed on-demand. +// All name strings are borrowed literals from the generated code. +class VlCovNamer final { + // MEMBERS + VlCovBinKind m_set; // which set the bins belong to + int m_count; // bins this namer covers (1 for Single) + int m_base; // first bin index (declaration order), assigned on append + VlCovBinNaming m_naming; // how bin names are built + const char* m_name; // bin name (Single) or array base name (Array) + const char* m_file; // declaration file + int m_line; // declaration line + int m_col; // declaration column + +public: + // CONSTRUCTORS + VlCovNamer(VlCovBinKind set, int count, int base, VlCovBinNaming naming, const char* name, + const char* file, int line, int col) + : m_set{set} + , m_count{count} + , m_base{base} + , m_naming{naming} + , m_name{name} + , m_file{file} + , m_line{line} + , m_col{col} {} + + // METHODS + VlCovBinKind set() const { return m_set; } + int count() const { return m_count; } + int base() const { return m_base; } + VlCovBinNaming naming() const { return m_naming; } + const char* name() const { return m_name; } + const char* file() const { return m_file; } + int line() const { return m_line; } + int col() const { return m_col; } +}; + +//============================================================================= +// VlCoverpoint +/// Per-instance coverpoint runtime. Bins are stored in declaration order; a +/// bin's set/name come from the owning namer. coverage() is computed on demand +/// by scanning bin counts, keeping the sample() hot path a plain counter bump. + +class VlCoverpoint final : public VlCoverpointIf { + // MEMBERS + std::string m_hier; // "covergroup.coverpoint" + uint32_t m_atLeast = 1; // option.at_least (coverpoint-wide) + int m_total = 0; // bins across all sets + int m_normal = 0; // Normal bins (coverage denominator) + int m_nextBase = 0; // running append cursor + std::vector m_counts; // [m_total], one per bin + std::vector m_namers; // appended in declaration order + + // PRIVATE METHODS + const VlCovNamer& namerFor(int i) const; // obtain the bin-specific name producer + void addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name, + const char* file, int line, int col); + +public: + // CONSTRUCTORS + VlCoverpoint() = default; + + // METHODS + // ---- configuration (from generated constructor) ---- + void init(const char* hier, uint32_t atLeast, int nBins); + void addSingleNamer(VlCovBinKind set, const char* name, const char* file, int line, int col) { + addNamer(set, 1, VlCovBinNaming::Single, name, file, line, col); + } + void addArrayNamer(VlCovBinKind set, int count, const char* name, const char* file, int line, + int col) { + addNamer(set, count, VlCovBinNaming::Array, name, file, line, col); + } + void registerBins(VerilatedCovContext* covcontextp, const char* page); + + // ---- hot path (from generated sample()) ---- + void incrementBin(int i) { ++m_counts[i]; } // Normal bin: count only + void recordHit(int i) { ++m_counts[i]; } // Ignore/Illegal/Default: count only + + // ---- VlCoverpointIf ---- + int binCount() const override { return m_total; } + std::string binName(int i) const override; + VlCovBinKind binKind(int i) const override { return namerFor(i).set(); } + void coverageParts(double& covered, double& total) const override { + // Count Normal bins that reached option.at_least on demand, so the hot + // path (incrementBin) stays a plain counter bump. + int numCovered = 0; + for (const VlCovNamer& nm : m_namers) { + if (nm.set() != VlCovBinKind::KIND_NORMAL) continue; + for (int i = nm.base(); i < nm.base() + nm.count(); ++i) { + if (m_counts[i] >= m_atLeast) ++numCovered; + } + } + covered = numCovered; + total = m_normal; + } +}; + +#endif // Guard diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index c12627886..dd1754a9d 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -1191,6 +1191,19 @@ public: = {"user", "array", "auto", "ignore", "illegal", "default", "wildcard", "transition"}; return names[m_e]; } + // VlCovBinKind enumerator naming the bin's set + const char* binSetEnum() const { + switch (m_e) { + case BINS_IGNORE: return "VlCovBinKind::KIND_IGNORE"; + case BINS_ILLEGAL: return "VlCovBinKind::KIND_ILLEGAL"; + case BINS_DEFAULT: return "VlCovBinKind::KIND_DEFAULT"; + default: return "VlCovBinKind::KIND_NORMAL"; + } + } + // Normal bins (feed coverage) are anything but ignore/illegal/default + bool binIsNormal() const { + return m_e != BINS_IGNORE && m_e != BINS_ILLEGAL && m_e != BINS_DEFAULT; + } }; constexpr bool operator==(const VCoverBinsType& lhs, VCoverBinsType::en rhs) { return lhs.m_e == rhs; diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 727b5689c..0d43e9f60 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -68,6 +68,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { , crossBins{cb} {} }; std::vector m_binInfos; // All bins in current covergroup + std::set m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy) + std::vector m_convCpVars; // VlCoverpoint members of converted coverpoints + AstCDType* m_vlCoverpointDTypep = nullptr; // Shared "VlCoverpoint" C++ member type VMemberMap m_memberMap; // Member names cached for fast lookup @@ -88,6 +91,17 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Clear bin info for this covergroup (deleting any orphaned cross pseudo-bins) clearBinInfos(); + // Coverpoints referenced by a cross keep the legacy per-bin-member path (the cross + // reads those members); collect their names before they are consumed by the cross. + m_crossedCpNames.clear(); + m_convCpVars.clear(); + for (AstCoverCross* crossp : m_coverCrosses) { + for (AstNode* itemp = crossp->itemsp(); itemp; itemp = itemp->nextp()) { + if (const AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef)) + m_crossedCpNames.insert(refp->name()); + } + } + // For each coverpoint, generate sampling code for (AstCoverpoint* cpp : m_coverpoints) generateCoverpointCode(cpp); @@ -504,6 +518,13 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Create implicit automatic bins if no regular bins exist createImplicitAutoBins(coverpointp, exprp, autoBinMax); + // Eligible coverpoints route through the VlCoverpoint runtime; the rest (cross-fed or + // transition-bearing) keep the legacy per-bin-member path below. + if (coverpointConvertible(coverpointp)) { + generateConvertedCoverpoint(coverpointp, exprp, atLeastValue); + return; + } + // Generate member variables and matching code for each bin // Process in two passes: first non-default bins, then default bins std::vector defaultBins; @@ -595,47 +616,179 @@ class FunctionalCoverageVisitor final : public VNVisitor { UINFO(4, " Successfully added if statement for bin: " << binp->name()); } + // Build the condition under which a default bin matches: NOT(OR of all normal bins). + AstNodeExpr* buildDefaultCondition(AstCoverpoint* coverpointp, AstNodeExpr* exprp, + FileLine* fl) { + AstNodeExpr* anyBinMatchp = nullptr; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT + || cbinp->binsType() == VCoverBinsType::BINS_IGNORE + || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) + continue; + AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); + UASSERT_OBJ(binCondp, cbinp, + "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); + anyBinMatchp = anyBinMatchp ? new AstOr{fl, anyBinMatchp, binCondp} : binCondp; + } + return anyBinMatchp ? static_cast(new AstNot{fl, anyBinMatchp}) + : static_cast(new AstConst{fl, AstConst::BitTrue{}}); + } + + //==================================================================== + // VlCoverpoint conversion (eligible coverpoints) + + // True if a coverpoint routes through the VlCoverpoint runtime. Cross-fed coverpoints + // (the cross reads their per-bin members) and transition-bearing ones stay legacy. + bool coverpointConvertible(AstCoverpoint* coverpointp) { + if (m_crossedCpNames.count(coverpointp->name())) return false; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + if (VN_AS(binp, CoverBin)->transp()) return false; + } + return true; + } + + // A 'this->m_member' reference for embedding in an AstCStmt + AstVarRef* memberRef(FileLine* fl, AstVar* varp) { + AstVarRef* const refp = new AstVarRef{fl, varp, VAccess::READ}; + refp->selfPointer(VSelfPointerText{VSelfPointerText::This{}}); + return refp; + } + + // Individual equality targets of an array bin (bins b[N] = {values/ranges}), in order. + std::vector extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp) { + std::vector values; + for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { + if (AstInsideRange* const irp = VN_CAST(rangep, InsideRange)) { + AstConst* const minp = VN_CAST(V3Const::constifyEdit(irp->lhsp()), Const); + AstConst* const maxp = VN_CAST(V3Const::constifyEdit(irp->rhsp()), Const); + if (!minp || !maxp) { + arrayBinp->v3error("Non-constant expression in array bins range; " + "range bounds must be constants"); + return values; + } + for (int val = minp->toSInt(); val <= maxp->toSInt(); ++val) + values.push_back(new AstConst{irp->fileline(), AstConst::WidthedValue{}, + static_cast(exprp->width()), + static_cast(val)}); + } else { + values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); + } + } + return values; + } + + // Emit a 'this->m_cp.addSingleNamer/addArrayNamer(...)' statement for one bin + AstCStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count) { + FileLine* const fl = binp->fileline(); + AstCStmt* const cs = new AstCStmt{fl}; + cs->add(memberRef(fl, cpVarp)); + const std::string loc = "\"" + std::string{fl->filename()} + "\", " + + std::to_string(fl->lineno()) + ", " + + std::to_string(fl->firstColumn()) + ");"; + if (count < 0) { // single bin + cs->add(".addSingleNamer(" + std::string{binp->binsType().binSetEnum()} + ", \"" + + binp->name() + "\", " + loc); + } else { // value array bin + cs->add(".addArrayNamer(" + std::string{binp->binsType().binSetEnum()} + ", " + + std::to_string(count) + ", \"" + binp->name() + "\", " + loc); + } + return cs; + } + + // Emit 'if (iff && cond) m_cp.incrementBin(idx);' (or recordHit, + illegal action) in sample() + void emitConvHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, int idx, + AstNodeExpr* condp) { + FileLine* const fl = binp->fileline(); + AstCStmt* const hitp = new AstCStmt{fl}; + hitp->add(memberRef(fl, cpVarp)); + hitp->add((binp->binsType().binIsNormal() ? ".incrementBin(" : ".recordHit(") + + std::to_string(idx) + ");"); + AstNode* actionp = hitp; + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + actionp->addNext(makeIllegalBinAction(fl, "Illegal bin " + binp->prettyNameQ() + + " hit in coverpoint " + + coverpointp->prettyNameQ())); + } + AstNodeExpr* const guardedp = applyCoverpointIffCondition(coverpointp, fl, condp); + UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set in converted coverpoint"); + m_sampleFuncp->addStmtsp(new AstIf{fl, guardedp, actionp, nullptr}); + } + + // Route an eligible coverpoint through a VlCoverpoint member: emit the member, its + // sample() increments, the constructor configuration (init + namers), and registration. + void generateConvertedCoverpoint(AstCoverpoint* coverpointp, AstNodeExpr* exprp, + int atLeastValue) { + FileLine* const fl = coverpointp->fileline(); + UINFO(4, " Converting coverpoint to VlCoverpoint: " << coverpointp->name()); + + if (!m_vlCoverpointDTypep) { + m_vlCoverpointDTypep = new AstCDType{fl, "VlCoverpoint"}; + v3Global.rootp()->typeTablep()->addTypesp(m_vlCoverpointDTypep); + } + AstVar* const cpVarp = new AstVar{fl, VVarType::MEMBER, "__Vcp_" + coverpointp->name(), + m_vlCoverpointDTypep}; + cpVarp->isStatic(false); + m_covergroupp->addMembersp(cpVarp); + m_convCpVars.push_back(cpVarp); + + // Walk bins (non-default, then default), assigning sequential indices that match the + // namer append order; emit sample increments and collect namer statements. + std::vector namerStmts; + std::vector defaultBins; + int idx = 0; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT) { + defaultBins.push_back(cbinp); + continue; + } + if (cbinp->isArray()) { // value array: bins b[N] = {...} -> b[0]..b[N-1] + std::vector values = extractArrayValues(cbinp, exprp); + namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast(values.size()))); + for (AstNodeExpr* valuep : values) { + emitConvHitIf(coverpointp, cbinp, cpVarp, idx++, + new AstEq{cbinp->fileline(), exprp->cloneTree(false), valuep}); + } + } else { + namerStmts.push_back(makeNamer(cpVarp, cbinp, -1)); + // buildBinCondition is null for 'ignore_bins = default' (no ranges); the bin + // still gets a reserved slot (recorded, never incremented). + if (AstNodeExpr* const condp = buildBinCondition(cbinp, exprp)) + emitConvHitIf(coverpointp, cbinp, cpVarp, idx, condp); + ++idx; + } + } + for (AstCoverBin* const defBinp : defaultBins) { + namerStmts.push_back(makeNamer(cpVarp, defBinp, -1)); + emitConvHitIf(coverpointp, defBinp, cpVarp, idx++, + buildDefaultCondition(coverpointp, exprp, defBinp->fileline())); + } + + // Constructor: init (allocates), namers, then registration (under --coverage) + const std::string hier = m_covergroupp->name() + "." + coverpointp->name(); + AstCStmt* const initp = new AstCStmt{fl}; + initp->add(memberRef(fl, cpVarp)); + initp->add(".init(\"" + hier + "\", " + std::to_string(atLeastValue) + ", " + + std::to_string(idx) + ");"); + m_constructorp->addStmtsp(initp); + for (AstCStmt* const ns : namerStmts) m_constructorp->addStmtsp(ns); + if (v3Global.opt.coverage()) { + AstCStmt* const regp = new AstCStmt{fl}; + regp->add(memberRef(fl, cpVarp)); + regp->add(".registerBins(vlSymsp->_vm_contextp__->coveragep(), \"v_covergroup/" + + m_covergroupp->name() + "\");"); + m_constructorp->addStmtsp(regp); + } + } + // Generate matching code for default bins // Default bins match when value doesn't match any other explicit bin void generateDefaultBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* defBinp, AstNodeExpr* exprp, AstVar* hitVarp) { UINFO(4, " Generating default bin match for: " << defBinp->name()); - // Build OR of all non-default, non-ignore bins - AstNodeExpr* anyBinMatchp = nullptr; - - for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { - AstCoverBin* const cbinp = VN_AS(binp, CoverBin); - - // Skip default, ignore, and illegal bins - if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT - || cbinp->binsType() == VCoverBinsType::BINS_IGNORE - || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - continue; - } - - // Build condition for this bin - AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); - UASSERT_OBJ(binCondp, cbinp, - "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); - - // OR with previous conditions - if (anyBinMatchp) { - anyBinMatchp = new AstOr{defBinp->fileline(), anyBinMatchp, binCondp}; - } else { - anyBinMatchp = binCondp; - } - } - - // Default matches when NO explicit bin matches - AstNodeExpr* defaultCondp = nullptr; - if (anyBinMatchp) { - // NOT (bin1 OR bin2 OR ... OR binN) - defaultCondp = new AstNot{defBinp->fileline(), anyBinMatchp}; - } else { - // No other bins - default always matches (shouldn't happen in practice) - defaultCondp = new AstConst{defBinp->fileline(), AstConst::BitTrue{}}; - } + AstNodeExpr* defaultCondp = buildDefaultCondition(coverpointp, exprp, defBinp->fileline()); // Apply iff condition if present if (AstNodeExpr* iffp = coverpointp->iffp()) { @@ -1320,15 +1473,50 @@ class FunctionalCoverageVisitor final : public VNVisitor { void generateCoverageMethodBody(AstFunc* funcp) { FileLine* const fl = funcp->fileline(); + AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - // Count total bins (excluding ignore_bins and illegal_bins) + // Converted coverpoints hold their bins in VlCoverpoint. Combine their contributions + // (via coverageParts) with any remaining legacy cross/cross-fed bins as the same flat + // covered/total ratio the all-legacy path below computes. Normal bins only: ignore, + // illegal, and default are excluded (LRM 19.5). + if (!m_convCpVars.empty()) { + AstCStmt* const headp = new AstCStmt{fl}; + headp->add("double __Vcov = 0.0; double __Vtot = 0.0;"); + funcp->addStmtsp(headp); + for (AstVar* const cpVarp : m_convCpVars) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("{ double __Vc = 0.0; double __Vt = 0.0; "); + cs->add(memberRef(fl, cpVarp)); + cs->add(".coverageParts(__Vc, __Vt); __Vcov += __Vc; __Vtot += __Vt; }"); + funcp->addStmtsp(cs); + } + int legacyRegular = 0; + for (const BinInfo& bi : m_binInfos) { + if (!bi.binp->binsType().binIsNormal()) continue; + ++legacyRegular; + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("if ("); + cs->add(memberRef(fl, bi.varp)); + cs->add(" >= " + std::to_string(bi.atLeast) + ") __Vcov += 1.0;"); + funcp->addStmtsp(cs); + } + if (legacyRegular) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("__Vtot += " + std::to_string(legacyRegular) + ".0;"); + funcp->addStmtsp(cs); + } + AstCStmt* const retp = new AstCStmt{fl}; + retp->add(new AstVarRef{fl, returnVarp, VAccess::WRITE}); + retp->add(" = (__Vtot != 0.0) ? (100.0 * __Vcov / __Vtot) : 100.0;"); + funcp->addStmtsp(retp); + return; + } + + // Count total bins (Normal only: excludes ignore/illegal/default) int totalBins = 0; for (const BinInfo& bi : m_binInfos) { UINFO(6, " Bin: " << bi.binp->name() << " type=" << bi.binp->binsType().ascii()); - if (bi.binp->binsType() != VCoverBinsType::BINS_IGNORE - && bi.binp->binsType() != VCoverBinsType::BINS_ILLEGAL) { - totalBins++; - } + if (bi.binp->binsType().binIsNormal()) totalBins++; } UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size()); @@ -1337,7 +1525,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { // No coverage to compute - return 100%. // Any parser-generated initialization of returnVar is overridden by our assignment. UINFO(4, " Empty covergroup, returning 100.0"); - AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, new AstConst{fl, AstConst::RealDouble{}, 100.0}}); UINFO(4, " Added assignment to return 100.0"); @@ -1356,11 +1543,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // For each regular bin, if count > 0, increment covered_count for (const BinInfo& bi : m_binInfos) { - // Skip ignore_bins and illegal_bins in coverage calculation - if (bi.binp->binsType() == VCoverBinsType::BINS_IGNORE - || bi.binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - continue; - } + // Skip ignore/illegal/default bins in coverage calculation + if (!bi.binp->binsType().binIsNormal()) continue; // if (bin_count >= at_least) covered_count++; AstIf* ifp = new AstIf{ @@ -1375,9 +1559,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { funcp->addStmtsp(ifp); } - // Find the return variable - AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - // Calculate coverage: (covered_count / total_bins) * 100.0 // return_var = (double)covered_count / (double)total_bins * 100.0 diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 1746139b8..524a39222 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -689,6 +689,7 @@ class EmitCHeader final : public EmitCConstInit { if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n"); if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n"); if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n"); + if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n"); if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n"); if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n"); if (v3Global.usesForce()) puts("#include \"verilated_force.h\"\n"); diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 211eea99f..6ddd53e77 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -70,6 +70,7 @@ class EmitCModel final : public EmitCFunc { if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n"); if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n"); if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n"); + if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n"); if (v3Global.dpi()) puts("#include \"svdpi.h\"\n"); // Declare foreign instances up front to make C++ happy diff --git a/src/V3Global.cpp b/src/V3Global.cpp index e0b6f42ea..2e7c25fc7 100644 --- a/src/V3Global.cpp +++ b/src/V3Global.cpp @@ -245,6 +245,7 @@ std::vector V3Global::verilatedCppFiles() { if (v3Global.opt.vpi()) result.emplace_back("verilated_vpi.cpp"); if (v3Global.opt.savable()) result.emplace_back("verilated_save.cpp"); if (v3Global.opt.coverage()) result.emplace_back("verilated_cov.cpp"); + if (v3Global.opt.coverage()) result.emplace_back("verilated_covergroup.cpp"); for (const string& base : v3Global.opt.traceSourceBases()) result.emplace_back(base + "_c.cpp"); if (v3Global.usesProbDist()) result.emplace_back("verilated_probdist.cpp"); diff --git a/test_regress/t/t_covergroup_array_bins.out b/test_regress/t/t_covergroup_array_bins.out index 4c0767563..ccbbbc297 100644 --- a/test_regress/t/t_covergroup_array_bins.out +++ b/test_regress/t/t_covergroup_array_bins.out @@ -1,4 +1,12 @@ cg.data.grouped: 2 -cg.data.values: 3 -cg2.cp.range_arr: 3 -cg3.cp.range_sized: 3 +cg.data.values[0]: 1 +cg.data.values[1]: 1 +cg.data.values[2]: 1 +cg2.cp.range_arr[0]: 1 +cg2.cp.range_arr[1]: 1 +cg2.cp.range_arr[2]: 1 +cg2.cp.range_arr[3]: 0 +cg3.cp.range_sized[0]: 1 +cg3.cp.range_sized[1]: 1 +cg3.cp.range_sized[2]: 1 +cg3.cp.range_sized[3]: 0 diff --git a/test_regress/t/t_covergroup_cross.out b/test_regress/t/t_covergroup_cross.out index 84d9104d6..a7311da80 100644 --- a/test_regress/t/t_covergroup_cross.out +++ b/test_regress/t/t_covergroup_cross.out @@ -65,6 +65,15 @@ cg_at_least.cp_addr.addr0: 1 cg_at_least.cp_addr.addr1: 1 cg_at_least.cp_cmd.read: 1 cg_at_least.cp_cmd.write: 1 +cg_def_cross.axc.a0_x_read [cross]: 1 +cg_def_cross.axc.a0_x_write [cross]: 0 +cg_def_cross.axc.a1_x_read [cross]: 0 +cg_def_cross.axc.a1_x_write [cross]: 0 +cg_def_cross.cp_a.a0: 1 +cg_def_cross.cp_a.a1: 0 +cg_def_cross.cp_a.ad: 1 +cg_def_cross.cp_c.read: 1 +cg_def_cross.cp_c.write: 1 cg_goal.addr_cmd_goal.addr0_x_read [cross]: 1 cg_goal.addr_cmd_goal.addr0_x_write [cross]: 0 cg_goal.addr_cmd_goal.addr1_x_read [cross]: 0 @@ -82,6 +91,16 @@ cg_ignore.cross_ab.a0_x_read [cross]: 1 cg_ignore.cross_ab.a0_x_write [cross]: 1 cg_ignore.cross_ab.a1_x_read [cross]: 1 cg_ignore.cross_ab.a1_x_write [cross]: 1 +cg_mixed.ab.addr0_x_read [cross]: 1 +cg_mixed.ab.addr0_x_write [cross]: 1 +cg_mixed.ab.addr1_x_read [cross]: 1 +cg_mixed.ab.addr1_x_write [cross]: 1 +cg_mixed.cp_addr.addr0: 2 +cg_mixed.cp_addr.addr1: 2 +cg_mixed.cp_cmd.read: 2 +cg_mixed.cp_cmd.write: 2 +cg_mixed.cp_solo.debug: 2 +cg_mixed.cp_solo.normal: 2 cg_range.addr_cmd_range.hi_range_x_read [cross]: 1 cg_range.addr_cmd_range.hi_range_x_write [cross]: 1 cg_range.addr_cmd_range.lo_range_x_read [cross]: 1 diff --git a/test_regress/t/t_covergroup_cross.v b/test_regress/t/t_covergroup_cross.v index 6245baa35..e86f179a4 100644 --- a/test_regress/t/t_covergroup_cross.v +++ b/test_regress/t/t_covergroup_cross.v @@ -99,6 +99,23 @@ module t; cross cp_a, cp_c; // no label: reported under the default cross name endgroup + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted + // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + covergroup cg_mixed; + cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed + ab: cross cp_addr, cp_cmd; + endgroup + + // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the + // legacy default-bin codegen path that converted coverpoints bypass. + covergroup cg_def_cross; + cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} + cp_c: coverpoint cmd {bins read = {0}; bins write = {1};} + axc: cross cp_a, cp_c; + endgroup + cg2 cg2_inst = new; cg_ignore cg_ignore_inst = new; cg_range cg_range_inst = new; @@ -109,6 +126,8 @@ module t; cg_goal cg_goal_inst = new; cg_unsup_cross_opt cg_unsup_cross_opt_inst = new; cg_unnamed_cross cg_unnamed_cross_inst = new; + cg_mixed cg_mixed_inst = new; + cg_def_cross cg_def_cross_inst = new; initial begin // Sample 2-way: hit all 4 combinations @@ -275,6 +294,23 @@ module t; cg_unnamed_cross_inst.sample(); // a1 x write `checkr(cg_unnamed_cross_inst.get_inst_coverage(), 75.0); + // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) + addr = 0; cmd = 0; mode = 0; + cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) + `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 + addr = 0; cmd = 1; mode = 1; + cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write) + addr = 1; cmd = 0; mode = 0; + cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read) + addr = 1; cmd = 1; mode = 1; + cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) + `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 + + // Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff) + mode = 1; + addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read + addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_default_bins.out b/test_regress/t/t_covergroup_default_bins.out index 9e4e7a1c6..e034545b8 100644 --- a/test_regress/t/t_covergroup_default_bins.out +++ b/test_regress/t/t_covergroup_default_bins.out @@ -1,11 +1,11 @@ cg.data.high: 1 cg.data.low: 1 -cg.data.other: 2 -cg2.cp_only_default.all: 4 +cg.data.other [default]: 2 +cg2.cp_only_default.all [default]: 4 cg3.data.bad [ignore]: 1 cg3.data.err [illegal]: 0 cg3.data.normal: 2 -cg3.data.other: 2 +cg3.data.other [default]: 2 cg4.cp_idx.auto_0: 1 cg4.cp_idx.auto_1: 1 cg4.cp_idx.auto_2: 1 diff --git a/test_regress/t/t_covergroup_default_bins.v b/test_regress/t/t_covergroup_default_bins.v index f4ba5148c..c97cf8f66 100644 --- a/test_regress/t/t_covergroup_default_bins.v +++ b/test_regress/t/t_covergroup_default_bins.v @@ -157,17 +157,17 @@ module t; // Sample cg3: verify ignore/illegal bins do not contribute to coverage data = 2; cg3_inst.sample(); // hits normal bin - `checkr(cg3_inst.get_inst_coverage(), 50.0); // 1/2 bins hit (normal) + `checkr(cg3_inst.get_inst_coverage(), 100.0); // 1/1: only 'normal' counts (default/ignore/illegal excluded, LRM 19.5) data = 7; cg3_inst.sample(); // hits normal bin again - `checkr(cg3_inst.get_inst_coverage(), 50.0); // no new bins + `checkr(cg3_inst.get_inst_coverage(), 100.0); // no new normal bins data = 255; - cg3_inst.sample(); // ignore_bins value; Verilator counts it toward default bin - `checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2: Verilator hits 'other' (default) even for ignore_bins + cg3_inst.sample(); // ignore_bins value; recorded but excluded from coverage + `checkr(cg3_inst.get_inst_coverage(), 100.0); // note: do not sample 254 (illegal_bins would cause runtime assertion) data = 100; - cg3_inst.sample(); // hits default (other) bin - `checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2 bins hit + cg3_inst.sample(); // hits default (other) bin; recorded but excluded from coverage + `checkr(cg3_inst.get_inst_coverage(), 100.0); // Sample cg4: auto-bins with one excluded value // idx=2 is in ignore_bins, so auto-bins cover 0, 1, 3 only (3 bins total) diff --git a/test_regress/t/t_covergroup_iff.out b/test_regress/t/t_covergroup_iff.out index 0e28a68cf..69a7711dd 100644 --- a/test_regress/t/t_covergroup_iff.out +++ b/test_regress/t/t_covergroup_iff.out @@ -2,9 +2,11 @@ cg_and.cp_concat.b00: 0 cg_and.cp_concat.b01: 1 cg_and.cp_concat.b10: 0 cg_and.cp_concat.b11: 0 -cg_array_iff.cp.arr: 1 +cg_array_iff.cp.arr[0]: 1 +cg_array_iff.cp.arr[1]: 0 +cg_array_iff.cp.arr[2]: 0 cg_bitw.cp.seven: 1 -cg_default_iff.cp.def: 1 +cg_default_iff.cp.def [default]: 1 cg_default_iff.cp.known: 1 cg_iff.cp_value.disabled_hi: 0 cg_iff.cp_value.disabled_lo: 0 diff --git a/test_regress/t/t_covergroup_iff.v b/test_regress/t/t_covergroup_iff.v index 1816bd327..8bf6e0016 100644 --- a/test_regress/t/t_covergroup_iff.v +++ b/test_regress/t/t_covergroup_iff.v @@ -129,7 +129,7 @@ module t; enable = 1; value = 10; cg2.sample(); // hits 'known' - `checkr(cg2.get_inst_coverage(), 50.0); + `checkr(cg2.get_inst_coverage(), 100.0); // 1/1: only 'known' counts (default excluded, LRM 19.5) value = 99; cg2.sample(); // hits 'def' (default) `checkr(cg2.get_inst_coverage(), 100.0); diff --git a/test_regress/t/t_covergroup_ignore_bins.out b/test_regress/t/t_covergroup_ignore_bins.out index bcd0b72e0..6a1cba188 100644 --- a/test_regress/t/t_covergroup_ignore_bins.out +++ b/test_regress/t/t_covergroup_ignore_bins.out @@ -1,5 +1,7 @@ -cg.data.arr [ignore]: 0 -cg.data.bad [illegal]: 0 +cg.data.arr[0] [ignore]: 0 +cg.data.arr[1] [ignore]: 0 +cg.data.bad[0] [illegal]: 0 +cg.data.bad[1] [illegal]: 0 cg.data.catch_all [ignore]: 0 cg.data.high: 1 cg.data.low: 1 diff --git a/test_regress/t/t_covergroup_illegal_bins.out b/test_regress/t/t_covergroup_illegal_bins.out index d2de75a65..798247e10 100644 --- a/test_regress/t/t_covergroup_illegal_bins.out +++ b/test_regress/t/t_covergroup_illegal_bins.out @@ -2,7 +2,9 @@ cg.data.forbidden [illegal]: 0 cg.data.high: 1 cg.data.low: 1 cg.data.mid: 1 -cg2.cp_arr.bad_arr [illegal]: 0 +cg2.cp_arr.bad_arr[0] [illegal]: 0 +cg2.cp_arr.bad_arr[1] [illegal]: 0 +cg2.cp_arr.bad_arr[2] [illegal]: 0 cg2.cp_arr.ok: 1 cg2.cp_arr.wlib [illegal]: 0 cg2.cp_trans.bad_2step [illegal]: 0 diff --git a/test_regress/t/t_covergroup_trans.out b/test_regress/t/t_covergroup_trans.out index f7621e74a..17e059ede 100644 --- a/test_regress/t/t_covergroup_trans.out +++ b/test_regress/t/t_covergroup_trans.out @@ -7,3 +7,5 @@ cg.cp_trans2.trans2: 1 cg.cp_trans2.trans3: 1 cg.cp_trans3.seq_a: 1 cg.cp_trans3.seq_b: 1 +cg_legacy.cp_mix.tr: 1 +cg_legacy.cp_mix.vals: 1 diff --git a/test_regress/t/t_covergroup_trans.v b/test_regress/t/t_covergroup_trans.v index dea93e6c5..f95561ed2 100644 --- a/test_regress/t/t_covergroup_trans.v +++ b/test_regress/t/t_covergroup_trans.v @@ -39,7 +39,17 @@ module t; } endgroup + // Non-convertible coverpoint (it has a transition bin) that also carries a value-array bin, + // so the legacy array codegen path (which converted coverpoints bypass) is still exercised. + covergroup cg_legacy; + cp_mix: coverpoint state { + bins tr = (0 => 1); + bins vals[] = {2, [4:5]}; // discrete + range elements (both legacy array paths) + } + endgroup + cg cg_inst = new; + cg_legacy cg_legacy_inst = new; initial begin // Drive sequence 0->1->2->3->4 which hits all bins @@ -56,6 +66,12 @@ module t; cg_inst.sample(); // 3=>4: seq_b done `checkr(cg_inst.get_inst_coverage(), 100.0); + // cg_legacy: exercise legacy array codegen (non-convertible coverpoint) + state = 0; cg_legacy_inst.sample(); + state = 1; cg_legacy_inst.sample(); // 0=>1: tr + state = 2; cg_legacy_inst.sample(); // vals + state = 3; cg_legacy_inst.sample(); // vals + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_vlcov_covergroup.annotate.out b/test_regress/t/t_vlcov_covergroup.annotate.out index 436973cb7..b8616ad2d 100644 --- a/test_regress/t/t_vlcov_covergroup.annotate.out +++ b/test_regress/t/t_vlcov_covergroup.annotate.out @@ -14,9 +14,9 @@ module t; %000001 logic [1:0] addr; --000001 point: type=toggle comment=addr[0]:0->1 hier=top.t +-000000 point: type=toggle comment=addr[0]:0->1 hier=top.t -000000 point: type=toggle comment=addr[0]:1->0 hier=top.t --000000 point: type=toggle comment=addr[1]:0->1 hier=top.t +-000001 point: type=toggle comment=addr[1]:0->1 hier=top.t -000000 point: type=toggle comment=addr[1]:1->0 hier=top.t %000001 logic cmd; -000001 point: type=toggle comment=cmd:0->1 hier=top.t @@ -278,6 +278,50 @@ // cross: [a1, write] endgroup + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted + // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + covergroup cg_mixed; +%000002 cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} +-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr0 +-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr1 +%000002 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.read +-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.write +%000002 cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed +-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.normal +-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.debug +%000001 ab: cross cp_addr, cp_cmd; +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_read + // cross: [addr0, read] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_write + // cross: [addr0, write] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_read + // cross: [addr1, read] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_write + // cross: [addr1, write] + endgroup + + // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the + // legacy default-bin codegen path that converted coverpoints bypass. + covergroup cg_def_cross; +%000001 cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.a0 +-000000 point: type=covergroup comment= hier=cg_def_cross.cp_a.a1 +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.ad +%000001 cp_c: coverpoint cmd {bins read = {0}; bins write = {1};} +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.read +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.write +%000001 axc: cross cp_a, cp_c; +-000001 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_read + // cross: [a0, read] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_write + // cross: [a0, write] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_read + // cross: [a1, read] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_write + // cross: [a1, write] + endgroup + %000001 cg2 cg2_inst = new; -000001 point: type=line comment=block hier=top.t %000001 cg_ignore cg_ignore_inst = new; @@ -298,6 +342,10 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_unnamed_cross cg_unnamed_cross_inst = new; -000001 point: type=line comment=block hier=top.t +%000001 cg_mixed cg_mixed_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_def_cross cg_def_cross_inst = new; +-000001 point: type=line comment=block hier=top.t %000001 initial begin -000001 point: type=line comment=block hier=top.t @@ -641,6 +689,40 @@ -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t + // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) +%000001 addr = 0; cmd = 0; mode = 0; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t +%000001 addr = 0; cmd = 1; mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write) +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 0; mode = 0; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read) +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 1; mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff) +%000001 mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write +-000001 point: type=line comment=block hier=top.t + %000001 $write("*-* All Finished *-*\n"); -000001 point: type=line comment=block hier=top.t %000001 $finish; From 87d261067405771c731c5422122aca0d9013d3ee Mon Sep 17 00:00:00 2001 From: Nick Brereton <85175726+nbstrike@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:32:01 -0400 Subject: [PATCH 21/89] Support unpacked struct stream (#7767) --- src/V3AstNodeDType.h | 4 + src/V3AstNodes.cpp | 41 +++ src/V3Const.cpp | 138 ++++++- src/V3Width.cpp | 53 ++- test_regress/t/t_stream_unpacked_struct.py | 18 + test_regress/t/t_stream_unpacked_struct.v | 347 ++++++++++++++++++ .../t/t_stream_unpacked_struct_bad.out | 30 ++ .../t/t_stream_unpacked_struct_bad.py | 16 + test_regress/t/t_stream_unpacked_struct_bad.v | 63 ++++ 9 files changed, 691 insertions(+), 19 deletions(-) create mode 100755 test_regress/t/t_stream_unpacked_struct.py create mode 100644 test_regress/t/t_stream_unpacked_struct.v create mode 100644 test_regress/t/t_stream_unpacked_struct_bad.out create mode 100755 test_regress/t/t_stream_unpacked_struct_bad.py create mode 100644 test_regress/t/t_stream_unpacked_struct_bad.v diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 1253c8527..4600911d2 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -170,6 +170,10 @@ public: void generic(bool flag) { m_generic = flag; } std::pair dimensions(bool includeBasic) const; uint32_t arrayUnpackedElements() const; // 1, or total multiplication of all dimensions + // Fixed aggregate streaming properties + bool isStreamableFixedAggregate() const; + bool containsUnpackedStruct() const; + int widthStream() const; static int uniqueNumInc() { return ++s_uniqueNum; } const char* charIQWN() const { return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I"); diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index e98c9bb89..746bd4595 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1259,6 +1259,47 @@ uint32_t AstNodeDType::arrayUnpackedElements() const { return entries; } +bool AstNodeDType::isStreamableFixedAggregate() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->isStreamableFixedAggregate(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (sdtypep->packed()) return true; + if (!VN_IS(sdtypep, StructDType)) return false; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + if (!itemp->dtypep()->isStreamableFixedAggregate()) return false; + } + return true; + } + return dtypep->isIntegralOrPacked() || dtypep->isDouble(); +} + +bool AstNodeDType::containsUnpackedStruct() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->containsUnpackedStruct(); + } + const AstStructDType* const sdtypep = VN_CAST(dtypep, StructDType); + return sdtypep && !sdtypep->packed(); +} + +int AstNodeDType::widthStream() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->widthStream() * adtypep->elementsConst(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (!VN_IS(sdtypep, StructDType) || sdtypep->packed()) return width(); + int width = 0; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + width += itemp->dtypep()->widthStream(); + } + return width; + } + return dtypep->width(); +} + std::pair AstNodeDType::dimensions(bool includeBasic) const { // How many array dimensions (packed,unpacked) does this Var have? uint32_t packed = 0; diff --git a/src/V3Const.cpp b/src/V3Const.cpp index de2e1d64f..2ff6a0a80 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -973,6 +973,96 @@ class ConstVisitor final : public VNVisitor { return !numv.isNumber() ? numv : V3Number{nodep, nodep->width(), numv}; } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } + + AstStructSel* newStructSel(AstNodeExpr* const fromp, const AstMemberDType* const itemp) { + AstStructSel* const selp = new AstStructSel{fromp->fileline(), fromp, itemp->name()}; + selp->dtypeFrom(itemp->dtypep()); + return selp; + } + + void collectFixedAggregateTerms(AstNodeExpr* const fromp, std::vector& termps, + const bool packReal) { + const AstNodeDType* const dtypep = fromp->dtypep()->skipRefp(); + if (const AstUnpackArrayDType* const unpackDtypep = VN_CAST(dtypep, UnpackArrayDType)) { + const int left = unpackDtypep->left(); + const int right = unpackDtypep->right(); + const int step = left <= right ? 1 : -1; + for (int idx = left;; idx += step) { + AstArraySel* const selp + = new AstArraySel{fromp->fileline(), fromp->cloneTreePure(false), idx}; + collectFixedAggregateTerms(selp, termps, packReal); + if (idx == right) break; + } + VL_DO_DANGLING(pushDeletep(fromp), fromp); + } else if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(dtypep, NodeUOrStructDType)) { + if (sdtypep->packed()) { + termps.push_back(fromp); + return; + } + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + collectFixedAggregateTerms(newStructSel(fromp->cloneTreePure(false), itemp), + termps, packReal); + } + VL_DO_DANGLING(pushDeletep(fromp), fromp); + } else if (packReal && dtypep->isDouble()) { + termps.push_back(new AstRealToBits{fromp->fileline(), fromp}); + } else { + termps.push_back(fromp); + } + } + + AstNodeExpr* packFixedAggregate(AstNodeExpr* const fromp) { + std::vector termps; + collectFixedAggregateTerms(fromp, termps, true); + UASSERT(!termps.empty(), "No stream terms"); + AstNodeExpr* resultp = termps[0]; + for (size_t i = 1; i < termps.size(); ++i) { + resultp = new AstConcat{resultp->fileline(), resultp, termps[i]}; + } + return resultp; + } + + void replaceAssignToFixedAggregate(AstNodeAssign* const nodep, AstNodeExpr* const dstp, + AstNodeExpr* srcp) { + const int dstWidth = dstp->dtypep()->widthStream(); + std::vector termps; + collectFixedAggregateTerms(dstp, termps, false); + int srcWidth = srcp->width(); + if (srcWidth < dstWidth) { + AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; + extendp->dtypeSetLogicSized(dstWidth, VSigning::UNSIGNED); + srcp = new AstShiftL{ + extendp->fileline(), extendp, + new AstConst{extendp->fileline(), static_cast(dstWidth - srcWidth)}, + dstWidth}; + srcWidth = dstWidth; + } + AstNodeAssign* newp = nullptr; + int offset = 0; + for (size_t i = 0; i < termps.size(); ++i) { + AstNodeExpr* const termp = termps[i]; + const int width = termp->dtypep()->widthStream(); + const int lsb = srcWidth - offset - width; + offset += width; + AstNodeExpr* rhsp + = new AstSel{srcp->fileline(), srcp->cloneTreePure(false), lsb, width}; + if (termp->dtypep()->skipRefp()->isDouble()) { + rhsp = new AstBitsToRealD{rhsp->fileline(), rhsp}; + } + AstNodeAssign* const assignp = nodep->cloneType(termp, rhsp); + assignp->dtypeFrom(termp); + newp = AstNode::addNext(newp, assignp); + } + nodep->addNextHere(newp); + VL_DO_DANGLING(pushDeletep(srcp), srcp); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + } + bool operandConst(const AstNode* nodep) { return VN_IS(nodep, Const); } bool operandAsvConst(const AstNode* nodep) { // BIASV(CONST, BIASV(CONST,...)) -> BIASV( BIASV_CONSTED(a,b), ...) @@ -2393,6 +2483,25 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(conp), conp); // Further reduce, either node may have more reductions. return true; + } else if (m_doV && VN_IS(nodep->rhsp(), CvtPackedToArray) + && lowerAsFixedAggregate(nodep->lhsp()->dtypep())) { + AstCvtPackedToArray* const cvtp + = VN_AS(nodep->rhsp(), CvtPackedToArray)->unlinkFrBack(); + AstNodeExpr* srcp = cvtp->fromp()->unlinkFrBack(); + if (lowerAsFixedAggregate(srcp->dtypep())) { + srcp = packFixedAggregate(srcp); + } else if (AstNodeStream* const streamp = VN_CAST(srcp, NodeStream)) { + AstNodeExpr* const streamSrcp = streamp->lhsp(); + if (lowerAsFixedAggregate(streamSrcp->dtypep())) { + AstNodeExpr* const packedp = packFixedAggregate(streamSrcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + } + } + VL_DO_DANGLING(pushDeletep(cvtp), cvtp); + replaceAssignToFixedAggregate(nodep, nodep->lhsp()->unlinkFrBack(), srcp); + return true; } else if (m_doV && VN_IS(nodep->rhsp(), StreamR) && !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) { // The right-streaming operator on rhs of assignment does not @@ -2402,7 +2511,9 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack(); AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); const AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); - if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { + if (lowerAsFixedAggregate(srcDTypep)) { + srcp = packFixedAggregate(srcp); + } else if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { int srcElementBits = 0; if (const AstNodeDType* const elemDtp = srcDTypep->subDTypep()) { @@ -2429,6 +2540,19 @@ class ConstVisitor final : public VNVisitor { srcp = new AstShiftL{srcp->fileline(), srcp, new AstConst{srcp->fileline(), offset}, packedBits}; } + if (!VN_IS(dstDTypep, UnpackArrayDType) && !VN_IS(dstDTypep, QueueDType) + && !VN_IS(dstDTypep, DynArrayDType)) { + const int sWidth = srcp->width(); + const int dWidth = nodep->lhsp()->width(); + if (sWidth < dWidth) { + AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; + extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); + srcp = new AstShiftL{ + srcp->fileline(), extendp, + new AstConst{srcp->fileline(), static_cast(dWidth - sWidth)}, + dWidth}; + } + } nodep->rhsp(srcp); VL_DO_DANGLING(pushDeletep(streamp), streamp); // Further reduce, any of the nodes may have more reductions. @@ -2438,7 +2562,7 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* streamp = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack(); AstNodeDType* const dstDTypep = dstp->dtypep()->skipRefp(); - AstNodeExpr* const srcp = nodep->rhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); // Handle unpacked/queue/dynarray source -> queue/dynarray dest via // CvtArrayToArray (StreamL reverses, so reverse=true) @@ -2466,6 +2590,7 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(streamp), streamp); return true; } + if (lowerAsFixedAggregate(srcDTypep)) srcp = packFixedAggregate(srcp); const int sWidth = srcp->width(); const int dWidth = dstp->width(); // Connect the rhs to the stream operator and update its width @@ -2556,7 +2681,7 @@ class ConstVisitor final : public VNVisitor { } else { // Source narrower than destination: left-justify by shifting left. // The right stream operator packs left-to-right, so remaining - // LSBs are zero-filled (IEEE 1800-2023 11.4.14.2). + // LSBs are zero-filled (IEEE 1800-2023 11.4.14.3). if (!VN_IS(srcp->dtypep()->skipRefp(), QueueDType)) { AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); @@ -2580,6 +2705,13 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); + if (lowerAsFixedAggregate(srcDTypep)) { + AstNodeExpr* const packedp = packFixedAggregate(srcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + srcp = packedp; + } if ((VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType) || VN_IS(srcDTypep, UnpackArrayDType))) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 861714e34..a323f9336 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -255,13 +255,6 @@ class WidthVisitor final : public VNVisitor { EXTEND_OFF // No extension }; - int widthUnpacked(const AstNodeDType* const dtypep) { - if (const AstUnpackArrayDType* const arrDtypep = VN_CAST(dtypep, UnpackArrayDType)) { - return arrDtypep->subDTypep()->width() * arrDtypep->arrayUnpackedElements(); - } - return dtypep->width(); - } - static void packIfUnpacked(AstNodeExpr* const nodep) { if (AstUnpackArrayDType* const unpackDTypep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) { const int elementsNum = unpackDTypep->arrayUnpackedElements(); @@ -274,6 +267,9 @@ class WidthVisitor final : public VNVisitor { nodep->findLogicDType(unpackBits, unpackMinBits, VSigning::UNSIGNED)}); } } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } // When fromp() is a DType (e.g. unlinked RefDType), resolve through // the ref chain; when it's an expression, dtypep() is already resolved. static AstNodeDType* fromDTypep(AstNode* fromp) { @@ -979,7 +975,10 @@ class WidthVisitor final : public VNVisitor { } const AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep()->skipRefToEnump(); if (VN_IS(lhsDtypep, DynArrayDType) || VN_IS(lhsDtypep, QueueDType) - || VN_IS(lhsDtypep, UnpackArrayDType)) { + || (VN_IS(lhsDtypep, UnpackArrayDType) && lhsDtypep->isStreamableFixedAggregate()) + || (VN_IS(lhsDtypep, NodeUOrStructDType) + && !VN_AS(lhsDtypep, NodeUOrStructDType)->packed() + && lhsDtypep->isStreamableFixedAggregate())) { nodep->dtypeSetStream(); } else if (lhsDtypep->isCompound()) { nodep->v3warn(E_UNSUPPORTED, @@ -6295,14 +6294,14 @@ class WidthVisitor final : public VNVisitor { userIterateAndNext(nodep->rhsp(), WidthVP{nodep->dtypep(), PRELIM}.p()); // // UINFOTREE(1, nodep, "", "assign"); - AstNodeDType* const lhsDTypep + AstNodeDType* lhsDTypep = nodep->lhsp()->dtypep(); // Note we use rhsp for context determined // Check width of stream and wrap if needed if (AstNodeStream* const streamp = VN_CAST(nodep->rhsp(), NodeStream)) { AstNodeDType* const lhsDTypeSkippedRefp = lhsDTypep->skipRefp(); - const int lwidth = widthUnpacked(lhsDTypeSkippedRefp); - const int rwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); + const int lwidth = lhsDTypeSkippedRefp->widthStream(); + const int rwidth = streamp->lhsp()->dtypep()->skipRefp()->widthStream(); if (lwidth != 0 && lwidth < rwidth) { nodep->v3widthWarn(lwidth, rwidth, "Target fixed size variable (" @@ -6314,16 +6313,18 @@ class WidthVisitor final : public VNVisitor { const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width(); UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS"); } - if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)) { + if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType) + || lowerAsFixedAggregate(lhsDTypeSkippedRefp)) { streamp->unlinkFrBack(); nodep->rhsp(new AstCvtPackedToArray{streamp->fileline(), streamp, lhsDTypeSkippedRefp}); } } - if (const AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { + if (AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { const AstNodeDType* const rhsDTypep = nodep->rhsp()->dtypep()->skipRefp(); - const int lwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); - const int rwidth = widthUnpacked(rhsDTypep); + AstNodeDType* const lhsStreamDTypep = streamp->lhsp()->dtypep()->skipRefp(); + const int lwidth = lhsStreamDTypep->widthStream(); + const int rwidth = rhsDTypep->widthStream(); if (rwidth != 0 && rwidth < lwidth) { nodep->v3widthWarn(lwidth, rwidth, "Stream target requires " @@ -6331,7 +6332,27 @@ class WidthVisitor final : public VNVisitor { << " bits, but source expression only provides " << rwidth << " bits (IEEE 1800-2023 11.4.14.3)"); } - if (VN_IS(rhsDTypep, UnpackArrayDType)) { + if (lowerAsFixedAggregate(lhsStreamDTypep)) { + AstNodeExpr* const streamExprp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const dstp = streamp->lhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); + if (VN_IS(streamp, StreamL)) { + streamp->lhsp(srcp); + streamp->dtypeSetLogicUnsized(srcp->width(), srcp->widthMin(), + VSigning::UNSIGNED); + srcp = streamExprp; + } else { + if (srcp->width() > lwidth) { + srcp = new AstSel{streamp->fileline(), srcp, srcp->width() - lwidth, + lwidth}; + } + VL_DO_DANGLING(pushDeletep(streamExprp), streamExprp); + } + nodep->lhsp(dstp); + nodep->rhsp(new AstCvtPackedToArray{srcp->fileline(), srcp, lhsStreamDTypep}); + nodep->dtypeFrom(dstp); + lhsDTypep = nodep->lhsp()->dtypep(); + } else if (VN_IS(rhsDTypep, UnpackArrayDType)) { AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); nodep->rhsp( new AstCvtArrayToPacked{rhsp->fileline(), rhsp, streamp->dtypep()}); diff --git a/test_regress/t/t_stream_unpacked_struct.py b/test_regress/t/t_stream_unpacked_struct.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct.v b/test_regress/t/t_stream_unpacked_struct.v new file mode 100644 index 000000000..509d8a035 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.v @@ -0,0 +1,347 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// Ref. to IEEE 1800-2023 11.4.14, A.8.1 + +module t; + + `define checkh(gotv, expv) \ + do if ((gotv) !== (expv)) begin \ + $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__, `__LINE__, (gotv), (expv)); \ + $stop; \ + end while (0); + + typedef struct packed { + logic [3:0] hi; + logic [3:0] lo; + } packed_pair_t; + + typedef union packed { + logic [7:0] byte_data; + packed_pair_t pair; + } packed_union_t; + + typedef struct { + byte a; + logic [3:0] b; + packed_pair_t p; + } simple_t; + + typedef struct { + byte prefix; + byte data[2]; + packed_pair_t p; + } array_t; + + typedef struct { + simple_t inner; + byte tail[2]; + } nested_t; + + typedef struct { + byte prefix; + simple_t items[2]; + byte suffix; + } struct_array_t; + + typedef struct { + byte data[1:0]; + } descending_array_t; + + typedef struct { + byte matrix[2][3]; + } matrix_array_t; + + typedef struct { + logic [1:0][3:0] data[2]; + } mixed_array_t; + + typedef enum logic [2:0] { + E0 = 3'd0, + E5 = 3'd5 + } enum_t; + + typedef struct { + enum_t e; + logic [4:0] x; + } enum_struct_t; + + typedef struct { + byte tag; + real r; + realtime rt; + real ra[2]; + } real_struct_t; + + typedef struct { + packed_union_t u; + byte tail; + } packed_union_struct_t; + + simple_t simple; + simple_t simple_out; + simple_t simple_cont_out; + array_t arrayed; + array_t arrayed_out; + nested_t nested; + nested_t nested_out; + struct_array_t struct_array; + struct_array_t struct_array_out; + descending_array_t descending; + descending_array_t descending_out; + matrix_array_t matrix_array; + matrix_array_t matrix_array_out; + mixed_array_t mixed_array; + mixed_array_t mixed_array_out; + simple_t simple_array[2]; + simple_t simple_array_out[2]; + enum_struct_t enum_struct; + enum_struct_t enum_struct_out; + real_struct_t real_struct; + real_struct_t real_struct_out; + packed_union_struct_t packed_union_struct; + packed_union_struct_t packed_union_struct_out; + + logic [19:0] simple_bits; + logic [31:0] wide_simple_bits; + logic [31:0] array_bits; + logic [35:0] nested_bits; + logic [55:0] struct_array_bits; + logic [15:0] descending_bits; + logic [47:0] matrix_array_bits; + logic [15:0] mixed_array_bits; + logic [39:0] simple_array_bits; + logic [31:0] byteswapped_bits; + logic [7:0] enum_bits; + logic [263:0] real_bits; + logic [15:0] packed_union_bits; + logic [11:0] narrow_bits; + logic [19:0] simple_streaml_src; + byte byte_array_out[2]; + + assign {>>{simple_cont_out}} = 20'habcde; + + initial begin + byteswapped_bits = {<<8{32'h11223344}}; + `checkh(byteswapped_bits, 32'h44332211); + byteswapped_bits = {<>{simple_bits}} = 20'h13579; + `checkh(simple_bits, 20'h13579); + {<<4{simple_bits}} = 20'h12345; + `checkh(simple_bits, 20'h54321); + + simple = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + simple_bits = {>>{simple}}; + `checkh(simple_bits, 20'h12abc); + /* verilator lint_off WIDTHEXPAND */ + wide_simple_bits = {>>{simple}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(wide_simple_bits, 32'h12abc000); + simple_bits = {<<4{simple}}; + `checkh(simple_bits, 20'hcba21); + + {>>{simple_out}} = 20'h345de; + `checkh(simple_out.a, 8'h34); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'hde); + `checkh(simple_cont_out.a, 8'hab); + `checkh(simple_cont_out.b, 4'hc); + `checkh(simple_cont_out.p, 8'hde); + + {<<4{simple_out}} = 20'h789ab; + `checkh(simple_out.a, 8'hba); + `checkh(simple_out.b, 4'h9); + `checkh(simple_out.p, 8'h87); + + simple_out = {>>{20'hfedcb}}; + `checkh(simple_out.a, 8'hfe); + `checkh(simple_out.b, 4'hd); + `checkh(simple_out.p, 8'hcb); + + simple_out = {>>{simple}}; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + {>>{simple_out}} = simple; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + if ($test$plusargs("t_stream_unpacked_struct_alt")) begin + narrow_bits = 12'h123; + end else begin + narrow_bits = 12'habd; + end + /* verilator lint_off WIDTHEXPAND */ + simple_bits = {>>{narrow_bits}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(simple_bits, {narrow_bits, 8'h00}); + + simple_out = {>>{narrow_bits}}; + `checkh(simple_out.a, narrow_bits[11:4]); + `checkh(simple_out.b, narrow_bits[3:0]); + `checkh(simple_out.p, 8'h00); + + {>>{simple_out}} = 24'habcdef; + `checkh(simple_out.a, 8'hab); + `checkh(simple_out.b, 4'hc); + `checkh(simple_out.p, 8'hde); + + simple_out = {<<4{20'h13579}}; + `checkh(simple_out.a, 8'h97); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'h31); + + simple_streaml_src = 20'h2468a; + simple_out = {<<4{simple_streaml_src}}; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<4{simple_out}} = simple_streaml_src; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<8{byte_array_out}} = 16'h1234; + `checkh(byte_array_out[0], 8'h34); + `checkh(byte_array_out[1], 8'h12); + + arrayed = '{8'h11, '{8'h22, 8'h33}, '{4'h4, 4'h5}}; + array_bits = {>>{arrayed}}; + `checkh(array_bits, 32'h11223345); + array_bits = {<<8{arrayed}}; + `checkh(array_bits, 32'h45332211); + + {>>{arrayed_out}} = 32'haabbccdd; + `checkh(arrayed_out.prefix, 8'haa); + `checkh(arrayed_out.data[0], 8'hbb); + `checkh(arrayed_out.data[1], 8'hcc); + `checkh(arrayed_out.p, 8'hdd); + + nested = '{'{8'h12, 4'ha, '{4'hb, 4'hc}}, '{8'h34, 8'h56}}; + nested_bits = {>>{nested}}; + `checkh(nested_bits, 36'h12abc3456); + + {>>{nested_out}} = 36'h6543210fe; + `checkh(nested_out.inner.a, 8'h65); + `checkh(nested_out.inner.b, 4'h4); + `checkh(nested_out.inner.p, 8'h32); + `checkh(nested_out.tail[0], 8'h10); + `checkh(nested_out.tail[1], 8'hfe); + + struct_array.prefix = 8'haa; + struct_array.items[0] = '{8'h12, 4'h3, '{4'h4, 4'h5}}; + struct_array.items[1] = '{8'h67, 4'h8, '{4'h9, 4'ha}}; + struct_array.suffix = 8'hbb; + struct_array_bits = {>>{struct_array}}; + `checkh(struct_array_bits, 56'haa123456789abb); + + {>>{struct_array_out}} = 56'hccdef0123456dd; + `checkh(struct_array_out.prefix, 8'hcc); + `checkh(struct_array_out.items[0].a, 8'hde); + `checkh(struct_array_out.items[0].b, 4'hf); + `checkh(struct_array_out.items[0].p, 8'h01); + `checkh(struct_array_out.items[1].a, 8'h23); + `checkh(struct_array_out.items[1].b, 4'h4); + `checkh(struct_array_out.items[1].p, 8'h56); + `checkh(struct_array_out.suffix, 8'hdd); + + descending = '{data: '{8'hcc, 8'hdd}}; + descending_bits = {>>{descending}}; + `checkh(descending_bits, 16'hccdd); + + {>>{descending_out}} = 16'h1234; + `checkh(descending_out.data[1], 8'h12); + `checkh(descending_out.data[0], 8'h34); + + matrix_array.matrix[0][0] = 8'h11; + matrix_array.matrix[0][1] = 8'h22; + matrix_array.matrix[0][2] = 8'h33; + matrix_array.matrix[1][0] = 8'h44; + matrix_array.matrix[1][1] = 8'h55; + matrix_array.matrix[1][2] = 8'h66; + matrix_array_bits = {>>{matrix_array}}; + `checkh(matrix_array_bits, 48'h112233445566); + + {>>{matrix_array_out}} = 48'haabbccddeeff; + `checkh(matrix_array_out.matrix[0][0], 8'haa); + `checkh(matrix_array_out.matrix[0][1], 8'hbb); + `checkh(matrix_array_out.matrix[0][2], 8'hcc); + `checkh(matrix_array_out.matrix[1][0], 8'hdd); + `checkh(matrix_array_out.matrix[1][1], 8'hee); + `checkh(matrix_array_out.matrix[1][2], 8'hff); + + mixed_array.data[0] = 8'hab; + mixed_array.data[1] = 8'hcd; + mixed_array_bits = {>>{mixed_array}}; + `checkh(mixed_array_bits, 16'habcd); + + {>>{mixed_array_out}} = 16'h1234; + `checkh(mixed_array_out.data[0], 8'h12); + `checkh(mixed_array_out.data[0][1], 4'h1); + `checkh(mixed_array_out.data[0][0], 4'h2); + `checkh(mixed_array_out.data[1], 8'h34); + `checkh(mixed_array_out.data[1][1], 4'h3); + `checkh(mixed_array_out.data[1][0], 4'h4); + + simple_array[0] = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + simple_array[1] = '{8'h34, 4'hd, '{4'he, 4'hf}}; + simple_array_bits = {>>{simple_array}}; + `checkh(simple_array_bits, 40'h12abc34def); + + {>>{simple_array_out}} = 40'h567899abcd; + `checkh(simple_array_out[0].a, 8'h56); + `checkh(simple_array_out[0].b, 4'h7); + `checkh(simple_array_out[0].p, 8'h89); + `checkh(simple_array_out[1].a, 8'h9a); + `checkh(simple_array_out[1].b, 4'hb); + `checkh(simple_array_out[1].p, 8'hcd); + + enum_struct = '{E5, 5'ha}; + enum_bits = {>>{enum_struct}}; + `checkh(enum_bits, 8'haa); + + {>>{enum_struct_out}} = 8'h71; + `checkh(enum_struct_out.e, 3'h3); + `checkh(enum_struct_out.x, 5'h11); + + real_struct.tag = 8'h42; + real_struct.r = 1.0; + real_struct.rt = 3.0; + real_struct.ra[0] = 4.0; + real_struct.ra[1] = 5.0; + real_bits = {>>{real_struct}}; + `checkh(real_bits, + {8'h42, $realtobits(1.0), $realtobits(3.0), $realtobits(4.0), $realtobits(5.0)}); + + {>>{real_struct_out}} + = {8'h99, $realtobits(2.0), $realtobits(6.0), $realtobits(7.0), $realtobits(8.0)}; + `checkh(real_struct_out.tag, 8'h99); + `checkh($realtobits(real_struct_out.r), $realtobits(2.0)); + `checkh($realtobits(real_struct_out.rt), $realtobits(6.0)); + `checkh($realtobits(real_struct_out.ra[0]), $realtobits(7.0)); + `checkh($realtobits(real_struct_out.ra[1]), $realtobits(8.0)); + + packed_union_struct.u.byte_data = 8'hbe; + packed_union_struct.tail = 8'hef; + packed_union_bits = {>>{packed_union_struct}}; + `checkh(packed_union_bits, 16'hbeef); + + {>>{packed_union_struct_out}} = 16'hcafe; + `checkh(packed_union_struct_out.u.byte_data, 8'hca); + `checkh(packed_union_struct_out.tail, 8'hfe); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_stream_unpacked_struct_bad.out b/test_regress/t/t_stream_unpacked_struct_bad.out new file mode 100644 index 000000000..9955e9208 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.out @@ -0,0 +1,30 @@ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:54:12: Unsupported: Stream operation on a variable of a type 'struct{}t.queue_struct_t' + : ... note: In instance 't' + 54 | out = {>>{queue_struct}}; + | ^~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:55:12: Unsupported: Stream operation on a variable of a type 'struct{}t.dynamic_struct_t' + : ... note: In instance 't' + 55 | out = {>>{dynamic_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:56:12: Unsupported: Stream operation on a variable of a type 'struct{}t.associative_struct_t' + : ... note: In instance 't' + 56 | out = {>>{associative_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:57:12: Unsupported: Stream operation on a variable of a type 'struct{}t.union_struct_t' + : ... note: In instance 't' + 57 | out = {>>{union_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:58:12: Unsupported: Stream operation on a variable of a type 'struct{}t.string_struct_t' + : ... note: In instance 't' + 58 | out = {>>{string_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:59:12: Unsupported: Stream operation on a variable of a type 'struct{}t.chandle_struct_t' + : ... note: In instance 't' + 59 | out = {>>{chandle_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:60:12: Unsupported: Stream operation on a variable of a type 'struct{}t.event_struct_t' + : ... note: In instance 't' + 60 | out = {>>{event_struct}}; + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_stream_unpacked_struct_bad.py b/test_regress/t/t_stream_unpacked_struct_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct_bad.v b/test_regress/t/t_stream_unpacked_struct_bad.v new file mode 100644 index 000000000..2c1e00b97 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.v @@ -0,0 +1,63 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module t; + + typedef struct { + byte bytes[$]; + } queue_struct_t; + + typedef struct { + byte bytes[]; + } dynamic_struct_t; + + typedef struct { + byte bytes[int]; + } associative_struct_t; + + typedef union { + byte a; + logic [15:0] b; + } unpacked_union_t; + + typedef struct { + unpacked_union_t u; + } union_struct_t; + + typedef struct { + string s; + } string_struct_t; + + typedef struct { + chandle c; + } chandle_struct_t; + + typedef struct { + event e; + } event_struct_t; + + logic [255:0] out; + queue_struct_t queue_struct; + dynamic_struct_t dynamic_struct; + associative_struct_t associative_struct; + union_struct_t union_struct; + string_struct_t string_struct; + chandle_struct_t chandle_struct; + event_struct_t event_struct; + + initial begin + out = {>>{queue_struct}}; + out = {>>{dynamic_struct}}; + out = {>>{associative_struct}}; + out = {>>{union_struct}}; + out = {>>{string_struct}}; + out = {>>{chandle_struct}}; + out = {>>{event_struct}}; + end + +endmodule From ba624d7ce16e5c9cfdafd39806807ed3582c1f9f Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 13 Jun 2026 08:45:26 +0100 Subject: [PATCH 22/89] Optimize away proven redundant case statement assertions (#7771) This is still mostly refactoring of V3Case, but with functional changes. Decouple the exhaustiveness/overlap analysis from the decision to convert the case using the fast bitwise testing method. This enables dropping the 'notParallel' assertions for those we can prove exhaustive and unique, even if we decide to convert them using the generic if/else ladder scheme. --- src/V3Case.cpp | 289 +++++++++++++----------- test_regress/t/t_case_unique_overlap.py | 4 +- 2 files changed, 165 insertions(+), 128 deletions(-) diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 9fc489c4d..65386df2a 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -129,10 +129,8 @@ public: // Case state, as a visitor of each AstNode class CaseVisitor final : public VNVisitor { - // Maximum width we can check for overlaps/exhaustiveness - constexpr static int CASE_OVERLAP_WIDTH = 16; - // Maximum number of case values for exhaustive analysis/optimization - constexpr static int CASE_MAX_VALUES = 1 << CASE_OVERLAP_WIDTH; + // Maximum width for detailed analysis + constexpr static int CASE_DETAILS_MAX_WIDTH = 16; // Levels of priority to be ORed together in top IF tree constexpr static int CASE_ENCODER_GROUP_DEPTH = 8; @@ -145,17 +143,37 @@ class CaseVisitor final : public VNVisitor { }; // STATE - VDouble0 m_statCaseFast; // Statistic tracking - VDouble0 m_statCaseSlow; // Statistic tracking + // Statistics tracking, as a struct so can be passed to 'const' methods + struct Stats final { + VDouble0 caseFast; // Cases using fast bit tree method + VDouble0 caseGeneric; // Cases using generic if/else tree method + VDouble0 provenAssertions; // Assertions proven to hold + } m_stats; const AstNode* m_alwaysp = nullptr; // Always in which case is located - // Per-CASE - bool m_caseExhaustive = false; // Proven exhaustive - bool m_caseNoOverlaps = false; // Proven no overlaps between cases - // Map from value (index) to the CaseRecord that covers this value - std::array m_value2CaseRecord; + // STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise + bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions) + size_t m_caseNConditions = 0; // Number of conditions in the case statement + bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid + struct final { + bool exhaustive = false; // Proven exhaustive + bool noOverlaps = false; // Proven no overlaps between cases + // Map from value (index) to the CaseRecord that covers this value + std::array records; + } m_caseDetails; // METHODS + + // Xs in case or casez are impossible due to two state simulations. + // Returns true if the item is never possible + static bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) { + const AstConst* const constp = VN_CAST(itemExprp, Const); + if (!constp) return false; + if (casep->casex() || casep->caseInside()) return false; + if (casep->casez()) return constp->num().isAnyX(); + return constp->num().isFourState(); + } + // Determine whether we should check case items are complete // Returns enum's dtype if should check, nullptr if shouldn't static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { @@ -185,7 +203,7 @@ class CaseVisitor final : public VNVisitor { // Check all cases to see if they cover this enum value/pattern for (uint32_t i = 0; i < numCases; ++i) { if ((i & mask) != val) continue; // This case is not for this enum value - if (m_value2CaseRecord[i].itemp) continue; // Covered case + if (m_caseDetails.records[i].itemp) continue; // Covered case // Warn unless unique0 case which allows no-match if (!nodep->unique0Pragma()) { nodep->v3warn(CASEINCOMPLETE, @@ -203,7 +221,7 @@ class CaseVisitor final : public VNVisitor { bool checkExhaustivePacked(AstCase* nodep) { const uint32_t numCases = 1UL << nodep->exprp()->width(); for (uint32_t i = 0; i < numCases; ++i) { - if (m_value2CaseRecord[i].itemp) continue; // Covered case + if (m_caseDetails.records[i].itemp) continue; // Covered case if (!nodep->unique0Pragma()) { nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered (example pattern 0x" << std::hex @@ -224,50 +242,28 @@ class CaseVisitor final : public VNVisitor { return checkExhaustivePacked(nodep); } - bool isCaseTreeFast(AstCase* nodep) { - m_caseExhaustive = true; // TODO: we haven't proven this yet, but is as was before - m_caseNoOverlaps = false; - - AstNode* const caseExprp = nodep->exprp(); - if (caseExprp->isDouble() || caseExprp->isString()) return false; - - const int caseWidth = caseExprp->width(); - if (!caseWidth) return false; - if (caseWidth > CASE_OVERLAP_WIDTH) return false; - - int caseConditions = 0; - - for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { - for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { - // Can't do anything with non-constants - if (!VN_IS(condp, Const)) return false; - // Count conditions - ++caseConditions; - } - } - - UINFO(8, "Simple case statement: " << nodep); - const uint32_t numCases = 1UL << caseWidth; - // Zero list of items for each value - for (uint32_t i = 0; i < numCases; ++i) { - m_value2CaseRecord[i].itemp = nullptr; - m_value2CaseRecord[i].constp = nullptr; - m_value2CaseRecord[i].stmtsp = nullptr; + // Analyze each value in the case statement. Updates 'm_caseDetails' and issues warnings. + void analyzeCaseDetails(AstCase* nodep) { + const uint32_t numValues = 1UL << nodep->exprp()->width(); + // Clear case records + for (uint32_t i = 0; i < numValues; ++i) { + m_caseDetails.records[i].itemp = nullptr; + m_caseDetails.records[i].constp = nullptr; + m_caseDetails.records[i].stmtsp = nullptr; } // Now pick up the values for each assignment // We can cheat and use uint32_t's because we only support narrow case's bool reportedOverlap = false; - bool reportedSubcase = false; bool hasDefault = false; - m_caseNoOverlaps = true; + m_caseDetails.noOverlaps = true; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { // Default case if (itemp->isDefault()) { // Default was moved to be the last item by V3LinkDot. Fill remaining cases - for (uint32_t i = 0; i < numCases; ++i) { - CaseRecord& caseRecord = m_value2CaseRecord[i]; + for (uint32_t i = 0; i < numValues; ++i) { + CaseRecord& caseRecord = m_caseDetails.records[i]; if (!caseRecord.itemp) { caseRecord.itemp = itemp; caseRecord.stmtsp = itemp->stmtsp(); @@ -293,10 +289,10 @@ class CaseVisitor final : public VNVisitor { bool foundNewCase = false; const AstConst* firstOverlapConstp = nullptr; uint32_t firstOverlapValue = 0; - for (uint32_t i = 0; i < numCases; ++i) { + for (uint32_t i = 0; i < numValues; ++i) { if ((i & mask) != val) continue; - CaseRecord& caseRecord = m_value2CaseRecord[i]; + CaseRecord& caseRecord = m_caseDetails.records[i]; // If this is the first case that covers this value, record it if (!caseRecord.itemp) { @@ -314,15 +310,21 @@ class CaseVisitor final : public VNVisitor { if (!firstOverlapConstp) { firstOverlapConstp = caseRecord.constp; firstOverlapValue = i; - m_caseNoOverlaps = false; + m_caseDetails.noOverlaps = false; } } + + // Only report first overlap + if (reportedOverlap || !firstOverlapConstp) continue; + + // Report first overlap if (nodep->priorityPragma()) { - // If this is a priority case, we only want to complain if every possible value - // for this item is already hit by some other item. This is true if - // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only covering - // item is this item itself, which is legal overlap within one item. - if (!reportedSubcase && !foundNewCase && firstOverlapConstp) { + // If this is a priority case, we only want to complain if every possible + // value for this item is already hit by some other item. This is true if + // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only + // covering item is this item itself, which is legal overlap within one + // item. + if (!foundNewCase) { iconstp->v3warn(CASEOVERLAP, "Case item ignored: every matching value is covered " "by an earlier condition\n" @@ -330,59 +332,76 @@ class CaseVisitor final : public VNVisitor { << firstOverlapConstp->warnOther() << "... Location of previous condition\n" << firstOverlapConstp->warnContextPrimary()); - reportedSubcase = true; + reportedOverlap = true; } } else { // If this case statement doesn't have the priority keyword, // we want to warn on any overlap. - if (!reportedOverlap && firstOverlapConstp) { - std::ostringstream examplePattern; - if (iconstp->num().isAnyXZ()) { - examplePattern << " (example pattern 0x" << std::hex - << firstOverlapValue << ")"; - } - iconstp->v3warn(CASEOVERLAP, - "Case conditions overlap" - << examplePattern.str() << "\n" - << iconstp->warnContextPrimary() << '\n' - << firstOverlapConstp->warnOther() - << "... Location of overlapping condition\n" - << firstOverlapConstp->warnContextSecondary()); - reportedOverlap = true; + std::ostringstream examplePattern; + if (iconstp->num().isAnyXZ()) { + examplePattern << " (example pattern 0x" << std::hex << firstOverlapValue + << ")"; } + iconstp->v3warn(CASEOVERLAP, + "Case conditions overlap" + << examplePattern.str() << "\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of overlapping condition\n" + << firstOverlapConstp->warnContextSecondary()); + reportedOverlap = true; } } } // If there was no default, check exhaustiveness - m_caseExhaustive = hasDefault || checkExhaustive(nodep); - if (!m_caseExhaustive) { - m_caseNoOverlaps = false; - return false; + m_caseDetails.exhaustive = hasDefault || checkExhaustive(nodep); + // Records now valid + m_caseDetailsValid = true; + } + + // Analyze case statement. Updates 'm_case*' members. Reports warnings. + void analyzeCase(AstCase* nodep) { + // Reset all analysis results + m_caseOpaque = false; + m_caseNConditions = 0; + m_caseDetailsValid = false; + + AstNode* const caseExprp = nodep->exprp(); + + // Mark opaque if not a packed value - TODO: can this be a class? + if (caseExprp->isDouble() || caseExprp->isString()) m_caseOpaque = true; + + // Check each condition expression + for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { + // Count conditions + ++m_caseNConditions; + // Mark opaque if non-constant condition + if (!VN_IS(condp, Const)) m_caseOpaque = true; + } } - if (caseConditions <= 3 - // Avoid e.g. priority expanders from going crazy in expansion - || (caseWidth >= 8 && (caseConditions <= (caseWidth + 1)))) { - return false; // Not worth simplifying - } + // Nothing else to do if not a packed type, or non-const conditions + if (m_caseOpaque) return; - return true; // All is fine + // If small enough, analyse details + if (caseExprp->width() <= CASE_DETAILS_MAX_WIDTH) analyzeCaseDetails(nodep); } // TODO: should return AstNodeStmt after #6280 - AstNode* replaceCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) { + AstNode* convertCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) const { // Base case: If reached the last bit, upperValue equals an exact value, just return // the statements from that CaseItem. Note: Not cloning here as the caller will do // an identity check. - if (msb < 0) return m_value2CaseRecord[upperValue].stmtsp; + if (msb < 0) return m_caseDetails.records[upperValue].stmtsp; // Recursive case: // Make left and right subtrees assuming cexpr[msb] is 0 and 1 respectively const uint32_t upperValue0 = upperValue; const uint32_t upperValue1 = upperValue | (1UL << msb); - AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue0); - AstNode* tree1p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue1); + AstNode* tree0p = convertCaseFastRecurse(cexprp, msb - 1, upperValue0); + AstNode* tree1p = convertCaseFastRecurse(cexprp, msb - 1, upperValue1); // If same logic on both sides, we can just return one of them if (tree0p == tree1p) return tree0p; @@ -391,7 +410,7 @@ class CaseVisitor final : public VNVisitor { { bool same = true; for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) { - if (m_value2CaseRecord[a].stmtsp != m_value2CaseRecord[b].stmtsp) { + if (m_caseDetails.records[a].stmtsp != m_caseDetails.records[b].stmtsp) { same = false; break; } @@ -414,23 +433,23 @@ class CaseVisitor final : public VNVisitor { return ifp; } + // CASEx(cexpr,.... + // -> tree of IF(msb, IF(msb-1, 11, 10) + // IF(msb-1, 01, 00)) // TODO: should return AstNodeStmt after #6280 - AstNode* replaceCaseFast(AstCase* nodep) { - // CASEx(cexpr,.... - // -> tree of IF(msb, IF(msb-1, 11, 10) - // IF(msb-1, 01, 00)) + AstNode* convertCaseFast(AstCase* nodep) const { const int caseWidth = nodep->exprp()->width(); - AstNode* const ifrootp = replaceCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); + AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp; } + // Convet case statement using generic if/else tree + // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) + // -> IF((cexpr==icond1),istmts1, + // IF((EQ (AND MASK cexpr) (AND MASK icond1) + // ,istmts2, istmts3 // TODO: should return AstNodeStmt after #6280 - AstNode* replaceCaseComplicated(AstCase* nodep) { - // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) - // -> IF((cexpr==icond1),istmts1, - // IF((EQ (AND MASK cexpr) (AND MASK icond1) - // ,istmts2, istmts3 - + AstNode* convertCaseGeneric(AstCase* nodep) const { // We'll do this in two stages. // First stage, convert the conditions to the appropriate IF AND terms. bool hasDefault = false; @@ -567,13 +586,38 @@ class CaseVisitor final : public VNVisitor { return grouprootp; } - bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) { - const AstConst* const constp = VN_CAST(itemExprp, Const); - if (!constp) return false; - // Xs in case or casez are impossible due to two state simulations - if (casep->casex() || casep->caseInside()) return false; - if (casep->casez()) return constp->num().isAnyX(); - return constp->num().isFourState(); + // Convert the given case statement to a representation not using AstCase + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCase(AstCase* nodep, Stats& stats) const { + // Determine if we should use the fast bitwise branching tree method + const bool useFastBitTree = [&]() { + // Not if disabled + if (!v3Global.opt.fCase()) return false; + // Can't do it without the detailed analysis + if (!m_caseDetailsValid) return false; + // Can't do it if not exhaustive + if (!m_caseDetails.exhaustive) return false; + // Not worth doing if there are few conditions + if (m_caseNConditions <= 3) return false; + // Avoid e.g. priority expanders from going crazy in expansion + const size_t caseWidth = nodep->exprp()->width(); + if (caseWidth >= 8 && m_caseNConditions <= (caseWidth + 1)) return false; + // Otherwise use the bit tree + return true; + }(); + if (useFastBitTree) { + ++stats.caseFast; + return convertCaseFast(nodep); + } + + // Convert using the generic if/else tree method + ++stats.caseGeneric; + // If a case statement is exhaustive, presume signals involved aren't forming a latch + // TODO: this is broken, but it is as was before + if (m_alwaysp && (!m_caseDetailsValid || m_caseDetails.exhaustive)) { + m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); + } + return convertCaseGeneric(nodep); } // VISITORS @@ -586,34 +630,24 @@ class CaseVisitor final : public VNVisitor { // Convert any children first iterateChildren(nodep); - // Convert the case statement - AstNode* replacementp = nullptr; - if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) { - // It's a simple priority encoder or complete statement - // we can make a tree of statements to avoid extra comparisons - ++m_statCaseFast; - replacementp = replaceCaseFast(nodep); - } else { - // If a case statement is exhaustive, presume signals involved aren't forming a latch - // TODO: this is broken, but it is as was before - if (m_alwaysp && m_caseExhaustive) { - m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); - } - ++m_statCaseSlow; - m_caseExhaustive = false; - m_caseNoOverlaps = false; - replacementp = replaceCaseComplicated(nodep); - } + // Analyze this case statement + analyzeCase(nodep); - // Take the notParallelp tree under the case statement created by V3Assert + // Take the 'notParallelp' statements under the case statement created by V3Assert. // If the statement was proven to have no overlaps and all cases covered, // it can be removed. Otherwise insert the assertion after the case statement. - if (nodep->notParallelp() && (!m_caseExhaustive || !m_caseNoOverlaps)) { - nodep->addNextHere(nodep->notParallelp()->unlinkFrBackWithNext()); + if (AstNode* const stmtp = nodep->notParallelp()) { + stmtp->unlinkFrBackWithNext(); + if (m_caseDetailsValid && m_caseDetails.exhaustive && m_caseDetails.noOverlaps) { + ++m_stats.provenAssertions; + VL_DO_DANGLING(stmtp->deleteTree(), stmtp); + } else { + nodep->addNextHere(stmtp); + } } - // Replace/remove the case statement - if (replacementp) { + // Convert the case statement and replace the original + if (AstNode* const replacementp = convertCase(nodep, m_stats)) { nodep->replaceWith(replacementp); } else { nodep->unlinkFrBack(); @@ -632,8 +666,9 @@ public: // CONSTRUCTORS explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); } ~CaseVisitor() override { - V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast); - V3Stats::addStat("Optimizations, Cases complex", m_statCaseSlow); + V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast); + V3Stats::addStat("Optimizations, Cases complex", m_stats.caseGeneric); + V3Stats::addStat("Optimizations, Cases proven assertions", m_stats.provenAssertions); } }; diff --git a/test_regress/t/t_case_unique_overlap.py b/test_regress/t/t_case_unique_overlap.py index 8a938befd..f3945fcdd 100755 --- a/test_regress/t/t_case_unique_overlap.py +++ b/test_regress/t/t_case_unique_overlap.py @@ -11,8 +11,10 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=['--stats']) test.execute() +test.file_grep(test.stats, r'Optimizations, Cases proven assertions\s+(\d+)', 1) + test.passes() From 7af22422c7a61f3248208351d665825d9a61f97d Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 13 Jun 2026 08:45:46 +0100 Subject: [PATCH 23/89] Optimize table lookups in Dfg (#7772) --- src/V3DfgPeephole.cpp | 20 +++++++++++++++++--- src/V3DfgPeepholePatterns.h | 1 + test_regress/t/t_dfg_peephole.v | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 34c1ac5a6..3f08f490f 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -1885,10 +1885,24 @@ class V3DfgPeephole final : public DfgVisitor { if (!idxp) return; DfgVarArray* const varp = vtxp->fromp()->cast(); if (!varp) return; - if (varp->vscp()->varp()->isForced()) return; - if (varp->vscp()->varp()->isSigUserRWPublic()) return; + AstVar* const astVarp = varp->vscp()->varp(); + if (astVarp->isForced()) return; + if (astVarp->isSigUserRWPublic()) return; DfgVertex* const srcp = varp->srcp(); - if (!srcp) return; + if (!srcp) { + if (vtxp->isPacked()) { + if (AstInitArray* const iap = VN_CAST(astVarp->valuep(), InitArray)) { + if (AstConst* const valp + = VN_CAST(iap->getIndexDefaultedValuep(idxp->toSizeT()), Const)) { + APPLYING(FOLD_ARRAYSEL_TABLE) { + replace(new DfgConst{m_dfg, valp->fileline(), valp->num()}); + return; + } + } + } + } + return; + } if (DfgSpliceArray* const splicep = srcp->cast()) { DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT()); diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index 4da947664..52d5717fe 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -27,6 +27,7 @@ // Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests). // clang-format off #define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ARRAYSEL_TABLE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_LHS_OF_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \ diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 2d679a8de..98d9146be 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -50,6 +50,9 @@ module t ( assign unitArrayParts[0][1] = rand_a[1]; assign unitArrayParts[0][9] = rand_a[9]; + // Complicated way to write constant 0 that only Dfg can decipher + wire [63:0] convoluted_zero = (({64{rand_a[0]}} & ~{64{rand_a[0]}})); + `signal(FOLD_UNARY_LogNot, !const_a[0]); `signal(FOLD_UNARY_Negate, -const_a); `signal(FOLD_UNARY_Not, ~const_a); @@ -133,6 +136,13 @@ module t ( `signal(FOLD_SEL, const_a[3:1]); + int fold_arraysel_table_one; + ffs ffs_a(convoluted_zero[0] ? 8'hff: 8'd2, fold_arraysel_table_one); + int fold_arraysel_table_two; + ffs ffs_b(convoluted_zero[1] ? 8'hff: 8'd7, fold_arraysel_table_two); + `signal(FOLD_ARRAYSEL_TABLE_ONE, fold_arraysel_table_one); + `signal(FOLD_ARRAYSEL_TABLE_TWO, fold_arraysel_table_two); + `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, rand_a + const_a); `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, rand_a + ~rand_a); `signal(SWAP_VAR_IN_COMMUTATIVE_BINARY, rand_b + rand_a); @@ -427,3 +437,25 @@ module t ( assign zero = '0; assign ones = '1; endmodule + +module ffs( + input logic [7:0] i, + output int o +); + // V3Table will convert this + always_comb begin + // verilator lint_off CASEOVERLAP + casez (i) + 8'b1???????: o = 7; + 8'b?1??????: o = 6; + 8'b??1?????: o = 5; + 8'b???1????: o = 4; + 8'b????1???: o = 3; + 8'b?????1??: o = 2; + 8'b??????1?: o = 1; + 8'b???????1: o = 0; + 8'b00000000: o = -1; + endcase + // verilator lint_on CASEOVERLAP + end +endmodule From 64d680c9ba910a0f62576a47b3cc8147b0f4d6f1 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 13 Jun 2026 08:40:36 -0400 Subject: [PATCH 24/89] CI: Ubuntu 26.04 (#7776) --- .github/workflows/build-test.yml | 128 ++++++++++++++++++++----------- 1 file changed, 85 insertions(+), 43 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 772fc1c95..d9fea2352 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -28,6 +28,38 @@ concurrency: jobs: + build-2604-gcc: + name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + uses: ./.github/workflows/reusable-build.yml + with: + sha: ${{ github.sha }} + os: ${{ matrix.os }} + os-name: linux + cc: ${{ matrix.cc }} + dev-asan: ${{ matrix.asan }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + - {os: ubuntu-26.04, cc: gcc, asan: 0} + + build-2604-clang: + name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + uses: ./.github/workflows/reusable-build.yml + with: + sha: ${{ github.sha }} + os: ${{ matrix.os }} + os-name: linux + cc: ${{ matrix.cc }} + dev-asan: ${{ matrix.asan }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + - {os: ubuntu-26.04, cc: clang, asan: 1} + build-2404-gcc: name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} uses: ./.github/workflows/reusable-build.yml @@ -58,7 +90,7 @@ jobs: fail-fast: false matrix: include: - - {os: ubuntu-24.04, cc: clang, asan: 1} + - {os: ubuntu-24.04, cc: clang, asan: 0} build-2204-gcc: name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} @@ -76,22 +108,6 @@ jobs: include: - {os: ubuntu-22.04, cc: gcc, asan: 0} - build-2204-clang: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} - uses: ./.github/workflows/reusable-build.yml - with: - sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-22.04, cc: clang, asan: 0} - build-osx-gcc: name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} uses: ./.github/workflows/reusable-build.yml @@ -160,6 +176,54 @@ jobs: path: ${{ github.workspace }}/repo/verilator.zip name: verilator-win.zip + test-2604-gcc: + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + needs: build-2604-gcc + uses: ./.github/workflows/reusable-test.yml + with: + archive: ${{ needs.build-2604-gcc.outputs.archive }} + os: ${{ matrix.os }} + cc: ${{ matrix.cc }} + reloc: ${{ matrix.reloc }} + suite: ${{ matrix.suite }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + # Ubuntu 26.04 gcc + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-0} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-1} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-2} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-3} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-0} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-1} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-2} + + test-2604-clang: + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + needs: build-2604-clang + uses: ./.github/workflows/reusable-test.yml + with: + archive: ${{ needs.build-2604-clang.outputs.archive }} + os: ${{ matrix.os }} + cc: ${{ matrix.cc }} + reloc: ${{ matrix.reloc }} + suite: ${{ matrix.suite }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + # Ubuntu 26.04 clang + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-0} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-1} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-2} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-3} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-0} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-1} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-2} + test-2404-gcc: name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} needs: build-2404-gcc @@ -232,30 +296,6 @@ jobs: - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-1} - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-2} - test-2204-clang: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} - needs: build-2204-clang - uses: ./.github/workflows/reusable-test.yml - with: - archive: ${{ needs.build-2204-clang.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} - suite: ${{ matrix.suite }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - # Ubuntu 22.04 clang, also test relocation - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-0} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-1} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-2} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-3} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-0} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-1} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-2} - lint-py: name: Lint Python uses: ./.github/workflows/reusable-lint-py.yml @@ -264,17 +304,19 @@ jobs: name: Test suite passed if: always() needs: + - build-2604-gcc + - build-2604-clang - build-2404-gcc - build-2404-clang - build-2204-gcc - - build-2204-clang - build-osx-gcc - build-osx-clang - build-windows + - build-2404-gcc + - build-2404-clang - test-2404-gcc - test-2404-clang - test-2204-gcc - - test-2204-clang - lint-py runs-on: ubuntu-24.04 From df1b1577d96c4c4af3c9cabe18077a2f126417a7 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 13 Jun 2026 19:40:29 +0100 Subject: [PATCH 25/89] Deprecate isolate_assignments attribute (#7774) As per discussion. Remove the unsound V3SplitAs pass. The isolate_assignments attribute/directive is now parsed and ignored in the frontend for compatibility but otherwise have no effect. Fixes #7144 --- docs/guide/control.rst | 4 + docs/guide/extensions.rst | 4 + docs/guide/warnings.rst | 5 - src/CMakeLists.txt | 2 - src/Makefile_obj.in | 1 - src/V3AstAttr.h | 3 +- src/V3AstNodeOther.h | 9 - src/V3AstNodes.cpp | 2 - src/V3Control.cpp | 13 +- src/V3LinkDot.cpp | 1 - src/V3LinkParse.cpp | 4 - src/V3SchedAcyclic.cpp | 3 +- src/V3SplitAs.cpp | 195 -------------------- src/V3SplitAs.h | 32 ---- src/Verilator.cpp | 2 - src/verilog.y | 10 +- test_regress/t/t_unopt_combo_isolate.py | 35 +--- test_regress/t/t_unopt_combo_isolate_vlt.py | 36 +--- test_regress/t/t_unoptflat_simple_2_bad.out | 2 +- test_regress/t/t_vlt_syntax_bad.out | 33 ++-- test_regress/t/t_vlt_syntax_bad.vlt | 2 - 21 files changed, 44 insertions(+), 354 deletions(-) delete mode 100644 src/V3SplitAs.cpp delete mode 100644 src/V3SplitAs.h diff --git a/docs/guide/control.rst b/docs/guide/control.rst index a01396040..c26159d56 100644 --- a/docs/guide/control.rst +++ b/docs/guide/control.rst @@ -187,6 +187,10 @@ The grammar of control commands is as follows: .. option:: isolate_assignments -module "" [-task ""] -var "" + Deprecated and has no effect (ignored). + + In versions before 5.050: + Used to indicate that the assignments to this signal in any blocks should be isolated into new blocks. Same as :option:`/*verilator&32;isolate_assignments*/` metacomment. diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index 0e944860e..4ea636af0 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -341,6 +341,10 @@ or "`ifdef`"'s may break other tools. .. option:: /*verilator&32;isolate_assignments*/ + Deprecated and has no effect (ignored). + + In versions before 5.050: + Used after a signal declaration to indicate the assignments to this signal in any blocks should be isolated into new blocks. When large combinatorial block results in a :option:`UNOPTFLAT` warning, attaching diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index d1818afd5..db41d8405 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -2366,11 +2366,6 @@ List Of Warnings the conflict. If you run with :vlopt:`--report-unoptflat`, Verilator will suggest possible candidates for :option:`/*verilator&32;split_var*/`. - The UNOPTFLAT warning may also occur where outputs from a block of logic - are independent, but occur in the same always block. To fix this, use - the :option:`/*verilator&32;isolate_assignments*/` metacomment described - above. - Before version 5.000, the UNOPTFLAT warning may also have been due to clock enables, identified from the reported path going through a clock gating instance. To fix these, the clock_enable meta comment was used. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b4f4be9aa..ba8f6a7b7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -175,7 +175,6 @@ set(HEADERS V3Simulate.h V3Slice.h V3Split.h - V3SplitAs.h V3SplitVar.h V3StackCount.h V3Stats.h @@ -350,7 +349,6 @@ set(COMMON_SOURCES V3Scoreboard.cpp V3Slice.cpp V3Split.cpp - V3SplitAs.cpp V3SplitVar.cpp V3StackCount.cpp V3Stats.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index dcd78a431..eb3b1d50a 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -333,7 +333,6 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Scoreboard.o \ V3Slice.o \ V3Split.o \ - V3SplitAs.o \ V3SplitVar.o \ V3StackCount.o \ V3Subst.o \ diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index dd1754a9d..096923300 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -342,7 +342,6 @@ public: VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT_RD, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT_RW, // V3LinkParse moves to AstVar::sigPublic - VAR_ISOLATE_ASSIGNMENTS, // V3LinkParse moves to AstVar::attrIsolateAssign VAR_SC_BIGUINT, // V3LinkParse moves to AstVar::attrScBigUint VAR_SC_BV, // V3LinkParse moves to AstVar::attrScBv VAR_SFORMAT, // V3LinkParse moves to AstVar::attrSFormat @@ -364,7 +363,7 @@ public: "TYPEID", "TYPENAME", "VAR_BASE", "VAR_FORCEABLE", "VAR_FSM_ARC_INCLUDE_COND", "VAR_FSM_RESET_ARC", "VAR_FSM_STATE", "VAR_PORT_DTYPE", "VAR_PUBLIC", "VAR_PUBLIC_FLAT", - "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_ISOLATE_ASSIGNMENTS", + "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR" }; // clang-format on diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 1e17d672c..1308818be 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -97,7 +97,6 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode { string m_ifacePortName; // Interface port name for out-of-block definition (IEEE 25.8) uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees bool m_taskPublic : 1; // Public task - bool m_attrIsolateAssign : 1; // User isolate_assignments attribute bool m_classMethod : 1; // Class method bool m_didProto : 1; // Did prototype processing bool m_prototype : 1; // Just a prototype @@ -130,7 +129,6 @@ protected: : AstNode{t, fl} , m_name{name} , m_taskPublic{false} - , m_attrIsolateAssign{false} , m_classMethod{false} , m_didProto{false} , m_prototype{false} @@ -179,8 +177,6 @@ public: uint64_t dpiOpenParent() const { return m_dpiOpenParent; } bool taskPublic() const { return m_taskPublic; } void taskPublic(bool flag) { m_taskPublic = flag; } - bool attrIsolateAssign() const { return m_attrIsolateAssign; } - void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } bool classMethod() const { return m_classMethod; } void classMethod(bool flag) { m_classMethod = flag; } bool didProto() const { return m_didProto; } @@ -2134,7 +2130,6 @@ class AstVar final : public AstNode { bool m_funcReturn : 1; // Return variable for a function bool m_attrScBv : 1; // User force bit vector attribute bool m_attrScBigUint : 1; // User force sc_biguint attribute - bool m_attrIsolateAssign : 1; // User isolate_assignments attribute bool m_attrSFormat : 1; // User sformat attribute bool m_attrSplitVar : 1; // declared with split_var metacomment bool m_attrFsmState : 1; // declared with fsm_state metacomment @@ -2197,7 +2192,6 @@ class AstVar final : public AstNode { m_funcReturn = false; m_attrScBv = false; m_attrScBigUint = false; - m_attrIsolateAssign = false; m_attrSFormat = false; m_attrSplitVar = false; m_attrFsmState = false; @@ -2346,7 +2340,6 @@ public: void attrFileDescr(bool flag) { m_fileDescr = flag; } void attrScBv(bool flag) { m_attrScBv = flag; } void attrScBigUint(bool flag) { m_attrScBigUint = flag; } - void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } void attrSFormat(bool flag) { m_attrSFormat = flag; } void attrSplitVar(bool flag) { m_attrSplitVar = flag; } void attrFsmState(bool flag) { m_attrFsmState = flag; } @@ -2522,7 +2515,6 @@ public: bool attrFsmRegisterWrapper() const { return m_attrFsmRegisterWrapper; } bool attrFsmResetArc() const { return m_attrFsmResetArc; } bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; } - bool attrIsolateAssign() const { return m_attrIsolateAssign; } AstIface* sensIfacep() const { return m_sensIfacep; } VRandAttr rand() const { return m_rand; } string verilogKwd() const override; @@ -2534,7 +2526,6 @@ public: // This is getting connected to fromp; keep attributes // Note the method below too if (fromp->attrFileDescr()) attrFileDescr(true); - if (fromp->attrIsolateAssign()) attrIsolateAssign(true); if (fromp->isContinuously()) isContinuously(true); } void propagateWrapAttrFrom(const AstVar* fromp) { diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 746bd4595..2c93b3d58 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -3205,7 +3205,6 @@ void AstVar::dump(std::ostream& str) const { if (noReset()) str << " [!RST]"; if (processQueue()) str << " [PROCQ]"; if (sampled()) str << " [SAMPLED]"; - if (attrIsolateAssign()) str << " [aISO]"; if (attrFsmState()) str << " [aFSMSTATE]"; if (attrFsmResetArc()) str << " [aFSMRESETARC]"; if (attrFsmArcInclCond()) str << " [aFSMARCCOND]"; @@ -3240,7 +3239,6 @@ void AstVar::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, noReset); dumpJsonBoolFuncIf(str, processQueue); dumpJsonBoolFuncIf(str, sampled); - dumpJsonBoolFuncIf(str, attrIsolateAssign); dumpJsonBoolFuncIf(str, attrFsmState); dumpJsonBoolFuncIf(str, attrFsmResetArc); dumpJsonBoolFuncIf(str, attrFsmArcInclCond); diff --git a/src/V3Control.cpp b/src/V3Control.cpp index e95e3c086..3ac7aa727 100644 --- a/src/V3Control.cpp +++ b/src/V3Control.cpp @@ -182,7 +182,6 @@ class V3ControlFTask final { V3ControlVarResolver m_params; // Parameters in function/task V3ControlVarResolver m_ports; // Ports in function/task V3ControlVarResolver m_vars; // Variables in function/task - bool m_isolate = false; // Isolate function return bool m_noinline = false; // Don't inline function/task bool m_public = false; // Public function/task @@ -190,7 +189,6 @@ public: V3ControlFTask() = default; void update(const V3ControlFTask& f) { // Don't overwrite true with false - if (f.m_isolate) m_isolate = true; if (f.m_noinline) m_noinline = true; if (f.m_public) m_public = true; m_params.update(f.m_params); @@ -203,7 +201,6 @@ public: V3ControlVarResolver& ports() { return m_ports; } V3ControlVarResolver& vars() { return m_vars; } - void setIsolate(bool set) { m_isolate = set; } void setNoInline(bool set) { m_noinline = set; } void setPublic(bool set) { m_public = set; } @@ -212,8 +209,6 @@ public: ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::NO_INLINE_TASK}); if (m_public) ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::PUBLIC_TASK}); - // Only functions can have isolate (return value) - if (VN_IS(ftaskp, Func)) ftaskp->attrIsolateAssign(m_isolate); } }; @@ -981,13 +976,7 @@ void V3Control::addVarAttr(FileLine* fl, const string& module, const string& fta // Semantics: Most of the attributes operate on signals if (pattern.empty()) { - if (attr == VAttrType::VAR_ISOLATE_ASSIGNMENTS) { - if (ftask.empty()) { - fl->v3error("isolate_assignments only applies to signals or functions/tasks"); - } else { - V3ControlResolver::s().modules().at(module).ftasks().at(ftask).setIsolate(true); - } - } else if (attr == VAttrType::VAR_PUBLIC) { + if (attr == VAttrType::VAR_PUBLIC) { if (ftask.empty()) { // public module, this is the only exception from var here V3ControlResolver::s().modules().at(module).addModulePragma( diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index a04b7bbdf..1ada078ba 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1727,7 +1727,6 @@ class LinkDotFindVisitor final : public VNVisitor { newvarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); newvarp->funcReturn(true); newvarp->trace(false); // Not user visible - newvarp->attrIsolateAssign(nodep->attrIsolateAssign()); nodep->fvarp(newvarp); // Explicit insert required, as the var name shadows the upper level's task name m_statep->insertSym(m_curSymp, newvarp->name(), newvarp, nullptr /*classOrPackagep*/); diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index e6beb5760..5403d77c2 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -606,10 +606,6 @@ class LinkParseVisitor final : public VNVisitor { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); m_varp->sigUserRWPublic(true); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } else if (nodep->attrType() == VAttrType::VAR_ISOLATE_ASSIGNMENTS) { - UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); - m_varp->attrIsolateAssign(true); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (nodep->attrType() == VAttrType::VAR_SFORMAT) { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); m_varp->attrSFormat(true); diff --git a/src/V3SchedAcyclic.cpp b/src/V3SchedAcyclic.cpp index 36a9cd096..ad8ba454e 100644 --- a/src/V3SchedAcyclic.cpp +++ b/src/V3SchedAcyclic.cpp @@ -351,8 +351,7 @@ std::string reportLoopVars(FileLine* /*warnFl*/, Graph* graphp, SchedAcyclicVarV if (splittable) { ss << V3Error::warnMore() - << "... Suggest add /*verilator split_var*/ or /*verilator " - "isolate_assignments*/ to appropriate variables above.\n"; + << "... Suggest add /*verilator split_var*/ to appropriate variables above.\n"; } V3Stats::addStat("Scheduling, split_var, candidates", splittable); return ss.str(); diff --git a/src/V3SplitAs.cpp b/src/V3SplitAs.cpp deleted file mode 100644 index 74b72a4dd..000000000 --- a/src/V3SplitAs.cpp +++ /dev/null @@ -1,195 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Break always into separate statements to reduce temps -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* -// V3SplitAs's Transformations: -// -// Search each ALWAYS for a VARREF lvalue with a /*isolate_assignments*/ attribute -// If found, color statements with both, assignment to that varref, or other assignments. -// Replicate the Always, and remove mis-colored duplicate code. -// -//************************************************************************* - -#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT - -#include "V3SplitAs.h" - -#include "V3Stats.h" - -VL_DEFINE_DEBUG_FUNCTIONS; - -//###################################################################### -// Find all split variables in a block - -class SplitAsFindVisitor final : public VNVisitorConst { - // STATE - across all visitors - AstVarScope* m_splitVscp = nullptr; // Variable we want to split - - // METHODS - void visit(AstVarRef* nodep) override { - if (nodep->access().isWriteOrRW() && !m_splitVscp && nodep->varp()->attrIsolateAssign()) { - m_splitVscp = nodep->varScopep(); - } - } - void visit(AstExprStmt* nodep) override { - // A function call inside the splitting assignment - // We need to presume the whole call is preserved (if the upper statement is) - // This will break if the m_splitVscp is a "ref" argument to the function, - // but little we can do. - } - void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } - -public: - // CONSTRUCTORS - explicit SplitAsFindVisitor(AstAlways* nodep) { iterateConst(nodep); } - ~SplitAsFindVisitor() override = default; - // METHODS - AstVarScope* splitVscp() const { return m_splitVscp; } -}; - -//###################################################################### -// Remove nodes not containing proper references - -class SplitAsCleanVisitor final : public VNVisitor { - // STATE - across all visitors - const AstVarScope* const m_splitVscp; // Variable we want to split - const bool m_modeMatch; // Remove matching Vscp, else non-matching - // STATE - for current visit position (use VL_RESTORER) - bool m_keepStmt = false; // Current Statement must be preserved - bool m_matches = false; // Statement below has matching lvalue reference - - // METHODS - void visit(AstVarRef* nodep) override { - if (nodep->access().isWriteOrRW()) { - if (nodep->varScopep() == m_splitVscp) { - UINFO(6, " CL VAR " << nodep); - m_matches = true; - } - } - } - void visit(AstNodeStmt* nodep) override { - UINFO(6, " CL STMT " << nodep); - const bool oldKeep = m_keepStmt; - { - VL_RESTORER(m_matches); - m_matches = false; - m_keepStmt = false; - - iterateChildren(nodep); - - if (m_keepStmt || (m_modeMatch ? m_matches : !m_matches)) { - UINFO(6, " Keep STMT " << nodep); - m_keepStmt = true; - } else { - UINFO(6, " Delete STMT " << nodep); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - } - } - // If something below matches, the upper statement remains too. - m_keepStmt = oldKeep || m_keepStmt; - UINFO(9, " upKeep=" << m_keepStmt << " STMT " << nodep); - } - void visit(AstExprStmt* nodep) override { - // A function call inside the splitting assignment - // We need to presume the whole call is preserved (if the upper statement is) - // This will break if the m_splitVscp is a "ref" argument to the function, - // but little we can do. - } - void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - SplitAsCleanVisitor(AstAlways* nodep, AstVarScope* vscp, bool modeMatch) - : m_splitVscp{vscp} - , m_modeMatch{modeMatch} { - iterate(nodep); - } - ~SplitAsCleanVisitor() override = default; -}; - -//###################################################################### -// SplitAs class functions - -class SplitAsVisitor final : public VNVisitor { - // NODE STATE - // AstAlways::user() -> bool. True if already processed - const VNUser1InUse m_inuser1; - - // STATE - across all visitors - VDouble0 m_statSplits; // Statistic tracking - - // METHODS - void splitAlways(AstAlways* nodep, AstVarScope* splitVscp) { - UINFOTREE(9, nodep, "", "in"); - // Duplicate it and link in - // Below cloneTree should perhaps be cloneTreePure, but given - // isolate_assignments is required to hit this code, we presume the user - // knows what they are asking for - AstAlways* const newp = nodep->cloneTree(false); - newp->user1(true); // So we don't clone it again - nodep->addNextHere(newp); - { // Delete stuff we don't want in old - const SplitAsCleanVisitor visitor{nodep, splitVscp, false}; - UINFOTREE(9, nodep, "", "out0"); - } - { // Delete stuff we don't want in new - const SplitAsCleanVisitor visitor{newp, splitVscp, true}; - UINFOTREE(9, newp, "", "out1"); - } - } - - void visit(AstAlways* nodep) override { - // Are there any lvalue references below this? - // There could be more than one. So, we process the first one found first. - const AstVarScope* lastSplitVscp = nullptr; - while (!nodep->user1()) { - // Find any splittable variables - const SplitAsFindVisitor visitor{nodep}; - AstVarScope* const splitVscp = visitor.splitVscp(); - // Now isolate the always - if (splitVscp) { - UINFO(3, "Split " << nodep); - UINFO(3, " For " << splitVscp); - // If we did this last time! Something's stuck! - UASSERT_OBJ(splitVscp != lastSplitVscp, nodep, - "Infinite loop in isolate_assignments removal for: " - << splitVscp->prettyNameQ()); - lastSplitVscp = splitVscp; - splitAlways(nodep, splitVscp); - ++m_statSplits; - } else { - nodep->user1(true); - } - } - } - - void visit(AstNodeExpr*) override {} // Accelerate - void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - explicit SplitAsVisitor(AstNetlist* nodep) { iterate(nodep); } - ~SplitAsVisitor() override { - V3Stats::addStat("Optimizations, isolate_assignments blocks", m_statSplits); - } -}; - -//###################################################################### -// SplitAs class functions - -void V3SplitAs::splitAsAll(AstNetlist* nodep) { - UINFO(2, __FUNCTION__ << ":"); - { SplitAsVisitor{nodep}; } // Destruct before checking - V3Global::dumpCheckGlobalTree("splitas", 0, dumpTreeEitherLevel() >= 3); -} diff --git a/src/V3SplitAs.h b/src/V3SplitAs.h deleted file mode 100644 index dc5c4cce6..000000000 --- a/src/V3SplitAs.h +++ /dev/null @@ -1,32 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Break always into separate statements to reduce temps -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#ifndef VERILATOR_V3SPLITAS_H_ -#define VERILATOR_V3SPLITAS_H_ - -#include "config_build.h" -#include "verilatedos.h" - -class AstNetlist; - -//============================================================================ - -class V3SplitAs final { -public: - static void splitAsAll(AstNetlist* nodep) VL_MT_DISABLED; -}; - -#endif // Guard diff --git a/src/Verilator.cpp b/src/Verilator.cpp index c30f8b28c..b37336a52 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -98,7 +98,6 @@ #include "V3Scoreboard.h" #include "V3Slice.h" #include "V3Split.h" -#include "V3SplitAs.h" #include "V3SplitVar.h" #include "V3Stats.h" #include "V3String.h" @@ -421,7 +420,6 @@ static void process() { // Split single ALWAYS blocks into multiple blocks for better ordering chances if (v3Global.opt.fSplit()) V3Split::splitAll(v3Global.rootp()); - V3SplitAs::splitAsAll(v3Global.rootp()); // Create tracing sample points, before we start eliminating signals if (v3Global.opt.trace()) V3TraceDecl::traceDeclAll(v3Global.rootp()); diff --git a/src/verilog.y b/src/verilog.y index 510545736..d4392a5bc 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3121,7 +3121,7 @@ sigAttr: | yVL_PUBLIC_FLAT { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT}; v3Global.dpi(true); } | yVL_PUBLIC_FLAT_RD { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RD}; v3Global.dpi(true); } | yVL_PUBLIC_FLAT_RW attr_event_controlE { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RW}; v3Global.dpi(true); DEL($2); } - | yVL_ISOLATE_ASSIGNMENTS { $$ = new AstAttrOf{$1, VAttrType::VAR_ISOLATE_ASSIGNMENTS}; } + | yVL_ISOLATE_ASSIGNMENTS { $$ = nullptr; /* Historical, now has no effect */ } | yVL_SC_BIGUINT { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BIGUINT}; } | yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; } | yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; } @@ -4657,12 +4657,12 @@ task_prototype: // ==IEEE: task_prototype function_declaration: // IEEE: function_declaration + function_body_declaration yFUNCTION dynamic_override_specifiersE lifetimeE funcId funcIsolateE tfGuts yENDFUNCTION endLabelE - { $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6); + { $$ = $4; $$->addStmtsp($6); $$->baseOverride($2); $$->lifetime($3); GRAMMARP->endLabel($8, $$, $8); } | yFUNCTION dynamic_override_specifiersE lifetimeE funcIdNew funcIsolateE tfNewGuts yENDFUNCTION endLabelE - { $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6); + { $$ = $4; $$->addStmtsp($6); $$->baseOverride($2); $$->lifetime($3); GRAMMARP->endLabel($8, $$, $8); } @@ -8553,8 +8553,7 @@ vltVarAttrSpecE: ; vltVarAttrFront: - yVLT_ISOLATE_ASSIGNMENTS { $$ = VAttrType::VAR_ISOLATE_ASSIGNMENTS; } - | yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; } + yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; } | yVLT_PUBLIC { $$ = VAttrType::VAR_PUBLIC; v3Global.dpi(true); } | yVLT_PUBLIC_FLAT { $$ = VAttrType::VAR_PUBLIC_FLAT; v3Global.dpi(true); } | yVLT_PUBLIC_FLAT_RD { $$ = VAttrType::VAR_PUBLIC_FLAT_RD; v3Global.dpi(true); } @@ -8568,6 +8567,7 @@ vltVarAttrFront: vltVarAttrFrontDeprecated: yVLT_CLOCK_ENABLE { } | yVLT_CLOCKER { } + | yVLT_ISOLATE_ASSIGNMENTS { } | yVLT_NO_CLOCKER { } ; diff --git a/test_regress/t/t_unopt_combo_isolate.py b/test_regress/t/t_unopt_combo_isolate.py index f7b4618d3..3ffd80545 100755 --- a/test_regress/t/t_unopt_combo_isolate.py +++ b/test_regress/t/t_unopt_combo_isolate.py @@ -9,37 +9,14 @@ import vltest_bootstrap -test.scenarios('simulator') +# Note: This test is historical for the isolate_assignments attribute, which +# was deprecated and has no effect today. This test ensures it still parses +# in SystemVerilog and Verilator control files for backward compatibility. + +test.scenarios('vlt') test.top_filename = "t/t_unopt_combo.v" -out_filename = test.obj_dir + "/V" + test.name + ".tree.json" - -test.compile(verilator_flags2=[ - "--no-json-edit-nums", "+define+ISOLATE", "--stats", "-fno-dfg", "-fno-lift-expr" -]) - -if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+4') - test.file_grep( - out_filename, - r'{"type":"VAR","name":"t.b",.*"loc":"\w,23:[^"]*",.*"origName":"b",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__Vfuncout",.*"loc":"\w,99:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__t_crc",.*"loc":"\w,100:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_crc",.*"loc":"\w,112:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_c",.*"loc":"\w,113:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_c",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) +test.compile(verilator_flags2=["+define+ISOLATE"]) test.execute() diff --git a/test_regress/t/t_unopt_combo_isolate_vlt.py b/test_regress/t/t_unopt_combo_isolate_vlt.py index 35e0cc8d2..1a041dac0 100755 --- a/test_regress/t/t_unopt_combo_isolate_vlt.py +++ b/test_regress/t/t_unopt_combo_isolate_vlt.py @@ -9,38 +9,14 @@ import vltest_bootstrap -test.scenarios('simulator') +# Note: This test is historical for the isolate_assignments attribute, which +# was deprecated and has no effect today. This test ensures it still parses +# in SystemVerilog and Verilator control files for backward compatibility. + +test.scenarios('vlt') test.top_filename = "t/t_unopt_combo.v" -out_filename = test.obj_dir + "/V" + test.name + ".tree.json" - -test.compile(verilator_flags2=[ - "--no-json-edit-nums", "--stats", test.t_dir + - "/t_unopt_combo_isolate.vlt", "-fno-dfg", "-fno-lift-expr" -]) - -if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+4') - test.file_grep( - out_filename, - r'{"type":"VAR","name":"t.b",.*"loc":"\w,23:[^"]*",.*"origName":"b",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__Vfuncout",.*"loc":"\w,104:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__t_crc",.*"loc":"\w,105:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_crc",.*"loc":"\w,115:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_c",.*"loc":"\w,116:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_c",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) +test.compile(verilator_flags2=[test.t_dir + "/t_unopt_combo_isolate.vlt"]) test.execute() diff --git a/test_regress/t/t_unoptflat_simple_2_bad.out b/test_regress/t/t_unoptflat_simple_2_bad.out index 8588ac55c..4c8793b04 100644 --- a/test_regress/t/t_unoptflat_simple_2_bad.out +++ b/test_regress/t/t_unoptflat_simple_2_bad.out @@ -10,5 +10,5 @@ t/t_unoptflat_simple_2.v:16:14: t.x, width 3, circular fanout 2, can split_var ... Candidates with the highest fanout: t/t_unoptflat_simple_2.v:16:14: t.x, width 3, circular fanout 2, can split_var - ... Suggest add /*verilator split_var*/ or /*verilator isolate_assignments*/ to appropriate variables above. + ... Suggest add /*verilator split_var*/ to appropriate variables above. %Error: Exiting due to diff --git a/test_regress/t/t_vlt_syntax_bad.out b/test_regress/t/t_vlt_syntax_bad.out index f1ad706be..65a3941ea 100644 --- a/test_regress/t/t_vlt_syntax_bad.out +++ b/test_regress/t/t_vlt_syntax_bad.out @@ -2,28 +2,25 @@ 9 | public -module "t" @(posedge clk) | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_vlt_syntax_bad.vlt:11:1: isolate_assignments only applies to signals or functions/tasks - 11 | isolate_assignments -module "t" - | ^~~~~~~~~~~~~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:13:1: Argument -match only supported for lint_off - 13 | tracing_off --file "*" -match "nothing" +%Error: t/t_vlt_syntax_bad.vlt:11:1: Argument -match only supported for lint_off + 11 | tracing_off --file "*" -match "nothing" | ^~~~~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:13:1: Argument -scope only supported for tracing_on/off + 13 | lint_off --rule UNOPTFLAT -scope "top*" + | ^~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:14:1: Argument -scope only supported for tracing_on/off_off + 14 | lint_off --rule UNOPTFLAT -scope "top*" -levels 0 + | ^~~~~~~~ %Error: t/t_vlt_syntax_bad.vlt:15:1: Argument -scope only supported for tracing_on/off - 15 | lint_off --rule UNOPTFLAT -scope "top*" - | ^~~~~~~~ + 15 | lint_on --rule UNOPTFLAT -scope "top*" + | ^~~~~~~ %Error: t/t_vlt_syntax_bad.vlt:16:1: Argument -scope only supported for tracing_on/off_off - 16 | lint_off --rule UNOPTFLAT -scope "top*" -levels 0 - | ^~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:17:1: Argument -scope only supported for tracing_on/off - 17 | lint_on --rule UNOPTFLAT -scope "top*" + 16 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 | ^~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:18:1: Argument -scope only supported for tracing_on/off_off - 18 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 - | ^~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:20:1: forceable missing -module - 20 | forceable -module "" -var "net_*" +%Error: t/t_vlt_syntax_bad.vlt:18:1: forceable missing -module + 18 | forceable -module "" -var "net_*" | ^~~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:22:1: missing -var - 22 | forceable -module "top" -var "" +%Error: t/t_vlt_syntax_bad.vlt:20:1: missing -var + 20 | forceable -module "top" -var "" | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_vlt_syntax_bad.vlt b/test_regress/t/t_vlt_syntax_bad.vlt index 2ebcefa7c..0a5cdbce0 100644 --- a/test_regress/t/t_vlt_syntax_bad.vlt +++ b/test_regress/t/t_vlt_syntax_bad.vlt @@ -7,8 +7,6 @@ `verilator_config public -module "t" @(posedge clk) -// only signals/functions/tasks -isolate_assignments -module "t" // -match not supported tracing_off --file "*" -match "nothing" // -scope not supported From 44bd8a0c14c44c78f6b073bfc432a9f655f2d529 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 13 Jun 2026 22:07:14 -0400 Subject: [PATCH 26/89] Commentary: Changes update --- Changes | 29 ++++- docs/spelling.txt | 1 + test_regress/t/t_case_inside_with_x.v | 20 +++- test_regress/t/t_case_priority_overlap.v | 5 +- test_regress/t/t_constraint_redops.v | 14 +-- test_regress/t/t_cover_fsm_concat_unsup.v | 6 +- test_regress/t/t_cover_fsm_sel.out | 22 ++-- test_regress/t/t_cover_fsm_sel.v | 22 ++-- test_regress/t/t_cover_fsm_sel_assign.out | 21 ++-- test_regress/t/t_cover_fsm_sel_assign.v | 21 ++-- .../t/t_cover_fsm_sel_togglevar_unsup.out | 12 +- .../t/t_cover_fsm_sel_togglevar_unsup.v | 25 ++-- test_regress/t/t_covergroup_param_bins.v | 6 +- test_regress/t/t_disable_fork_nested.v | 113 +++++++++--------- test_regress/t/t_fsm_duplicate.v | 62 ++++------ .../t/t_property_s_eventually_iface_param.v | 10 +- test_regress/t/t_select_bound_side_effect.v | 12 +- test_regress/t/t_stream_unpacked_struct.v | 3 +- 18 files changed, 217 insertions(+), 187 deletions(-) diff --git a/Changes b/Changes index fcb769eae..ced84ea4e 100644 --- a/Changes +++ b/Changes @@ -15,7 +15,7 @@ Verilator 5.049 devel **Important:** -* Support covergroups, coverpoints, and bins (#784) (#7117). [Matthew Ballance] +* Support covergroups, coverpoints, and bins (#784) (#7117) (#7728). [Matthew Ballance] * Support new FST writer API (#6871) (#6992). [Yu-Sheng Lin] Use of FST may requiring installing liblz4 and/or liblz4-dev packages, see docs/install.rst. @@ -27,9 +27,11 @@ Verilator 5.049 devel * Add `--coverage-per-instance` (#7636). [Yogish Sekhar] * Add NOTREDOP error on reduction and negation operators (#7417) (#7623) (#7624). * Add hierarchy-aware reporting to `verilator_coverage` (#7657). [Yogish Sekhar] +* Deprecate isolate_assignments attribute (#7774) (#7144). [Geza Lore, Testorrent USA, Inc.] * Improve `--coverage-fsm` (#7490) (#7529) (#7561) (#7573) (#7619). [Yogish Sekhar] * Change `+verilator+seed` to default to 1, and 0 to randomly select (#7325) (#7516). [Miguel] * Change JSON to include parameter constant mnemonics for FSM Coverage (#7531). [Yogish Sekhar] +* Support assert property 'default disable iff` (#4848) (#7723). [Artur Bieniek, Antmicro Ltd.] * Support printing enum names for %p and %s (#5523) (#7338 repair) (#7521) (#7527). [Nick Brereton] * Support weak `until` / `until_with` property operators (#7290) (#7548) (#7685). [Yilou Wang] * Support `s_eventually` (#7291) (#7508). [Bartłomiej Chmiel, Antmicro Ltd.] @@ -59,6 +61,12 @@ Verilator 5.049 devel * Support if/if-else in properties (#7692). [Artur Bieniek, Antmicro Ltd.] * Support process::self().srand() (#7695). [Igor Zaworski, Antmicro Ltd.] * Support MacOS lldb (#7697). [Tracy Narine] +* Support assoc array methods with wide value types (#7680). [pawelktk] +* Support property case (#7721). [Artur Bieniek, Antmicro Ltd.] +* Support `s_until` and `s_until_with`(#7722). [Artur Bieniek, Antmicro Ltd.] +* Support covergroup runtime model Phase A1 (#7728). [Matthew Ballance] +* Support reduction XOR/AND operations in constraints (#7753). [Kornel Uriasz, Antmicro Ltd.] +* Support unpacked struct stream (#7767). [Nick Brereton] * Optimize emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.] * Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.] * Optimize forced signal handling (#7554 partial) (#7572) (#7594) (#7596). [Krzysztof Bieganski, Artur Bieniek, Antmicro Ltd.] @@ -71,8 +79,16 @@ Verilator 5.049 devel * Optimize runtime assertOn() checks (#7707). [Geza Lore, Testorrent USA, Inc.] * Optimize $countones and $onehot in DFG. [Geza Lore, Testorrent USA, Inc.] * Optimize procedural loop unrolling. [Geza Lore, Testorrent USA, Inc.] +* Optimize V3Gate inlining heuristic (#7716). [Geza Lore, Testorrent USA, Inc.] +* Optimize reset in DFG (#7737). [Geza Lore, Testorrent USA, Inc.] +* Optimize DFG with relaxed live variable analysis (#7739). [Geza Lore, Testorrent USA, Inc.] +* Optimize conditional patterns sharing common MBSs/LSBs in DfgPeephole (#7760). [Geza Lore, Testorrent USA, Inc.] +* Optimize bit select removal earlier in DFG (#7762). [Geza Lore, Testorrent USA, Inc.] +* Optimize away proven redundant case statement assertions (#7771). [Geza Lore, Testorrent USA, Inc.] +* Optimize table lookups in DFG (#7772). [Geza Lore, Testorrent USA, Inc.] * Fix TSP variable ordering for mtasks (#5342) (#7610). [Muzaffer Kal] * Fix inlining static initializer in V3Gate (#5381) (#7503). [Andrew Nolte] [Geza Lore, Testorrent USA, Inc.] +* Fix timed nested fork block with disable (#6720) (#7743). [Marco Bartoli] * Fix segmentation fault when using --trace with --lib-create (#7299) (#7518). [anonkey] * Fix destructive event state before dynamic waits (#7340). [Nick Brereton] * Fix ALWCOMBORDER on variable ordering (#7350) (#7608). [Cookie] @@ -128,6 +144,7 @@ Verilator 5.049 devel * Fix loss of events due to bit shift (#7670). [Artur Bieniek, Antmicro Ltd.] * Fix parameter read through locally-declared interface instance (#7679). [Nick Brereton] * Fix skipping nulls in $sscanf (#7689). +* Fix bounds checks in expressions with read/write references (#7694). [Ryszard Rozak, Antmicro Ltd.] * Fix (const) ref default task argument handling (#7698). [Nick Brereton] * Fix `ref` argument type check for packed arrays with differing range directions (#7700). [Nick Brereton] * Fix ignoring not-found modules with encoded names (#7706). [Igor Zaworski, Antmicro Ltd.] @@ -135,6 +152,16 @@ Verilator 5.049 devel * Fix Makefile action to not write to ${srcdir} (#7715). [Larry Doolittle] * Fix splitting functions containing fork logic (#7717). [Mateusz Gancarz, Antmicro Ltd.] * Fix optimizations of assignments with timing controls (#7718). [Ryszard Rozak, Antmicro Ltd.] +* Fix s_eventually on interface (#7731) (#7733). [Marco Bartoli] +* Fix parameter values in coverage bins widths (#7732) (#7734). [Marco Bartoli] +* Fix configure fall back on dynamic malloc libraries (#7736). [Geza Lore, Testorrent USA, Inc.] +* Fix crash on overlapping priority case. [Geza Lore, Testorrent USA, Inc.] +* Fix s_eventually in parameterized interfaces (#7741). [Nick Brereton] +* Fix dpi export pointers (#7742) (#7751). [Yilin Li] +* Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.] +* Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang] +* Fix 'case (_) inside' with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.] + Verilator 5.048 2026-04-26 diff --git a/docs/spelling.txt b/docs/spelling.txt index a3565cb1d..c41825793 100644 --- a/docs/spelling.txt +++ b/docs/spelling.txt @@ -924,6 +924,7 @@ localparams localtime logicals longint +lookups lossy lsb lubc diff --git a/test_regress/t/t_case_inside_with_x.v b/test_regress/t/t_case_inside_with_x.v index 51a456c7a..935d779e8 100644 --- a/test_regress/t/t_case_inside_with_x.v +++ b/test_regress/t/t_case_inside_with_x.v @@ -18,12 +18,24 @@ module top; always @(posedge clk) begin // verilator lint_off CASEWITHX case (cyc) inside - 3'b000: begin $display("case inside 000"); ++count; end - 3'b001: begin $display("case inside 001"); ++count; end + 3'b000: begin + $display("case inside 000"); + ++count; + end + 3'b001: begin + $display("case inside 001"); + ++count; + end // Should match z - 3'b01?: begin $display("case inside 01?"); ++count; end + 3'b01?: begin + $display("case inside 01?"); + ++count; + end // Should match x - 3'b1xx: begin $display("case inside 1xx"); ++count; end + 3'b1xx: begin + $display("case inside 1xx"); + ++count; + end endcase // verilator lint_on CASEWITHX cyc <= cyc + 3'd1; diff --git a/test_regress/t/t_case_priority_overlap.v b/test_regress/t/t_case_priority_overlap.v index 2de27080a..9b7e794d6 100644 --- a/test_regress/t/t_case_priority_overlap.v +++ b/test_regress/t/t_case_priority_overlap.v @@ -26,8 +26,9 @@ module t; always_comb begin priority casez (in) - 2'b1?, // fully subsumes 2'b11 below on the same case clause - 2'b11: out = 2'b10; + 2'b1?, // fully subsumes 2'b11 below on the same case clause + 2'b11: + out = 2'b10; 2'b0?: out = 2'b01; endcase end diff --git a/test_regress/t/t_constraint_redops.v b/test_regress/t/t_constraint_redops.v index e285f8e24..5eff12a63 100644 --- a/test_regress/t/t_constraint_redops.v +++ b/test_regress/t/t_constraint_redops.v @@ -83,13 +83,13 @@ class test_redops_bitfields #(RANDVAL_BITWIDTH=8); endclass module t; - test_redops_bitfields #(.RANDVAL_BITWIDTH(1)) redops_1bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(8)) redops_8bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(16)) redops_16bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(32)) redops_32bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(47)) redops_47bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(63)) redops_63bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(64)) redops_64bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(1)) redops_1bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(8)) redops_8bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(16)) redops_16bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(32)) redops_32bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(47)) redops_47bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(63)) redops_63bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(64)) redops_64bit; test_redops_bitfields #(.RANDVAL_BITWIDTH(128)) redops_128bit; initial begin diff --git a/test_regress/t/t_cover_fsm_concat_unsup.v b/test_regress/t/t_cover_fsm_concat_unsup.v index 1f9cee77a..a7d9a1f84 100644 --- a/test_regress/t/t_cover_fsm_concat_unsup.v +++ b/test_regress/t/t_cover_fsm_concat_unsup.v @@ -5,9 +5,9 @@ // SPDX-License-Identifier: CC0-1.0 module t ( - input logic[6:0] a, - input logic b, - output logic c + input logic [6:0] a, + input logic b, + output logic c ); assign c = ({a, b} == 8'h00); diff --git a/test_regress/t/t_cover_fsm_sel.out b/test_regress/t/t_cover_fsm_sel.out index 4913f5502..a87e0569d 100644 --- a/test_regress/t/t_cover_fsm_sel.out +++ b/test_regress/t/t_cover_fsm_sel.out @@ -6,19 +6,16 @@ // SPDX-License-Identifier: CC0-1.0 package P; - typedef struct packed{ - logic [7:0] vs; - } C; - typedef struct packed{ - C a; int b; + typedef struct packed {logic [7:0] vs;} C; + typedef struct packed { + C a; + int b; } B; - typedef struct packed{ - B a; - } A; + typedef struct packed {B a;} A; endpackage module t ( -%000009 input clk +%000009 input clk ); typedef enum logic [1:0] { S_IDLE = 2'd0, @@ -84,9 +81,10 @@ %000003 a.a.a.vs <= a.a.a.vs + 1; %000003 done <= (a.a.a.vs == 8'h1); %000002 if (done) begin -%000001 state <= S_DONE; -%000002 end else begin -%000002 state <= S_RUN; +%000001 state <= S_DONE; + end +%000002 else begin +%000002 state <= S_RUN; end end %000002 S_DONE: state <= S_DONE; diff --git a/test_regress/t/t_cover_fsm_sel.v b/test_regress/t/t_cover_fsm_sel.v index a06275b03..732a63553 100644 --- a/test_regress/t/t_cover_fsm_sel.v +++ b/test_regress/t/t_cover_fsm_sel.v @@ -5,19 +5,16 @@ // SPDX-License-Identifier: CC0-1.0 package P; - typedef struct packed{ - logic [7:0] vs; - } C; - typedef struct packed{ - C a; int b; + typedef struct packed {logic [7:0] vs;} C; + typedef struct packed { + C a; + int b; } B; - typedef struct packed{ - B a; - } A; + typedef struct packed {B a;} A; endpackage module t ( - input clk + input clk ); typedef enum logic [1:0] { S_IDLE = 2'd0, @@ -74,9 +71,10 @@ module t ( a.a.a.vs <= a.a.a.vs + 1; done <= (a.a.a.vs == 8'h1); if (done) begin - state <= S_DONE; - end else begin - state <= S_RUN; + state <= S_DONE; + end + else begin + state <= S_RUN; end end S_DONE: state <= S_DONE; diff --git a/test_regress/t/t_cover_fsm_sel_assign.out b/test_regress/t/t_cover_fsm_sel_assign.out index c033ed2ef..1f0c3983e 100644 --- a/test_regress/t/t_cover_fsm_sel_assign.out +++ b/test_regress/t/t_cover_fsm_sel_assign.out @@ -6,11 +6,11 @@ // SPDX-License-Identifier: CC0-1.0 module t #( - parameter int unsigned W = 16, - parameter int unsigned D = 4, - parameter int unsigned BW = 2 + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 ) ( -%000009 input clk +%000009 input clk ); typedef enum logic [1:0] { S_IDLE = 2'd0, @@ -30,8 +30,7 @@ begin %000001 logic [D-1:0][W-1:0] s; begin -%000009 always_ff @(posedge clk) -%000009 s[b] <= a; +%000009 always_ff @(posedge clk) s[b] <= a; end end @@ -71,12 +70,14 @@ %000002 S_IDLE: %000001 if (start) state <= S_RUN; %000001 else state <= S_IDLE; -%000003 S_RUN: begin; +%000003 S_RUN: begin + ; %000003 done_arr[0] <= (a[0] == 1'b1); %000002 if (done_arr[0]) begin -%000001 state <= S_DONE; -%000002 end else begin -%000002 state <= S_RUN; +%000001 state <= S_DONE; + end +%000002 else begin +%000002 state <= S_RUN; end end %000002 S_DONE: state <= S_DONE; diff --git a/test_regress/t/t_cover_fsm_sel_assign.v b/test_regress/t/t_cover_fsm_sel_assign.v index 293fa4692..9f3ecca24 100644 --- a/test_regress/t/t_cover_fsm_sel_assign.v +++ b/test_regress/t/t_cover_fsm_sel_assign.v @@ -5,11 +5,11 @@ // SPDX-License-Identifier: CC0-1.0 module t #( - parameter int unsigned W = 16, - parameter int unsigned D = 4, - parameter int unsigned BW = 2 + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 ) ( - input clk + input clk ); typedef enum logic [1:0] { S_IDLE = 2'd0, @@ -29,8 +29,7 @@ module t #( begin logic [D-1:0][W-1:0] s; begin - always_ff @(posedge clk) - s[b] <= a; + always_ff @(posedge clk) s[b] <= a; end end @@ -61,12 +60,14 @@ module t #( S_IDLE: if (start) state <= S_RUN; else state <= S_IDLE; - S_RUN: begin; + S_RUN: begin + ; done_arr[0] <= (a[0] == 1'b1); if (done_arr[0]) begin - state <= S_DONE; - end else begin - state <= S_RUN; + state <= S_DONE; + end + else begin + state <= S_RUN; end end S_DONE: state <= S_DONE; diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out index 9883741c3..4b76f7298 100644 --- a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out @@ -1,11 +1,11 @@ -%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:20:14: Coverage ignored for type ASSOCARRAYDTYPE +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:19:16: Coverage ignored for type ASSOCARRAYDTYPE : ... note: In instance 't' - 20 | input P::A a, - | ^ + 19 | input P::A a, + | ^ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:20:14: Coverage ignored for type WILDCARDARRAYDTYPE +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:19:16: Coverage ignored for type WILDCARDARRAYDTYPE : ... note: In instance 't' - 20 | input P::A a, - | ^ + 19 | input P::A a, + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v index 547a14d85..9be3e9a7a 100644 --- a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v @@ -5,21 +5,20 @@ // SPDX-License-Identifier: CC0-1.0 package P; - typedef struct { - logic [7:0] va[int]; - logic [7:0] vw[*]; - } C; - typedef struct { - C a; int b; - } B; - typedef struct { - B a; - } A; + typedef struct { + logic [7:0] va[int]; + logic [7:0] vw[*]; + } C; + typedef struct { + C a; + int b; + } B; + typedef struct {B a;} A; endpackage module t ( - input P::A a, - output logic b, - output logic c + input P::A a, + output logic b, + output logic c ); assign b = (a.a.a.va[0] == 8'h0); assign c = (a.a.a.vw[0] == 8'h0); diff --git a/test_regress/t/t_covergroup_param_bins.v b/test_regress/t/t_covergroup_param_bins.v index ab57a4a62..ece6521cb 100644 --- a/test_regress/t/t_covergroup_param_bins.v +++ b/test_regress/t/t_covergroup_param_bins.v @@ -26,10 +26,10 @@ module t #( covergroup cg; cp: coverpoint value { bins negative = {[PMIN : -1]}; // parameter as range lower bound - bins zero = {0}; + bins zero = {0}; bins positive = {[1 : LMAX]}; // localparam as range upper bound - bins maxv = {LMAX}; // localparam as single value - bins minv = {PMIN}; // parameter as single value + bins maxv = {LMAX}; // localparam as single value + bins minv = {PMIN}; // parameter as single value } endgroup diff --git a/test_regress/t/t_disable_fork_nested.v b/test_regress/t/t_disable_fork_nested.v index ad6a4d51e..de1b2526a 100644 --- a/test_regress/t/t_disable_fork_nested.v +++ b/test_regress/t/t_disable_fork_nested.v @@ -10,45 +10,45 @@ // block keeps iterating. module disable_fork ( - input logic i_clk, + input logic i_clk, output logic [2:0] o_counter ); - time delay1 = 500ns; // min period - time delay2 = 3333ns; // max period + time delay1 = 500ns; // min period + time delay2 = 3333ns; // max period - logic clk_re = 1'b0; // rising edge of the clock - logic [2:0] counter = 3'b000; + logic clk_re = 1'b0; // rising edge of the clock + logic [2:0] counter = 3'b000; - always begin - fork - begin : check1 - #delay1; - #1 disable check2; - fork - begin : check3 - #(delay2 - delay1); - clk_re <= 1'b0; - #1 disable check4; - if (counter < 3'b111) counter <= counter + 3'b001; - end - begin : check4 - @(posedge i_clk); - clk_re <= 1'b1; - counter <= 3'b000; - #1 disable check3; - end - join - end - begin : check2 - @(posedge i_clk); + always begin + fork + begin : check1 + #delay1; + #1 disable check2; + fork + begin : check3 + #(delay2 - delay1); clk_re <= 1'b0; - #1 disable check1; + #1 disable check4; if (counter < 3'b111) counter <= counter + 3'b001; - end - join - end + end + begin : check4 + @(posedge i_clk); + clk_re <= 1'b1; + counter <= 3'b000; + #1 disable check3; + end + join + end + begin : check2 + @(posedge i_clk); + clk_re <= 1'b0; + #1 disable check1; + if (counter < 3'b111) counter <= counter + 3'b001; + end + join + end - assign o_counter = counter; + assign o_counter = counter; endmodule // verilog_format: off @@ -57,30 +57,33 @@ endmodule // verilog_format: on module t; - logic clk; - logic [2:0] counter; + logic clk; + logic [2:0] counter; - task clk_cycle(input time half_period); - clk = 1'b1; - #half_period; - clk = 1'b0; - #half_period; - endtask : clk_cycle + task clk_cycle(input time half_period); + clk = 1'b1; + #half_period; + clk = 1'b0; + #half_period; + endtask : clk_cycle - initial begin - // Fast clock (period below delay1): every edge arrives before the - // min-period timeout, so the counter saturates at its max. - repeat (100) clk_cycle(200ns); - $display("Fast clock (200ns half-period): o_counter=%0d", counter); - `checkh(counter, 3'h7); - // Slow clock (period above delay2): the nested fork path runs, which - // only works if disabling check1 releases the inner fork..join. - repeat (100) clk_cycle(5400ns); - $display("Slow clock (5400ns half-period): o_counter=%0d", counter); - `checkh(counter, 3'h3); - $write("*-* All Finished *-*\n"); - $finish; - end + initial begin + // Fast clock (period below delay1): every edge arrives before the + // min-period timeout, so the counter saturates at its max. + repeat (100) clk_cycle(200ns); + $display("Fast clock (200ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h7); + // Slow clock (period above delay2): the nested fork path runs, which + // only works if disabling check1 releases the inner fork..join. + repeat (100) clk_cycle(5400ns); + $display("Slow clock (5400ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h3); + $write("*-* All Finished *-*\n"); + $finish; + end - disable_fork a_inst(.i_clk(clk), .o_counter(counter)); + disable_fork a_inst ( + .i_clk(clk), + .o_counter(counter) + ); endmodule diff --git a/test_regress/t/t_fsm_duplicate.v b/test_regress/t/t_fsm_duplicate.v index 20d7fd62e..c0edc5755 100644 --- a/test_regress/t/t_fsm_duplicate.v +++ b/test_regress/t/t_fsm_duplicate.v @@ -4,15 +4,14 @@ // SPDX-FileCopyrightText: 2026 Antmicro // SPDX-License-Identifier: CC0-1.0 -module rr -#( +module rr #( ) ( input logic clk, input logic rst, - input logic [7:0] data, - input logic data_q + input logic [7:0] data, + input logic data_q ); - logic a; + logic a; logic [15:0] dcnt; typedef enum logic [7:0] { S0, @@ -21,23 +20,21 @@ module rr S3 } state_t; state_t state_d, state_q; - always_ff @(posedge clk or negedge rst) - if (!rst) state_q <= S0; + always_ff @(posedge clk or negedge rst) if (!rst) state_q <= S0; always_ff @(posedge clk) unique case (state_q) - S1: if (a) dcnt[7:0] <= data; - S2: if (a) dcnt[15:8] <= data; - S3: if (data_q) dcnt <= dcnt - 1; + S1: if (a) dcnt[7:0] <= data; + S2: if (a) dcnt[15:8] <= data; + S3: if (data_q) dcnt <= dcnt - 1; default: dcnt <= dcnt; endcase endmodule -module re -#( +module re #( ) ( input logic clk, input logic rst, output logic o, - input unused0, /* block optimizations */ + input unused0, /* block optimizations */ input unused1, input unused2, input unused3, @@ -85,20 +82,18 @@ module re S1 } state_t; state_t state_d, state_q; - always_ff @(posedge clk or negedge rst) - if (!rst) state_q <= S0; + always_ff @(posedge clk or negedge rst) if (!rst) state_q <= S0; always_ff @(posedge clk) unique case (state_q) S1: o <= dcnt[0]; - default: o <= '0; + default: o <= '0; endcase initial begin $write("*-* All Finished *-*\n"); $finish; end endmodule -module rh -#( +module rh #( ) ( input logic clk ); @@ -110,24 +105,21 @@ module rh rr xrr ( .clk, .rst(rst), - .data (a), - .data_q (b & c) + .data(a), + .data_q(b & c) ); re xre ( .clk, .rst(rst), - .o (d) + .o(d) ); endmodule -module U -#( +module U #( ) ( input clk, input rst ); - rh xrh ( - .clk (clk) - ); + rh xrh (.clk(clk)); endmodule module C #( ) ( @@ -139,9 +131,7 @@ module C #( .rst ); endmodule -module A #( -) ( -); +module A #() (); logic clk; logic rst; C c0 ( @@ -153,9 +143,7 @@ module A #( .rst ); endmodule -module B #( -) ( -); +module B #() (); logic clk; logic rst; C xC ( @@ -163,11 +151,7 @@ module B #( .rst ); endmodule -module t #( -) ( -); - B b ( - ); - A a ( - ); +module t #() (); + B b (); + A a (); endmodule diff --git a/test_regress/t/t_property_s_eventually_iface_param.v b/test_regress/t/t_property_s_eventually_iface_param.v index 8d44f6373..6a76feabf 100644 --- a/test_regress/t/t_property_s_eventually_iface_param.v +++ b/test_regress/t/t_property_s_eventually_iface_param.v @@ -4,7 +4,11 @@ // SPDX-FileCopyrightText: 2026 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 -interface iface_if #(parameter int W = 8) (input bit clk); +interface iface_if #( + parameter int W = 8 +) ( + input bit clk +); logic [W-1:0] sig = 0; int passed = 0; assert property (@(posedge clk) s_eventually (sig == 1)) passed++; @@ -18,8 +22,8 @@ module t; // Two distinct specializations: V3Param clones the interface into two // modules, each with its own s_eventually tracking. The generated final // block must stay per-module. - iface_if #(.W(4)) a(.clk(clk)); - iface_if #(.W(8)) b(.clk(clk)); + iface_if #(.W(4)) a (.clk(clk)); + iface_if #(.W(8)) b (.clk(clk)); always @(posedge clk) begin ++cyc; diff --git a/test_regress/t/t_select_bound_side_effect.v b/test_regress/t/t_select_bound_side_effect.v index 5fd21bdc8..b6a182d04 100644 --- a/test_regress/t/t_select_bound_side_effect.v +++ b/test_regress/t/t_select_bound_side_effect.v @@ -32,10 +32,10 @@ module t; if (i < 5) `checkh(arr[i], expected); endtask - task automatic add_z(inout int a); - a += z; - z++; - endtask + task automatic add_z(inout int a); + a += z; + z++; + endtask task automatic assign_side_effect_inout(input int i, input int expected); if (i < 5) arr[i] = 1; @@ -63,8 +63,8 @@ module t; arr[get_y()] = i; if (y < 5) `checkh(arr[y], i); `checkh(y, 2 * i + 1); - arr[get_y() % (i + 1)] = i; - if (y % (i + 1) < 5) `checkh(arr[y % (i + 1)], i); + arr[get_y()%(i+1)] = i; + if (y % (i + 1) < 5) `checkh(arr[y%(i+1)], i); `checkh(y, 2 * (i + 1)); end diff --git a/test_regress/t/t_stream_unpacked_struct.v b/test_regress/t/t_stream_unpacked_struct.v index 509d8a035..8a169c20e 100644 --- a/test_regress/t/t_stream_unpacked_struct.v +++ b/test_regress/t/t_stream_unpacked_struct.v @@ -177,7 +177,8 @@ module t; if ($test$plusargs("t_stream_unpacked_struct_alt")) begin narrow_bits = 12'h123; - end else begin + end + else begin narrow_bits = 12'habd; end /* verilator lint_off WIDTHEXPAND */ From c6a5255ea0f29e74605f503f1d0ad9ad434b95ac Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 13 Jun 2026 22:07:18 -0400 Subject: [PATCH 27/89] Tests: Disable unstable --vltmt tests (#7779) (#7780) (#7781) --- test_regress/t/t_assert_iff_clk.py | 3 ++- test_regress/t/t_covergroup_clocked_sample.py | 3 ++- test_regress/t/t_stream_queue.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test_regress/t/t_assert_iff_clk.py b/test_regress/t/t_assert_iff_clk.py index 35e44000c..fd4b2dc44 100755 --- a/test_regress/t/t_assert_iff_clk.py +++ b/test_regress/t/t_assert_iff_clk.py @@ -9,7 +9,8 @@ import vltest_bootstrap -test.scenarios('simulator') +# Issue #7781 unstable with --vltmt +test.scenarios('simulator_st') test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) diff --git a/test_regress/t/t_covergroup_clocked_sample.py b/test_regress/t/t_covergroup_clocked_sample.py index 9f6b5465d..be9b453d3 100755 --- a/test_regress/t/t_covergroup_clocked_sample.py +++ b/test_regress/t/t_covergroup_clocked_sample.py @@ -10,6 +10,7 @@ import vltest_bootstrap import coverage_covergroup_common -test.scenarios('vlt_all') +# Issue #7779 unstable with --vltmt +test.scenarios('vlt') coverage_covergroup_common.run(test) diff --git a/test_regress/t/t_stream_queue.py b/test_regress/t/t_stream_queue.py index 3a46a7545..f7785cc52 100755 --- a/test_regress/t/t_stream_queue.py +++ b/test_regress/t/t_stream_queue.py @@ -9,7 +9,8 @@ import vltest_bootstrap -test.scenarios('simulator') +# Issue #7780 unstable with --vltmt +test.scenarios('simulator_st') test.compile(verilator_flags2=["--timing"]) From b973b1df5aa282d1f2877609e615b94892478659 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 14 Jun 2026 13:13:47 +0100 Subject: [PATCH 28/89] Fix hang in assertion optimization (#7707 repair) --- src/V3Assert.cpp | 42 ++++++++++++++++++---------- test_regress/t/t_assert_opt_check.py | 4 +-- test_regress/t/t_assert_opt_check.v | 8 ++++++ 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 160121c0b..77214da14 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -659,6 +659,34 @@ class AssertVisitor final : public VNVisitor { iterateChildren(nodep); } + if (nodep->user2()) { + // Combine consecutive assertOn checks if possible + if (AstIf* const backp = VN_CAST(nodep->backp(), If)) { + if (backp->nextp() == nodep // + && backp->user2() // + && backp->condp()->sameTree(nodep->condp())) { + ++m_statAssertOnCombined; + backp->addThensp(nodep->thensp()->unlinkFrBackWithNext()); + nodep->unlinkFrBack(); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } + // Combine nested assertOn checks if possible + if (nodep->thensp() && !nodep->thensp()->nextp() && isEmptyStmt(nodep->elsesp())) { + AstIf* const checkp = VN_CAST(nodep->thensp(), If); + if (checkp // + && checkp->user2() // + && checkp->condp()->sameTree(nodep->condp())) { + ++m_statAssertOnCombined; + nodep->addThensp(checkp->thensp()->unlinkFrBackWithNext()); + VL_DO_DANGLING(checkp->unlinkFrBack(), checkp); + return; + } + } + return; + } + // Swap assertOn check with single statement 'if' statement to bubble up for combining // Note we can't just swap the conditions as they two Ifs have different flags, // so swapping the Ifs themeselves then swapping back the bodies. @@ -684,20 +712,6 @@ class AssertVisitor final : public VNVisitor { } } } - - // Combine consecutive assertOn checks if possible - if (nodep->user2()) { - if (AstIf* const backp = VN_CAST(nodep->backp(), If)) { - if (backp->nextp() == nodep // - && backp->user2() // - && backp->condp()->sameTree(nodep->condp())) { - ++m_statAssertOnCombined; - backp->addThensp(nodep->thensp()->unlinkFrBackWithNext()); - nodep->unlinkFrBack(); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } - } - } } //========== Case assertions diff --git a/test_regress/t/t_assert_opt_check.py b/test_regress/t/t_assert_opt_check.py index b7652f1ab..884e2876c 100755 --- a/test_regress/t/t_assert_opt_check.py +++ b/test_regress/t/t_assert_opt_check.py @@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--binary', '--stats']) test.execute(check_finished=True) -test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 2) -test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 11) +test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 3) +test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 15) test.passes() diff --git a/test_regress/t/t_assert_opt_check.v b/test_regress/t/t_assert_opt_check.v index ccfea327e..7d0f54537 100644 --- a/test_regress/t/t_assert_opt_check.v +++ b/test_regress/t/t_assert_opt_check.v @@ -58,4 +58,12 @@ module t; end end + // Should combine the 2 nested assertOn checks after hoisting + always @(posedge clk) begin + if (assertEnable) begin + // This is an 'assert' with another 'assert' in the fail branch + assert(cntB - 100 == cntA); else assert(cntB == cntA + 100); + end + end + endmodule From 9a0cd289e83737e84ad90ec68206f390d6bdcbc5 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 14 Jun 2026 14:53:40 +0100 Subject: [PATCH 29/89] Fix memory leak in previous patch --- src/V3Assert.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 77214da14..6b3fcb777 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -680,7 +680,7 @@ class AssertVisitor final : public VNVisitor { && checkp->condp()->sameTree(nodep->condp())) { ++m_statAssertOnCombined; nodep->addThensp(checkp->thensp()->unlinkFrBackWithNext()); - VL_DO_DANGLING(checkp->unlinkFrBack(), checkp); + VL_DO_DANGLING(pushDeletep(checkp->unlinkFrBack()), checkp); return; } } From 77e6a21224d76ff2a0335c907c20a25736bbfb9b Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 14 Jun 2026 16:29:27 +0100 Subject: [PATCH 30/89] Internals: Inline AstVar::isToggleCoverable() Inline into the single call site, remove unnecessary isSc() and isPrimaryIO() guards (these flags are set only in a later pass). No functional change. --- src/V3AstNodeOther.h | 6 ------ src/V3Coverage.cpp | 9 ++++++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 1308818be..b08033033 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2458,12 +2458,6 @@ public: bool isWor() const { return varType().isWor(); } bool isWiredNet() const { return varType().isWiredNet(); } bool isTemp() const { return varType().isTemp(); } - bool isToggleCoverable() const { - return ((isIO() || isSignal()) - && (isIO() || isBitLogic()) - // Wrapper would otherwise duplicate wrapped module's coverage - && !isSc() && !isPrimaryIO() && !isConst() && !isDouble() && !isString()); - } bool isClassMember() const { return varType() == VVarType::MEMBER; } bool isVirtIface() const { if (AstIfaceRefDType* const dtp = VN_CAST(dtypep(), IfaceRefDType)) { diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index 6d1ff5bc1..12bb973dc 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -166,10 +166,13 @@ class CoverageVisitor final : public VNVisitor { // METHODS + // Return non-nullptr reason if this variable shouldn't have toggle coverage const char* varIgnoreToggle(const AstVar* nodep) { - // Return true if this shouldn't be traced - // See also similar rule in V3TraceDecl::varIgnoreTrace - if (!nodep->isToggleCoverable()) return "Not relevant signal type"; + const bool cover = nodep->isIO() || (nodep->isSignal() && nodep->isBitLogic()); + if (!cover) return "Not relevant signal"; + if (nodep->isConst()) return "Signal is constant"; + if (nodep->isDouble()) return "Signal is double"; + if (nodep->isString()) return "Signal is string"; if (!v3Global.opt.coverageUnderscore()) { const string prettyName = nodep->prettyName(); if (prettyName[0] == '_') return "Leading underscore"; From 12bcf85d33abad924b49218f9e93f5b09cef03f8 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 14 Jun 2026 09:28:25 -0400 Subject: [PATCH 31/89] Internals: Misc V3Life cleanups. No functional change intended. --- src/V3AstNodeStmt.h | 1 + src/V3AstNodes.cpp | 4 ++++ src/V3Life.cpp | 54 ++++++++++++++++++++++++++++++--------------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 336da7bae..aed79ddc0 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -59,6 +59,7 @@ protected: public: ASTGEN_MEMBERS_AstNodeAssign; // Clone single node, just get same type back. + void dump(std::ostream& str) const override; virtual AstNodeAssign* cloneType(AstNodeExpr* lhsp, AstNodeExpr* rhsp) = 0; bool hasDType() const override VL_MT_SAFE { return true; } virtual bool cleanRhs() const { return true; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 2c93b3d58..ef8713999 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2771,6 +2771,10 @@ void AstNodeArrayDType::dumpJson(std::ostream& str) const { dumpJsonStr(str, "declRange", cvtToStr(declRange())); dumpJsonGen(str); } +void AstNodeAssign::dump(std::ostream& str) const { + this->AstNode::dump(str); + if (timingControlp()) str << " [TIMING=" << nodeAddr(timingControlp()) << "]"; +} string AstPackArrayDType::prettyDTypeName(bool full) const { std::ostringstream os; if (const auto subp = subDTypep()) os << subp->prettyDTypeName(full); diff --git a/src/V3Life.cpp b/src/V3Life.cpp index 77ac85c87..7b4d5ab1a 100644 --- a/src/V3Life.cpp +++ b/src/V3Life.cpp @@ -60,7 +60,7 @@ public: class LifeVarEntry final { // Last assignment to this varscope, nullptr if no longer relevant - AstNodeStmt* m_assignp = nullptr; + AstNodeStmt* m_assignp = nullptr; // Last simple assignment AstConst* m_constp = nullptr; // Known constant value bool m_isNew = true; // Is just created // First access was a set (and thus block above may have a set that can be deleted @@ -77,6 +77,17 @@ public: m_isNew = false; m_setBeforeUse = setBeforeUse; } + string ascii() const { // LCOV_EXCL_START + std::ostringstream os; + os << "[Life "; + if (m_isNew) os << " isNew"; + if (m_setBeforeUse) os << " setBeforeUse"; + if (m_everSet) os << " everSet"; + if (m_assignp) os << " assignp=" << AstNode::nodeAddr(m_assignp); + if (m_constp) os << " constp=" << AstNode::nodeAddr(m_constp); + os << "]"; + return os.str(); + } // LCOV_EXCL_STOP void simpleAssign(AstNodeStmt* nodep) { // New simple A=.... assignment UASSERT_OBJ(!m_isNew, nodep, "Uninitialized new entry"); @@ -84,7 +95,10 @@ public: m_constp = nullptr; m_everSet = true; if (AstNodeAssign* const assp = VN_CAST(nodep, Assign)) { - if (VN_IS(assp->rhsp(), Const)) m_constp = VN_AS(assp->rhsp(), Const); + if (VN_IS(assp->rhsp(), Const)) { + m_constp = VN_AS(assp->rhsp(), Const); + UINFO(9, "assign-const " << assp->rhsp() << " = " << m_constp); + } } } void complexAssign() { // A[x]=... or some complicated assignment @@ -179,6 +193,7 @@ public: // Aha, variable is constant; substitute in. // We'll later constant propagate UINFO(4, " replaceconst: " << varrefp); + UINFO(9, " replaceval: " << constp); varrefp->replaceWith(constp->cloneTree(false)); m_replacedVref = true; VL_DO_DANGLING(varrefp->deleteTree(), varrefp); @@ -264,7 +279,9 @@ class LifeVisitor final : public VNVisitor { LifeBlock* m_lifep = nullptr; // Current active lifetime map for current scope // METHODS - void setNoopt() { + void setNoopt(const char* reasonp) { + (void)reasonp; + // UINFO(9, "setNoopt " << reasonp); m_noopt = true; m_lifep->clear(); } @@ -310,7 +327,7 @@ class LifeVisitor final : public VNVisitor { void visit(AstNodeAssign* nodep) override { if (nodep->isTimingControl() || VN_IS(nodep, AssignForce)) { // V3Life doesn't understand time sense nor force assigns - don't optimize - setNoopt(); + setNoopt("timing|force"); if (nodep->isTimingControl()) m_containsTiming = true; iterateChildren(nodep); return; @@ -325,7 +342,7 @@ class LifeVisitor final : public VNVisitor { // V3Life doesn't understand time sense if (nodep->isTimingControl()) { // Don't optimize - setNoopt(); + setNoopt("assigndly"); m_containsTiming = true; } // Don't treat as normal assign @@ -337,19 +354,19 @@ class LifeVisitor final : public VNVisitor { UINFO(4, " IF " << nodep); // Condition is part of PREVIOUS block iterateAndNextNull(nodep->condp()); - LifeBlock* const prevLifep = m_lifep; - LifeBlock* const ifLifep = new LifeBlock{prevLifep, m_statep}; - LifeBlock* const elseLifep = new LifeBlock{prevLifep, m_statep}; + LifeBlock* const ifLifep = new LifeBlock{m_lifep, m_statep}; + LifeBlock* const elseLifep = new LifeBlock{m_lifep, m_statep}; { + VL_RESTORER(m_lifep); m_lifep = ifLifep; iterateAndNextNull(nodep->thensp()); } { + VL_RESTORER(m_lifep); m_lifep = elseLifep; iterateAndNextNull(nodep->elsesp()); } - m_lifep = prevLifep; - UINFO(4, " join "); + UINFO(4, " join " << nodep); // Find sets on both flows m_lifep->dualBranch(ifLifep, elseLifep); // For the next assignments, clear any variables that were read or written in the block @@ -357,6 +374,7 @@ class LifeVisitor final : public VNVisitor { elseLifep->lifeToAbove(); VL_DO_DANGLING(delete ifLifep, ifLifep); VL_DO_DANGLING(delete elseLifep, elseLifep); + UINFO(4, " if-done " << nodep); } void visit(AstLoop* nodep) override { // Similar problem to AstJumpBlock, don't optimize loop bodies - most are unrolled @@ -366,14 +384,14 @@ class LifeVisitor final : public VNVisitor { VL_RESTORER(m_noopt); VL_RESTORER(m_lifep); m_lifep = new LifeBlock{m_lifep, m_statep}; - setNoopt(); + setNoopt("loop"); iterateAndNextNull(nodep->stmtsp()); UINFO(4, " joinloop"); // For the next assignments, clear any variables that were read or written in the block m_lifep->lifeToAbove(); VL_DO_DANGLING(delete m_lifep, m_lifep); } - if (m_containsTiming) setNoopt(); + if (m_containsTiming) setNoopt("timing"); } void visit(AstJumpBlock* nodep) override { // As with Loop's we can't predict if a JumpGo will kill us or not @@ -384,14 +402,14 @@ class LifeVisitor final : public VNVisitor { VL_RESTORER(m_noopt); VL_RESTORER(m_lifep); m_lifep = new LifeBlock{m_lifep, m_statep}; - setNoopt(); + setNoopt("jumpblock"); iterateAndNextNull(nodep->stmtsp()); UINFO(4, " joinjump"); // For the next assignments, clear any variables that were read or written in the block m_lifep->lifeToAbove(); VL_DO_DANGLING(delete m_lifep, m_lifep); } - if (m_containsTiming) setNoopt(); + if (m_containsTiming) setNoopt("timing"); } void visit(AstNodeCCall* nodep) override { // UINFO(4, " CCALL " << nodep); @@ -399,7 +417,7 @@ class LifeVisitor final : public VNVisitor { // Enter the function and trace it // else is non-inline or public function we optimize separately if (nodep->funcp()->entryPoint()) { - setNoopt(); + setNoopt("ccall"); } else { m_tracingCall = true; iterate(nodep->funcp()); @@ -409,8 +427,8 @@ class LifeVisitor final : public VNVisitor { // UINFO(4, " CFUNC " << nodep); if (!m_tracingCall && !nodep->entryPoint()) return; m_tracingCall = false; - if (nodep->recursive()) setNoopt(); - if (nodep->noLife()) setNoopt(); + if (nodep->recursive()) setNoopt("recursive"); + if (nodep->noLife()) setNoopt("nolife"); if (nodep->dpiImportPrototype() && !nodep->dpiPure()) { m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment } @@ -429,7 +447,7 @@ class LifeVisitor final : public VNVisitor { void visit(AstNode* nodep) override { if (nodep->isTimingControl()) { // V3Life doesn't understand time sense - don't optimize - setNoopt(); + setNoopt("timing"); m_containsTiming = true; } iterateChildren(nodep); From df78db0e73a3d49385748e298763f5d21b14b6ed Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 14 Jun 2026 13:01:59 -0400 Subject: [PATCH 32/89] Fix $fflush and autoflush with --threads (#7782). Fixes #7782. --- Changes | 2 +- include/verilated.cpp | 7 +++++++ include/verilated_funcs.h | 5 +++-- src/V3EmitCFunc.h | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index ced84ea4e..9877ca716 100644 --- a/Changes +++ b/Changes @@ -161,7 +161,7 @@ Verilator 5.049 devel * Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.] * Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang] * Fix 'case (_) inside' with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.] - +* Fix $fflush and autoflush with --threads (#7782). Verilator 5.048 2026-04-26 diff --git a/include/verilated.cpp b/include/verilated.cpp index 6bf868cad..a7d78f3bd 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -318,6 +318,13 @@ void VL_PRINTF_MT(const char* formatp, ...) VL_MT_SAFE { }}); } +void VL_FFLUSH_MT() VL_MT_SAFE { + va_list ap; + VerilatedThreadMsgQueue::post(VerilatedMsg{[=]() { // + Verilated::runFlushCallbacks(); + }}); +} + template static size_t _vl_snprintf_string(std::string& str, const char* format, snprintf_args_ts... args) VL_MT_SAFE { diff --git a/include/verilated_funcs.h b/include/verilated_funcs.h index db4c39e5c..8aebd1e34 100644 --- a/include/verilated_funcs.h +++ b/include/verilated_funcs.h @@ -74,10 +74,8 @@ extern void VL_FATAL_MT(const char* filename, int linenum, const char* hier, extern void VL_WARN_MT(const char* filename, int linenum, const char* hier, const char* msg) VL_MT_SAFE; -// clang-format off /// Print a string, multithread safe. Eventually VL_PRINTF will get called. extern void VL_PRINTF_MT(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; -// clang-format on /// Print a debug message from internals with standard prefix, with printf style format extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; @@ -85,6 +83,9 @@ extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; /// Print a debug message from string via VL_DBG_MSGF inline void VL_DBG_MSGS(const std::string& str) VL_MT_SAFE { VL_DBG_MSGF("%s", str.c_str()); } +/// Flush stdout +extern void VL_FFLUSH_MT() VL_MT_SAFE; + // EMIT_RULE: VL_RANDOM: oclean=dirty inline IData VL_RANDOM_I() VL_MT_SAFE { return vl_rand64(); } inline QData VL_RANDOM_Q() VL_MT_SAFE { return vl_rand64(); } diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 6eedcbc69..969e2fd0b 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -1134,7 +1134,7 @@ public: } void visit(AstFFlush* nodep) override { if (!nodep->filep()) { - putns(nodep, "Verilated::runFlushCallbacks();\n"); + putns(nodep, "VL_FFLUSH_MT();"); } else { putns(nodep, "VL_FFLUSH_I("); iterateAndNextConstNull(nodep->filep()); From 5ab2bf1ec439fe82c50194d9f9981c6c6e8ed573 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 15 Jun 2026 05:42:00 +0100 Subject: [PATCH 33/89] Optimize input combinational logic by change detection (#7784) When a lot of combinational logic is driven from top level inputs, work can be wasted evaluating that logic if the top level inputs don't change. This change adds an optimization by performing a change detect on the top level inputs, and evaluate 'ico' logic only if the top level input actually changed. This especially helps with --hierarchical/--lib-create which runs the 'ico' of each sub-model in the eval settle loop. This was observed to yield 40%+ run-time speedup on some partitioned designs. The added change detection is cheap, so it is emitted even if the 'ico' region is small, and is on by default. The optimization is only sound if the model itself does not write to the top level inputs (otherwise the 'previous value' variables would be out of sync, which are not updated by internal writes.). If we can detect a top level input is written within the design, then for that input, we fall back on always running the relevant logic. With --vpi we cannot prove safety statically, so --vpi will disable this optimisation unless explicitly enabled. (In which case it's the user's responsibility to not write to top level inputs via the VPI.) --- docs/guide/exe_verilator.rst | 15 ++++ src/V3AstNodeOther.h | 4 ++ src/V3AstNodes.cpp | 3 + src/V3LinkLevel.cpp | 1 + src/V3Options.cpp | 6 ++ src/V3Options.h | 3 + src/V3Sched.cpp | 70 ++++++++++++++++--- src/V3Width.cpp | 1 + test_regress/t/t_constraint_json_only.out | 2 +- test_regress/t/t_flag_expand_limit.py | 2 +- test_regress/t/t_json_only_begin_hier.out | 2 +- test_regress/t/t_json_only_first.out | 14 ++-- test_regress/t/t_json_only_flat.out | 16 ++--- test_regress/t/t_json_only_flat_vlvbound.out | 8 +-- test_regress/t/t_json_only_primary_io.out | 6 +- test_regress/t/t_json_only_tag.out | 2 +- test_regress/t/t_opt_balance_cats.py | 2 +- ...sched_ico_change_detect_input_assigned.cpp | 45 ++++++++++++ ..._sched_ico_change_detect_input_assigned.py | 28 ++++++++ ...t_sched_ico_change_detect_input_assigned.v | 38 ++++++++++ ...ed_ico_change_detect_input_assigned_off.py | 27 +++++++ ...ed_ico_change_detect_input_assigned_vpi.py | 27 +++++++ ...ico_change_detect_input_assigned_vpi_on.py | 27 +++++++ .../t/t_wrapper_context__top0.dat.out | 6 +- .../t/t_wrapper_context__top1.dat.out | 8 +-- 25 files changed, 321 insertions(+), 42 deletions(-) create mode 100644 test_regress/t/t_sched_ico_change_detect_input_assigned.cpp create mode 100755 test_regress/t/t_sched_ico_change_detect_input_assigned.py create mode 100644 test_regress/t/t_sched_ico_change_detect_input_assigned.v create mode 100755 test_regress/t/t_sched_ico_change_detect_input_assigned_off.py create mode 100755 test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py create mode 100755 test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 705bf3f71..73ed42486 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -737,6 +737,21 @@ Summary: this is not recommended as may cause additional warnings and ordering issues. +.. option:: -fno-ico-change-detect + + Rarely needed. Disable input change detection in the input combinational + ('ico') region. With change detection enabled (the default, unless + :vlopt:`--vpi` is passed), the input combinational logic is evaluated only + when a top level input has actually changed, rather than unconditionally on + the first scheduling iteration. + + The change detection logic assumes a top level input only ever changes + externally between evaluations. The optimization is automatically disabled + for top level input signals that are written within the design. Accesses via + the VPI cannot be analyzed at compile time, therefore :vlopt:`--vpi` + disables this optimization for all inputs; it may be turned back on by + explicitly passing :vlopt:`-fico-change-detect`. + .. option:: -fno-inline .. option:: -fno-inline-funcs diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index b08033033..f9518bf63 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2138,6 +2138,7 @@ class AstVar final : public AstNode { bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment bool m_fileDescr : 1; // File descriptor bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration + bool m_icoMaybeWritten : 1; // Design might write this input signal - for ico change detect bool m_isConst : 1; // Table contains constant data bool m_isContinuously : 1; // Ever assigned continuously (for force/release) bool m_hasStrengthAssignment : 1; // Is on LHS of assignment with strength specifier @@ -2200,6 +2201,7 @@ class AstVar final : public AstNode { m_attrFsmArcInclCond = false; m_fileDescr = false; m_gotNansiType = false; + m_icoMaybeWritten = false; m_isConst = false; m_isContinuously = false; m_hasStrengthAssignment = false; @@ -2379,6 +2381,8 @@ public: void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; } bool hasUserInit() const { return m_hasUserInit; } void hasUserInit(bool flag) { m_hasUserInit = flag; } + void icoMaybeWritten(bool flag) { m_icoMaybeWritten = flag; } + bool icoMaybeWritten() const { return m_icoMaybeWritten; } bool isDpiOpenArray() const VL_MT_SAFE { return m_isDpiOpenArray; } void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; } bool isHideLocal() const { return m_isHideLocal; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index ef8713999..0707c8d8f 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -630,6 +630,7 @@ void AstVar::combineType(const AstVar* otherp) { varType(otherp->varType()); direction(otherp->direction()); } + if (otherp->icoMaybeWritten()) icoMaybeWritten(true); } void AstVar::combineType(VVarType type) { // These flags get combined with the existing settings of the flags. @@ -3219,6 +3220,7 @@ void AstVar::dump(std::ostream& str) const { str << " [FUNC]"; } if (hasUserInit()) str << " [UINIT]"; + if (icoMaybeWritten()) str << " [ICOMAYBEWRITTEN]"; if (isDpiOpenArray()) str << " [DPIOPENA]"; if (ignorePostWrite()) str << " [IGNPWR]"; if (ignoreSchedWrite()) str << " [IGNWR]"; @@ -3247,6 +3249,7 @@ void AstVar::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, attrFsmResetArc); dumpJsonBoolFuncIf(str, attrFsmArcInclCond); dumpJsonBoolFuncIf(str, attrFileDescr); + dumpJsonBoolFuncIf(str, icoMaybeWritten); dumpJsonBoolFuncIf(str, isDpiOpenArray); dumpJsonBoolFuncIf(str, isFuncReturn); dumpJsonBoolFuncIf(str, isFuncLocal); diff --git a/src/V3LinkLevel.cpp b/src/V3LinkLevel.cpp index 012c87166..066097239 100644 --- a/src/V3LinkLevel.cpp +++ b/src/V3LinkLevel.cpp @@ -291,6 +291,7 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { varp->sigPublic(true); // User needs to be able to get to it... oldvarp->primaryIO(false); varp->primaryIO(true); + varp->icoMaybeWritten(oldvarp->icoMaybeWritten()); if (varp->isRef() || varp->isConstRef()) { varp->v3warn(E_UNSUPPORTED, "Unsupported: ref/const ref as primary input/output: " diff --git a/src/V3Options.cpp b/src/V3Options.cpp index b8d7a0a29..dd26e7e06 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1077,6 +1077,9 @@ void V3Options::notify() VL_MT_DISABLED { // Preprocessor defines based on options used if (timing().isSetTrue()) V3PreShell::defineCmdLine("VERILATOR_TIMING", "1"); + // If VPI is used, and no explicit ico change detect option was passed, disable it by default + if (m_vpi && m_fIcoChangeDetect.isDefault()) m_fIcoChangeDetect.setTrueOrFalse(false); + // === Leave last // Mark options as available m_available = true; @@ -1483,6 +1486,9 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-ffunc-opt-balance-cat", FOnOff, &m_fFuncBalanceCat); DECL_OPTION("-ffunc-opt-split-cat", FOnOff, &m_fFuncSplitCat); DECL_OPTION("-fgate", FOnOff, &m_fGate); + DECL_OPTION("-fico-change-detect", CbFOnOff, [this](bool flag) { // + m_fIcoChangeDetect.setTrueOrFalse(flag); + }); DECL_OPTION("-finline", FOnOff, &m_fInline); DECL_OPTION("-finline-funcs", FOnOff, &m_fInlineFuncs); DECL_OPTION("-finline-funcs-eager", FOnOff, &m_fInlineFuncsEager); diff --git a/src/V3Options.h b/src/V3Options.h index 729c68f47..56ba51f79 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -410,6 +410,8 @@ private: bool m_fFuncBalanceCat = true; // main switch: -fno-func-balance-cat: expansion of C macros bool m_fFuncSplitCat = true; // main switch: -fno-func-split-cat: expansion of C macros bool m_fGate; // main switch: -fno-gate: gate wire elimination + // main switch: -fno-ico-change-detect: input change detection optimization + VOptionBool m_fIcoChangeDetect{VOptionBool::OPT_DEFAULT_TRUE}; bool m_fInline; // main switch: -fno-inline: module inlining bool m_fInlineFuncs = true; // main switch: -fno-inline-funcs: function inlining bool m_fInlineFuncsEager = true; // main switch: -fno-inline-funcs-eager: don't inline eagerly @@ -745,6 +747,7 @@ public: bool fFuncSplitCat() const { return m_fFuncSplitCat; } bool fFunc() const { return fFuncSplitCat() || fFuncBalanceCat(); } bool fGate() const { return m_fGate; } + VOptionBool fIcoChangeDetect() const { return m_fIcoChangeDetect; } bool fInline() const { return m_fInline; } bool fInlineFuncs() const { return m_fInlineFuncs; } bool fInlineFuncsEager() const { return m_fInlineFuncsEager; } diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 8adc0a52e..217cd3f95 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -529,8 +529,46 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, + entry.m_memberp->name()); } + // Create the input change detect SenTrees. + // If there is a lot of combinationallogic hanging of the top level inputs, we can save + // a lot of work by only evaluating it if an input has actually changed. This in + // paticular helps hierarchical models partitioned across combinaitonal boundaries. + // The change detect itself should be fairly cheap otherwise so alway do it. + // For correctness, don't create a change detect for top level inputs also written + // by the design, as the change detect 'previous value' would get out of sync. + // Also omit a SenTree for types that don't have the required '!=' operator. + // Any signal that does not have an explicit change detect trigger will fall back to + // using the 'first iteration' trigger, same as if this optimization was disabled. + std::unordered_map inp2changedp; + std::vector icoChangeSenTreeps; + if (v3Global.opt.fIcoChangeDetect().isTrue()) { + FileLine* const flp = netlistp->fileline(); + AstScope* const scopep = netlistp->topScopep()->scopep(); + for (AstVarScope* vscp = scopep->varsp(); vscp; vscp = VN_AS(vscp->nextp(), VarScope)) { + // Only for top level ports, assume outputs don't change externally + if (!vscp->varp()->isPrimaryInish()) continue; + // Don't do if written by the design - wouldn't update the change detect 'prev' value + if (vscp->varp()->icoMaybeWritten()) continue; + // Don't do if forceable, as we can't see the actual value - this is belt and braces + if (vscp->varp()->isForced()) continue; + // Can't handle unpacked arrays (they have special types when primary input) + if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) continue; + // Similarly to arrays, can't handle SystemC types + if (vscp->varp()->isSc()) continue; + // Create a sen tree triggered when this input changes + AstSenTree*& senTreepr = inp2changedp[vscp]; + UASSERT_OBJ(!senTreepr, vscp, "Duplicate input change detect trigger"); + AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ}; + AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_CHANGED, refp}; + senTreepr = new AstSenTree{flp, senItemp}; + icoChangeSenTreeps.push_back(senTreepr); + } + } + V3Stats::addStat("Scheduling, 'ico' change detect triggers", icoChangeSenTreeps.size()); + // Gather the relevant sensitivity expressions and create the trigger kit - const auto& senTreeps = getSenTreesUsedBy({&logic}); + std::vector senTreeps = getSenTreesUsedBy({&logic}); + senTreeps.insert(senTreeps.end(), icoChangeSenTreeps.begin(), icoChangeSenTreeps.end()); const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBuilder, {}, senTreeps, "ico", extraTriggers, false, false); std::ignore = senExprBuilder.getAndClearResults(); @@ -543,14 +581,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, // Remap sensitivities remapSensitivities(logic, trigKit.mapVec()); + for (auto& pair : inp2changedp) pair.second = trigKit.mapVec().at(pair.second); // Create the inverse map from trigger ref AstSenTree to original AstSenTree V3Order::TrigToSenMap trigToSen; invertAndMergeSenTreeMap(trigToSen, trigKit.mapVec()); - // The trigger top level inputs (first iteration) - AstSenTree* const inputChanged - = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); + // The 'first iteration' trigger for top level inputs - lazy constructed only if needed + AstSenTree* firstIterTriggerp = nullptr; // The DPI Export trigger AstSenTree* const dpiExportTriggered @@ -565,9 +603,19 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, netlistp, {&logic}, trigToSen, "ico", false, false, [&](const AstVarScope* vscp, std::vector& out) { AstVar* const varp = vscp->varp(); - if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) { - out.push_back(inputChanged); + // If it has an explicit change detect trigger, use that, + // otherwise fall back to using the 'first iteration' trigger + auto it = inp2changedp.find(vscp); + if (it != inp2changedp.end()) { + out.push_back(it->second); + } else if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) { + if (!firstIterTriggerp) { + firstIterTriggerp + = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); + } + out.push_back(firstIterTriggerp); } + // Add other triggers if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered); if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) { const auto& ifaceTriggered @@ -593,8 +641,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, // Work statements: Invoke the 'ico' function util::callVoidFunc(icoFuncp)); - // Add the first iteration trigger to the trigger computation function - trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false); + // Add the first iteration trigger to the trigger computation function - if used + if (firstIterTriggerp) { + trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false); + } + + // Release temporary input change detect SenTrees + for (AstSenTree* const senTreep : icoChangeSenTreeps) senTreep->deleteTree(); + icoChangeSenTreeps.clear(); return icoLoop.stmtsp; } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index a323f9336..f5e55d2a7 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3073,6 +3073,7 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(nodep->dtypep(), nodep, "LHS var should be dtype completed"); } // UINFOTREE(9, nodep, "", "VRout"); + if (nodep->access().isWriteOrRW()) nodep->varp()->icoMaybeWritten(true); if (nodep->access().isWriteOrRW() && nodep->varp()->direction() == VDirection::CONSTREF) { nodep->v3error("Assigning to const ref variable: " << nodep->prettyNameQ()); } else if (nodep->access().isWriteOrRW() && nodep->varp()->isInput() diff --git a/test_regress/t/t_constraint_json_only.out b/test_regress/t/t_constraint_json_only.out index ce4b7e0d1..c18d8a989 100644 --- a/test_regress/t/t_constraint_json_only.out +++ b/test_regress/t/t_constraint_json_only.out @@ -29,7 +29,7 @@ {"type":"VAR","name":"state","addr":"(Z)","loc":"d,17:10,17:15","dtypep":"(M)","origName":"state","verilogName":"state","direction":"NONE","lifetime":"VAUTOMI","varType":"MEMBER","dtypeName":"string","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"FUNC","name":"strings_equal","addr":"(AB)","loc":"d,61:16,61:29","dtypep":"(U)","method":true,"cname":"strings_equal", "fvarp": [ - {"type":"VAR","name":"strings_equal","addr":"(BB)","loc":"d,61:16,61:29","dtypep":"(U)","origName":"strings_equal","verilogName":"strings_equal","direction":"OUTPUT","noCReset":true,"isFuncReturn":true,"isFuncLocal":true,"lifetime":"VAUTOM","varType":"VAR","dtypeName":"bit","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} + {"type":"VAR","name":"strings_equal","addr":"(BB)","loc":"d,61:16,61:29","dtypep":"(U)","origName":"strings_equal","verilogName":"strings_equal","direction":"OUTPUT","noCReset":true,"icoMaybeWritten":true,"isFuncReturn":true,"isFuncLocal":true,"lifetime":"VAUTOM","varType":"VAR","dtypeName":"bit","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} ],"classOrPackagep": [], "stmtsp": [ {"type":"VAR","name":"a","addr":"(CB)","loc":"d,61:37,61:38","dtypep":"(M)","origName":"a","verilogName":"a","direction":"INPUT","isFuncLocal":true,"lifetime":"VAUTOMI","varType":"PORT","dtypeName":"string","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, diff --git a/test_regress/t/t_flag_expand_limit.py b/test_regress/t/t_flag_expand_limit.py index ec0cc54a6..d9b19b528 100755 --- a/test_regress/t/t_flag_expand_limit.py +++ b/test_regress/t/t_flag_expand_limit.py @@ -13,6 +13,6 @@ test.scenarios('vlt') test.compile(verilator_flags2=['--expand-limit 1 --stats -fno-dfg']) -test.file_grep(test.stats, r'Optimizations, expand limited\s+(\d+)', 7) +test.file_grep(test.stats, r'Optimizations, expand limited\s+(\d+)', 29) test.passes() diff --git a/test_regress/t/t_json_only_begin_hier.out b/test_regress/t/t_json_only_begin_hier.out index 8e779675c..68cc2bd11 100644 --- a/test_regress/t/t_json_only_begin_hier.out +++ b/test_regress/t/t_json_only_begin_hier.out @@ -2,7 +2,7 @@ "modulesp": [ {"type":"MODULE","name":"test","addr":"(E)","loc":"d,21:8,21:12","origName":"test","verilogName":"test","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"N","addr":"(F)","loc":"d,22:10,22:11","dtypep":"(G)","origName":"N","verilogName":"N","direction":"NONE","isUsedLoopIdx":true,"lifetime":"VSTATICI","varType":"GENVAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"N","addr":"(F)","loc":"d,22:10,22:11","dtypep":"(G)","origName":"N","verilogName":"N","direction":"NONE","isUsedLoopIdx":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"GENVAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"GENBLOCK","name":"FOR_GENERATE","addr":"(H)","loc":"d,24:5,24:8","implied":true,"genforp": [],"itemsp": []}, {"type":"GENBLOCK","name":"FOR_GENERATE[0]","addr":"(I)","loc":"d,25:14,25:24","genforp": [], "itemsp": [ diff --git a/test_regress/t/t_json_only_first.out b/test_regress/t/t_json_only_first.out index f32e2e41d..0f60e9c25 100644 --- a/test_regress/t/t_json_only_first.out +++ b/test_regress/t/t_json_only_first.out @@ -2,13 +2,13 @@ "modulesp": [ {"type":"MODULE","name":"t","addr":"(E)","loc":"d,7:8,7:9","origName":"t","verilogName":"t","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"q","addr":"(F)","loc":"d,16:21,16:22","dtypep":"(G)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(F)","loc":"d,16:21,16:22","dtypep":"(G)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(H)","loc":"d,14:9,14:12","dtypep":"(I)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(J)","loc":"d,15:15,15:16","dtypep":"(G)","origName":"d","verilogName":"d","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"between","addr":"(K)","loc":"d,18:15,18:22","dtypep":"(G)","origName":"between","verilogName":"between","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"direct_named","addr":"(L)","loc":"d,19:9,19:21","dtypep":"(I)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"computed_named","addr":"(M)","loc":"d,20:9,20:23","dtypep":"(I)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"anonymous_expr","addr":"(N)","loc":"d,21:9,21:23","dtypep":"(I)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"between","addr":"(K)","loc":"d,18:15,18:22","dtypep":"(G)","origName":"between","verilogName":"between","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"direct_named","addr":"(L)","loc":"d,19:9,19:21","dtypep":"(I)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"computed_named","addr":"(M)","loc":"d,20:9,20:23","dtypep":"(I)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"anonymous_expr","addr":"(N)","loc":"d,21:9,21:23","dtypep":"(I)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"S_IDLE","addr":"(O)","loc":"d,23:26,23:32","dtypep":"(P)","origName":"S_IDLE","verilogName":"S_IDLE","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"2'h0","addr":"(Q)","loc":"d,23:35,23:40","dtypep":"(R)"} @@ -122,7 +122,7 @@ "stmtsp": [ {"type":"VAR","name":"clk","addr":"(QC)","loc":"d,62:11,62:14","dtypep":"(I)","origName":"clk","verilogName":"clk","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(KC)","loc":"d,63:17,63:18","dtypep":"(G)","origName":"d","verilogName":"d","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"q","addr":"(NC)","loc":"d,64:23,64:24","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(NC)","loc":"d,64:23,64:24","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(SC)","loc":"d,67:12,67:13","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(TC)","loc":"d,67:12,67:13","dtypep":"(G)", @@ -142,7 +142,7 @@ ],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(CC)","loc":"d,50:11,50:14","dtypep":"(I)","origName":"clk","verilogName":"clk","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(FC)","loc":"d,51:23,51:24","dtypep":"(G)","origName":"d","verilogName":"d","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"q","addr":"(ZB)","loc":"d,52:30,52:31","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(ZB)","loc":"d,52:30,52:31","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"IGNORED","addr":"(AD)","loc":"d,55:14,55:21","dtypep":"(XC)","origName":"IGNORED","verilogName":"IGNORED","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"32'sh1","addr":"(BD)","loc":"d,55:24,55:25","dtypep":"(ZC)"} diff --git a/test_regress/t/t_json_only_flat.out b/test_regress/t/t_json_only_flat.out index 727724001..e90bba08b 100644 --- a/test_regress/t/t_json_only_flat.out +++ b/test_regress/t/t_json_only_flat.out @@ -2,16 +2,16 @@ "modulesp": [ {"type":"MODULE","name":"$root","addr":"(F)","loc":"d,7:8,7:9","origName":"$root","verilogName":"$root","level":1,"modPublic":true,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"q","addr":"(G)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(G)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(I)","loc":"d,14:9,14:12","dtypep":"(J)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(K)","loc":"d,15:15,15:16","dtypep":"(H)","origName":"d","verilogName":"d","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.q","addr":"(L)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.q","addr":"(L)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.clk","addr":"(M)","loc":"d,14:9,14:12","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.d","addr":"(N)","loc":"d,15:15,15:16","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.between","addr":"(O)","loc":"d,18:15,18:22","dtypep":"(H)","origName":"between","verilogName":"between","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.direct_named","addr":"(P)","loc":"d,19:9,19:21","dtypep":"(J)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.computed_named","addr":"(Q)","loc":"d,20:9,20:23","dtypep":"(J)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.anonymous_expr","addr":"(R)","loc":"d,21:9,21:23","dtypep":"(J)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.between","addr":"(O)","loc":"d,18:15,18:22","dtypep":"(H)","origName":"between","verilogName":"between","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.direct_named","addr":"(P)","loc":"d,19:9,19:21","dtypep":"(J)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.computed_named","addr":"(Q)","loc":"d,20:9,20:23","dtypep":"(J)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.anonymous_expr","addr":"(R)","loc":"d,21:9,21:23","dtypep":"(J)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.S_IDLE","addr":"(S)","loc":"d,23:26,23:32","dtypep":"(T)","origName":"S_IDLE","verilogName":"S_IDLE","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"2'h0","addr":"(U)","loc":"d,23:35,23:40","dtypep":"(V)"} @@ -30,14 +30,14 @@ ],"attrsp": []}, {"type":"VAR","name":"t.cell1.clk","addr":"(EB)","loc":"d,50:11,50:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell1.d","addr":"(FB)","loc":"d,51:23,51:24","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell1.q","addr":"(GB)","loc":"d,52:30,52:31","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell1.q","addr":"(GB)","loc":"d,52:30,52:31","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell1.IGNORED","addr":"(HB)","loc":"d,55:14,55:21","dtypep":"(BB)","origName":"IGNORED","verilogName":"IGNORED","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"32'sh1","addr":"(IB)","loc":"d,55:24,55:25","dtypep":"(DB)"} ],"attrsp": []}, {"type":"VAR","name":"t.cell2.clk","addr":"(JB)","loc":"d,62:11,62:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell2.d","addr":"(KB)","loc":"d,63:17,63:18","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell2.q","addr":"(LB)","loc":"d,64:23,64:24","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell2.q","addr":"(LB)","loc":"d,64:23,64:24","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"TOPSCOPE","name":"","addr":"(E)","loc":"d,7:8,7:9","senTreesp": [], "scopep": [ {"type":"SCOPE","name":"TOP","addr":"(MB)","loc":"d,7:8,7:9","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(F)", diff --git a/test_regress/t/t_json_only_flat_vlvbound.out b/test_regress/t/t_json_only_flat_vlvbound.out index ff23ac385..1725da9ef 100644 --- a/test_regress/t/t_json_only_flat_vlvbound.out +++ b/test_regress/t/t_json_only_flat_vlvbound.out @@ -4,12 +4,12 @@ "stmtsp": [ {"type":"VAR","name":"i_a","addr":"(G)","loc":"d,8:24,8:27","dtypep":"(H)","origName":"i_a","verilogName":"i_a","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"i_b","addr":"(I)","loc":"d,9:24,9:27","dtypep":"(H)","origName":"i_b","verilogName":"i_b","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"o_a","addr":"(J)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"o_b","addr":"(L)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"o_a","addr":"(J)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"o_b","addr":"(L)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"vlvbound_test.i_a","addr":"(M)","loc":"d,8:24,8:27","dtypep":"(H)","origName":"i_a","verilogName":"i_a","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"vlvbound_test.i_b","addr":"(N)","loc":"d,9:24,9:27","dtypep":"(H)","origName":"i_b","verilogName":"i_b","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"vlvbound_test.o_a","addr":"(O)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"vlvbound_test.o_b","addr":"(P)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"vlvbound_test.o_a","addr":"(O)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"vlvbound_test.o_b","addr":"(P)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"TOPSCOPE","name":"","addr":"(E)","loc":"d,7:8,7:21","senTreesp": [], "scopep": [ {"type":"SCOPE","name":"TOP","addr":"(Q)","loc":"d,7:8,7:21","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(F)", diff --git a/test_regress/t/t_json_only_primary_io.out b/test_regress/t/t_json_only_primary_io.out index 896aa716b..a611f6917 100644 --- a/test_regress/t/t_json_only_primary_io.out +++ b/test_regress/t/t_json_only_primary_io.out @@ -5,8 +5,8 @@ {"type":"VAR","name":"clk","addr":"(F)","loc":"d,13:9,13:12","dtypep":"(G)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a1","addr":"(H)","loc":"d,14:9,14:11","dtypep":"(G)","origName":"a1","verilogName":"a1","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a2","addr":"(I)","loc":"d,15:9,15:11","dtypep":"(G)","origName":"a2","verilogName":"a2","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"ready","addr":"(J)","loc":"d,16:10,16:15","dtypep":"(G)","origName":"ready","verilogName":"ready","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"ready_reg","addr":"(K)","loc":"d,18:8,18:17","dtypep":"(G)","origName":"ready_reg","verilogName":"ready_reg","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"ready","addr":"(J)","loc":"d,16:10,16:15","dtypep":"(G)","origName":"ready","verilogName":"ready","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"ready_reg","addr":"(K)","loc":"d,18:8,18:17","dtypep":"(G)","origName":"ready_reg","verilogName":"ready_reg","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"CELL","name":"and_cell","addr":"(L)","loc":"d,20:11,20:19","origName":"and_cell","verilogName":"and_cell","modp":"(M)", "pinsp": [ {"type":"PIN","name":"a1","addr":"(N)","loc":"d,21:8,21:10","svDotName":true,"modVarp":"(O)","modPTypep":"UNLINKED", @@ -37,7 +37,7 @@ "stmtsp": [ {"type":"VAR","name":"a1","addr":"(O)","loc":"d,30:16,30:18","dtypep":"(G)","origName":"a1","verilogName":"a1","direction":"INPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a2","addr":"(R)","loc":"d,31:16,31:18","dtypep":"(G)","origName":"a2","verilogName":"a2","direction":"INPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"zn","addr":"(U)","loc":"d,32:17,32:19","dtypep":"(G)","origName":"zn","verilogName":"zn","direction":"OUTPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"zn","addr":"(U)","loc":"d,32:17,32:19","dtypep":"(G)","origName":"zn","verilogName":"zn","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(AB)","loc":"d,34:13,34:14","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(BB)","loc":"d,34:13,34:14","dtypep":"(G)", diff --git a/test_regress/t/t_json_only_tag.out b/test_regress/t/t_json_only_tag.out index c0d396689..c7e9999c6 100644 --- a/test_regress/t/t_json_only_tag.out +++ b/test_regress/t/t_json_only_tag.out @@ -9,7 +9,7 @@ {"type":"CELL","name":"itop","addr":"(L)","loc":"d,29:7,29:11","origName":"itop","verilogName":"itop","modp":"(M)","pinsp": [],"paramsp": [],"rangep": [],"intfRefsp": []}, {"type":"VAR","name":"itop","addr":"(N)","loc":"d,29:7,29:11","dtypep":"(O)","origName":"itop__Viftop","verilogName":"itop__Viftop","direction":"NONE","lifetime":"VSTATICI","varType":"IFACEREF","dtypeName":"","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"this_struct","addr":"(P)","loc":"d,31:13,31:24","dtypep":"(Q)","origName":"this_struct","verilogName":"this_struct","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"dotted","addr":"(R)","loc":"d,33:15,33:21","dtypep":"(S)","origName":"dotted","verilogName":"dotted","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"dotted","addr":"(R)","loc":"d,33:15,33:21","dtypep":"(S)","origName":"dotted","verilogName":"dotted","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(T)","loc":"d,33:22,33:23","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(U)","loc":"d,33:22,33:23","dtypep":"(S)", diff --git a/test_regress/t/t_opt_balance_cats.py b/test_regress/t/t_opt_balance_cats.py index 577c19985..d5e863848 100755 --- a/test_regress/t/t_opt_balance_cats.py +++ b/test_regress/t/t_opt_balance_cats.py @@ -14,7 +14,7 @@ test.scenarios('vlt') test.compile( verilator_flags2=["--stats", "--build", "--gate-stmts", "10000", "--expand-limit", "128"]) -test.file_grep(test.stats, r'Optimizations, FuncOpt concat trees balanced\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, FuncOpt concat trees balanced\s+(\d+)', 3) test.file_grep(test.stats, r'Optimizations, FuncOpt concat splits\s+(\d+)', 67) test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp b/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp new file mode 100644 index 000000000..27e7a3d39 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp @@ -0,0 +1,45 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +#include + +#include +#include + +#include VM_PREFIX_INCLUDE + +int main(int argc, char** argv) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->threads(1); + contextp->commandArgs(argc, argv); + + const std::unique_ptr topp{new VM_PREFIX{contextp.get(), "top"}}; + topp->clk = 0; + topp->i = 0; + topp->eval(); + + while ((contextp->time() < 10000) && !contextp->gotFinish()) { + contextp->timeInc(1); + topp->clk = !topp->clk; + // Always set to the same constant value, so change detection will think it's not changing + topp->i = 500000; + topp->eval(); + if (topp->o != topp->i + 10) { + const std::string msg = "%Error: incorrect output, got: " + std::to_string(topp->o) + + " expected: " + std::to_string(topp->i + 10); + vl_fatal(__FILE__, __LINE__, "main", msg.c_str()); + break; + } + } + + if (!contextp->gotFinish()) { + vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish"); + } + topp->final(); + return 0; +} diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.py b/test_regress/t/t_sched_ico_change_detect_input_assigned.py new file mode 100755 index 000000000..c837fa2b9 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp" + ]) + +test.execute() + +# There should be a change detect for 'clk', but not for 'i' +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 1) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.v b/test_regress/t/t_sched_ico_change_detect_input_assigned.v new file mode 100644 index 000000000..0f6f9025d --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.v @@ -0,0 +1,38 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t( + clk, i, o, cyc +); + + input clk, i; + output o, cyc; + + logic clk; + int i; // Primary input that the design also drives + int o; + int cyc = 0; + + // Logic dependent on primary input 'i' + always_comb o = i + 10; + + always @(posedge clk) begin + cyc <= cyc + 1; + // On even cycles, assign 'i' + if (cyc % 2 == 0) i = cyc + 1000; + if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py new file mode 100755 index 000000000..b98a8e1b6 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "-fno-ico-change-detect" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 0) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py new file mode 100755 index 000000000..cd22fef43 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "--vpi" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 0) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py new file mode 100755 index 000000000..afd44e7ce --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "-fico-change-detect", "--vpi" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 1) + +test.passes() diff --git a/test_regress/t/t_wrapper_context__top0.dat.out b/test_regress/t/t_wrapper_context__top0.dat.out index 07d4800da..ae165bca9 100644 --- a/test_regress/t/t_wrapper_context__top0.dat.out +++ b/test_regress/t/t_wrapper_context__top0.dat.out @@ -139,12 +139,12 @@ C 'ft/t_wrapper_context.vl21n3tlinepagev_line/topoblockS21-24,28,3 C 'ft/t_wrapper_context.vl35n3tlinepagev_line/topoblockS35htop0.top' 11 C 'ft/t_wrapper_context.vl36n5tbranchpagev_branch/topoifS36htop0.top' 1 C 'ft/t_wrapper_context.vl36n6tbranchpagev_branch/topoelseS37htop0.top' 10 -C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop0.top' 34 +C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop0.top' 13 C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop0.top' 0 -C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop0.top' 34 +C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop0.top' 13 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop0.top' 0 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==1 && stop==1) => 1htop0.top' 0 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo(stop==0) => 0htop0.top' 0 C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop0.top' 0 C 'ft/t_wrapper_context.vl48n7tbranchpagev_branch/topoifS48-50htop0.top' 1 -C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop0.top' 33 +C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop0.top' 12 diff --git a/test_regress/t/t_wrapper_context__top1.dat.out b/test_regress/t/t_wrapper_context__top1.dat.out index 8ef66f3d0..073c8c4b5 100644 --- a/test_regress/t/t_wrapper_context__top1.dat.out +++ b/test_regress/t/t_wrapper_context__top1.dat.out @@ -139,12 +139,12 @@ C 'ft/t_wrapper_context.vl21n3tlinepagev_line/topoblockS21-24,28,3 C 'ft/t_wrapper_context.vl35n3tlinepagev_line/topoblockS35htop1.top' 6 C 'ft/t_wrapper_context.vl36n5tbranchpagev_branch/topoifS36htop1.top' 1 C 'ft/t_wrapper_context.vl36n6tbranchpagev_branch/topoelseS37htop1.top' 5 -C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop1.top' 19 -C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop1.top' 19 +C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop1.top' 8 +C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop1.top' 8 C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop1.top' 0 -C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop1.top' 18 +C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop1.top' 7 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==1 && stop==1) => 1htop1.top' 1 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo(stop==0) => 0htop1.top' 0 -C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop1.top' 18 +C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop1.top' 7 C 'ft/t_wrapper_context.vl48n7tbranchpagev_branch/topoifS48-50htop1.top' 0 C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop1.top' 0 From a07a980b73cdde207a096e74a797f9cd0cc4da31 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 15 Jun 2026 09:17:41 +0100 Subject: [PATCH 34/89] Internals: Do not overload AstVar::isPrimaryIO() for sampled value vars --- src/V3Sampled.cpp | 2 -- src/V3Sched.cpp | 2 +- src/V3SchedReplicate.cpp | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/V3Sampled.cpp b/src/V3Sampled.cpp index 4fb45e772..841b32d55 100644 --- a/src/V3Sampled.cpp +++ b/src/V3Sampled.cpp @@ -51,8 +51,6 @@ class SampledVisitor final : public VNVisitor { AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()}; m_scopep->modp()->addStmtsp(newvarp); AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; - newvarp->direction(VDirection::INPUT); // Inform V3Sched that it will be driven later - newvarp->primaryIO(true); newvarp->sampled(true); vscp->user1p(newvscp); m_scopep->addVarsp(newvscp); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 217cd3f95..6078fbbf0 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -608,7 +608,7 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, auto it = inp2changedp.find(vscp); if (it != inp2changedp.end()) { out.push_back(it->second); - } else if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) { + } else if (varp->isPrimaryInish() || varp->isSigUserRWPublic() || varp->sampled()) { if (!firstIterTriggerp) { firstIterTriggerp = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 35718b98a..3aa404018 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -150,8 +150,8 @@ public: : SchedReplicateVertex{graphp} , m_vscp{vscp} { // Top level inputs are - if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi() - || varp()->sensIfacep() || varp()->isVirtIface()) { + if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->sampled() + || varp()->isWrittenByDpi() || varp()->sensIfacep() || varp()->isVirtIface()) { addDrivingRegions(INPUT); } // Currently we always execute suspendable processes at the beginning of From 969a775ae503a89fe867f87e6d57c326f2792c0b Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 15 Jun 2026 13:33:55 +0200 Subject: [PATCH 35/89] Support assertion control system tasks in classes and interfaces (#7761) --- src/V3Assert.cpp | 6 - test_regress/t/t_assert_ctl_immediate.out | 19 ++- test_regress/t/t_assert_ctl_immediate.v | 144 ++++++++++++++++++++++ test_regress/t/t_assert_ctl_unsup.out | 124 ++++++++----------- test_regress/t/t_assert_ctl_unsup.v | 134 ++------------------ 5 files changed, 218 insertions(+), 209 deletions(-) diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 6b3fcb777..47c1874f3 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -1001,12 +1001,6 @@ class AssertVisitor final : public VNVisitor { visitAssertionIterate(nodep, nodep->failsp()); } void visit(AstAssertCtl* nodep) override { - if (VN_IS(m_modp, Class) || VN_IS(m_modp, Iface)) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: assertcontrols in classes or interfaces"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; - } - iterateChildren(nodep); if (!resolveAssertType(nodep)) { diff --git a/test_regress/t/t_assert_ctl_immediate.out b/test_regress/t/t_assert_ctl_immediate.out index a10fd21c4..58b76b5f2 100644 --- a/test_regress/t/t_assert_ctl_immediate.out +++ b/test_regress/t/t_assert_ctl_immediate.out @@ -1,6 +1,15 @@ -[0] %Error: t_assert_ctl_immediate.v:51: Assertion failed in top.t.module_with_assertctl: 'assert' failed. --Info: t/t_assert_ctl_immediate.v:51: Verilog $stop, ignored due to +verilator+error+limit -[0] %Error: t_assert_ctl_immediate.v:57: Assertion failed in top.t.module_with_assertctl: 'assert' failed. -[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. -[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:52: Assertion failed in top.t.module_with_assertctl: 'assert' failed. +-Info: t/t_assert_ctl_immediate.v:52: Verilog $stop, ignored due to +verilator+error+limit +[0] %Error: t_assert_ctl_immediate.v:58: Assertion failed in top.t.module_with_assertctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:159: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:132: Assertion failed in top.t.module_with_method_ctl.Ctl.check_positive: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:169: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:93: Assertion failed in top.t.module_with_method_ctl.iface.check_positive: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:180: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:186: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:195: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:199: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:203: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. *-* All Finished *-* diff --git a/test_regress/t/t_assert_ctl_immediate.v b/test_regress/t/t_assert_ctl_immediate.v index 6c5848aaf..cc9d22b1b 100644 --- a/test_regress/t/t_assert_ctl_immediate.v +++ b/test_regress/t/t_assert_ctl_immediate.v @@ -10,6 +10,7 @@ module t ( module_with_assert module_with_assert (clk); module_with_assertctl module_with_assertctl (clk); + module_with_method_ctl module_with_method_ctl (); always @(posedge clk) begin assert (0); @@ -63,3 +64,146 @@ module module_with_assertctl ( f_assert(); end endmodule + +// Assertion control invoked from class methods and interface functions, in a +// single sub-module with a single initial block so the golden output order is +// stable across --fno-inline (otherwise parallel sub-module initials are +// interleaved differently by the inliner). +// +// Covers: +// - class task $assertoff / $asserton +// - class static method $assertcontrol(Off=4 / On=3), IEEE 1800-2023 Table 20-5 +// - $assertkill from a class method +// - assert that lives inside a class method (obeys context-global gating) +// - interface function $assertoff / $asserton +// - assert inside an interface function +// - virtual interface dispatch to an assertion-control function +// - interface class (AstClass with isInterfaceClass) via a concrete impl +// - multiple instances of each thing: OFF via one instance, ON via another +// (assertion control is global per-context, IEEE 1800-2023 20.11) + +interface AssertCtlIf; + function void suppress(); + $assertoff; + endfunction + function void enable(); + $asserton; + endfunction + function void check_positive(int v); + assert (v > 0); + endfunction +endinterface + +// verilog_format: off (verible-verilog-format mangles `pure virtual function`) +interface class IAssertCtl; + pure virtual function void suppress(); + pure virtual function void enable(); +endclass +// verilog_format: on + +class IAssertCtlImpl implements IAssertCtl; + virtual function void suppress(); + $assertoff; + endfunction + virtual function void enable(); + $asserton; + endfunction +endclass + +module module_with_method_ctl; + class Ctl; + virtual AssertCtlIf vif; + static function void off_all(); + $assertcontrol(4); + endfunction + static function void on_all(); + $asserton; + endfunction + function void kill_all(); + $assertkill; + endfunction + function void inst_off(); + $assertoff; + endfunction + function void inst_on(); + $asserton; + endfunction + function void check_positive(int v); + assert (v > 0); + endfunction + function void vif_suppress(); + vif.suppress(); + endfunction + function void vif_enable(); + vif.enable(); + endfunction + endclass + + Ctl c; + Ctl c2; + AssertCtlIf iface (); + AssertCtlIf iface2 (); + IAssertCtlImpl impl; + IAssertCtlImpl impl2; + + initial begin + c = new; + c2 = new; + impl = new; + impl2 = new; + + // --- class method coverage --- + Ctl::off_all(); + assert (0); // gated via class static -> no fire + Ctl::on_all(); + assert (0); // fires + Ctl::off_all(); + c.check_positive(-1); // assert inside class method, gated -> no fire + Ctl::on_all(); + c.check_positive(-2); // assert inside class method, fires + + // --- interface function coverage --- + iface.suppress(); + assert (0); // gated via iface fn -> no fire + iface.enable(); + assert (0); // fires + iface.suppress(); + iface.check_positive(-1); // assert inside iface fn, gated -> no fire + iface.enable(); + iface.check_positive(-2); // assert inside iface fn, fires + + // --- virtual interface dispatch coverage --- + c.vif = iface; + c.vif_suppress(); + assert (0); // gated via virtual interface dispatch -> no fire + c.vif_enable(); + assert (0); // fires + + // --- interface class via concrete impl --- + impl.suppress(); + assert (0); // gated via interface-class impl -> no fire + impl.enable(); + assert (0); // fires + + // --- multiple instances: OFF via one instance, ON via another --- + // Assertion control is global per-context (IEEE 1800-2023 20.11, no scope + // list), so OFF issued via one instance gates every assertion and ON issued + // via a different instance re-enables them. + c.inst_off(); // class: OFF via c + assert (0); // gated -> no fire + c2.inst_on(); // class: ON via c2 + assert (0); // fires + iface.suppress(); // interface: OFF via iface + assert (0); // gated -> no fire + iface2.enable(); // interface: ON via iface2 + assert (0); // fires + impl.suppress(); // interface class: OFF via impl + assert (0); // gated -> no fire + impl2.enable(); // interface class: ON via impl2 + assert (0); // fires + + // --- $assertkill (last: terminal per IEEE 1800-2023 Table 20-5) --- + c.kill_all(); + assert (0); // killed -> no fire + end +endmodule diff --git a/test_regress/t/t_assert_ctl_unsup.out b/test_regress/t/t_assert_ctl_unsup.out index 26e32ff0d..b00a2a58f 100644 --- a/test_regress/t/t_assert_ctl_unsup.out +++ b/test_regress/t/t_assert_ctl_unsup.out @@ -1,123 +1,103 @@ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:25:5: Unsupported: non-constant assert assertion-type expression +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:26:5: Unsupported: non-constant assert assertion-type expression : ... note: In instance 't.unsupported_ctl_type' - 25 | $assertcontrol(Lock, a); + 26 | $assertcontrol(Lock, a); | ^~~~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:27:5: Unsupported: $assertcontrol control_type '2' - 27 | $assertcontrol(Unlock); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:28:5: Unsupported: $assertcontrol control_type '2' + 28 | $assertcontrol(Unlock); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:29:5: Unsupported: $assertcontrol control_type '6' - 29 | $assertcontrol(PassOn); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: $assertcontrol control_type '6' + 30 | $assertcontrol(PassOn); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 30 | $assertpasson; - | ^~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:31:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 31 | $assertpasson(a); + 31 | $assertpasson; | ^~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:32:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 32 | $assertpasson(a, t); + 32 | $assertpasson(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:34:5: Unsupported: $assertcontrol control_type '7' - 34 | $assertcontrol(PassOff); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:33:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 35 | $assertpassoff; + 33 | $assertpasson(a, t); + | ^~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: $assertcontrol control_type '7' + 35 | $assertcontrol(PassOff); | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:36:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 36 | $assertpassoff(a); + 36 | $assertpassoff; | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:37:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 37 | $assertpassoff(a, t); + 37 | $assertpassoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:39:5: Unsupported: $assertcontrol control_type '8' - 39 | $assertcontrol(FailOn); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:38:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 40 | $assertfailon; - | ^~~~~~~~~~~~~ + 38 | $assertpassoff(a, t); + | ^~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: $assertcontrol control_type '8' + 40 | $assertcontrol(FailOn); + | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:41:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 41 | $assertfailon(a); + 41 | $assertfailon; | ^~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:42:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 42 | $assertfailon(a, t); + 42 | $assertfailon(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:44:5: Unsupported: $assertcontrol control_type '9' - 44 | $assertcontrol(FailOff); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:43:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 45 | $assertfailoff; + 43 | $assertfailon(a, t); + | ^~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: $assertcontrol control_type '9' + 45 | $assertcontrol(FailOff); | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:46:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 46 | $assertfailoff(a); + 46 | $assertfailoff; | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:47:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 47 | $assertfailoff(a, t); + 47 | $assertfailoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:49:5: Unsupported: $assertcontrol control_type '10' - 49 | $assertcontrol(NonvacuousOn); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:48:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 50 | $assertnonvacuouson; - | ^~~~~~~~~~~~~~~~~~~ + 48 | $assertfailoff(a, t); + | ^~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: $assertcontrol control_type '10' + 50 | $assertcontrol(NonvacuousOn); + | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:51:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 51 | $assertnonvacuouson(a); + 51 | $assertnonvacuouson; | ^~~~~~~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:52:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 52 | $assertnonvacuouson(a, t); + 52 | $assertnonvacuouson(a); | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:54:5: Unsupported: $assertcontrol control_type '11' - 54 | $assertcontrol(VacuousOff); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:53:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 55 | $assertvacuousoff; - | ^~~~~~~~~~~~~~~~~ + 53 | $assertnonvacuouson(a, t); + | ^~~~~~~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: $assertcontrol control_type '11' + 55 | $assertcontrol(VacuousOff); + | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:56:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 56 | $assertvacuousoff(a); + 56 | $assertvacuousoff; | ^~~~~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:57:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 57 | $assertvacuousoff(a, t); + 57 | $assertvacuousoff(a); | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:64:5: Unsupported: non-const assert control type expression +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:58:5: Unsupported: assert control assertion_type + : ... note: In instance 't.unsupported_ctl_type' + 58 | $assertvacuousoff(a, t); + | ^~~~~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:65:5: Unsupported: non-const assert control type expression : ... note: In instance 't.unsupported_ctl_type_expr' - 64 | $assertcontrol(ctl_type); + 65 | $assertcontrol(ctl_type); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:93:7: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_class' - 93 | $asserton; - | ^~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:99:7: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_class' - 99 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:172:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface' - 172 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:138:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface_class' - 138 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:145:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface_class' - 145 | $asserton; - | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assert_ctl_unsup.v b/test_regress/t/t_assert_ctl_unsup.v index 98afcad10..90d63e86c 100644 --- a/test_regress/t/t_assert_ctl_unsup.v +++ b/test_regress/t/t_assert_ctl_unsup.v @@ -4,15 +4,16 @@ // SPDX-FileCopyrightText: 2024 Antmicro // SPDX-License-Identifier: CC0-1.0 -module t(input logic clk); - unsupported_ctl_type unsupported_ctl_type(clk ? 1 : 2); - unsupported_ctl_type_expr unsupported_ctl_type_expr(); - assert_class assert_class(); - assert_iface assert_iface(); - assert_iface_class assert_iface_class(); +module t ( + input logic clk +); + unsupported_ctl_type unsupported_ctl_type (clk ? 1 : 2); + unsupported_ctl_type_expr unsupported_ctl_type_expr (); endmodule -module unsupported_ctl_type(input int a); +module unsupported_ctl_type ( + input int a +); initial begin let Lock = 1; let Unlock = 2; @@ -64,122 +65,3 @@ module unsupported_ctl_type_expr; $assertcontrol(ctl_type); end endmodule - -module assert_class; - virtual class AssertCtl; - pure virtual function void virtual_assert_ctl(); - endclass - - class AssertCls; - static function void static_function(); - assert(0); - endfunction - static task static_task(); - assert(0); - endtask - function void assert_function(); - assert(0); - endfunction - task assert_task(); - assert(0); - endtask - virtual function void virtual_assert(); - assert(0); - endfunction - endclass - - class AssertOn extends AssertCtl; - virtual function void virtual_assert_ctl(); - $asserton; - endfunction - endclass - - class AssertOff extends AssertCtl; - virtual function void virtual_assert_ctl(); - $assertoff; - endfunction - endclass - - AssertCls assertCls; - AssertOn assertOn; - AssertOff assertOff; - initial begin - $assertoff; - AssertCls::static_function(); - AssertCls::static_task(); - $asserton; - AssertCls::static_function(); - AssertCls::static_task(); - - assertCls = new; - assertOn = new; - assertOff = new; - - assertOff.virtual_assert_ctl(); - assertCls.assert_function(); - assertCls.assert_task(); - assertCls.virtual_assert(); - - assertOn.virtual_assert_ctl(); - assertCls.assert_function(); - assertCls.assert_task(); - assertCls.virtual_assert(); - assertOff.virtual_assert_ctl(); - assertCls.assert_function(); - end -endmodule - -interface Iface; - function void assert_func(); - assert(0); - endfunction - - function void assertoff_func(); - $assertoff; - endfunction - - initial begin - assertoff_func(); - assert(0); - assert_func(); - $asserton; - assert(0); - assert_func(); - end -endinterface - -module assert_iface; - Iface iface(); - virtual Iface vIface = iface; - initial begin - vIface.assert_func(); - vIface.assertoff_func(); - vIface.assert_func(); - - iface.assert_func(); - iface.assertoff_func(); - iface.assert_func(); - end -endmodule - -interface class IfaceClass; - pure virtual function void assertoff_func(); - pure virtual function void assert_func(); -endclass - -class IfaceClassImpl implements IfaceClass; - virtual function void assertoff_func(); - $assertoff; - endfunction - virtual function void assert_func(); - assert(0); - endfunction -endclass - -module assert_iface_class; - IfaceClassImpl ifaceClassImpl = new; - initial begin - ifaceClassImpl.assertoff_func(); - ifaceClassImpl.assert_func(); - end -endmodule From 077558a9b0d6968bd9d3738b054108c28bbd73f7 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 15 Jun 2026 14:36:21 +0200 Subject: [PATCH 36/89] Support cover sequence statement (#7764) --- src/V3AssertNfa.cpp | 167 +++++++++++++++++++--- src/V3AstNodeStmt.h | 6 + src/V3AstNodes.cpp | 8 ++ src/verilog.y | 30 +++- test_regress/t/t_cover_sequence.py | 18 +++ test_regress/t/t_cover_sequence.v | 90 ++++++++++++ test_regress/t/t_cover_sequence_unsup.out | 18 +++ test_regress/t/t_cover_sequence_unsup.py | 18 +++ test_regress/t/t_cover_sequence_unsup.v | 35 +++++ test_regress/t/t_debug_emitv.out | 5 + test_regress/t/t_debug_emitv.v | 2 + test_regress/t/t_sequence_sexpr_unsup.out | 11 -- 12 files changed, 369 insertions(+), 39 deletions(-) create mode 100755 test_regress/t/t_cover_sequence.py create mode 100644 test_regress/t/t_cover_sequence.v create mode 100644 test_regress/t/t_cover_sequence_unsup.out create mode 100755 test_regress/t/t_cover_sequence_unsup.py create mode 100644 test_regress/t/t_cover_sequence_unsup.v diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index 8db3e7761..c5c36178d 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -177,6 +177,10 @@ struct BuildResult final { // Mid-window sources for range delays (pure boolean RHS): match-only (isUnbounded) std::vector midSources; bool errorEmitted = false; // Builder already emitted specific error; skip generic + // For cover_sequence: when true, midSources already enumerate every + // end-of-match, so wireMatchAndMidSources must NOT add the main + // termVtxp -> matchVertex Link (would double-count via the merge vertex). + bool termIsMidMerge = false; bool valid() const { return termVertexp != nullptr; } static BuildResult fail(bool errored = false) { return {nullptr, nullptr, {}, errored}; } static BuildResult failWithError() { return {nullptr, nullptr, {}, true}; } @@ -199,6 +203,10 @@ class SvaNfaBuilder final { // (IEEE 1800-2023 16.12.14 outer-wraps-inner). std::vector m_outerAbortStack; bool m_inUnboundedScope = false; // Sticky: nodes created after inherit liveness + // IEEE 1800-2023 16.14.3 cover sequence: each end-of-match fires the action, + // not just the first. Builder builds parallel-branch (no first-match-wins) + // topology when true. Default false preserves cover_property semantics. + bool m_isCoverSeq = false; AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) { if (m_throughoutStack.empty()) return baseCondp; @@ -406,6 +414,16 @@ class SvaNfaBuilder final { // count is O(1) in range regardless of user input; no adversarial N // blowup is possible. constexpr int kChainLimit = 256; + // IEEE 1800-2023 16.14.3: only a small bounded range before a plain + // boolean enumerates every end-of-match below. The counter FSM drops + // overlapping ends and the nested-sequence merge collapses them, so + // reject those for a cover sequence rather than under-count. + if (m_isCoverSeq && (range > kChainLimit || VN_IS(rhsExprp, SExpr))) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with this ranged cycle delay"); + outErrorEmitted = true; + return false; + } if (range > kChainLimit) { // Large range: counter FSM. Overlapping triggers during an active // count are dropped (non-overlapping semantics only). @@ -428,13 +446,21 @@ class SvaNfaBuilder final { } else { // Pure boolean RHS: register chain. Each mid-position links to // match (match-only); last position is the reject source. - AstVar* const hoistVarp = tryHoistSampled(rhsExprp, flp, range); + // For cover_sequence (IEEE 1800-2023 16.14.3) the advance edge is + // unconditional so every (start, end) pair fires independently -- + // dropping NOT(b) turns "first-match-wins" into "every end fires". + AstVar* const hoistVarp + = m_isCoverSeq ? nullptr : tryHoistSampled(rhsExprp, flp, range); midSources.push_back(currentp); for (int i = 0; i < range; ++i) { SvaStateVertex* const nextVtxp = scopedCreateVertex(); - AstNodeExpr* const notExprp - = new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)}; - guardedEdge(currentp, nextVtxp, notExprp, flp); + if (m_isCoverSeq) { + guardedEdge(currentp, nextVtxp, flp); + } else { + AstNodeExpr* const notExprp + = new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)}; + guardedEdge(currentp, nextVtxp, notExprp, flp); + } if (i < range - 1) midSources.push_back(nextVtxp); currentp = nextVtxp; } @@ -517,6 +543,10 @@ class SvaNfaBuilder final { if (exceedsAssertUnrollLimit(repp, totalSites)) return BuildResult::failWithError(); AstVar* const hoistVarp = tryHoistSampled(exprp, flp, totalSites); + // Cover-sequence (IEEE 1800-2023 16.14.3): collect each end-of-match + // position so they all fire the action, not just the merged terminal. + std::vector consMidSources; + SvaStateVertex* currentp = entryVtxp; for (int i = 0; i < minN; ++i) { if (i > 0) { @@ -532,6 +562,10 @@ class SvaNfaBuilder final { if (isTopLevelStep && (i == 0 || i == minN - 1)) { linkp->m_rejectOnFail = true; } currentp = condVtxp; } + // After minN: currentp is the first valid end-of-match position for [*m:n]. + if (m_isCoverSeq && (repp->unbounded() || repp->maxCountp())) { + consMidSources.push_back(currentp); + } if (repp->unbounded()) { if (minN == 0) { @@ -565,11 +599,19 @@ class SvaNfaBuilder final { guardedLink(nextVtxp, checkVtxp, sampledRefOrClone(hoistVarp, exprp, flp), flp); guardedLink(checkVtxp, mergeVtxp, flp); currentp = checkVtxp; + if (m_isCoverSeq) consMidSources.push_back(checkVtxp); } currentp = mergeVtxp; } // finalCond = nullptr (already checked via Links) - return {currentp, nullptr, {}}; + BuildResult res; + res.termVertexp = currentp; + res.finalCondp = nullptr; + res.midSources = std::move(consMidSources); + // mergeVtxp is the OR of all the end-positions we already pushed to + // midSources, so the main termVtxp -> matchVertex Link would duplicate. + res.termIsMidMerge = m_isCoverSeq && !res.midSources.empty(); + return res; } // always[lo:hi] / s_always[lo:hi] (IEEE 1800-2023 16.12.11). @@ -608,6 +650,16 @@ class SvaNfaBuilder final { UASSERT_OBJ(maxN >= minN, repp, "GotoRep range max < min (V3Width invariant)"); if (exceedsAssertUnrollLimit(repp, maxN)) return BuildResult::failWithError(); + // IEEE 1800-2023 16.14.3: a ranged goto repetition b[->M:N] ends at every + // M..N-th match, but only the shared merge vertex below reaches the + // terminal, so a cover sequence would under-count. Reject the ranged form + // (the single-count b[->N] has one end and is enumerated correctly). + if (m_isCoverSeq && hasMax && maxN > minN) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with a ranged goto repetition"); + return BuildResult::failWithError(); + } + // Wait + match per iter -> 2 sites per iteration; range form needs // sites for every iteration in [0..maxN). NOT($sampled(x)) matches // $sampled(NOT(x)) at the value level (IEEE 1800-2023 16.9.9); @@ -662,6 +714,16 @@ class SvaNfaBuilder final { if (!lhs.valid() || !rhs.valid()) { // LCOV_EXCL_START -- sub-build fail bail return BuildResult::fail(lhs.errorEmitted || rhs.errorEmitted); } // LCOV_EXCL_STOP + // IEEE 1800-2023 16.14.3: a cover sequence counts every end-of-match. A + // sequence operand of 'or' can end more than once, but only its final + // end reaches the merge vertex below, so reject sequence operands rather + // than under-count. Plain boolean disjunction has one end per cycle and + // is handled by the OR-fold. + if (m_isCoverSeq && (lhs.termVertexp != entryVtxp || rhs.termVertexp != entryVtxp)) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with a sequence operand of 'or'"); + return BuildResult::failWithError(); + } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); if (lhs.finalCondp) { guardedLink(lhs.termVertexp, mergeVtxp, sampled(lhs.finalCondp->cloneTreePure(false)), @@ -971,10 +1033,12 @@ class SvaNfaBuilder final { } public: - SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames) + SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames, + bool isCoverSeq = false) : m_graph{graph} , m_modp{modp} - , m_propTempNames{propTempNames} {} + , m_propTempNames{propTempNames} + , m_isCoverSeq{isCoverSeq} {} // Reset scope between antecedent and consequent: liveness must not leak. void resetScope() { @@ -1347,8 +1411,11 @@ class SvaNfaLowering final { // throughout-drop reject; clean up intermediate state signals. // Phase 3: terminalActive and rejectBase from Links to matchVertex. // Builder only adds Links (non-clocked) to matchVertex via addLink in - // wireMatchAndMidSources. - void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs) { + // wireMatchAndMidSources. When outPerMidSrcsp is non-null, also collect + // the per-edge match signal (IEEE 1800-2023 16.14.3 cover sequence: each + // end-of-match fires the action independently, no OR-fold). + void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs, + std::vector* outPerMidSrcsp = nullptr) { for (const SvaTransEdge* const tep : c.edges) { if (tep->toVtxp() != c.graph.m_matchVertexp) continue; const int fi = tep->fromVtxp()->color(); @@ -1360,6 +1427,18 @@ class SvaNfaLowering final { if (snapshotOkp) { srcSigp = new AstLogAnd{c.flp, srcSigp, snapshotOkp->cloneTreePure(false)}; } + if (outPerMidSrcsp) { + // Per-mid signal must also AND in matchCondp (the final boolean + // check, e.g. sampled(b) for `a ##[1:3] b`). assembleResult does + // this for the OR-collapsed terminalActivep; we replicate it + // per-edge here so each end-of-match is gated identically. + AstNodeExpr* perMidp = srcSigp->cloneTreePure(false); + if (c.matchCondp) { + perMidp = new AstLogAnd{c.flp, perMidp, + sampled(c.matchCondp->cloneTreePure(false))}; + } + outPerMidSrcsp->push_back(perMidp); + } if (tep->fromVtxp()->m_isCounter) { sigs.terminalActivep @@ -1410,7 +1489,8 @@ class SvaNfaLowering final { } } - SignalSet computeSignals(LowerCtx& c, std::vector* outRequiredStepSrcsp) { + SignalSet computeSignals(LowerCtx& c, std::vector* outRequiredStepSrcsp, + std::vector* outPerMidSrcsp = nullptr) { SignalSet sigs; // Snapshot comparison expression for disable-iff counter. @@ -1422,7 +1502,7 @@ class SvaNfaLowering final { new AstVarRef{c.flp, c.disableCntVarp, VAccess::READ}}; } - computeTerminalMatchAndReject(c, snapshotOkp, sigs); + computeTerminalMatchAndReject(c, snapshotOkp, sigs, outPerMidSrcsp); // Phase 3a: required-step rejection. // Builder only sets m_rejectOnFail on non-clocked Links with m_condp, @@ -1649,7 +1729,8 @@ public: AstNodeExpr* disableExprp = nullptr, bool negated = false, AstNodeExpr** outMatchpp = nullptr, AstVar* disableCntVarp = nullptr, AstVar* snapshotVarp = nullptr, - std::vector* outRequiredStepSrcsp = nullptr) { + std::vector* outRequiredStepSrcsp = nullptr, + std::vector* outPerMidSrcsp = nullptr) { const std::string baseName = m_names.get(""); // Number vertices with sequential colors for array indexing. @@ -1735,7 +1816,7 @@ public: emitAndCombinerDoneLatchNba(c); // Phase 3/3a/3b: Compute terminal match/reject signals (cleans up stateSig). - const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp); + const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp, outPerMidSrcsp); AstNodeExpr* const resultp = assembleResult( flp, isCover, negated, matchCondp, sigs.terminalActivep, sigs.rejectBasep, @@ -1766,7 +1847,10 @@ class AssertNfaVisitor final : public VNVisitor { // Wire match vertex and mid-window sources for a successful NFA build. static void wireMatchAndMidSources(SvaGraph& graph, const BuildResult& result, FileLine* flp) { graph.createMatchVertex(); - graph.addLink(result.termVertexp, graph.m_matchVertexp); + // Skip the main term Link when midSources already cover every + // end-of-match (cover_sequence path); otherwise the per-mid extraction + // double-counts via the merge vertex. + if (!result.termIsMidMerge) { graph.addLink(result.termVertexp, graph.m_matchVertexp); } for (SvaStateVertex* srcVtxp : result.midSources) { AstNodeExpr* condp = nullptr; for (AstNodeExpr* const tc : srcVtxp->m_throughoutConds) { @@ -2235,9 +2319,11 @@ class AssertNfaVisitor final : public VNVisitor { FileLine* const flp = assertp->fileline(); const bool isCover = VN_IS(assertp, Cover); + AstCover* const coverp = VN_CAST(assertp, Cover); + const bool isCoverSeq = coverp && coverp->isCoverSeq(); SvaGraph graph; - SvaNfaBuilder builder{graph, m_modp, m_propTempNames}; + SvaNfaBuilder builder{graph, m_modp, m_propTempNames, isCoverSeq}; const BuildResult result = buildAssertionGraph(builder, graph, seqBodyp, parts, flp); if (result.valid()) wireMatchAndMidSources(graph, result, flp); @@ -2269,12 +2355,18 @@ class AssertNfaVisitor final : public VNVisitor { = !isCover && !parts.hasImplication && assertWithFailp && assertWithFailp->failsp(); std::vector requiredStepSrcs; + // For `cover sequence` (IEEE 1800-2023 16.14.3) collect per-edge match + // signals so each end-of-match fires the action independently, rather + // than getting OR-folded into a single per-cycle terminalActive. + // coverp / isCoverSeq are computed earlier (passed to SvaNfaBuilder). + std::vector perMidSrcs; + AstNodeExpr* const alwaysTriggerp = new AstConst{flp, AstConst::BitTrue{}}; - AstNodeExpr* const outputExprp - = m_loweringp->lower(flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover, - disableExprp ? disableExprp->cloneTreePure(false) : nullptr, - negated, needMatch ? &matchExprp : nullptr, disableCntVarp, - snapshotVarp, needPerSrcFail ? &requiredStepSrcs : nullptr); + AstNodeExpr* const outputExprp = m_loweringp->lower( + flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover, + disableExprp ? disableExprp->cloneTreePure(false) : nullptr, negated, + needMatch ? &matchExprp : nullptr, disableCntVarp, snapshotVarp, + needPerSrcFail ? &requiredStepSrcs : nullptr, isCoverSeq ? &perMidSrcs : nullptr); AstSenTree* const perSrcSenTreep = (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr; @@ -2287,9 +2379,38 @@ class AssertNfaVisitor final : public VNVisitor { attachMatchHandlers(flp, assertAssertp, assertWithFailp, needMatch ? matchExprp : nullptr, perSrcSenTreep, requiredStepSrcs); - AstNode* const innerPropp = propSpecp->propp(); - innerPropp->replaceWith(outputExprp); - VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); + if (isCoverSeq && perMidSrcs.size() > 1) { + // Clone AstCover (N-1) times, each gated by its own per-mid signal. + // V3Assert sees N independent covers and emits N `if (cond_i) {coverinc; + // userAction}` bodies; the shared AstCoverDecl bucket is incremented + // per fire, matching IEEE "executed each time the sequence matches." + // Clones reuse AstCover->propp's original SVA tree, but we overwrite + // each clone's inner propp with the corresponding per-mid signal + // BEFORE the next iterator step, so hasMultiCycleExpr() returns false + // and processAssertion skips them on revisit. + std::vector coverList; + coverList.push_back(coverp); + for (size_t i = 1; i < perMidSrcs.size(); ++i) { + AstCover* const clonep = coverp->cloneTree(false); + coverp->addNextHere(clonep); + coverList.push_back(clonep); + } + for (size_t i = 0; i < perMidSrcs.size(); ++i) { + AstPropSpec* const clonePropSpecp = VN_CAST(coverList[i]->propp(), PropSpec); + AstNode* const innerp = clonePropSpecp->propp(); + innerp->replaceWith(perMidSrcs[i]); + VL_DO_DANGLING(pushDeletep(innerp), innerp); + } + // Discard the OR-collapsed fallback signal -- cover_sequence path + // does not use it. + VL_DO_DANGLING(outputExprp->deleteTree(), outputExprp); + } else { + AstNode* const innerPropp = propSpecp->propp(); + innerPropp->replaceWith(outputExprp); + VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); + // If we collected per-mid (N==1) but didn't clone, drop the spare. + for (AstNodeExpr* const sp : perMidSrcs) pushDeletep(sp); + } UINFO(4, "NFA converted assertion at " << flp << endl); } diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index aed79ddc0..46c49b515 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -1607,12 +1607,18 @@ public: }; class AstCover final : public AstNodeCoverOrAssert { // @astgen op3 := coverincsp: List[AstNode] // Coverage node + bool m_isCoverSeq = false; // 'cover sequence' (IEEE 1800-2023 16.14.3): fires per + // end-of-match, not per property success public: ASTGEN_MEMBERS_AstCover; AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, VAssertType type, const string& name = "") : ASTGEN_SUPER_Cover(fl, propp, stmtsp, type, VAssertDirectiveType::COVER, name) {} string verilogKwd() const override { return "cover"; } + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + bool isCoverSeq() const { return m_isCoverSeq; } + void isCoverSeq(bool flag) { m_isCoverSeq = flag; } }; class AstRestrict final : public AstNodeCoverOrAssert { public: diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 0707c8d8f..453c45d21 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2181,6 +2181,14 @@ void AstNodeCoverOrAssert::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, immediate); dumpJsonBoolFuncIf(str, senFromAlways); } +void AstCover::dump(std::ostream& str) const { + this->AstNodeCoverOrAssert::dump(str); + if (isCoverSeq()) str << " [COVERSEQ]"; +} +void AstCover::dumpJson(std::ostream& str) const { + dumpJsonBoolFuncIf(str, isCoverSeq); + this->AstNodeCoverOrAssert::dumpJson(str); +} void AstClocking::dump(std::ostream& str) const { this->AstNode::dump(str); if (isDefault()) str << " [DEFAULT]"; diff --git a/src/verilog.y b/src/verilog.y index d4392a5bc..847a5f828 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -6497,14 +6497,34 @@ concurrent_assertion_statement: // ==IEEE: concurrent_assertion_stat | yCOVER yPROPERTY '(' property_spec ')' stmt { $$ = new AstCover{$1, $4, $6, VAssertType::CONCURRENT}; } // // IEEE: cover_sequence_statement + // // Reuses AstCover + AstPropSpec (same wrapper as + // // cover_property_statement above) and the isCoverSeq + // // flag drives V3AssertNfa to fire stmt per end-of-match + // // (IEEE 1800-2023 16.14.3), not per property success. | yCOVER ySEQUENCE '(' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $6); } - // // IEEE: yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt - // // sexpr already includes "clocking_event sexpr" + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), nullptr, nullptr, $4}, + $6, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } + | yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), $4, nullptr, $5}, + $7, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } | yCOVER ySEQUENCE '(' clocking_event yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $8, $10, $12);} + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), $4, $8, $10}, + $12, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } | yCOVER ySEQUENCE '(' yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($7, $9, $11); } + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$7->fileline(), nullptr, $7, $9}, + $11, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } // // IEEE: restrict_property_statement | yRESTRICT yPROPERTY '(' property_spec ')' ';' { $$ = new AstRestrict{$1, $4}; } diff --git a/test_regress/t/t_cover_sequence.py b/test_regress/t/t_cover_sequence.py new file mode 100755 index 000000000..23e13d99b --- /dev/null +++ b/test_regress/t/t_cover_sequence.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing', '--coverage-user']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_cover_sequence.v b/test_regress/t/t_cover_sequence.v new file mode 100644 index 000000000..719a3c1ba --- /dev/null +++ b/test_regress/t/t_cover_sequence.v @@ -0,0 +1,90 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + logic [63:0] crc = 64'h5aef0c8dd70a4497; + logic rst_n = 1'b0; + logic a, b, c, d, e; + int cyc = 0; + + int hit_simple = 0; + int hit_clocked = 0; + int hit_clocked_disable = 0; + int hit_default_disable = 0; + int hit_consrep_range = 0; + int hit_consrep_2 = 0; + int hit_consrep_3 = 0; + + default clocking cb @(posedge clk); + endclocking + + // Non-adjacent CRC bits to avoid LFSR shift correlation + assign a = crc[0]; + assign b = crc[5]; + assign c = crc[10]; + assign d = crc[15]; + assign e = crc[20]; + + // Form 1: cover sequence ( sexpr ) stmt + cover sequence (a | b | c | d | e) hit_simple++; + + // Form 2: cover sequence ( clocking_event sexpr ) stmt + cover sequence (@(posedge clk) (a | b | c | d | e) ##[1:3] b) hit_clocked++; + + // Form 3: cover sequence ( clocking_event disable iff (expr) sexpr ) stmt + cover sequence (@(posedge clk) disable iff (!rst_n) a ##1 b) hit_clocked_disable++; + + // Form 4: cover sequence ( disable iff (expr) sexpr ) stmt + cover sequence (disable iff (!rst_n) a ##1 c) hit_default_disable++; + + // Form 5: consecutive repetition, counted per end-of-match + cover sequence (a [* 2: 3]) hit_consrep_range++; + cover sequence (a [* 2]) hit_consrep_2++; + cover sequence (a [* 3]) hit_consrep_3++; + + always @(posedge clk) begin +`ifdef TEST_VERBOSE + $write("[%0t] cyc=%0d crc=%x a=%b b=%b c=%b d=%b e=%b\n", $time, cyc, crc, a, b, c, d, e); +`endif + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[1] ^ crc[0]}; + if (cyc == 2) rst_n <= 1'b1; + if (cyc == 99) begin + `checkh(crc, 64'h261a9f1371d7aadf); + $finish; + end + end + + // Read the counters in 'final', not the clocked block: a same-cycle read of a + // cover counter races the cover's increment under --threads (vltmt). Verilator + // counts one more end-of-match than Questa 2022.3 on some forms at the + // simulation boundary; the Questa value is noted per check. + final begin +`ifdef TEST_VERBOSE + $write("simple=%0d clocked=%0d clk_dis=%0d def_dis=%0d range=%0d 2=%0d 3=%0d\n", hit_simple, + hit_clocked, hit_clocked_disable, hit_default_disable, hit_consrep_range, hit_consrep_2, + hit_consrep_3); +`endif + `checkd(hit_simple, 96); // Questa: 95 + `checkd(hit_clocked, 149); // Questa: 149 + `checkd(hit_clocked_disable, 28); // Questa: 27 + `checkd(hit_default_disable, 30); // Questa: 30 + `checkd(hit_consrep_2, 30); // Questa: 29 + `checkd(hit_consrep_3, 14); // Questa: 13 + // a[*2:3] == a[*2] or a[*3] (IEEE 1800-2023 16.9.2) + `checkd(hit_consrep_range, hit_consrep_2 + hit_consrep_3); // 44; Questa: 42 + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_cover_sequence_unsup.out b/test_regress/t/t_cover_sequence_unsup.out new file mode 100644 index 000000000..47af703cb --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.out @@ -0,0 +1,18 @@ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:21:33: Ignoring unsupported: cover sequence with a sequence operand of 'or' + 21 | cover sequence ((a ##[1:3] b) or 1'b0); + | ^~ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:24:32: Ignoring unsupported: cover sequence with a sequence operand of 'or' + 24 | cover sequence ((a [* 1: 3]) or 1'b0); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:27:21: Ignoring unsupported: cover sequence with this ranged cycle delay + 27 | cover sequence (a ##[1:2] (b ##1 c)); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:30:21: Ignoring unsupported: cover sequence with this ranged cycle delay + 30 | cover sequence (a ##[1:300] b); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:33:21: Ignoring unsupported: cover sequence with a ranged goto repetition + 33 | cover sequence (a [-> 2: 3]); + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_cover_sequence_unsup.py b/test_regress/t/t_cover_sequence_unsup.py new file mode 100755 index 000000000..56c514bc5 --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(expect_filename=test.golden_filename, + verilator_flags2=['--assert --error-limit 1000'], + fails=True) + +test.passes() diff --git a/test_regress/t/t_cover_sequence_unsup.v b/test_regress/t/t_cover_sequence_unsup.v new file mode 100644 index 000000000..b2770a2fa --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + logic a, b, c; + + default clocking cb @(posedge clk); + endclocking + + // cover sequence (IEEE 1800-2023 16.14.3) counts every end-of-match. The + // following forms put a sub-sequence where only its final end is forwarded, + // so they are ignored (COVERIGN) rather than under-counted. + + // Sequence operand of 'or' (ranged cycle delay). + cover sequence ((a ##[1:3] b) or 1'b0); + + // Sequence operand of 'or' (consecutive repetition). + cover sequence ((a [* 1: 3]) or 1'b0); + + // Ranged cycle delay before a multi-cycle sequence. + cover sequence (a ##[1:2] (b ##1 c)); + + // Ranged cycle delay wide enough to use the counter FSM. + cover sequence (a ##[1:300] b); + + // Ranged goto repetition (every M..N-th match is a separate end). + cover sequence (a [-> 2: 3]); + +endmodule diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index b65ab991e..53f8ac53b 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -651,6 +651,11 @@ module Vt_debug_emitv_t; $display("pass"); end end + begin : cover_sequence_concurrent + cover property (@(posedge clk) in##1 in + ) begin + end + end begin : assert_prop_always assert property (@(posedge clk) always ['sh0: 'sh3] in diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index edc1ec87b..7d3805052 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -343,6 +343,8 @@ module t (/*AUTOARG*/ cover_concurrent: cover property(prop); cover_concurrent_stmt: cover property(prop) $display("pass"); + cover_sequence_concurrent: cover sequence (@(posedge clk) in ##1 in); + assert_prop_always: assert property (@(posedge clk) always [0:3] in); assert_prop_s_always: assert property (@(posedge clk) s_always [1:2] in); assert_prop_overlap_impl: assert property (@(posedge clk) in |-> in); diff --git a/test_regress/t/t_sequence_sexpr_unsup.out b/test_regress/t/t_sequence_sexpr_unsup.out index 975a06491..9bf5bf9ea 100644 --- a/test_regress/t/t_sequence_sexpr_unsup.out +++ b/test_regress/t/t_sequence_sexpr_unsup.out @@ -8,15 +8,4 @@ %Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:99:5: Unsupported: first_match with sequence_match_items 99 | first_match (a, res0 = 1, res1 = 2); | ^~~~~~~~~~~ -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:102:9: Ignoring unsupported: cover sequence - 102 | cover sequence (s_a) $display(""); - | ^~~~~~~~ - ... For warning description see https://verilator.org/warn/COVERIGN?v=latest - ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:103:9: Ignoring unsupported: cover sequence - 103 | cover sequence (@(posedge a) disable iff (b) s_a) $display(""); - | ^~~~~~~~ -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:104:9: Ignoring unsupported: cover sequence - 104 | cover sequence (disable iff (b) s_a) $display(""); - | ^~~~~~~~ %Error: Exiting due to From 709c444df3567b69d8f46af479d4b14d24444a17 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 15 Jun 2026 14:42:34 +0200 Subject: [PATCH 37/89] Internals: Add AGENTS files to guide AI contributions (#7562) (#7765) Fixes #7562. --- AGENTS.md | 142 ++++++++++++++++++++++++++ docs/AGENTS.md | 38 +++++++ include/AGENTS.md | 60 +++++++++++ src/AGENTS.md | 227 +++++++++++++++++++++++++++++++++++++++++ test_regress/AGENTS.md | 128 +++++++++++++++++++++++ 5 files changed, 595 insertions(+) create mode 100644 AGENTS.md create mode 100644 docs/AGENTS.md create mode 100644 include/AGENTS.md create mode 100644 src/AGENTS.md create mode 100644 test_regress/AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..1f0c6a6be --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,142 @@ + + +# Verilator Guidelines for AI Coding Agents + +These files are the general layer an agent loads first -- nearest file wins, so +you read this repository-root file plus the one for the directory you are +editing. They stay deliberately high-level: where to start, how the tree is laid +out, and the conventions reviewers otherwise enforce by hand. They are an index, +not the architecture reference -- for depth (how a pass works internally, the +algorithms, node lifetime) they point you to `docs/internals.rst`. When the +guidance here is not enough, that is where to look next. + +This file has two parts. **Orientation** gets you productive in the codebase from +a cold start. **Before you open a PR** is the checklist of conventions reviewers +otherwise have to enforce by hand -- read it before submitting any change. + +Then read the directory guide for the area you are editing: + +- [src/AGENTS.md](src/AGENTS.md) -- compiler C++ sources: AST, visitors, passes, parser, style +- [include/AGENTS.md](include/AGENTS.md) -- runtime library (`verilated*`): C++14, MT-safety, fixed-width types +- [test_regress/AGENTS.md](test_regress/AGENTS.md) -- regression tests: harness, drivers, golden files +- [docs/AGENTS.md](docs/AGENTS.md) -- documentation (`*.rst`) + +--- + +# Orientation + +## What Verilator is + +Verilator is a *compiler*, not an interpreter. It translates synthesizable (and +much behavioral) SystemVerilog into a cycle-accurate C++ model that you then +compile and run. Almost every decision is made at compile ("verilation") time; +the generated C++ just advances state each evaluation. Optimize for verilation- +time work over runtime work. + +## The pipeline is the spine + +A run is an ordered sequence of passes over one shared AST (abstract syntax +tree). In source order: + +| Stage | What it does | Key files | +|---|---|---| +| Preprocess + parse | Lex and parse text into a raw AST -- builds nodes only, no semantic checks | `verilog.l`, `verilog.y` | +| Link / elaborate | Resolve names, scopes, parameters; instantiate the hierarchy | `V3LinkParse`, `V3LinkDot`, `V3Param` | +| Width / type | Assign and check data types and bit widths | `V3Width` | +| Transform / optimize / schedule | Constant fold, lower language features, schedule events | `V3Const`, `V3Randomize`, `V3Assert*`, `V3Sched`, `V3Timing`, `V3Dfg` | +| Emit | Lower the final AST to generated C++ | `V3EmitC*` | +| Runtime | Library the generated model links against | `include/verilated*` | + +This table is the map; `docs/internals.rst` has the detail behind each stage. + +## Where to make a change + +Map the symptom to the pass that owns it, then start by reading that pass's +top-of-file comment. + +| Symptom or feature area | Start in | +|---|---| +| Type/width error, "what type is this", implicit conversion | `V3Width` | +| Name/scope/parameter resolution ("Can't find...", hierarchy) | `V3LinkDot`, `V3Param` | +| `randomize` / `constraint` / `rand` / `randc` | `V3Randomize` | +| `assert` / `property` / `sequence` / `cover` | `V3Assert`, `V3AssertPre`, `V3AssertNfa` | +| `fork` / timing / `#delay` / NBA / event scheduling | `V3Sched`, `V3Timing`, `V3Fork` | +| Syntax wrongly accepted or rejected | `verilog.y`, `verilog.l` | +| Wrong generated C++ | `V3EmitC*` | +| Runtime model behavior | `include/verilated*` | + +## Build and run a test + +- Build in the source tree: `autoconf && ./configure && make -j8`. Configure with + `--enable-ccwarn` so a new compiler warning stops the build. +- Run one test from the repository root: `test_regress/t/t_.py`. +- Run the full regression with `make test`. The complete suite requires + configuring with `--enable-longtests` (works on every OS, including macOS). + +--- + +# Before you open a PR + +## Scope and process + +- [ ] Searched open PRs and issues -- duplicating in-flight work wastes review time. +- [ ] Fixed the general root cause, not just the reported case -- if it also + affects other modules/classes/interfaces, cover them or expect rejection. +- [ ] PR is single-purpose. Refactors, drive-by fixes found along the way, and new + features each go in separate PRs; land standalone cleanups first. +- [ ] Every bug fix has a test that fails *without* the fix; include the issue's + own reproducer when possible. +- [ ] New code aims for 100% line coverage; branch coverage far below line coverage + signals guards callers never violate -- justify or remove them. +- [ ] Ran `make format` (clang-format), `make cppcheck`, and `make lint-py`; + self-reviewed the diff for leftover debug code, stale comments, and + copy-paste errors. +- [ ] Ran the full regression on at least one OS before submitting. Partial runs + are fine during development, but the submitted PR is expected to pass every + test. +- [ ] Did not edit `docs/CONTRIBUTORS` (humans only) or `Changes` (maintainer + updates it near release). + +## Pick the right diagnostic (and its required test) + +The API you choose determines which test must accompany the change. + +| API | Output | Meaning | Required test | +|---|---|---|---| +| `v3error("...")` | `%Error:` | User wrote invalid SystemVerilog | `t_*_bad*.v` + `.out` golden | +| `v3error("Unsupported: ...")` | `%Error-UNSUPPORTED:` | Legal SV that Verilator does not yet support | `t_*_unsup*.v` + `.out` golden | +| `v3warn(CODE, "...")` | `%Warning-CODE:` | Legal but suspicious code | warning test + `.out` golden | +| `v3fatalSrc("...")` | `%Error: Internal Error` | Should-never-happen internal assertion | none -- not user-triggerable | + +- Every `v3error`/`v3warn` needs a test in `test_regress/t/` -- enforced by the + warn-coverage distribution test. `v3fatalSrc` is exempt. +- Reserve "Unsupported:" for not-yet-implemented features, never for user mistakes. +- When an error enforces a spec-defined restriction, cite the clause + (`IEEE 1800-2023 11.4.7`) so it is verifiable. Update `docs/guide/warnings.rst` + when adding or changing a warning. +- On error paths, clean up or replace invalid AST (e.g. `AstConst::BitFalse`) so + later passes do not crash after the error. + +## Cross-cutting code rules + +- [ ] No non-ASCII characters in C++ sources or headers: write `--` (two ASCII + hyphens) rather than a Unicode em-dash, and a plain `'` rather than a smart + quote. At write time, not when CI complains. +- [ ] Lists stay sorted: lexer/parser tokens, option declarations, enum values, + configure feature lists, documented option lists. +- [ ] `bin/` scripts are Python (distributed cross-platform); `nodist/` may use + bash and platform-specific code (developer-only, not packaged). +- [ ] Runtime code in `include/` targets C++14 (`--no-timing` builds must work); + C++20 only in timing code paths. +- [ ] In `include/` public headers, prefix public classes with `Verilated`/`Vl` + and document the API with `///` comments. +- [ ] A new code pattern is applied globally or not at all -- no one-off + convention in a single file. + +## Commits + +- Subject line is short and imperative and conventionally ends with the PR number: + `Support property case (#7721)`. A body is optional and common for non-trivial + changes. diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 000000000..ee4ae5249 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,38 @@ + + +# docs/ Guidelines -- Verilator documentation (*.rst) + +When to check: before editing anything under `docs/`. +Read the repository-root [AGENTS.md](../AGENTS.md) first for process and cross-cutting rules. + +## Writing rules + +- Rewrap paragraphs after editing -- keep consistent line length in `*.rst` files. + +- Document only stable, implemented features -- never planned or in-development capabilities; prevents user confusion and support burden. + +- Explain WHAT and WHEN, not HOW -- conceptual purpose and practical use cases over implementation mechanics; describe behavior ("optimized as pure", not "treated as pure") and clarify ambiguous referents ("in the internals of Verilator"). + +- Keep terminology consistent -- one term per concept; update docs when renaming code constructs; spell out full terms, avoiding abbreviations like "sim"/"sims". + +- Use "how many" for countable nouns (threads, tasks, workers) and "how much" for uncountable quantities. + +- Mark internal or experimental features "for internal use only" -- prevents user dependence and forced deprecation cycles later. + +- Use specific IEEE references: `IEEE {number}-{year}` plus the section (e.g. `Annex I`) -- a vague "IEEE spec requires" is unverifiable. + +- Document all flags with descriptions, not just syntax. + +## reStructuredText mechanics + +- Use the `:vlopt:` role for Verilator option references -- makes cross-references clickable and consistent. + +- Escape angle brackets (`\<`, `\>`) in link targets -- prevents broken links to command-line options. + +- Generate documentation examples with `test.extract` from `test_regress` test files -- examples stay synced with actually tested behavior. + +## Hard constraint + +- Never edit `docs/CONTRIBUTORS` -- only humans may, after reading and agreeing to its statement; remind the user instead. diff --git a/include/AGENTS.md b/include/AGENTS.md new file mode 100644 index 000000000..5b409dfaf --- /dev/null +++ b/include/AGENTS.md @@ -0,0 +1,60 @@ + + +# include/ -- Verilator runtime library + +Covers the C++ runtime under `include/` (`verilated.h/.cpp`, `verilated_*.h/.cpp`) +that every generated model links against. Read the repository-root +[AGENTS.md](../AGENTS.md) first. The rules here differ from `src/`: this code +ships to users, runs every simulation cycle, and must stay portable and fast. + +--- + +# Orientation + +- **This is the runtime, not the compiler.** The passes in `src/` *emit* C++ that + calls into this library; this code then *runs* during simulation. Optimize it + for execution speed and portability, not for compile-time clarity. +- **Key files:** `verilated.h` (core model API), `verilated_types.h` (data + types), `verilated_random.cpp` (constrained-random runtime), `verilated_cov.*` + (coverage), `verilated_threads.*` (MT runtime), `verilated_timing.*` + (`--timing` runtime), `verilated_vcd_c.*`/`verilated_fst_c.*` (tracing). +- A runtime-only fix lives entirely here and does not rebuild `verilator_bin`. + +--- + +# Before you open a PR + +- **C++14 baseline.** The runtime must build under `--no-timing` with C++14; C++20 + features are allowed only in `--timing` code paths. +- **Public API naming and docs.** Prefix public classes and types with + `Verilated`/`Vl` to avoid collisions with user code, and document the API with + `///` comments -- they feed doc generation. +- **No exceptions in runtime code** -- use error returns or assertions; exceptions + add overhead on every path. +- **Use fixed-width model types** (`CData`/`SData`/`IData`/`QData`/`VlWide`), never + `size_t`, for model data. Process wide data word-by-word (`VL_ZERO_W`, + `VL_MEMCPY_W`), never bit-by-bit or byte-by-byte. +- **Do all string parsing at verilation time** -- never parse strings during + simulation; emit structured data or compile-time hints instead. +- **Keep per-cycle code lean.** Do not add vtables to high-frequency objects + (8 bytes per instance); guard optional features behind + `hasClasses()`/`hasEvents()`-style checks; functions called per cycle must avoid + mutexes -- use atomics or lockless designs. +- **Emit no runtime loops** the compiler could have expanded at verilation time; + prefer a single runtime call. +- **Thread safety.** Annotate with the hierarchy `VL_PURE` > `VL_MT_SAFE` > + `VL_MT_STABLE`; annotations must match the implementation. Annotate + mutex-protected members with `VL_GUARDED_BY` and document acquisition ordering. + Prefer has-a over is-a: a guarded class wrapping the unguarded internal one, with + the guarded version as the default public API. +- **Keep runtime and compiler headers separate** -- never include `verilated.h` + into the compiler in `src/`. + +## File-specific rules + +| File | Rule | +|---|---| +| `verilated_random.cpp` | Emit only portable SMT-LIB 2.6 -- no solver-specific or MaxSMT extensions; the generated solver text is the model's runtime constraint interface | +| `verilated_cov.cpp` | Coverage runtime is shared by all models -- keep per-point overhead minimal and the on-disk format stable for `verilator_coverage` | diff --git a/src/AGENTS.md b/src/AGENTS.md new file mode 100644 index 000000000..1904eb5c9 --- /dev/null +++ b/src/AGENTS.md @@ -0,0 +1,227 @@ + + +# src/ -- Verilator compiler sources + +Covers all C++ under `src/`, including astgen inputs and the parser/lexer +(`verilog.y`, `verilog.l`). Read the repository-root [AGENTS.md](../AGENTS.md) +first. This file has two parts: **Orientation** explains the AST and pass model; +**Before you open a PR** is the style and correctness checklist. + +--- + +# Orientation: the AST and the visitor model + +- **Everything is an `AstNode`.** Each construct is an `Ast*` subclass (`AstAdd`, + `AstVar`, `AstIf`). The design under analysis is one tree, with statement lists + threaded by sibling `nextp()` links. +- **Children sit in numbered slots `op1p()`..`op4p()`, accessed by name.** Use the + named accessors (`lhsp()`, `condp()`, `thensp()`), not the raw slots -- the + numbering is an implementation detail. +- **`astgen` generates node boilerplate.** Declare children and cross-node + pointers with `@astgen op` / `@astgen ptr` annotations in the `V3AstNode*.h` + headers; it emits accessors, clone, and broken-check code. Do not hand-write + what astgen can generate. +- **A pass is a visitor.** Convention: a class with a private constructor and a + static `apply()` entry point, named after its file (`TimingSuspendableVisitor` + in `V3Timing.cpp`). It walks the tree through `visit(AstFoo*)` handlers and + `iterateChildren()`. To understand a pass, read its top-of-file comment first -- + every `.cpp` opens with one describing the algorithm. +- **Scratch state lives on nodes.** Passes stash data in `user1p()`..`user5p()` + (and `user1()`..`user5()`), claimed for the pass lifetime with a `VNUser*InUse` + guard. Save and restore visitor members across recursion with `VL_RESTORER`. +- **Three downcasts, three null behaviors:** `VN_IS` returns bool, `VN_CAST` + returns nullptr on mismatch, `VN_AS` asserts the type. `V3Broken` re-validates + tree invariants between passes, so trust resolved pointers (`dtypep()`, + `varp()`) instead of adding defensive null checks for impossible cases. +- `docs/internals.rst` is the authoritative reference for the AST, the pass list, + and node lifetime. + +--- + +# Before you open a PR + +## Code style + +- Mark every variable, parameter, pointer, and member function `const` where possible. +- Pointer variables take a `p` suffix and pointer locals are doubly const where + possible: `AstVar* const varp`; non-pointers never use the `p` suffix. +- Do not use `auto` except for iterators or genuinely unwieldy types. +- Use pre-increment (`++i`) unless you specifically need post-increment's old value. +- Brace-initialize node and struct construction: `new AstIf{fl, condp, thenp, elsep}`. +- Never use C-style casts; instead use `static_cast` for non-AST types and + `VN_AS`/`VN_CAST` for AST downcasts. +- `static constexpr` for compile-time constants, not `#define` or file-scope const. +- Mark every `class`/`struct` `final` or `VL_NOT_FINAL` -- a distribution test + scans all definitions. +- Keep functions under roughly 100-150 lines; thread shared state through a + context struct rather than long parameter lists. +- Keep headers lean: move implementation to `.cpp`; convert large lambdas into + named member functions -- lambdas are opaque in stack traces and block reuse. +- Start every new `.cpp` with a top-of-file comment explaining the algorithm. +- Comments are capitalized sentences written for an unknown future reader, without + "I"/"we"/"our"; remove commented-out code -- version control preserves history. +- No `using namespace`; prefix non-namespaced symbols with `VL`/`Vl`. +- Prefer semantic predicates over enum comparisons: `varp->isClassMember()`, not + `varp->varType() == VVarType::MEMBER`. +- `new*` functions return a `new` object; `make*` functions do something more + complex -- pick the prefix accordingly. +- Name compiler-created temporaries with a `__V` prefix plus a context suffix + (`__VInside`, `__VCase`); runtime utility functions use a `vl_` prefix. +- Use `VL_*` bit/word macros from `verilatedos.h` (`VL_WORDS_I`, `VL_MASK_I`); do + not include `` directly. +- Replace magic numbers with named `static constexpr` constants. + +## AST construction and manipulation + +- Build logic as AST nodes, never as raw C text in `AstCStmt` -- later passes + (V3Name, `--protect`) rename AST identifiers but cannot see into raw strings. +- Know the cast forms (above) and never pair `VN_IS` with `VN_AS` on the same + node -- use a single `VN_CAST`: + + ```cpp + // BAD: redundant double check + if (VN_IS(nodep, VarRef)) { AstVarRef* const refp = VN_AS(nodep, VarRef); } + // GOOD: single conditional cast + if (const AstVarRef* const refp = VN_CAST(nodep, VarRef)) { ... } + ``` + +- Use `UASSERT_OBJ(cond, nodep, "...")` over `UASSERT` when a node is in scope; + use `v3fatalSrc("...")` for unreachable paths, never a silent `if (!ptr) return;`. +- Use `VL_DO_DANGLING(pushDeletep(nodep), nodep)` instead of `deleteTree()` in + visitors -- deferred deletion is safe against re-entry and unlinking order. + `deleteTree()` is only for fresh nodes that never entered the tree. +- Every new AST member needs both `dump()` and `dumpJson()` support -- never wrap + these in `LCOV_EXCL`; cover them by adding a construct to `t_debug_emitv.v`. + Override `isSame()` to include any new semantically meaningful field. +- Pointers to nodes outside op1p-op4p require a `broken()` override and + `cloneRelink()` support -- avoid storing out-of-tree node pointers when possible. +- Never allocate AstNode objects on the stack or by value -- always pointers. +- Prefer a new `visit()` in an existing visitor over `nodep->foreach(...)` -- + better for runtime, and handles diverse node types better. Prefer named + accessors over `op1p()`..`op4p()`, and verify traversal order is preserved + when converting manual iteration. +- Prefer `AstForeach` over generating unrolled loop bodies -- constant-size code + instead of O(N); wrap the body in `AstBegin` for scope isolation. +- Always `skipRefp()` when comparing or resolving dtypes -- missing it breaks + typedef support; prove the paths with typedef tests. +- Use `num().isOpaque()` rather than `isDouble() || isString()` when guarding + V3Number comparisons against non-integer types. +- Use `FileLine::operatorCompare` for source-position ordering -- never hand-roll + filename/lineno comparisons. +- Identify compiler-generated constructs by an attribute flag on the node (with + dump support), never by name-pattern matching -- magic names break with escaped + identifiers. +- Use `V3Number` arithmetic for `AstConst` values wider than 32 bits -- `1 << i` + silently overflows at `i >= 32`. +- Use `VMemberMap`/`findMember()` for name lookups in structs, classes, modules, + and packages -- O(1) versus quadratic linear scans. +- Never emit raw source filenames or identifiers in generated code -- pass them + through `protect()`/`putsQuoted` so `--protect` does not leak source. + +## Visitors and passes + +- `VL_RESTORER` on every visitor member a `visit()` modifies before iterating + children -- even when nesting "cannot happen" today. +- Every pass using `userNp()` needs a `VNUserNInUse` guard, and the header + documents which user fields it uses. +- Use `iterateAndNextNull()` rather than `iterate()` -- the null-safe form + prevents copy-paste errors during refactors. +- Derive read-only visitors from `VNVisitorConst` with `iterateChildrenConst`. +- Reset per-module visitor state in `visit(AstNodeModule*)`, including numeric ID + counters, to keep generated names stable. +- Capture first-occurrence module state inside the node's own `visit()` handler, + not via a `foreach` pre-scan from `visit(AstNodeModule)` -- source order already + matches IEEE declaration-before-use. +- Avoid `backp()` -- it returns parent or prior sibling depending on position and + causes O(n^2) hunts; build maps or capture context during forward traversal. +- When raw node pointers key a map or set, erase entries when the node is deleted + -- allocators reuse addresses, so stale entries alias new nodes. +- Derive graph-shaped passes from V3Graph (`V3GraphVertex`/`V3GraphEdge`) -- it + gives dump, color, rank, topological sort, and reachability for free. +- When a change outgrows local rewrites, create a dedicated pass instead of + growing an existing one. +- State explicitly how side effects are preserved in optimizations involving + purity, expression lifting, or simplification. + +## Errors and warnings + +(See the root checklist for choosing the diagnostic API and its required test.) + +- Append `nodep->prettyNameQ()` for user-facing names; use `name()` only in + debug/UINFO output. Enclose specific values in single quotes: `'value'`. +- End messages with periods, never exclamation marks; do not write "Error:" in the + text -- the macro prints the prefix. +- State what was attempted and what was found: "Instance attempts to connect to + 'PARAM' as a parameter, but it is a variable". Add a `warnMore()` suggestion + where possible. +- Name warning codes object-first and short (`ASCRANGE`, not `RANGEASC`); rename + via `renamedTo()` so old suppressions keep working. +- Set warning suppression on `AstVar`, not `AstVarRef` -- VarRefs get recreated + and lose `warnIsOff`. +- "Unsupported:" messages describe the specific unsupported context, not just the + construct name -- each message must be distinct. +- When replacing or refactoring a pass, keep existing user-facing error strings -- + `.out` goldens and documentation depend on the wording. + +## Performance and memory + +- O(n^2) is never acceptable -- build maps for batch lookups; any quadratic loop + needs explicit justification in a comment. +- Prefer `std::map` for per-module structures (many small instances); use + `unordered_map` only for one-per-netlist data, and never let `unordered_*` + iteration order reach generated output. +- Prefer `emplace` over `insert` and check the returned `.second` instead of a + separate `find()`. `reserve()` strings and vectors when the size is estimable. +- Add no new static or global mutable data -- statics are being eliminated for + future parallelism. +- Use Verilator's fixed-width data types for model data (`CData`/`SData`/`IData`/ + `QData`/`VlWide`), not `size_t`. Process wide data word-by-word + (`VL_ZERO_W`, `VL_MEMCPY_W`), never bit-by-bit. +- No exceptions in verilated runtime code; do string parsing at verilation time, + never during simulation. +- Wrap unlikely hot-path branches in `VL_UNLIKELY`/`VL_LIKELY`. +- Count what every new pass does via V3Stats -- stats become deterministic + regression anchors. + +## Thread safety + +- Annotate with the hierarchy `VL_PURE` > `VL_MT_SAFE` > `VL_MT_STABLE`: PURE has + no side effects and calls only PURE; MT_SAFE is safe under locks; MT_STABLE is + safe only while tree topology is stable. Annotations must match the + implementation. +- Never include `verilated.h` in the compiler itself -- use `verilatedos.h`. +- Annotate mutex-protected members with `VL_GUARDED_BY` and document acquisition + ordering. `++` on shared state and container `empty()` are not thread-safe. + +## Parser and lexer (verilog.y, verilog.l) + +- Preserve IEEE Appendix A BNF comments (`// IEEE: {rule}`); comment explicitly + when accepting syntax beyond IEEE as an extension. +- The parser only builds AST nodes -- defer semantic validation, `VN_IS` checks, + and context-dependent logic to V3LinkParse/V3Width and later passes. +- Represent hierarchical paths as structured nodes (`AstDot`/parse-ref chains via + `idDotted`), never concatenated strings -- preserves per-segment FileLine. +- Prefer tightening a grammar rule's operand type over a runtime cast-chain guard + in a later visitor -- illegal operands then fail with a clean syntax error. +- Solve ambiguities with token-pipeline look-ahead (`tokenPipeScan*`) rather than + limiting grammar rules; mark unsupported rules with `//UNSUP`. +- Sort token declarations alphabetically by string literal; sort `yD_*` + productions by token name. +- Add a test for every `|` alternative and optional clause of a new or changed + grammar rule -- untested alternatives are where parse regressions hide. + +## File-specific rules + +| File | Rule | +|---|---| +| `src/V3Options.cpp` | Chain `.notForRerun()` onto `DECL_OPTION()` for options that do not affect semantic output | +| `src/V3Ast.cpp` | For composite types (queues, dynamic arrays) use `computeCastableImp()` on subtypes -- shallow `width()`/`similarDType()` checks miss nested incompatibility | +| `src/V3AstNode*.h` | Every node class gets a what-construct comment and every member a semantic-purpose comment; put enum type definitions in `V3AstAttr.h` | +| `src/V3AstNodeExpr.h` | `CCast` is only for basic C types (char/short/int/QData) -- never 4-state logic or structs | +| `src/V3AstNodeOther.h` | `cloneRelink` must propagate all stateful flags (e.g. `maybePointedTo`) and update internal references | +| `src/V3Const.cpp` | Check `keepIfEmpty` before removing empty functions -- flagged functions must survive for codegen/side effects | +| `src/V3Coverage.cpp` | Instrumentation contexts are opt-in (allowlist), never blocklist -- blocklists silently break when new contexts appear | +| `src/V3Inline.cpp` | Preserve `VarXRef::varp()` during passes -- pin-reconnection needs it before V3LinkDot re-resolves | +| `src/V3Sched*.cpp` | Every change needs a test proving necessity; isolate unrelated scheduler changes into separate PRs -- high-risk area | diff --git a/test_regress/AGENTS.md b/test_regress/AGENTS.md new file mode 100644 index 000000000..05833d460 --- /dev/null +++ b/test_regress/AGENTS.md @@ -0,0 +1,128 @@ + + +# test_regress/ -- regression tests + +Covers `.v`/`.sv` sources, `.py` drivers, and `.out` golden files under +`test_regress/`. Read the repository-root [AGENTS.md](../AGENTS.md) first. This +file has two parts: **Orientation** explains how the harness runs a test; +**Before you open a PR** is the test-authoring checklist. + +--- + +# Orientation: how the harness works + +- **A test is a `.v`/`.sv` source plus a matching `.py` driver in `t/`.** The + driver calls `test.compile()`, `test.execute()`, and/or `test.lint()`. Without a + `.py` the source never runs and is dead code giving false coverage confidence. +- **Golden output lives in `.out` files**, compared via `expect_filename`. + Generate them with `HARNESS_UPDATE_GOLDEN=1 python3 t/.py` -- never + hand-write them; the harness normalizes paths, version markers, and line numbers + that hand edits get wrong. +- **Run one test from the repository root:** `test_regress/t/t_.py`. When + running from a checkout, set `VERILATOR_ROOT` to the checkout root first so the + harness compiles the generated C++ against the right headers. +- **`test.compile()` defaults are richer than they look:** the vlt driver + auto-injects `--exe` and a generated main, and `assert property` fires its + action blocks without `--assert` -- try the simple form before adding flags. +- **The `t_dist_*` tests enforce repository conventions** (file headers, sorted + lists, warning documentation, ASCII-only) -- run the relevant ones before + submitting. + +--- + +# Before you open a PR + +## Naming + +- Name tests `t_{category}_{description}` in snake_case; the first word groups the + category (`t_lint_unused_func_bad`, not `t_unused_func_lint_bad`) so related + tests are findable and runnable together. +- Use `_bad` when the code is illegal SystemVerilog, `_unsup` when it is legal but + unsupported, `_off` for disabled-behavior tests -- never `_fail`. +- Keep filenames under roughly 30-35 characters. + +## Files and drivers + +- Every `.v` needs a matching `.py` driver that actually calls `test.compile()` + and `test.execute()` (or `test.lint()` for static-analysis-only tests) -- a + driver that sets up but never runs hides coverage gaps silently. +- Copy the license header from an existing file: `.v` tests carry the CC0 notice, + `.py` drivers carry the standard driver header -- distribution tests check + headers on every committed file. +- Use `--binary` instead of `--exe --main --timing` -- it implies the others. +- Error tests use `test.compile(fails=True, expect_filename=test.golden_filename)` + (or `test.lint(...)`) and must not call `test.execute()`. +- Use `.out` golden files with `expect_filename` instead of inline `expect` + regex strings -- goldens are diffable and maintainable. +- Use `test.glob_one()`/`test.glob_some()` and `test.timeout()` instead of raw + `glob.glob()` and manual `time.time()` measurements. +- Coverage tests run `verilator_coverage` and verify individual bins and points, + not just aggregate percentages. +- Assert optimization stats with exact expected counts: + `test.file_grep(test.stats, r'Optimizations, ...\s+(\d+)', N)`. +- Add a `_protect_ids` variant when a feature emits user identifiers or filenames; + use conservative thread counts (2 or fewer) in multithreaded tests. +- Extend existing test files with related cases instead of creating many + single-purpose files; keep drivers minimal -- test logic belongs in the `.v`. + +## Golden .out files + +- Never hand-write or hand-edit `.out` goldens -- regenerate with + `HARNESS_UPDATE_GOLDEN=1`. +- When a feature lands, remove its now-supported entries from `t_*_unsup.v` / + `t_*_bad.v` in the same change and regenerate the goldens -- stale entries no + longer error and reviewers will flag them. + +## Verilog style + +- 2-space indentation, no tabs. +- Declarations are flush-left with a single space between type and name; never + column-align: + + ```systemverilog + // WRONG: column-aligned + bit [63:0] crc = 64'h5aef0c8d_d70a4497; + int cyc = 0; + // RIGHT: flush-left + bit [63:0] crc = 64'h5aef0c8d_d70a4497; + int cyc = 0; + ``` + +- Run `nodist/verilog_format` on new `.v` files; wrap macro definitions in + `// verilog_format: off`/`on` so the formatter does not split them. +- Use `$display("%0d", ...)` not `%d` -- avoids leading-space padding. +- Wrap Verilator-specific test code (e.g. `$c`) in `` `ifdef VERILATOR ``. +- Use inline `// verilator lint_off WARNCODE` only when that warning is itself + under test -- fix root causes otherwise. +- Use only IEEE 1800-compliant constructs other simulators also accept -- tests + validate standard behavior, not Verilator's parser leniency. +- Omit optional end labels on `endmodule`/`endclass`/`endtask`/`endfunction`. + +## Self-checking + +- Use the `checkh`/`checkd`/`checks` assertion macros, not manual + `if`/`$display`/`$stop` sequences. Note `checkh` prints with `%p`, which renders + integers as hex -- use `checkd` for integer comparisons. +- Use the `` `stop `` macro rather than direct `$stop`. +- Drive logic with runtime-varying inputs (counters, CRC/LFSR registers) so + constant folding cannot pre-evaluate the logic under test; check behavior across + multiple clock cycles, not just initial values. +- For a test whose pass/fail depends on varying or random values, loop enough + iterations that the values demonstrably differ and size the value space so a + failure is probable per run, then confirm the test fails on an un-fixed tree + before submitting. + +## Test design + +- Use non-power-of-2, non-word-aligned widths (7, 15, 31, 33, 63, 65, 95) -- + exposes masking and word-boundary bugs that 32/64/128 hide. +- Test both `[high:low]` and `[low:high]` orderings plus non-zero bounds like + `[3:1]`; use different ranges for each axis of multidimensional arrays. +- When adding type support, test all basic types (chandle, string, real) and + typedef-wrapped variants. +- Include the issue's own reproducer as a committed test, and verify it fails + without the fix. +- Assert non-blocking-assignment results in the cycle immediately after they take + effect, before later overwrites, using indices that change post-NBA. From 0233e5044cb42dc53d7d9c58d6230bdd344f6830 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 15 Jun 2026 14:56:34 +0200 Subject: [PATCH 38/89] Tests: Restore Antmicro copyright on test files accidentally overwritten (#7786) --- test_regress/t/t_constraint_unsup.v | 2 +- test_regress/t/t_property_sexpr_multi.v | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test_regress/t/t_constraint_unsup.v b/test_regress/t/t_constraint_unsup.v index 4b1c95031..84f67e5ce 100644 --- a/test_regress/t/t_constraint_unsup.v +++ b/test_regress/t/t_constraint_unsup.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 class Packet; diff --git a/test_regress/t/t_property_sexpr_multi.v b/test_regress/t/t_property_sexpr_multi.v index 9a99739e5..ff126810f 100644 --- a/test_regress/t/t_property_sexpr_multi.v +++ b/test_regress/t/t_property_sexpr_multi.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 // verilog_format: off From 7061c1f04d3356a22568ddea80b6d20cdeba396e Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Mon, 15 Jun 2026 21:32:11 +0200 Subject: [PATCH 39/89] Fix not failing assertion when RHS of a range window rejects once (#7773) --- src/V3AssertNfa.cpp | 83 ++++++++++++++++--- test_regress/t/t_property_sexpr_range_delay.v | 34 ++++++-- 2 files changed, 100 insertions(+), 17 deletions(-) diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index c5c36178d..a7f2a8c1b 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -110,6 +110,9 @@ public: // Reject when source is active and condp is false; set only on // outermost required-step Link bool m_rejectOnFail = false; + // Optional dynamic condition vertex for m_rejectOnFail. Used when the + // success condition is another NFA state rather than a static expression. + SvaStateVertex* m_condVtxp = nullptr; // CONSTRUCTORS SvaTransEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, AstNodeExpr* condp, @@ -208,6 +211,12 @@ class SvaNfaBuilder final { // topology when true. Default false preserves cover_property semantics. bool m_isCoverSeq = false; + struct RangeDelayRejectInfo final { + SvaStateVertex* startp = nullptr; + int range = 0; + int rhsLen = 0; + }; + AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) { if (m_throughoutStack.empty()) return baseCondp; // AND all throughout conditions (supports nesting) @@ -374,7 +383,7 @@ class SvaNfaBuilder final { // failure, sets outErrorEmitted per semantic-error policy and returns false. bool applyRangeDelay(AstDelay* delayp, AstNodeExpr* rhsExprp, SvaStateVertex*& currentp, std::vector& midSources, FileLine* flp, - bool& outErrorEmitted) { + bool& outErrorEmitted, RangeDelayRejectInfo* rangeRejectInfop = nullptr) { const int minDelay = getConstInt(delayp->lhsp()); if (minDelay < 0) { delayp->v3error("Range delay minimum is not a non-negative elaboration-time constant" @@ -433,8 +442,14 @@ class SvaNfaBuilder final { guardedEdge(currentp, counterVtxp, flp); currentp = counterVtxp; } else if (VN_IS(rhsExprp, SExpr)) { - // Nested-SExpr RHS: merge all [M,N] positions; continuation is per-attempt. + // Nested-SExpr RHS: merge all [M,N] positions. Candidate-local misses + // are not assertion rejects while a later position can still match. + if (rangeRejectInfop) { + const int rhsLen = fixedLength(rhsExprp); + if (rhsLen >= 0) *rangeRejectInfop = {currentp, range, rhsLen}; + } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); + mergeVtxp->m_isUnbounded = true; guardedLink(currentp, mergeVtxp, flp); for (int i = 0; i < range; ++i) { SvaStateVertex* const nextVtxp = scopedCreateVertex(); @@ -443,6 +458,7 @@ class SvaNfaBuilder final { currentp = nextVtxp; } currentp = mergeVtxp; + m_inUnboundedScope = true; } else { // Pure boolean RHS: register chain. Each mid-position links to // match (match-only); last position is the reject source. @@ -468,12 +484,44 @@ class SvaNfaBuilder final { return true; } + void addFiniteRangeReject(const RangeDelayRejectInfo& info, const BuildResult& result, + FileLine* flp) { + if (!info.startp) return; + + SvaStateVertex* const expiryVtxp + = addDelayChain(info.startp, info.range + info.rhsLen, flp); + SvaStateVertex* const expiryMatchp = scopedCreateVertex(); + std::vector sources = result.midSources; + sources.push_back(result.termVertexp); + for (SvaStateVertex* const srcp : sources) { + AstNodeExpr* const condp + = result.finalCondp ? sampled(result.finalCondp->cloneTreePure(false)) : nullptr; + SvaStateVertex* const successNowp = scopedCreateVertex(); + guardedLink(srcp, successNowp, condp, flp); + SvaStateVertex* stagep = successNowp; + guardedLink(stagep, expiryMatchp, flp); + for (int i = 0; i < info.range; ++i) { + SvaStateVertex* const nextp = scopedCreateVertex(); + guardedEdge(stagep, nextp, flp); + stagep = nextp; + guardedLink(stagep, expiryMatchp, flp); + } + } + + SvaStateVertex* const sinkVtxp = m_graph.createStateVertex(); + sinkVtxp->m_isRejectSink = true; + SvaTransEdge* const rejectp = m_graph.addLink(expiryVtxp, sinkVtxp); + rejectp->m_rejectOnFail = true; + rejectp->m_condVtxp = expiryMatchp; + } + BuildResult buildSExpr(AstSExpr* sexprp, SvaStateVertex* entryVtxp, bool isTopLevelStep = false) { AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay); if (!delayp || !delayp->isCycleDelay()) return BuildResult::fail(); FileLine* const flp = sexprp->fileline(); + AstNodeExpr* const exprp = sexprp->exprp(); // Handle LHS (preExpr) SvaStateVertex* currentp = entryVtxp; @@ -496,10 +544,12 @@ class SvaNfaBuilder final { // Handle delay std::vector rangeMidSources; + RangeDelayRejectInfo rangeRejectInfo; + const bool addRangeReject = isTopLevelStep && !m_inUnboundedScope; if (delayp->isRangeDelay()) { bool errorEmitted = false; if (!applyRangeDelay(delayp, sexprp->exprp(), currentp, rangeMidSources, flp, - errorEmitted)) { + errorEmitted, addRangeReject ? &rangeRejectInfo : nullptr)) { return BuildResult::fail(errorEmitted); } } else { @@ -514,8 +564,11 @@ class SvaNfaBuilder final { } // Multi-cycle RHS: recurse (only plain boolean is returned as finalCondp). - AstNodeExpr* const exprp = sexprp->exprp(); - if (exprp->isMultiCycleSva()) return buildExpr(exprp, currentp, isTopLevelStep); + if (exprp->isMultiCycleSva()) { + const BuildResult result = buildExpr(exprp, currentp, isTopLevelStep); + if (result.valid()) addFiniteRangeReject(rangeRejectInfo, result, flp); + return result; + } return {currentp, exprp, std::move(rangeMidSources)}; } @@ -1505,15 +1558,25 @@ class SvaNfaLowering final { computeTerminalMatchAndReject(c, snapshotOkp, sigs, outPerMidSrcsp); // Phase 3a: required-step rejection. - // Builder only sets m_rejectOnFail on non-clocked Links with m_condp, - // and the source always has a resolved stateSig. + // Builder only sets m_rejectOnFail on non-clocked Links with m_condp + // or m_condVtxp, and the source always has a resolved stateSig. for (const SvaTransEdge* const tep : c.edges) { if (!tep->m_rejectOnFail) continue; const int fi = tep->fromVtxp()->color(); - UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && tep->m_condp, tep->fromVtxp(), - "rejectOnFail Link must have condp and source stateSig"); + UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && (tep->m_condp || tep->m_condVtxp), + tep->fromVtxp(), + "rejectOnFail Link must have condp/condVtxp and source stateSig"); AstNodeExpr* const srcSigp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false); - AstNodeExpr* const notCondp = new AstLogNot{c.flp, tep->m_condp->cloneTreePure(false)}; + AstNodeExpr* condp = nullptr; + if (tep->m_condVtxp) { + const int ci = tep->m_condVtxp->color(); + UASSERT_OBJ(c.vtx[ci]->datap()->stateSigp, tep->m_condVtxp, + "rejectOnFail condVtxp missing stateSig"); + condp = c.vtx[ci]->datap()->stateSigp->cloneTreePure(false); + } else { + condp = tep->m_condp->cloneTreePure(false); + } + AstNodeExpr* const notCondp = new AstLogNot{c.flp, condp}; AstNodeExpr* const failp = new AstLogAnd{c.flp, srcSigp, notCondp}; if (outRequiredStepSrcsp) { outRequiredStepSrcsp->push_back(failp->cloneTreePure(false)); diff --git a/test_regress/t/t_property_sexpr_range_delay.v b/test_regress/t/t_property_sexpr_range_delay.v index 5594b6a54..cd3037d03 100644 --- a/test_regress/t/t_property_sexpr_range_delay.v +++ b/test_regress/t/t_property_sexpr_range_delay.v @@ -30,6 +30,7 @@ module t ( int count_fail2 = 0; int count_fail3 = 0; int count_fail4 = 0; + int count_fail5 = 0; always_ff @(posedge clk) begin `ifdef TEST_VERBOSE @@ -49,13 +50,11 @@ module t ( else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); `checkh(sum, 64'h38c614665c6b71ad); - // count_fail1 overcounts Questa by 1: NFA per-cycle reject merging - // OR's requiredStep-fail and terminal-fail into one signal; Questa - // resolves the same overlap as a single per-attempt miss. - `checkd(count_fail1, 25); // Questa: 24 - `checkd(count_fail2, 50); // Questa: 50 - `checkd(count_fail3, 24); // Questa: 24 - `checkd(count_fail4, 1); // Questa: 1 + `checkd(count_fail1, 24); + `checkd(count_fail2, 50); + `checkd(count_fail3, 24); + `checkd(count_fail4, 1); + `checkd(count_fail5, 1); $write("*-* All Finished *-*\n"); $finish; end @@ -131,4 +130,25 @@ module t ( assert property (@(posedge clk) disable iff (cyc < 2) a |-> ##[+] b ##1 (a | b | c | d | e)); + // Finite range delay with a multi-cycle RHS must not reject on an earlier + // candidate when a later candidate in the same window matches. + assert property (@(posedge clk) + cyc == 10 |-> ##[2:4] ((cyc == 12 || cyc == 13) ##1 cyc == 14)); + + // Same shape, but every RHS candidate in the range window rejects, so the + // assertion attempt itself must reject once. + assert property (@(posedge clk) + cyc == 20 |-> ##[2:4] ((cyc == 22 || cyc == 23) ##1 cyc == 25)) + else count_fail5 <= count_fail5 + 1; + + // Variable-length nested RHS, then another finite range below + // the liveness scope. + assert property (@(posedge clk) + cyc == 30 |-> ##[1:2] ((cyc == 31) ##[1:2] ((cyc == 32) ##1 1'b1))); + + // Finite range whose RHS ends in an NFA state, not a final + // boolean condition. + assert property (@(posedge clk) + cyc == 40 |-> ##[1:2] (##1 (((cyc == 43) ##1 1'b1) or ((cyc == 43) ##1 1'b1)))); + endmodule From c86816476c132049cc7755da42ddee2414812378 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Mon, 15 Jun 2026 17:37:49 -0400 Subject: [PATCH 40/89] Commentary: Changes update --- Changes | 4 ++++ docs/guide/exe_verilator.rst | 2 +- docs/spelling.txt | 1 + .../t_sched_ico_change_detect_input_assigned.v | 17 ++++++++++------- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Changes b/Changes index 9877ca716..afe5731e2 100644 --- a/Changes +++ b/Changes @@ -66,6 +66,8 @@ Verilator 5.049 devel * Support `s_until` and `s_until_with`(#7722). [Artur Bieniek, Antmicro Ltd.] * Support covergroup runtime model Phase A1 (#7728). [Matthew Ballance] * Support reduction XOR/AND operations in constraints (#7753). [Kornel Uriasz, Antmicro Ltd.] +* Support assertion control system tasks in classes and interfaces (#7761). [Yilou Wang] +* Support cover sequence statement (#7764). [Yilou Wang] * Support unpacked struct stream (#7767). [Nick Brereton] * Optimize emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.] * Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.] @@ -86,6 +88,7 @@ Verilator 5.049 devel * Optimize bit select removal earlier in DFG (#7762). [Geza Lore, Testorrent USA, Inc.] * Optimize away proven redundant case statement assertions (#7771). [Geza Lore, Testorrent USA, Inc.] * Optimize table lookups in DFG (#7772). [Geza Lore, Testorrent USA, Inc.] +* Optimize input combinational logic by change detection (#7784). [Geza Lore, Testorrent USA, Inc.] * Fix TSP variable ordering for mtasks (#5342) (#7610). [Muzaffer Kal] * Fix inlining static initializer in V3Gate (#5381) (#7503). [Andrew Nolte] [Geza Lore, Testorrent USA, Inc.] * Fix timed nested fork block with disable (#6720) (#7743). [Marco Bartoli] @@ -161,6 +164,7 @@ Verilator 5.049 devel * Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.] * Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang] * Fix 'case (_) inside' with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.] +* Fix not failing assertion when RHS of a range window rejects once (#7773). [Artur Bieniek, Antmicro Ltd.] * Fix $fflush and autoflush with --threads (#7782). diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 73ed42486..29e3876c4 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -750,7 +750,7 @@ Summary: for top level input signals that are written within the design. Accesses via the VPI cannot be analyzed at compile time, therefore :vlopt:`--vpi` disables this optimization for all inputs; it may be turned back on by - explicitly passing :vlopt:`-fico-change-detect`. + explicitly passing :vlopt:`-fico-change-detect <-fno-ico-change-detect>`. .. option:: -fno-inline diff --git a/docs/spelling.txt b/docs/spelling.txt index c41825793..a21f66ed5 100644 --- a/docs/spelling.txt +++ b/docs/spelling.txt @@ -861,6 +861,7 @@ hx hyperthreading hyperthreads icecream +ico idmap ifdef ifdefed diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.v b/test_regress/t/t_sched_ico_change_detect_input_assigned.v index 0f6f9025d..9a4f79b0b 100644 --- a/test_regress/t/t_sched_ico_change_detect_input_assigned.v +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.v @@ -10,20 +10,23 @@ `define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); // verilog_format: on -module t( - clk, i, o, cyc +module t ( + clk, + i, + o, + cyc ); input clk, i; output o, cyc; - logic clk; - int i; // Primary input that the design also drives - int o; - int cyc = 0; + logic clk; + int i; // Primary input that the design also drives + int o; + int cyc = 0; // Logic dependent on primary input 'i' - always_comb o = i + 10; + always_comb o = i + 10; always @(posedge clk) begin cyc <= cyc + 1; From 0e4a3a92b0f922508e4bc155bc47df1d523bccb3 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Mon, 15 Jun 2026 17:44:50 -0400 Subject: [PATCH 41/89] CI: Autoformat markdown files --- AGENTS.md | 36 ++++++++++++++++++------------------ Makefile.in | 14 +++++++++++++- docs/AGENTS.md | 2 +- include/AGENTS.md | 4 ++-- python-dev-requirements.txt | 1 + src/AGENTS.md | 18 ++++++++++++++++-- test_regress/AGENTS.md | 10 ++++++++-- 7 files changed, 59 insertions(+), 26 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1f0c6a6be..2f5f06eda 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,7 @@ Then read the directory guide for the area you are editing: - [test_regress/AGENTS.md](test_regress/AGENTS.md) -- regression tests: harness, drivers, golden files - [docs/AGENTS.md](docs/AGENTS.md) -- documentation (`*.rst`) ---- +______________________________________________________________________ # Orientation @@ -75,7 +75,7 @@ top-of-file comment. - Run the full regression with `make test`. The complete suite requires configuring with `--enable-longtests` (works on every OS, including macOS). ---- +______________________________________________________________________ # Before you open a PR @@ -83,21 +83,21 @@ top-of-file comment. - [ ] Searched open PRs and issues -- duplicating in-flight work wastes review time. - [ ] Fixed the general root cause, not just the reported case -- if it also - affects other modules/classes/interfaces, cover them or expect rejection. + affects other modules/classes/interfaces, cover them or expect rejection. - [ ] PR is single-purpose. Refactors, drive-by fixes found along the way, and new - features each go in separate PRs; land standalone cleanups first. + features each go in separate PRs; land standalone cleanups first. - [ ] Every bug fix has a test that fails *without* the fix; include the issue's - own reproducer when possible. + own reproducer when possible. - [ ] New code aims for 100% line coverage; branch coverage far below line coverage - signals guards callers never violate -- justify or remove them. + signals guards callers never violate -- justify or remove them. - [ ] Ran `make format` (clang-format), `make cppcheck`, and `make lint-py`; - self-reviewed the diff for leftover debug code, stale comments, and - copy-paste errors. + self-reviewed the diff for leftover debug code, stale comments, and + copy-paste errors. - [ ] Ran the full regression on at least one OS before submitting. Partial runs - are fine during development, but the submitted PR is expected to pass every - test. + are fine during development, but the submitted PR is expected to pass every + test. - [ ] Did not edit `docs/CONTRIBUTORS` (humans only) or `Changes` (maintainer - updates it near release). + updates it near release). ## Pick the right diagnostic (and its required test) @@ -122,18 +122,18 @@ The API you choose determines which test must accompany the change. ## Cross-cutting code rules - [ ] No non-ASCII characters in C++ sources or headers: write `--` (two ASCII - hyphens) rather than a Unicode em-dash, and a plain `'` rather than a smart - quote. At write time, not when CI complains. + hyphens) rather than a Unicode em-dash, and a plain `'` rather than a smart + quote. At write time, not when CI complains. - [ ] Lists stay sorted: lexer/parser tokens, option declarations, enum values, - configure feature lists, documented option lists. + configure feature lists, documented option lists. - [ ] `bin/` scripts are Python (distributed cross-platform); `nodist/` may use - bash and platform-specific code (developer-only, not packaged). + bash and platform-specific code (developer-only, not packaged). - [ ] Runtime code in `include/` targets C++14 (`--no-timing` builds must work); - C++20 only in timing code paths. + C++20 only in timing code paths. - [ ] In `include/` public headers, prefix public classes with `Verilated`/`Vl` - and document the API with `///` comments. + and document the API with `///` comments. - [ ] A new code pattern is applied globally or not at all -- no one-off - convention in a single file. + convention in a single file. ## Commits diff --git a/Makefile.in b/Makefile.in index c2b60d004..6bcbdbaa5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -495,6 +495,11 @@ MAKE_FILES = \ src/Makefile*.in \ test_regress/Makefile* \ +# Markdown +MD_FILES = \ + *.md \ + */*.md \ + # Perl programs PERL_PROGRAMS = \ bin/redirect \ @@ -553,7 +558,7 @@ YAML_FILES = \ # Format format: - $(MAKE) -j 5 format-c format-cmake format-exec format-py format-yaml + $(MAKE) -j 5 format-c format-cmake format-exec format-md format-py format-yaml BEAUTYSH = beautysh BEAUTYSH_FLAGS = --indent-size 2 @@ -590,6 +595,13 @@ format-make mbake: $(MBAKE) --version $(MBAKE) $(MBAKE_FLAGS) $(MAKE_FILES) +MDFORMAT = mdformat +MDFORMAT_FLAGS = + +format-md: + $(MDFORMAT) --version + $(MDFORMAT) $(MDFORMAT_FLAGS) $(MD_FILES) + YAPF = yapf YAPF_FLAGS = -i --parallel diff --git a/docs/AGENTS.md b/docs/AGENTS.md index ee4ae5249..1c0524c78 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -2,7 +2,7 @@ SPDX-FileCopyrightText: 2026-2026 Wilson Snyder SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 --> -# docs/ Guidelines -- Verilator documentation (*.rst) +# docs/ Guidelines -- Verilator documentation (\*.rst) When to check: before editing anything under `docs/`. Read the repository-root [AGENTS.md](../AGENTS.md) first for process and cross-cutting rules. diff --git a/include/AGENTS.md b/include/AGENTS.md index 5b409dfaf..0ac1fcbfe 100644 --- a/include/AGENTS.md +++ b/include/AGENTS.md @@ -9,7 +9,7 @@ that every generated model links against. Read the repository-root [AGENTS.md](../AGENTS.md) first. The rules here differ from `src/`: this code ships to users, runs every simulation cycle, and must stay portable and fast. ---- +______________________________________________________________________ # Orientation @@ -22,7 +22,7 @@ ships to users, runs every simulation cycle, and must stay portable and fast. (`--timing` runtime), `verilated_vcd_c.*`/`verilated_fst_c.*` (tracing). - A runtime-only fix lives entirely here and does not rebuild `verilator_bin`. ---- +______________________________________________________________________ # Before you open a PR diff --git a/python-dev-requirements.txt b/python-dev-requirements.txt index 11f9fad84..d6bcba7b1 100644 --- a/python-dev-requirements.txt +++ b/python-dev-requirements.txt @@ -21,6 +21,7 @@ compiledb==0.10.7 distro==1.9.0 gersemi==0.23.1 mbake==1.4.3 +mdformat==1.0.0 mypy==1.19.0 pylint==3.0.2 ruff==0.14.8 diff --git a/src/AGENTS.md b/src/AGENTS.md index 1904eb5c9..cb53dc31c 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -9,7 +9,7 @@ Covers all C++ under `src/`, including astgen inputs and the parser/lexer first. This file has two parts: **Orientation** explains the AST and pass model; **Before you open a PR** is the style and correctness checklist. ---- +______________________________________________________________________ # Orientation: the AST and the visitor model @@ -38,7 +38,7 @@ first. This file has two parts: **Orientation** explains the AST and pass model; - `docs/internals.rst` is the authoritative reference for the AST, the pass list, and node lifetime. ---- +______________________________________________________________________ # Before you open a PR @@ -77,6 +77,7 @@ first. This file has two parts: **Orientation** explains the AST and pass model; - Build logic as AST nodes, never as raw C text in `AstCStmt` -- later passes (V3Name, `--protect`) rename AST identifiers but cannot see into raw strings. + - Know the cast forms (above) and never pair `VN_IS` with `VN_AS` on the same node -- use a single `VN_CAST`: @@ -89,34 +90,47 @@ first. This file has two parts: **Orientation** explains the AST and pass model; - Use `UASSERT_OBJ(cond, nodep, "...")` over `UASSERT` when a node is in scope; use `v3fatalSrc("...")` for unreachable paths, never a silent `if (!ptr) return;`. + - Use `VL_DO_DANGLING(pushDeletep(nodep), nodep)` instead of `deleteTree()` in visitors -- deferred deletion is safe against re-entry and unlinking order. `deleteTree()` is only for fresh nodes that never entered the tree. + - Every new AST member needs both `dump()` and `dumpJson()` support -- never wrap these in `LCOV_EXCL`; cover them by adding a construct to `t_debug_emitv.v`. Override `isSame()` to include any new semantically meaningful field. + - Pointers to nodes outside op1p-op4p require a `broken()` override and `cloneRelink()` support -- avoid storing out-of-tree node pointers when possible. + - Never allocate AstNode objects on the stack or by value -- always pointers. + - Prefer a new `visit()` in an existing visitor over `nodep->foreach(...)` -- better for runtime, and handles diverse node types better. Prefer named accessors over `op1p()`..`op4p()`, and verify traversal order is preserved when converting manual iteration. + - Prefer `AstForeach` over generating unrolled loop bodies -- constant-size code instead of O(N); wrap the body in `AstBegin` for scope isolation. + - Always `skipRefp()` when comparing or resolving dtypes -- missing it breaks typedef support; prove the paths with typedef tests. + - Use `num().isOpaque()` rather than `isDouble() || isString()` when guarding V3Number comparisons against non-integer types. + - Use `FileLine::operatorCompare` for source-position ordering -- never hand-roll filename/lineno comparisons. + - Identify compiler-generated constructs by an attribute flag on the node (with dump support), never by name-pattern matching -- magic names break with escaped identifiers. + - Use `V3Number` arithmetic for `AstConst` values wider than 32 bits -- `1 << i` silently overflows at `i >= 32`. + - Use `VMemberMap`/`findMember()` for name lookups in structs, classes, modules, and packages -- O(1) versus quadratic linear scans. + - Never emit raw source filenames or identifiers in generated code -- pass them through `protect()`/`putsQuoted` so `--protect` does not leak source. diff --git a/test_regress/AGENTS.md b/test_regress/AGENTS.md index 05833d460..7d6b1b708 100644 --- a/test_regress/AGENTS.md +++ b/test_regress/AGENTS.md @@ -9,7 +9,7 @@ Covers `.v`/`.sv` sources, `.py` drivers, and `.out` golden files under file has two parts: **Orientation** explains how the harness runs a test; **Before you open a PR** is the test-authoring checklist. ---- +______________________________________________________________________ # Orientation: how the harness works @@ -30,7 +30,7 @@ file has two parts: **Orientation** explains how the harness runs a test; lists, warning documentation, ASCII-only) -- run the relevant ones before submitting. ---- +______________________________________________________________________ # Before you open a PR @@ -78,6 +78,7 @@ file has two parts: **Orientation** explains how the harness runs a test; ## Verilog style - 2-space indentation, no tabs. + - Declarations are flush-left with a single space between type and name; never column-align: @@ -92,12 +93,17 @@ file has two parts: **Orientation** explains how the harness runs a test; - Run `nodist/verilog_format` on new `.v` files; wrap macro definitions in `// verilog_format: off`/`on` so the formatter does not split them. + - Use `$display("%0d", ...)` not `%d` -- avoids leading-space padding. + - Wrap Verilator-specific test code (e.g. `$c`) in `` `ifdef VERILATOR ``. + - Use inline `// verilator lint_off WARNCODE` only when that warning is itself under test -- fix root causes otherwise. + - Use only IEEE 1800-compliant constructs other simulators also accept -- tests validate standard behavior, not Verilator's parser leniency. + - Omit optional end labels on `endmodule`/`endclass`/`endtask`/`endfunction`. ## Self-checking From a5fad9882fe1557a95ca6fe5a9678486731c0db2 Mon Sep 17 00:00:00 2001 From: Nikolai Kumar Date: Mon, 15 Jun 2026 20:57:59 -0500 Subject: [PATCH 42/89] Fix force unpacked bitselect (#7744) (#7745) Fixes #7744. --- include/verilated_force.h | 107 +++++++++++++++--- src/V3Force.cpp | 50 ++++----- test_regress/t/t_force_unpacked_bitsel.py | 18 +++ test_regress/t/t_force_unpacked_bitsel.v | 131 ++++++++++++++++++++++ 4 files changed, 265 insertions(+), 41 deletions(-) create mode 100755 test_regress/t/t_force_unpacked_bitsel.py create mode 100644 test_regress/t/t_force_unpacked_bitsel.v diff --git a/include/verilated_force.h b/include/verilated_force.h index d50d27c18..5cbafdc62 100644 --- a/include/verilated_force.h +++ b/include/verilated_force.h @@ -101,12 +101,13 @@ using VlForceStorageType = typename VlForceStorageTypeOf>::ty class VlForceVec final { private: struct Entry final { - int m_lsb; // Inclusive lower bit - int m_msb; // Inclusive upper bit + int m_lsb; // Inclusive lower bit for scalar path or element index for unpacked + int m_msb; // Inclusive upper bit for scalar path or element index for unpacked int m_rhsLsb; // Destination index that maps to RHS index 0 const void* m_rhsDatap; // Pointer to RHS storage - - bool operator<(const Entry& other) const { return m_msb < other.m_msb; } + int m_bitLsb = 0; + int m_bitMsb = 0; + int m_elemWidth = 0; }; std::vector m_entries; // Sorted by msb, non-overlapping @@ -134,6 +135,41 @@ private: return it; } + std::size_t trimElementBitRange(int elem, int bitLsb, int bitMsb) { + auto it = std::lower_bound(m_entries.begin(), m_entries.end(), elem, + [](const Entry& e, int idx) { return e.m_msb < idx; }); + while (it != m_entries.end() && it->m_lsb <= elem) { + if (it->m_elemWidth == 0 || it->m_bitMsb < bitLsb || it->m_bitLsb > bitMsb) { + ++it; + continue; + } + if (it->m_bitLsb < bitLsb && it->m_bitMsb > bitMsb) { + Entry high = *it; + high.m_bitLsb = bitMsb + 1; + it->m_bitMsb = bitLsb - 1; + m_entries.insert(it + 1, high); + break; + } + if (it->m_bitLsb < bitLsb) { + it->m_bitMsb = bitLsb - 1; + ++it; + continue; + } + if (it->m_bitMsb > bitMsb) { + it->m_bitLsb = bitMsb + 1; + break; + } + it = m_entries.erase(it); + } + auto ins = std::lower_bound(m_entries.begin(), m_entries.end(), elem, + [](const Entry& e, int idx) { return e.m_msb < idx; }); + while (ins != m_entries.end() && ins->m_lsb <= elem + && (ins->m_elemWidth == 0 || ins->m_bitLsb <= bitLsb)) { + ++ins; + } + return static_cast(ins - m_entries.begin()); + } + static QData extractRhsChunk(const Entry& entry, int rhsLsb, int width) { assert(width > 0 && width <= VL_QUADSIZE); assert(rhsLsb >= 0); @@ -195,6 +231,22 @@ private: return *static_cast*>(entry.m_rhsDatap); } + template + static typename std::enable_if::value, Elem>::type + blendElem(Elem cur, const Entry& e) { + const Entry bitEntry{e.m_bitLsb, e.m_bitMsb, e.m_rhsLsb, e.m_rhsDatap, 0, 0, 0}; + return applyEntry(cur, bitEntry); + } + + template + static typename std::enable_if::value, Elem>::type blendElem(Elem cur, + const Entry& e) { + Elem res = cur; + const Entry bitEntry{e.m_bitLsb, e.m_bitMsb, e.m_rhsLsb, e.m_rhsDatap, 0, 0, 0}; + applyEntry(res, bitEntry, e.m_bitLsb, e.m_bitMsb, 0); + return res; + } + template typename std::enable_if::value>::type applyEntries(T& val) const { for (const auto& entry : m_entries) { @@ -229,15 +281,19 @@ public: using ElemRef = decltype(VlForceArrayIndexer::elem(result, static_cast(0))); using Elem = VlForceBaseType; - const int total = static_cast(VlForceArrayIndexer::size); for (const auto& entry : m_entries) { - const Elem* const rhsBasep = static_cast(entry.m_rhsDatap); const int startIdx = entry.m_lsb; const int endIdx = entry.m_msb; for (int idx = startIdx; idx <= endIdx; idx++) { - const int rhsIndex = idx - entry.m_rhsLsb; const std::size_t uidx = static_cast(idx); - VlForceArrayIndexer::elem(result, uidx) = rhsBasep[rhsIndex]; + Elem& dst = VlForceArrayIndexer::elem(result, uidx); + if (entry.m_elemWidth == 0) { + const Elem* const rhsBasep = static_cast(entry.m_rhsDatap); + const int rhsIndex = idx - entry.m_rhsLsb; + dst = rhsBasep[rhsIndex]; + } else { + dst = blendElem(dst, entry); + } } } return result; @@ -250,14 +306,18 @@ public: T readIndex(T origVal, int index) const { if (m_entries.empty()) return origVal; - const auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index, - [](const Entry& e, int idx) { return e.m_msb < idx; }); - if (it != m_entries.end() && it->m_lsb <= index) { - const int rhsIndex = index - it->m_rhsLsb; - const T* const rhsBasep = static_cast(it->m_rhsDatap); - return rhsBasep[rhsIndex]; + T result = origVal; + for (auto it = std::lower_bound(m_entries.begin(), m_entries.end(), index, + [](const Entry& e, int idx) { return e.m_msb < idx; }); + it != m_entries.end() && it->m_lsb <= index; ++it) { + if (it->m_elemWidth == 0) { + const int rhsIndex = index - it->m_rhsLsb; + result = static_cast(it->m_rhsDatap)[rhsIndex]; + } else { + result = blendElem(result, *it); + } } - return origVal; + return result; } IData readSelI(int lbits, WDataInP valp, int lsb, int width) const { @@ -291,11 +351,28 @@ public: m_entries.insert(it, {lsb, msb, rhsLsb, rhsDatap}); } + void addForce(int lsb, int msb, const void* rhsDatap, int rhsLsb, int bitLsb, int bitMsb, + int elemWidth) { + assert(lsb == msb); + assert(rhsDatap); + assert(elemWidth > 0); + assert(0 <= bitLsb && bitLsb <= bitMsb && bitMsb < elemWidth); + const std::size_t at = trimElementBitRange(lsb, bitLsb, bitMsb); + m_entries.insert(m_entries.begin() + at, + Entry{lsb, msb, rhsLsb, rhsDatap, bitLsb, bitMsb, elemWidth}); + } + void release(int lsb, int msb) { assert(lsb <= msb); trimEntries(lsb, msb); } + void release(int lsb, int msb, int bitLsb, int bitMsb) { + assert(lsb == msb); + assert(bitLsb <= bitMsb); + trimElementBitRange(lsb, bitLsb, bitMsb); + } + void touch() {} }; diff --git a/src/V3Force.cpp b/src/V3Force.cpp index 75f3cf256..3a7b51ce9 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -870,30 +870,8 @@ class ForceDiscoveryVisitor final : public VNVisitorConst { ForceState::ForceRangeInfo rangeInfo = m_state.getForceRangeInfo(nodep->lhsp(), forcedVarp, true); - // Start from a cloned RHS expression; adjust below for partial bit selects. - const AstSel* const selLhsp = VN_CAST(nodep->lhsp(), Sel); - AstNodeExpr* rhsExprp = nodep->rhsp()->cloneTreePure(false); - - // For bitwise selects inside arrays, merge updated bits with preserved base bits. - if (rangeInfo.m_hasArraySel && rangeInfo.m_arrayInfo.m_hasBitSel && selLhsp - && ForceState::isBitwiseDType(selLhsp->fromp())) { - AstNodeExpr* const baseExprp = selLhsp->fromp()->cloneTreePure(false); - baseExprp->foreach( - [](AstVarRef* const refp) { ForceState::markNonReplaceable(refp); }); - - // Pad the selected value back to full base width before masking/or-ing. - rhsExprp = ForceState::zeroPadToBaseWidth(rhsExprp, selLhsp->fromp()->width(), - rangeInfo.m_padLsb, rangeInfo.m_padMsb); - - // Keep untouched base bits and insert the newly forced bit range. - // rhsExpr = (baseExpr & ~mask(range)) | (zeroPad(force_rhs) & mask(range)); - AstConst* const maskConstp = ForceState::makeRangeMaskConst( - nodep->lhsp(), selLhsp->fromp()->width(), rangeInfo.m_padLsb, rangeInfo.m_padMsb); - AstNodeExpr* const maskedOldp - = new AstAnd{nodep->lhsp()->fileline(), baseExprp, - new AstNot{nodep->lhsp()->fileline(), maskConstp}}; - rhsExprp = new AstOr{nodep->lhsp()->fileline(), maskedOldp, rhsExprp}; - } + // Keep narrow rhs, VlForceVec blends unpacked-array bit-select forces at read time + AstNodeExpr* const rhsExprp = nodep->rhsp()->cloneTreePure(false); m_state.addForceAssignment(forcedVarp, lhsVarRefp->varScopep(), rhsExprp, nodep, rangeInfo.m_rangeLsb, rangeInfo.m_rangeMsb, rangeInfo.m_padLsb, @@ -1114,6 +1092,11 @@ class ForceConvertVisitor final : public VNVisitor { // Verilog pseudocode: // forceVec.addForce(range_lsb, range_msb, &forceRHS[id], rhs_lsb); + const AstSel* const selLhsp = VN_CAST(lhsp, Sel); + const bool arrayBitSel + = info.m_hasArraySel && selLhsp && ForceState::getArraySelInfo(lhsp).m_hasBitSel + && ForceState::isBitwiseDType(selLhsp->fromp()) + && (info.m_padMsb - info.m_padLsb + 1) < selLhsp->fromp()->width(); AstNodeExpr* const rhsDatap = ForceState::buildRhsDataExpr(flp, info); AstCExpr* const rhsAddrp = new AstCExpr{flp}; rhsAddrp->add("&("); @@ -1124,7 +1107,13 @@ class ForceConvertVisitor final : public VNVisitor { ForceState::makeConst32(flp, info.m_rangeLsb)}; addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_rangeMsb)); addForceCallp->addPinsp(rhsAddrp); - addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_rangeLsb)); + addForceCallp->addPinsp( + ForceState::makeConst32(flp, arrayBitSel ? info.m_padLsb : info.m_rangeLsb)); + if (arrayBitSel) { + addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_padLsb)); + addForceCallp->addPinsp(ForceState::makeConst32(flp, info.m_padMsb)); + addForceCallp->addPinsp(ForceState::makeConst32(flp, selLhsp->fromp()->width())); + } addForceCallp->dtypeSetVoid(); AstNodeStmt* const stmtp = addForceCallp->makeStmt(); @@ -1164,12 +1153,21 @@ class ForceConvertVisitor final : public VNVisitor { const ForceState::ForceRangeInfo rangeInfo = m_state.getForceRangeInfo(lhsp, releasedVarp, false); + const AstSel* const selLhsp = VN_CAST(lhsp, Sel); + const bool arrayBitSel + = rangeInfo.m_hasArraySel && selLhsp && rangeInfo.m_arrayInfo.m_hasBitSel + && ForceState::isBitwiseDType(selLhsp->fromp()) + && (rangeInfo.m_padMsb - rangeInfo.m_padLsb + 1) < selLhsp->fromp()->width(); AstCMethodHard* const releaseCallp = new AstCMethodHard{ flp, new AstVarRef{flp, varInfo->m_forceVecVscp, VAccess::WRITE}, VCMethod::FORCE_RELEASE, ForceState::makeConst32(flp, rangeInfo.m_rangeLsb)}; releaseCallp->addPinsp(ForceState::makeConst32(flp, rangeInfo.m_rangeMsb)); + if (arrayBitSel) { + releaseCallp->addPinsp(ForceState::makeConst32(flp, rangeInfo.m_padLsb)); + releaseCallp->addPinsp(ForceState::makeConst32(flp, rangeInfo.m_padMsb)); + } releaseCallp->dtypeSetVoid(); - // forceVec.release(range_lsb, range_msb); + // forceVec.release(range_lsb, range_msb [, bit_lsb, bit_msb]); AstNodeStmt* const releasep = releaseCallp->makeStmt(); AstAssign* clearEnp = nullptr; diff --git a/test_regress/t/t_force_unpacked_bitsel.py b/test_regress/t/t_force_unpacked_bitsel.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_force_unpacked_bitsel.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_force_unpacked_bitsel.v b/test_regress/t/t_force_unpacked_bitsel.v new file mode 100644 index 000000000..7a98869d0 --- /dev/null +++ b/test_regress/t/t_force_unpacked_bitsel.v @@ -0,0 +1,131 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Nikolai Kumar +// SPDX-License-Identifier: CC0-1.0 + +`define checkh(g,e) do if ((g) !==(e)) begin $write("%%Error: %s:%0d: got=%b exp=%b\n", `__FILE__,`__LINE__, (g),(e)); $stop; end while(0) + + +module t ( + input clk +); + int cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + logic [4:1] var_arr [2]; + /* verilator lint_off ASCRANGE */ + logic [1:4] var_arr_a [2]; + /* verilator lint_on ASCRANGE */ + logic [72:1] wide_arr [2]; + + always @(posedge clk) begin + var_arr[0] <= 4'b0101; + var_arr[1] <= (cyc <= 3) ? 4'b1111 : (cyc <= 7) ? 4'b0000 : 4'b0001; + var_arr_a[0] <= 4'b1010; + var_arr_a[1] <= 4'b0000; + wide_arr[0] <= '0; + wide_arr[1] <= 72'hAB_CDEF0123_456789AB; + end + + always @(posedge clk) begin + if (cyc == 2) force var_arr[1][1] = 1'b0; + if (cyc == 6) force var_arr[1][4] = 1'b1; + if (cyc == 8) release var_arr[1][1]; + if (cyc == 10) force var_arr[1] = 4'b1010; + if (cyc == 12) release var_arr[1]; + + if (cyc == 2) force wide_arr[1][36:5] = 32'hffff_ffff; + if (cyc == 4) release wide_arr[1]; + + if (cyc == 2) force var_arr_a[1][2:4] = 3'b111; + if (cyc == 4) force var_arr_a[1][3]= 1'b0; + if (cyc == 6) release var_arr_a[1]; + if (cyc == 7) force var_arr_a[1][3:4] = 2'b11; + if (cyc == 9) force var_arr_a[1][2:3]= 2'b00; + if (cyc == 11) release var_arr_a[1]; + if (cyc == 12) force var_arr_a[1][1:2] = 2'b11; + if (cyc == 14) force var_arr_a[1][2:3]= 2'b00; + if (cyc == 16) release var_arr_a[1]; + if (cyc == 17) force var_arr_a[1][2:3] = 2'b11; + if (cyc == 19) force var_arr_a[1][1:3]= 3'b000; + if (cyc == 21) release var_arr_a[1]; + + if (cyc == 14) force var_arr[1][1] = 1'b0; + if (cyc == 14) force var_arr[0][1] = 1'b0; + if (cyc == 16) release var_arr[1][1]; + if (cyc == 16) release var_arr[0][1]; + if (cyc == 18) force var_arr[0] = 4'b1010; + if (cyc == 20) force var_arr[0][1] = 1'b1; + if (cyc == 22) release var_arr[0]; + end + + always @(posedge clk) case (cyc) + 1: begin + `checkh(var_arr[0], 4'b0101); + `checkh(var_arr[1], 4'b1111); + end + 3: begin + `checkh(var_arr[1], 4'b1110); + `checkh(wide_arr[1][36:5], 32'hffff_ffff); + `checkh(wide_arr[1][4:1], 4'hB); + `checkh(wide_arr[1][72:37], 36'hABCDEF012); + `checkh(var_arr_a[1], 4'b0111); + `checkh(var_arr_a[0], 4'b1010); + end + 5: begin + `checkh(var_arr[1], 4'b0000); + `checkh(wide_arr[1], 72'hAB_CDEF0123_456789AB); + `checkh(var_arr_a[1], 4'b0101); + end + 7: begin + `checkh(var_arr[1], 4'b1000); + end + 8: begin + `checkh(var_arr_a[1], 4'b0011); + end + 9: begin + `checkh(var_arr[1], 4'b1001); + end + 10: begin + `checkh(var_arr_a[1], 4'b0001); + end + 11: begin + `checkh(var_arr[1], 4'b1010); + `checkh(var_arr[0], 4'b0101); + end + 13: begin + `checkh(var_arr[1], 4'b0001); + `checkh(var_arr_a[1], 4'b1100); + end + 15: begin + `checkh(var_arr_a[1], 4'b1000); + `checkh(var_arr[1], 4'b0000); + `checkh(var_arr[0], 4'b0100); + end + 17: begin + `checkh(var_arr[1], 4'b0001); + `checkh(var_arr[0], 4'b0101); + end + 18: begin + `checkh(var_arr_a[1], 4'b0110); + end + 19: begin + `checkh(var_arr[0], 4'b1010); + end + 20: begin + `checkh(var_arr_a[1], 4'b0000); + end + 21: begin + `checkh(var_arr[0], 4'b1011); + end + 22: begin + `checkh(var_arr_a[1], 4'b0000); + end + 23: begin + `checkh(var_arr[0], 4'b0101); + $write("*-* All Finished *-*\n"); + $finish; + end + endcase +endmodule From 38fd99b37b8505f3fe58e3dcfe34a2b4eb779268 Mon Sep 17 00:00:00 2001 From: Jakub Michalski <143384197+jmichalski-ant@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:21:11 +0200 Subject: [PATCH 43/89] Fix out-of-bounds read value for 2-state types (#7785) Signed-off-by: Jakub Michalski --- src/V3Unknown.cpp | 6 +++++- test_regress/t/t_oob_2state_array.py | 19 +++++++++++++++++++ test_regress/t/t_oob_2state_array.v | 28 ++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100755 test_regress/t/t_oob_2state_array.py create mode 100644 test_regress/t/t_oob_2state_array.v diff --git a/src/V3Unknown.cpp b/src/V3Unknown.cpp index c8851280d..d0a4aff84 100644 --- a/src/V3Unknown.cpp +++ b/src/V3Unknown.cpp @@ -514,7 +514,11 @@ class UnknownVisitor final : public VNVisitor { } else if (nodeDtp->isString()) { xnum = V3Number{nodep, V3Number::String{}, ""}; } else { - xnum.setAllBitsX(); + if (nodeDtp->isFourstate()) { + xnum.setAllBitsX(); + } else { + xnum.setAllBits0(); + } } AstNode* const newp = new AstCond{nodep->fileline(), condp, nodep, new AstConst{nodep->fileline(), xnum}}; diff --git a/test_regress/t/t_oob_2state_array.py b/test_regress/t/t_oob_2state_array.py new file mode 100755 index 000000000..7b63fce7f --- /dev/null +++ b/test_regress/t/t_oob_2state_array.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +# x-assign shouldn't affect 2-state oob read +test.compile(verilator_flags2=["--binary --x-assign 1"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_oob_2state_array.v b/test_regress/t/t_oob_2state_array.v new file mode 100644 index 000000000..0fd01af2f --- /dev/null +++ b/test_regress/t/t_oob_2state_array.v @@ -0,0 +1,28 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); + + +module t; + logic clk = 1'b0; + always #1 clk = ~clk; + + int idx = 0; + bit a[0:2] = {1, 1, 1}; + + always @(posedge clk) begin + if (idx == 4) begin + $write("*-* All Finished *-*\n"); + $finish; + end + else begin + idx <= idx + 1; + `checkh(a[idx], idx <= 2 ? 1 : 0); + end + end +endmodule From bec45125bd3319ea99e14a52ce1e04dbe9ee9f3c Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Tue, 16 Jun 2026 17:52:35 +0200 Subject: [PATCH 44/89] Fix `cover property` of an implication counting vacuous matches (#7789) --- src/V3AssertPre.cpp | 14 +++++- test_regress/t/t_cover_property.py | 18 +++++++ test_regress/t/t_cover_property.v | 77 ++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100755 test_regress/t/t_cover_property.py create mode 100644 test_regress/t/t_cover_property.v diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index 05fb804fc..22f4112b7 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -61,6 +61,7 @@ private: AstNodeExpr* m_disablep = nullptr; // Last disable AstIf* m_disableSeqIfp = nullptr; // Used for handling disable iff in sequences AstPExpr* m_pexprp = nullptr; // Last AstPExpr + bool m_underCover = false; // True if the enclosing assertion is a cover // Other: V3UniqueNames m_cycleDlyNames{"__VcycleDly"}; // Cycle delay counter name generator V3UniqueNames m_consRepNames{"__VconsRep"}; // Consecutive repetition counter name generator @@ -1282,7 +1283,10 @@ private: // Don't iterate pexprp here -- it was already iterated when created // (in visit(AstSExpr*)), so delays and disable iff are already processed. } else if (nodep->isOverlapped()) { - nodep->replaceWith(new AstLogOr{flp, new AstLogNot{flp, lhsp}, rhsp}); + AstNodeExpr* const exprp + = m_underCover ? static_cast(new AstLogAnd{flp, lhsp, rhsp}) + : new AstLogOr{flp, new AstLogNot{flp, lhsp}, rhsp}; + nodep->replaceWith(exprp); } else { if (m_disablep) { lhsp = new AstAnd{flp, new AstNot{flp, m_disablep->cloneTreePure(false)}, lhsp}; @@ -1291,7 +1295,9 @@ private: AstPast* const pastp = new AstPast{flp, lhsp}; pastp->dtypeFrom(lhsp); pastp->sentreep(newSenTree(nodep)); - AstNodeExpr* const exprp = new AstOr{flp, new AstNot{flp, pastp}, rhsp}; + AstNodeExpr* const exprp + = m_underCover ? static_cast(new AstAnd{flp, pastp, rhsp}) + : new AstOr{flp, new AstNot{flp, pastp}, rhsp}; exprp->dtypeSetBit(); nodep->replaceWith(exprp); } @@ -1469,6 +1475,10 @@ private: nodep->propp(new AstSampled{nodep->fileline(), nodep->propp()->unlinkFrBack(), propDtp->dtypep()}); } + // cover counts non-vacuous matches only (IEEE 1800-2023 16.15.2), so an + // implication antecedent must hold; assert passes vacuously instead. + VL_RESTORER(m_underCover); + m_underCover = VN_IS(nodep->backp(), Cover); iterate(nodep->propp()); } void visit(AstPExpr* nodep) override { diff --git a/test_regress/t/t_cover_property.py b/test_regress/t/t_cover_property.py new file mode 100755 index 000000000..ef1f5c1ed --- /dev/null +++ b/test_regress/t/t_cover_property.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--coverage', '--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_cover_property.v b/test_regress/t/t_cover_property.v new file mode 100644 index 000000000..40089451c --- /dev/null +++ b/test_regress/t/t_cover_property.v @@ -0,0 +1,77 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + logic [63:0] crc = 64'h5aef0c8dd70a4497; + logic a, b; + int cyc = 0; + + int n_imp_no = 0; // cover property (a |=> b) -- non-overlapped implication + int n_imp_ov = 0; // cover property (a |-> b) -- overlapped implication + int n_seq = 0; // cover property (a ##1 b) -- identity with |=> + int n_seq0 = 0; // cover property (a ##0 b) -- identity with |-> + int n_bool = 0; // cover property (a) -- bare boolean baseline + int n_named = 0; // cover property (named pr) -- identity with |=> + + default clocking cb @(posedge clk); + endclocking + + assign a = crc[0]; + assign b = crc[5]; + + property pr; + a |=> b; + endproperty + + cp_imp_no : + cover property (a |=> b) n_imp_no++; + cp_imp_ov : + cover property (a |-> b) n_imp_ov++; + cp_seq : + cover property (a ##1 b) n_seq++; + cp_seq0 : + cover property (a ##0 b) n_seq0++; + cp_bool : + cover property (a) n_bool++; + cp_named : + cover property (pr) n_named++; + + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[1] ^ crc[0]}; + if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + final begin + // A cover of an implication counts only non-vacuous matches (IEEE + // 1800-2023 16.15.2): the antecedent must match. So it is identical to the + // corresponding sequence cover, not the vacuous implication value. + `checkd(n_imp_no, n_seq) + `checkd(n_imp_ov, n_seq0) + // A named-property cover lowers the same implication, so it also counts + // non-vacuously (regression guard for the property-inlining path). + `checkd(n_named, n_imp_no) + // Pinned Verilator counts; Questa golden cross-checked. + `checkd(n_imp_no, 28) // Questa: 28 + `checkd(n_imp_ov, 27) // Questa: 27 + `checkd(n_seq, 28) // Questa: 28 + `checkd(n_seq0, 27) // Questa: 27 + `checkd(n_bool, 55) // Questa: 54 + `checkd(n_named, 28) // Questa: 28 + end + +endmodule From 792008514b5d988dd4c782d304dc6b194573fdd7 Mon Sep 17 00:00:00 2001 From: Ryszard Rozak Date: Tue, 16 Jun 2026 18:19:00 +0200 Subject: [PATCH 45/89] Fix randomization of dynamic arrs of objects (#7790) --- src/V3Randomize.cpp | 22 ++++++++++ test_regress/t/t_constraint_global_cls_arr.py | 21 ++++++++++ test_regress/t/t_constraint_global_cls_arr.v | 37 +++++++++++++++++ .../t_constraint_global_cls_arr_2d_unsup.out | 6 +++ .../t/t_constraint_global_cls_arr_2d_unsup.py | 16 ++++++++ .../t/t_constraint_global_cls_arr_2d_unsup.v | 41 +++++++++++++++++++ 6 files changed, 143 insertions(+) create mode 100755 test_regress/t/t_constraint_global_cls_arr.py create mode 100644 test_regress/t/t_constraint_global_cls_arr.v create mode 100644 test_regress/t/t_constraint_global_cls_arr_2d_unsup.out create mode 100755 test_regress/t/t_constraint_global_cls_arr_2d_unsup.py create mode 100644 test_regress/t/t_constraint_global_cls_arr_2d_unsup.v diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 929bbf115..911660103 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -4569,6 +4569,16 @@ class RandomizeVisitor final : public VNVisitor { } } + static bool isDynArrOfClassTypeRecurse(const AstNodeDType* const dtypep) { + const AstNodeDType* const refp = dtypep->skipRefp(); + if (VN_IS(refp, DynArrayDType) || VN_IS(refp, QueueDType)) { + return isDynArrOfClassTypeRecurse(refp->subDTypep()); + } else if (VN_IS(refp, ClassRefDType)) { + return true; + } + return false; + } + // VISITORS void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); @@ -4801,6 +4811,18 @@ class RandomizeVisitor final : public VNVisitor { // Refresh array element tables after resize for (AstVar* const arrVarp : sizeArraysIt->second) { + // Array elements of class data type are passed to the solver as separate + // variables, so passing the original array variable is redundant, because it + // won't be referenced + if (isDynArrOfClassTypeRecurse(arrVarp->dtypep())) { + const uint32_t unpackedDims = arrVarp->dtypep()->dimensions(false).second; + if (unpackedDims > 1) { + arrVarp->v3warn( + E_UNSUPPORTED, + "Unsupported: Nested array element access in global constraint"); + } + continue; + } AstCMethodHard* const methodp = new AstCMethodHard{ fl, new AstVarRef{fl, genModp, genp, VAccess::READWRITE}, VCMethod::RANDOMIZER_WRITE_VAR}; diff --git a/test_regress/t/t_constraint_global_cls_arr.py b/test_regress/t/t_constraint_global_cls_arr.py new file mode 100755 index 000000000..8862c2c31 --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_global_cls_arr.v b/test_regress/t/t_constraint_global_cls_arr.v new file mode 100644 index 000000000..1f6efdd42 --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr.v @@ -0,0 +1,37 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +class Foo; + rand int abcd; +endclass + +class Bar; + rand Foo foo_arr[]; + + function new(); + foo_arr = new[12]; + foreach(foo_arr[i]) foo_arr[i] = new; + endfunction + + constraint c { + foo_arr.size() == 10; + foreach (foo_arr[i]) foo_arr[i].abcd < 8; + } +endclass + +module t; + Bar bar; + initial begin + bar = new(); + bar.randomize(); + if (bar.foo_arr.size() != 10) $stop; + foreach (bar.foo_arr[i]) begin + if (bar.foo_arr[i] >= 8) $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_global_cls_arr_2d_unsup.out b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.out new file mode 100644 index 000000000..9d049fc2e --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_constraint_global_cls_arr_2d_unsup.v:13:12: Unsupported: Nested array element access in global constraint + : ... note: In instance 't' + 13 | rand Foo foo_arr[$][]; + | ^~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_constraint_global_cls_arr_2d_unsup.py b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.py new file mode 100755 index 000000000..4cebd5d8e --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v new file mode 100644 index 000000000..b83e1362d --- /dev/null +++ b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v @@ -0,0 +1,41 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +class Foo; + rand int abcd; + constraint c { abcd >= 2; } +endclass + +class Bar; + rand Foo foo_arr[$][]; + + function new(); + for (int i = 0; i < 3; i++) foo_arr[i] = new[5]; + foreach(foo_arr[i, j]) foo_arr[i][j] = new; + endfunction + + constraint c { + foo_arr.size() == 10; + foreach (foo_arr[i, j]) foo_arr[i][j].abcd < 8; + } +endclass + +module t; + Bar bar; + initial begin + bar = new(); + void'(bar.randomize()); + if (bar.foo_arr.size() != 10) $stop; + foreach (bar.foo_arr[i, j]) begin + if (bar.foo_arr[i][j].abcd < 2 || bar.foo_arr[i][j].abcd >= 8) $stop; + end + for (int i = 3; i < 10; i++) begin + if (bar.foo_arr[i].size() != 0) $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From a534a1d1bca737f3b0397b931650cea28648b61b Mon Sep 17 00:00:00 2001 From: em2machine <92717390+em2machine@users.noreply.github.com> Date: Tue, 16 Jun 2026 20:03:28 +0200 Subject: [PATCH 46/89] Fix parameter pollution when using class parameters (#7711) (#7763) Fixes #7711. --- src/V3Param.cpp | 14 +++++++ .../t/t_interface_param_class_bits.py | 18 ++++++++ test_regress/t/t_interface_param_class_bits.v | 42 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100755 test_regress/t/t_interface_param_class_bits.py create mode 100644 test_regress/t/t_interface_param_class_bits.v diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 063c55d3a..0cf109211 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1348,6 +1348,17 @@ class ParamProcessor final { } } + // True if a $bits/$size type query in nodep's parameters reads another type parameter. + static bool defaultParamsHaveTypeQueryOnParamType(const AstClassRefDType* nodep) { + bool found = false; + nodep->foreach([&](const AstAttrOf* attrp) { + if (found || !attrp->attrType().isTypeQuery()) return; + const AstRefDType* const refp = VN_CAST(attrp->fromp(), RefDType); + if (refp && VN_IS(refp->refDTypep(), ParamTypeDType)) found = true; + }); + return found; + } + // Check if exprp's class matches origp's class after deparameterization. // Handles both the simple case (user4p link from defaultsResolved) and the // nested case where the default's inner class has non-default sub-parameters @@ -1362,6 +1373,9 @@ class ParamProcessor final { const AstNodeModule* const defaultClonep = VN_CAST(origClassRefp->classp()->user4p(), Class); if (defaultClonep && defaultClonep == exprClassRefp->classp()) return true; + // Skip the comparison when the default's $bits/$size reads another type parameter, as + // deparameterizing it below would resolve that shared type at the wrong width (#7711). + if (defaultParamsHaveTypeQueryOnParamType(origClassRefp)) return false; // Slow path: deparameterize the default type and compare the result. // Different templates can never match; use origName() because exprp's // class may already be a specialization (clone) of the template. diff --git a/test_regress/t/t_interface_param_class_bits.py b/test_regress/t/t_interface_param_class_bits.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_interface_param_class_bits.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_param_class_bits.v b/test_regress/t/t_interface_param_class_bits.v new file mode 100644 index 000000000..8994a3152 --- /dev/null +++ b/test_regress/t/t_interface_param_class_bits.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// A class type parameter whose default reads $bits of a sibling type +// parameter must not freeze that sibling at its default width when other +// parameters of the interface are overridden (#7711). +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +class cls #(int width); +endclass + +interface ifc + #(parameter int width = 8, + parameter type dtype = logic[width-1:0], + parameter type cparam = cls#($bits(dtype))); + dtype data; +endinterface + +module t; + // width is overridden, dtype keeps its default logic[width-1:0], and the + // class type parameter is overridden. dtype must follow width (1 bit). + ifc #(.width(1), .cparam(cls#(1))) inst1(); + // Same interface left at its default width (8 bits) must still work. + ifc inst8(); + + always_comb inst1.data = 1'b0; + + initial begin + if ($bits(inst1.data) != 1) begin + $write("%%Error: $bits(inst1.data)=%0d exp=1\n", $bits(inst1.data)); + $stop; + end + if ($bits(inst8.data) != 8) begin + $write("%%Error: $bits(inst8.data)=%0d exp=8\n", $bits(inst8.data)); + $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From b581f73843dec967cb695020bd8169d914d6838a Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Wed, 17 Jun 2026 13:17:39 +0200 Subject: [PATCH 47/89] Support $assertcontrol control_type from lock to kill (#7788) --- include/verilated.cpp | 46 +++++-- include/verilated.h | 11 ++ src/V3Assert.cpp | 151 +++++++++------------- test_regress/t/t_assert_ctl_arg_unsup.out | 4 - test_regress/t/t_assert_ctl_lock.py | 18 +++ test_regress/t/t_assert_ctl_lock.v | 66 ++++++++++ test_regress/t/t_assert_ctl_lock_noinl.py | 19 +++ test_regress/t/t_assert_ctl_unsup.out | 125 +++++++----------- test_regress/t/t_assert_ctl_unsup.v | 14 -- 9 files changed, 256 insertions(+), 198 deletions(-) create mode 100755 test_regress/t/t_assert_ctl_lock.py create mode 100644 test_regress/t/t_assert_ctl_lock.v create mode 100755 test_regress/t/t_assert_ctl_lock_noinl.py diff --git a/include/verilated.cpp b/include/verilated.cpp index a7d78f3bd..fcfbab58a 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -3050,24 +3050,44 @@ bool VerilatedContext::assertOnGet(VerilatedAssertType_t type, // Check if directive type bit is enabled in corresponding assertion type bits. return m_s.m_assertOn & (directive << (typeMaskPosition * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH)); } +uint32_t VerilatedContext::assertOnMask(VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_PURE { + // Place the directive bits at each selected assertion type's 3-bit group. + uint32_t mask = 0; + for (int i = 0; i < std::numeric_limits::digits; ++i) { + if (VL_BITISSET_I(types, i)) mask |= directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH); + } + return mask; +} void VerilatedContext::assertOnSet(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { - // For each assertion type, set directive bits. - - // Iterate through all positions of assertion type bits. If bit for this assertion type is set, - // set directive type bits mask at this group index. - for (int i = 0; i < std::numeric_limits::digits; ++i) { - if (VL_BITISSET_I(types, i)) - m_s.m_assertOn |= directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH); - } + m_s.m_assertOn |= assertOnMask(types, directives); } void VerilatedContext::assertOnClear(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { - // Iterate through all positions of assertion type bits. If bit for this assertion type is set, - // clear directive type bits mask at this group index. - for (int i = 0; i < std::numeric_limits::digits; ++i) { - if (VL_BITISSET_I(types, i)) - m_s.m_assertOn &= ~(directives << (i * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH)); + m_s.m_assertOn &= ~assertOnMask(types, directives); +} +void VerilatedContext::assertCtl(uint32_t controlType, VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_MT_SAFE { + // IEEE 1800-2023 Table 20-5 control_type. Lock freezes the On/Off state of the + // selected bits until Unlock; On/Off/Kill leave locked bits unchanged. + const uint32_t mask = assertOnMask(types, directives); + switch (controlType) { + case 1: // Lock + m_s.m_assertLock |= mask; + break; + case 2: // Unlock + m_s.m_assertLock &= ~mask; + break; + case 3: // On + m_s.m_assertOn |= mask & ~m_s.m_assertLock; + break; + case 4: // Off + case 5: // Kill + m_s.m_assertOn &= ~(mask & ~m_s.m_assertLock); + break; + default: // 6..11 (Pass/Fail/Vacuous action control) are not modeled; ignore + break; } } void VerilatedContext::calcUnusedSigs(bool flag) VL_MT_SAFE { diff --git a/include/verilated.h b/include/verilated.h index 707ede5ce..f8075567e 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -356,6 +356,9 @@ private: static constexpr size_t ASSERT_ON_WIDTH = ASSERT_DIRECTIVE_TYPE_MASK_WIDTH * std::numeric_limits::digits + 1; + // Build the m_assertOn/m_assertLock bit mask for the given assertion x directive types. + static uint32_t assertOnMask(VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_PURE; protected: // TYPES @@ -375,6 +378,9 @@ protected: // for each VerilatedAssertType we store // 3-bits, one for each directive type. Last // bit guards internal directive types. + std::atomic m_assertLock{0}; // Locked assertion bits (IEEE 1800-2023 20.11 + // Lock/Unlock); same layout as m_assertOn. While + // a bit is locked, On/Off/Kill leave it unchanged. bool m_calcUnusedSigs = false; // Waves file on, need all signals calculated bool m_fatalOnError = true; // Fatal on $stop/non-fatal error bool m_fatalOnVpiError = true; // Fatal on vpi error/unsupported @@ -484,6 +490,11 @@ public: /// Clear enabled status for given assertion types void assertOnClear(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE; + /// Apply a $assertcontrol control_type (IEEE 1800-2023 Table 20-5) to the given + /// assertion and directive types: 1=Lock, 2=Unlock, 3=On, 4=Off, 5=Kill. Locked + /// bits are left unchanged by On/Off/Kill. Other control_type values are ignored. + void assertCtl(uint32_t controlType, VerilatedAssertType_t types, + VerilatedAssertDirectiveType_t directives) VL_MT_SAFE; /// Return if calculating of unused signals (for traces) bool calcUnusedSigs() const VL_MT_SAFE { return m_s.m_calcUnusedSigs; } /// Enable calculation of unused signals (for traces) diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 47c1874f3..18df21153 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -280,36 +280,12 @@ class AssertVisitor final : public VNVisitor { + cvtToStr(nodep->fileline()->lineno()) + ": %m" + ((message != "") ? ": " : "") + message + "\n"); } - static bool resolveAssertType(AstAssertCtl* nodep) { - if (!nodep->assertTypesp()) { - nodep->ctlAssertTypes(VAssertType{ALL_ASSERT_TYPES}); - return true; - } - if (const AstConst* const assertTypesp = VN_CAST(nodep->assertTypesp(), Const)) { - nodep->ctlAssertTypes(VAssertType{assertTypesp->toSInt()}); - return true; - } - return false; - } - static bool resolveControlType(AstAssertCtl* nodep) { - if (const AstConst* const constp = VN_CAST(nodep->controlTypep(), Const)) { - nodep->ctlType(constp->toSInt()); - return true; - } - return false; - } - static bool resolveDirectiveType(AstAssertCtl* nodep) { - if (!nodep->directiveTypesp()) { - nodep->ctlDirectiveTypes(VAssertDirectiveType::ASSERT | VAssertDirectiveType::ASSUME - | VAssertDirectiveType::COVER); - return true; - } - if (const AstConst* const directiveTypesp = VN_CAST(nodep->directiveTypesp(), Const)) { - nodep->ctlDirectiveTypes(VAssertDirectiveType{directiveTypesp->toSInt()}); - return true; - } - return false; - } + // Default assertion_type and directive_type when the argument is omitted + // (IEEE 1800-2023 20.11): assertion_type defaults to all types, directive_type + // to Assert|Cover|Assume. + static constexpr uint8_t DEFAULT_DIRECTIVE_TYPES = VAssertDirectiveType::ASSERT + | VAssertDirectiveType::COVER + | VAssertDirectiveType::ASSUME; void replaceDisplay(AstDisplay* nodep, const string& prefix) { nodep->fmtp()->text( assertDisplayMessage(nodep, prefix, nodep->fmtp()->text(), nodep->displayType())); @@ -1002,71 +978,66 @@ class AssertVisitor final : public VNVisitor { } void visit(AstAssertCtl* nodep) override { iterateChildren(nodep); - - if (!resolveAssertType(nodep)) { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: non-constant assert assertion-type expression"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; - } - if (nodep->ctlAssertTypes() != ALL_ASSERT_TYPES - && nodep->ctlAssertTypes().containsAny(VAssertType::EXPECT | VAssertType::UNIQUE - | VAssertType::UNIQUE0 - | VAssertType::PRIORITY)) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: assert control assertion_type"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; - } - if (!resolveControlType(nodep)) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: non-const assert control type expression"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; - } - if (!resolveDirectiveType(nodep)) { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: non-const assert directive type expression"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; - } - FileLine* const fl = nodep->fileline(); - switch (nodep->ctlType()) { - case VAssertCtlType::ON: - UINFO(9, "Generating assertctl for a module: " << m_modp); - nodep->replaceWith( - new AstCStmt{fl, "vlSymsp->_vm_contextp__->assertOnSet("s - + std::to_string(nodep->ctlAssertTypes()) + ", "s - + std::to_string(nodep->ctlDirectiveTypes()) + ");\n"s}); - break; - case VAssertCtlType::OFF: - case VAssertCtlType::KILL: { - UINFO(9, "Generating assertctl for a module: " << m_modp); - nodep->replaceWith( - new AstCStmt{fl, "vlSymsp->_vm_contextp__->assertOnClear("s - + std::to_string(nodep->ctlAssertTypes()) + " ,"s - + std::to_string(nodep->ctlDirectiveTypes()) + ");\n"s}); - break; + + // control_type, assertion_type and directive_type are integer expressions + // (IEEE 1800-2023 20.11) and may be non-constant; they are evaluated at runtime + // by VerilatedContext::assertCtl. The levels and scope/assertion-list arguments + // are not modeled -- control applies to the whole context. + // When control_type is a compile-time constant, reject the not-yet-modeled + // action-control codes (Table 20-5 values 6..11) and out-of-range values with a + // clear error rather than emitting a runtime no-op. + if (const AstConst* const controlp = VN_CAST(nodep->controlTypep(), Const)) { + const int32_t control = controlp->toSInt(); + if (control < VAssertCtlType::LOCK || control > VAssertCtlType::VACUOUS_OFF) { + nodep->unlinkFrBack(); + nodep->v3warn(EC_ERROR, "Bad $assertcontrol control_type '" + << control << "' (IEEE 1800-2023 Table 20-5)"); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + if (control >= VAssertCtlType::PASS_ON) { + nodep->unlinkFrBack(); + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: $assertcontrol control_type '" << control << "'"); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } } - case VAssertCtlType::LOCK: - case VAssertCtlType::UNLOCK: - case VAssertCtlType::PASS_ON: - case VAssertCtlType::PASS_OFF: - case VAssertCtlType::FAIL_ON: - case VAssertCtlType::FAIL_OFF: - case VAssertCtlType::NONVACUOUS_ON: - case VAssertCtlType::VACUOUS_OFF: { - nodep->unlinkFrBack(); - nodep->v3warn(E_UNSUPPORTED, "Unsupported: $assertcontrol control_type '" << cvtToStr( - static_cast(nodep->ctlType())) << "'"); - break; + + // When assertion_type is a compile-time constant, reject values that cannot be + // filtered at runtime because unique/priority violations use bare assertOn() rather + // than a per-type assertOnGet() call (IEEE 1800-2023 Table 20-6). + if (nodep->assertTypesp()) { + if (const AstConst* const typecp = VN_CAST(nodep->assertTypesp(), Const)) { + if (typecp->toUInt() + & (VAssertType::EXPECT | VAssertType::UNIQUE | VAssertType::UNIQUE0 + | VAssertType::PRIORITY)) { + nodep->unlinkFrBack(); + nodep->v3warn(E_UNSUPPORTED, "Unsupported: assert control assertion_type"); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } } - default: { - nodep->unlinkFrBack(); - nodep->v3warn(EC_ERROR, "Bad $assertcontrol control_type '" - << cvtToStr(static_cast(nodep->ctlType())) - << "' (IEEE 1800-2023 Table 20-5)"); + + UINFO(9, "Generating assertctl in module: " << m_modp); + AstCStmt* const callp = new AstCStmt{fl, "vlSymsp->_vm_contextp__->assertCtl("}; + callp->add(nodep->controlTypep()->unlinkFrBack()); + callp->add(", "); + if (AstNodeExpr* const typesp = nodep->assertTypesp()) { + callp->add(typesp->unlinkFrBack()); + } else { + callp->add(std::to_string(ALL_ASSERT_TYPES)); } + callp->add(", "); + if (AstNodeExpr* const directivesp = nodep->directiveTypesp()) { + callp->add(directivesp->unlinkFrBack()); + } else { + callp->add(std::to_string(DEFAULT_DIRECTIVE_TYPES)); } + callp->add(");\n"); + nodep->replaceWith(callp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void visit(AstAssertIntrinsic* nodep) override { // diff --git a/test_regress/t/t_assert_ctl_arg_unsup.out b/test_regress/t/t_assert_ctl_arg_unsup.out index c9bad92c3..da5e1145d 100644 --- a/test_regress/t/t_assert_ctl_arg_unsup.out +++ b/test_regress/t/t_assert_ctl_arg_unsup.out @@ -1,18 +1,14 @@ %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:15:5: Unsupported: assert control assertion_type - : ... note: In instance 't' 15 | $assertcontrol(OFF, EXPECT); | ^~~~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:16:5: Unsupported: assert control assertion_type - : ... note: In instance 't' 16 | $assertcontrol(OFF, UNIQUE); | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:17:5: Unsupported: assert control assertion_type - : ... note: In instance 't' 17 | $assertcontrol(OFF, UNIQUE0); | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:18:5: Unsupported: assert control assertion_type - : ... note: In instance 't' 18 | $assertcontrol(OFF, PRIORITY); | ^~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assert_ctl_lock.py b/test_regress/t/t_assert_ctl_lock.py new file mode 100755 index 000000000..fdd1c0d4d --- /dev/null +++ b/test_regress/t/t_assert_ctl_lock.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary --assert"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_ctl_lock.v b/test_regress/t/t_assert_ctl_lock.v new file mode 100644 index 000000000..cc9ce8411 --- /dev/null +++ b/test_regress/t/t_assert_ctl_lock.v @@ -0,0 +1,66 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +`ifdef verilator + `define no_optimize(v) $c(v) +`else + `define no_optimize(v) (v) +`endif + +module t ( /*AUTOARG*/); + logic clk = 0; + int imm_fails = 0, conc_fails = 0; + logic a = 1'b1; // antecedent always true + logic b = 1'b0; // consequent always false -> assertion always violates + + always #5 clk = ~clk; + + // Immediate assertion: assertion_type SIMPLE_IMMEDIATE (mask value 2). + always @(posedge clk) + ia : + assert (b) + else imm_fails = imm_fails + 1; + + // Concurrent assertion: assertion_type CONCURRENT (mask value 1). + ca : + assert property (@(posedge clk) a |-> b) + else conc_fails = conc_fails + 1; + + int ctl; + + // posedge clk at t = 5, 15, 25, 35, 45, 55, 65, 75, 85, 95. + initial begin + // Phase A (t=0..): everything on. edge@5 -> imm+1, conc+1. + #6; // t=6 + // Phase B: lock the on state, then $assertoff must be ignored while locked. + $assertcontrol(1); // Lock, all types + $assertoff; // ignored -> edges @15,@25 still fire both + #24; // t=30 + // Phase C: unlock, then turn off only SIMPLE_IMMEDIATE via assertion_type filter. + $assertcontrol(2); // Unlock + $assertcontrol(4, 2); // Off, assertion_type = SIMPLE_IMMEDIATE only + #20; // t=50: edges @35,@45 -> imm off, conc still on + // Phase D: non-constant control_type and assertion_type re-enable the immediate. + ctl = `no_optimize(3); // On + $assertcontrol(ctl, `no_optimize(2)); // non-const On, SIMPLE_IMMEDIATE + #20; // t=70: edges @55,@65 -> both on + // Phase E: off everything, then a non-const action-control code (no runtime effect). + $assertcontrol(4); // Off all + ctl = `no_optimize(7); // Pass_Off (IEEE 1800-2023 Table 20-5 action control) + $assertcontrol(ctl); // non-const action-control: evaluated at runtime, ignored + #30; // t=100: edges @75,@85,@95 -> none + $finish; + end + + final begin + // Concrete counts cross-checked against Questa 2022.3: imm_fails=5 conc_fails=7. + if (imm_fails != 5 || conc_fails != 7) begin + $display("%%Error: imm_fails=%0d (exp 5) conc_fails=%0d (exp 7)", imm_fails, conc_fails); + $stop; + end + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_assert_ctl_lock_noinl.py b/test_regress/t/t_assert_ctl_lock_noinl.py new file mode 100755 index 000000000..83440d018 --- /dev/null +++ b/test_regress/t/t_assert_ctl_lock_noinl.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = "t_assert_ctl_lock.v" + +test.compile(verilator_flags2=["--binary --assert --fno-inline"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_ctl_unsup.out b/test_regress/t/t_assert_ctl_unsup.out index b00a2a58f..d0aaa3d7e 100644 --- a/test_regress/t/t_assert_ctl_unsup.out +++ b/test_regress/t/t_assert_ctl_unsup.out @@ -1,103 +1,74 @@ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:26:5: Unsupported: non-constant assert assertion-type expression - : ... note: In instance 't.unsupported_ctl_type' - 26 | $assertcontrol(Lock, a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:23:5: Unsupported: $assertcontrol control_type '6' + 23 | $assertcontrol(PassOn); | ^~~~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:28:5: Unsupported: $assertcontrol control_type '2' - 28 | $assertcontrol(Unlock); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: $assertcontrol control_type '6' - 30 | $assertcontrol(PassOn); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:31:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 31 | $assertpasson; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:24:5: Unsupported: $assertcontrol control_type '6' + 24 | $assertpasson; | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:32:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 32 | $assertpasson(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:25:5: Unsupported: $assertcontrol control_type '6' + 25 | $assertpasson(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:33:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 33 | $assertpasson(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:26:5: Unsupported: $assertcontrol control_type '6' + 26 | $assertpasson(a, t); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: $assertcontrol control_type '7' - 35 | $assertcontrol(PassOff); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:28:5: Unsupported: $assertcontrol control_type '7' + 28 | $assertcontrol(PassOff); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:36:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 36 | $assertpassoff; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:29:5: Unsupported: $assertcontrol control_type '7' + 29 | $assertpassoff; | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:37:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 37 | $assertpassoff(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: $assertcontrol control_type '7' + 30 | $assertpassoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:38:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 38 | $assertpassoff(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:31:5: Unsupported: $assertcontrol control_type '7' + 31 | $assertpassoff(a, t); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: $assertcontrol control_type '8' - 40 | $assertcontrol(FailOn); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:33:5: Unsupported: $assertcontrol control_type '8' + 33 | $assertcontrol(FailOn); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:41:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 41 | $assertfailon; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:34:5: Unsupported: $assertcontrol control_type '8' + 34 | $assertfailon; | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:42:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 42 | $assertfailon(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: $assertcontrol control_type '8' + 35 | $assertfailon(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:43:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 43 | $assertfailon(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:36:5: Unsupported: $assertcontrol control_type '8' + 36 | $assertfailon(a, t); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: $assertcontrol control_type '9' - 45 | $assertcontrol(FailOff); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:38:5: Unsupported: $assertcontrol control_type '9' + 38 | $assertcontrol(FailOff); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:46:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 46 | $assertfailoff; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:39:5: Unsupported: $assertcontrol control_type '9' + 39 | $assertfailoff; | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:47:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 47 | $assertfailoff(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: $assertcontrol control_type '9' + 40 | $assertfailoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:48:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 48 | $assertfailoff(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:41:5: Unsupported: $assertcontrol control_type '9' + 41 | $assertfailoff(a, t); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: $assertcontrol control_type '10' - 50 | $assertcontrol(NonvacuousOn); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:43:5: Unsupported: $assertcontrol control_type '10' + 43 | $assertcontrol(NonvacuousOn); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:51:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 51 | $assertnonvacuouson; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:44:5: Unsupported: $assertcontrol control_type '10' + 44 | $assertnonvacuouson; | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:52:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 52 | $assertnonvacuouson(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: $assertcontrol control_type '10' + 45 | $assertnonvacuouson(a); | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:53:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 53 | $assertnonvacuouson(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:46:5: Unsupported: $assertcontrol control_type '10' + 46 | $assertnonvacuouson(a, t); | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: $assertcontrol control_type '11' - 55 | $assertcontrol(VacuousOff); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:48:5: Unsupported: $assertcontrol control_type '11' + 48 | $assertcontrol(VacuousOff); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:56:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 56 | $assertvacuousoff; +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:49:5: Unsupported: $assertcontrol control_type '11' + 49 | $assertvacuousoff; | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:57:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 57 | $assertvacuousoff(a); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: $assertcontrol control_type '11' + 50 | $assertvacuousoff(a); | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:58:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 58 | $assertvacuousoff(a, t); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:51:5: Unsupported: $assertcontrol control_type '11' + 51 | $assertvacuousoff(a, t); | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:65:5: Unsupported: non-const assert control type expression - : ... note: In instance 't.unsupported_ctl_type_expr' - 65 | $assertcontrol(ctl_type); - | ^~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assert_ctl_unsup.v b/test_regress/t/t_assert_ctl_unsup.v index 90d63e86c..027b4e824 100644 --- a/test_regress/t/t_assert_ctl_unsup.v +++ b/test_regress/t/t_assert_ctl_unsup.v @@ -8,25 +8,18 @@ module t ( input logic clk ); unsupported_ctl_type unsupported_ctl_type (clk ? 1 : 2); - unsupported_ctl_type_expr unsupported_ctl_type_expr (); endmodule module unsupported_ctl_type ( input int a ); initial begin - let Lock = 1; - let Unlock = 2; let PassOn = 6; let PassOff = 7; let FailOn = 8; let FailOff = 9; let NonvacuousOn = 10; let VacuousOff = 11; - $assertcontrol(Lock, a); - - $assertcontrol(Unlock); - $assertcontrol(PassOn); $assertpasson; $assertpasson(a); @@ -58,10 +51,3 @@ module unsupported_ctl_type ( $assertvacuousoff(a, t); end endmodule - -module unsupported_ctl_type_expr; - int ctl_type = 1; - initial begin - $assertcontrol(ctl_type); - end -endmodule From f11c4084d11d8eb5a160e86189f72f8229d20ccd Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 16 Jun 2026 14:55:24 +0100 Subject: [PATCH 48/89] Internals: Minor cleanup of case statement lowering - Factor out match mask construction - Rename V3Number method to reflect behaviour --- src/V3Case.cpp | 53 +++++++++++++++++++++++++---------------------- src/V3Const.cpp | 2 +- src/V3Number.cpp | 2 +- src/V3Number.h | 2 +- src/V3Unknown.cpp | 2 +- 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 65386df2a..80dffba68 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -174,6 +174,21 @@ class CaseVisitor final : public VNVisitor { return constp->num().isFourState(); } + // Returns the matching mask and bits for a case item constant condition + // TODO: with 'neverItem' checked, this is alway the same for all types of cases + // 4-state will have to fix this up + static std::pair matchPattern(const AstCase* /*casep*/, + const AstConst* condp) { + // As one to encourage NRVO/move + std::pair pairMaskBits{ + std::piecewise_construct, // + std::forward_as_tuple(condp->fileline(), condp->width(), 0), + std::forward_as_tuple(condp->fileline(), condp->width(), 0)}; + pairMaskBits.first.opBitsNonXZ(condp->num()); + pairMaskBits.second.opBitsOne(condp->num()); + return pairMaskBits; + } + // Determine whether we should check case items are complete // Returns enum's dtype if should check, nullptr if shouldn't static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { @@ -191,18 +206,13 @@ class CaseVisitor final : public VNVisitor { bool checkExhaustiveEnum(const AstCase* const nodep, const AstEnumDType* const enump) { const uint32_t numCases = 1UL << nodep->exprp()->width(); for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) { - AstConst* const econstp = VN_AS(eip->valuep(), Const); - V3Number nummask{eip, econstp->width()}; - nummask.opBitsNonX(econstp->num()); - V3Number numval{eip, econstp->width()}; - numval.opBitsOne(econstp->num()); - - const uint32_t mask = nummask.toUInt(); - const uint32_t val = numval.toUInt(); + const auto& match = matchPattern(nodep, VN_AS(eip->valuep(), Const)); + const uint32_t mask = match.first.toUInt(); + const uint32_t bits = match.second.toUInt(); // Check all cases to see if they cover this enum value/pattern for (uint32_t i = 0; i < numCases; ++i) { - if ((i & mask) != val) continue; // This case is not for this enum value + if ((i & mask) != bits) continue; // This case is not for this enum value if (m_caseDetails.records[i].itemp) continue; // Covered case // Warn unless unique0 case which allows no-match if (!nodep->unique0Pragma()) { @@ -278,19 +288,15 @@ class CaseVisitor final : public VNVisitor { // Some items can never match due to 2-state simulation if (neverItem(nodep, iconstp)) continue; - V3Number nummask{itemp, iconstp->width()}; - nummask.opBitsNonX(iconstp->num()); - V3Number numval{itemp, iconstp->width()}; - numval.opBitsOne(iconstp->num()); - - const uint32_t mask = nummask.toUInt(); - const uint32_t val = numval.toUInt(); + const auto& match = matchPattern(nodep, iconstp); + const uint32_t mask = match.first.toUInt(); + const uint32_t bits = match.second.toUInt(); bool foundNewCase = false; const AstConst* firstOverlapConstp = nullptr; uint32_t firstOverlapValue = 0; for (uint32_t i = 0; i < numValues; ++i) { - if ((i & mask) != val) continue; + if ((i & mask) != bits) continue; CaseRecord& caseRecord = m_caseDetails.records[i]; @@ -498,17 +504,14 @@ class CaseVisitor final : public VNVisitor { if (itemConstp->num().isFourState() && (nodep->casex() || nodep->casez() || nodep->caseInside())) { // Wildcard match, make 'caseExpr' & 'mask' == 'itemExpr' & 'mask' - V3Number numMask{itemp, itemConstp->width()}; - numMask.opBitsNonX(itemConstp->num()); - V3Number numOne{itemp, itemConstp->width()}; - numOne.opBitsOne(itemConstp->num()); - V3Number numRhs{itemp, itemConstp->width()}; - numRhs.opAnd(numOne, numMask); + const auto& match = matchPattern(nodep, itemConstp); + const V3Number& matchMask = match.first; + const V3Number& matchBits = match.second; VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemConstp); return AstEq::newTyped( flp, // - new AstConst{flp, numRhs}, - new AstAnd{flp, caseExprp, new AstConst{flp, numMask}}); + new AstConst{flp, matchBits}, + new AstAnd{flp, caseExprp, new AstConst{flp, matchMask}}); } } diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 2ff6a0a80..0098f1d94 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -286,7 +286,7 @@ class ConstBitOpTreeVisitor final : public VNVisitorConst { // Get the mask that selects the bits that are relevant in this term V3Number maskNum{srcp, m_width, 0}; - maskNum.opBitsNonX(m_bitPolarity); // 'x' -> 0, 0->1, 1->1 + maskNum.opBitsNonXZ(m_bitPolarity); // 'x' -> 0, 0->1, 1->1 const uint64_t maskVal = maskNum.toUQuad(); UASSERT_OBJ(maskVal != 0, m_refp, "Should have been recognized as having const 0 result"); diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 8adf7ce78..58bb2740b 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -1310,7 +1310,7 @@ uint32_t V3Number::mostSetBitP1() const { } //====================================================================== -V3Number& V3Number::opBitsNonX(const V3Number& lhs) { // 0/1->1, X/Z->0 +V3Number& V3Number::opBitsNonXZ(const V3Number& lhs) { // 0/1->1, X/Z->0 // Correct number of zero bits/width matters // op i, L(lhs) bit return NUM_ASSERT_OP_ARGS1(lhs); diff --git a/src/V3Number.h b/src/V3Number.h index 771f5edd4..df726e496 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -741,7 +741,7 @@ public: // MATH // "this" is the output, as we need the output width before some computations - V3Number& opBitsNonX(const V3Number& lhs); // 0/1->1, X/Z->0 + V3Number& opBitsNonXZ(const V3Number& lhs); // 0/1->1, X/Z->0 V3Number& opBitsOne(const V3Number& lhs); // 1->1, 0/X/Z->0 V3Number& opBitsXZ(const V3Number& lhs); // 0/1->0, X/Z->1 V3Number& opBitsZ(const V3Number& lhs); // Z->1, 0/1/X->0 diff --git a/src/V3Unknown.cpp b/src/V3Unknown.cpp index d0a4aff84..57e12e478 100644 --- a/src/V3Unknown.cpp +++ b/src/V3Unknown.cpp @@ -264,7 +264,7 @@ class UnknownVisitor final : public VNVisitor { } else { // X or Z's become mask, ala case statements. V3Number nummask{rhsp, rhsp->width()}; - nummask.opBitsNonX(VN_AS(rhsp, Const)->num()); + nummask.opBitsNonXZ(VN_AS(rhsp, Const)->num()); V3Number numval{rhsp, rhsp->width()}; numval.opBitsOne(VN_AS(rhsp, Const)->num()); AstNodeExpr* const and1p = new AstAnd{nodep->fileline(), lhsp, From 4c5e1048258fc87cf4fdffd13a78bfa8de9991b9 Mon Sep 17 00:00:00 2001 From: Tracy Narine <76846523+tmnarine@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:12:25 -0400 Subject: [PATCH 49/89] Internals: Remove bad cmake file reference (#7794) --- src/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba8f6a7b7..469456b45 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -201,7 +201,6 @@ set(HEADERS V3WidthCommit.h V3WidthRemove.h VlcBucket.h - VlcCovergroup.h VlcOptions.h VlcPoint.h VlcSource.h From 26c85d449587e11debe8d802ccc0497de3d49975 Mon Sep 17 00:00:00 2001 From: Nick Brereton <85175726+nbstrike@users.noreply.github.com> Date: Wed, 17 Jun 2026 17:56:48 -0400 Subject: [PATCH 50/89] Fix `$bits` on unpacked structs (#4521) (#7796) Fixes #4521. --- src/V3LinkDot.cpp | 50 ++++++++++++++++++++++- src/V3Width.cpp | 3 +- test_regress/t/t_stream_unpacked_struct.v | 30 ++++++++++++++ test_regress/t/t_struct_type_bad.out | 7 ++++ test_regress/t/t_struct_type_bad.v | 6 +++ 5 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 1ada078ba..a6b038396 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1993,6 +1993,17 @@ class LinkDotFindVisitor final : public VNVisitor { // No need to insert, only the real typedef matters, but need to track for errors nodep->user1p(m_curSymp); } + void visit(AstNodeUOrStructDType* nodep) override { // FindVisitor:: + UASSERT_OBJ(m_curSymp, nodep, "Struct/union dtype not under module/package/$unit"); + VL_RESTORER(m_curSymp); + m_curSymp = m_statep->insertBlock(m_curSymp, "__Vdtype" + cvtToStr(nodep->uniqueNum()), + nodep, m_classOrPackagep); + iterateChildren(nodep); + } + void visit(AstMemberDType* nodep) override { // FindVisitor:: + iterateChildren(nodep); + m_statep->insertSym(m_curSymp, nodep->name(), nodep, m_classOrPackagep); + } void visit(AstParamTypeDType* nodep) override { // FindVisitor:: UASSERT_OBJ(m_curSymp, nodep, "Parameter type not under module/package/$unit"); @@ -3511,6 +3522,20 @@ class LinkDotResolveVisitor final : public VNVisitor { << declp->warnContextSecondary()); } } + void checkMemberDeclOrder(AstNode* nodep, AstMemberDType* declp) { + const uint32_t declTokenNum = declp->fileline()->tokenNum(); + if (nodep->fileline()->tokenNum() < declTokenNum) { + UINFO(1, "Related node " << nodep->fileline()->tokenNum() << " " << nodep); + UINFO(1, "Related decl " << declTokenNum << " " << declp); + nodep->v3error("Reference to " + << nodep->prettyNameQ() << " before declaration (IEEE 1800-2023 6.18)\n" + << nodep->warnMore() + << "... Suggest move the declaration before the reference\n" + << nodep->warnContextPrimary() << '\n' + << declp->warnOther() << "... Location of original declaration\n" + << declp->warnContextSecondary()); + } + } void replaceWithCheckBreak(AstNode* oldp, AstNodeDType* newp) { // Flag now to avoid V3Broken throwing an internal error @@ -3536,6 +3561,15 @@ class LinkDotResolveVisitor final : public VNVisitor { if (nodep && nodep->isParam()) nodep->usedParam(true); } + static VSymEnt* findIdFallbackSkipMemberDType(VSymEnt* lookp, const string& name) { + while (lookp) { + VSymEnt* const foundp = lookp->findIdFlat(name); + if (foundp && !VN_IS(foundp->nodep(), MemberDType)) return foundp; + lookp = lookp->fallbackp(); + } + return nullptr; + } + void symIterateChildren(AstNode* nodep, VSymEnt* symp) { // Iterate children, changing to given context, with restore to old context VL_RESTORER(m_ds); @@ -4532,6 +4566,16 @@ class LinkDotResolveVisitor final : public VNVisitor { } else { (void)defp; // Prevent unused variable warning } + } else if (AstMemberDType* const defp = VN_CAST(foundp->nodep(), MemberDType)) { + if (allowVar) { + checkMemberDeclOrder(nodep, defp); + AstRefDType* const refp = new AstRefDType{nodep->fileline(), nodep->name()}; + refp->refDTypep(defp); + replaceWithCheckBreak(nodep, refp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + ok = true; + m_ds.m_dotText = ""; + } } else if (AstEnumItem* const valuep = VN_CAST(foundp->nodep(), EnumItem)) { if (allowVar) { AstNode* const newp @@ -4967,6 +5011,10 @@ class LinkDotResolveVisitor final : public VNVisitor { refdtypep->v3error("Self-referential enumerated type definition"); } } + void visit(AstNodeUOrStructDType* nodep) override { + LINKDOT_VISIT_START(); + symIterateChildren(nodep, m_statep->getNodeSym(nodep)); + } void visit(AstEnumItemRef* nodep) override { // Resolve its reference // EnumItemRefs are created by the first pass, but V3Param may regenerate due to @@ -5984,7 +6032,7 @@ class LinkDotResolveVisitor final : public VNVisitor { if (nodep->classOrPackagep()) { foundp = m_statep->getNodeSym(nodep->classOrPackagep())->findIdFlat(nodep->name()); } else if (m_ds.m_dotPos == DP_FIRST || m_ds.m_dotPos == DP_NONE) { - foundp = m_curSymp->findIdFallback(nodep->name()); + foundp = findIdFallbackSkipMemberDType(m_curSymp, nodep->name()); } else { // Defensive: dotPos should be DP_FIRST/DP_NONE or classOrPackagep set. v3fatalSrc("Unexpected dotPos=" diff --git a/src/V3Width.cpp b/src/V3Width.cpp index f5e55d2a7..e11285aa6 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -9661,7 +9661,8 @@ class WidthVisitor final : public VNVisitor { continue; } else if (const AstNodeUOrStructDType* const adtypep = VN_CAST(dtypep, NodeUOrStructDType)) { - bits *= adtypep->width(); + bits *= adtypep->isStreamableFixedAggregate() ? adtypep->widthStream() + : adtypep->width(); break; } else if (const AstBasicDType* const adtypep = VN_CAST(dtypep, BasicDType)) { bits *= adtypep->width(); diff --git a/test_regress/t/t_stream_unpacked_struct.v b/test_regress/t/t_stream_unpacked_struct.v index 8a169c20e..6b0c6f2d6 100644 --- a/test_regress/t/t_stream_unpacked_struct.v +++ b/test_regress/t/t_stream_unpacked_struct.v @@ -83,6 +83,17 @@ module t; byte tail; } packed_union_struct_t; + localparam int WORD_WIDTH = 16; + + typedef union packed { + struct packed { + logic [15:0] temp_1; + logic [15:0] temp_2; + logic [15:0] temp_3; + } regs; + logic [($bits(regs) / WORD_WIDTH)-1:0][WORD_WIDTH-1:0] words; + } issue_4521_union_t; + simple_t simple; simple_t simple_out; simple_t simple_cont_out; @@ -106,6 +117,7 @@ module t; real_struct_t real_struct_out; packed_union_struct_t packed_union_struct; packed_union_struct_t packed_union_struct_out; + issue_4521_union_t issue_4521_union; logic [19:0] simple_bits; logic [31:0] wide_simple_bits; @@ -122,6 +134,7 @@ module t; logic [15:0] packed_union_bits; logic [11:0] narrow_bits; logic [19:0] simple_streaml_src; + logic [$bits(simple_t)-1:0] simple_bits_from_bits; byte byte_array_out[2]; assign {>>{simple_cont_out}} = 20'habcde; @@ -138,6 +151,13 @@ module t; `checkh(simple_bits, 20'h54321); simple = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + `checkh($bits(simple_t), 20); + `checkh($bits(array_t), 32); + `checkh($bits(nested_t), 36); + `checkh($bits(struct_array_t), 56); + `checkh($bits(simple_array), 40); + simple_bits_from_bits = {>>{simple}}; + `checkh(simple_bits_from_bits, 20'h12abc); simple_bits = {>>{simple}}; `checkh(simple_bits, 20'h12abc); /* verilator lint_off WIDTHEXPAND */ @@ -341,6 +361,16 @@ module t; `checkh(packed_union_struct_out.u.byte_data, 8'hca); `checkh(packed_union_struct_out.tail, 8'hfe); + `checkh($bits(issue_4521_union_t), 48); + `checkh($bits(issue_4521_union.regs), 48); + `checkh($bits(issue_4521_union.words), 48); + issue_4521_union.regs.temp_1 = 16'h1234; + issue_4521_union.regs.temp_2 = 16'h5678; + issue_4521_union.regs.temp_3 = 16'h9abc; + `checkh(issue_4521_union.words[2], 16'h1234); + `checkh(issue_4521_union.words[1], 16'h5678); + `checkh(issue_4521_union.words[0], 16'h9abc); + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_struct_type_bad.out b/test_regress/t/t_struct_type_bad.out index f57c1dd0c..2085d6ecd 100644 --- a/test_regress/t/t_struct_type_bad.out +++ b/test_regress/t/t_struct_type_bad.out @@ -2,4 +2,11 @@ 13 | i badi; | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_struct_type_bad.v:18:19: Reference to 'later' before declaration (IEEE 1800-2023 6.18) + : ... Suggest move the declaration before the reference + 18 | logic [($bits(later) / 8)-1:0][7:0] bad_forward; + | ^~~~~ + t/t_struct_type_bad.v:19:18: ... Location of original declaration + 19 | logic [15:0] later; + | ^~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_struct_type_bad.v b/test_regress/t/t_struct_type_bad.v index 622f2e8e8..1d977d7b7 100644 --- a/test_regress/t/t_struct_type_bad.v +++ b/test_regress/t/t_struct_type_bad.v @@ -13,4 +13,10 @@ module t; i badi; // Bad } struct_t; + typedef union packed { + // Bad + logic [($bits(later) / 8)-1:0][7:0] bad_forward; + logic [15:0] later; + } forward_member_t; + endmodule From 3a4377d39eec2b76fd37d45523b24a4fc6749769 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Wed, 17 Jun 2026 23:57:18 +0200 Subject: [PATCH 51/89] Support clocking event on a sequence declaration body (#7598) (#7793) Fixes #7598. --- src/V3AssertNfa.cpp | 41 ++++++++++++++ src/V3AstNodeExpr.h | 17 ++++++ src/V3EmitV.cpp | 7 +++ src/V3Width.cpp | 16 ++++++ src/verilog.y | 6 +++ test_regress/t/t_assert_seq_clocking.py | 18 +++++++ test_regress/t/t_assert_seq_clocking.v | 53 +++++++++++++++++++ .../t/t_assert_seq_clocking_unsup.out | 30 +++++++++++ test_regress/t/t_assert_seq_clocking_unsup.py | 16 ++++++ test_regress/t/t_assert_seq_clocking_unsup.v | 36 +++++++++++++ test_regress/t/t_debug_emitv.out | 10 ++++ test_regress/t/t_debug_emitv.v | 8 +++ 12 files changed, 258 insertions(+) create mode 100755 test_regress/t/t_assert_seq_clocking.py create mode 100644 test_regress/t/t_assert_seq_clocking.v create mode 100644 test_regress/t/t_assert_seq_clocking_unsup.out create mode 100755 test_regress/t/t_assert_seq_clocking_unsup.py create mode 100644 test_regress/t/t_assert_seq_clocking_unsup.v diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index a7f2a8c1b..e302be9eb 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -2162,6 +2162,43 @@ class AssertNfaVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); } + // Hoist a leading clocking event (IEEE 1800-2023 16.7): + bool hoistClockedSeq(AstPropSpec* specp) { + while (AstSClocked* const clockedp = VN_CAST(specp->propp(), SClocked)) { + if (specp->sensesp()) { + clockedp->v3warn(E_UNSUPPORTED, "Unsupported: multiclocked sequence or property"); + replaceBodyOnBuildError(specp->fileline(), specp, true); + return true; + } + for (const AstSenItem* sp = clockedp->sensesp(); sp; + sp = VN_CAST(sp->nextp(), SenItem)) { + if (!sp->edgeType().anEdge()) { + clockedp->v3warn(E_UNSUPPORTED, + "Unsupported: non-edge clocking event on a sequence; " + "use an edge such as @(posedge clk)"); + replaceBodyOnBuildError(specp->fileline(), specp, true); + return true; + } + } + specp->sensesp(clockedp->sensesp()->unlinkFrBackWithNext()); + AstNodeExpr* const bodyp = clockedp->exprp()->unlinkFrBack(); + clockedp->replaceWith(bodyp); + VL_DO_DANGLING(pushDeletep(clockedp), clockedp); + } + // A clocking event anywhere else in the sequence is not supported. + const AstSClocked* nestedp = nullptr; + specp->propp()->foreach([&](const AstSClocked* p) { + if (!nestedp) nestedp = p; + }); + if (nestedp) { + nestedp->v3warn(E_UNSUPPORTED, + "Unsupported: clocking event inside sequence expression"); + replaceBodyOnBuildError(specp->fileline(), specp, true); + return true; + } + return false; + } + // Build the NFA graph for a property body, handling both the antecedent // |-> consequent and simple sequence cases. Returns the consequent/body // BuildResult (invalid on parse/build failure). @@ -2337,6 +2374,10 @@ class AssertNfaVisitor final : public VNVisitor { inlineAllSequenceRefs(assertp->propp()); + if (AstPropSpec* const specp = VN_CAST(assertp->propp(), PropSpec)) { + if (hoistClockedSeq(specp)) return; + } + AstNode* const propp = assertp->propp(); if (!hasMultiCycleExpr(propp)) return; if (isBareTopLevelUntil(propp)) return; diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 41d980697..f6e0d98f5 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -2225,6 +2225,23 @@ public: bool sameNode(const AstNode* /*samep*/) const override { return true; } bool isSystemFunc() const override { return true; } }; +class AstSClocked final : public AstNodeExpr { + // Sequence expression with an explicit leading clocking event + // IEEE 1800-2023 16.7: sequence_expr ::= clocking_event sequence_expr + // The clocking event is hoisted to the enclosing assertion clock by V3AssertNfa. + // @astgen op1 := sensesp : AstSenItem + // @astgen op2 := exprp : AstNodeExpr +public: + AstSClocked(FileLine* fl, AstSenItem* sensesp, AstNodeExpr* exprp) + : ASTGEN_SUPER_SClocked(fl) { + this->sensesp(sensesp); + this->exprp(exprp); + } + ASTGEN_MEMBERS_AstSClocked; + string emitVerilog() override { V3ERROR_NA_RETURN(""); } + string emitC() override { V3ERROR_NA_RETURN(""); } + bool cleanOut() const override { V3ERROR_NA_RETURN(false); } +}; class AstSConsRep final : public AstNodeExpr { // Consecutive repetition [*N], [*N:M], [+], [*] (IEEE 1800-2023 16.9.2) // op1 := exprp -- the repeated expression diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 4201a0ad5..db0798e38 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -1142,6 +1142,13 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { puts(") "); iterateConst(nodep->propp()); } + void visit(AstSClocked* nodep) override { + puts("@("); + iterateConst(nodep->sensesp()); + puts(") "); + iterateConst(nodep->exprp()); + puts("\n"); + } void visit(AstPropAlways* nodep) override { puts(nodep->isStrong() ? "s_always" : "always"); if (!VN_IS(nodep->loBoundp(), Unbounded) || !VN_IS(nodep->hiBoundp(), Unbounded)) { diff --git a/src/V3Width.cpp b/src/V3Width.cpp index e11285aa6..b323c0b92 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -1837,6 +1837,22 @@ class WidthVisitor final : public VNVisitor { nodep->dtypeSetBit(); } } + void visit(AstSClocked* nodep) override { + VL_RESTORER(m_underSExpr); + m_underSExpr = true; + m_hasSExpr = true; + assertAtExpr(nodep); + if (m_vup->prelim()) { + // Width each clock expression directly; the senitem chain is hoisted to + // the assertion clock by V3AssertNfa, where it is fully processed. + for (AstSenItem* senp = nodep->sensesp(); senp; + senp = VN_CAST(senp->nextp(), SenItem)) { + userIterateAndNext(senp->sensp(), WidthVP{SELF, BOTH}.p()); + } + iterateCheckBool(nodep, "exprp", nodep->exprp(), BOTH); + nodep->dtypeSetBit(); + } + } void visit(AstURandomRange* nodep) override { assertAtExpr(nodep); if (m_vup->prelim()) { diff --git a/src/verilog.y b/src/verilog.y index 847a5f828..6d6efea6b 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -6673,6 +6673,12 @@ sequence_declarationBody: // IEEE: part of sequence_declaration | assertion_variable_declarationList sexpr ';' { $$ = addNextNull($1, $2); } | sexpr { $$ = $1; } | sexpr ';' { $$ = $1; } + // // IEEE: clocking_event sequence_expr (16.7) + // // A leading clocking event on a named sequence body. + | '@' '(' event_expression ')' sexpr { $$ = new AstSClocked{$1, $3, $5}; } + | '@' '(' event_expression ')' sexpr ';' { $$ = new AstSClocked{$1, $3, $5}; } + | '@' senitemVar sexpr { $$ = new AstSClocked{$1, $2, $3}; } + | '@' senitemVar sexpr ';' { $$ = new AstSClocked{$1, $2, $3}; } ; property_spec: // IEEE: property_spec diff --git a/test_regress/t/t_assert_seq_clocking.py b/test_regress/t/t_assert_seq_clocking.py new file mode 100755 index 000000000..23f04b54c --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_seq_clocking.v b/test_regress/t/t_assert_seq_clocking.v new file mode 100644 index 000000000..b835acb85 --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking.v @@ -0,0 +1,53 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + int unsigned crc = 32'h1; + bit a, b; + int cyc = 0; + int fails_single = 0; + int fails_multi = 0; + + // verilog_format: off // verible does not support clocking events inside sequence declarations + sequence s_single; + @(posedge clk) a + endsequence + + sequence s_multi; + @(posedge clk) (a ##1 b); + endsequence + + sequence s_unused; + @(posedge clk) b; + endsequence + // verilog_format: on + + ap_single: assert property (s_single) else fails_single = fails_single + 1; + ap_multi: assert property (s_multi) else fails_multi = fails_multi + 1; + + always @(posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[30:0], crc[31] ^ crc[21] ^ crc[1] ^ crc[0]}; + a <= crc[0]; + b <= crc[1]; + if (cyc == 40) $finish; + end + + // Counts read in final (Postponed) to avoid same-timestep races. + // Concrete Verilator counts; Questa: fails_single=17 fails_multi=17 + final begin + `checkd(fails_single, 17); + `checkd(fails_multi, 17); + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_assert_seq_clocking_unsup.out b/test_regress/t/t_assert_seq_clocking_unsup.out new file mode 100644 index 000000000..5ab9bb741 --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking_unsup.out @@ -0,0 +1,30 @@ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:15:5: Unsupported: multiclocked sequence or property + : ... note: In instance 't' + 15 | @(posedge clk) a; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:19:5: Unsupported: clocking event inside sequence expression + : ... note: In instance 't' + 19 | @(posedge clk) b; + | ^ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:23:5: Unsupported: non-edge clocking event on a sequence; use an edge such as @(posedge clk) + : ... note: In instance 't' + 23 | @clk a; + | ^ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:27:5: Unsupported: non-edge clocking event on a sequence; use an edge such as @(posedge clk) + : ... note: In instance 't' + 27 | @clk a + | ^ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:32:3: Unsupported: Unclocked assertion + : ... note: In instance 't' + 32 | assert property (s_nest ##1 a); + | ^~~~~~ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:33:3: Unsupported: Unclocked assertion + : ... note: In instance 't' + 33 | assert property (s_level); + | ^~~~~~ +%Error-UNSUPPORTED: t/t_assert_seq_clocking_unsup.v:34:3: Unsupported: Unclocked assertion + : ... note: In instance 't' + 34 | assert property (s_level2); + | ^~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_assert_seq_clocking_unsup.py b/test_regress/t/t_assert_seq_clocking_unsup.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_assert_seq_clocking_unsup.v b/test_regress/t/t_assert_seq_clocking_unsup.v new file mode 100644 index 000000000..785c67000 --- /dev/null +++ b/test_regress/t/t_assert_seq_clocking_unsup.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk, + input clk2 +); + logic a, b; + + // verilog_format: off + sequence s_multi; + @(posedge clk) a; + endsequence + + sequence s_nest; + @(posedge clk) b; + endsequence + + sequence s_level; + @clk a; + endsequence + + sequence s_level2; + @clk a + endsequence + // verilog_format: on + + assert property (@(posedge clk2) s_multi); + assert property (s_nest ##1 a); + assert property (s_level); + assert property (s_level2); + +endmodule diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 53f8ac53b..8c0ad2245 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -364,6 +364,16 @@ module Vt_debug_emitv_t; release sum; end end + sequence s_clocked; + @(posedge clk) in + endsequence + begin : assert_seq_clocked + assert property ( s_clocked() + ) begin + end + else begin + end + end property p; @(posedge clk) ##1 sum[0] endproperty diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index 7d3805052..813d02ea9 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -286,6 +286,14 @@ module t (/*AUTOARG*/ release sum; end + // verilog_format: off // verible does not support clocking events inside sequence declarations + sequence s_clocked; + @(posedge clk) in + endsequence + // verilog_format: on + + assert_seq_clocked: assert property (s_clocked); + property p; @(posedge clk) ##1 sum[0] endproperty From 5712f9b6149bb28e3d26e7bc60f7e12e3fbf9645 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 18 Jun 2026 09:30:50 +0100 Subject: [PATCH 52/89] Optimize decoder case statements into lookup tables (#7795) Recognize "decoder" case statements (where every case item only assigns constants to a fixed set of left-hand sides) and replace them with a single packed constant lookup table indexed by the case expression. Small tables are materialized inline in the generated code, and are always optimized. Larger ones are placed in the constant pool and only optimized if deemed beneficial over branches. While this slightly conflicts with V3Table, and is not worth that much on it's own, there will be a follow up patch that converts more cases of this form which will be much more valuable. This patch does the necessary analysis and the simple table conversion when possible. Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the existing bitwise branch-tree conversion); -fno-case is now an alias for both. Default branches, assignments preceding the case (used as default values), casez wildcards, multiple and partial left-hand sides, and both blocking and non-blocking assignments are handled. Cases that cannot be safely tabled (e.g. non-exhaustive with no default, overlapping writes to one variable, or mixed blocking/non-blocking assignments) fall back to the existing if/else lowering. Consequently disabled re-inlining of constant pool variables in V3Const, and rebuild the constant pool hash in V3Dead (previously we didn't create constant pool entries early enough for this to matter) --- docs/guide/exe_verilator.rst | 12 + src/V3AstNodeOther.h | 6 + src/V3AstNodes.cpp | 14 + src/V3Case.cpp | 423 +++++++++++++++++++-- src/V3Const.cpp | 2 +- src/V3Dead.cpp | 1 + src/V3DfgOptimizer.cpp | 12 +- src/V3Options.cpp | 10 +- src/V3Options.h | 6 +- test_regress/t/t_case_huge.py | 12 +- test_regress/t/t_case_huge_nocase.py | 2 + test_regress/t/t_case_huge_nocase_tree.py | 23 ++ test_regress/t/t_case_table_normal.py | 21 + test_regress/t/t_case_table_normal.v | 273 +++++++++++++ test_regress/t/t_case_table_normal_off.py | 23 ++ test_regress/t/t_case_table_tiny.py | 21 + test_regress/t/t_case_table_tiny.v | 369 ++++++++++++++++++ test_regress/t/t_case_table_tiny_off.py | 23 ++ test_regress/t/t_dfg_constpool_unused.py | 20 + test_regress/t/t_dfg_constpool_unused.v | 44 +++ test_regress/t/t_opt_table_enum.py | 2 +- test_regress/t/t_opt_table_packed_array.py | 2 +- test_regress/t/t_opt_table_real.py | 2 +- test_regress/t/t_opt_table_same.py | 2 +- test_regress/t/t_opt_table_signed.py | 2 +- test_regress/t/t_opt_table_string.py | 2 +- test_regress/t/t_opt_table_struct.py | 2 +- 27 files changed, 1287 insertions(+), 44 deletions(-) create mode 100755 test_regress/t/t_case_huge_nocase_tree.py create mode 100755 test_regress/t/t_case_table_normal.py create mode 100644 test_regress/t/t_case_table_normal.v create mode 100755 test_regress/t/t_case_table_normal_off.py create mode 100755 test_regress/t/t_case_table_tiny.py create mode 100644 test_regress/t/t_case_table_tiny.v create mode 100755 test_regress/t/t_case_table_tiny_off.py create mode 100755 test_regress/t/t_dfg_constpool_unused.py create mode 100644 test_regress/t/t_dfg_constpool_unused.v diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 29e3876c4..72da2961f 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -659,6 +659,18 @@ Summary: .. option:: -fno-case + Rarely needed. Disable all case statement optimizations. + + Alias for all other `-fno-case-*` options. + +.. option:: -fno-case-table + + Rarely needed. Disable converting case statements into table lookups. + +.. option:: -fno-case-tree + + Rarely needed. Disable converting case statements into bitwise branch trees. + .. option:: -fno-combine .. option:: -fno-const diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index f9518bf63..e3bbcde14 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1000,6 +1000,8 @@ public: // this matters, the caller must handle the dtype difference as appropriate. If 'mergeDType' is // false, the returned VarScope will have _->dtypep()->sameTree(initp->dtypep()) return true. AstVarScope* findConst(AstConst* initp, bool mergeDType); + // Rebuild hashes after potential removals + void reCache(); }; class AstConstraint final : public AstNode { // Constraint @@ -2136,6 +2138,7 @@ class AstVar final : public AstNode { bool m_attrFsmRegisterWrapper : 1; // connected to an fsm_register_wrapper instance bool m_attrFsmResetArc : 1; // declared with fsm_reset_arc metacomment bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment + bool m_constPoolEntry : 1; // Constant pool variable bool m_fileDescr : 1; // File descriptor bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration bool m_icoMaybeWritten : 1; // Design might write this input signal - for ico change detect @@ -2199,6 +2202,7 @@ class AstVar final : public AstNode { m_attrFsmRegisterWrapper = false; m_attrFsmResetArc = false; m_attrFsmArcInclCond = false; + m_constPoolEntry = false; m_fileDescr = false; m_gotNansiType = false; m_icoMaybeWritten = false; @@ -2348,6 +2352,8 @@ public: void attrFsmRegisterWrapper(bool flag) { m_attrFsmRegisterWrapper = flag; } void attrFsmResetArc(bool flag) { m_attrFsmResetArc = flag; } void attrFsmArcInclCond(bool flag) { m_attrFsmArcInclCond = flag; } + bool constPoolEntry() const { return m_constPoolEntry; } + void setConstPoolEntry() { m_constPoolEntry = true; } void rand(const VRandAttr flag) { m_rand = flag; } void usedParam(bool flag) { m_usedParam = flag; } void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 453c45d21..3b35037a8 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1629,6 +1629,7 @@ AstConstPool::AstConstPool(FileLine* fl) AstVarScope* AstConstPool::createNewEntry(const string& name, AstNodeExpr* initp) { FileLine* const fl = initp->fileline(); AstVar* const varp = new AstVar{fl, VVarType::MODULETEMP, name, initp->dtypep()}; + varp->setConstPoolEntry(); varp->isConst(true); varp->isStatic(true); varp->valuep(initp->cloneTree(false)); @@ -1748,6 +1749,17 @@ AstVarScope* AstConstPool::findConst(AstConst* initp, bool mergeDType) { return varScopep; } +void AstConstPool::reCache() { + m_tables.clear(); + m_consts.clear(); + for (AstVarScope* vscp = m_scopep->varsp(); vscp; vscp = VN_CAST(vscp->nextp(), VarScope)) { + AstNode* const valuep = vscp->varp()->valuep(); + const V3Hash hash = V3Hasher::uncachedHash(valuep); + if (VN_IS(valuep, InitArray)) m_tables.emplace(hash.value(), vscp); + if (VN_IS(valuep, Const)) m_consts.emplace(hash.value(), vscp); + } +} + //====================================================================== // Per-type Debugging @@ -3198,6 +3210,7 @@ int AstVarRef::instrCount() const { } void AstVar::dump(std::ostream& str) const { this->AstNode::dump(str); + if (constPoolEntry()) str << " [CONSTPOOL]"; if (isSc()) str << " [SC]"; if (isPrimaryIO()) str << (isInout() ? " [PIO]" : (isWritable() ? " [PO]" : " [PI]")); if (isPrimaryClock()) str << " [PCLK]"; @@ -3239,6 +3252,7 @@ void AstVar::dump(std::ostream& str) const { void AstVar::dumpJson(std::ostream& str) const { dumpJsonStrFunc(str, origName); dumpJsonStrFunc(str, verilogName); + dumpJsonBoolFuncIf(str, constPoolEntry); dumpJsonBoolFuncIf(str, isSc); dumpJsonBoolFuncIf(str, isPrimaryIO); dumpJsonBoolFuncIf(str, isPrimaryClock); diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 80dffba68..251c67202 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -133,6 +133,12 @@ class CaseVisitor final : public VNVisitor { constexpr static int CASE_DETAILS_MAX_WIDTH = 16; // Levels of priority to be ORed together in top IF tree constexpr static int CASE_ENCODER_GROUP_DEPTH = 8; + // Maximum size for tiny lookup tables - materialized in code + constexpr static size_t CASE_TABLE_TINY_BITS = 32; // Up to 2 instructions to materialize + // Maximum size for normal lookup tables - stored in constant pool + constexpr static size_t CASE_TABLE_MAX_BITS = 1ULL << 16; // 64Kbits / 8KBytes + // Minimum number of the branches a table must replace to be worth a load + constexpr static size_t CASE_TABLE_MIN_BRANCHES = 3; // TYPES // Record for each case value @@ -142,21 +148,49 @@ class CaseVisitor final : public VNVisitor { AstNode* stmtsp; // Statements of 'itemp' (might be nullptr if case is empty) }; + // Record for each LHS of a decoder pattern + struct LhsRecord final { + AstNodeExpr* lhsp = nullptr; // LHS of the assignment + AstNodeAssign* preDefaultp = nullptr; // Default assignment *before the case statement* + size_t nCaseAssigns = 0; // Number of AstAssigns to this LHS in case clauses + size_t nCaseAssignDlys = 0; // Number of AstAssignDlys to this LHS in case clauses + size_t offset = 0; // Offset in the table for this LHS + + static size_t s_nextId; // Static unique Id counter + size_t id = ++s_nextId; // Unique Id for sorting + }; + + // NODE STATE: + // AstVarScope::user1() -> bool: true if written to, only in parts of analysis phase + // STATE // Statistics tracking, as a struct so can be passed to 'const' methods struct Stats final { + VDouble0 caseTableNormal; // Cases using table method with normal table + VDouble0 caseTableTiny; // Cases using table method with tiny table VDouble0 caseFast; // Cases using fast bit tree method VDouble0 caseGeneric; // Cases using generic if/else tree method VDouble0 provenAssertions; // Assertions proven to hold } m_stats; const AstNode* m_alwaysp = nullptr; // Always in which case is located + size_t m_nTmps = 0; // Sequence numbers for temporary variables + AstScope* m_scopep = nullptr; // Current scope // STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions) + bool m_caseHasDefault = false; // Indicates the case statement has a default case + size_t m_caseNCaseItems = 0; // Number of AstCaseItems in the case statement size_t m_caseNConditions = 0; // Number of conditions in the case statement + // Map from LHSs of decoder pattern to corresponding LhsRecord. + std::unordered_map, LhsRecord> m_caseLhsRecords; + // Values of 'm_caseLhsRecords' in sorted order, if case statement is a decoder pattern + std::vector m_caseDecoderRecords; + size_t m_caseDecoderEntryWidth = 0; // Width of each entry in the decoder table + size_t m_caseTableWidth = 0; // Total width of the case table - 0 means can't optimize bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid struct final { bool exhaustive = false; // Proven exhaustive + bool exhaustiveOverEnumOnly = false; // Exhaustive over enum values only bool noOverlaps = false; // Proven no overlaps between cases // Map from value (index) to the CaseRecord that covers this value std::array records; @@ -189,6 +223,50 @@ class CaseVisitor final : public VNVisitor { return pairMaskBits; } + // If the given statement is an assignment that fits the decoder pattern, + // return it, otherwise return nullptr + static AstNodeAssign* checkDecoderAssign(AstNode* stmtp) { + // Only Assign and AssignDly are supported + if (!VN_IS(stmtp, Assign) && !VN_IS(stmtp, AssignDly)) return nullptr; + AstNodeAssign* const assp = VN_AS(stmtp, NodeAssign); + // Only if no timing control + if (assp->timingControlp()) return nullptr; + // Only if assigning a constant + if (!VN_IS(assp->rhsp(), Const)) return nullptr; + // Only if it's a packed value + AstNodeDType* const dtypep = assp->rhsp()->dtypep(); + if (dtypep->isString() || dtypep->isDouble()) return nullptr; + // Only if the LHS has no reads (can be relaxed, but need to prove there is no r/w hazard) + if (assp->lhsp()->exists([](AstVarRef* refp) { return refp->access().isReadOrRW(); })) { + return nullptr; + } + // This is an assignment that fits the decoder pattern + return assp; + } + + // Analyze if the given case item fits the decoder pattern, return true iff so. + // Updates 'm_caseLhsRecords'. + bool analyzeDecoderCaseItem(AstCaseItem* cip) { + // AstVarScope::user1() -> bool: true if written to + const VNUser1InUse user1InUse; + for (AstNode* stmtp = cip->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + // Must be an assignment that fits the decoder pattern + AstNodeAssign* const assp = checkDecoderAssign(stmtp); + if (!assp) return false; + // Must assign each LHS exactly once - RHS is Const + const bool multipleAssignments = assp->lhsp()->exists([](AstVarRef* refp) { // + return refp->varScopep()->user1SetOnce(); + }); + if (multipleAssignments) return false; + // Update LhsRecord + LhsRecord& lhsRecord = m_caseLhsRecords[*assp->lhsp()]; + if (!lhsRecord.lhsp) lhsRecord.lhsp = assp->lhsp(); + lhsRecord.nCaseAssigns += VN_IS(assp, Assign); + lhsRecord.nCaseAssignDlys += VN_IS(assp, AssignDly); + } + return true; + } + // Determine whether we should check case items are complete // Returns enum's dtype if should check, nullptr if shouldn't static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { @@ -245,13 +323,6 @@ class CaseVisitor final : public VNVisitor { return true; } - bool checkExhaustive(AstCase* nodep) { - if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) { - return checkExhaustiveEnum(nodep, enump); - } - return checkExhaustivePacked(nodep); - } - // Analyze each value in the case statement. Updates 'm_caseDetails' and issues warnings. void analyzeCaseDetails(AstCase* nodep) { const uint32_t numValues = 1UL << nodep->exprp()->width(); @@ -361,16 +432,136 @@ class CaseVisitor final : public VNVisitor { } // If there was no default, check exhaustiveness - m_caseDetails.exhaustive = hasDefault || checkExhaustive(nodep); + m_caseDetails.exhaustiveOverEnumOnly = false; + m_caseDetails.exhaustive = hasDefault; + if (!hasDefault) { + if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) { + // Only checks enum values are covered, not all bit patterns of the case expression + const bool exhaustiveOverEnum = checkExhaustiveEnum(nodep, enump); + m_caseDetails.exhaustiveOverEnumOnly = exhaustiveOverEnum; + m_caseDetails.exhaustive = exhaustiveOverEnum; + } else { + m_caseDetails.exhaustive = checkExhaustivePacked(nodep); + } + } + // Records now valid m_caseDetailsValid = true; } + void analyzeDecoderPattern(AstCase* nodep) { + // Check each LHS record + for (auto it = m_caseLhsRecords.cbegin(); it != m_caseLhsRecords.cend();) { + const LhsRecord& lhsRecord = it->second; + + // Delete records that have no assignments in any case item (only pre-defaults) + if (!lhsRecord.nCaseAssigns && !lhsRecord.nCaseAssignDlys) { + it = m_caseLhsRecords.erase(it); + continue; + } + ++it; + + // If mixed assignments, it's not a decoder pattern + if (lhsRecord.nCaseAssigns && lhsRecord.nCaseAssignDlys) return; + + // If assigned in all branches, it's good - but only if every table entry will be + // covered, i.e. the case has a default, or is exhaustive over all bit patterns. + // Enum-only exhaustiveness is not enough: out-of-enum values leave entries + // uncovered. + if (m_caseHasDefault + || (m_caseDetailsValid && m_caseDetails.exhaustive + && !m_caseDetails.exhaustiveOverEnumOnly)) { + if (lhsRecord.nCaseAssigns == m_caseNCaseItems) continue; + if (lhsRecord.nCaseAssignDlys == m_caseNCaseItems) continue; + } + + // Otherwise it needs to have a pre-default assignment + AstNode* const preDefaultp = lhsRecord.preDefaultp; + if (!preDefaultp) return; + // And the pre-default needs to be the same type + if (lhsRecord.nCaseAssigns && !VN_IS(preDefaultp, Assign)) return; + if (lhsRecord.nCaseAssignDlys && !VN_IS(preDefaultp, AssignDly)) return; + } + // All cases check out, can optimize if there are some entries left + if (m_caseLhsRecords.empty()) return; + + // Gather all the LhsRecords and sort them - there is a copy here, it's ok, won't be many + m_caseDecoderRecords.reserve(m_caseLhsRecords.size()); + for (const auto& item : m_caseLhsRecords) m_caseDecoderRecords.emplace_back(item.second); + std::sort(m_caseDecoderRecords.begin(), m_caseDecoderRecords.end(), + [](const LhsRecord& a, const LhsRecord& b) { + // Sort by width, then id + const int aWidth = a.lhsp->width(); + const int bWidth = b.lhsp->width(); + if (aWidth != bWidth) return aWidth < bWidth; + return a.id < b.id; + }); + + // We can either create a single lookup table for all LHSs, or one for each LHS. + // With a single table, we need to select out of the lookup via a temporary variable. + // With one table per LHS, we need to do multiple loads. The table is likely to incur a + // D-cache miss on large designs, so we choose single table. + + const int caseWidth = nodep->exprp()->width(); + + // Safely check if table with 'entryWidth' entries would fit within 'maxWidth' bits + const auto fitsLimit = [&](size_t entryWidth, size_t maxWidth) -> bool { + size_t totalWidth = entryWidth; + // Multiply cases - iterative to avoid overflow + for (int i = 0; i < caseWidth; ++i) { + totalWidth <<= 1; + if (totalWidth > maxWidth) return false; + } + return true; + }; + + // Check if the whole table would fit in a tiny table packed tightly + m_caseDecoderEntryWidth = 0; + for (LhsRecord& lhsRecord : m_caseDecoderRecords) { + lhsRecord.offset = m_caseDecoderEntryWidth; + m_caseDecoderEntryWidth += lhsRecord.lhsp->width(); + } + // If it fits, we will pack it tightly + if (fitsLimit(m_caseDecoderEntryWidth, CASE_TABLE_TINY_BITS)) { + m_caseTableWidth = m_caseDecoderEntryWidth << caseWidth; // Can optimize + return; + } + + // Tabel will be bigish. To avoid expensive bit swizzling, align each entry to a + // word boundary if it would cross a word boundary. + m_caseDecoderEntryWidth = 0; + for (LhsRecord& lhsRecord : m_caseDecoderRecords) { + const size_t width = lhsRecord.lhsp->width(); + const size_t lsbWord = VL_BITWORD_E(m_caseDecoderEntryWidth); + const size_t msbWord = VL_BITWORD_E(m_caseDecoderEntryWidth + width - 1); + if (lsbWord != msbWord) { + m_caseDecoderEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE; + } + lhsRecord.offset = m_caseDecoderEntryWidth; + m_caseDecoderEntryWidth += width; + } + // Also align the whole entry width to a word boundary + m_caseDecoderEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE; + // Check the table fits max size + if (fitsLimit(m_caseDecoderEntryWidth, CASE_TABLE_MAX_BITS)) { + m_caseTableWidth = m_caseDecoderEntryWidth << caseWidth; // Can optimize + return; + } + + // Can't optimize - yet ... + } + // Analyze case statement. Updates 'm_case*' members. Reports warnings. void analyzeCase(AstCase* nodep) { // Reset all analysis results m_caseOpaque = false; + m_caseHasDefault = false; + m_caseNCaseItems = 0; m_caseNConditions = 0; + m_caseDecoderRecords.clear(); + m_caseDecoderEntryWidth = 0; + m_caseTableWidth = 0; + m_caseLhsRecords.clear(); m_caseDetailsValid = false; AstNode* const caseExprp = nodep->exprp(); @@ -378,14 +569,44 @@ class CaseVisitor final : public VNVisitor { // Mark opaque if not a packed value - TODO: can this be a class? if (caseExprp->isDouble() || caseExprp->isString()) m_caseOpaque = true; - // Check each condition expression + // Gather pre-default assignments of decoder pattern + { + // AstVarScope::user1() -> bool: true if written to + const VNUser1InUse user1InUse; + for (AstNode* prevp = nodep->prevp(); prevp; prevp = prevp->prevp()) { + AstNodeAssign* const assp = checkDecoderAssign(prevp); + if (!assp) break; // Stop if not a decoder assignment + // Stop if multiple assignments + const bool multipleAssignments = assp->lhsp()->exists([&](AstVarRef* refp) { // + return refp->varScopep()->user1SetOnce(); + }); + if (multipleAssignments) break; + // Store pre-default assignment + LhsRecord& lhsRecord = m_caseLhsRecords[*assp->lhsp()]; + lhsRecord.lhsp = assp->lhsp(); + lhsRecord.preDefaultp = assp; + } + } + + // Check each case item + bool canBeDecoder = true; for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + // Check conditions for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { // Count conditions ++m_caseNConditions; // Mark opaque if non-constant condition - if (!VN_IS(condp, Const)) m_caseOpaque = true; + if (!VN_IS(condp, Const)) { + m_caseOpaque = true; + canBeDecoder = false; // Can't be a decoder if opaque + } } + // Check if it has a default case + if (cip->isDefault()) m_caseHasDefault = true; + // Count case items + ++m_caseNCaseItems; + // Check if it fits the decoder pattern, if still possible + if (canBeDecoder) canBeDecoder = analyzeDecoderCaseItem(cip); } // Nothing else to do if not a packed type, or non-const conditions @@ -393,6 +614,135 @@ class CaseVisitor final : public VNVisitor { // If small enough, analyse details if (caseExprp->width() <= CASE_DETAILS_MAX_WIDTH) analyzeCaseDetails(nodep); + + // Check if it actually fits a full decoder pattern + if (canBeDecoder) analyzeDecoderPattern(nodep); + } + + AstNodeStmt* convertCaseTable(AstCase* nodep) { + // Create the table constant + FileLine* const flp = nodep->fileline(); + AstConst* const tablep + = new AstConst{flp, AstConst::WidthedValue{}, static_cast(m_caseTableWidth), 0}; + const uint32_t tableEntries = 1U << nodep->exprp()->width(); + + // Populate the table + for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { + const int lhsWidth = lhsRecord.lhsp->width(); + const int lhsOffset = lhsRecord.offset; + + // Broadcast the pre-default assignment + if (lhsRecord.preDefaultp) { + AstConst* const rhsp = VN_AS(lhsRecord.preDefaultp->rhsp(), Const); + for (uint32_t index = 0; index < tableEntries; ++index) { + const uint32_t tableOffset = index * m_caseDecoderEntryWidth + lhsOffset; + tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); + } + } + + // Populate table based on each case item. In reverse order so earlier items win + for (AstCaseItem* cip = VN_AS(nodep->itemsp()->lastp(), CaseItem); cip; + cip = VN_AS(cip->prevp(), CaseItem)) { + // Find the RHS in this case + AstConst* const rhsp = [&]() -> AstConst* { + for (AstNode* stmtp = cip->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + AstNodeAssign* const ap = VN_AS(stmtp, NodeAssign); + if (lhsRecord.lhsp->sameTree(ap->lhsp())) return VN_AS(ap->rhsp(), Const); + } + // Not assigned in this case, use the pre-assigned default + return VN_AS(lhsRecord.preDefaultp->rhsp(), Const); + }(); + + // If default, broadcast it + if (cip->isDefault()) { + for (uint32_t index = 0; index < tableEntries; ++index) { + const uint32_t tableOffset = index * m_caseDecoderEntryWidth + lhsOffset; + tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); + } + continue; + } + + // Iterate case conditions in reverse order + for (AstConst* condp = VN_AS(cip->condsp()->lastp(), Const); condp; + condp = VN_AS(condp->prevp(), Const)) { + if (neverItem(nodep, condp)) continue; // If item never matches, ignore it + const auto& match = matchPattern(nodep, condp); + const uint32_t matchMask = match.first.toUInt(); + const uint32_t matchBits = match.second.toUInt(); + const uint32_t inverseMask = ~matchMask & ((1U << condp->width()) - 1); + // This iterates through all integers that are a subset of the inverse mask, + // i.e.: all don't care values masked out + for (uint32_t i = inverseMask; true; i = (i - 1) & inverseMask) { + const uint32_t index = i | matchBits; + const uint32_t tableOffset = index * m_caseDecoderEntryWidth + lhsOffset; + tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); + if (!i) break; + } + } + } + } + + // Create the table in the constant pool, unless using an inline table + AstVarScope* const tableVscp = [&]() -> AstVarScope* { + if (m_caseTableWidth <= CASE_TABLE_TINY_BITS) { + ++m_stats.caseTableTiny; + return nullptr; + } + ++m_stats.caseTableNormal; + AstVarScope* vscp = v3Global.rootp()->constPoolp()->findConst(tablep, true); + VL_DO_DANGLING(tablep->deleteTree(), tablep); // findConst clones + return vscp; + }(); + + // Create the lookup table reference and index + AstNodeExpr* const tableRefp + = tableVscp ? static_cast(new AstVarRef{flp, tableVscp, VAccess::READ}) + : static_cast(tablep); + AstNodeExpr* const caseExprp + = new AstExtend{flp, nodep->exprp()->cloneTreePure(false), 32}; + AstNodeExpr* const scalep + = new AstConst{flp, static_cast(m_caseDecoderEntryWidth)}; + AstNodeExpr* const tableLsbp = new AstMul{flp, scalep, caseExprp}; + + // If there is only one LHS, just use the result + if (m_caseDecoderRecords.size() == 1) { + const LhsRecord& lhsRecord = m_caseDecoderRecords[0]; + const int width = lhsRecord.lhsp->width(); + AstNodeExpr* const rhsp = new AstSel{flp, tableRefp, tableLsbp, width}; + AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false); + if (lhsRecord.nCaseAssigns) { + return new AstAssign{flp, lhsp, rhsp}; + } else if (lhsRecord.nCaseAssignDlys) { + return new AstAssignDly{flp, lhsp, rhsp}; + } else { + nodep->v3fatalSrc("Unknown assignment type"); + } + } + + // There are multiple LHSs, store the lookup result in a temporary + const std::string name = "__VcaseTableOut" + std::to_string(m_nTmps++); + AstVarScope* const tempVscp = m_scopep->createTemp(name, m_caseDecoderEntryWidth); + AstNodeExpr* const tempWritep = new AstVarRef{flp, tempVscp, VAccess::WRITE}; + AstNodeExpr* const tableSelp + = new AstSel{flp, tableRefp, tableLsbp, static_cast(m_caseDecoderEntryWidth)}; + AstNodeStmt* const resultp = new AstAssign{flp, tempWritep, tableSelp}; + + // For each LHS, select out the result + for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { + const int width = lhsRecord.lhsp->width(); + const int lsb = lhsRecord.offset; + AstNodeExpr* const tempReadp = new AstVarRef{flp, tempVscp, VAccess::READ}; + AstNodeExpr* const rhsp = new AstSel{flp, tempReadp, lsb, width}; + AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false); + if (lhsRecord.nCaseAssigns) { + resultp->addNext(new AstAssign{flp, lhsp, rhsp}); + } else if (lhsRecord.nCaseAssignDlys) { + resultp->addNext(new AstAssignDly{flp, lhsp, rhsp}); + } else { + nodep->v3fatalSrc("Unknown assignment type"); + } + } + return resultp; } // TODO: should return AstNodeStmt after #6280 @@ -443,7 +793,8 @@ class CaseVisitor final : public VNVisitor { // -> tree of IF(msb, IF(msb-1, 11, 10) // IF(msb-1, 01, 00)) // TODO: should return AstNodeStmt after #6280 - AstNode* convertCaseFast(AstCase* nodep) const { + AstNode* convertCaseFast(AstCase* nodep) { + ++m_stats.caseFast; const int caseWidth = nodep->exprp()->width(); AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp; @@ -455,7 +806,8 @@ class CaseVisitor final : public VNVisitor { // IF((EQ (AND MASK cexpr) (AND MASK icond1) // ,istmts2, istmts3 // TODO: should return AstNodeStmt after #6280 - AstNode* convertCaseGeneric(AstCase* nodep) const { + AstNode* convertCaseGeneric(AstCase* nodep) { + ++m_stats.caseGeneric; // We'll do this in two stages. // First stage, convert the conditions to the appropriate IF AND terms. bool hasDefault = false; @@ -522,7 +874,8 @@ class CaseVisitor final : public VNVisitor { // 'Or' new term with previous terms newCondp = newCondp ? new AstLogOr{flp, newCondp, termp} : termp; } - // Replace expression in tree. Needs to be non-null, so add a constant false if needed + // Replace expression in tree. Needs to be non-null, so add a constant false if + // needed if (!newCondp) newCondp = new AstConst{flp, AstConst::BitFalse{}}; itemp->addCondsp(newCondp); } @@ -591,11 +944,31 @@ class CaseVisitor final : public VNVisitor { // Convert the given case statement to a representation not using AstCase // TODO: should return AstNodeStmt after #6280 - AstNode* convertCase(AstCase* nodep, Stats& stats) const { + AstNode* convertCase(AstCase* nodep) { + // Determine if we should use the lookup table method + const bool useTable = [&]() { + // Not if disabled + if (!v3Global.opt.fCaseTable()) return false; + // Not if analysis tells us we can't + if (!m_caseTableWidth) return false; + // Always if tiny - it is materialized inline, so there is no load to amortize + if (m_caseTableWidth <= CASE_TABLE_TINY_BITS) return true; + // For a normal (constant-pool) table, weigh the indexed load against the branch + // lowering it would replace. That lowering's depth is bounded by the selector + // width (a balanced bit tree tests ~one bit per level) and by the number of + // distinct values (a generic if/else does ~one compare per value). A few compares + // are cheaper than a load that is likely to be a cache miss, so only table once that + // depth is exceeded. + const size_t branches = std::min(nodep->exprp()->width(), m_caseNConditions); + if (branches < CASE_TABLE_MIN_BRANCHES) return false; + return true; + }(); + if (useTable) return convertCaseTable(nodep); + // Determine if we should use the fast bitwise branching tree method const bool useFastBitTree = [&]() { // Not if disabled - if (!v3Global.opt.fCase()) return false; + if (!v3Global.opt.fCaseTree()) return false; // Can't do it without the detailed analysis if (!m_caseDetailsValid) return false; // Can't do it if not exhaustive @@ -608,13 +981,9 @@ class CaseVisitor final : public VNVisitor { // Otherwise use the bit tree return true; }(); - if (useFastBitTree) { - ++stats.caseFast; - return convertCaseFast(nodep); - } + if (useFastBitTree) return convertCaseFast(nodep); // Convert using the generic if/else tree method - ++stats.caseGeneric; // If a case statement is exhaustive, presume signals involved aren't forming a latch // TODO: this is broken, but it is as was before if (m_alwaysp && (!m_caseDetailsValid || m_caseDetails.exhaustive)) { @@ -650,14 +1019,20 @@ class CaseVisitor final : public VNVisitor { } // Convert the case statement and replace the original - if (AstNode* const replacementp = convertCase(nodep, m_stats)) { + if (AstNode* const replacementp = convertCase(nodep)) { nodep->replaceWith(replacementp); } else { nodep->unlinkFrBack(); } VL_DO_DANGLING(nodep->deleteTree(), nodep); } - //-------------------- + + void visit(AstScope* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + iterateChildren(nodep); + } + void visit(AstAlways* nodep) override { VL_RESTORER(m_alwaysp); m_alwaysp = nodep; @@ -669,12 +1044,16 @@ public: // CONSTRUCTORS explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); } ~CaseVisitor() override { + V3Stats::addStat("Optimizations, Cases table normal", m_stats.caseTableNormal); + V3Stats::addStat("Optimizations, Cases table tiny", m_stats.caseTableTiny); V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast); V3Stats::addStat("Optimizations, Cases complex", m_stats.caseGeneric); V3Stats::addStat("Optimizations, Cases proven assertions", m_stats.provenAssertions); } }; +size_t CaseVisitor::LhsRecord::s_nextId = 0; + //###################################################################### // Case class functions diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 0098f1d94..34c4f349a 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -3281,7 +3281,7 @@ class ConstVisitor final : public VNVisitor { iterateChildren(nodep); UASSERT_OBJ(nodep->varp(), nodep, "Not linked"); bool did = false; - if (m_doV && nodep->varp()->valuep() && !m_attrp) { + if (m_doV && !nodep->varp()->constPoolEntry() && nodep->varp()->valuep() && !m_attrp) { // UINFOTREE(1, valuep, "", "visitvaref"); iterateAndNextNull(nodep->varp()->valuep()); // May change nodep->varp()->valuep() AstNode* const valuep = nodep->varp()->valuep(); diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index 1a08da9e8..677f0b2aa 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -597,6 +597,7 @@ public: // We may have removed some datatypes, cleanup nodep->typeTablep()->repairCache(); VIsCached::clearCacheTree(); // Removing assignments may affect isPure + nodep->constPoolp()->reCache(); } ~DeadVisitor() override { V3Stats::addStatSum("Optimizations, deadified FTasks", m_statFTasksDeadified); diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index 0ad3b8aa9..07002049d 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -78,9 +78,15 @@ class DataflowOptimize final { if (AstVarScope* const vscp = VN_CAST(nodep, VarScope)) { const AstVar* const varp = vscp->varp(); // Force and trace have already been processed - const bool hasExtRd = varp->isPrimaryIO() || varp->isSigUserRdPublic(); - const bool hasExtWr - = (varp->isPrimaryIO() && varp->isNonOutput()) || varp->isSigUserRWPublic(); + const bool hasExtRd = // + varp->isPrimaryIO() // Top level port - readable + || varp->isSigUserRdPublic() // Readable by user + || varp->constPoolEntry() // Stored in AstConstPool hashmap, but read only + ; + const bool hasExtWr = // + (varp->isPrimaryIO() && varp->isNonOutput()) // Top level port - writable + || varp->isSigUserRWPublic() // Writable by user + ; if (hasExtRd) DfgVertexVar::setHasExtRdRefs(vscp); if (hasExtWr) DfgVertexVar::setHasExtWrRefs(vscp); return; diff --git a/src/V3Options.cpp b/src/V3Options.cpp index dd26e7e06..575a498f9 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1448,7 +1448,12 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-facyc-simp", FOnOff, &m_fAcycSimp); DECL_OPTION("-fassemble", FOnOff, &m_fAssemble); - DECL_OPTION("-fcase", FOnOff, &m_fCase); + DECL_OPTION("-fcase", CbFOnOff, [this](bool flag) { + m_fCaseTable = flag; + m_fCaseTree = flag; + }); + DECL_OPTION("-fcase-table", FOnOff, &m_fCaseTable); + DECL_OPTION("-fcase-tree", FOnOff, &m_fCaseTree); DECL_OPTION("-fcombine", FOnOff, &m_fCombine); DECL_OPTION("-fconst", FOnOff, &m_fConst); DECL_OPTION("-fconst-before-dfg", FOnOff, &m_fConstBeforeDfg); @@ -2351,7 +2356,8 @@ void V3Options::optimize(int level) { const bool flag = level > 0; m_fAcycSimp = flag; m_fAssemble = flag; - m_fCase = flag; + m_fCaseTable = flag; + m_fCaseTree = flag; m_fCombine = flag; m_fConst = flag; m_fConstBitOpTree = flag; diff --git a/src/V3Options.h b/src/V3Options.h index 56ba51f79..0dca257d7 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -392,7 +392,8 @@ private: // MEMBERS (optimizations) bool m_fAcycSimp; // main switch: -fno-acyc-simp: acyclic pre-optimizations bool m_fAssemble; // main switch: -fno-assemble: assign assemble - bool m_fCase; // main switch: -fno-case: case tree conversion + bool m_fCaseTable; // main switch: -fno-case-table: case table conversion + bool m_fCaseTree; // main switch: -fno-case-tree: case tree conversion bool m_fCombine; // main switch: -fno-combine: common icode packing bool m_fConst; // main switch: -fno-const: constant folding bool m_fConstBeforeDfg = true; // main switch: -fno-const-before-dfg for testing only! @@ -725,7 +726,8 @@ public: // ACCESSORS (optimization options) bool fAcycSimp() const { return m_fAcycSimp; } bool fAssemble() const { return m_fAssemble; } - bool fCase() const { return m_fCase; } + bool fCaseTable() const { return m_fCaseTable; } + bool fCaseTree() const { return m_fCaseTree; } bool fCombine() const { return m_fCombine; } bool fConst() const { return m_fConst; } bool fConstBeforeDfg() const { return m_fConstBeforeDfg; } diff --git a/test_regress/t/t_case_huge.py b/test_regress/t/t_case_huge.py index 0ac31a2f8..57891d35b 100755 --- a/test_regress/t/t_case_huge.py +++ b/test_regress/t/t_case_huge.py @@ -16,12 +16,10 @@ test.compile(verilator_flags2=["--stats", "-fno-dfg"]) test.execute() -if test.vlt: - test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 11) - test.file_grep(test.stats, r'Optimizations, Combined CFuncs\s+(\d+)', 8) - test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 10) -elif test.vltmt: - test.file_grep(test.stats, r'Optimizations, Combined CFuncs\s+(\d+)', 9) - test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 10) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 8) +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 3) +test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Combined CFuncs\s+(\d+)', 9 if test.vltmt else 8) test.passes() diff --git a/test_regress/t/t_case_huge_nocase.py b/test_regress/t/t_case_huge_nocase.py index 2b3aad742..011bbdc67 100755 --- a/test_regress/t/t_case_huge_nocase.py +++ b/test_regress/t/t_case_huge_nocase.py @@ -16,6 +16,8 @@ test.compile(verilator_flags2=["--stats -fno-case"]) test.execute() +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 0) test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_case_huge_nocase_tree.py b/test_regress/t/t_case_huge_nocase_tree.py new file mode 100755 index 000000000..2083db119 --- /dev/null +++ b/test_regress/t/t_case_huge_nocase_tree.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator_st') +test.top_filename = 't/t_case_huge.v' + +test.compile(verilator_flags2=["--stats -fno-case-tree"]) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 8) +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases parallelized\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_table_normal.py b/test_regress/t/t_case_table_normal.py new file mode 100755 index 000000000..25746e901 --- /dev/null +++ b/test_regress/t/t_case_table_normal.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 8) +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_table_normal.v b/test_regress/t/t_case_table_normal.v new file mode 100644 index 000000000..26e32fa81 --- /dev/null +++ b/test_regress/t/t_case_table_normal.v @@ -0,0 +1,273 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 +// +// Case statements that become a "normal" (constant-pool) lookup table, followed by +// cases that must not be converted to one. Each output is compared against an +// equivalent reference computed without a case statement, so the reference itself is +// never tabled. Selectors are wide enough, with enough distinct values, that the +// branch lowering they replace is deep enough to make a table worthwhile. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Accept A: single output, blocking assignment, all selector values covered. + logic [15:0] accept_a_out, accept_a_ref; + always_comb + case (cyc[3:0]) + 4'd0: accept_a_out = 16'h1111; + 4'd1: accept_a_out = 16'h2222; + 4'd2: accept_a_out = 16'h4444; + 4'd3: accept_a_out = 16'h8888; + default: accept_a_out = 16'h0f0f; + endcase + assign accept_a_ref = (cyc[3:0] == 4'd0) ? 16'h1111 + : (cyc[3:0] == 4'd1) ? 16'h2222 + : (cyc[3:0] == 4'd2) ? 16'h4444 + : (cyc[3:0] == 4'd3) ? 16'h8888 : 16'h0f0f; + + // Accept B: single output, non-blocking assignment, with a default value set before + // the case and not all selector values covered. + logic [15:0] accept_b_out, accept_b_ref; + // verilator lint_off CASEINCOMPLETE + always_ff @(posedge clk) begin + accept_b_out <= 16'hffff; + case (cyc[3:0]) + 4'd0: accept_b_out <= 16'h0001; + 4'd1: accept_b_out <= 16'h0002; + 4'd2: accept_b_out <= 16'h0004; + 4'd3: accept_b_out <= 16'h0008; + endcase + end + // verilator lint_on CASEINCOMPLETE + always_ff @(posedge clk) + accept_b_ref <= (cyc[3:0] == 4'd0) ? 16'h0001 + : (cyc[3:0] == 4'd1) ? 16'h0002 + : (cyc[3:0] == 4'd2) ? 16'h0004 + : (cyc[3:0] == 4'd3) ? 16'h0008 : 16'hffff; + + // Accept C: three outputs, blocking assignment, with a default branch. + logic [11:0] accept_c_out_0, accept_c_ref_0; + logic [11:0] accept_c_out_1, accept_c_ref_1; + logic [11:0] accept_c_out_2, accept_c_ref_2; + always_comb + case (cyc[3:0]) + 4'd0: begin accept_c_out_0 = 12'h001; accept_c_out_1 = 12'h010; accept_c_out_2 = 12'h100; end + 4'd1: begin accept_c_out_0 = 12'h002; accept_c_out_1 = 12'h020; accept_c_out_2 = 12'h200; end + 4'd2: begin accept_c_out_0 = 12'h004; accept_c_out_1 = 12'h040; accept_c_out_2 = 12'h400; end + 4'd3: begin accept_c_out_0 = 12'h008; accept_c_out_1 = 12'h080; accept_c_out_2 = 12'h800; end + default: begin accept_c_out_0 = 12'h000; accept_c_out_1 = 12'h0ff; accept_c_out_2 = 12'hfff; end + endcase + assign accept_c_ref_0 = (cyc[3:0] == 4'd0) ? 12'h001 : (cyc[3:0] == 4'd1) ? 12'h002 + : (cyc[3:0] == 4'd2) ? 12'h004 : (cyc[3:0] == 4'd3) ? 12'h008 : 12'h000; + assign accept_c_ref_1 = (cyc[3:0] == 4'd0) ? 12'h010 : (cyc[3:0] == 4'd1) ? 12'h020 + : (cyc[3:0] == 4'd2) ? 12'h040 : (cyc[3:0] == 4'd3) ? 12'h080 : 12'h0ff; + assign accept_c_ref_2 = (cyc[3:0] == 4'd0) ? 12'h100 : (cyc[3:0] == 4'd1) ? 12'h200 + : (cyc[3:0] == 4'd2) ? 12'h400 : (cyc[3:0] == 4'd3) ? 12'h800 : 12'hfff; + + // Accept D: two outputs, non-blocking assignment, empty default branch, with default + // values set before the case. + logic [15:0] accept_d_out_0, accept_d_ref_0; + logic [15:0] accept_d_out_1, accept_d_ref_1; + always_ff @(posedge clk) begin + accept_d_out_0 <= 16'h0000; + accept_d_out_1 <= 16'hffff; + case (cyc[3:0]) + 4'd0: begin accept_d_out_0 <= 16'h0001; accept_d_out_1 <= 16'h0010; end + 4'd1: begin accept_d_out_0 <= 16'h0002; accept_d_out_1 <= 16'h0020; end + 4'd2: begin accept_d_out_0 <= 16'h0004; accept_d_out_1 <= 16'h0040; end + 4'd3: begin accept_d_out_0 <= 16'h0008; accept_d_out_1 <= 16'h0080; end + default: begin end + endcase + end + always_ff @(posedge clk) begin + accept_d_ref_0 <= (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002 + : (cyc[3:0] == 4'd2) ? 16'h0004 : (cyc[3:0] == 4'd3) ? 16'h0008 : 16'h0000; + accept_d_ref_1 <= (cyc[3:0] == 4'd0) ? 16'h0010 : (cyc[3:0] == 4'd1) ? 16'h0020 + : (cyc[3:0] == 4'd2) ? 16'h0040 : (cyc[3:0] == 4'd3) ? 16'h0080 : 16'hffff; + end + + // Accept E: casez with don't-care bits. + logic [15:0] accept_e_out, accept_e_ref; + always_comb + casez (cyc[3:0]) + 4'b00??: accept_e_out = 16'haaaa; + 4'b01??: accept_e_out = 16'hbbbb; + 4'b10??: accept_e_out = 16'hcccc; + 4'b11??: accept_e_out = 16'hdddd; + endcase + assign accept_e_ref = (cyc[3:2] == 2'd0) ? 16'haaaa : (cyc[3:2] == 2'd1) ? 16'hbbbb + : (cyc[3:2] == 2'd2) ? 16'hcccc : 16'hdddd; + + // Accept F: an item that can never match, and an item listing multiple values. + logic [15:0] accept_f_out, accept_f_ref; + // verilator lint_off CASEWITHX + always_comb + casez (cyc[3:0]) + 4'bxxx0: accept_f_out = 16'h0000; // X can never match in 2-state + 4'b0001, 4'b0011, 4'b0101: accept_f_out = 16'h5555; // lists three values + default: accept_f_out = 16'h9999; + endcase + // verilator lint_on CASEWITHX + assign accept_f_ref = (cyc[3:0] == 4'd1 || cyc[3:0] == 4'd3 || cyc[3:0] == 4'd5) + ? 16'h5555 : 16'h9999; + + // Accept G: items assign different subsets of two outputs, with default values (and an + // unrelated output) set before the case. + logic [15:0] accept_g_out_0, accept_g_ref_0; + logic [15:0] accept_g_out_1, accept_g_ref_1; + logic [15:0] accept_g_out_2, accept_g_ref_2; + always_comb begin + accept_g_out_0 = 16'h0000; + accept_g_out_1 = 16'hffff; + accept_g_out_2 = 16'h3333; // not assigned in the case + case (cyc[3:0]) + 4'd0: accept_g_out_0 = 16'h0001; + 4'd1: accept_g_out_1 = 16'h0002; + 4'd2: begin accept_g_out_0 = 16'h0004; accept_g_out_1 = 16'h0008; end + 4'd3: accept_g_out_0 = 16'h0010; + default: ; + endcase + end + assign accept_g_ref_0 = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd2) ? 16'h0004 + : (cyc[3:0] == 4'd3) ? 16'h0010 : 16'h0000; + assign accept_g_ref_1 = (cyc[3:0] == 4'd1) ? 16'h0002 : (cyc[3:0] == 4'd2) ? 16'h0008 : 16'hffff; + assign accept_g_ref_2 = 16'h3333; + + // Accept H: unique0 enum case; the selector may hold an out-of-range value. + typedef enum logic [3:0] {NE0, NE1, NE2, NE3, NE4} ne_t; + ne_t accept_h_in; + assign accept_h_in = ne_t'(cyc[3:0]); + logic [15:0] accept_h_out, accept_h_ref; + always_comb begin + accept_h_out = 16'hffff; + unique0 case (accept_h_in) + NE0: accept_h_out = 16'h0001; + NE1: accept_h_out = 16'h0002; + NE2: accept_h_out = 16'h0003; + NE3: accept_h_out = 16'h0004; + NE4: accept_h_out = 16'h0005; + endcase + end + assign accept_h_ref = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002 + : (cyc[3:0] == 4'd2) ? 16'h0003 : (cyc[3:0] == 4'd3) ? 16'h0004 + : (cyc[3:0] == 4'd4) ? 16'h0005 : 16'hffff; + + // The cases below are intentionally NOT converted to a lookup table. + + // Reject A: too few distinct values, so the branch lowering is cheaper than a load. + logic [15:0] reject_a_out, reject_a_ref; + always_comb + case (cyc[3:0]) + 4'd0: reject_a_out = 16'h0001; + 4'd1: reject_a_out = 16'h0002; + default: reject_a_out = 16'h00ff; + endcase + assign reject_a_ref = (cyc[3:0] == 4'd0) ? 16'h0001 : (cyc[3:0] == 4'd1) ? 16'h0002 : 16'h00ff; + + // Reject B: a one-bit selector, too shallow to be worth a load. + logic [19:0] reject_b_out, reject_b_ref; + always_comb + case (cyc[0]) + 1'b0: reject_b_out = 20'h00001; + 1'b1: reject_b_out = 20'h00002; + default: reject_b_out = 20'h00000; + endcase + assign reject_b_ref = cyc[0] ? 20'h00002 : 20'h00001; + + // Reject C: a 12-bit selector, too wide to table. + logic [15:0] reject_c_out, reject_c_ref; + always_comb + case (cyc[11:0]) + 12'd0: reject_c_out = 16'h0001; + 12'd1: reject_c_out = 16'h0002; + 12'd2: reject_c_out = 16'h0004; + default: reject_c_out = 16'h0000; + endcase + assign reject_c_ref = (cyc[11:0] == 12'd0) ? 16'h0001 + : (cyc[11:0] == 12'd1) ? 16'h0002 + : (cyc[11:0] == 12'd2) ? 16'h0004 : 16'h0000; + + // Reject D: a 17-bit selector, too wide to table. + logic [16:0] reject_d_in; + assign reject_d_in = cyc[16:0]; + logic [15:0] reject_d_out, reject_d_ref; + // verilator lint_off CASEINCOMPLETE + always_comb begin + reject_d_out = 16'hbeef; + case (reject_d_in) + 17'd0: reject_d_out = 16'h0001; + 17'd1: reject_d_out = 16'h0002; + 17'd2: reject_d_out = 16'h0004; + endcase + end + // verilator lint_on CASEINCOMPLETE + assign reject_d_ref = (reject_d_in == 17'd0) ? 16'h0001 + : (reject_d_in == 17'd1) ? 16'h0002 + : (reject_d_in == 17'd2) ? 16'h0004 : 16'hbeef; + + // Reject E: a whole output and a sub-range of it assigned in different items. + logic [7:0] reject_e_out, reject_e_ref; + always_comb begin + reject_e_out = 8'h00; + reject_e_out[3:0] = 4'h0; + case (cyc[1:0]) + 2'b00: reject_e_out = 8'haa; // assigns the whole output + 2'b01: reject_e_out[3:0] = 4'h5; // assigns a sub-range of the same output + default: ; + endcase + end + assign reject_e_ref = (cyc[1:0] == 2'd0) ? 8'haa : (cyc[1:0] == 2'd1) ? 8'h05 : 8'h00; + + // Reject F: a sub-range's default value is overwritten by a later whole-output default + // before the case, so the sub-range's pre-case value is set elsewhere. + logic [31:0] reject_f_out, reject_f_ref; + always_comb begin + reject_f_out[15:0] = 16'h0005; // farther default for the sub-range + reject_f_out = 32'h0; // closer whole-output default overwrites the sub-range to 0 + case (cyc[1:0]) + 2'b00: reject_f_out[15:0] = 16'habcd; // only the sub-range is assigned in the case + default: ; + endcase + end + assign reject_f_ref = (cyc[1:0] == 2'd0) ? 32'h0000abcd : 32'h00000000; + + // Test driver/checker + always @(posedge clk) begin + `checkh(accept_a_out, accept_a_ref); + `checkh(accept_b_out, accept_b_ref); + `checkh(accept_c_out_0, accept_c_ref_0); + `checkh(accept_c_out_1, accept_c_ref_1); + `checkh(accept_c_out_2, accept_c_ref_2); + `checkh(accept_d_out_0, accept_d_ref_0); + `checkh(accept_d_out_1, accept_d_ref_1); + `checkh(accept_e_out, accept_e_ref); + `checkh(accept_f_out, accept_f_ref); + `checkh(accept_g_out_0, accept_g_ref_0); + `checkh(accept_g_out_1, accept_g_ref_1); + `checkh(accept_g_out_2, accept_g_ref_2); + `checkh(accept_h_out, accept_h_ref); + `checkh(reject_a_out, reject_a_ref); + `checkh(reject_b_out, reject_b_ref); + `checkh(reject_c_out, reject_c_ref); + `checkh(reject_d_out, reject_d_ref); + `checkh(reject_e_out, reject_e_ref); + `checkh(reject_f_out, reject_f_ref); + + cyc <= cyc + 32'd1; + if (cyc == 32'd32) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_case_table_normal_off.py b/test_regress/t/t_case_table_normal_off.py new file mode 100755 index 000000000..7497fbc92 --- /dev/null +++ b/test_regress/t/t_case_table_normal_off.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_case_table_normal.v" + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-case-table']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_case_table_tiny.py b/test_regress/t/t_case_table_tiny.py new file mode 100755 index 000000000..55d103058 --- /dev/null +++ b/test_regress/t/t_case_table_tiny.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 11) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_case_table_tiny.v b/test_regress/t/t_case_table_tiny.v new file mode 100644 index 000000000..dd3c6fb5b --- /dev/null +++ b/test_regress/t/t_case_table_tiny.v @@ -0,0 +1,369 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 +// +// Case statements that become a "tiny" lookup table, followed by cases that must +// not be converted to one. Each output is compared against an equivalent reference +// computed without a case statement, so the reference itself is never tabled. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Accept A: single output, blocking assignment, all selector values covered. + wire [2:0] accept_a_in = cyc[2:0]; + logic [3:0] accept_a_out, accept_a_ref; + always_comb + case (accept_a_in) + 3'd0: accept_a_out = 4'd3; + 3'd1: accept_a_out = 4'd4; + 3'd2: accept_a_out = 4'd5; + 3'd3: accept_a_out = 4'd6; + 3'd4: accept_a_out = 4'd7; + 3'd5: accept_a_out = 4'd8; + 3'd6: accept_a_out = 4'd9; + 3'd7: accept_a_out = 4'd10; + endcase + assign accept_a_ref = 4'd3 + {1'b0, accept_a_in}; + + // Accept B: single output, non-blocking assignment, with a default value set before + // the case and not all selector values covered. + logic [3:0] accept_b_out, accept_b_ref; + // verilator lint_off CASEINCOMPLETE + always_ff @(posedge clk) begin + accept_b_out <= 4'hf; + case (cyc[1:0]) + 2'b00: accept_b_out <= 4'h1; + 2'b01: accept_b_out <= 4'h2; + endcase + end + // verilator lint_on CASEINCOMPLETE + always_ff @(posedge clk) + accept_b_ref <= (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'hf; + + // Accept C: two outputs of different widths, blocking assignment, with a default branch. + logic [2:0] accept_c_out_0, accept_c_ref_0; + logic [3:0] accept_c_out_1, accept_c_ref_1; + always_comb + case (cyc[1:0]) + 2'b00: begin accept_c_out_0 = 3'd1; accept_c_out_1 = 4'd6; end + 2'b01: begin accept_c_out_0 = 3'd2; accept_c_out_1 = 4'd5; end + default: begin accept_c_out_0 = 3'd0; accept_c_out_1 = 4'd7; end + endcase + assign accept_c_ref_0 = (cyc[1:0] == 2'b00) ? 3'd1 : (cyc[1:0] == 2'b01) ? 3'd2 : 3'd0; + assign accept_c_ref_1 = (cyc[1:0] == 2'b00) ? 4'd6 : (cyc[1:0] == 2'b01) ? 4'd5 : 4'd7; + + // Accept D: two outputs, non-blocking assignment, empty default branch, with default + // values set before the case. + logic [2:0] accept_d_out_0, accept_d_ref_0; + logic [2:0] accept_d_out_1, accept_d_ref_1; + always_ff @(posedge clk) begin + accept_d_out_0 <= 3'd0; + accept_d_out_1 <= 3'd7; + case (cyc[1:0]) + 2'b00: begin accept_d_out_0 <= 3'd1; accept_d_out_1 <= 3'd6; end + 2'b01: begin accept_d_out_0 <= 3'd2; accept_d_out_1 <= 3'd5; end + default: begin end + endcase + end + always_ff @(posedge clk) begin + accept_d_ref_0 <= (cyc[1:0] == 2'b00) ? 3'd1 : (cyc[1:0] == 2'b01) ? 3'd2 : 3'd0; + accept_d_ref_1 <= (cyc[1:0] == 2'b00) ? 3'd6 : (cyc[1:0] == 2'b01) ? 3'd5 : 3'd7; + end + + // Accept E: casez with a don't-care bit. + logic [3:0] accept_e_out, accept_e_ref; + always_comb + casez (cyc[1:0]) + 2'b1?: accept_e_out = 4'ha; + 2'b0?: accept_e_out = 4'hb; + endcase + assign accept_e_ref = cyc[1] ? 4'ha : 4'hb; + + // Accept F: an item that can never match, and an item listing multiple values. + logic [3:0] accept_f_out, accept_f_ref; + // verilator lint_off CASEWITHX + always_comb + casez (cyc[1:0]) + 2'bx0: accept_f_out = 4'h0; // X can never match in 2-state + 2'b01, 2'b11: accept_f_out = 4'h5; // lists two values + default: accept_f_out = 4'h9; + endcase + // verilator lint_on CASEWITHX + assign accept_f_ref = (cyc[1:0] == 2'b01 || cyc[1:0] == 2'b11) ? 4'h5 : 4'h9; + + // Accept G: items assign different subsets of two outputs, with default values (and an + // unrelated output) set before the case. + logic [3:0] accept_g_out_0, accept_g_ref_0; + logic [3:0] accept_g_out_1, accept_g_ref_1; + logic [3:0] accept_g_out_2, accept_g_ref_2; + // verilator lint_off CASEINCOMPLETE + always_comb begin + accept_g_out_0 = 4'h0; + accept_g_out_1 = 4'hf; + accept_g_out_2 = 4'h3; // not assigned in the case + case (cyc[1:0]) + 2'b00: accept_g_out_0 = 4'h1; + 2'b01: accept_g_out_1 = 4'h2; + endcase + end + // verilator lint_on CASEINCOMPLETE + assign accept_g_ref_0 = (cyc[1:0] == 2'b00) ? 4'h1 : 4'h0; + assign accept_g_ref_1 = (cyc[1:0] == 2'b01) ? 4'h2 : 4'hf; + assign accept_g_ref_2 = 4'h3; + + // Accept H: single output, non-blocking assignment, all selector values covered. + logic [3:0] accept_h_out, accept_h_ref; + always_ff @(posedge clk) + case (cyc[1:0]) + 2'b00: accept_h_out <= 4'h1; + 2'b01: accept_h_out <= 4'h2; + 2'b10: accept_h_out <= 4'h4; + 2'b11: accept_h_out <= 4'h8; + endcase + always_ff @(posedge clk) + accept_h_ref <= 4'h1 << cyc[1:0]; + + // Accept I: unique0 enum case; the selector may hold an out-of-range value. + typedef enum logic [1:0] {E0, E1, E2} e_t; + e_t accept_i_in; + assign accept_i_in = e_t'(cyc[1:0]); + logic [3:0] accept_i_out, accept_i_ref; + always_comb begin + accept_i_out = 4'hf; + unique0 case (accept_i_in) + E0: accept_i_out = 4'h1; + E1: accept_i_out = 4'h2; + E2: accept_i_out = 4'h3; + endcase + end + assign accept_i_ref = (cyc[1:0] == 2'd0) ? 4'h1 + : (cyc[1:0] == 2'd1) ? 4'h2 + : (cyc[1:0] == 2'd2) ? 4'h3 : 4'hf; + + // Accept J: wide output, materialized as a normal (not tiny) lookup table. + logic [8:0] accept_j_out, accept_j_ref; + always_comb + case (cyc[3:0]) + 4'd0: accept_j_out = 9'h001; + 4'd1: accept_j_out = 9'h002; + 4'd2: accept_j_out = 9'h004; + 4'd3: accept_j_out = 9'h008; + default: accept_j_out = 9'h010; + endcase + assign accept_j_ref = (cyc[3:0] < 4'd4) ? (9'h1 << cyc[3:0]) : 9'h010; + + // Accept K: a non-constant assignment precedes the case. + logic [3:0] accept_k_out_0, accept_k_ref_0; + logic [3:0] accept_k_out_1, accept_k_ref_1; + always_comb begin + accept_k_out_1 = cyc[3:0] ^ 4'ha; // non-constant value + case (cyc[1:0]) + 2'b00: accept_k_out_0 = 4'h1; + 2'b01: accept_k_out_0 = 4'h2; + 2'b10: accept_k_out_0 = 4'h4; + 2'b11: accept_k_out_0 = 4'h8; + endcase + end + assign accept_k_ref_0 = 4'h1 << cyc[1:0]; + assign accept_k_ref_1 = cyc[3:0] ^ 4'ha; + + // Accept L: the same output is given a default value twice before the case. + logic [3:0] accept_l_out, accept_l_ref; + // verilator lint_off CASEINCOMPLETE + always_comb begin + accept_l_out = 4'h1; + accept_l_out = 4'h6; // assigned a second time before the case + case (cyc[1:0]) + 2'b00: accept_l_out = 4'h2; + 2'b01: accept_l_out = 4'h3; + endcase + end + // verilator lint_on CASEINCOMPLETE + assign accept_l_ref = (cyc[1:0] == 2'd0) ? 4'h2 : (cyc[1:0] == 2'd1) ? 4'h3 : 4'h6; + + // The cases below are intentionally NOT converted to a lookup table. + + // Reject A: an item whose body is not a simple assignment. + logic [3:0] reject_a_out, reject_a_ref; + always_comb begin + reject_a_out = 4'h0; + case (cyc[1:0]) + 2'b00: reject_a_out = 4'h1; + 2'b01: if (cyc[0]) reject_a_out = 4'h2; // not a simple assignment + default: reject_a_out = 4'h3; + endcase + end + assign reject_a_ref = (cyc[1:0] == 2'd0) ? 4'h1 : (cyc[1:0] == 2'd1) ? 4'h2 : 4'h3; + + // Reject B: an item assigns through a variable bit-select (the index is read). + logic [3:0] reject_b_out, reject_b_ref; + always_comb begin + reject_b_out = 4'h0; + case (cyc[1:0]) + 2'b00: reject_b_out[cyc[1:0]] = 1'b1; + default: reject_b_out = 4'h5; + endcase + end + assign reject_b_ref = (cyc[1:0] == 2'd0) ? 4'h1 : 4'h5; + + // Reject C: an item assigns the same output twice. + logic [3:0] reject_c_out, reject_c_ref; + always_comb begin + reject_c_out = 4'h0; + case (cyc[1:0]) + 2'b00: begin reject_c_out = 4'h1; reject_c_out = 4'h2; end + default: reject_c_out = 4'h3; + endcase + end + assign reject_c_ref = (cyc[1:0] == 2'd0) ? 4'h2 : 4'h3; + + // Reject D: a non-constant case-item value. + logic [1:0] reject_d_in; + assign reject_d_in = cyc[1:0]; + logic [3:0] reject_d_out, reject_d_ref; + always_comb begin + reject_d_out = 4'h0; + case (cyc[1:0]) + reject_d_in: reject_d_out = 4'h7; // non-constant item value + default: reject_d_out = 4'h9; + endcase + end + assign reject_d_ref = 4'h7; // reject_d_in always equals the case expression + + // Reject E: all items are empty. + logic [3:0] reject_e_out, reject_e_ref; + always_comb begin + reject_e_out = 4'h7; + case (cyc[2:0]) + 3'd0: ; + 3'd1: ; + 3'd2: ; + 3'd3: ; + 3'd4: ; + 3'd5: ; + 3'd6: ; + 3'd7: ; + endcase + end + assign reject_e_ref = 4'h7; + + // Reject F: an item uses a delayed (intra-assignment) assignment. + logic [3:0] reject_f_out, reject_f_ref; + always_ff @(posedge clk) + case (cyc[1:0]) + 2'b00: reject_f_out <= #1 4'h1; // delayed assignment + default: reject_f_out <= 4'h2; + endcase + always_ff @(posedge clk) + if (cyc[1:0] == 2'b00) reject_f_ref <= #1 4'h1; + else reject_f_ref <= 4'h2; + + // Reject G: an output assigned with both blocking and non-blocking assignments. The three + // variants exercise the distinct ways the assignment kinds conflict. The deliberate + // mixing warnings are waived. + // verilator lint_off BLKANDNBLK + // verilator lint_off COMBDLY + // verilator lint_off CASEINCOMPLETE + // Variant 0: an item mixes a blocking and a non-blocking assignment to the same output. + logic [3:0] reject_g_out_0, reject_g_ref_0; + always_comb + case (cyc[1:0]) + 2'b00: reject_g_out_0 = 4'h1; + 2'b01: reject_g_out_0 <= 4'h2; + default: reject_g_out_0 = 4'h3; + endcase + assign reject_g_ref_0 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h3; + // Variant 1: blocking items, but the pre-case default is a non-blocking assignment. + logic [3:0] reject_g_out_1, reject_g_ref_1; + always_comb begin + reject_g_out_1 <= 4'h0; + case (cyc[1:0]) + 2'b00: reject_g_out_1 = 4'h1; + 2'b01: reject_g_out_1 = 4'h2; + endcase + end + assign reject_g_ref_1 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h0; + // Variant 2: non-blocking items, but the pre-case default is a blocking assignment. + logic [3:0] reject_g_out_2, reject_g_ref_2; + always_comb begin + reject_g_out_2 = 4'h0; + case (cyc[1:0]) + 2'b00: reject_g_out_2 <= 4'h1; + 2'b01: reject_g_out_2 <= 4'h2; + endcase + end + assign reject_g_ref_2 = (cyc[1:0] == 2'b00) ? 4'h1 : (cyc[1:0] == 2'b01) ? 4'h2 : 4'h0; + // verilator lint_on CASEINCOMPLETE + // verilator lint_on COMBDLY + // verilator lint_on BLKANDNBLK + + // Reject H: items assign a real (non-packed) output. + real reject_h_out, reject_h_ref; + always_comb + case (cyc[1:0]) + 2'b00: reject_h_out = 1.5; + 2'b01: reject_h_out = 2.5; + default: reject_h_out = 9.0; + endcase + always_comb reject_h_ref = (cyc[1:0] == 2'b00) ? 1.5 : (cyc[1:0] == 2'b01) ? 2.5 : 9.0; + + // Reject I: items assign a string (non-packed) output. + string reject_i_out, reject_i_ref; + always_comb + case (cyc[1:0]) + 2'b00: reject_i_out = "zero"; + 2'b01: reject_i_out = "one"; + default: reject_i_out = "other"; + endcase + always_comb reject_i_ref = (cyc[1:0] == 2'b00) ? "zero" : (cyc[1:0] == 2'b01) ? "one" : "other"; + + // Test driver/checker + always @(posedge clk) begin + `checkh(accept_a_out, accept_a_ref); + `checkh(accept_b_out, accept_b_ref); + `checkh(accept_c_out_0, accept_c_ref_0); + `checkh(accept_c_out_1, accept_c_ref_1); + `checkh(accept_d_out_0, accept_d_ref_0); + `checkh(accept_d_out_1, accept_d_ref_1); + `checkh(accept_e_out, accept_e_ref); + `checkh(accept_f_out, accept_f_ref); + `checkh(accept_g_out_0, accept_g_ref_0); + `checkh(accept_g_out_1, accept_g_ref_1); + `checkh(accept_g_out_2, accept_g_ref_2); + `checkh(accept_h_out, accept_h_ref); + `checkh(accept_i_out, accept_i_ref); + `checkh(accept_j_out, accept_j_ref); + `checkh(accept_k_out_0, accept_k_ref_0); + `checkh(accept_k_out_1, accept_k_ref_1); + `checkh(accept_l_out, accept_l_ref); + `checkh(reject_a_out, reject_a_ref); + `checkh(reject_b_out, reject_b_ref); + `checkh(reject_c_out, reject_c_ref); + `checkh(reject_d_out, reject_d_ref); + `checkh(reject_e_out, reject_e_ref); + `checkh(reject_f_out, reject_f_ref); + `checkh(reject_g_out_0, reject_g_ref_0); + `checkh(reject_g_out_1, reject_g_ref_1); + `checkh(reject_g_out_2, reject_g_ref_2); + `checkr(reject_h_out, reject_h_ref); + `checks(reject_i_out, reject_i_ref); + + cyc <= cyc + 32'd1; + if (cyc == 32'd32) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_case_table_tiny_off.py b/test_regress/t/t_case_table_tiny_off.py new file mode 100755 index 000000000..8890dcd07 --- /dev/null +++ b/test_regress/t/t_case_table_tiny_off.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_case_table_tiny.v" + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-case-table']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases table tiny\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_dfg_constpool_unused.py b/test_regress/t/t_dfg_constpool_unused.py new file mode 100755 index 000000000..d8194a13e --- /dev/null +++ b/test_regress/t/t_dfg_constpool_unused.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.file_grep(test.stats, r'Optimizations, Cases table normal\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, DFG, Peephole, remove var\s+(\d+)', 2) +test.file_grep_not(test.stats, r'ConstPool, Constants emitted') # Removed by V3Dead later + +test.passes() diff --git a/test_regress/t/t_dfg_constpool_unused.v b/test_regress/t/t_dfg_constpool_unused.v new file mode 100644 index 000000000..339346ffa --- /dev/null +++ b/test_regress/t/t_dfg_constpool_unused.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Converted to case table in const pool, but proven unused by Dfg + logic [15:0] out; + always_comb begin + case (cyc[3:0]) + 4'd0: out = 16'h1111; + 4'd1: out = 16'h2222; + 4'd2: out = 16'h4444; + 4'd3: out = 16'h8888; + default: out = 16'h0f0f; + endcase + end + + // Complicated way to write constant 0 that only Dfg can decipher + wire [63:0] convoluted_zero = (({64{cyc[0]}} & ~{64{cyc[0]}})); + + wire logic [15:0] zero = &convoluted_zero ? out : 16'd0; + + // Test driver/checker + always @(posedge clk) begin + `checkh(zero, 16'd0); + cyc <= cyc + 32'd1; + if (cyc == 32'd32) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_table_enum.py b/test_regress/t/t_opt_table_enum.py index 5908d7cde..561f0c766 100755 --- a/test_regress/t/t_opt_table_enum.py +++ b/test_regress/t/t_opt_table_enum.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_packed_array.py b/test_regress/t/t_opt_table_packed_array.py index 5908d7cde..561f0c766 100755 --- a/test_regress/t/t_opt_table_packed_array.py +++ b/test_regress/t/t_opt_table_packed_array.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_real.py b/test_regress/t/t_opt_table_real.py index 5908d7cde..561f0c766 100755 --- a/test_regress/t/t_opt_table_real.py +++ b/test_regress/t/t_opt_table_real.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_same.py b/test_regress/t/t_opt_table_same.py index 2cee4586a..a51a48a73 100755 --- a/test_regress/t/t_opt_table_same.py +++ b/test_regress/t/t_opt_table_same.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 2) diff --git a/test_regress/t/t_opt_table_signed.py b/test_regress/t/t_opt_table_signed.py index 5908d7cde..561f0c766 100755 --- a/test_regress/t/t_opt_table_signed.py +++ b/test_regress/t/t_opt_table_signed.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_string.py b/test_regress/t/t_opt_table_string.py index 5908d7cde..561f0c766 100755 --- a/test_regress/t/t_opt_table_string.py +++ b/test_regress/t/t_opt_table_string.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_struct.py b/test_regress/t/t_opt_table_struct.py index 5908d7cde..561f0c766 100755 --- a/test_regress/t/t_opt_table_struct.py +++ b/test_regress/t/t_opt_table_struct.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) From 22b45f0fd7948af62f62923fe778d77185d8a093 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Thu, 18 Jun 2026 17:04:57 +0200 Subject: [PATCH 53/89] Fix randomize() with skipping derived pre/post_randomize (#7799) --- src/V3Randomize.cpp | 85 ++++++++++++++++- .../t/t_randomize_prepost_with_baseref.py | 21 +++++ .../t/t_randomize_prepost_with_baseref.v | 94 +++++++++++++++++++ 3 files changed, 197 insertions(+), 3 deletions(-) create mode 100755 test_regress/t/t_randomize_prepost_with_baseref.py create mode 100644 test_regress/t/t_randomize_prepost_with_baseref.v diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 911660103..52a750a6a 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -3105,6 +3105,8 @@ class RandomizeVisitor final : public VNVisitor { std::map m_staticConstraintModeVars; // Static constraint mode vars per class std::map m_staticRandModeVars; // Static rand mode vars per class + std::map> + m_prePostWrap; // Per-handle-type pre/post virtual wrapper presence // METHODS // Check if two nodes are semantically equivalent (not pointer equality): @@ -3797,12 +3799,75 @@ class RandomizeVisitor final : public VNVisitor { } return nullptr; } - void addPrePostCall(AstClass* const classp, AstFunc* const funcp, const string& name) { + void addPrePostCall(AstClass* const classp, AstNodeFTask* const funcp, const string& name) { if (AstTask* const userFuncp = findPrePostTask(classp, name)) { AstTaskRef* const callp = new AstTaskRef{userFuncp->fileline(), userFuncp}; funcp->addStmtsp(callp->makeStmt()); } } + // Per-class virtual wrapper that invokes the class's effective + // pre_randomize/post_randomize. IEEE 1800-2023 18.6.2: pre_randomize and + // post_randomize "appear to behave as virtual methods" because randomize() + // is virtual. The inline `randomize() with` path builds a non-virtual + // function on the static handle type, so it dispatches pre/post through + // this wrapper to reach the dynamic type's override. + AstTask* getCreatePrePostCallback(AstClass* const classp, const string& which) { + const string name = "__V" + which; + if (AstTask* const existingp = VN_CAST(m_memberMap.findMember(classp, name), Task)) { + return existingp; + } + AstTask* const taskp = new AstTask{classp->fileline(), name, nullptr}; + taskp->classMethod(true); + taskp->isVirtual(classp->isExtended()); + classp->addMembersp(taskp); + m_memberMap.insert(classp, taskp); + addPrePostCall(classp, taskp, which); + return taskp; + } + // Build the virtual pre/post wrappers across classp's whole hierarchy so a + // `randomize() with` through a base handle dispatches to a derived + // override. Returns whether a pre/post wrapper exists anywhere in the + // hierarchy (cached per static handle type). + std::pair buildPrePostVirtualWrappers(AstClass* const classp) { + const auto cachedIt = m_prePostWrap.find(classp); + if (cachedIt != m_prePostWrap.end()) return cachedIt->second; + std::vector hierp{classp}; + v3Global.rootp()->foreach([&](AstClass* subp) { + if (subp != classp && AstClass::isClassExtendedFrom(subp, classp)) + hierp.push_back(subp); + }); + bool hasPre = false; + bool hasPost = false; + for (AstClass* const cp : hierp) { + if (findPrePostTask(cp, "pre_randomize")) { + getCreatePrePostCallback(cp, "pre_randomize"); + hasPre = true; + } + if (findPrePostTask(cp, "post_randomize")) { + getCreatePrePostCallback(cp, "post_randomize"); + hasPost = true; + } + } + // Ensure the static handle type owns the slot whenever a subclass + // overrides, so the virtual call resolves on a base handle. + if (hasPre) getCreatePrePostCallback(classp, "pre_randomize"); + if (hasPost) getCreatePrePostCallback(classp, "post_randomize"); + const std::pair result{hasPre, hasPost}; + m_prePostWrap.emplace(classp, result); + return result; + } + void addVirtualPrePostCall(AstFunc* const randomizeFuncp, AstClass* const classp, + const string& which) { + FileLine* const fl = classp->fileline(); + AstTask* const wrapperp = getCreatePrePostCallback(classp, which); + AstClassRefDType* const refDTypep = new AstClassRefDType{fl, classp, nullptr}; + v3Global.rootp()->typeTablep()->addTypesp(refDTypep); + AstMethodCall* const callp + = new AstMethodCall{fl, new AstThisRef{fl, refDTypep}, wrapperp->name(), nullptr}; + callp->taskp(wrapperp); + callp->dtypeSetVoid(); + randomizeFuncp->addStmtsp(callp->makeStmt()); + } // Check if a class (including inherited members) has any rand class-type members bool classHasRandClassMembers(AstClass* classp) { return classp->existsMember([](const AstClass*, const AstVar* varp) { @@ -5228,7 +5293,17 @@ class RandomizeVisitor final : public VNVisitor { AstFunc* const randomizeFuncp = V3Randomize::newRandomizeFunc( m_memberMap, classp, m_inlineUniqueNames.get(nodep), false); - addPrePostCall(classp, randomizeFuncp, "pre_randomize"); + // A base-handle `randomize() with` must still reach a derived + // pre/post_randomize. Route them through per-class virtual wrappers + // when the static handle type participates in inheritance. + const std::pair prePostWrap = classp->isExtended() + ? buildPrePostVirtualWrappers(classp) + : std::pair{false, false}; + if (prePostWrap.first) { + addVirtualPrePostCall(randomizeFuncp, classp, "pre_randomize"); + } else { + addPrePostCall(classp, randomizeFuncp, "pre_randomize"); + } // Call nested pre_randomize on rand class-type members (IEEE 18.4.1) if (classHasRandClassMembers(classp)) { @@ -5390,7 +5465,11 @@ class RandomizeVisitor final : public VNVisitor { randomizeFuncp->addStmtsp((new AstTaskRef{nodep->fileline(), postTaskp})->makeStmt()); } - addPrePostCall(classp, randomizeFuncp, "post_randomize"); + if (prePostWrap.second) { + addVirtualPrePostCall(randomizeFuncp, classp, "post_randomize"); + } else { + addPrePostCall(classp, randomizeFuncp, "post_randomize"); + } // Replace the node with a call to that function nodep->name(randomizeFuncp->name()); diff --git a/test_regress/t/t_randomize_prepost_with_baseref.py b/test_regress/t/t_randomize_prepost_with_baseref.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_prepost_with_baseref.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_randomize_prepost_with_baseref.v b/test_regress/t/t_randomize_prepost_with_baseref.v new file mode 100644 index 000000000..1bfe4ca64 --- /dev/null +++ b/test_regress/t/t_randomize_prepost_with_baseref.v @@ -0,0 +1,94 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +class Base; + rand bit [7:0] a; + bit [7:0] m_pre; + bit [7:0] m_post; +endclass + +class Derived extends Base; + function void pre_randomize; + `checkd(m_pre, 8'd0); + m_pre = 8'd10; + endfunction + function void post_randomize; + `checkd(m_pre, 8'd10); + m_post = a + 8'd1; + endfunction +endclass + +class Base2; + rand bit [7:0] b; + bit [7:0] bp; + bit [7:0] bq; + function void pre_randomize; + bp = 8'd1; + endfunction + function void post_randomize; + bq = b; + endfunction +endclass + +class Derived2 extends Base2; + bit [7:0] dp; + bit [7:0] dq; + function void pre_randomize; + dp = 8'd2; + super.pre_randomize(); + endfunction + function void post_randomize; + dq = b + 8'd1; + super.post_randomize(); + endfunction +endclass + +module t; + initial begin + Base b; + Derived d; + Base2 b2; + Derived2 d2; + int ok; + + // Plain randomize through a base handle already dispatches pre/post + d = new; + b = d; + ok = b.randomize(); + `checkd(ok, 1); + `checkd(d.m_pre, 8'd10); + `checkd(d.m_post, d.a + 8'd1); + + // randomize() with through a base handle whose static type lacks pre/post + d = new; + b = d; + ok = b.randomize() with {a == 8'h3c;}; + `checkd(ok, 1); + `checkd(b.a, 8'h3c); + `checkd(d.m_pre, 8'd10); + `checkd(d.m_post, 8'h3d); + + // randomize() with through a base handle that DOES define pre/post, + // overridden by the derived class with super chaining + d2 = new; + b2 = d2; + ok = b2.randomize() with {b == 8'h11;}; + `checkd(ok, 1); + `checkd(d2.b, 8'h11); + `checkd(d2.dp, 8'd2); + `checkd(d2.bp, 8'd1); + `checkd(d2.dq, 8'h12); + `checkd(d2.bq, 8'h11); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From a5a16cfbfd0f169929c0a5aafb716860aec197fb Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Thu, 18 Jun 2026 17:17:09 +0200 Subject: [PATCH 54/89] Support unbounded always [m:$] and strong s_always liveness (#7798) --- src/V3AssertNfa.cpp | 60 +++++++++++++++++++ src/V3Width.cpp | 40 +++++++------ test_regress/t/t_assert_always_unbounded.py | 18 ++++++ test_regress/t/t_assert_always_unbounded.v | 57 ++++++++++++++++++ .../t/t_assert_always_unbounded_bad.out | 14 +++++ .../t/t_assert_always_unbounded_bad.py | 16 +++++ .../t/t_assert_always_unbounded_bad.v | 20 +++++++ test_regress/t/t_prop_always.v | 8 --- test_regress/t/t_prop_always_bad.out | 20 +++---- test_regress/t/t_prop_always_unsup.out | 16 ++--- test_regress/t/t_prop_always_unsup.v | 3 - test_regress/t/t_prop_always_wide.v | 9 +-- test_regress/t/t_prop_s_always_liveness.out | 4 ++ test_regress/t/t_prop_s_always_liveness.py | 17 ++++++ test_regress/t/t_prop_s_always_liveness.v | 43 +++++++++++++ 15 files changed, 289 insertions(+), 56 deletions(-) create mode 100755 test_regress/t/t_assert_always_unbounded.py create mode 100644 test_regress/t/t_assert_always_unbounded.v create mode 100644 test_regress/t/t_assert_always_unbounded_bad.out create mode 100755 test_regress/t/t_assert_always_unbounded_bad.py create mode 100644 test_regress/t/t_assert_always_unbounded_bad.v create mode 100644 test_regress/t/t_prop_s_always_liveness.out create mode 100755 test_regress/t/t_prop_s_always_liveness.py create mode 100644 test_regress/t/t_prop_s_always_liveness.v diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index e302be9eb..222d88128 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -78,6 +78,10 @@ public: AstNodeExpr* m_andRhsCondp = nullptr; // OWNED; RHS final condition (may be nullptr) // Reject sink for SAnd rejectOnFail wiring; not a state-signal source bool m_isRejectSink = false; + // In-window vertex of a strong s_always[m:n]: if its state is still set at + // end-of-simulation the universal-quantifier window never completed, which is + // a liveness failure (IEEE 1800-2023 16.12.11 strong semantics). + bool m_strongPending = false; // CONSTRUCTORS explicit SvaStateVertex(V3Graph* graphp) @@ -206,6 +210,7 @@ class SvaNfaBuilder final { // (IEEE 1800-2023 16.12.14 outer-wraps-inner). std::vector m_outerAbortStack; bool m_inUnboundedScope = false; // Sticky: nodes created after inherit liveness + bool m_markStrongPending = false; // Mark new vertices as strong s_always in-window // IEEE 1800-2023 16.14.3 cover sequence: each end-of-match fires the action, // not just the first. Builder builds parallel-branch (no first-match-wins) // topology when true. Default false preserves cover_property semantics. @@ -348,6 +353,7 @@ class SvaNfaBuilder final { vtxp->m_throughoutConds.push_back(cp->cloneTreePure(false)); } if (m_inUnboundedScope) vtxp->m_isUnbounded = true; + if (m_markStrongPending) vtxp->m_strongPending = true; return vtxp; } @@ -673,10 +679,37 @@ class SvaNfaBuilder final { FileLine* const flp = nodep->fileline(); AstNodeExpr* const propp = nodep->propp(); const int lo = getConstInt(nodep->loBoundp()); + if (VN_IS(nodep->hiBoundp(), Unbounded)) { + // Weak always [lo:$]: unbounded upper bound (IEEE 1800-2023 16.12.11). + // p must hold at every clock tick at least lo cycles after the attempt + // start; those ticks are not required to exist, so there is no + // end-of-trace obligation (weak). The self-loop keeps the attempt live + // every cycle; each observed cycle is a safety obligation, so a false p + // rejects immediately. + UASSERT_OBJ(!nodep->isStrong() && lo >= 0, nodep, + "Unbounded always must be weak with non-negative lo (V3Width)"); + SvaStateVertex* const livep = addDelayChain(entryVtxp, lo, flp); + livep->m_isUnbounded = true; + guardedEdge(livep, livep, flp); // stay active every subsequent cycle + SvaStateVertex* const sinkp = m_graph.createStateVertex(); + sinkp->m_isRejectSink = true; + SvaTransEdge* const rejEdgep + = guardedLink(livep, sinkp, sampled(propp->cloneTreePure(false)), flp); + if (isTopLevelStep) rejEdgep->m_rejectOnFail = true; + return {livep, nullptr, {}}; + } const int hi = getConstInt(nodep->hiBoundp()); UASSERT_OBJ(lo >= 0 && hi >= lo, nodep, "PropAlways bounds invariant (V3Width)"); if (exceedsAssertUnrollLimit(nodep, hi - lo + 1)) return BuildResult::failWithError(); AstVar* const hoistVarp = tryHoistSampled(propp, flp, hi - lo + 1); + // Strong s_always[m:n]: mark every in-window registered vertex so an + // attempt still mid-window at end-of-simulation is reported as a liveness + // failure (IEEE strong: the n+1 ticks must exist). An attempt that has + // completed earlier in the trace has already cleared its state, so it is + // not flagged; an attempt whose final tick coincides with $finish is still + // flagged, matching the strong reference. Weak always[m:n] is not marked. + VL_RESTORER(m_markStrongPending); + m_markStrongPending = nodep->isStrong(); SvaStateVertex* currentp = addDelayChain(entryVtxp, lo, flp); for (int k = 0; k <= hi - lo; ++k) { if (k > 0) { @@ -1885,6 +1918,33 @@ public: flp, isCover, negated, matchCondp, sigs.terminalActivep, sigs.rejectBasep, sigs.throughoutRejectp, sigs.requiredStepRejectp, outMatchpp); + // Strong s_always[m:n] end-of-simulation liveness: if any in-window state + // is still set at $finish, the universal-quantifier window never completed + // (IEEE 1800-2023 16.12.11 strong semantics). Fire the assertion failure + // from a final block; V3Assert turns the DT_ERROR display into the standard + // "Assertion failed in %m" message. + AstNodeExpr* pendingp = nullptr; + for (int i = 0; i < N; ++i) { + if (!vtx[i]->m_strongPending || !vtx[i]->datap()->stateVarp) continue; + AstNodeExpr* const svp = new AstVarRef{flp, vtx[i]->datap()->stateVarp, VAccess::READ}; + if (!pendingp) { + pendingp = svp; + } else { + pendingp = new AstLogOr{flp, pendingp, svp}; + } + } + if (pendingp) { + AstCExpr* const assertOnp + = new AstCExpr{flp, AstCExpr::Pure{}, "vlSymsp->_vm_contextp__->assertOn()", 1}; + AstNodeExpr* const condp = new AstLogAnd{flp, assertOnp, pendingp}; + AstDisplay* const dispp + = new AstDisplay{flp, VDisplayType::DT_ERROR, "", nullptr, nullptr}; + dispp->fmtp()->timeunit(m_modp->timeunit()); + AstNodeStmt* const firep = dispp; + if (v3Global.opt.stopFail()) firep->addNext(new AstStop{flp, false}); + m_modp->addStmtsp(new AstFinal{flp, new AstIf{flp, condp, firep}}); + } + // Clear userp on every vertex before vertexData unique_ptrs are destroyed. for (int i = 0; i < N; ++i) vtx[i]->userp(nullptr); return resultp; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index b323c0b92..dbbea7022 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -1563,33 +1563,39 @@ class WidthVisitor final : public VNVisitor { } const bool loUnbounded = VN_IS(nodep->loBoundp(), Unbounded); const bool hiUnbounded = VN_IS(nodep->hiBoundp(), Unbounded); - if (loUnbounded || hiUnbounded) { - if (nodep->isStrong()) { - nodep->v3error("s_always range must be bounded (IEEE 1800-2023 16.12.11)"); - } else { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: unbounded always range (always [m:$])"); - } + // Strong always must be bounded (IEEE 1800-2023 16.12.11: "the range + // for a strong always shall be bounded"). Weak always [m:$] is legal: + // an unbounded upper bound imposes no end-of-trace obligation. + if (nodep->isStrong() && (loUnbounded || hiUnbounded)) { + AstNode* const boundp = loUnbounded ? nodep->loBoundp() : nodep->hiBoundp(); + boundp->v3error("s_always range must be bounded (IEEE 1800-2023 16.12.11)"); + nodep->dtypeSetBit(); + return; + } + if (loUnbounded) { + // Only the high bound may be $ (cycle_delay_const_range_expression). + nodep->loBoundp()->v3error("always range low bound must be a constant expression" + " (IEEE 1800-2023 16.12.11)"); nodep->dtypeSetBit(); return; } const AstConst* const loConstp = VN_CAST(nodep->loBoundp(), Const); const AstConst* const hiConstp = VN_CAST(nodep->hiBoundp(), Const); if (!loConstp) { - nodep->v3error("always range low bound must be a constant expression" - " (IEEE 1800-2023 16.12.11)"); + nodep->loBoundp()->v3error("always range low bound must be a constant expression" + " (IEEE 1800-2023 16.12.11)"); } - if (!hiConstp) { - nodep->v3error("always range high bound must be a constant expression" - " (IEEE 1800-2023 16.12.11)"); + if (!hiUnbounded && !hiConstp) { + nodep->hiBoundp()->v3error("always range high bound must be a constant expression" + " (IEEE 1800-2023 16.12.11)"); } if (loConstp && loConstp->toSInt() < 0) { - nodep->v3error("always range low bound must be non-negative" - " (IEEE 1800-2023 16.12.11)"); + nodep->loBoundp()->v3error("always range low bound must be non-negative" + " (IEEE 1800-2023 16.12.11)"); } - if (loConstp && hiConstp && hiConstp->toSInt() < loConstp->toSInt()) { - nodep->v3error("always range high bound must be >= low bound" - " (IEEE 1800-2023 16.12.11)"); + if (!hiUnbounded && loConstp && hiConstp && hiConstp->toSInt() < loConstp->toSInt()) { + nodep->hiBoundp()->v3error("always range high bound must be >= low bound" + " (IEEE 1800-2023 16.12.11)"); } bool hasPropertyOp = propp->isMultiCycleSva(); if (!hasPropertyOp) { diff --git a/test_regress/t/t_assert_always_unbounded.py b/test_regress/t/t_assert_always_unbounded.py new file mode 100755 index 000000000..2351d6963 --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_always_unbounded.v b/test_regress/t/t_assert_always_unbounded.v new file mode 100644 index 000000000..9f41a2627 --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + int cyc = 0; + logic a_high = 1'b1; + logic a_low = 1'b0; + logic a_drop = 1'b1; + + // Weak always [m:$] is a pure safety property: it has no end-of-trace + // obligation, so it can only fail (else fires), once per failing tick. + int high_fail_q[$]; + int low0_fail_q[$]; + int low2_fail_q[$]; + int drop_fail_q[$]; + + // Constant-true input: never fails at any tick. + assert property (@(posedge clk) always [2:$] a_high) else high_fail_q.push_back(cyc); + + // Constant-false input, m=0: fails at every observed tick. + assert property (@(posedge clk) always [0:$] a_low) else low0_fail_q.push_back(cyc); + + // Constant-false input, m=2: fails at every tick once the window is live. + assert property (@(posedge clk) always [2:$] a_low) else low2_fail_q.push_back(cyc); + + // a_drop is high then drops at cyc 5 and stays low: deterministic single + // transition, so Verilator and Questa agree on the failing ticks exactly. + assert property (@(posedge clk) always [2:$] a_drop) else drop_fail_q.push_back(cyc); + + always @(posedge clk) begin + 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 + // Questa is one lower (it does not fire the end-of-sim tick); see the sva + // lessons "multi-cycle end-of-simulation offset" note. + `checkd(high_fail_q.size(), 0); // Questa: 0 + `checkd(low0_fail_q.size(), 20); // Questa: 19 + `checkd(low2_fail_q.size(), 18); // Questa: 17 + `checkd(drop_fail_q[0], 5); // first fail tick: a_drop sampled low from cyc 5 + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_assert_always_unbounded_bad.out b/test_regress/t/t_assert_always_unbounded_bad.out new file mode 100644 index 000000000..155120065 --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded_bad.out @@ -0,0 +1,14 @@ +%Error: t/t_assert_always_unbounded_bad.v:13:35: s_always range must be bounded (IEEE 1800-2023 16.12.11) + : ... note: In instance 't' + 13 | assert property (@(posedge clk) s_always a); + | ^~~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_assert_always_unbounded_bad.v:14:47: s_always range must be bounded (IEEE 1800-2023 16.12.11) + : ... note: In instance 't' + 14 | assert property (@(posedge clk) s_always [2:$] a); + | ^ +%Error: t/t_assert_always_unbounded_bad.v:18:43: always range low bound must be a constant expression (IEEE 1800-2023 16.12.11) + : ... note: In instance 't' + 18 | assert property (@(posedge clk) always [$:5] a); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_assert_always_unbounded_bad.py b/test_regress/t/t_assert_always_unbounded_bad.py new file mode 100755 index 000000000..77a0ac64b --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(expect_filename=test.golden_filename, fails=True) + +test.passes() diff --git a/test_regress/t/t_assert_always_unbounded_bad.v b/test_regress/t/t_assert_always_unbounded_bad.v new file mode 100644 index 000000000..9103865ef --- /dev/null +++ b/test_regress/t/t_assert_always_unbounded_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +module t (input clk); + logic a; + + // A strong always must be bounded (IEEE 1800-2023 16.12.11): there is no bare + // s_always grammar production, and s_always [m:$] is the explicit "// Illegal" + // example (p5). Both forms are rejected. + assert property (@(posedge clk) s_always a); + assert property (@(posedge clk) s_always [2:$] a); + + // A weak always range may only place $ on the high bound; an unbounded low + // bound is not a legal cycle_delay_const_range_expression. + assert property (@(posedge clk) always [$:5] a); + +endmodule diff --git a/test_regress/t/t_prop_always.v b/test_regress/t/t_prop_always.v index 3f54b1023..52cfea02c 100644 --- a/test_regress/t/t_prop_always.v +++ b/test_regress/t/t_prop_always.v @@ -24,7 +24,6 @@ module t ( // For "always [m:n] P" the action runs at cyc=K+n on success and at the // detected-violation cyc on failure -- both deterministic given the inputs. int high_bounded_pass_q[$]; - int high_sbounded_pass_q[$]; int high_degenerate_pass_q[$]; int low_bounded_fail_q[$]; int low_degenerate_fail_q[$]; @@ -39,9 +38,6 @@ module t ( // Bounded weak always over constant-true input. assert property (@(posedge clk) always [0:3] a_high) high_bounded_pass_q.push_back(cyc); - // Bounded strong s_always over constant-true input. - assert property (@(posedge clk) s_always [1:2] a_high) high_sbounded_pass_q.push_back(cyc); - // Degenerate [0:0]: equivalent to immediate sample. assert property (@(posedge clk) always [0:0] a_high) high_degenerate_pass_q.push_back(cyc); @@ -83,10 +79,6 @@ module t ( `checkd(high_bounded_pass_q.size(), 17); `checkd(high_bounded_pass_q[0], 3); `checkd(high_bounded_pass_q[$], 19); - // Strong [1:2]: K=0..17 succeed at cyc K+2 = 2..19. - `checkd(high_sbounded_pass_q.size(), 18); - `checkd(high_sbounded_pass_q[0], 2); - `checkd(high_sbounded_pass_q[$], 19); // Degenerate [0:0]: K=0..19 succeed at cyc K = 0..19. `checkd(high_degenerate_pass_q.size(), 20); `checkd(high_degenerate_pass_q[0], 0); diff --git a/test_regress/t/t_prop_always_bad.out b/test_regress/t/t_prop_always_bad.out index 4e06a8feb..62d3d66a8 100644 --- a/test_regress/t/t_prop_always_bad.out +++ b/test_regress/t/t_prop_always_bad.out @@ -1,34 +1,34 @@ -%Error: t/t_prop_always_bad.v:13:20: always range low bound must be non-negative (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:13:28: always range low bound must be non-negative (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 13 | assert property (always [-1:3] a); - | ^~~~~~ + | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_prop_always_bad.v:14:20: always range high bound must be >= low bound (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:14:30: always range high bound must be >= low bound (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 14 | assert property (always [5:2] a); - | ^~~~~~ + | ^ %Error: t/t_prop_always_bad.v:15:28: Expecting expression to be constant, but variable isn't const: 'x' : ... note: In instance 't' 15 | assert property (always [x:3] a); | ^ -%Error: t/t_prop_always_bad.v:15:20: always range low bound must be a constant expression (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:15:28: always range low bound must be a constant expression (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 15 | assert property (always [x:3] a); - | ^~~~~~ + | ^ %Error: t/t_prop_always_bad.v:16:30: Expecting expression to be constant, but variable isn't const: 'x' : ... note: In instance 't' 16 | assert property (always [1:x] a); | ^ -%Error: t/t_prop_always_bad.v:16:20: always range high bound must be a constant expression (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:16:30: always range high bound must be a constant expression (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 16 | assert property (always [1:x] a); - | ^~~~~~ + | ^ %Error: t/t_prop_always_bad.v:17:20: s_always range must be bounded (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 17 | assert property (s_always a); | ^~~~~~~~ -%Error: t/t_prop_always_bad.v:18:20: s_always range must be bounded (IEEE 1800-2023 16.12.11) +%Error: t/t_prop_always_bad.v:18:32: s_always range must be bounded (IEEE 1800-2023 16.12.11) : ... note: In instance 't' 18 | assert property (s_always [1:$] a); - | ^~~~~~~~ + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_prop_always_unsup.out b/test_regress/t/t_prop_always_unsup.out index 52bd14918..3bc897181 100644 --- a/test_regress/t/t_prop_always_unsup.out +++ b/test_regress/t/t_prop_always_unsup.out @@ -1,18 +1,14 @@ -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:12:35: Unsupported: unbounded always range (always [m:$]) +%Error-UNSUPPORTED: t/t_prop_always_unsup.v:12:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) : ... note: In instance 't' - 12 | assert property (@(posedge clk) always [2:$] a); + 12 | assert property (@(posedge clk) always [0:3] (a |-> b)); | ^~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:15:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) +%Error-UNSUPPORTED: t/t_prop_always_unsup.v:13:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) : ... note: In instance 't' - 15 | assert property (@(posedge clk) always [0:3] (a |-> b)); + 13 | assert property (@(posedge clk) always [0:3] (a |=> b)); | ^~~~~~ -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:16:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) +%Error-UNSUPPORTED: t/t_prop_always_unsup.v:14:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) : ... note: In instance 't' - 16 | assert property (@(posedge clk) always [0:3] (a |=> b)); - | ^~~~~~ -%Error-UNSUPPORTED: t/t_prop_always_unsup.v:17:35: Unsupported: property/sequence operator inside bounded always (IEEE 1800-2023 16.12.11) - : ... note: In instance 't' - 17 | assert property (@(posedge clk) always [0:3] (a ##1 b)); + 14 | assert property (@(posedge clk) always [0:3] (a ##1 b)); | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_prop_always_unsup.v b/test_regress/t/t_prop_always_unsup.v index 7013c9fff..998516d7d 100644 --- a/test_regress/t/t_prop_always_unsup.v +++ b/test_regress/t/t_prop_always_unsup.v @@ -8,9 +8,6 @@ module t (input clk); logic a; logic b; - // IEEE-legal but engine has no sim-end liveness. - assert property (@(posedge clk) always [2:$] a); - // Nested sequence/property operators inside bounded always. assert property (@(posedge clk) always [0:3] (a |-> b)); assert property (@(posedge clk) always [0:3] (a |=> b)); diff --git a/test_regress/t/t_prop_always_wide.v b/test_regress/t/t_prop_always_wide.v index a0e297293..742e37d9a 100644 --- a/test_regress/t/t_prop_always_wide.v +++ b/test_regress/t/t_prop_always_wide.v @@ -15,17 +15,13 @@ module t ( int cyc = 0; logic a_high = 1'b1, b_high = 1'b1, c_high = 1'b1; - int wide_pass_q[$], wide_strong_pass_q[$]; + int wide_pass_q[$]; // Wide range with multi-operand pure propp -- exercises the shared // $sampled(propp) hoist path; pre-fix would clone propp 33 times. assert property (@(posedge clk) always[1: 33] (a_high && b_high && c_high)) wide_pass_q.push_back(cyc); - // Wide strong s_always over the same expression (in-window matches weak). - assert property (@(posedge clk) s_always[1: 33] (a_high && b_high && c_high)) - wide_strong_pass_q.push_back(cyc); - always @(posedge clk) begin cyc <= cyc + 1; if (cyc == 49) begin @@ -33,9 +29,6 @@ module t ( `checkd(wide_pass_q.size(), 17); `checkd(wide_pass_q[0], 33); `checkd(wide_pass_q[$], 49); - `checkd(wide_strong_pass_q.size(), 17); - `checkd(wide_strong_pass_q[0], 33); - `checkd(wide_strong_pass_q[$], 49); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_prop_s_always_liveness.out b/test_regress/t/t_prop_s_always_liveness.out new file mode 100644 index 000000000..8a1f69c16 --- /dev/null +++ b/test_regress/t/t_prop_s_always_liveness.out @@ -0,0 +1,4 @@ +*-* All Finished *-* +[115] %Error: t_prop_s_always_liveness.v:25: Assertion failed in top.t +%Error: t/t_prop_s_always_liveness.v:25: Verilog $stop +Aborting... diff --git a/test_regress/t/t_prop_s_always_liveness.py b/test_regress/t/t_prop_s_always_liveness.py new file mode 100755 index 000000000..e6fa1c51f --- /dev/null +++ b/test_regress/t/t_prop_s_always_liveness.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--assert']) +test.execute(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_prop_s_always_liveness.v b/test_regress/t/t_prop_s_always_liveness.v new file mode 100644 index 000000000..3251b7754 --- /dev/null +++ b/test_regress/t/t_prop_s_always_liveness.v @@ -0,0 +1,43 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + int cyc = 0; + logic a_high = 1'b1; + logic a_low = 1'b0; + + int low_s_fail_q[$]; + int low_w_fail_q[$]; + + // The youngest [2:5] windows are still open at $finish, so strong s_always + // reports a liveness failure even with a_high always 1; weak always does not. + assert property (@(posedge clk) s_always [2:5] a_high); + assert property (@(posedge clk) always [2:5] a_high); + + assert property (@(posedge clk) s_always [2:5] a_low) + else low_s_fail_q.push_back(cyc); + assert property (@(posedge clk) always [2:5] a_low) + else low_w_fail_q.push_back(cyc); + + always @(posedge clk) begin + 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); + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule From 51022d61528572c635fd234c21fada33b0376a48 Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Thu, 18 Jun 2026 20:40:54 +0200 Subject: [PATCH 55/89] Tests: Fix unstable --vltmt test (#7803) Fixes #7803. --- test_regress/t/t_assert_iff_clk.py | 3 +-- test_regress/t/t_assert_iff_clk.v | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test_regress/t/t_assert_iff_clk.py b/test_regress/t/t_assert_iff_clk.py index fd4b2dc44..35e44000c 100755 --- a/test_regress/t/t_assert_iff_clk.py +++ b/test_regress/t/t_assert_iff_clk.py @@ -9,8 +9,7 @@ import vltest_bootstrap -# Issue #7781 unstable with --vltmt -test.scenarios('simulator_st') +test.scenarios('simulator') test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) diff --git a/test_regress/t/t_assert_iff_clk.v b/test_regress/t/t_assert_iff_clk.v index 3dfb3458b..73c8b82b5 100644 --- a/test_regress/t/t_assert_iff_clk.v +++ b/test_regress/t/t_assert_iff_clk.v @@ -32,7 +32,7 @@ module t ( assert property (@(posedge clk) disable iff (cyc < 5) 1 ##1 0) else post_temporal_fail++; - always @(posedge clk) begin + always @(negedge clk) begin cyc <= cyc + 1; rst <= cyc < 4; x <= cyc < 4; From 129cfc19c01cb7ecffdfe6dd18bde6316802896a Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Fri, 19 Jun 2026 01:07:37 +0200 Subject: [PATCH 56/89] Fix cross-hierarchy tristate drivers of interface nets (#7339) (#7801) Closes #7339. --- src/V3Tristate.cpp | 43 +++++++- test_regress/t/t_interface_inout_tristate.py | 18 +++ test_regress/t/t_interface_inout_tristate.v | 70 ++++++++++++ .../t/t_interface_tristate_plain_xhier.py | 18 +++ .../t/t_interface_tristate_plain_xhier.v | 104 ++++++++++++++++++ .../t_interface_tristate_plain_xhier_noinl.py | 19 ++++ 6 files changed, 270 insertions(+), 2 deletions(-) create mode 100755 test_regress/t/t_interface_inout_tristate.py create mode 100644 test_regress/t/t_interface_inout_tristate.v create mode 100755 test_regress/t/t_interface_tristate_plain_xhier.py create mode 100644 test_regress/t/t_interface_tristate_plain_xhier.v create mode 100755 test_regress/t/t_interface_tristate_plain_xhier_noinl.py diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 6fa174ed0..5bc72076b 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -352,7 +352,9 @@ class TristatePinVisitor final : public TristateBaseVisitor { TristateGraph& m_tgraph; const bool m_lvalue; // Flip to be an LVALUE // VISITORS - void visit(AstVarRef* nodep) override { + // AstNodeVarRef, not just AstVarRef: a cross-hierarchy pin expression into an + // interface (.pin(iface.net)) is an AstVarXRef and needs the same access flip. + void visit(AstNodeVarRef* nodep) override { UASSERT_OBJ(!nodep->access().isRW(), nodep, "Tristate unexpected on R/W access flip"); if (m_lvalue && !nodep->access().isWriteOrRW()) { UINFO(9, " Flip-to-LValue " << nodep); @@ -405,6 +407,11 @@ class TristateVisitor final : public TristateBaseVisitor { struct AuxAstVar final { AstPull* pullp = nullptr; // pullup/pulldown direction (whole variable) AstVar* outVarp = nullptr; // output __out var + bool ifaceTristate = false; // Interface var known to be tristate (set when the + // interface module is processed). Lets a module that + // drives this var across hierarchy with a plain (non-Z) + // assign be recognised as a tristate contributor even + // though that module's own graph has no Z on the net. std::unordered_map bitPulls; // Per-bit pull: bit_index -> direction (1=up, 0=down) }; @@ -439,6 +446,7 @@ class TristateVisitor final : public TristateBaseVisitor { int m_unique = 0; bool m_alhs = false; // On LHS of assignment bool m_inAlias = false; // Inside alias statement + bool m_processedIfaces = false; // Interface modules already processed (interfaces-first pass) VStrength m_currentStrength = VStrength::STRONG; // Current strength of assignment, // Used only on LHS of assignment const AstNode* m_logicp = nullptr; // Current logic being built @@ -755,6 +763,9 @@ class TristateVisitor final : public TristateBaseVisitor { } } } else if (isIfaceTri) { + // Mark here too, so a net made tristate only by an external 'z driver + // still captures a plain cross-hierarchy driver processed later. + m_varAux(invarp).ifaceTristate = true; // Interface tristate vars: drivers from different interface instances // (different VarXRef dotted paths) must be processed separately. // E.g. io_ifc.d and io_ifc_local.d both target the same AstVar d in @@ -780,7 +791,11 @@ class TristateVisitor final : public TristateBaseVisitor { } } else if (VN_IS(nodep, Iface) && !invarp->isIO()) { // Local driver in an interface module - use contribution mechanism - // so it can be combined with any external drivers later + // so it can be combined with any external drivers later. Record that + // this interface net is tristate, so a module that drives it across + // hierarchy with a plain (non-Z) assign is also routed through the + // contribution mechanism (its own graph has no Z to mark it tristate). + m_varAux(invarp).ifaceTristate = true; insertTristatesSignal(nodep, invarp, refsp, true, "", "", nullptr); } else { insertTristatesSignal(nodep, invarp, refsp, false, "", "", nullptr); @@ -2080,6 +2095,15 @@ class TristateVisitor final : public TristateBaseVisitor { if (m_graphing) { if (nodep->access().isWriteOrRW()) associateLogic(nodep, nodep->varp()); if (nodep->access().isReadOrRW()) associateLogic(nodep->varp(), nodep); + // Only interface tristate nets need this: a plain (non-Z) cross-hierarchy + // driver has no Z in its own module's graph, so mark the net tristate here to + // collect the driver as a contribution. The ifaceTristate flag is only set for + // interface nets; a non-interface cross-module tri driver does nothing here and + // is instead rejected (E_UNSUPPORTED) later in insertTristates. + if (nodep->access().isWriteOrRW() && VN_IS(nodep, VarXRef) + && m_varAux(nodep->varp()).ifaceTristate) { + m_tgraph.setTristate(nodep->varp()); + } } else { if (nodep->user2() & U2_NONGRAPH) return; // Processed nodep->user2Or(U2_NONGRAPH); @@ -2160,6 +2184,9 @@ class TristateVisitor final : public TristateBaseVisitor { } void visit(AstNodeModule* nodep) override { + // Interfaces are processed first in the constructor; skip the duplicate visit + // during the later iterateChildrenBackwardsConst() pass over all modules. + if (m_processedIfaces && VN_IS(nodep, Iface)) return; UINFO(8, dbgState() << nodep); VL_RESTORER(m_modp); VL_RESTORER(m_graphing); @@ -2296,6 +2323,18 @@ public: // CONSTRUCTORS explicit TristateVisitor(AstNetlist* netlistp) { m_tgraph.clearAndCheck(); + // Process interface modules first, so an interface tristate net is recorded + // (AuxAstVar::ifaceTristate) before any module that drives it across hierarchy is + // graphed. A module driving such a net with a plain (non-Z) assign has no Z in its + // own graph to mark the net tristate, so without this its driver would be dropped. + // Collect only the interfaces (reverse declaration order to match the pass below). + std::vector ifacesp; + for (AstNode* modp = netlistp->modulesp(); modp; modp = modp->nextp()) { + if (VN_IS(modp, Iface)) ifacesp.push_back(VN_AS(modp, NodeModule)); + } + for (auto it = ifacesp.rbegin(); it != ifacesp.rend(); ++it) iterate(*it); + m_processedIfaces = true; + // Then the rest in the historical order; interfaces are skipped (already done). iterateChildrenBackwardsConst(netlistp); // Combine interface tristate contributions after all modules processed diff --git a/test_regress/t/t_interface_inout_tristate.py b/test_regress/t/t_interface_inout_tristate.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_interface_inout_tristate.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_inout_tristate.v b/test_regress/t/t_interface_inout_tristate.v new file mode 100644 index 000000000..36b20fea2 --- /dev/null +++ b/test_regress/t/t_interface_inout_tristate.v @@ -0,0 +1,70 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`ifdef verilator + `define no_optimize(v) $c(v) +`else + `define no_optimize(v) (v) +`endif +// verilog_format: on + +// verilator lint_off MULTIDRIVEN + +interface ifc; + // Tristate net resolved across an inout port connected hierarchically to + // this interface signal (iface.data <-> dut inout port). + wire [7:0] data; +endinterface + +module dut ( + inout wire [7:0] data, + input bit oe, + input logic [7:0] val +); + // The inout port drives the interface net when enabled, releases it (Z) otherwise. + assign data = oe ? val : 8'hzz; +endmodule + +module t; + ifc ifc0 (); + bit oe, top_oe; + logic [7:0] val, topval; + + // A second driver from the top, so resolution is observable from both sides. + assign ifc0.data = top_oe ? topval : 8'hzz; + + dut u ( + .data(ifc0.data), + .oe(oe), + .val(val) + ); + + initial begin + // dut drives the interface net through the inout port. + oe = `no_optimize(1'b1); + val = `no_optimize(8'hA5); + top_oe = `no_optimize(1'b0); + topval = `no_optimize(8'h00); + #1; + `checkh(ifc0.data, 8'hA5); + // top drives, dut releases. + oe = `no_optimize(1'b0); + top_oe = `no_optimize(1'b1); + topval = `no_optimize(8'h3C); + #1; + `checkh(ifc0.data, 8'h3C); + // Neither drives: net floats to Z. + oe = `no_optimize(1'b0); + top_oe = `no_optimize(1'b0); + #1; + `checkh(ifc0.data, 8'hzz); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_tristate_plain_xhier.py b/test_regress/t/t_interface_tristate_plain_xhier.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_interface_tristate_plain_xhier.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_tristate_plain_xhier.v b/test_regress/t/t_interface_tristate_plain_xhier.v new file mode 100644 index 000000000..d7c7d6ca6 --- /dev/null +++ b/test_regress/t/t_interface_tristate_plain_xhier.v @@ -0,0 +1,104 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`ifdef verilator + `define no_optimize(v) $c(v) +`else + `define no_optimize(v) (v) +`endif +// verilog_format: on + +// verilator lint_off MULTIDRIVEN + +interface ifc; + wire [1:0] w; + // Self-contained interface-internal tristate driver: 'z while en==0, so any + // external driver must win the net resolution. no_optimize keeps the driver + // values from being constant-folded, so the resolution runs at run time. + logic en; + logic [1:0] wint; + assign en = `no_optimize(1'b0); + assign wint = `no_optimize(2'b11); + assign w = en ? wint : 2'bzz; + // Read the resolved net from inside the interface (a consumer's view), so the + // check sees the true resolution, not the driving module's own local copy. + function automatic logic [1:0] get_w(); + return w; + endfunction +endinterface + +// Interface net made tristate only by an external 'z driver (no internal driver). +interface ifc_tri; + tri [1:0] w; +endinterface + +module driver ( + ifc io, + input logic [1:0] din +); + // Plain (non-Z) cross-hierarchy driver of an interface tristate net. + // This module's own tristate graph has no 'z on io.w, so before the fix this + // contribution was silently dropped and io.w resolved to the interface-internal + // 'z (== 0) only. + assign io.w = din; +endmodule + +module driver_tri ( + ifc_tri io, + input logic [1:0] din +); + assign io.w = din; +endmodule + +module zdriver ( + ifc_tri io +); + assign io.w = 2'bzz; +endmodule + +module t; + // u_a, u_b: interface nets driven plainly from the top module (depth-0 xref). + // u_c: interface net driven plainly from a child module (depth-1 xref). + ifc u_a (); + ifc u_b (); + ifc u_c (); + // u_e: net is tristate only via zdriver's external 'z; the plain driver must + // still win (the interface itself has no internal driver). + ifc_tri u_e (); + + logic [1:0] va, vb, vc, ve; + assign va = `no_optimize(2'b01); + assign vb = `no_optimize(2'b10); + assign vc = `no_optimize(2'b11); + assign ve = `no_optimize(2'b01); + + assign u_a.w = va; // plain top-level driver + assign u_b.w = vb; // plain top-level driver + driver u_drv ( + .io(u_c), + .din(vc) + ); // plain driver in a child module + driver_tri u_pdrv ( + .io(u_e), + .din(ve) + ); // plain driver of an externally-tristated net + zdriver u_zdrv (.io(u_e)); // external 'z driver + + initial begin + #1; + // The plain external driver must win over the interface-internal 'z. + `checkh(u_a.get_w(), 2'b01); + `checkh(u_b.get_w(), 2'b10); + `checkh(u_c.get_w(), 2'b11); + // The plain driver must win over an external-only 'z (no internal driver). + `checkh(u_e.w, 2'b01); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_tristate_plain_xhier_noinl.py b/test_regress/t/t_interface_tristate_plain_xhier_noinl.py new file mode 100755 index 000000000..12aff0c40 --- /dev/null +++ b/test_regress/t/t_interface_tristate_plain_xhier_noinl.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.top_filename = "t_interface_tristate_plain_xhier.v" + +test.compile(timing_loop=True, verilator_flags2=['--timing', '-fno-inline']) + +test.execute() + +test.passes() From 50c15f3705c946ca1c8e593a47e3f498c977414d Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Thu, 18 Jun 2026 21:56:02 -0400 Subject: [PATCH 57/89] Commentary: Changes update --- Changes | 14 +- docs/spelling.txt | 1 + test_regress/t/t_assert_ctl_lock.v | 2 + test_regress/t/t_case_table_normal.v | 66 ++++++-- test_regress/t/t_case_table_tiny.v | 42 +++-- test_regress/t/t_constraint_global_cls_arr.v | 2 +- .../t/t_constraint_global_cls_arr_2d_unsup.v | 4 +- test_regress/t/t_force_unpacked_bitsel.v | 154 +++++++++--------- test_regress/t/t_interface_param_class_bits.v | 22 ++- test_regress/t/t_oob_2state_array.v | 3 +- 10 files changed, 199 insertions(+), 111 deletions(-) diff --git a/Changes b/Changes index afe5731e2..9df830925 100644 --- a/Changes +++ b/Changes @@ -52,6 +52,7 @@ Verilator 5.049 devel * Support procedural concurrent assertions with inferred clock (#7581). [Yilou Wang] * Support calling interface functions without parens (#7584). [Krzysztof Bieganski, Antmicro Ltd.] * Support streaming on queues (#7597). [Benjamin Collier, Secturion Systems, Inc.] +* Support clocking event on a sequence declaration body (#7598) (#7793). [Yilou Wang] * Support generic interface arrays (#7604). [Krzysztof Bieganski, Antmicro Ltd.] * Support FSM detection in primitive wrappers (#7607). [Yogish Sekhar] * Support busses with mix of pullup/pulldown (#7632). [Lucas Amaral] @@ -62,13 +63,15 @@ Verilator 5.049 devel * Support process::self().srand() (#7695). [Igor Zaworski, Antmicro Ltd.] * Support MacOS lldb (#7697). [Tracy Narine] * Support assoc array methods with wide value types (#7680). [pawelktk] -* Support property case (#7721). [Artur Bieniek, Antmicro Ltd.] +* Support property case (#7682) (#7721). [Artur Bieniek, Antmicro Ltd.] * Support `s_until` and `s_until_with`(#7722). [Artur Bieniek, Antmicro Ltd.] * Support covergroup runtime model Phase A1 (#7728). [Matthew Ballance] * Support reduction XOR/AND operations in constraints (#7753). [Kornel Uriasz, Antmicro Ltd.] * Support assertion control system tasks in classes and interfaces (#7761). [Yilou Wang] * Support cover sequence statement (#7764). [Yilou Wang] * Support unpacked struct stream (#7767). [Nick Brereton] +* Support $assertcontrol control_type from lock to kill (#7788). [Yilou Wang] +* Support unbounded always [m:$] and strong s_always liveness (#7798). [Yilou Wang] * Optimize emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.] * Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.] * Optimize forced signal handling (#7554 partial) (#7572) (#7594) (#7596). [Krzysztof Bieganski, Artur Bieniek, Antmicro Ltd.] @@ -89,10 +92,12 @@ Verilator 5.049 devel * Optimize away proven redundant case statement assertions (#7771). [Geza Lore, Testorrent USA, Inc.] * Optimize table lookups in DFG (#7772). [Geza Lore, Testorrent USA, Inc.] * Optimize input combinational logic by change detection (#7784). [Geza Lore, Testorrent USA, Inc.] +* Optimize decoder case statements into lookup tables (#7795). [Geza Lore, Testorrent USA, Inc.] * Fix TSP variable ordering for mtasks (#5342) (#7610). [Muzaffer Kal] * Fix inlining static initializer in V3Gate (#5381) (#7503). [Andrew Nolte] [Geza Lore, Testorrent USA, Inc.] * Fix timed nested fork block with disable (#6720) (#7743). [Marco Bartoli] * Fix segmentation fault when using --trace with --lib-create (#7299) (#7518). [anonkey] +* Fix cross-hierarchy tristate drivers of interface nets (#7339) (#7801). [Yilou Wang] * Fix destructive event state before dynamic waits (#7340). [Nick Brereton] * Fix ALWCOMBORDER on variable ordering (#7350) (#7608). [Cookie] * Fix false MULTIDRIVEN warning on always_ff variables (#7351) (#7621) (#7672). @@ -152,6 +157,7 @@ Verilator 5.049 devel * Fix `ref` argument type check for packed arrays with differing range directions (#7700). [Nick Brereton] * Fix ignoring not-found modules with encoded names (#7706). [Igor Zaworski, Antmicro Ltd.] * Fix MULTIDRIVEN in generates (#7709). [Todd Strader] +* Fix parameter pollution when using class parameters (#7711) (#7763). [em2machine] * Fix Makefile action to not write to ${srcdir} (#7715). [Larry Doolittle] * Fix splitting functions containing fork logic (#7717). [Mateusz Gancarz, Antmicro Ltd.] * Fix optimizations of assignments with timing controls (#7718). [Ryszard Rozak, Antmicro Ltd.] @@ -161,11 +167,17 @@ Verilator 5.049 devel * Fix crash on overlapping priority case. [Geza Lore, Testorrent USA, Inc.] * Fix s_eventually in parameterized interfaces (#7741). [Nick Brereton] * Fix dpi export pointers (#7742) (#7751). [Yilin Li] +* Fix force on unpacked bit select (#7744) (#7745). [Nikolai Kumar] * Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.] * Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang] * Fix 'case (_) inside' with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.] * Fix not failing assertion when RHS of a range window rejects once (#7773). [Artur Bieniek, Antmicro Ltd.] * Fix $fflush and autoflush with --threads (#7782). +* Fix out-of-bounds read value for 2-state types (#7785). [Jakub Michalski] +* Fix `cover property` of an implication counting vacuous matches (#7789). [Yilou Wang] +* Fix randomization of dynamic arrays of objects (#7790). [Ryszard Rozak, Antmicro Ltd.] +* Fix `$bits` on unpacked structs (#4521) (#7796). [Nick Brereton] +* Fix randomize() with skipping derived pre/post_randomize (#7799). [Yilou Wang] Verilator 5.048 2026-04-26 diff --git a/docs/spelling.txt b/docs/spelling.txt index a21f66ed5..fa7fae5e9 100644 --- a/docs/spelling.txt +++ b/docs/spelling.txt @@ -917,6 +917,7 @@ linters linux liu livelock +liveness lldb ln loc diff --git a/test_regress/t/t_assert_ctl_lock.v b/test_regress/t/t_assert_ctl_lock.v index cc9ce8411..59c98a006 100644 --- a/test_regress/t/t_assert_ctl_lock.v +++ b/test_regress/t/t_assert_ctl_lock.v @@ -4,11 +4,13 @@ // SPDX-FileCopyrightText: 2026 PlanV GmbH // SPDX-License-Identifier: CC0-1.0 +// verilog_format: off `ifdef verilator `define no_optimize(v) $c(v) `else `define no_optimize(v) (v) `endif +// verilog_format: on module t ( /*AUTOARG*/); logic clk = 0; diff --git a/test_regress/t/t_case_table_normal.v b/test_regress/t/t_case_table_normal.v index 26e32fa81..a7b6285f0 100644 --- a/test_regress/t/t_case_table_normal.v +++ b/test_regress/t/t_case_table_normal.v @@ -62,11 +62,31 @@ module t; logic [11:0] accept_c_out_2, accept_c_ref_2; always_comb case (cyc[3:0]) - 4'd0: begin accept_c_out_0 = 12'h001; accept_c_out_1 = 12'h010; accept_c_out_2 = 12'h100; end - 4'd1: begin accept_c_out_0 = 12'h002; accept_c_out_1 = 12'h020; accept_c_out_2 = 12'h200; end - 4'd2: begin accept_c_out_0 = 12'h004; accept_c_out_1 = 12'h040; accept_c_out_2 = 12'h400; end - 4'd3: begin accept_c_out_0 = 12'h008; accept_c_out_1 = 12'h080; accept_c_out_2 = 12'h800; end - default: begin accept_c_out_0 = 12'h000; accept_c_out_1 = 12'h0ff; accept_c_out_2 = 12'hfff; end + 4'd0: begin + accept_c_out_0 = 12'h001; + accept_c_out_1 = 12'h010; + accept_c_out_2 = 12'h100; + end + 4'd1: begin + accept_c_out_0 = 12'h002; + accept_c_out_1 = 12'h020; + accept_c_out_2 = 12'h200; + end + 4'd2: begin + accept_c_out_0 = 12'h004; + accept_c_out_1 = 12'h040; + accept_c_out_2 = 12'h400; + end + 4'd3: begin + accept_c_out_0 = 12'h008; + accept_c_out_1 = 12'h080; + accept_c_out_2 = 12'h800; + end + default: begin + accept_c_out_0 = 12'h000; + accept_c_out_1 = 12'h0ff; + accept_c_out_2 = 12'hfff; + end endcase assign accept_c_ref_0 = (cyc[3:0] == 4'd0) ? 12'h001 : (cyc[3:0] == 4'd1) ? 12'h002 : (cyc[3:0] == 4'd2) ? 12'h004 : (cyc[3:0] == 4'd3) ? 12'h008 : 12'h000; @@ -83,11 +103,24 @@ module t; accept_d_out_0 <= 16'h0000; accept_d_out_1 <= 16'hffff; case (cyc[3:0]) - 4'd0: begin accept_d_out_0 <= 16'h0001; accept_d_out_1 <= 16'h0010; end - 4'd1: begin accept_d_out_0 <= 16'h0002; accept_d_out_1 <= 16'h0020; end - 4'd2: begin accept_d_out_0 <= 16'h0004; accept_d_out_1 <= 16'h0040; end - 4'd3: begin accept_d_out_0 <= 16'h0008; accept_d_out_1 <= 16'h0080; end - default: begin end + 4'd0: begin + accept_d_out_0 <= 16'h0001; + accept_d_out_1 <= 16'h0010; + end + 4'd1: begin + accept_d_out_0 <= 16'h0002; + accept_d_out_1 <= 16'h0020; + end + 4'd2: begin + accept_d_out_0 <= 16'h0004; + accept_d_out_1 <= 16'h0040; + end + 4'd3: begin + accept_d_out_0 <= 16'h0008; + accept_d_out_1 <= 16'h0080; + end + default: begin + end endcase end always_ff @(posedge clk) begin @@ -134,7 +167,10 @@ module t; case (cyc[3:0]) 4'd0: accept_g_out_0 = 16'h0001; 4'd1: accept_g_out_1 = 16'h0002; - 4'd2: begin accept_g_out_0 = 16'h0004; accept_g_out_1 = 16'h0008; end + 4'd2: begin + accept_g_out_0 = 16'h0004; + accept_g_out_1 = 16'h0008; + end 4'd3: accept_g_out_0 = 16'h0010; default: ; endcase @@ -145,7 +181,13 @@ module t; assign accept_g_ref_2 = 16'h3333; // Accept H: unique0 enum case; the selector may hold an out-of-range value. - typedef enum logic [3:0] {NE0, NE1, NE2, NE3, NE4} ne_t; + typedef enum logic [3:0] { + NE0, + NE1, + NE2, + NE3, + NE4 + } ne_t; ne_t accept_h_in; assign accept_h_in = ne_t'(cyc[3:0]); logic [15:0] accept_h_out, accept_h_ref; diff --git a/test_regress/t/t_case_table_tiny.v b/test_regress/t/t_case_table_tiny.v index dd3c6fb5b..ecf79db3d 100644 --- a/test_regress/t/t_case_table_tiny.v +++ b/test_regress/t/t_case_table_tiny.v @@ -57,9 +57,18 @@ module t; logic [3:0] accept_c_out_1, accept_c_ref_1; always_comb case (cyc[1:0]) - 2'b00: begin accept_c_out_0 = 3'd1; accept_c_out_1 = 4'd6; end - 2'b01: begin accept_c_out_0 = 3'd2; accept_c_out_1 = 4'd5; end - default: begin accept_c_out_0 = 3'd0; accept_c_out_1 = 4'd7; end + 2'b00: begin + accept_c_out_0 = 3'd1; + accept_c_out_1 = 4'd6; + end + 2'b01: begin + accept_c_out_0 = 3'd2; + accept_c_out_1 = 4'd5; + end + default: begin + accept_c_out_0 = 3'd0; + accept_c_out_1 = 4'd7; + end endcase assign accept_c_ref_0 = (cyc[1:0] == 2'b00) ? 3'd1 : (cyc[1:0] == 2'b01) ? 3'd2 : 3'd0; assign accept_c_ref_1 = (cyc[1:0] == 2'b00) ? 4'd6 : (cyc[1:0] == 2'b01) ? 4'd5 : 4'd7; @@ -72,9 +81,16 @@ module t; accept_d_out_0 <= 3'd0; accept_d_out_1 <= 3'd7; case (cyc[1:0]) - 2'b00: begin accept_d_out_0 <= 3'd1; accept_d_out_1 <= 3'd6; end - 2'b01: begin accept_d_out_0 <= 3'd2; accept_d_out_1 <= 3'd5; end - default: begin end + 2'b00: begin + accept_d_out_0 <= 3'd1; + accept_d_out_1 <= 3'd6; + end + 2'b01: begin + accept_d_out_0 <= 3'd2; + accept_d_out_1 <= 3'd5; + end + default: begin + end endcase end always_ff @(posedge clk) begin @@ -132,11 +148,14 @@ module t; 2'b10: accept_h_out <= 4'h4; 2'b11: accept_h_out <= 4'h8; endcase - always_ff @(posedge clk) - accept_h_ref <= 4'h1 << cyc[1:0]; + always_ff @(posedge clk) accept_h_ref <= 4'h1 << cyc[1:0]; // Accept I: unique0 enum case; the selector may hold an out-of-range value. - typedef enum logic [1:0] {E0, E1, E2} e_t; + typedef enum logic [1:0] { + E0, + E1, + E2 + } e_t; e_t accept_i_in; assign accept_i_in = e_t'(cyc[1:0]); logic [3:0] accept_i_out, accept_i_ref; @@ -223,7 +242,10 @@ module t; always_comb begin reject_c_out = 4'h0; case (cyc[1:0]) - 2'b00: begin reject_c_out = 4'h1; reject_c_out = 4'h2; end + 2'b00: begin + reject_c_out = 4'h1; + reject_c_out = 4'h2; + end default: reject_c_out = 4'h3; endcase end diff --git a/test_regress/t/t_constraint_global_cls_arr.v b/test_regress/t/t_constraint_global_cls_arr.v index 1f6efdd42..1c9c3d6d4 100644 --- a/test_regress/t/t_constraint_global_cls_arr.v +++ b/test_regress/t/t_constraint_global_cls_arr.v @@ -13,7 +13,7 @@ class Bar; function new(); foo_arr = new[12]; - foreach(foo_arr[i]) foo_arr[i] = new; + foreach (foo_arr[i]) foo_arr[i] = new; endfunction constraint c { diff --git a/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v index b83e1362d..46be2a8f3 100644 --- a/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v +++ b/test_regress/t/t_constraint_global_cls_arr_2d_unsup.v @@ -6,7 +6,7 @@ class Foo; rand int abcd; - constraint c { abcd >= 2; } + constraint c {abcd >= 2;} endclass class Bar; @@ -14,7 +14,7 @@ class Bar; function new(); for (int i = 0; i < 3; i++) foo_arr[i] = new[5]; - foreach(foo_arr[i, j]) foo_arr[i][j] = new; + foreach (foo_arr[i, j]) foo_arr[i][j] = new; endfunction constraint c { diff --git a/test_regress/t/t_force_unpacked_bitsel.v b/test_regress/t/t_force_unpacked_bitsel.v index 7a98869d0..402d15f83 100644 --- a/test_regress/t/t_force_unpacked_bitsel.v +++ b/test_regress/t/t_force_unpacked_bitsel.v @@ -4,7 +4,8 @@ // SPDX-FileCopyrightText: 2026 Nikolai Kumar // SPDX-License-Identifier: CC0-1.0 -`define checkh(g,e) do if ((g) !==(e)) begin $write("%%Error: %s:%0d: got=%b exp=%b\n", `__FILE__,`__LINE__, (g),(e)); $stop; end while(0) +`define checkh(g, + e) do if ((g) !==(e)) begin $write("%%Error: %s:%0d: got=%b exp=%b\n", `__FILE__,`__LINE__, (g),(e)); $stop; end while(0) module t ( @@ -13,11 +14,11 @@ module t ( int cyc = 0; always @(posedge clk) cyc <= cyc + 1; - logic [4:1] var_arr [2]; + logic [4:1] var_arr[2]; /* verilator lint_off ASCRANGE */ - logic [1:4] var_arr_a [2]; + logic [1:4] var_arr_a[2]; /* verilator lint_on ASCRANGE */ - logic [72:1] wide_arr [2]; + logic [72:1] wide_arr[2]; always @(posedge clk) begin var_arr[0] <= 4'b0101; @@ -39,16 +40,16 @@ module t ( if (cyc == 4) release wide_arr[1]; if (cyc == 2) force var_arr_a[1][2:4] = 3'b111; - if (cyc == 4) force var_arr_a[1][3]= 1'b0; + if (cyc == 4) force var_arr_a[1][3] = 1'b0; if (cyc == 6) release var_arr_a[1]; if (cyc == 7) force var_arr_a[1][3:4] = 2'b11; - if (cyc == 9) force var_arr_a[1][2:3]= 2'b00; + if (cyc == 9) force var_arr_a[1][2:3] = 2'b00; if (cyc == 11) release var_arr_a[1]; if (cyc == 12) force var_arr_a[1][1:2] = 2'b11; - if (cyc == 14) force var_arr_a[1][2:3]= 2'b00; + if (cyc == 14) force var_arr_a[1][2:3] = 2'b00; if (cyc == 16) release var_arr_a[1]; if (cyc == 17) force var_arr_a[1][2:3] = 2'b11; - if (cyc == 19) force var_arr_a[1][1:3]= 3'b000; + if (cyc == 19) force var_arr_a[1][1:3] = 3'b000; if (cyc == 21) release var_arr_a[1]; if (cyc == 14) force var_arr[1][1] = 1'b0; @@ -60,72 +61,73 @@ module t ( if (cyc == 22) release var_arr[0]; end - always @(posedge clk) case (cyc) - 1: begin - `checkh(var_arr[0], 4'b0101); - `checkh(var_arr[1], 4'b1111); - end - 3: begin - `checkh(var_arr[1], 4'b1110); - `checkh(wide_arr[1][36:5], 32'hffff_ffff); - `checkh(wide_arr[1][4:1], 4'hB); - `checkh(wide_arr[1][72:37], 36'hABCDEF012); - `checkh(var_arr_a[1], 4'b0111); - `checkh(var_arr_a[0], 4'b1010); - end - 5: begin - `checkh(var_arr[1], 4'b0000); - `checkh(wide_arr[1], 72'hAB_CDEF0123_456789AB); - `checkh(var_arr_a[1], 4'b0101); - end - 7: begin - `checkh(var_arr[1], 4'b1000); - end - 8: begin - `checkh(var_arr_a[1], 4'b0011); - end - 9: begin - `checkh(var_arr[1], 4'b1001); - end - 10: begin - `checkh(var_arr_a[1], 4'b0001); - end - 11: begin - `checkh(var_arr[1], 4'b1010); - `checkh(var_arr[0], 4'b0101); - end - 13: begin - `checkh(var_arr[1], 4'b0001); - `checkh(var_arr_a[1], 4'b1100); - end - 15: begin - `checkh(var_arr_a[1], 4'b1000); - `checkh(var_arr[1], 4'b0000); - `checkh(var_arr[0], 4'b0100); - end - 17: begin - `checkh(var_arr[1], 4'b0001); - `checkh(var_arr[0], 4'b0101); - end - 18: begin - `checkh(var_arr_a[1], 4'b0110); - end - 19: begin - `checkh(var_arr[0], 4'b1010); - end - 20: begin - `checkh(var_arr_a[1], 4'b0000); - end - 21: begin - `checkh(var_arr[0], 4'b1011); - end - 22: begin - `checkh(var_arr_a[1], 4'b0000); - end - 23: begin - `checkh(var_arr[0], 4'b0101); - $write("*-* All Finished *-*\n"); - $finish; - end - endcase + always @(posedge clk) + case (cyc) + 1: begin + `checkh(var_arr[0], 4'b0101); + `checkh(var_arr[1], 4'b1111); + end + 3: begin + `checkh(var_arr[1], 4'b1110); + `checkh(wide_arr[1][36:5], 32'hffff_ffff); + `checkh(wide_arr[1][4:1], 4'hB); + `checkh(wide_arr[1][72:37], 36'hABCDEF012); + `checkh(var_arr_a[1], 4'b0111); + `checkh(var_arr_a[0], 4'b1010); + end + 5: begin + `checkh(var_arr[1], 4'b0000); + `checkh(wide_arr[1], 72'hAB_CDEF0123_456789AB); + `checkh(var_arr_a[1], 4'b0101); + end + 7: begin + `checkh(var_arr[1], 4'b1000); + end + 8: begin + `checkh(var_arr_a[1], 4'b0011); + end + 9: begin + `checkh(var_arr[1], 4'b1001); + end + 10: begin + `checkh(var_arr_a[1], 4'b0001); + end + 11: begin + `checkh(var_arr[1], 4'b1010); + `checkh(var_arr[0], 4'b0101); + end + 13: begin + `checkh(var_arr[1], 4'b0001); + `checkh(var_arr_a[1], 4'b1100); + end + 15: begin + `checkh(var_arr_a[1], 4'b1000); + `checkh(var_arr[1], 4'b0000); + `checkh(var_arr[0], 4'b0100); + end + 17: begin + `checkh(var_arr[1], 4'b0001); + `checkh(var_arr[0], 4'b0101); + end + 18: begin + `checkh(var_arr_a[1], 4'b0110); + end + 19: begin + `checkh(var_arr[0], 4'b1010); + end + 20: begin + `checkh(var_arr_a[1], 4'b0000); + end + 21: begin + `checkh(var_arr[0], 4'b1011); + end + 22: begin + `checkh(var_arr_a[1], 4'b0000); + end + 23: begin + `checkh(var_arr[0], 4'b0101); + $write("*-* All Finished *-*\n"); + $finish; + end + endcase endmodule diff --git a/test_regress/t/t_interface_param_class_bits.v b/test_regress/t/t_interface_param_class_bits.v index 8994a3152..35f80c334 100644 --- a/test_regress/t/t_interface_param_class_bits.v +++ b/test_regress/t/t_interface_param_class_bits.v @@ -8,22 +8,28 @@ // SPDX-FileCopyrightText: 2026 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 -class cls #(int width); +class cls #( + int width +); endclass -interface ifc - #(parameter int width = 8, - parameter type dtype = logic[width-1:0], - parameter type cparam = cls#($bits(dtype))); - dtype data; +interface ifc #( + parameter int width = 8, + parameter type dtype = logic [width-1:0], + parameter type cparam = cls#($bits(dtype)) +); + dtype data; endinterface module t; // width is overridden, dtype keeps its default logic[width-1:0], and the // class type parameter is overridden. dtype must follow width (1 bit). - ifc #(.width(1), .cparam(cls#(1))) inst1(); + ifc #( + .width(1), + .cparam(cls #(1)) + ) inst1 (); // Same interface left at its default width (8 bits) must still work. - ifc inst8(); + ifc inst8 (); always_comb inst1.data = 1'b0; diff --git a/test_regress/t/t_oob_2state_array.v b/test_regress/t/t_oob_2state_array.v index 0fd01af2f..ac59838cb 100644 --- a/test_regress/t/t_oob_2state_array.v +++ b/test_regress/t/t_oob_2state_array.v @@ -4,9 +4,10 @@ // SPDX-FileCopyrightText: 2026 Antmicro // SPDX-License-Identifier: CC0-1.0 +// verilog_format: off `define stop $stop `define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); - +// verilog_format: on module t; logic clk = 1'b0; From 749b93e4053434590f3ee4358c06f2eb8624872f Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Thu, 18 Jun 2026 21:58:01 -0400 Subject: [PATCH 58/89] Commentary: Use standard multiline rst comments, other cleanups --- README.rst | 9 +++--- ci/docker/buildenv/README.rst | 5 ++-- ci/docker/run/README.rst | 5 ++-- docs/CONTRIBUTING.rst | 5 ++-- docs/README.rst | 5 ++-- docs/guide/changes.rst | 5 ++-- docs/guide/connecting.rst | 5 ++-- docs/guide/contributing.rst | 5 ++-- docs/guide/contributors.rst | 5 ++-- docs/guide/control.rst | 5 ++-- docs/guide/copyright.rst | 5 ++-- docs/guide/deprecations.rst | 5 ++-- docs/guide/environment.rst | 5 ++-- docs/guide/example_binary.rst | 5 ++-- docs/guide/example_cc.rst | 5 ++-- docs/guide/example_common_install.rst | 5 ++-- docs/guide/example_dist.rst | 5 ++-- docs/guide/example_sc.rst | 5 ++-- docs/guide/examples.rst | 5 ++-- docs/guide/exe_sim.rst | 9 +++--- docs/guide/exe_verilator.rst | 7 +++-- docs/guide/exe_verilator_coverage.rst | 5 ++-- docs/guide/exe_verilator_gantt.rst | 5 ++-- docs/guide/exe_verilator_profcfunc.rst | 5 ++-- docs/guide/executables.rst | 5 ++-- docs/guide/extensions.rst | 5 ++-- docs/guide/faq.rst | 5 ++-- docs/guide/files.rst | 11 ++++--- docs/guide/index.rst | 5 ++-- docs/guide/install-cmake.rst | 5 ++-- docs/guide/install.rst | 5 ++-- docs/guide/languages.rst | 5 ++-- docs/guide/overview.rst | 5 ++-- docs/guide/simulating.rst | 40 +++++++++++++------------- docs/guide/verilating.rst | 5 ++-- docs/guide/warnings.rst | 5 ++-- docs/internals.rst | 19 ++++++------ docs/security.rst | 7 +++-- 38 files changed, 148 insertions(+), 109 deletions(-) diff --git a/README.rst b/README.rst index bec2182ad..8dd591838 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,8 @@ -.. Github doesn't render images unless absolute URL -.. Do not know of a conditional tag, "only: github" nor "github display" works -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + Github doesn't render images unless absolute URL + Do not know of a conditional tag, "only: github" nor "github display" works + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 |badge1| |badge2| |badge3| |badge4| |badge5| |badge7| |badge8| diff --git a/ci/docker/buildenv/README.rst b/ci/docker/buildenv/README.rst index 146725466..e1648420d 100644 --- a/ci/docker/buildenv/README.rst +++ b/ci/docker/buildenv/README.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _verilator build docker container: diff --git a/ci/docker/run/README.rst b/ci/docker/run/README.rst index 1ef7f9fb4..4feaf0804 100644 --- a/ci/docker/run/README.rst +++ b/ci/docker/run/README.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Verilator Executable Docker Container ===================================== diff --git a/docs/CONTRIBUTING.rst b/docs/CONTRIBUTING.rst index 6567d22b4..bec0684c6 100644 --- a/docs/CONTRIBUTING.rst +++ b/docs/CONTRIBUTING.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Contributing to Verilator ========================= diff --git a/docs/README.rst b/docs/README.rst index 5ace9194a..706619968 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Verilator Documentation ======================= diff --git a/docs/guide/changes.rst b/docs/guide/changes.rst index 09264212b..743fc77b9 100644 --- a/docs/guide/changes.rst +++ b/docs/guide/changes.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 **************** Revision History diff --git a/docs/guide/connecting.rst b/docs/guide/connecting.rst index 65640aee9..67cad4ef3 100644 --- a/docs/guide/connecting.rst +++ b/docs/guide/connecting.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _connecting: diff --git a/docs/guide/contributing.rst b/docs/guide/contributing.rst index 9975c7512..3a2e57e81 100644 --- a/docs/guide/contributing.rst +++ b/docs/guide/contributing.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ******************************* Contributing and Reporting Bugs diff --git a/docs/guide/contributors.rst b/docs/guide/contributors.rst index 782a79865..2d31c91c8 100644 --- a/docs/guide/contributors.rst +++ b/docs/guide/contributors.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ************************ Contributors and Origins diff --git a/docs/guide/control.rst b/docs/guide/control.rst index c26159d56..8553de997 100644 --- a/docs/guide/control.rst +++ b/docs/guide/control.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _verilator control files: diff --git a/docs/guide/copyright.rst b/docs/guide/copyright.rst index 75b00d4ab..b5a59e1fe 100644 --- a/docs/guide/copyright.rst +++ b/docs/guide/copyright.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ********* Copyright diff --git a/docs/guide/deprecations.rst b/docs/guide/deprecations.rst index 75cad0492..7d69ab1c2 100644 --- a/docs/guide/deprecations.rst +++ b/docs/guide/deprecations.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 Deprecations ============ diff --git a/docs/guide/environment.rst b/docs/guide/environment.rst index 1b3fd5ed4..a66ebe755 100644 --- a/docs/guide/environment.rst +++ b/docs/guide/environment.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _environment: diff --git a/docs/guide/example_binary.rst b/docs/guide/example_binary.rst index fd36fe8ce..c02469907 100644 --- a/docs/guide/example_binary.rst +++ b/docs/guide/example_binary.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _example create-binary execution: diff --git a/docs/guide/example_cc.rst b/docs/guide/example_cc.rst index 2a8384e85..d50caaa1d 100644 --- a/docs/guide/example_cc.rst +++ b/docs/guide/example_cc.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _example c++ execution: diff --git a/docs/guide/example_common_install.rst b/docs/guide/example_common_install.rst index 0e38fa6ff..1c963f39d 100644 --- a/docs/guide/example_common_install.rst +++ b/docs/guide/example_common_install.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 First you need Verilator installed, see :ref:`Installation`. In brief, if you installed Verilator using the package manager of your operating system, diff --git a/docs/guide/example_dist.rst b/docs/guide/example_dist.rst index f3e1765f5..8427394c0 100644 --- a/docs/guide/example_dist.rst +++ b/docs/guide/example_dist.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _examples in the distribution: diff --git a/docs/guide/example_sc.rst b/docs/guide/example_sc.rst index 349645080..06ec49070 100644 --- a/docs/guide/example_sc.rst +++ b/docs/guide/example_sc.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _example systemc execution: diff --git a/docs/guide/examples.rst b/docs/guide/examples.rst index 5e0b2e260..1702ac513 100644 --- a/docs/guide/examples.rst +++ b/docs/guide/examples.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _examples: diff --git a/docs/guide/exe_sim.rst b/docs/guide/exe_sim.rst index 14fecf63e..ac2b73f16 100644 --- a/docs/guide/exe_sim.rst +++ b/docs/guide/exe_sim.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _simulation runtime arguments: @@ -50,8 +51,8 @@ Options: .. option:: +verilator+log+file+ - Log all stdout and stderr to the specified output filename. If not specified - the normal stdout/stderr streams are used. + Log all stdout and stderr to the specified output filename. If not specified + the normal stdout/stderr streams are used. .. option:: +verilator+noassert diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 72da2961f..2be7d5bf4 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 =================== verilator Arguments @@ -669,7 +670,7 @@ Summary: .. option:: -fno-case-tree - Rarely needed. Disable converting case statements into bitwise branch trees. + Rarely needed. Disable converting case statements into bit-wise branch trees. .. option:: -fno-combine diff --git a/docs/guide/exe_verilator_coverage.rst b/docs/guide/exe_verilator_coverage.rst index b5f0598c2..388f67cfb 100644 --- a/docs/guide/exe_verilator_coverage.rst +++ b/docs/guide/exe_verilator_coverage.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 verilator_coverage ================== diff --git a/docs/guide/exe_verilator_gantt.rst b/docs/guide/exe_verilator_gantt.rst index 5b45969f9..b9944575c 100644 --- a/docs/guide/exe_verilator_gantt.rst +++ b/docs/guide/exe_verilator_gantt.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 verilator_gantt =============== diff --git a/docs/guide/exe_verilator_profcfunc.rst b/docs/guide/exe_verilator_profcfunc.rst index c79f86436..a29a5a34c 100644 --- a/docs/guide/exe_verilator_profcfunc.rst +++ b/docs/guide/exe_verilator_profcfunc.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 verilator_profcfunc =================== diff --git a/docs/guide/executables.rst b/docs/guide/executables.rst index ac4e3f85e..7d86091b5 100644 --- a/docs/guide/executables.rst +++ b/docs/guide/executables.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ********************************* Executable and Argument Reference diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index 4ea636af0..8e8d5d183 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 =================== Language Extensions diff --git a/docs/guide/faq.rst b/docs/guide/faq.rst index ec55a9a9c..e0b184385 100644 --- a/docs/guide/faq.rst +++ b/docs/guide/faq.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ****************************** FAQ/Frequently Asked Questions diff --git a/docs/guide/files.rst b/docs/guide/files.rst index 2e5fa2225..d50b291df 100644 --- a/docs/guide/files.rst +++ b/docs/guide/files.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ***** Files @@ -163,9 +164,11 @@ The Verilated executable may produce the following: .. list-table:: * - coverage.dat - - Code coverage output, and default input filename for :command:`verilator_coverage` + - Code coverage output, and default input filename for + :command:`verilator_coverage` * - gmon.out - - GCC/clang code profiler output, often fed into :command:`verilator_profcfunc` + - GCC/clang code profiler output, often fed into + :command:`verilator_profcfunc` * - profile.vlt - --prof-pgo data file for :ref:`Thread PGO` * - profile_exec.dat diff --git a/docs/guide/index.rst b/docs/guide/index.rst index 5df45c271..8b7271352 100644 --- a/docs/guide/index.rst +++ b/docs/guide/index.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ###################### Verilator User's Guide diff --git a/docs/guide/install-cmake.rst b/docs/guide/install-cmake.rst index 03566ac01..56723842b 100644 --- a/docs/guide/install-cmake.rst +++ b/docs/guide/install-cmake.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _cmakeinstallation: diff --git a/docs/guide/install.rst b/docs/guide/install.rst index 09a58a94f..59b29b2cf 100644 --- a/docs/guide/install.rst +++ b/docs/guide/install.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _installation: diff --git a/docs/guide/languages.rst b/docs/guide/languages.rst index 04fb44ba4..b0d2f57f5 100644 --- a/docs/guide/languages.rst +++ b/docs/guide/languages.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 *************** Input Languages diff --git a/docs/guide/overview.rst b/docs/guide/overview.rst index 5ffe52ed2..7bd01c1b8 100644 --- a/docs/guide/overview.rst +++ b/docs/guide/overview.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ******** Overview diff --git a/docs/guide/simulating.rst b/docs/guide/simulating.rst index 89baf9ccc..168a02414 100644 --- a/docs/guide/simulating.rst +++ b/docs/guide/simulating.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 .. _simulating: @@ -218,8 +219,8 @@ Covergroup Coverage With :vlopt:`--coverage` or :vlopt:`--coverage-user`, Verilator will translate covergroup coverage points the user has inserted manually in -SystemVerilog code into the Verilated model. Verilator supports -coverpoints with value and transition bins, and cross points. +SystemVerilog code into the Verilated model. Verilator supports coverpoints +with value and transition bins, and cross points. .. _fsm coverage: @@ -244,15 +245,15 @@ encodings in these common forms: - Single-process FSMs, whose state dispatch is written as ``case (state)`` or as a top-level ``if`` / ``else if`` chain comparing the same state variable against known state values -- Two-process and three-block FSMs, where a clocked state register is paired - with a combinational next-state block using the same supported +- Two-process and three-block FSMs, where a clocked state register is + paired with a combinational next-state block using the same supported ``case`` or top-level ``if`` / ``else if`` dispatch forms -Scalar state encodings may be wider than 32 bits. This allows sparse -state encodings, such as high-Hamming-distance enum or localparam values, -to be preserved in the detected FSM model. Verilator uses the declared -enum item name, parameter name, or localparam name as the reported state -label where possible. +Scalar state encodings may be wider than 32 bits. This allows sparse state +encodings, such as high-Hamming-distance enum or localparam values, to be +preserved in the detected FSM model. Verilator uses the declared enum item +name, parameter name, or localparam name as the reported state label where +possible. Simple input guards are supported when they appear inside a recognized state branch, or as a top-level conjunction containing exactly one state @@ -275,10 +276,10 @@ the extracted coverage model: - ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch arcs that would otherwise be skipped by the conservative extractor. -State registers may also be wrapped by a transparent instance, for -example a project flop wrapper or primitive. Such wrappers must be -described explicitly with a VLT command file action before Verilator will -use their data, state, clock, or reset connections for FSM extraction: +State registers may also be wrapped by a transparent instance, for example +a project flop wrapper or primitive. Such wrappers must be described +explicitly with a VLT command file action before Verilator will use their +data, state, clock, or reset connections for FSM extraction: .. code-block:: sv @@ -298,11 +299,10 @@ Optional reset metadata may also be supplied: fsm_register_wrapper -module "my_fsm_flop" -d "state_i" -q "state_o" -clock "clk_i" \ -reset "rst_ni" -reset_value "ResetValue" -Reset arcs are emitted only when the configured reset port has an -inferable edge in the wrapper and the configured reset value parameter is -statically resolvable. If reset metadata is incomplete, Verilator warns -and may still emit FSM state and transition coverage, but reset arcs are -omitted. +Reset arcs are emitted only when the configured reset port has an inferable +edge in the wrapper and the configured reset value parameter is statically +resolvable. If reset metadata is incomplete, Verilator warns and may still +emit FSM state and transition coverage, but reset arcs are omitted. Reset transitions are included in the collected data either way. By default, :command:`verilator_coverage` summarizes reset-only arcs rather diff --git a/docs/guide/verilating.rst b/docs/guide/verilating.rst index 977d73c38..65a6f2d07 100644 --- a/docs/guide/verilating.rst +++ b/docs/guide/verilating.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 ********** Verilating diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index db41d8405..209a52ebb 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -1,5 +1,6 @@ -.. SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +.. + SPDX-FileCopyrightText: 2003-2026 Wilson Snyder + SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 =================== Errors and Warnings diff --git a/docs/internals.rst b/docs/internals.rst index 7b86beff6..241c4b14a 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -732,8 +732,8 @@ event `a` was called first - which is necessary to know. There are two functions for managing timing logic called by ``_eval()``: -* ``_timing_ready()``, which commits all coroutines whose triggers were - not set in the current iteration, +* ``_timing_ready()``, which commits all coroutines whose triggers were not + set in the current iteration, * ``_timing_resume()``, which calls `resume()` on all trigger and delay schedulers whose triggers were set in the current iteration. @@ -937,14 +937,15 @@ macro-task's dataset fits in one core's local caches. To achieve spatial locality, we tag each variable with the set of macro-tasks that access it. Let's call this set the "footprint" of that -variable. The variables in a given module have a set of footprints. We group -variables with identical non-empty footprints, emit those groups in deterministic -footprint-key order, then emit variables with no footprint information last. +variable. The variables in a given module have a set of footprints. We +group variables with identical non-empty footprints, emit those groups in +deterministic footprint-key order, then emit variables with no footprint +information last. -The first emitted variable in each footprint group is aligned to a cache-line -boundary. This avoids false sharing between different macro-task footprints -without building a complete pairwise-distance graph over all footprints, which -would use excessive memory on very large models. +The first emitted variable in each footprint group is aligned to a +cache-line boundary. This avoids false sharing between different macro-task +footprints without building a complete pairwise-distance graph over all +footprints, which would use excessive memory on very large models. This is an old idea. Simulators designed at DEC in the early 1990s used similar techniques to optimize both single-thread and multithread modes. diff --git a/docs/security.rst b/docs/security.rst index 4080bad92..3adb6c8d9 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -1,6 +1,7 @@ -.. for github, vim: syntax=reStructuredText -.. SPDX-FileCopyrightText: 2025-2026 Wilson Snyder -.. SPDX-License-Identifier: CC0-1.0 +.. + for github, vim: syntax=reStructuredText + SPDX-FileCopyrightText: 2025-2026 Wilson Snyder + SPDX-License-Identifier: CC0-1.0 Security Policy =============== From 59fba72cb60d8329d1e4386324308777adef1bf2 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Fri, 19 Jun 2026 14:41:48 +0200 Subject: [PATCH 59/89] Support method calls on a sub-interface via a virtual interface (#7800) --- src/V3Width.cpp | 26 ++++++- .../t/t_interface_virtual_sub_iface_method.py | 18 +++++ .../t/t_interface_virtual_sub_iface_method.v | 71 +++++++++++++++++++ 3 files changed, 112 insertions(+), 3 deletions(-) create mode 100755 test_regress/t/t_interface_virtual_sub_iface_method.py create mode 100644 test_regress/t/t_interface_virtual_sub_iface_method.v diff --git a/src/V3Width.cpp b/src/V3Width.cpp index dbbea7022..86d688cc7 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3811,7 +3811,7 @@ class WidthVisitor final : public VNVisitor { if (!varp->didWidth()) userIterate(varp, nullptr); nodep->dtypep(foundp->dtypep()); nodep->varp(varp); - AstIface* const ifacep = adtypep->ifacep(); + AstIface* const ifacep = adtypep->ifaceViaCellp(); varp->sensIfacep(ifacep); nodep->didWidth(true); return; @@ -3852,7 +3852,25 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(viftopVarp, nodep, "No __Viftop variable for sub-interface cell"); if (!viftopVarp->didWidth()) userIterate(viftopVarp, nullptr); - nodep->dtypep(viftopVarp->dtypep()); + AstNodeDType* subDtypep = viftopVarp->dtypep(); + if (adtypep->isVirtual()) { + // A sub-interface selected through a virtual interface + // handle is itself a virtual reference (a runtime + // instance pointer), not a static instance. Mark the + // dtype virtual so a method call on it dispatches per + // instance instead of inlining to one fixed scope. + if (AstIfaceRefDType* const subRefp + = VN_CAST(subDtypep->skipRefp(), IfaceRefDType)) { + AstIfaceRefDType* const newDtypep = new AstIfaceRefDType{ + nodep->fileline(), subRefp->cellName(), subRefp->ifaceName()}; + newDtypep->ifacep(subRefp->ifacep()); + newDtypep->cellp(subRefp->cellp()); + newDtypep->isVirtual(true); + v3Global.rootp()->typeTablep()->addTypesp(newDtypep); + subDtypep = newDtypep; + } + } + nodep->dtypep(subDtypep); nodep->varp(viftopVarp); viftopVarp->sensIfacep(VN_AS(cellp->modp(), Iface)); nodep->didWidth(true); @@ -4803,7 +4821,9 @@ class WidthVisitor final : public VNVisitor { } } void methodCallIfaceRef(AstMethodCall* nodep, AstIfaceRefDType* adtypep) { - AstIface* const ifacep = adtypep->ifacep(); + // ifaceViaCellp() resolves the interface via cellp when ifacep is null, + // as for a sub-interface selected through a virtual interface handle. + AstIface* const ifacep = adtypep->ifaceViaCellp(); UINFO(5, __FUNCTION__ << ":" << nodep); if (AstNodeFTask* const ftaskp = VN_CAST(m_memberMap.findMember(ifacep, nodep->name()), NodeFTask)) { diff --git a/test_regress/t/t_interface_virtual_sub_iface_method.py b/test_regress/t/t_interface_virtual_sub_iface_method.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_interface_virtual_sub_iface_method.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_sub_iface_method.v b/test_regress/t/t_interface_virtual_sub_iface_method.v new file mode 100644 index 000000000..6cf486f5b --- /dev/null +++ b/test_regress/t/t_interface_virtual_sub_iface_method.v @@ -0,0 +1,71 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 +// verilog_format: off +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); +// verilog_format: on + +interface inner_if; + logic [7:0] cnt = 0; + function automatic void bump(); + cnt = cnt + 1; + endfunction + function automatic logic [7:0] get(); + return cnt; + endfunction + task automatic addn(input logic [7:0] n); + cnt = cnt + n; + endtask +endinterface + +interface mid_if; + inner_if sub (); +endinterface + +interface data_if; + logic [7:0] val = 0; +endinterface + +interface outer_if; + inner_if a (); + inner_if b (); + mid_if m (); + data_if d (); +endinterface + +class driver_c; + virtual outer_if vif; + task run(); + vif.a.bump(); // sibling a: void function, +1 + vif.b.bump(); // sibling b: void function, +1 + vif.b.addn(8'd5); // sibling b: task with arg, +5 + vif.m.sub.bump(); // two-level nesting, +1 + vif.d.val = 8'd99; // sub-interface variable write via vif (no method) + endtask + function logic [7:0] read_a(); + return vif.a.get(); // function returning value via vif sub-interface + endfunction +endclass + +module t; + outer_if oif (); + driver_c drv; + initial begin + drv = new(); + drv.vif = oif; + drv.run(); + // Per-instance dispatch: a, b and m.sub are distinct instances. + `checkd(oif.a.cnt, 8'd1) + `checkd(oif.b.cnt, 8'd6) + `checkd(oif.m.sub.cnt, 8'd1) + `checkd(drv.read_a(), 8'd1) + `checkd(oif.d.val, 8'd99) + // Direct (non-virtual) sub-interface method call: covers the non-virtual dtype path. + oif.a.bump(); + `checkd(oif.a.cnt, 8'd2) + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From a37e2ee94beb7fde8c7ccfa016e3dc69535890a1 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 19 Jun 2026 19:46:13 +0100 Subject: [PATCH 60/89] Optimize wide decoder case statements into decoder expressions (#7804) Extend the decoder-pattern case optimization to selectors that are too wide for a full 2^width lookup table. A decoder-pattern case (where every case item assigns constants to a fixed set of LHSs) is lowered to a new AstMachMasked expression. AstMachMasked is emitted as a run-time VL_MATCHMASKEd_* function call. It contains a packed constant pool table, 'matchp', which is a list of '(mask, bits)' pairs. At runtime, the index of the first matching entry is returned, and is used to index a value table. This single (albeit complicated) expression can replace large if-else trees whole, resulting in much more compact code with fewer static hard to predict branches. It is worth about 10% speed and 30% code size in some designs. Example: ```systemverilog logic [39:0] sel; always_comb casez (sel) 40'b???????????????????????????????????????1: out = 8'h01; 40'b??????????????????????????????????????1?: out = 8'h02; 40'b?????????????????????????????????????1??: out = 8'h03; default: out = 8'hff; endcase ``` is compiled to: ```c++ out = TABLE_value[VL_MATCHMASKED_Q(sel, CONST_match)]; ``` Where 'CONST_match' contains 4 entries, of a 40-bit mask and 40-bit bit pattern each, and 'TABLE_value' contains 4 entries of the corresponding 8-bit results. (Entries are aligned to word boundaries to avoid runtime bit swizzling) --- docs/guide/exe_verilator.rst | 4 + include/verilated_funcs.h | 35 ++ src/V3AstInlines.h | 7 + src/V3AstNodeExpr.h | 16 + src/V3AstNodes.cpp | 20 + src/V3Case.cpp | 243 +++++++--- src/V3Clean.cpp | 5 + src/V3Const.cpp | 12 +- src/V3Dfg.cpp | 14 + src/V3Dfg.h | 7 +- src/V3DfgBreakCycles.cpp | 52 +- src/V3DfgCse.cpp | 2 + src/V3DfgDfgToAst.cpp | 6 + src/V3DfgPeephole.cpp | 10 + src/V3DfgPeepholePatterns.h | 1 + src/V3DfgSynthesize.cpp | 23 + src/V3DfgVertices.h | 15 + src/V3EmitCFunc.h | 3 + src/V3Gate.cpp | 5 + src/V3Options.cpp | 3 + src/V3Options.h | 2 + src/V3Premit.cpp | 8 + test_regress/t/t_case_decoder.py | 20 + test_regress/t/t_case_decoder.v | 524 +++++++++++++++++++++ test_regress/t/t_case_decoder_off.py | 22 + test_regress/t/t_dfg_break_cycles.v | 82 ++++ test_regress/t/t_dfg_peephole.v | 46 +- test_regress/t/t_opt_table_enum.py | 2 +- test_regress/t/t_opt_table_packed_array.py | 2 +- test_regress/t/t_opt_table_real.py | 2 +- test_regress/t/t_opt_table_same.py | 2 +- test_regress/t/t_opt_table_signed.py | 2 +- test_regress/t/t_opt_table_string.py | 2 +- test_regress/t/t_opt_table_struct.py | 2 +- 34 files changed, 1114 insertions(+), 87 deletions(-) create mode 100755 test_regress/t/t_case_decoder.py create mode 100644 test_regress/t/t_case_decoder.v create mode 100755 test_regress/t/t_case_decoder_off.py diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 2be7d5bf4..4e302f06d 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -664,6 +664,10 @@ Summary: Alias for all other `-fno-case-*` options. +.. option:: -fno-case-decoder + + Rarely needed. Disable converting case statements into decoder tables. + .. option:: -fno-case-table Rarely needed. Disable converting case statements into table lookups. diff --git a/include/verilated_funcs.h b/include/verilated_funcs.h index 8aebd1e34..d69bca256 100644 --- a/include/verilated_funcs.h +++ b/include/verilated_funcs.h @@ -269,6 +269,41 @@ static inline VlQueue VL_CVT_UNPACK_TO_Q(const VlUnpacked& q) VL_ return ret; } +// Masked match functions +static inline IData VL_MATCHMASKED_I(int, IData lhs, WDataInP matchp) VL_PURE { + size_t i = 0; + while (true) { + const IData mask = matchp[i * 2]; + const IData bits = matchp[i * 2 + 1]; + if ((mask & lhs) == bits) break; + ++i; + } + return i; +} +static inline IData VL_MATCHMASKED_Q(int, QData lhs, WDataInP matchp) VL_PURE { + size_t i = 0; + while (true) { + const QData mask = VL_SET_QW(matchp + i * 4); + const QData bits = VL_SET_QW(matchp + i * 4 + 2); + if ((mask & lhs) == bits) break; + ++i; + } + return i; +} +static inline IData VL_MATCHMASKED_W(int lbits, WDataInP lhsp, WDataInP matchp) VL_MT_SAFE { + const int iwords = VL_WORDS_I(lbits); + size_t i = 0; + while (true) { + const WDataInP maskp = matchp + (i * iwords * 2); + const WDataInP bitsp = matchp + (i * iwords * 2 + iwords); + EData diff = 0; + for (int j = 0; j < iwords; ++j) diff |= (maskp[j] & lhsp[j]) ^ bitsp[j]; + if (!diff) break; + ++i; + } + return i; +} + // Return double from lhs (numeric) unsigned double VL_ITOR_D_W(int lbits, WDataInP const lwp) VL_PURE; static inline double VL_ITOR_D_I(int, IData lhs) VL_PURE { diff --git a/src/V3AstInlines.h b/src/V3AstInlines.h index 8a4477060..6ef16eec6 100644 --- a/src/V3AstInlines.h +++ b/src/V3AstInlines.h @@ -163,6 +163,13 @@ bool AstVar::sameNode(const AstNode* samep) const { return m_name == asamep->m_name && varType() == asamep->varType(); } +AstMatchMasked::AstMatchMasked(FileLine* fl, AstNodeExpr* lhsp, AstVarScope* matchp) + : ASTGEN_SUPER_MatchMasked(fl) { + this->lhsp(lhsp); + this->matchp(new AstVarRef{fl, matchp, VAccess::READ}); + dtypeSetUInt32(); +} + AstVarRef::AstVarRef(FileLine* fl, AstVar* varp, const VAccess& access) : ASTGEN_SUPER_VarRef(fl, varp, access) { if (v3Global.assertDTypesResolved()) { diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index f6e0d98f5..91d689227 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -1855,6 +1855,22 @@ public: bool index() const { return m_index; } bool isExprCoverageEligible() const override { return false; } }; +class AstMatchMasked final : public AstNodeExpr { + // This is a non-source construct, created internally to represent + // some case statements. It is a '(mask & _) == bits' matching loop + // where {mask, bits} pairs are packed into a single wide 'matchp', + // and the result is the index of the first matching entry. + // See VL_DECODER_* runtime functions. + // @astgen op1 := lhsp : AstNodeExpr + // @astgen op2 := matchp : AstVarRef +public: + inline AstMatchMasked(FileLine* fl, AstNodeExpr* lhsp, AstVarScope* matchp); + ASTGEN_MEMBERS_AstMatchMasked; + string emitVerilog() override { V3ERROR_NA_RETURN(""); } + string emitC() override { return "VL_MATCHMASKED_%lq(%lw, %li, %ri)"; } + bool cleanOut() const override { return true; } + static uint32_t fold(const V3Number& lhs, AstVar* matchVarp); +}; class AstMatches final : public AstNodeExpr { // "matches" operator: "expr matches pattern" // @astgen op1 := lhsp : AstNodeExpr // Expression to match diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 3b35037a8..85b077053 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -546,6 +546,26 @@ AstConst* AstConst::parseParamLiteral(FileLine* fl, const string& literal) { string AstConstraintRef::name() const { return constrp()->name(); } +uint32_t AstMatchMasked::fold(const V3Number& lhs, AstVar* matchVarp) { + const V3Number& numTable = VN_AS(matchVarp->valuep(), Const)->num(); + V3Number numMask{matchVarp, lhs.width(), 0}; + V3Number numBits{matchVarp, lhs.width(), 0}; + V3Number numAnd{matchVarp, lhs.width(), 0}; + const int width = lhs.width(); + const int entryWidth = VL_WORDS_I(width) * VL_EDATASIZE; + uint32_t i = 0; + while (true) { + const int lsb = 2 * i * entryWidth; + const int msb = lsb + width - 1; + numMask.opSel(numTable, msb, lsb); + numBits.opSel(numTable, msb + entryWidth, lsb + entryWidth); + numAnd.opAnd(numMask, lhs); + if (numAnd.isCaseEq(numBits)) break; + ++i; + } + return i; +} + AstNetlist::AstNetlist() : ASTGEN_SUPER_Netlist(new FileLine{FileLine::builtInFilename()}) , m_typeTablep{new AstTypeTable{fileline()}} diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 251c67202..c4d4fbc21 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -166,6 +166,7 @@ class CaseVisitor final : public VNVisitor { // STATE // Statistics tracking, as a struct so can be passed to 'const' methods struct Stats final { + VDouble0 caseDecoder; // Cases using decoder method VDouble0 caseTableNormal; // Cases using table method with normal table VDouble0 caseTableTiny; // Cases using table method with tiny table VDouble0 caseFast; // Cases using fast bit tree method @@ -540,15 +541,14 @@ class CaseVisitor final : public VNVisitor { lhsRecord.offset = m_caseDecoderEntryWidth; m_caseDecoderEntryWidth += width; } - // Also align the whole entry width to a word boundary - m_caseDecoderEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE; // Check the table fits max size - if (fitsLimit(m_caseDecoderEntryWidth, CASE_TABLE_MAX_BITS)) { - m_caseTableWidth = m_caseDecoderEntryWidth << caseWidth; // Can optimize + const size_t alignedEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE; + if (fitsLimit(alignedEntryWidth, CASE_TABLE_MAX_BITS)) { + m_caseTableWidth = alignedEntryWidth << caseWidth; // Can optimize return; } - // Can't optimize - yet ... + // Can optimize as AstMatchMasked, no other info needed } // Analyze case statement. Updates 'm_case*' members. Reports warnings. @@ -593,8 +593,8 @@ class CaseVisitor final : public VNVisitor { for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { // Check conditions for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { - // Count conditions - ++m_caseNConditions; + // Count conditions that can actually match. + if (!neverItem(nodep, VN_AS(condp, NodeExpr))) ++m_caseNConditions; // Mark opaque if non-constant condition if (!VN_IS(condp, Const)) { m_caseOpaque = true; @@ -619,6 +619,50 @@ class CaseVisitor final : public VNVisitor { if (canBeDecoder) analyzeDecoderPattern(nodep); } + AstNodeStmt* connectDecoderOutputs(AstCase* nodep, AstNodeExpr* exprp, + const char* tmpPrefixp) { + FileLine* const flp = nodep->fileline(); + + // If there is only one LHS, just use the result + if (m_caseDecoderRecords.size() == 1) { + const LhsRecord& lhsRecord = m_caseDecoderRecords[0]; + const int width = lhsRecord.lhsp->width(); + AstNodeExpr* const rhsp + = exprp->width() == width ? exprp : new AstSel{flp, exprp, 0, width}; + AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false); + if (lhsRecord.nCaseAssigns) { + return new AstAssign{flp, lhsp, rhsp}; + } else if (lhsRecord.nCaseAssignDlys) { + return new AstAssignDly{flp, lhsp, rhsp}; + } else { + nodep->v3fatalSrc("Unknown assignment type"); + } + } + + // There are multiple LHSs, store the lookup result in a temporary + const std::string name = tmpPrefixp + std::to_string(m_nTmps++); + AstVarScope* const tempVscp = m_scopep->createTemp(name, m_caseDecoderEntryWidth); + AstNodeExpr* const tempWritep = new AstVarRef{flp, tempVscp, VAccess::WRITE}; + AstNodeStmt* const resultp = new AstAssign{flp, tempWritep, exprp}; + + // For each LHS, select out the result + for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { + const int width = lhsRecord.lhsp->width(); + const int lsb = lhsRecord.offset; + AstNodeExpr* const tempReadp = new AstVarRef{flp, tempVscp, VAccess::READ}; + AstNodeExpr* const rhsp = new AstSel{flp, tempReadp, lsb, width}; + AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false); + if (lhsRecord.nCaseAssigns) { + resultp->addNext(new AstAssign{flp, lhsp, rhsp}); + } else if (lhsRecord.nCaseAssignDlys) { + resultp->addNext(new AstAssignDly{flp, lhsp, rhsp}); + } else { + nodep->v3fatalSrc("Unknown assignment type"); + } + } + return resultp; + } + AstNodeStmt* convertCaseTable(AstCase* nodep) { // Create the table constant FileLine* const flp = nodep->fileline(); @@ -626,7 +670,17 @@ class CaseVisitor final : public VNVisitor { = new AstConst{flp, AstConst::WidthedValue{}, static_cast(m_caseTableWidth), 0}; const uint32_t tableEntries = 1U << nodep->exprp()->width(); - // Populate the table + const bool isTinyTable = m_caseTableWidth <= CASE_TABLE_TINY_BITS; + if (isTinyTable) { + ++m_stats.caseTableTiny; + } else { + ++m_stats.caseTableNormal; + } + + // Populate the table. Align entries to a word boundary to avoid bit swizzling at runtime. + const uint32_t entryWidth = isTinyTable + ? m_caseDecoderEntryWidth + : VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE; for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { const int lhsWidth = lhsRecord.lhsp->width(); const int lhsOffset = lhsRecord.offset; @@ -635,7 +689,7 @@ class CaseVisitor final : public VNVisitor { if (lhsRecord.preDefaultp) { AstConst* const rhsp = VN_AS(lhsRecord.preDefaultp->rhsp(), Const); for (uint32_t index = 0; index < tableEntries; ++index) { - const uint32_t tableOffset = index * m_caseDecoderEntryWidth + lhsOffset; + const uint32_t tableOffset = index * entryWidth + lhsOffset; tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); } } @@ -656,7 +710,7 @@ class CaseVisitor final : public VNVisitor { // If default, broadcast it if (cip->isDefault()) { for (uint32_t index = 0; index < tableEntries; ++index) { - const uint32_t tableOffset = index * m_caseDecoderEntryWidth + lhsOffset; + const uint32_t tableOffset = index * entryWidth + lhsOffset; tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); } continue; @@ -674,7 +728,7 @@ class CaseVisitor final : public VNVisitor { // i.e.: all don't care values masked out for (uint32_t i = inverseMask; true; i = (i - 1) & inverseMask) { const uint32_t index = i | matchBits; - const uint32_t tableOffset = index * m_caseDecoderEntryWidth + lhsOffset; + const uint32_t tableOffset = index * entryWidth + lhsOffset; tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth); if (!i) break; } @@ -684,11 +738,7 @@ class CaseVisitor final : public VNVisitor { // Create the table in the constant pool, unless using an inline table AstVarScope* const tableVscp = [&]() -> AstVarScope* { - if (m_caseTableWidth <= CASE_TABLE_TINY_BITS) { - ++m_stats.caseTableTiny; - return nullptr; - } - ++m_stats.caseTableNormal; + if (isTinyTable) return nullptr; AstVarScope* vscp = v3Global.rootp()->constPoolp()->findConst(tablep, true); VL_DO_DANGLING(tablep->deleteTree(), tablep); // findConst clones return vscp; @@ -700,49 +750,121 @@ class CaseVisitor final : public VNVisitor { : static_cast(tablep); AstNodeExpr* const caseExprp = new AstExtend{flp, nodep->exprp()->cloneTreePure(false), 32}; - AstNodeExpr* const scalep - = new AstConst{flp, static_cast(m_caseDecoderEntryWidth)}; + AstNodeExpr* const scalep = new AstConst{flp, entryWidth}; AstNodeExpr* const tableLsbp = new AstMul{flp, scalep, caseExprp}; - - // If there is only one LHS, just use the result - if (m_caseDecoderRecords.size() == 1) { - const LhsRecord& lhsRecord = m_caseDecoderRecords[0]; - const int width = lhsRecord.lhsp->width(); - AstNodeExpr* const rhsp = new AstSel{flp, tableRefp, tableLsbp, width}; - AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false); - if (lhsRecord.nCaseAssigns) { - return new AstAssign{flp, lhsp, rhsp}; - } else if (lhsRecord.nCaseAssignDlys) { - return new AstAssignDly{flp, lhsp, rhsp}; - } else { - nodep->v3fatalSrc("Unknown assignment type"); - } - } - - // There are multiple LHSs, store the lookup result in a temporary - const std::string name = "__VcaseTableOut" + std::to_string(m_nTmps++); - AstVarScope* const tempVscp = m_scopep->createTemp(name, m_caseDecoderEntryWidth); - AstNodeExpr* const tempWritep = new AstVarRef{flp, tempVscp, VAccess::WRITE}; AstNodeExpr* const tableSelp = new AstSel{flp, tableRefp, tableLsbp, static_cast(m_caseDecoderEntryWidth)}; - AstNodeStmt* const resultp = new AstAssign{flp, tempWritep, tableSelp}; - // For each LHS, select out the result - for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { - const int width = lhsRecord.lhsp->width(); - const int lsb = lhsRecord.offset; - AstNodeExpr* const tempReadp = new AstVarRef{flp, tempVscp, VAccess::READ}; - AstNodeExpr* const rhsp = new AstSel{flp, tempReadp, lsb, width}; - AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false); - if (lhsRecord.nCaseAssigns) { - resultp->addNext(new AstAssign{flp, lhsp, rhsp}); - } else if (lhsRecord.nCaseAssignDlys) { - resultp->addNext(new AstAssignDly{flp, lhsp, rhsp}); - } else { - nodep->v3fatalSrc("Unknown assignment type"); + // Connect outputs + return connectDecoderOutputs(nodep, tableSelp, "__VcaseTableOut"); + } + + AstNodeStmt* convertCaseDecoder(AstCase* nodep) { + ++m_stats.caseDecoder; + + FileLine* const flp = nodep->fileline(); + + // Gather all the case conditions, paird with their statements. A 'nullptr' condition + // matches anything (the default case, or the catch-all added below). + std::vector> clauses; // (condition, item statements) + clauses.reserve(m_caseNConditions + 1); + for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + if (cip->isDefault()) { + clauses.emplace_back(nullptr, cip->stmtsp()); + continue; + } + for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { + AstConst* const iconstp = VN_AS(condp, Const); + // Skip items that can never match in 2-state simulation (e.g. X in casez) + if (neverItem(nodep, iconstp)) continue; + clauses.emplace_back(iconstp, cip->stmtsp()); } } - return resultp; + // If the case has no default item and is not provably exhaustive, unmatched selector + // values fall back to the pre-defaults. Represent that with a catch-all clause (null + // condition and no statements, so every LHS uses its pre-default). 'analyzeDecoderPattern' + // guarantees every LHS has a pre-default in this case. + const bool provenExhaustive = m_caseDetailsValid && m_caseDetails.exhaustive + && !m_caseDetails.exhaustiveOverEnumOnly; + if (clauses.back().first && !provenExhaustive) clauses.emplace_back(nullptr, nullptr); + + // Number of entries in decoder table + const int decoderEnries = clauses.size(); + + // Build the match table: a {matchBits, matchMask} packed pair per clause, shared by all + // LHSs. Each field is rounded up to a whole EDATA word boundary to avoid bit swizzling + // at runtime. We use a packed value so the runtime function can take a VlWide pointer + // without templating on the array size. + const int condWidth = nodep->exprp()->width(); + const int matchWidth = 2 * VL_WORDS_I(condWidth) * VL_EDATASIZE; + AstConst* const matchp + = new AstConst{flp, AstConst::WidthedValue{}, decoderEnries * matchWidth, 0}; + for (int i = 0; i < decoderEnries; ++i) { + const int entryLsb = i * matchWidth; + + // If the entry has a condition, use it's match bits and mask + if (AstConst* const condp = clauses[i].first) { + const auto& match = matchPattern(nodep, condp); + matchp->num().opSelInto(match.first, entryLsb, condWidth); + matchp->num().opSelInto(match.second, entryLsb + matchWidth / 2, condWidth); + continue; + } + + // Otherwise use zero for mask and bits, which matches anything + V3Number numZero{flp, condWidth, 0}; + matchp->num().opSelInto(numZero, entryLsb, condWidth); + matchp->num().opSelInto(numZero, entryLsb + matchWidth / 2, condWidth); + } + + // Create the table initializer + AstRange* const rangep = new AstRange{flp, decoderEnries - 1, 0}; + AstNodeDType* const entryDtypep = nodep->findBitDType( + m_caseDecoderEntryWidth, m_caseDecoderEntryWidth, VSigning::UNSIGNED); + AstNodeDType* const tableDtypep = new AstUnpackArrayDType{flp, entryDtypep, rangep}; + v3Global.rootp()->typeTablep()->addTypesp(tableDtypep); + AstInitArray* const tablep = new AstInitArray{flp, tableDtypep, nullptr}; + + // Build a single value table for all LHSs: one entry per clause, packing each LHS's value + // at its offset. The entry width is the table packing computed by 'analyzeDecoderPattern' + // Rounded up to a whole EDATA word boundary to avoid bit swizzling at runtime. + for (int i = 0; i < decoderEnries; ++i) { + AstNode* const stmtsp = clauses[i].second; + AstConst* const entryp = new AstConst{flp, AstConst::WidthedValue{}, + static_cast(m_caseDecoderEntryWidth), 0}; + for (const LhsRecord& lhsRecord : m_caseDecoderRecords) { + AstNodeExpr* const lhsp = lhsRecord.lhsp; + // Find the value assigned to this LHS in the clause's statements + AstConst* valConstp = nullptr; + for (AstNode* stmtp = stmtsp; stmtp; stmtp = stmtp->nextp()) { + AstNodeAssign* const assignp = VN_AS(stmtp, NodeAssign); + if (!lhsp->sameTree(assignp->lhsp())) continue; + valConstp = VN_AS(assignp->rhsp(), Const); + break; + } + // Not assigned in this clause, so use the pre-assigned default + if (!valConstp) { + UASSERT_OBJ(lhsRecord.preDefaultp, nodep, + "Decoder LHS unassigned in case item without a pre-default"); + valConstp = VN_AS(lhsRecord.preDefaultp->rhsp(), Const); + } + entryp->num().opSelInto(valConstp->num(), lhsRecord.offset, lhsp->width()); + } + tablep->addIndexValuep(i, entryp); + } + + // Create the tables + AstVarScope* const matchVscp = v3Global.rootp()->constPoolp()->findConst(matchp, true); + AstVarScope* const tableVscp = v3Global.rootp()->constPoolp()->findTable(tablep); + VL_DO_DANGLING(matchp->deleteTree(), matchp); + VL_DO_DANGLING(tablep->deleteTree(), tablep); + + // AstMatchMasked produces the index of the matching entry + AstNodeExpr* const tableRefp = new AstVarRef{flp, tableVscp, VAccess::READ}; + AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false); + AstMatchMasked* const indexp = new AstMatchMasked{flp, caseExprp, matchVscp}; + AstNodeExpr* const entryp = new AstArraySel{flp, tableRefp, indexp}; + + return connectDecoderOutputs(nodep, entryp, "__VcaseDecoderOut"); } // TODO: should return AstNodeStmt after #6280 @@ -965,6 +1087,20 @@ class CaseVisitor final : public VNVisitor { }(); if (useTable) return convertCaseTable(nodep); + // Determine if we should use the decoder method. + const bool useDecoder = [&]() { + // Not if disabled + if (!v3Global.opt.fCaseDecoder()) return false; + // Not if not a decoder pattern + if (m_caseDecoderRecords.empty()) return false; + // Only worth it once the branch lowering it would replace is deep enough (see + // useTable) + const size_t branches = std::min(nodep->exprp()->width(), m_caseNConditions); + if (branches < CASE_TABLE_MIN_BRANCHES) return false; + return true; + }(); + if (useDecoder) return convertCaseDecoder(nodep); + // Determine if we should use the fast bitwise branching tree method const bool useFastBitTree = [&]() { // Not if disabled @@ -1044,6 +1180,7 @@ public: // CONSTRUCTORS explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); } ~CaseVisitor() override { + V3Stats::addStat("Optimizations, Cases decoder", m_stats.caseDecoder); V3Stats::addStat("Optimizations, Cases table normal", m_stats.caseTableNormal); V3Stats::addStat("Optimizations, Cases table tiny", m_stats.caseTableTiny); V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast); diff --git a/src/V3Clean.cpp b/src/V3Clean.cpp index 2c326bbb7..07920f8de 100644 --- a/src/V3Clean.cpp +++ b/src/V3Clean.cpp @@ -232,6 +232,11 @@ class CleanVisitor final : public VNVisitor { ensureClean(nodep->rhsp()); setClean(nodep, true); } + void visit(AstMatchMasked* nodep) override { + iterateChildren(nodep); + ensureClean(nodep->lhsp()); + setClean(nodep, true); + } void visit(AstSel* nodep) override { operandBiop(nodep); setClean(nodep, nodep->cleanOut()); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 34c4f349a..739313ff8 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -3019,6 +3019,15 @@ class ConstVisitor final : public VNVisitor { } } void visit(AstClassOrPackageRef* nodep) override { iterateChildren(nodep); } + + void visit(AstMatchMasked* nodep) override { + // Do not iterate the tables, they must be constant pool entries + iterate(nodep->lhsp()); + if (AstConst* const constp = VN_CAST(nodep->lhsp(), Const)) { + replaceNum(nodep, AstMatchMasked::fold(constp->num(), nodep->matchp()->varp())); + } + } + void visit(AstPin* nodep) override { iterateChildren(nodep); } void replaceLogEq(AstLogEq* nodep) { @@ -3281,7 +3290,8 @@ class ConstVisitor final : public VNVisitor { iterateChildren(nodep); UASSERT_OBJ(nodep->varp(), nodep, "Not linked"); bool did = false; - if (m_doV && !nodep->varp()->constPoolEntry() && nodep->varp()->valuep() && !m_attrp) { + if (m_doV && (!nodep->varp()->constPoolEntry() || m_selp) && nodep->varp()->valuep() + && !m_attrp) { // UINFOTREE(1, valuep, "", "visitvaref"); iterateAndNextNull(nodep->varp()->valuep()); // May change nodep->varp()->valuep() AstNode* const valuep = nodep->varp()->valuep(); diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 637cf184f..cb18ba704 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -96,6 +96,11 @@ std::unique_ptr DfgGraph::clone() const { vtxp2clonep.emplace(&vtx, cp); break; } // LCOV_EXCL_STOP + case VDfgType::MatchMasked: { + DfgMatchMasked* const cp = new DfgMatchMasked{*clonep, vtx.fileline(), vtx.dtype()}; + vtxp2clonep.emplace(&vtx, cp); + break; + } case VDfgType::Sel: { DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtype()}; cp->lsb(vtx.as()->lsb()); @@ -672,6 +677,15 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const { CHECK(v.dtype() == DfgDataType::select(v.srcp()->dtype(), v.lsb(), v.size()), "sel"); return; } + case VDfgType::MatchMasked: { + const DfgMatchMasked& v = *as(); + CHECK(v.isPacked(), "Should be Packed type"); + CHECK(v.size() == 32U, "Should yield a 32-bit result"); + CHECK(v.lhsp()->isPacked(), "Lhs should be packed"); + CHECK(v.matchp()->isPacked(), "Match should be Packed type"); + CHECK(v.matchp()->is(), "Match should be a variable"); + return; + } case VDfgType::Mux: { const DfgMux& v = *as(); CHECK(v.isPacked(), "Should be Packed type"); diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 95320daad..b93c1c716 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -817,8 +817,11 @@ bool DfgVertex::isCheaperThanLoad() const { if (is()) return true; // Variables if (is()) return true; - // Array sels are just address computation - if (is()) return true; + // Array sels are just address computation, but the address itself can be expensive + if (const DfgArraySel* aselp = cast()) { + if (aselp->bitp()->is()) return false; + return true; + } // Small select from variable if (const DfgSel* const selp = cast()) { if (!selp->fromp()->is()) return false; diff --git a/src/V3DfgBreakCycles.cpp b/src/V3DfgBreakCycles.cpp index 73789cb9b..ecdcc875d 100644 --- a/src/V3DfgBreakCycles.cpp +++ b/src/V3DfgBreakCycles.cpp @@ -332,12 +332,25 @@ class TraceDriver final : public DfgVisitor { } void visit(DfgArraySel* vtxp) override { - // Only constant select - const DfgConst* const idxp = vtxp->bitp()->cast(); - if (!idxp) return; // From a variable const DfgVarArray* varp = vtxp->fromp()->cast(); if (!varp) return; + + // If index is not constant, independence was proven only if the 'fromp' is + // independent, so no need to trace that + if (!vtxp->bitp()->is()) { + DfgArraySel* const resp = make(vtxp, vtxp->width()); + resp->fromp(vtxp->fromp()); + resp->bitp(trace(vtxp->bitp(), vtxp->bitp()->width() - 1, 0)); + DfgSel* const selp = make(vtxp, m_msb - m_lsb + 1); + selp->fromp(resp); + selp->lsb(m_lsb); + SET_RESULT(selp); + return; + } + + // Trace the relevant driver based on the static index + const DfgConst* const idxp = vtxp->bitp()->as(); UASSERT_OBJ(!varp->isVolatile(), vtxp, "Should not trace through volatile VarArray"); // Skip through intermediate variables while (varp->srcp() && varp->srcp()->is()) { @@ -589,6 +602,16 @@ class TraceDriver final : public DfgVisitor { SET_RESULT(resp); } + void visit(DfgMatchMasked* vtxp) override { + DfgMatchMasked* const resp = make(vtxp, vtxp->width()); + resp->lhsp(trace(vtxp->lhsp(), vtxp->lhsp()->width() - 1, 0)); + resp->matchp(trace(vtxp->matchp(), vtxp->matchp()->width() - 1, 0)); + DfgSel* const selp = make(vtxp, m_msb - m_lsb + 1); + selp->fromp(resp); + selp->lsb(m_lsb); + SET_RESULT(resp); + } + #undef SET_RESULT public: @@ -721,12 +744,23 @@ class IndependentBits final : public DfgVisitor { } void visit(DfgArraySel* vtxp) override { - // Only constant select - const DfgConst* const idxp = vtxp->bitp()->cast(); - if (!idxp) return; // From a variable const DfgVarArray* varp = vtxp->fromp()->cast(); if (!varp) return; + + // If index is not constant, independent only if the variable index + // is indenpendent and the array is independent. We don't track arrays, + // so we will assume an array is only independent if it has no drivers + // in the graph. TODO: could check all drivers. + if (!vtxp->bitp()->is()) { + if (MASK(vtxp->bitp()).isEqAllOnes() && !varp->srcp() && !varp->defaultp()) { + MASK(vtxp).setAllBits1(); + } + return; + } + + // Trace the relevant driver based on the static index + const DfgConst* const idxp = vtxp->bitp()->as(); // We cannot trace through a volatile variable, so pretend all bits are dependent if (varp->isVolatile()) return; // Skip through intermediate variables @@ -954,6 +988,12 @@ class IndependentBits final : public DfgVisitor { } } + void visit(DfgMatchMasked* vtxp) override { + if (MASK(vtxp->lhsp()).isEqAllOnes() && MASK(vtxp->matchp()).isEqAllOnes()) { // + MASK(vtxp).setAllBits1(); + } + } + #undef MASK // Enqueue sinks of vertex to work list for traversal - only called from constructor diff --git a/src/V3DfgCse.cpp b/src/V3DfgCse.cpp index 7a60b4be7..c1b3862e4 100644 --- a/src/V3DfgCse.cpp +++ b/src/V3DfgCse.cpp @@ -72,6 +72,7 @@ class V3DfgCse final { } // Vertices with no internal information + case VDfgType::MatchMasked: case VDfgType::Mux: case VDfgType::UnitArray: return V3Hash{}; @@ -197,6 +198,7 @@ class V3DfgCse final { } // Vertices with no internal information + case VDfgType::MatchMasked: case VDfgType::Mux: case VDfgType::UnitArray: return true; diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index ca349ccb3..b2af96461 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -234,6 +234,12 @@ class DfgToAstVisitor final : DfgVisitor { AstVar* const varp = sinkp->as()->vscp()->varp(); m_resultp = new AstCReset{vtxp->fileline(), varp, false}; } + void visit(DfgMatchMasked* vtxp) override { + FileLine* const flp = vtxp->fileline(); + AstNodeExpr* const lhsp = convertDfgVertexToAstNodeExpr(vtxp->lhsp()); + AstVarScope* const matchp = vtxp->matchp()->as()->vscp(); + m_resultp = new AstMatchMasked{flp, lhsp, matchp}; + } void visit(DfgRep* vtxp) override { FileLine* const flp = vtxp->fileline(); diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 3f08f490f..de92790a8 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -2751,6 +2751,16 @@ class V3DfgPeephole final : public DfgVisitor { } } + void visit(DfgMatchMasked* const vtxp) override { + if (DfgConst* const constp = vtxp->lhsp()->cast()) { + APPLYING(FOLD_MATCHMASKED) { + AstVar* const matchVarp = vtxp->matchp()->as()->vscp()->varp(); + replace(makeI32(vtxp->fileline(), AstMatchMasked::fold(constp->num(), matchVarp))); + return; + } + } + } + //========================================================================= // DfgVertexTernary //========================================================================= diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index 52d5717fe..1c9de14e2 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -31,6 +31,7 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_LHS_OF_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_MATCHMASKED) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_MUX_FROM_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_MUX_FROM_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_REP) \ diff --git a/src/V3DfgSynthesize.cpp b/src/V3DfgSynthesize.cpp index 706c9c0c9..c0f5bdc99 100644 --- a/src/V3DfgSynthesize.cpp +++ b/src/V3DfgSynthesize.cpp @@ -402,6 +402,29 @@ class AstToDfgConverter final : public VNVisitor { DfgVertex* const vtxp = make(nodep->fileline(), *dtypep); nodep->user2p(vtxp); } + void visit(AstMatchMasked* nodep) override { + UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); + UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); + if (unhandled(nodep)) return; + + const DfgDataType* const dtypep = DfgDataType::fromAst(nodep->dtypep()); + if (!dtypep) { + m_foundUnhandled = true; + ++m_ctx.m_conv.nonRepDType; + return; + } + + iterate(nodep->lhsp()); + if (m_foundUnhandled) return; + iterate(nodep->matchp()); + if (m_foundUnhandled) return; + + FileLine* const flp = nodep->fileline(); + DfgMatchMasked* const vtxp = make(flp, *dtypep); + vtxp->lhsp(nodep->lhsp()->user2u().to()); + vtxp->matchp(nodep->matchp()->user2u().to()); + nodep->user2p(vtxp); + } void visit(AstReplicate* nodep) override { UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting"); UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex"); diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index f8ca8e9c8..d932de1a3 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -329,6 +329,21 @@ public: ASTGEN_MEMBERS_DfgVertexBinary; }; +class DfgMatchMasked final : public DfgVertexBinary { + // Dfg equivalent of AstMatchMasked +public: + DfgMatchMasked(DfgGraph& dfg, FileLine* flp, const DfgDataType& dtype) + : DfgVertexBinary{dfg, dfgType(), flp, dtype} {} + ASTGEN_MEMBERS_DfgMatchMasked; + + DfgVertex* lhsp() const { return inputp(0); } + void lhsp(DfgVertex* vtxp) { inputp(0, vtxp); } + DfgVertex* matchp() const { return inputp(1); } + void matchp(DfgVertex* vtxp) { inputp(1, vtxp); } + + std::string srcName(size_t idx) const override { return idx ? "matchp" : "lhsp"; } +}; + class DfgMux final : public DfgVertexBinary { // AstSel is binary, but 'lsbp' is very often constant. As AstSel is fairly // common, we special case as a DfgSel for the constant 'lsbp', and as diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 969e2fd0b..5e527870c 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -1590,6 +1590,9 @@ public: puts(")"); } } + void visit(AstMatchMasked* nodep) override { + emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->matchp(), nullptr); + } void visit(AstMemberSel* nodep) override { iterateAndNextConstNull(nodep->fromp()); putnbs(nodep, "->"); diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp index b96c892f2..e41b61daf 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -471,6 +471,11 @@ class GateOkVisitor final : public VNVisitorConst { // assign to get randomization etc clearSimple("CReset"); } + void visit(AstMatchMasked* nodep) override { + if (!m_isSimple) return; + // This node can be expensive + clearSimple("MatchMasked"); + } //-------------------- void visit(AstNode* nodep) override { if (!m_isSimple) return; // Fastpath diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 575a498f9..22f96ca7e 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1449,9 +1449,11 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-facyc-simp", FOnOff, &m_fAcycSimp); DECL_OPTION("-fassemble", FOnOff, &m_fAssemble); DECL_OPTION("-fcase", CbFOnOff, [this](bool flag) { + m_fCaseDecoder = flag; m_fCaseTable = flag; m_fCaseTree = flag; }); + DECL_OPTION("-fcase-decoder", FOnOff, &m_fCaseDecoder); DECL_OPTION("-fcase-table", FOnOff, &m_fCaseTable); DECL_OPTION("-fcase-tree", FOnOff, &m_fCaseTree); DECL_OPTION("-fcombine", FOnOff, &m_fCombine); @@ -2356,6 +2358,7 @@ void V3Options::optimize(int level) { const bool flag = level > 0; m_fAcycSimp = flag; m_fAssemble = flag; + m_fCaseDecoder = flag; m_fCaseTable = flag; m_fCaseTree = flag; m_fCombine = flag; diff --git a/src/V3Options.h b/src/V3Options.h index 0dca257d7..71a32c31b 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -392,6 +392,7 @@ private: // MEMBERS (optimizations) bool m_fAcycSimp; // main switch: -fno-acyc-simp: acyclic pre-optimizations bool m_fAssemble; // main switch: -fno-assemble: assign assemble + bool m_fCaseDecoder; // main switch: -fno-case-decoder: case decoder conversion bool m_fCaseTable; // main switch: -fno-case-table: case table conversion bool m_fCaseTree; // main switch: -fno-case-tree: case tree conversion bool m_fCombine; // main switch: -fno-combine: common icode packing @@ -726,6 +727,7 @@ public: // ACCESSORS (optimization options) bool fAcycSimp() const { return m_fAcycSimp; } bool fAssemble() const { return m_fAssemble; } + bool fCaseDecoder() const { return m_fCaseDecoder; } bool fCaseTable() const { return m_fCaseTable; } bool fCaseTree() const { return m_fCaseTree; } bool fCombine() const { return m_fCombine; } diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 50c23da38..9fcd89ddf 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -347,6 +347,14 @@ class PremitVisitor final : public VNVisitor { } checkNode(nodep); } + void visit(AstMatchMasked* nodep) override { + iterateChildren(nodep); + if (!nodep->user1SetOnce()) { + // Don't want this replicated by V3Expand + AstVar* const varp = createTemp(nodep); + varp->noSubst(true); // Do not re-inline in V3Subst + } + } void visit(AstCond* nodep) override { // Convert AstCond to AstIf in order to avoid evaluating // sub-expressions in both branches unconditionally. diff --git a/test_regress/t/t_case_decoder.py b/test_regress/t/t_case_decoder.py new file mode 100755 index 000000000..4265c138b --- /dev/null +++ b/test_regress/t/t_case_decoder.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary', '--stats']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases decoder\s+(\d+)', 17) + +test.passes() diff --git a/test_regress/t/t_case_decoder.v b/test_regress/t/t_case_decoder.v new file mode 100644 index 000000000..6d5c779c2 --- /dev/null +++ b/test_regress/t/t_case_decoder.v @@ -0,0 +1,524 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 +// +// Case statements that become a "decoder" (the selector is matched against a packed constant +// table at runtime), followed by cases that must not be converted to one. Each output is +// compared against an equivalent reference computed without a case statement, so the reference +// itself is never converted. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [31:0] cyc = 0; + + // Accept A: a 31-bit (I) selector decoded into outputs of three widths (I/Q/W result). + wire [30:0] accept_a_in = 31'b1 << cyc[3:0]; + logic [ 5:0] accept_a_out_0, accept_a_ref_0; + logic [ 55:0] accept_a_out_1, accept_a_ref_1; + logic [142:0] accept_a_out_2, accept_a_ref_2; + always_comb begin + casez (accept_a_in) + 31'b???????_????????_????????_???????1 : accept_a_out_0 = 6'd00; + 31'b???????_????????_????????_??????1? : accept_a_out_0 = 6'd01; + 31'b???????_????????_????????_?????1?? : accept_a_out_0 = 6'd02; + 31'b???????_????????_????????_????1??? : accept_a_out_0 = 6'd03; + 31'b???????_????????_????????_???1???? : accept_a_out_0 = 6'd04; + 31'b???????_????????_????????_??1????? : accept_a_out_0 = 6'd05; + 31'b???????_????????_????????_?1?????? : accept_a_out_0 = 6'd06; + 31'b???????_????????_????????_1??????? : accept_a_out_0 = 6'd07; + 31'b???????_????????_???????1_???????? : accept_a_out_0 = 6'd08; + 31'b???????_????????_??????1?_???????? : accept_a_out_0 = 6'd09; + 31'b???????_????????_?????1??_???????? : accept_a_out_0 = 6'd10; + 31'b???????_????????_????1???_???????? : accept_a_out_0 = 6'd11; + 31'b???????_????????_???1????_???????? : accept_a_out_0 = 6'd12; + 31'b???????_????????_??1?????_???????? : accept_a_out_0 = 6'd13; + 31'b???????_????????_?1??????_???????? : accept_a_out_0 = 6'd14; + 31'b???????_????????_1???????_???????? : accept_a_out_0 = 6'd15; + 31'b???????_???????1_????????_???????? : accept_a_out_0 = 6'd16; + 31'b???????_??????1?_????????_???????? : accept_a_out_0 = 6'd17; + 31'b???????_?????1??_????????_???????? : accept_a_out_0 = 6'd18; + 31'b???????_????1???_????????_???????? : accept_a_out_0 = 6'd19; + 31'b???????_???1????_????????_???????? : accept_a_out_0 = 6'd20; + 31'b???????_??1?????_????????_???????? : accept_a_out_0 = 6'd21; + 31'b???????_?1??????_????????_???????? : accept_a_out_0 = 6'd22; + 31'b???????_1???????_????????_???????? : accept_a_out_0 = 6'd23; + 31'b??????1_????????_????????_???????? : accept_a_out_0 = 6'd24; + 31'b?????1?_????????_????????_???????? : accept_a_out_0 = 6'd25; + 31'b????1??_????????_????????_???????? : accept_a_out_0 = 6'd26; + 31'b???1???_????????_????????_???????? : accept_a_out_0 = 6'd27; + 31'b??1????_????????_????????_???????? : accept_a_out_0 = 6'd28; + 31'b?1?????_????????_????????_???????? : accept_a_out_0 = 6'd29; + 31'b1??????_????????_????????_???????? : accept_a_out_0 = 6'd30; + default: accept_a_out_0 = '1; + endcase + casez (accept_a_in) + 31'b???????_????????_????????_???????1 : accept_a_out_1 = 56'd0000; + 31'b???????_????????_????????_??????1? : accept_a_out_1 = 56'd0100; + 31'b???????_????????_????????_?????1?? : accept_a_out_1 = 56'd0200; + 31'b???????_????????_????????_????1??? : accept_a_out_1 = 56'd0300; + 31'b???????_????????_????????_???1???? : accept_a_out_1 = 56'd0400; + 31'b???????_????????_????????_??1????? : accept_a_out_1 = 56'd0500; + 31'b???????_????????_????????_?1?????? : accept_a_out_1 = 56'd0600; + 31'b???????_????????_????????_1??????? : accept_a_out_1 = 56'd0700; + 31'b???????_????????_???????1_???????? : accept_a_out_1 = 56'd0800; + 31'b???????_????????_??????1?_???????? : accept_a_out_1 = 56'd0900; + 31'b???????_????????_?????1??_???????? : accept_a_out_1 = 56'd1000; + 31'b???????_????????_????1???_???????? : accept_a_out_1 = 56'd1100; + 31'b???????_????????_???1????_???????? : accept_a_out_1 = 56'd1200; + 31'b???????_????????_??1?????_???????? : accept_a_out_1 = 56'd1300; + 31'b???????_????????_?1??????_???????? : accept_a_out_1 = 56'd1400; + 31'b???????_????????_1???????_???????? : accept_a_out_1 = 56'd1500; + 31'b???????_???????1_????????_???????? : accept_a_out_1 = 56'd1600; + 31'b???????_??????1?_????????_???????? : accept_a_out_1 = 56'd1700; + 31'b???????_?????1??_????????_???????? : accept_a_out_1 = 56'd1800; + 31'b???????_????1???_????????_???????? : accept_a_out_1 = 56'd1900; + 31'b???????_???1????_????????_???????? : accept_a_out_1 = 56'd2000; + 31'b???????_??1?????_????????_???????? : accept_a_out_1 = 56'd2100; + 31'b???????_?1??????_????????_???????? : accept_a_out_1 = 56'd2200; + 31'b???????_1???????_????????_???????? : accept_a_out_1 = 56'd2300; + 31'b??????1_????????_????????_???????? : accept_a_out_1 = 56'd2400; + 31'b?????1?_????????_????????_???????? : accept_a_out_1 = 56'd2500; + 31'b????1??_????????_????????_???????? : accept_a_out_1 = 56'd2600; + 31'b???1???_????????_????????_???????? : accept_a_out_1 = 56'd2700; + 31'b??1????_????????_????????_???????? : accept_a_out_1 = 56'd2800; + 31'b?1?????_????????_????????_???????? : accept_a_out_1 = 56'd2900; + 31'b1??????_????????_????????_???????? : accept_a_out_1 = 56'd3000; + default: accept_a_out_1 = '1; + endcase + casez (accept_a_in) + 31'b???????_????????_????????_???????1 : accept_a_out_2 = 143'd0000000000; + 31'b???????_????????_????????_??????1? : accept_a_out_2 = 143'd0100000000; + 31'b???????_????????_????????_?????1?? : accept_a_out_2 = 143'd0200000000; + 31'b???????_????????_????????_????1??? : accept_a_out_2 = 143'd0300000000; + 31'b???????_????????_????????_???1???? : accept_a_out_2 = 143'd0400000000; + 31'b???????_????????_????????_??1????? : accept_a_out_2 = 143'd0500000000; + 31'b???????_????????_????????_?1?????? : accept_a_out_2 = 143'd0600000000; + 31'b???????_????????_????????_1??????? : accept_a_out_2 = 143'd0700000000; + 31'b???????_????????_???????1_???????? : accept_a_out_2 = 143'd0800000000; + 31'b???????_????????_??????1?_???????? : accept_a_out_2 = 143'd0900000000; + 31'b???????_????????_?????1??_???????? : accept_a_out_2 = 143'd1000000000; + 31'b???????_????????_????1???_???????? : accept_a_out_2 = 143'd1100000000; + 31'b???????_????????_???1????_???????? : accept_a_out_2 = 143'd1200000000; + 31'b???????_????????_??1?????_???????? : accept_a_out_2 = 143'd1300000000; + 31'b???????_????????_?1??????_???????? : accept_a_out_2 = 143'd1400000000; + 31'b???????_????????_1???????_???????? : accept_a_out_2 = 143'd1500000000; + 31'b???????_???????1_????????_???????? : accept_a_out_2 = 143'd1600000000; + 31'b???????_??????1?_????????_???????? : accept_a_out_2 = 143'd1700000000; + 31'b???????_?????1??_????????_???????? : accept_a_out_2 = 143'd1800000000; + 31'b???????_????1???_????????_???????? : accept_a_out_2 = 143'd1900000000; + 31'b???????_???1????_????????_???????? : accept_a_out_2 = 143'd2000000000; + 31'b???????_??1?????_????????_???????? : accept_a_out_2 = 143'd2100000000; + 31'b???????_?1??????_????????_???????? : accept_a_out_2 = 143'd2200000000; + 31'b???????_1???????_????????_???????? : accept_a_out_2 = 143'd2300000000; + 31'b??????1_????????_????????_???????? : accept_a_out_2 = 143'd2400000000; + 31'b?????1?_????????_????????_???????? : accept_a_out_2 = 143'd2500000000; + 31'b????1??_????????_????????_???????? : accept_a_out_2 = 143'd2600000000; + 31'b???1???_????????_????????_???????? : accept_a_out_2 = 143'd2700000000; + 31'b??1????_????????_????????_???????? : accept_a_out_2 = 143'd2800000000; + 31'b?1?????_????????_????????_???????? : accept_a_out_2 = 143'd2900000000; + 31'b1??????_????????_????????_???????? : accept_a_out_2 = 143'd3000000000; + default: accept_a_out_2 = '1; + endcase + end + assign accept_a_ref_0 = 6'(cyc[3:0]); + assign accept_a_ref_1 = 56'(cyc[3:0]) * 56'd100; + assign accept_a_ref_2 = 143'(cyc[3:0]) * 143'd100000000; + + // Accept B: a 40-bit (Q) selector decoded into outputs of three widths (I/Q/W result). + wire [39:0] accept_b_in = 40'b1 << cyc[5:1]; + logic [ 5:0] accept_b_out_0, accept_b_ref_0; + logic [ 55:0] accept_b_out_1, accept_b_ref_1; + logic [142:0] accept_b_out_2, accept_b_ref_2; + always_comb begin + casez (accept_b_in) + 40'b????????_????????_????????_????????_???????1 : accept_b_out_0 = 6'd00; + 40'b????????_????????_????????_????????_??????1? : accept_b_out_0 = 6'd01; + 40'b????????_????????_????????_????????_?????1?? : accept_b_out_0 = 6'd02; + 40'b????????_????????_????????_????????_????1??? : accept_b_out_0 = 6'd03; + 40'b????????_????????_????????_????????_???1???? : accept_b_out_0 = 6'd04; + 40'b????????_????????_????????_????????_??1????? : accept_b_out_0 = 6'd05; + 40'b????????_????????_????????_????????_?1?????? : accept_b_out_0 = 6'd06; + 40'b????????_????????_????????_????????_1??????? : accept_b_out_0 = 6'd07; + 40'b????????_????????_????????_???????1_???????? : accept_b_out_0 = 6'd08; + 40'b????????_????????_????????_??????1?_???????? : accept_b_out_0 = 6'd09; + 40'b????????_????????_????????_?????1??_???????? : accept_b_out_0 = 6'd10; + 40'b????????_????????_????????_????1???_???????? : accept_b_out_0 = 6'd11; + 40'b????????_????????_????????_???1????_???????? : accept_b_out_0 = 6'd12; + 40'b????????_????????_????????_??1?????_???????? : accept_b_out_0 = 6'd13; + 40'b????????_????????_????????_?1??????_???????? : accept_b_out_0 = 6'd14; + 40'b????????_????????_????????_1???????_???????? : accept_b_out_0 = 6'd15; + 40'b????????_????????_???????1_????????_???????? : accept_b_out_0 = 6'd16; + 40'b????????_????????_??????1?_????????_???????? : accept_b_out_0 = 6'd17; + 40'b????????_????????_?????1??_????????_???????? : accept_b_out_0 = 6'd18; + 40'b????????_????????_????1???_????????_???????? : accept_b_out_0 = 6'd19; + 40'b????????_????????_???1????_????????_???????? : accept_b_out_0 = 6'd20; + 40'b????????_????????_??1?????_????????_???????? : accept_b_out_0 = 6'd21; + 40'b????????_????????_?1??????_????????_???????? : accept_b_out_0 = 6'd22; + 40'b????????_????????_1???????_????????_???????? : accept_b_out_0 = 6'd23; + 40'b????????_???????1_????????_????????_???????? : accept_b_out_0 = 6'd24; + 40'b????????_??????1?_????????_????????_???????? : accept_b_out_0 = 6'd25; + 40'b????????_?????1??_????????_????????_???????? : accept_b_out_0 = 6'd26; + 40'b????????_????1???_????????_????????_???????? : accept_b_out_0 = 6'd27; + 40'b????????_???1????_????????_????????_???????? : accept_b_out_0 = 6'd28; + 40'b????????_??1?????_????????_????????_???????? : accept_b_out_0 = 6'd29; + 40'b????????_?1??????_????????_????????_???????? : accept_b_out_0 = 6'd30; + 40'b????????_1???????_????????_????????_???????? : accept_b_out_0 = 6'd31; + 40'b???????1_????????_????????_????????_???????? : accept_b_out_0 = 6'd32; + 40'b??????1?_????????_????????_????????_???????? : accept_b_out_0 = 6'd33; + 40'b?????1??_????????_????????_????????_???????? : accept_b_out_0 = 6'd34; + 40'b????1???_????????_????????_????????_???????? : accept_b_out_0 = 6'd35; + 40'b???1????_????????_????????_????????_???????? : accept_b_out_0 = 6'd36; + 40'b??1?????_????????_????????_????????_???????? : accept_b_out_0 = 6'd37; + 40'b?1??????_????????_????????_????????_???????? : accept_b_out_0 = 6'd38; + 40'b1???????_????????_????????_????????_???????? : accept_b_out_0 = 6'd39; + default: accept_b_out_0 = '1; + endcase + casez (accept_b_in) + 40'b????????_????????_????????_????????_???????1 : accept_b_out_1 = 56'd0000; + 40'b????????_????????_????????_????????_??????1? : accept_b_out_1 = 56'd0100; + 40'b????????_????????_????????_????????_?????1?? : accept_b_out_1 = 56'd0200; + 40'b????????_????????_????????_????????_????1??? : accept_b_out_1 = 56'd0300; + 40'b????????_????????_????????_????????_???1???? : accept_b_out_1 = 56'd0400; + 40'b????????_????????_????????_????????_??1????? : accept_b_out_1 = 56'd0500; + 40'b????????_????????_????????_????????_?1?????? : accept_b_out_1 = 56'd0600; + 40'b????????_????????_????????_????????_1??????? : accept_b_out_1 = 56'd0700; + 40'b????????_????????_????????_???????1_???????? : accept_b_out_1 = 56'd0800; + 40'b????????_????????_????????_??????1?_???????? : accept_b_out_1 = 56'd0900; + 40'b????????_????????_????????_?????1??_???????? : accept_b_out_1 = 56'd1000; + 40'b????????_????????_????????_????1???_???????? : accept_b_out_1 = 56'd1100; + 40'b????????_????????_????????_???1????_???????? : accept_b_out_1 = 56'd1200; + 40'b????????_????????_????????_??1?????_???????? : accept_b_out_1 = 56'd1300; + 40'b????????_????????_????????_?1??????_???????? : accept_b_out_1 = 56'd1400; + 40'b????????_????????_????????_1???????_???????? : accept_b_out_1 = 56'd1500; + 40'b????????_????????_???????1_????????_???????? : accept_b_out_1 = 56'd1600; + 40'b????????_????????_??????1?_????????_???????? : accept_b_out_1 = 56'd1700; + 40'b????????_????????_?????1??_????????_???????? : accept_b_out_1 = 56'd1800; + 40'b????????_????????_????1???_????????_???????? : accept_b_out_1 = 56'd1900; + 40'b????????_????????_???1????_????????_???????? : accept_b_out_1 = 56'd2000; + 40'b????????_????????_??1?????_????????_???????? : accept_b_out_1 = 56'd2100; + 40'b????????_????????_?1??????_????????_???????? : accept_b_out_1 = 56'd2200; + 40'b????????_????????_1???????_????????_???????? : accept_b_out_1 = 56'd2300; + 40'b????????_???????1_????????_????????_???????? : accept_b_out_1 = 56'd2400; + 40'b????????_??????1?_????????_????????_???????? : accept_b_out_1 = 56'd2500; + 40'b????????_?????1??_????????_????????_???????? : accept_b_out_1 = 56'd2600; + 40'b????????_????1???_????????_????????_???????? : accept_b_out_1 = 56'd2700; + 40'b????????_???1????_????????_????????_???????? : accept_b_out_1 = 56'd2800; + 40'b????????_??1?????_????????_????????_???????? : accept_b_out_1 = 56'd2900; + 40'b????????_?1??????_????????_????????_???????? : accept_b_out_1 = 56'd3000; + 40'b????????_1???????_????????_????????_???????? : accept_b_out_1 = 56'd3100; + 40'b???????1_????????_????????_????????_???????? : accept_b_out_1 = 56'd3200; + 40'b??????1?_????????_????????_????????_???????? : accept_b_out_1 = 56'd3300; + 40'b?????1??_????????_????????_????????_???????? : accept_b_out_1 = 56'd3400; + 40'b????1???_????????_????????_????????_???????? : accept_b_out_1 = 56'd3500; + 40'b???1????_????????_????????_????????_???????? : accept_b_out_1 = 56'd3600; + 40'b??1?????_????????_????????_????????_???????? : accept_b_out_1 = 56'd3700; + 40'b?1??????_????????_????????_????????_???????? : accept_b_out_1 = 56'd3800; + 40'b1???????_????????_????????_????????_???????? : accept_b_out_1 = 56'd3900; + default: accept_b_out_1 = '1; + endcase + casez (accept_b_in) + 40'b????????_????????_????????_????????_???????1 : accept_b_out_2 = 143'd0000000000; + 40'b????????_????????_????????_????????_??????1? : accept_b_out_2 = 143'd0100000000; + 40'b????????_????????_????????_????????_?????1?? : accept_b_out_2 = 143'd0200000000; + 40'b????????_????????_????????_????????_????1??? : accept_b_out_2 = 143'd0300000000; + 40'b????????_????????_????????_????????_???1???? : accept_b_out_2 = 143'd0400000000; + 40'b????????_????????_????????_????????_??1????? : accept_b_out_2 = 143'd0500000000; + 40'b????????_????????_????????_????????_?1?????? : accept_b_out_2 = 143'd0600000000; + 40'b????????_????????_????????_????????_1??????? : accept_b_out_2 = 143'd0700000000; + 40'b????????_????????_????????_???????1_???????? : accept_b_out_2 = 143'd0800000000; + 40'b????????_????????_????????_??????1?_???????? : accept_b_out_2 = 143'd0900000000; + 40'b????????_????????_????????_?????1??_???????? : accept_b_out_2 = 143'd1000000000; + 40'b????????_????????_????????_????1???_???????? : accept_b_out_2 = 143'd1100000000; + 40'b????????_????????_????????_???1????_???????? : accept_b_out_2 = 143'd1200000000; + 40'b????????_????????_????????_??1?????_???????? : accept_b_out_2 = 143'd1300000000; + 40'b????????_????????_????????_?1??????_???????? : accept_b_out_2 = 143'd1400000000; + 40'b????????_????????_????????_1???????_???????? : accept_b_out_2 = 143'd1500000000; + 40'b????????_????????_???????1_????????_???????? : accept_b_out_2 = 143'd1600000000; + 40'b????????_????????_??????1?_????????_???????? : accept_b_out_2 = 143'd1700000000; + 40'b????????_????????_?????1??_????????_???????? : accept_b_out_2 = 143'd1800000000; + 40'b????????_????????_????1???_????????_???????? : accept_b_out_2 = 143'd1900000000; + 40'b????????_????????_???1????_????????_???????? : accept_b_out_2 = 143'd2000000000; + 40'b????????_????????_??1?????_????????_???????? : accept_b_out_2 = 143'd2100000000; + 40'b????????_????????_?1??????_????????_???????? : accept_b_out_2 = 143'd2200000000; + 40'b????????_????????_1???????_????????_???????? : accept_b_out_2 = 143'd2300000000; + 40'b????????_???????1_????????_????????_???????? : accept_b_out_2 = 143'd2400000000; + 40'b????????_??????1?_????????_????????_???????? : accept_b_out_2 = 143'd2500000000; + 40'b????????_?????1??_????????_????????_???????? : accept_b_out_2 = 143'd2600000000; + 40'b????????_????1???_????????_????????_???????? : accept_b_out_2 = 143'd2700000000; + 40'b????????_???1????_????????_????????_???????? : accept_b_out_2 = 143'd2800000000; + 40'b????????_??1?????_????????_????????_???????? : accept_b_out_2 = 143'd2900000000; + 40'b????????_?1??????_????????_????????_???????? : accept_b_out_2 = 143'd3000000000; + 40'b????????_1???????_????????_????????_???????? : accept_b_out_2 = 143'd3100000000; + 40'b???????1_????????_????????_????????_???????? : accept_b_out_2 = 143'd3200000000; + 40'b??????1?_????????_????????_????????_???????? : accept_b_out_2 = 143'd3300000000; + 40'b?????1??_????????_????????_????????_???????? : accept_b_out_2 = 143'd3400000000; + 40'b????1???_????????_????????_????????_???????? : accept_b_out_2 = 143'd3500000000; + 40'b???1????_????????_????????_????????_???????? : accept_b_out_2 = 143'd3600000000; + 40'b??1?????_????????_????????_????????_???????? : accept_b_out_2 = 143'd3700000000; + 40'b?1??????_????????_????????_????????_???????? : accept_b_out_2 = 143'd3800000000; + 40'b1???????_????????_????????_????????_???????? : accept_b_out_2 = 143'd3900000000; + default: accept_b_out_2 = '1; + endcase + end + assign accept_b_ref_0 = 6'(cyc[5:1]); + assign accept_b_ref_1 = 56'(cyc[5:1]) * 56'd100; + assign accept_b_ref_2 = 143'(cyc[5:1]) * 143'd100000000; + + // Accept C: a 155-bit (W) selector decoded into outputs of three widths (I/Q/W result). + wire [154:0] accept_c_in = 155'b1 << cyc[5:0]; + logic [ 5:0] accept_c_out_0, accept_c_ref_0; + logic [ 55:0] accept_c_out_1, accept_c_ref_1; + logic [142:0] accept_c_out_2, accept_c_ref_2; + always_comb begin + casez (accept_c_in) + 155'b????????_????????_????????_????????_???????1 : accept_c_out_0 = 6'd00; + 155'b????????_????????_????????_????????_??????1? : accept_c_out_0 = 6'd01; + 155'b????????_????????_????????_????????_?????1?? : accept_c_out_0 = 6'd02; + 155'b????????_????????_????????_????????_????1??? : accept_c_out_0 = 6'd03; + 155'b????????_????????_????????_????????_???1???? : accept_c_out_0 = 6'd04; + 155'b????????_????????_????????_????????_??1????? : accept_c_out_0 = 6'd05; + 155'b????????_????????_????????_????????_?1?????? : accept_c_out_0 = 6'd06; + 155'b????????_????????_????????_????????_1??????? : accept_c_out_0 = 6'd07; + 155'b????????_????????_????????_???????1_???????? : accept_c_out_0 = 6'd08; + 155'b????????_????????_????????_??????1?_???????? : accept_c_out_0 = 6'd09; + 155'b????????_????????_????????_?????1??_???????? : accept_c_out_0 = 6'd10; + default: accept_c_out_0 = '1; + endcase + casez (accept_c_in) + 155'b????????_????????_????????_????????_???????1 : accept_c_out_1 = 56'd0000; + 155'b????????_????????_????????_????????_??????1? : accept_c_out_1 = 56'd0100; + 155'b????????_????????_????????_????????_?????1?? : accept_c_out_1 = 56'd0200; + 155'b????????_????????_????????_????????_????1??? : accept_c_out_1 = 56'd0300; + 155'b????????_????????_????????_????????_???1???? : accept_c_out_1 = 56'd0400; + 155'b????????_????????_????????_????????_??1????? : accept_c_out_1 = 56'd0500; + 155'b????????_????????_????????_????????_?1?????? : accept_c_out_1 = 56'd0600; + 155'b????????_????????_????????_????????_1??????? : accept_c_out_1 = 56'd0700; + 155'b????????_????????_????????_???????1_???????? : accept_c_out_1 = 56'd0800; + 155'b????????_????????_????????_??????1?_???????? : accept_c_out_1 = 56'd0900; + 155'b????????_????????_????????_?????1??_???????? : accept_c_out_1 = 56'd1000; + default: accept_c_out_1 = '1; + endcase + casez (accept_c_in) + 155'b????????_????????_????????_????????_???????1 : accept_c_out_2 = 143'd0000000000; + 155'b????????_????????_????????_????????_??????1? : accept_c_out_2 = 143'd0100000000; + 155'b????????_????????_????????_????????_?????1?? : accept_c_out_2 = 143'd0200000000; + 155'b????????_????????_????????_????????_????1??? : accept_c_out_2 = 143'd0300000000; + 155'b????????_????????_????????_????????_???1???? : accept_c_out_2 = 143'd0400000000; + 155'b????????_????????_????????_????????_??1????? : accept_c_out_2 = 143'd0500000000; + 155'b????????_????????_????????_????????_?1?????? : accept_c_out_2 = 143'd0600000000; + 155'b????????_????????_????????_????????_1??????? : accept_c_out_2 = 143'd0700000000; + 155'b????????_????????_????????_???????1_???????? : accept_c_out_2 = 143'd0800000000; + 155'b????????_????????_????????_??????1?_???????? : accept_c_out_2 = 143'd0900000000; + 155'b????????_????????_????????_?????1??_???????? : accept_c_out_2 = 143'd1000000000; + default: accept_c_out_2 = '1; + endcase + end + assign accept_c_ref_0 = cyc[5:0] <= 6'd10 ? 6'(cyc[5:0]) : ~6'd0; + assign accept_c_ref_1 = cyc[5:0] <= 6'd10 ? 56'(cyc[5:0]) * 56'd100 : ~56'd0; + assign accept_c_ref_2 = cyc[5:0] <= 6'd10 ? 143'(cyc[5:0]) * 143'd100000000 : ~143'd0; + + // Accept D: the default value is set before the case, with no default item. + wire [39:0] accept_d_in = 40'b1 << cyc[4:0]; + logic [4:0] accept_d_out, accept_d_ref; + always_comb begin + accept_d_out = '1; + casez (accept_d_in) + 40'b????????_????????_????????_????????_???????1 : accept_d_out = 5'd0; + 40'b????????_????????_????????_????????_??????1? : accept_d_out = 5'd1; + 40'b????????_????????_????????_????????_?????1?? : accept_d_out = 5'd2; + 40'b????????_????????_????????_????????_????1??? : accept_d_out = 5'd3; + endcase + end + assign accept_d_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; + + // Accept E: an empty default item. + wire [39:0] accept_e_in = 40'b1 << cyc[4:0]; + logic [4:0] accept_e_out, accept_e_ref; + always_comb begin + accept_e_out = '1; + casez (accept_e_in) + 40'b????????_????????_????????_????????_???????1 : accept_e_out = 5'd0; + 40'b????????_????????_????????_????????_??????1? : accept_e_out = 5'd1; + 40'b????????_????????_????????_????????_?????1?? : accept_e_out = 5'd2; + 40'b????????_????????_????????_????????_????1??? : accept_e_out = 5'd3; + default: begin end + endcase + end + assign accept_e_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; + + // Accept F: an item with an empty body (output keeps its pre-case default). + wire [39:0] accept_f_in = 40'b1 << cyc[4:0]; + logic [4:0] accept_f_out, accept_f_ref; + always_comb begin + accept_f_out = '1; + casez (accept_f_in) + 40'b????????_????????_????????_????????_???1???? : begin end + 40'b????????_????????_????????_????????_???????1 : accept_f_out = 5'd0; + 40'b????????_????????_????????_????????_??????1? : accept_f_out = 5'd1; + 40'b????????_????????_????????_????????_?????1?? : accept_f_out = 5'd2; + 40'b????????_????????_????????_????????_????1??? : accept_f_out = 5'd3; + default: begin end + endcase + end + assign accept_f_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; + + // Accept G: non-blocking assignments. + wire [23:0] accept_g_in = 24'b1 << cyc[4:0]; + logic [5:0] accept_g_out, accept_g_ref; + always_ff @(posedge clk) begin + accept_g_out <= '1; + casez (accept_g_in) + 24'b????????_????????_???????1 : accept_g_out <= 6'd0; + 24'b????????_????????_??????1? : accept_g_out <= 6'd1; + 24'b????????_????????_?????1?? : accept_g_out <= 6'd2; + 24'b????????_????????_????1??? : accept_g_out <= 6'd3; + endcase + end + always_ff @(posedge clk) + accept_g_ref <= cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 + : cyc[4:0] == 5'd2 ? 6'd2 : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + + // Accept H: multiple outputs from one decoder, some items assigning only a subset. + wire [23:0] accept_h_in = 24'b1 << cyc[4:0]; + logic [ 5:0] accept_h_out_0, accept_h_ref_0; + logic [11:0] accept_h_out_1, accept_h_ref_1; + always_comb begin + accept_h_out_0 = '1; + accept_h_out_1 = '1; + casez (accept_h_in) + 24'b????????_????????_???????1 : begin accept_h_out_0 = 6'd0; accept_h_out_1 = 12'h001; end + 24'b????????_????????_??????1? : accept_h_out_0 = 6'd1; + 24'b????????_????????_?????1?? : accept_h_out_1 = 12'h004; + 24'b????????_????????_????1??? : begin accept_h_out_0 = 6'd3; accept_h_out_1 = 12'h008; end + endcase + end + assign accept_h_ref_0 = cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 + : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + assign accept_h_ref_1 = cyc[4:0] == 5'd0 ? 12'h001 : cyc[4:0] == 5'd2 ? 12'h004 + : cyc[4:0] == 5'd3 ? 12'h008 : ~12'd0; + + // Accept I: multiple outputs from one decoder, non-blocking assignments. + wire [23:0] accept_i_in = 24'b1 << cyc[4:0]; + logic [ 5:0] accept_i_out_0, accept_i_ref_0; + logic [11:0] accept_i_out_1, accept_i_ref_1; + always_ff @(posedge clk) begin + accept_i_out_0 <= '1; + accept_i_out_1 <= '1; + casez (accept_i_in) + 24'b????????_????????_???????1 : begin accept_i_out_0 <= 6'd0; accept_i_out_1 <= 12'h001; end + 24'b????????_????????_??????1? : begin accept_i_out_0 <= 6'd1; accept_i_out_1 <= 12'h002; end + 24'b????????_????????_?????1?? : begin accept_i_out_0 <= 6'd2; accept_i_out_1 <= 12'h004; end + 24'b????????_????????_????1??? : begin accept_i_out_0 <= 6'd3; accept_i_out_1 <= 12'h008; end + endcase + end + always_ff @(posedge clk) begin + accept_i_ref_0 <= cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 + : cyc[4:0] == 5'd2 ? 6'd2 : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + accept_i_ref_1 <= cyc[4:0] == 5'd0 ? 12'h001 : cyc[4:0] == 5'd1 ? 12'h002 + : cyc[4:0] == 5'd2 ? 12'h004 : cyc[4:0] == 5'd3 ? 12'h008 : ~12'd0; + end + + // Accept J: an item that can never match in 2-state (an X in casez) is skipped. + // verilator lint_off CASEWITHX + wire [23:0] accept_j_in = 24'b1 << cyc[4:0]; + logic [5:0] accept_j_out, accept_j_ref; + always_comb begin + accept_j_out = '1; + casez (accept_j_in) + 24'b????????_????????_???????x : accept_j_out = 6'd9; // X never matches in 2-state, skipped + 24'b????????_????????_??????1? : accept_j_out = 6'd1; + 24'b????????_????????_?????1?? : accept_j_out = 6'd2; + 24'b????????_????????_????1??? : accept_j_out = 6'd3; + endcase + end + // verilator lint_on CASEWITHX + assign accept_j_ref = cyc[4:0] == 5'd1 ? 6'd1 : cyc[4:0] == 5'd2 ? 6'd2 + : cyc[4:0] == 5'd3 ? 6'd3 : ~6'd0; + + // Accept K: Will constant fold after converting to decoder + wire [23:0] accept_k_in; + logic [5:0] accept_k_out, accept_k_ref; + always_ff @(posedge clk) begin + accept_k_out <= '1; + casez (accept_k_in) + 24'b????????_????????_???????1 : accept_k_out <= 6'd0; + 24'b????????_????????_??????1? : accept_k_out <= 6'd1; + 24'b????????_????????_?????1?? : accept_k_out <= 6'd2; + 24'b????????_????????_????1??? : accept_k_out <= 6'd3; + endcase + end + always_ff @(posedge clk) + accept_k_ref <= 6'd2; + assign accept_k_in = 24'b100; + + // The cases below are intentionally NOT converted to a decoder. + + // Reject A: too few conditions to be worth the indexed load. + wire [23:0] reject_a_in = 24'b1 << cyc[4:0]; + logic [5:0] reject_a_out, reject_a_ref; + always_comb begin + reject_a_out = '1; + casez (reject_a_in) + 24'b????????_????????_???????1 : reject_a_out = 6'd0; + 24'b????????_????????_??????1? : reject_a_out = 6'd1; + endcase + end + assign reject_a_ref = cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 : ~6'd0; + + // Reject B: every condition contains an X, so none can ever match in 2-state. The case has no + // matchable branches, so it must not be treated as a decoder - it just keeps the + // pre-case default. + // verilator lint_off CASEWITHX + wire [23:0] reject_b_in = 24'b1 << cyc[4:0]; + logic [5:0] reject_b_out, reject_b_ref; + always_comb begin + reject_b_out = 6'h2a; + casez (reject_b_in) + 24'b????????_????????_???????x : reject_b_out = 6'd0; + 24'b????????_????????_??????x? : reject_b_out = 6'd1; + 24'b????????_????????_?????x?? : reject_b_out = 6'd2; + endcase + end + // verilator lint_on CASEWITHX + assign reject_b_ref = 6'h2a; // No condition can ever match, so always the pre-default + + // Test driver/checker + always @(posedge clk) begin + `checkh(accept_a_out_0, accept_a_ref_0); + `checkh(accept_a_out_1, accept_a_ref_1); + `checkh(accept_a_out_2, accept_a_ref_2); + `checkh(accept_b_out_0, accept_b_ref_0); + `checkh(accept_b_out_1, accept_b_ref_1); + `checkh(accept_b_out_2, accept_b_ref_2); + `checkh(accept_c_out_0, accept_c_ref_0); + `checkh(accept_c_out_1, accept_c_ref_1); + `checkh(accept_c_out_2, accept_c_ref_2); + `checkh(accept_d_out, accept_d_ref); + `checkh(accept_e_out, accept_e_ref); + `checkh(accept_f_out, accept_f_ref); + `checkh(accept_g_out, accept_g_ref); + `checkh(accept_h_out_0, accept_h_ref_0); + `checkh(accept_h_out_1, accept_h_ref_1); + `checkh(accept_i_out_0, accept_i_ref_0); + `checkh(accept_i_out_1, accept_i_ref_1); + `checkh(accept_j_out, accept_j_ref); + `checkh(accept_k_out, accept_k_ref); + `checkh(reject_a_out, reject_a_ref); + `checkh(reject_b_out, reject_b_ref); + + cyc <= cyc + 32'd1; + if (cyc[16]) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_case_decoder_off.py b/test_regress/t/t_case_decoder_off.py new file mode 100755 index 000000000..abc61d879 --- /dev/null +++ b/test_regress/t/t_case_decoder_off.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.top_filename = "t/t_case_decoder.v" + +test.compile(verilator_flags2=['--binary', '--stats', '-fno-case-decoder']) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases decoder\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index 80c5aad04..ae7e6d781 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -436,4 +436,86 @@ module t ( assign VOLATILE_ARRAY_IN_CYCLE_1 = volatile_array_in_cycle_1a[1]; // verilator lint_on + ////////////////////////////////////////////////////////////////////////// + // Match masked + ////////////////////////////////////////////////////////////////////////// + + logic [63:0] match_masked; // UNOPTFLAT + always_comb begin + casez (rand_a[31:0]) + 32'b????????_????????_????????_???????1 : match_masked[31:0] = 32'd00; + 32'b????????_????????_????????_??????1? : match_masked[31:0] = 32'd01; + 32'b????????_????????_????????_?????1?? : match_masked[31:0] = 32'd02; + 32'b????????_????????_????????_????1??? : match_masked[31:0] = 32'd03; + 32'b????????_????????_????????_???1???? : match_masked[31:0] = 32'd04; + 32'b????????_????????_????????_??1????? : match_masked[31:0] = 32'd05; + 32'b????????_????????_????????_?1?????? : match_masked[31:0] = 32'd06; + 32'b????????_????????_????????_1??????? : match_masked[31:0] = 32'd07; + 32'b????????_????????_???????1_???????? : match_masked[31:0] = 32'd08; + 32'b????????_????????_??????1?_???????? : match_masked[31:0] = 32'd09; + 32'b????????_????????_?????1??_???????? : match_masked[31:0] = 32'd10; + 32'b????????_????????_????1???_???????? : match_masked[31:0] = 32'd11; + 32'b????????_????????_???1????_???????? : match_masked[31:0] = 32'd12; + 32'b????????_????????_??1?????_???????? : match_masked[31:0] = 32'd13; + 32'b????????_????????_?1??????_???????? : match_masked[31:0] = 32'd14; + 32'b????????_????????_1???????_???????? : match_masked[31:0] = 32'd15; + 32'b????????_???????1_????????_???????? : match_masked[31:0] = 32'd16; + 32'b????????_??????1?_????????_???????? : match_masked[31:0] = 32'd17; + 32'b????????_?????1??_????????_???????? : match_masked[31:0] = 32'd18; + 32'b????????_????1???_????????_???????? : match_masked[31:0] = 32'd19; + 32'b????????_???1????_????????_???????? : match_masked[31:0] = 32'd20; + 32'b????????_??1?????_????????_???????? : match_masked[31:0] = 32'd21; + 32'b????????_?1??????_????????_???????? : match_masked[31:0] = 32'd22; + 32'b????????_1???????_????????_???????? : match_masked[31:0] = 32'd23; + 32'b???????1_????????_????????_???????? : match_masked[31:0] = 32'd24; + 32'b??????1?_????????_????????_???????? : match_masked[31:0] = 32'd25; + 32'b?????1??_????????_????????_???????? : match_masked[31:0] = 32'd26; + 32'b????1???_????????_????????_???????? : match_masked[31:0] = 32'd27; + 32'b???1????_????????_????????_???????? : match_masked[31:0] = 32'd28; + 32'b??1?????_????????_????????_???????? : match_masked[31:0] = 32'd29; + 32'b?1??????_????????_????????_???????? : match_masked[31:0] = 32'd30; + 32'b1???????_????????_????????_???????? : match_masked[31:0] = 32'd31; + default : match_masked[31:0] = '1; + endcase + end + always_comb begin + casez (match_masked[31:0]) + 32'd00 : match_masked[63:32] = 32'b00000000_00000000_00000000_00000001; + 32'd01 : match_masked[63:32] = 32'b00000000_00000000_00000000_00000010; + 32'd02 : match_masked[63:32] = 32'b00000000_00000000_00000000_00000100; + 32'd03 : match_masked[63:32] = 32'b00000000_00000000_00000000_00001000; + 32'd04 : match_masked[63:32] = 32'b00000000_00000000_00000000_00010000; + 32'd05 : match_masked[63:32] = 32'b00000000_00000000_00000000_00100000; + 32'd06 : match_masked[63:32] = 32'b00000000_00000000_00000000_01000000; + 32'd07 : match_masked[63:32] = 32'b00000000_00000000_00000000_10000000; + 32'd08 : match_masked[63:32] = 32'b00000000_00000000_00000001_00000000; + 32'd09 : match_masked[63:32] = 32'b00000000_00000000_00000010_00000000; + 32'd10 : match_masked[63:32] = 32'b00000000_00000000_00000100_00000000; + 32'd11 : match_masked[63:32] = 32'b00000000_00000000_00001000_00000000; + 32'd12 : match_masked[63:32] = 32'b00000000_00000000_00010000_00000000; + 32'd13 : match_masked[63:32] = 32'b00000000_00000000_00100000_00000000; + 32'd14 : match_masked[63:32] = 32'b00000000_00000000_01000000_00000000; + 32'd15 : match_masked[63:32] = 32'b00000000_00000000_10000000_00000000; + 32'd16 : match_masked[63:32] = 32'b00000000_00000001_00000000_00000000; + 32'd17 : match_masked[63:32] = 32'b00000000_00000010_00000000_00000000; + 32'd18 : match_masked[63:32] = 32'b00000000_00000100_00000000_00000000; + 32'd19 : match_masked[63:32] = 32'b00000000_00001000_00000000_00000000; + 32'd20 : match_masked[63:32] = 32'b00000000_00010000_00000000_00000000; + 32'd21 : match_masked[63:32] = 32'b00000000_00100000_00000000_00000000; + 32'd22 : match_masked[63:32] = 32'b00000000_01000000_00000000_00000000; + 32'd23 : match_masked[63:32] = 32'b00000000_10000000_00000000_00000000; + 32'd24 : match_masked[63:32] = 32'b00000001_00000000_00000000_00000000; + 32'd25 : match_masked[63:32] = 32'b00000010_00000000_00000000_00000000; + 32'd26 : match_masked[63:32] = 32'b00000100_00000000_00000000_00000000; + 32'd27 : match_masked[63:32] = 32'b00001000_00000000_00000000_00000000; + 32'd28 : match_masked[63:32] = 32'b00010000_00000000_00000000_00000000; + 32'd29 : match_masked[63:32] = 32'b00100000_00000000_00000000_00000000; + 32'd30 : match_masked[63:32] = 32'b01000000_00000000_00000000_00000000; + 32'd31 : match_masked[63:32] = 32'b10000000_00000000_00000000_00000000; + default: match_masked[63:32] = 32'b00000000_00000000_00000000_00000000; + endcase + end + `signal(MATCH_MASKED, 64); + assign MATCH_MASKED = match_masked; + endmodule diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 98d9146be..3e393a297 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -136,12 +136,12 @@ module t ( `signal(FOLD_SEL, const_a[3:1]); - int fold_arraysel_table_one; - ffs ffs_a(convoluted_zero[0] ? 8'hff: 8'd2, fold_arraysel_table_one); - int fold_arraysel_table_two; - ffs ffs_b(convoluted_zero[1] ? 8'hff: 8'd7, fold_arraysel_table_two); - `signal(FOLD_ARRAYSEL_TABLE_ONE, fold_arraysel_table_one); - `signal(FOLD_ARRAYSEL_TABLE_TWO, fold_arraysel_table_two); + int fold_arraysel_table; + ffs ffs_a(convoluted_zero[0] ? 20'hff: 20'd2, fold_arraysel_table); + int fold_matchmasked; + ffs ffs_b(convoluted_zero[1] ? 20'hff: 20'd7, fold_matchmasked); + `signal(FOLD_ARRAYSEL_TABLE, fold_arraysel_table); + `signal(FOLD_MATCHMASKED, fold_matchmasked); `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, rand_a + const_a); `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, rand_a + ~rand_a); @@ -439,23 +439,33 @@ module t ( endmodule module ffs( - input logic [7:0] i, + input logic [19:0] i, output int o ); // V3Table will convert this always_comb begin - // verilator lint_off CASEOVERLAP casez (i) - 8'b1???????: o = 7; - 8'b?1??????: o = 6; - 8'b??1?????: o = 5; - 8'b???1????: o = 4; - 8'b????1???: o = 3; - 8'b?????1??: o = 2; - 8'b??????1?: o = 1; - 8'b???????1: o = 0; - 8'b00000000: o = -1; + 20'b1???????????????????: o = 19; + 20'b?1??????????????????: o = 18; + 20'b??1?????????????????: o = 17; + 20'b???1????????????????: o = 16; + 20'b????1???????????????: o = 15; + 20'b?????1??????????????: o = 14; + 20'b??????1?????????????: o = 13; + 20'b???????1????????????: o = 12; + 20'b????????1???????????: o = 11; + 20'b?????????1??????????: o = 10; + 20'b??????????1?????????: o = 9; + 20'b???????????1????????: o = 8; + 20'b????????????1???????: o = 7; + 20'b?????????????1??????: o = 6; + 20'b??????????????1?????: o = 5; + 20'b???????????????1????: o = 4; + 20'b????????????????1???: o = 3; + 20'b?????????????????1??: o = 2; + 20'b??????????????????1?: o = 1; + 20'b???????????????????1: o = 0; + default: o = 32'hffffffff; endcase - // verilator lint_on CASEOVERLAP end endmodule diff --git a/test_regress/t/t_opt_table_enum.py b/test_regress/t/t_opt_table_enum.py index 561f0c766..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_enum.py +++ b/test_regress/t/t_opt_table_enum.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats", "-fno-case-table"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_packed_array.py b/test_regress/t/t_opt_table_packed_array.py index 561f0c766..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_packed_array.py +++ b/test_regress/t/t_opt_table_packed_array.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats", "-fno-case-table"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_real.py b/test_regress/t/t_opt_table_real.py index 561f0c766..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_real.py +++ b/test_regress/t/t_opt_table_real.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats", "-fno-case-table"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_same.py b/test_regress/t/t_opt_table_same.py index a51a48a73..8f09062b6 100755 --- a/test_regress/t/t_opt_table_same.py +++ b/test_regress/t/t_opt_table_same.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats", "-fno-case-table"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 2) diff --git a/test_regress/t/t_opt_table_signed.py b/test_regress/t/t_opt_table_signed.py index 561f0c766..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_signed.py +++ b/test_regress/t/t_opt_table_signed.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats", "-fno-case-table"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_string.py b/test_regress/t/t_opt_table_string.py index 561f0c766..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_string.py +++ b/test_regress/t/t_opt_table_string.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats", "-fno-case-table"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_table_struct.py b/test_regress/t/t_opt_table_struct.py index 561f0c766..00a2e3a5a 100755 --- a/test_regress/t/t_opt_table_struct.py +++ b/test_regress/t/t_opt_table_struct.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats", "-fno-case-table"]) +test.compile(verilator_flags2=["--stats", "-fno-case-table", "-fno-case-decoder"]) if test.vlt_all: test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) From 9a231d254dad8a242ce0b275cb9397309905ad3e Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 19 Jun 2026 22:28:50 +0100 Subject: [PATCH 61/89] Optimize Dfg cycle breaking to do less work (#7210) When a vertex is made acyclic, conservatively update the SCC map to propagate and mark connected vertices as acyclic as much as possible. This way we can stop early if the graph becomes acyclic after some fixups. This can significantly reduce the number of fixups needing to be applied, avoiding introducing redundancy. --- src/V3DfgBreakCycles.cpp | 235 +++++++++++++++++++++------- test_regress/t/t_dfg_break_cycles.v | 13 ++ 2 files changed, 191 insertions(+), 57 deletions(-) diff --git a/src/V3DfgBreakCycles.cpp b/src/V3DfgBreakCycles.cpp index ecdcc875d..4bab8858d 100644 --- a/src/V3DfgBreakCycles.cpp +++ b/src/V3DfgBreakCycles.cpp @@ -31,8 +31,109 @@ VL_DEFINE_DEBUG_FUNCTIONS; namespace V3DfgBreakCycles { -// Throughout these algorithm, we use the DfgUserMap as a map to the SCC number -using Vtx2Scc = DfgUserMap; +// Mutable map from vertices to the SCC index they belong to. Initialized +// from a proper analysis, then updated as we break cycles. After updates +// the information is consertvative, meaning a vertex that is marked as +// cyclic might not actually be if an SCC split into multple SCCs after a +// fixup. However it should always be true that if a vertex is thought to +// be non-cyclic based on this datastructure, then it indeed is not cyclic. +class SccInfo final { + DfgGraph& m_dfg; // The annotated graph + // The map from vertices to their SCC index (0 if trivial SCC - non cyclic) + std::unordered_map m_vtx2Scc; + size_t m_nCyclicVertices = 0; // Number of vertices in non-trivial SCCs + +public: + SccInfo(DfgGraph& dfg) + : m_dfg{dfg} { + // Initialize m_vtx2Scc for the graph from a proper analysis + DfgUserMap vtx2Scc = dfg.makeUserMap(); + V3DfgPasses::colorStronglyConnectedComponents(dfg, vtx2Scc); + m_dfg.forEachVertex([&](const DfgVertex& vtx) { + const uint64_t sccIdx = vtx2Scc[vtx]; + m_vtx2Scc[&vtx] = sccIdx; + if (sccIdx) ++m_nCyclicVertices; + }); + } + + // Is the graph cyclic still? + bool isCyclic() const { return m_nCyclicVertices; } + + // Returns the index of the SCC the given vertex is a part of + // This is 0 iff the vertex is in a trivial SCC (no cycles through vertex) + uint64_t get(const DfgVertex& vtx) const { return m_vtx2Scc.at(&vtx); } + + // Add a new vertex to graph that is part of the given SCC + void add(const DfgVertex& vtx, uint64_t sccIdx) { + const bool newEntry = m_vtx2Scc.emplace(&vtx, sccIdx).second; + UASSERT_OBJ(newEntry, &vtx, "Vertex already inserted"); + if (sccIdx) ++m_nCyclicVertices; + } + + bool stillCyclicFwd(const DfgVertex& vtx) const { + // If it only reads non-cyclic vertices, it has also become acyclic + return vtx.foreachSource([&](const DfgVertex& src) { // + return m_vtx2Scc.at(&src); + }); + } + + bool stillCyclicBwd(const DfgVertex& vtx) const { + // If itonly drives non-cyclic vertices, it has also become acyclic + return vtx.foreachSink([&](const DfgVertex& dst) { // + return m_vtx2Scc.at(&dst); + }); + } + + // The given vertex is known to have become acyclic after some edits, + // propagate through graph to mark connected vertices as well. + void updateAcyclic(const DfgVertex& vtx) { + // Mark as acyclic + uint64_t& sccIdxr = m_vtx2Scc.at(&vtx); + UASSERT_OBJ(sccIdxr, &vtx, "Vertex should be cyclic"); + sccIdxr = 0; + --m_nCyclicVertices; + // Propagate the update through the graph forward + vtx.foreachSink([&](const DfgVertex& dst) { + // Sink was known to be acyclic, stop + if (!m_vtx2Scc.at(&dst)) return false; + if (!stillCyclicFwd(dst)) updateAcyclic(dst); + return false; + }); + // Propagate the update through the graph backward + vtx.foreachSource([&](const DfgVertex& src) { + // Source was known to be acyclic, stop + if (!m_vtx2Scc.at(&src)) return false; + if (!stillCyclicBwd(src)) updateAcyclic(src); + return false; + }); + } + + /* + Check stored information is consistent with actual SCCs. Note we + can't detect during updates if an SCC has split into multiple SCCs. + Consider: + A -- E -- G + / \ / \ + B C H I + \ / \ / + D -- F -- J + If we fixed up E, the original SCC would split into two, (A,B,C,D) + and (G,H,I,J), but also F would no longer be cyclic. For this reason, + we can only assert that anything that we think is not cyclic based + on the SccInfo must indeed be not cyclic, but might still think a + vertex is cyclic based on the SccInfo but it actually isn't. + */ + void validate() const { + // Re-compute the actual SCCs + DfgUserMap actual = m_dfg.makeUserMap(); + V3DfgPasses::colorStronglyConnectedComponents(m_dfg, actual); + // Assert that if we think a vertex is not cyclic, it indeed is not + m_dfg.forEachVertex([&](const DfgVertex& vtx) { + // 'think not cyclic' implies 'actually not cyclic' + UASSERT_OBJ(m_vtx2Scc.at(&vtx) || !actual.at(vtx), &vtx, "Inconsisten SccInfo"); + }); + } +}; class TraceDriver final : public DfgVisitor { // TYPES @@ -67,7 +168,7 @@ class TraceDriver final : public DfgVisitor { // STATE DfgGraph& m_dfg; // The graph being processed - Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map + SccInfo& m_sccInfo; // The SccInfo instance // The strongly connected component we are currently trying to escape uint64_t m_component = 0; uint32_t m_lsb = 0; // LSB to extract from the currently visited Vertex @@ -86,11 +187,11 @@ class TraceDriver final : public DfgVisitor { // Create and return a new Vertex. Always use this to create new vertices. // Fileline is taken from 'refp', but 'refp' is otherwise not used. - // Also sets m_vtx2Scc[vtx] to 0, indicating the new vertex is not part of - // a strongly connected component. This should always be true, as all the - // vertices we create here are driven from outside the component we are - // trying to escape, and will sink into that component. Given those are - // separate SCCs, these new vertices must be acyclic. + // Also adds the vertex to m_sccInfo with scc index 0, indicating the new + // vertex is not part of a strongly connected component. This should always + // be true, as all the vertices we create here are driven from outside the + // component we are trying to escape, and will sink into that component. + // Given those are separate SCCs, these new vertices must be acyclic. template Vertex* make(const DfgVertex* refp, uint32_t width) { static_assert(std::is_base_of::value // @@ -99,7 +200,7 @@ class TraceDriver final : public DfgVisitor { if VL_CONSTEXPR_CXX17 (std::is_same::value) { DfgConst* const vtxp = new DfgConst{m_dfg, refp->fileline(), width, 0}; - m_vtx2Scc[vtxp] = 0; + m_sccInfo.add(*vtxp, 0); return reinterpret_cast(vtxp); } else { // TODO: this is a gross hack around lack of C++17 'if constexpr' Vtx is always Vertex @@ -108,7 +209,7 @@ class TraceDriver final : public DfgVisitor { using Vtx = typename std::conditional::value, DfgSel, Vertex>::type; Vtx* const vtxp = new Vtx{m_dfg, refp->fileline(), DfgDataType::packed(width)}; - m_vtx2Scc[vtxp] = 0; + m_sccInfo.add(*vtxp, 0); return reinterpret_cast(vtxp); } } @@ -123,7 +224,7 @@ class TraceDriver final : public DfgVisitor { DfgVertexVar* const varp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep); varp->vscp()->varp()->isInternal(true); varp->tmpForp(varp->vscp()); - m_vtx2Scc[varp] = 0; + m_sccInfo.add(*varp, 0); return varp; } @@ -146,7 +247,7 @@ class TraceDriver final : public DfgVisitor { // If already traced this vtxp/msb/lsb, just use the result. // This is important to avoid combinatorial explosion when the // same sub-expression is needed multiple times. - } else if (m_vtx2Scc.at(vtxp) != m_component) { + } else if (m_sccInfo.get(*vtxp) != m_component) { // If the currently traced vertex is in a different component, // then we found what we were looking for. respr = vtxp; @@ -616,9 +717,9 @@ class TraceDriver final : public DfgVisitor { public: // CONSTRUCTOR - TraceDriver(DfgGraph& dfg, Vtx2Scc& vtx2Scc) + TraceDriver(DfgGraph& dfg, SccInfo& sccInfo) : m_dfg{dfg} - , m_vtx2Scc{vtx2Scc} { + , m_sccInfo{sccInfo} { #ifdef VL_DEBUG if (v3Global.opt.debugCheck()) { m_lineCoverageFile.open( // @@ -635,7 +736,7 @@ public: // trace can always succeed. DfgVertex* apply(DfgVertex& vtx, uint32_t lsb, uint32_t width) { VL_RESTORER(m_component); - m_component = m_vtx2Scc.at(&vtx); + m_component = m_sccInfo.get(vtx); return trace(&vtx, lsb + width - 1, lsb); } }; @@ -648,7 +749,7 @@ class IndependentBits final : public DfgVisitor { }; // STATE - const Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map + const SccInfo& m_sccInfo; // The SccInfo instance // Vertex to current bit mask map. The mask is set for the bits that are independent of the SCC std::unordered_map m_vtxp2Mask; // Work list for the traversal (min-queue of vertex RPO numbers) @@ -1000,7 +1101,7 @@ class IndependentBits final : public DfgVisitor { void enqueueSinks(DfgVertex& vtx) { vtx.foreachSink([&](DfgVertex& sink) { // Ignore if sink is not part of an SCC, already has all bits marked independent - if (!m_vtx2Scc.at(sink)) return false; + if (!m_sccInfo.get(sink)) return false; // If a vertex is not handled directly, recursively enqueue its sinks instead if (!handledDirectly(sink)) { enqueueSinks(sink); @@ -1031,8 +1132,8 @@ class IndependentBits final : public DfgVisitor { postOrderEnumeration.emplace_back(&vtx); }; - IndependentBits(DfgGraph& dfg, const Vtx2Scc& vtx2Scc) - : m_vtx2Scc{vtx2Scc} { + IndependentBits(DfgGraph& dfg, const SccInfo& sccInfo) + : m_sccInfo{sccInfo} { #ifdef VL_DEBUG if (v3Global.opt.debugCheck()) { @@ -1069,12 +1170,12 @@ class IndependentBits final : public DfgVisitor { // Enqueue sinks of all SCC vertices that have at least one independent bit for (DfgVertex* const vtxp : rpoEnumeration) { if (!handledDirectly(*vtxp)) continue; - if (m_vtx2Scc.at(vtxp)) continue; + if (m_sccInfo.get(*vtxp)) continue; mask(*vtxp).setAllBits1(); } for (DfgVertex* const vtxp : rpoEnumeration) { if (!handledDirectly(*vtxp)) continue; - if (!m_vtx2Scc.at(vtxp)) continue; + if (!m_sccInfo.get(*vtxp)) continue; iterate(vtxp); UINFO(9, "Initial independent bits of " << vtxp << " " << vtxp->typeName() @@ -1089,7 +1190,7 @@ class IndependentBits final : public DfgVisitor { m_workQueue.pop(); m_vtxp2State.at(currp).m_isOnWorkList = false; // Should not enqueue vertices that are not in an SCC - UASSERT_OBJ(m_vtx2Scc.at(currp), currp, "Vertex should be in an SCC"); + UASSERT_OBJ(m_sccInfo.get(*currp), currp, "Vertex should be in an SCC"); // Should only enqueue packed vertices UASSERT_OBJ(handledDirectly(*currp), currp, "Vertex should be handled directly"); @@ -1128,18 +1229,18 @@ public: // set if the corresponding bit in that vertex is known to be independent // of the values of vertices in the same SCC as the vertex resides in. static std::unordered_map apply(DfgGraph& dfg, - const Vtx2Scc& vtx2Scc) { - return std::move(IndependentBits{dfg, vtx2Scc}.m_vtxp2Mask); + const SccInfo& sccInfo) { + return std::move(IndependentBits{dfg, sccInfo}.m_vtxp2Mask); } }; class FixUp final { DfgGraph& m_dfg; // The graph being processed - Vtx2Scc& m_vtx2Scc; // The Vertex to SCC map - TraceDriver m_traceDriver{m_dfg, m_vtx2Scc}; + SccInfo& m_sccInfo; // The SccInfo instance + TraceDriver m_traceDriver{m_dfg, m_sccInfo}; // The independent bits map const std::unordered_map m_independentBits - = IndependentBits::apply(m_dfg, m_vtx2Scc); + = IndependentBits::apply(m_dfg, m_sccInfo); size_t m_nImprovements = 0; // Number of improvements mde // Returns a bitmask set if that bit of 'vtx' is used (has a sink) @@ -1189,7 +1290,7 @@ class FixUp final { // If dependent, fall back on using the part of the variable DfgSel* const selp = new DfgSel{m_dfg, vtx.fileline(), DfgDataType::packed(width)}; // Same component as 'vtxp', as reads 'vtxp' and will replace 'vtxp' - m_vtx2Scc[selp] = m_vtx2Scc.at(vtx); + m_sccInfo.add(*selp, m_sccInfo.get(vtx)); // Do not connect selp->fromp yet, need to do afer replacing 'vtxp' selp->lsb(lsb); termps.emplace_back(selp); @@ -1238,7 +1339,7 @@ class FixUp final { } else { // The range is not used, just use constant 0 as a placeholder DfgConst* const constp = new DfgConst{m_dfg, flp, msb - lsb + 1, 0}; - m_vtx2Scc[constp] = 0; + m_sccInfo.add(*constp, 0); termps.emplace_back(constp); } // Next iteration @@ -1248,7 +1349,7 @@ class FixUp final { // Concatenate all the terms to create the replacement DfgVertex* replacementp = termps.front(); - const uint64_t vComp = m_vtx2Scc.at(vtx); + const uint64_t vComp = m_sccInfo.get(vtx); for (size_t i = 1; i < termps.size(); ++i) { DfgVertex* const termp = termps[i]; const uint32_t catWidth = replacementp->width() + termp->width(); @@ -1260,9 +1361,9 @@ class FixUp final { // component, it's part of that component, otherwise its not // cyclic (all terms are from outside the original component, // and feed into the original component). - const uint64_t tComp = m_vtx2Scc.at(termp); - const uint64_t rComp = m_vtx2Scc.at(replacementp); - m_vtx2Scc[catp] = tComp == vComp || rComp == vComp ? vComp : 0; + const uint64_t tComp = m_sccInfo.get(*termp); + const uint64_t rComp = m_sccInfo.get(*replacementp); + m_sccInfo.add(*catp, tComp == vComp || rComp == vComp ? vComp : 0); replacementp = catp; } @@ -1274,6 +1375,22 @@ class FixUp final { if (!selp->fromp()) selp->fromp(&vtx); } } + + // If the vertex still has sinks, then the replacement is still cyclic, and vice versa + UASSERT_OBJ(!vtx.hasSinks() == !m_sccInfo.get(*replacementp), &vtx, + "Replacement vertex SCC inconsistent"); + + // If we broke the cycle through this vertex we can update the SccInfo + if (!vtx.hasSinks()) { + m_sccInfo.updateAcyclic(vtx); + if (!m_sccInfo.get(*replacementp)) { + replacementp->foreachSink([&](DfgVertex& dst) { + if (!m_sccInfo.get(dst)) return false; + if (!m_sccInfo.stillCyclicFwd(dst)) m_sccInfo.updateAcyclic(dst); + return false; + }); + } + } } void main(DfgVertexVar& var) { @@ -1284,10 +1401,10 @@ class FixUp final { fixUpPacked(var); } else if (var.is()) { // For array variables, fix up element-wise - const uint64_t component = m_vtx2Scc.at(var); + const uint64_t component = m_sccInfo.get(var); var.foreachSink([&](DfgVertex& sink) { // Ignore if sink is not part of same cycle - if (m_vtx2Scc.at(sink) != component) return false; + if (m_sccInfo.get(sink) != component) return false; // Only handle ArraySels with constant index DfgArraySel* const aselp = sink.cast(); if (!aselp) return false; @@ -1301,23 +1418,27 @@ class FixUp final { UINFO(9, "FixUp made " << m_nImprovements << " improvements"); } - FixUp(DfgGraph& dfg, Vtx2Scc& vtx2Scc) + FixUp(DfgGraph& dfg, SccInfo& sccInfo) : m_dfg{dfg} - , m_vtx2Scc{vtx2Scc} {} + , m_sccInfo{sccInfo} {} public: // Compute which bits of vertices are independent of the SCC they reside // in, and replace rferences to used independent bits with an equivalent // vertex that is not part of the same SCC. - static size_t apply(DfgGraph& dfg, Vtx2Scc& vtx2Scc) { - FixUp fixUp{dfg, vtx2Scc}; + static size_t apply(DfgGraph& dfg, SccInfo& sccInfo) { + if (!sccInfo.isCyclic()) return 0; + + FixUp fixUp{dfg, sccInfo}; // TODO: Compute minimal feedback vertex set and cut only those. // Sadly that is a computationally hard problem. - // Fix up cyclic variables + // Fix up cyclic variables, stop as soon as the graph becomes acyclic for (DfgVertexVar& vtx : dfg.varVertices()) { - if (vtx2Scc.at(vtx)) fixUp.main(vtx); + if (!sccInfo.get(vtx)) continue; + fixUp.main(vtx); + if (!sccInfo.isCyclic()) break; } // Return the number of improvements made return fixUp.m_nImprovements; @@ -1359,35 +1480,35 @@ breakCycles(const DfgGraph& dfg, V3DfgContext& ctx) { // Iterate while an improvement can be made and the graph is still cyclic do { - // Color SCCs (populates DfgVertex::user()) - Vtx2Scc vtx2Scc = res.makeUserMap(); - const uint32_t numNonTrivialSCCs - = V3DfgPasses::colorStronglyConnectedComponents(res, vtx2Scc); - - // Congrats if it has become acyclic - if (!numNonTrivialSCCs) { - UINFO(7, "Graph became acyclic after " << nImprovements << " improvements"); - dump(7, res, "result-acyclic"); - ++ctx.m_breakCyclesContext.m_nFixed; - return {std::move(resultp), true}; - } + // Compute SCCs + SccInfo sccInfo{res}; // Fix up independent ranges in vertices UINFO(9, "New iteration after " << nImprovements << " improvements"); prevNImprovements = nImprovements; - const size_t nFixed = FixUp::apply(res, vtx2Scc); + const size_t nFixed = FixUp::apply(res, sccInfo); if (nFixed) { nImprovements += nFixed; ctx.m_breakCyclesContext.m_nImprovements += nFixed; dump(9, res, "FixUp"); } + + // Validate SccInfo if in debug mode + if (v3Global.opt.debugCheck()) sccInfo.validate(); + + // Congrats if it has become acyclic + if (!sccInfo.isCyclic()) { + UINFO(7, "Graph became acyclic after " << nImprovements << " improvements"); + dump(7, res, "result-acyclic"); + ++ctx.m_breakCyclesContext.m_nFixed; + return {std::move(resultp), true}; + } } while (nImprovements != prevNImprovements); if (dumpDfgLevel() >= 9) { - Vtx2Scc vtx2Scc = res.makeUserMap(); - V3DfgPasses::colorStronglyConnectedComponents(res, vtx2Scc); + const SccInfo sccInfo{res}; res.dumpDotFilePrefixed("breakCycles-remaining", [&](const DfgVertex& vtx) { - return vtx2Scc[vtx]; // + return sccInfo.get(vtx); // }); } diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index ae7e6d781..b2124b1d6 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -124,6 +124,9 @@ module t ( assign SHIFTRS_VARIABLE_4_B = signed'({4'b1111, SHIFTRS_VARIABLE_4_A[1]}) >>> rand_b[0]; assign SHIFTRS_VARIABLE_4_A = rand_a[3:0] ^ SHIFTRS_VARIABLE_4_B[4:1]; + `signal(SHIFTRS_VARIABLE_5, 2); // UNOPTFLAT + assign SHIFTRS_VARIABLE_5 = signed'({rand_a[0], SHIFTRS_VARIABLE_5[1]}) >>> rand_b[0]; + `signal(SHIFTR, 14); // UNOPTFLAT assign SHIFTR = { SHIFTR[6:5], // 13:12 @@ -201,6 +204,16 @@ module t ( assign REPLICATE_4_B = {2{REPLICATE_4_A[1:0]}}; assign REPLICATE_4_A = {REPLICATE_4_B[2:1], rand_a[1:0]}; + function automatic logic [1:0] span_repl(input logic [1:0] x); + logic [3:0] r; + r = {2{x}}; + return r[2:1]; + endfunction + wire logic [3:0] replicate_5_int; // UNOPTFLAT + assign replicate_5_int = {span_repl(replicate_5_int[1:0]), rand_a[1:0]}; + `signal(REPLICATE_5, 4); + assign REPLICATE_5 = replicate_5_int; + `signal(PARTIAL, 4); // UNOPTFLAT assign PARTIAL[0] = rand_a[0]; // PARTIAL[1] intentionally unconnected From d66f96e2465fe691e45779078c49bc109469bc78 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 20 Jun 2026 06:45:04 -0400 Subject: [PATCH 62/89] Commentary: Changes update --- Changes | 3 + docs/internals.rst | 4 +- test_regress/t/t_case_decoder.v | 612 +++++++++++++++++--------------- 3 files changed, 321 insertions(+), 298 deletions(-) diff --git a/Changes b/Changes index 9df830925..5458cd2bd 100644 --- a/Changes +++ b/Changes @@ -72,6 +72,7 @@ Verilator 5.049 devel * Support unpacked struct stream (#7767). [Nick Brereton] * Support $assertcontrol control_type from lock to kill (#7788). [Yilou Wang] * Support unbounded always [m:$] and strong s_always liveness (#7798). [Yilou Wang] +* Support method calls on a sub-interface via a virtual interface (#7800). [Yilou Wang] * Optimize emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.] * Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.] * Optimize forced signal handling (#7554 partial) (#7572) (#7594) (#7596). [Krzysztof Bieganski, Artur Bieniek, Antmicro Ltd.] @@ -93,6 +94,8 @@ Verilator 5.049 devel * Optimize table lookups in DFG (#7772). [Geza Lore, Testorrent USA, Inc.] * Optimize input combinational logic by change detection (#7784). [Geza Lore, Testorrent USA, Inc.] * Optimize decoder case statements into lookup tables (#7795). [Geza Lore, Testorrent USA, Inc.] +* Optimize wide decoder case statements into decoder expressions (#7804). [Geza Lore, Testorrent USA, Inc.] +* Optimize DFG cycle breaking to do less work (#7210). [Geza Lore, Testorrent USA, Inc.] * Fix TSP variable ordering for mtasks (#5342) (#7610). [Muzaffer Kal] * Fix inlining static initializer in V3Gate (#5381) (#7503). [Andrew Nolte] [Geza Lore, Testorrent USA, Inc.] * Fix timed nested fork block with disable (#6720) (#7743). [Marco Bartoli] diff --git a/docs/internals.rst b/docs/internals.rst index 241c4b14a..ab5754dd0 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -748,8 +748,8 @@ Thanks to this separation a coroutine: * cannot be resumed before it is suspended - ``test_regress/t/t_event_control_double_excessive.v``; * firing cannot cannot be lost - (``test_regress/t/t_event_control_double_lost.v``) - which is possible when - triggers are not evaluated right before awaiting. + (``test_regress/t/t_event_control_double_lost.v``) - which is possible + when triggers are not evaluated right before awaiting. All coroutines are committed and resumed in the 'act' eval loop. With timing features enabled, the ``_eval()`` function takes this form: diff --git a/test_regress/t/t_case_decoder.v b/test_regress/t/t_case_decoder.v index 6d5c779c2..495c19320 100644 --- a/test_regress/t/t_case_decoder.v +++ b/test_regress/t/t_case_decoder.v @@ -22,110 +22,110 @@ module t; // Accept A: a 31-bit (I) selector decoded into outputs of three widths (I/Q/W result). wire [30:0] accept_a_in = 31'b1 << cyc[3:0]; - logic [ 5:0] accept_a_out_0, accept_a_ref_0; - logic [ 55:0] accept_a_out_1, accept_a_ref_1; + logic [5:0] accept_a_out_0, accept_a_ref_0; + logic [55:0] accept_a_out_1, accept_a_ref_1; logic [142:0] accept_a_out_2, accept_a_ref_2; always_comb begin casez (accept_a_in) - 31'b???????_????????_????????_???????1 : accept_a_out_0 = 6'd00; - 31'b???????_????????_????????_??????1? : accept_a_out_0 = 6'd01; - 31'b???????_????????_????????_?????1?? : accept_a_out_0 = 6'd02; - 31'b???????_????????_????????_????1??? : accept_a_out_0 = 6'd03; - 31'b???????_????????_????????_???1???? : accept_a_out_0 = 6'd04; - 31'b???????_????????_????????_??1????? : accept_a_out_0 = 6'd05; - 31'b???????_????????_????????_?1?????? : accept_a_out_0 = 6'd06; - 31'b???????_????????_????????_1??????? : accept_a_out_0 = 6'd07; - 31'b???????_????????_???????1_???????? : accept_a_out_0 = 6'd08; - 31'b???????_????????_??????1?_???????? : accept_a_out_0 = 6'd09; - 31'b???????_????????_?????1??_???????? : accept_a_out_0 = 6'd10; - 31'b???????_????????_????1???_???????? : accept_a_out_0 = 6'd11; - 31'b???????_????????_???1????_???????? : accept_a_out_0 = 6'd12; - 31'b???????_????????_??1?????_???????? : accept_a_out_0 = 6'd13; - 31'b???????_????????_?1??????_???????? : accept_a_out_0 = 6'd14; - 31'b???????_????????_1???????_???????? : accept_a_out_0 = 6'd15; - 31'b???????_???????1_????????_???????? : accept_a_out_0 = 6'd16; - 31'b???????_??????1?_????????_???????? : accept_a_out_0 = 6'd17; - 31'b???????_?????1??_????????_???????? : accept_a_out_0 = 6'd18; - 31'b???????_????1???_????????_???????? : accept_a_out_0 = 6'd19; - 31'b???????_???1????_????????_???????? : accept_a_out_0 = 6'd20; - 31'b???????_??1?????_????????_???????? : accept_a_out_0 = 6'd21; - 31'b???????_?1??????_????????_???????? : accept_a_out_0 = 6'd22; - 31'b???????_1???????_????????_???????? : accept_a_out_0 = 6'd23; - 31'b??????1_????????_????????_???????? : accept_a_out_0 = 6'd24; - 31'b?????1?_????????_????????_???????? : accept_a_out_0 = 6'd25; - 31'b????1??_????????_????????_???????? : accept_a_out_0 = 6'd26; - 31'b???1???_????????_????????_???????? : accept_a_out_0 = 6'd27; - 31'b??1????_????????_????????_???????? : accept_a_out_0 = 6'd28; - 31'b?1?????_????????_????????_???????? : accept_a_out_0 = 6'd29; - 31'b1??????_????????_????????_???????? : accept_a_out_0 = 6'd30; + 31'b???????_????????_????????_???????1: accept_a_out_0 = 6'd00; + 31'b???????_????????_????????_??????1?: accept_a_out_0 = 6'd01; + 31'b???????_????????_????????_?????1??: accept_a_out_0 = 6'd02; + 31'b???????_????????_????????_????1???: accept_a_out_0 = 6'd03; + 31'b???????_????????_????????_???1????: accept_a_out_0 = 6'd04; + 31'b???????_????????_????????_??1?????: accept_a_out_0 = 6'd05; + 31'b???????_????????_????????_?1??????: accept_a_out_0 = 6'd06; + 31'b???????_????????_????????_1???????: accept_a_out_0 = 6'd07; + 31'b???????_????????_???????1_????????: accept_a_out_0 = 6'd08; + 31'b???????_????????_??????1?_????????: accept_a_out_0 = 6'd09; + 31'b???????_????????_?????1??_????????: accept_a_out_0 = 6'd10; + 31'b???????_????????_????1???_????????: accept_a_out_0 = 6'd11; + 31'b???????_????????_???1????_????????: accept_a_out_0 = 6'd12; + 31'b???????_????????_??1?????_????????: accept_a_out_0 = 6'd13; + 31'b???????_????????_?1??????_????????: accept_a_out_0 = 6'd14; + 31'b???????_????????_1???????_????????: accept_a_out_0 = 6'd15; + 31'b???????_???????1_????????_????????: accept_a_out_0 = 6'd16; + 31'b???????_??????1?_????????_????????: accept_a_out_0 = 6'd17; + 31'b???????_?????1??_????????_????????: accept_a_out_0 = 6'd18; + 31'b???????_????1???_????????_????????: accept_a_out_0 = 6'd19; + 31'b???????_???1????_????????_????????: accept_a_out_0 = 6'd20; + 31'b???????_??1?????_????????_????????: accept_a_out_0 = 6'd21; + 31'b???????_?1??????_????????_????????: accept_a_out_0 = 6'd22; + 31'b???????_1???????_????????_????????: accept_a_out_0 = 6'd23; + 31'b??????1_????????_????????_????????: accept_a_out_0 = 6'd24; + 31'b?????1?_????????_????????_????????: accept_a_out_0 = 6'd25; + 31'b????1??_????????_????????_????????: accept_a_out_0 = 6'd26; + 31'b???1???_????????_????????_????????: accept_a_out_0 = 6'd27; + 31'b??1????_????????_????????_????????: accept_a_out_0 = 6'd28; + 31'b?1?????_????????_????????_????????: accept_a_out_0 = 6'd29; + 31'b1??????_????????_????????_????????: accept_a_out_0 = 6'd30; default: accept_a_out_0 = '1; endcase casez (accept_a_in) - 31'b???????_????????_????????_???????1 : accept_a_out_1 = 56'd0000; - 31'b???????_????????_????????_??????1? : accept_a_out_1 = 56'd0100; - 31'b???????_????????_????????_?????1?? : accept_a_out_1 = 56'd0200; - 31'b???????_????????_????????_????1??? : accept_a_out_1 = 56'd0300; - 31'b???????_????????_????????_???1???? : accept_a_out_1 = 56'd0400; - 31'b???????_????????_????????_??1????? : accept_a_out_1 = 56'd0500; - 31'b???????_????????_????????_?1?????? : accept_a_out_1 = 56'd0600; - 31'b???????_????????_????????_1??????? : accept_a_out_1 = 56'd0700; - 31'b???????_????????_???????1_???????? : accept_a_out_1 = 56'd0800; - 31'b???????_????????_??????1?_???????? : accept_a_out_1 = 56'd0900; - 31'b???????_????????_?????1??_???????? : accept_a_out_1 = 56'd1000; - 31'b???????_????????_????1???_???????? : accept_a_out_1 = 56'd1100; - 31'b???????_????????_???1????_???????? : accept_a_out_1 = 56'd1200; - 31'b???????_????????_??1?????_???????? : accept_a_out_1 = 56'd1300; - 31'b???????_????????_?1??????_???????? : accept_a_out_1 = 56'd1400; - 31'b???????_????????_1???????_???????? : accept_a_out_1 = 56'd1500; - 31'b???????_???????1_????????_???????? : accept_a_out_1 = 56'd1600; - 31'b???????_??????1?_????????_???????? : accept_a_out_1 = 56'd1700; - 31'b???????_?????1??_????????_???????? : accept_a_out_1 = 56'd1800; - 31'b???????_????1???_????????_???????? : accept_a_out_1 = 56'd1900; - 31'b???????_???1????_????????_???????? : accept_a_out_1 = 56'd2000; - 31'b???????_??1?????_????????_???????? : accept_a_out_1 = 56'd2100; - 31'b???????_?1??????_????????_???????? : accept_a_out_1 = 56'd2200; - 31'b???????_1???????_????????_???????? : accept_a_out_1 = 56'd2300; - 31'b??????1_????????_????????_???????? : accept_a_out_1 = 56'd2400; - 31'b?????1?_????????_????????_???????? : accept_a_out_1 = 56'd2500; - 31'b????1??_????????_????????_???????? : accept_a_out_1 = 56'd2600; - 31'b???1???_????????_????????_???????? : accept_a_out_1 = 56'd2700; - 31'b??1????_????????_????????_???????? : accept_a_out_1 = 56'd2800; - 31'b?1?????_????????_????????_???????? : accept_a_out_1 = 56'd2900; - 31'b1??????_????????_????????_???????? : accept_a_out_1 = 56'd3000; + 31'b???????_????????_????????_???????1: accept_a_out_1 = 56'd0000; + 31'b???????_????????_????????_??????1?: accept_a_out_1 = 56'd0100; + 31'b???????_????????_????????_?????1??: accept_a_out_1 = 56'd0200; + 31'b???????_????????_????????_????1???: accept_a_out_1 = 56'd0300; + 31'b???????_????????_????????_???1????: accept_a_out_1 = 56'd0400; + 31'b???????_????????_????????_??1?????: accept_a_out_1 = 56'd0500; + 31'b???????_????????_????????_?1??????: accept_a_out_1 = 56'd0600; + 31'b???????_????????_????????_1???????: accept_a_out_1 = 56'd0700; + 31'b???????_????????_???????1_????????: accept_a_out_1 = 56'd0800; + 31'b???????_????????_??????1?_????????: accept_a_out_1 = 56'd0900; + 31'b???????_????????_?????1??_????????: accept_a_out_1 = 56'd1000; + 31'b???????_????????_????1???_????????: accept_a_out_1 = 56'd1100; + 31'b???????_????????_???1????_????????: accept_a_out_1 = 56'd1200; + 31'b???????_????????_??1?????_????????: accept_a_out_1 = 56'd1300; + 31'b???????_????????_?1??????_????????: accept_a_out_1 = 56'd1400; + 31'b???????_????????_1???????_????????: accept_a_out_1 = 56'd1500; + 31'b???????_???????1_????????_????????: accept_a_out_1 = 56'd1600; + 31'b???????_??????1?_????????_????????: accept_a_out_1 = 56'd1700; + 31'b???????_?????1??_????????_????????: accept_a_out_1 = 56'd1800; + 31'b???????_????1???_????????_????????: accept_a_out_1 = 56'd1900; + 31'b???????_???1????_????????_????????: accept_a_out_1 = 56'd2000; + 31'b???????_??1?????_????????_????????: accept_a_out_1 = 56'd2100; + 31'b???????_?1??????_????????_????????: accept_a_out_1 = 56'd2200; + 31'b???????_1???????_????????_????????: accept_a_out_1 = 56'd2300; + 31'b??????1_????????_????????_????????: accept_a_out_1 = 56'd2400; + 31'b?????1?_????????_????????_????????: accept_a_out_1 = 56'd2500; + 31'b????1??_????????_????????_????????: accept_a_out_1 = 56'd2600; + 31'b???1???_????????_????????_????????: accept_a_out_1 = 56'd2700; + 31'b??1????_????????_????????_????????: accept_a_out_1 = 56'd2800; + 31'b?1?????_????????_????????_????????: accept_a_out_1 = 56'd2900; + 31'b1??????_????????_????????_????????: accept_a_out_1 = 56'd3000; default: accept_a_out_1 = '1; endcase casez (accept_a_in) - 31'b???????_????????_????????_???????1 : accept_a_out_2 = 143'd0000000000; - 31'b???????_????????_????????_??????1? : accept_a_out_2 = 143'd0100000000; - 31'b???????_????????_????????_?????1?? : accept_a_out_2 = 143'd0200000000; - 31'b???????_????????_????????_????1??? : accept_a_out_2 = 143'd0300000000; - 31'b???????_????????_????????_???1???? : accept_a_out_2 = 143'd0400000000; - 31'b???????_????????_????????_??1????? : accept_a_out_2 = 143'd0500000000; - 31'b???????_????????_????????_?1?????? : accept_a_out_2 = 143'd0600000000; - 31'b???????_????????_????????_1??????? : accept_a_out_2 = 143'd0700000000; - 31'b???????_????????_???????1_???????? : accept_a_out_2 = 143'd0800000000; - 31'b???????_????????_??????1?_???????? : accept_a_out_2 = 143'd0900000000; - 31'b???????_????????_?????1??_???????? : accept_a_out_2 = 143'd1000000000; - 31'b???????_????????_????1???_???????? : accept_a_out_2 = 143'd1100000000; - 31'b???????_????????_???1????_???????? : accept_a_out_2 = 143'd1200000000; - 31'b???????_????????_??1?????_???????? : accept_a_out_2 = 143'd1300000000; - 31'b???????_????????_?1??????_???????? : accept_a_out_2 = 143'd1400000000; - 31'b???????_????????_1???????_???????? : accept_a_out_2 = 143'd1500000000; - 31'b???????_???????1_????????_???????? : accept_a_out_2 = 143'd1600000000; - 31'b???????_??????1?_????????_???????? : accept_a_out_2 = 143'd1700000000; - 31'b???????_?????1??_????????_???????? : accept_a_out_2 = 143'd1800000000; - 31'b???????_????1???_????????_???????? : accept_a_out_2 = 143'd1900000000; - 31'b???????_???1????_????????_???????? : accept_a_out_2 = 143'd2000000000; - 31'b???????_??1?????_????????_???????? : accept_a_out_2 = 143'd2100000000; - 31'b???????_?1??????_????????_???????? : accept_a_out_2 = 143'd2200000000; - 31'b???????_1???????_????????_???????? : accept_a_out_2 = 143'd2300000000; - 31'b??????1_????????_????????_???????? : accept_a_out_2 = 143'd2400000000; - 31'b?????1?_????????_????????_???????? : accept_a_out_2 = 143'd2500000000; - 31'b????1??_????????_????????_???????? : accept_a_out_2 = 143'd2600000000; - 31'b???1???_????????_????????_???????? : accept_a_out_2 = 143'd2700000000; - 31'b??1????_????????_????????_???????? : accept_a_out_2 = 143'd2800000000; - 31'b?1?????_????????_????????_???????? : accept_a_out_2 = 143'd2900000000; - 31'b1??????_????????_????????_???????? : accept_a_out_2 = 143'd3000000000; + 31'b???????_????????_????????_???????1: accept_a_out_2 = 143'd0000000000; + 31'b???????_????????_????????_??????1?: accept_a_out_2 = 143'd0100000000; + 31'b???????_????????_????????_?????1??: accept_a_out_2 = 143'd0200000000; + 31'b???????_????????_????????_????1???: accept_a_out_2 = 143'd0300000000; + 31'b???????_????????_????????_???1????: accept_a_out_2 = 143'd0400000000; + 31'b???????_????????_????????_??1?????: accept_a_out_2 = 143'd0500000000; + 31'b???????_????????_????????_?1??????: accept_a_out_2 = 143'd0600000000; + 31'b???????_????????_????????_1???????: accept_a_out_2 = 143'd0700000000; + 31'b???????_????????_???????1_????????: accept_a_out_2 = 143'd0800000000; + 31'b???????_????????_??????1?_????????: accept_a_out_2 = 143'd0900000000; + 31'b???????_????????_?????1??_????????: accept_a_out_2 = 143'd1000000000; + 31'b???????_????????_????1???_????????: accept_a_out_2 = 143'd1100000000; + 31'b???????_????????_???1????_????????: accept_a_out_2 = 143'd1200000000; + 31'b???????_????????_??1?????_????????: accept_a_out_2 = 143'd1300000000; + 31'b???????_????????_?1??????_????????: accept_a_out_2 = 143'd1400000000; + 31'b???????_????????_1???????_????????: accept_a_out_2 = 143'd1500000000; + 31'b???????_???????1_????????_????????: accept_a_out_2 = 143'd1600000000; + 31'b???????_??????1?_????????_????????: accept_a_out_2 = 143'd1700000000; + 31'b???????_?????1??_????????_????????: accept_a_out_2 = 143'd1800000000; + 31'b???????_????1???_????????_????????: accept_a_out_2 = 143'd1900000000; + 31'b???????_???1????_????????_????????: accept_a_out_2 = 143'd2000000000; + 31'b???????_??1?????_????????_????????: accept_a_out_2 = 143'd2100000000; + 31'b???????_?1??????_????????_????????: accept_a_out_2 = 143'd2200000000; + 31'b???????_1???????_????????_????????: accept_a_out_2 = 143'd2300000000; + 31'b??????1_????????_????????_????????: accept_a_out_2 = 143'd2400000000; + 31'b?????1?_????????_????????_????????: accept_a_out_2 = 143'd2500000000; + 31'b????1??_????????_????????_????????: accept_a_out_2 = 143'd2600000000; + 31'b???1???_????????_????????_????????: accept_a_out_2 = 143'd2700000000; + 31'b??1????_????????_????????_????????: accept_a_out_2 = 143'd2800000000; + 31'b?1?????_????????_????????_????????: accept_a_out_2 = 143'd2900000000; + 31'b1??????_????????_????????_????????: accept_a_out_2 = 143'd3000000000; default: accept_a_out_2 = '1; endcase end @@ -135,137 +135,137 @@ module t; // Accept B: a 40-bit (Q) selector decoded into outputs of three widths (I/Q/W result). wire [39:0] accept_b_in = 40'b1 << cyc[5:1]; - logic [ 5:0] accept_b_out_0, accept_b_ref_0; - logic [ 55:0] accept_b_out_1, accept_b_ref_1; + logic [5:0] accept_b_out_0, accept_b_ref_0; + logic [55:0] accept_b_out_1, accept_b_ref_1; logic [142:0] accept_b_out_2, accept_b_ref_2; always_comb begin casez (accept_b_in) - 40'b????????_????????_????????_????????_???????1 : accept_b_out_0 = 6'd00; - 40'b????????_????????_????????_????????_??????1? : accept_b_out_0 = 6'd01; - 40'b????????_????????_????????_????????_?????1?? : accept_b_out_0 = 6'd02; - 40'b????????_????????_????????_????????_????1??? : accept_b_out_0 = 6'd03; - 40'b????????_????????_????????_????????_???1???? : accept_b_out_0 = 6'd04; - 40'b????????_????????_????????_????????_??1????? : accept_b_out_0 = 6'd05; - 40'b????????_????????_????????_????????_?1?????? : accept_b_out_0 = 6'd06; - 40'b????????_????????_????????_????????_1??????? : accept_b_out_0 = 6'd07; - 40'b????????_????????_????????_???????1_???????? : accept_b_out_0 = 6'd08; - 40'b????????_????????_????????_??????1?_???????? : accept_b_out_0 = 6'd09; - 40'b????????_????????_????????_?????1??_???????? : accept_b_out_0 = 6'd10; - 40'b????????_????????_????????_????1???_???????? : accept_b_out_0 = 6'd11; - 40'b????????_????????_????????_???1????_???????? : accept_b_out_0 = 6'd12; - 40'b????????_????????_????????_??1?????_???????? : accept_b_out_0 = 6'd13; - 40'b????????_????????_????????_?1??????_???????? : accept_b_out_0 = 6'd14; - 40'b????????_????????_????????_1???????_???????? : accept_b_out_0 = 6'd15; - 40'b????????_????????_???????1_????????_???????? : accept_b_out_0 = 6'd16; - 40'b????????_????????_??????1?_????????_???????? : accept_b_out_0 = 6'd17; - 40'b????????_????????_?????1??_????????_???????? : accept_b_out_0 = 6'd18; - 40'b????????_????????_????1???_????????_???????? : accept_b_out_0 = 6'd19; - 40'b????????_????????_???1????_????????_???????? : accept_b_out_0 = 6'd20; - 40'b????????_????????_??1?????_????????_???????? : accept_b_out_0 = 6'd21; - 40'b????????_????????_?1??????_????????_???????? : accept_b_out_0 = 6'd22; - 40'b????????_????????_1???????_????????_???????? : accept_b_out_0 = 6'd23; - 40'b????????_???????1_????????_????????_???????? : accept_b_out_0 = 6'd24; - 40'b????????_??????1?_????????_????????_???????? : accept_b_out_0 = 6'd25; - 40'b????????_?????1??_????????_????????_???????? : accept_b_out_0 = 6'd26; - 40'b????????_????1???_????????_????????_???????? : accept_b_out_0 = 6'd27; - 40'b????????_???1????_????????_????????_???????? : accept_b_out_0 = 6'd28; - 40'b????????_??1?????_????????_????????_???????? : accept_b_out_0 = 6'd29; - 40'b????????_?1??????_????????_????????_???????? : accept_b_out_0 = 6'd30; - 40'b????????_1???????_????????_????????_???????? : accept_b_out_0 = 6'd31; - 40'b???????1_????????_????????_????????_???????? : accept_b_out_0 = 6'd32; - 40'b??????1?_????????_????????_????????_???????? : accept_b_out_0 = 6'd33; - 40'b?????1??_????????_????????_????????_???????? : accept_b_out_0 = 6'd34; - 40'b????1???_????????_????????_????????_???????? : accept_b_out_0 = 6'd35; - 40'b???1????_????????_????????_????????_???????? : accept_b_out_0 = 6'd36; - 40'b??1?????_????????_????????_????????_???????? : accept_b_out_0 = 6'd37; - 40'b?1??????_????????_????????_????????_???????? : accept_b_out_0 = 6'd38; - 40'b1???????_????????_????????_????????_???????? : accept_b_out_0 = 6'd39; + 40'b????????_????????_????????_????????_???????1: accept_b_out_0 = 6'd00; + 40'b????????_????????_????????_????????_??????1?: accept_b_out_0 = 6'd01; + 40'b????????_????????_????????_????????_?????1??: accept_b_out_0 = 6'd02; + 40'b????????_????????_????????_????????_????1???: accept_b_out_0 = 6'd03; + 40'b????????_????????_????????_????????_???1????: accept_b_out_0 = 6'd04; + 40'b????????_????????_????????_????????_??1?????: accept_b_out_0 = 6'd05; + 40'b????????_????????_????????_????????_?1??????: accept_b_out_0 = 6'd06; + 40'b????????_????????_????????_????????_1???????: accept_b_out_0 = 6'd07; + 40'b????????_????????_????????_???????1_????????: accept_b_out_0 = 6'd08; + 40'b????????_????????_????????_??????1?_????????: accept_b_out_0 = 6'd09; + 40'b????????_????????_????????_?????1??_????????: accept_b_out_0 = 6'd10; + 40'b????????_????????_????????_????1???_????????: accept_b_out_0 = 6'd11; + 40'b????????_????????_????????_???1????_????????: accept_b_out_0 = 6'd12; + 40'b????????_????????_????????_??1?????_????????: accept_b_out_0 = 6'd13; + 40'b????????_????????_????????_?1??????_????????: accept_b_out_0 = 6'd14; + 40'b????????_????????_????????_1???????_????????: accept_b_out_0 = 6'd15; + 40'b????????_????????_???????1_????????_????????: accept_b_out_0 = 6'd16; + 40'b????????_????????_??????1?_????????_????????: accept_b_out_0 = 6'd17; + 40'b????????_????????_?????1??_????????_????????: accept_b_out_0 = 6'd18; + 40'b????????_????????_????1???_????????_????????: accept_b_out_0 = 6'd19; + 40'b????????_????????_???1????_????????_????????: accept_b_out_0 = 6'd20; + 40'b????????_????????_??1?????_????????_????????: accept_b_out_0 = 6'd21; + 40'b????????_????????_?1??????_????????_????????: accept_b_out_0 = 6'd22; + 40'b????????_????????_1???????_????????_????????: accept_b_out_0 = 6'd23; + 40'b????????_???????1_????????_????????_????????: accept_b_out_0 = 6'd24; + 40'b????????_??????1?_????????_????????_????????: accept_b_out_0 = 6'd25; + 40'b????????_?????1??_????????_????????_????????: accept_b_out_0 = 6'd26; + 40'b????????_????1???_????????_????????_????????: accept_b_out_0 = 6'd27; + 40'b????????_???1????_????????_????????_????????: accept_b_out_0 = 6'd28; + 40'b????????_??1?????_????????_????????_????????: accept_b_out_0 = 6'd29; + 40'b????????_?1??????_????????_????????_????????: accept_b_out_0 = 6'd30; + 40'b????????_1???????_????????_????????_????????: accept_b_out_0 = 6'd31; + 40'b???????1_????????_????????_????????_????????: accept_b_out_0 = 6'd32; + 40'b??????1?_????????_????????_????????_????????: accept_b_out_0 = 6'd33; + 40'b?????1??_????????_????????_????????_????????: accept_b_out_0 = 6'd34; + 40'b????1???_????????_????????_????????_????????: accept_b_out_0 = 6'd35; + 40'b???1????_????????_????????_????????_????????: accept_b_out_0 = 6'd36; + 40'b??1?????_????????_????????_????????_????????: accept_b_out_0 = 6'd37; + 40'b?1??????_????????_????????_????????_????????: accept_b_out_0 = 6'd38; + 40'b1???????_????????_????????_????????_????????: accept_b_out_0 = 6'd39; default: accept_b_out_0 = '1; endcase casez (accept_b_in) - 40'b????????_????????_????????_????????_???????1 : accept_b_out_1 = 56'd0000; - 40'b????????_????????_????????_????????_??????1? : accept_b_out_1 = 56'd0100; - 40'b????????_????????_????????_????????_?????1?? : accept_b_out_1 = 56'd0200; - 40'b????????_????????_????????_????????_????1??? : accept_b_out_1 = 56'd0300; - 40'b????????_????????_????????_????????_???1???? : accept_b_out_1 = 56'd0400; - 40'b????????_????????_????????_????????_??1????? : accept_b_out_1 = 56'd0500; - 40'b????????_????????_????????_????????_?1?????? : accept_b_out_1 = 56'd0600; - 40'b????????_????????_????????_????????_1??????? : accept_b_out_1 = 56'd0700; - 40'b????????_????????_????????_???????1_???????? : accept_b_out_1 = 56'd0800; - 40'b????????_????????_????????_??????1?_???????? : accept_b_out_1 = 56'd0900; - 40'b????????_????????_????????_?????1??_???????? : accept_b_out_1 = 56'd1000; - 40'b????????_????????_????????_????1???_???????? : accept_b_out_1 = 56'd1100; - 40'b????????_????????_????????_???1????_???????? : accept_b_out_1 = 56'd1200; - 40'b????????_????????_????????_??1?????_???????? : accept_b_out_1 = 56'd1300; - 40'b????????_????????_????????_?1??????_???????? : accept_b_out_1 = 56'd1400; - 40'b????????_????????_????????_1???????_???????? : accept_b_out_1 = 56'd1500; - 40'b????????_????????_???????1_????????_???????? : accept_b_out_1 = 56'd1600; - 40'b????????_????????_??????1?_????????_???????? : accept_b_out_1 = 56'd1700; - 40'b????????_????????_?????1??_????????_???????? : accept_b_out_1 = 56'd1800; - 40'b????????_????????_????1???_????????_???????? : accept_b_out_1 = 56'd1900; - 40'b????????_????????_???1????_????????_???????? : accept_b_out_1 = 56'd2000; - 40'b????????_????????_??1?????_????????_???????? : accept_b_out_1 = 56'd2100; - 40'b????????_????????_?1??????_????????_???????? : accept_b_out_1 = 56'd2200; - 40'b????????_????????_1???????_????????_???????? : accept_b_out_1 = 56'd2300; - 40'b????????_???????1_????????_????????_???????? : accept_b_out_1 = 56'd2400; - 40'b????????_??????1?_????????_????????_???????? : accept_b_out_1 = 56'd2500; - 40'b????????_?????1??_????????_????????_???????? : accept_b_out_1 = 56'd2600; - 40'b????????_????1???_????????_????????_???????? : accept_b_out_1 = 56'd2700; - 40'b????????_???1????_????????_????????_???????? : accept_b_out_1 = 56'd2800; - 40'b????????_??1?????_????????_????????_???????? : accept_b_out_1 = 56'd2900; - 40'b????????_?1??????_????????_????????_???????? : accept_b_out_1 = 56'd3000; - 40'b????????_1???????_????????_????????_???????? : accept_b_out_1 = 56'd3100; - 40'b???????1_????????_????????_????????_???????? : accept_b_out_1 = 56'd3200; - 40'b??????1?_????????_????????_????????_???????? : accept_b_out_1 = 56'd3300; - 40'b?????1??_????????_????????_????????_???????? : accept_b_out_1 = 56'd3400; - 40'b????1???_????????_????????_????????_???????? : accept_b_out_1 = 56'd3500; - 40'b???1????_????????_????????_????????_???????? : accept_b_out_1 = 56'd3600; - 40'b??1?????_????????_????????_????????_???????? : accept_b_out_1 = 56'd3700; - 40'b?1??????_????????_????????_????????_???????? : accept_b_out_1 = 56'd3800; - 40'b1???????_????????_????????_????????_???????? : accept_b_out_1 = 56'd3900; + 40'b????????_????????_????????_????????_???????1: accept_b_out_1 = 56'd0000; + 40'b????????_????????_????????_????????_??????1?: accept_b_out_1 = 56'd0100; + 40'b????????_????????_????????_????????_?????1??: accept_b_out_1 = 56'd0200; + 40'b????????_????????_????????_????????_????1???: accept_b_out_1 = 56'd0300; + 40'b????????_????????_????????_????????_???1????: accept_b_out_1 = 56'd0400; + 40'b????????_????????_????????_????????_??1?????: accept_b_out_1 = 56'd0500; + 40'b????????_????????_????????_????????_?1??????: accept_b_out_1 = 56'd0600; + 40'b????????_????????_????????_????????_1???????: accept_b_out_1 = 56'd0700; + 40'b????????_????????_????????_???????1_????????: accept_b_out_1 = 56'd0800; + 40'b????????_????????_????????_??????1?_????????: accept_b_out_1 = 56'd0900; + 40'b????????_????????_????????_?????1??_????????: accept_b_out_1 = 56'd1000; + 40'b????????_????????_????????_????1???_????????: accept_b_out_1 = 56'd1100; + 40'b????????_????????_????????_???1????_????????: accept_b_out_1 = 56'd1200; + 40'b????????_????????_????????_??1?????_????????: accept_b_out_1 = 56'd1300; + 40'b????????_????????_????????_?1??????_????????: accept_b_out_1 = 56'd1400; + 40'b????????_????????_????????_1???????_????????: accept_b_out_1 = 56'd1500; + 40'b????????_????????_???????1_????????_????????: accept_b_out_1 = 56'd1600; + 40'b????????_????????_??????1?_????????_????????: accept_b_out_1 = 56'd1700; + 40'b????????_????????_?????1??_????????_????????: accept_b_out_1 = 56'd1800; + 40'b????????_????????_????1???_????????_????????: accept_b_out_1 = 56'd1900; + 40'b????????_????????_???1????_????????_????????: accept_b_out_1 = 56'd2000; + 40'b????????_????????_??1?????_????????_????????: accept_b_out_1 = 56'd2100; + 40'b????????_????????_?1??????_????????_????????: accept_b_out_1 = 56'd2200; + 40'b????????_????????_1???????_????????_????????: accept_b_out_1 = 56'd2300; + 40'b????????_???????1_????????_????????_????????: accept_b_out_1 = 56'd2400; + 40'b????????_??????1?_????????_????????_????????: accept_b_out_1 = 56'd2500; + 40'b????????_?????1??_????????_????????_????????: accept_b_out_1 = 56'd2600; + 40'b????????_????1???_????????_????????_????????: accept_b_out_1 = 56'd2700; + 40'b????????_???1????_????????_????????_????????: accept_b_out_1 = 56'd2800; + 40'b????????_??1?????_????????_????????_????????: accept_b_out_1 = 56'd2900; + 40'b????????_?1??????_????????_????????_????????: accept_b_out_1 = 56'd3000; + 40'b????????_1???????_????????_????????_????????: accept_b_out_1 = 56'd3100; + 40'b???????1_????????_????????_????????_????????: accept_b_out_1 = 56'd3200; + 40'b??????1?_????????_????????_????????_????????: accept_b_out_1 = 56'd3300; + 40'b?????1??_????????_????????_????????_????????: accept_b_out_1 = 56'd3400; + 40'b????1???_????????_????????_????????_????????: accept_b_out_1 = 56'd3500; + 40'b???1????_????????_????????_????????_????????: accept_b_out_1 = 56'd3600; + 40'b??1?????_????????_????????_????????_????????: accept_b_out_1 = 56'd3700; + 40'b?1??????_????????_????????_????????_????????: accept_b_out_1 = 56'd3800; + 40'b1???????_????????_????????_????????_????????: accept_b_out_1 = 56'd3900; default: accept_b_out_1 = '1; endcase casez (accept_b_in) - 40'b????????_????????_????????_????????_???????1 : accept_b_out_2 = 143'd0000000000; - 40'b????????_????????_????????_????????_??????1? : accept_b_out_2 = 143'd0100000000; - 40'b????????_????????_????????_????????_?????1?? : accept_b_out_2 = 143'd0200000000; - 40'b????????_????????_????????_????????_????1??? : accept_b_out_2 = 143'd0300000000; - 40'b????????_????????_????????_????????_???1???? : accept_b_out_2 = 143'd0400000000; - 40'b????????_????????_????????_????????_??1????? : accept_b_out_2 = 143'd0500000000; - 40'b????????_????????_????????_????????_?1?????? : accept_b_out_2 = 143'd0600000000; - 40'b????????_????????_????????_????????_1??????? : accept_b_out_2 = 143'd0700000000; - 40'b????????_????????_????????_???????1_???????? : accept_b_out_2 = 143'd0800000000; - 40'b????????_????????_????????_??????1?_???????? : accept_b_out_2 = 143'd0900000000; - 40'b????????_????????_????????_?????1??_???????? : accept_b_out_2 = 143'd1000000000; - 40'b????????_????????_????????_????1???_???????? : accept_b_out_2 = 143'd1100000000; - 40'b????????_????????_????????_???1????_???????? : accept_b_out_2 = 143'd1200000000; - 40'b????????_????????_????????_??1?????_???????? : accept_b_out_2 = 143'd1300000000; - 40'b????????_????????_????????_?1??????_???????? : accept_b_out_2 = 143'd1400000000; - 40'b????????_????????_????????_1???????_???????? : accept_b_out_2 = 143'd1500000000; - 40'b????????_????????_???????1_????????_???????? : accept_b_out_2 = 143'd1600000000; - 40'b????????_????????_??????1?_????????_???????? : accept_b_out_2 = 143'd1700000000; - 40'b????????_????????_?????1??_????????_???????? : accept_b_out_2 = 143'd1800000000; - 40'b????????_????????_????1???_????????_???????? : accept_b_out_2 = 143'd1900000000; - 40'b????????_????????_???1????_????????_???????? : accept_b_out_2 = 143'd2000000000; - 40'b????????_????????_??1?????_????????_???????? : accept_b_out_2 = 143'd2100000000; - 40'b????????_????????_?1??????_????????_???????? : accept_b_out_2 = 143'd2200000000; - 40'b????????_????????_1???????_????????_???????? : accept_b_out_2 = 143'd2300000000; - 40'b????????_???????1_????????_????????_???????? : accept_b_out_2 = 143'd2400000000; - 40'b????????_??????1?_????????_????????_???????? : accept_b_out_2 = 143'd2500000000; - 40'b????????_?????1??_????????_????????_???????? : accept_b_out_2 = 143'd2600000000; - 40'b????????_????1???_????????_????????_???????? : accept_b_out_2 = 143'd2700000000; - 40'b????????_???1????_????????_????????_???????? : accept_b_out_2 = 143'd2800000000; - 40'b????????_??1?????_????????_????????_???????? : accept_b_out_2 = 143'd2900000000; - 40'b????????_?1??????_????????_????????_???????? : accept_b_out_2 = 143'd3000000000; - 40'b????????_1???????_????????_????????_???????? : accept_b_out_2 = 143'd3100000000; - 40'b???????1_????????_????????_????????_???????? : accept_b_out_2 = 143'd3200000000; - 40'b??????1?_????????_????????_????????_???????? : accept_b_out_2 = 143'd3300000000; - 40'b?????1??_????????_????????_????????_???????? : accept_b_out_2 = 143'd3400000000; - 40'b????1???_????????_????????_????????_???????? : accept_b_out_2 = 143'd3500000000; - 40'b???1????_????????_????????_????????_???????? : accept_b_out_2 = 143'd3600000000; - 40'b??1?????_????????_????????_????????_???????? : accept_b_out_2 = 143'd3700000000; - 40'b?1??????_????????_????????_????????_???????? : accept_b_out_2 = 143'd3800000000; - 40'b1???????_????????_????????_????????_???????? : accept_b_out_2 = 143'd3900000000; + 40'b????????_????????_????????_????????_???????1: accept_b_out_2 = 143'd0000000000; + 40'b????????_????????_????????_????????_??????1?: accept_b_out_2 = 143'd0100000000; + 40'b????????_????????_????????_????????_?????1??: accept_b_out_2 = 143'd0200000000; + 40'b????????_????????_????????_????????_????1???: accept_b_out_2 = 143'd0300000000; + 40'b????????_????????_????????_????????_???1????: accept_b_out_2 = 143'd0400000000; + 40'b????????_????????_????????_????????_??1?????: accept_b_out_2 = 143'd0500000000; + 40'b????????_????????_????????_????????_?1??????: accept_b_out_2 = 143'd0600000000; + 40'b????????_????????_????????_????????_1???????: accept_b_out_2 = 143'd0700000000; + 40'b????????_????????_????????_???????1_????????: accept_b_out_2 = 143'd0800000000; + 40'b????????_????????_????????_??????1?_????????: accept_b_out_2 = 143'd0900000000; + 40'b????????_????????_????????_?????1??_????????: accept_b_out_2 = 143'd1000000000; + 40'b????????_????????_????????_????1???_????????: accept_b_out_2 = 143'd1100000000; + 40'b????????_????????_????????_???1????_????????: accept_b_out_2 = 143'd1200000000; + 40'b????????_????????_????????_??1?????_????????: accept_b_out_2 = 143'd1300000000; + 40'b????????_????????_????????_?1??????_????????: accept_b_out_2 = 143'd1400000000; + 40'b????????_????????_????????_1???????_????????: accept_b_out_2 = 143'd1500000000; + 40'b????????_????????_???????1_????????_????????: accept_b_out_2 = 143'd1600000000; + 40'b????????_????????_??????1?_????????_????????: accept_b_out_2 = 143'd1700000000; + 40'b????????_????????_?????1??_????????_????????: accept_b_out_2 = 143'd1800000000; + 40'b????????_????????_????1???_????????_????????: accept_b_out_2 = 143'd1900000000; + 40'b????????_????????_???1????_????????_????????: accept_b_out_2 = 143'd2000000000; + 40'b????????_????????_??1?????_????????_????????: accept_b_out_2 = 143'd2100000000; + 40'b????????_????????_?1??????_????????_????????: accept_b_out_2 = 143'd2200000000; + 40'b????????_????????_1???????_????????_????????: accept_b_out_2 = 143'd2300000000; + 40'b????????_???????1_????????_????????_????????: accept_b_out_2 = 143'd2400000000; + 40'b????????_??????1?_????????_????????_????????: accept_b_out_2 = 143'd2500000000; + 40'b????????_?????1??_????????_????????_????????: accept_b_out_2 = 143'd2600000000; + 40'b????????_????1???_????????_????????_????????: accept_b_out_2 = 143'd2700000000; + 40'b????????_???1????_????????_????????_????????: accept_b_out_2 = 143'd2800000000; + 40'b????????_??1?????_????????_????????_????????: accept_b_out_2 = 143'd2900000000; + 40'b????????_?1??????_????????_????????_????????: accept_b_out_2 = 143'd3000000000; + 40'b????????_1???????_????????_????????_????????: accept_b_out_2 = 143'd3100000000; + 40'b???????1_????????_????????_????????_????????: accept_b_out_2 = 143'd3200000000; + 40'b??????1?_????????_????????_????????_????????: accept_b_out_2 = 143'd3300000000; + 40'b?????1??_????????_????????_????????_????????: accept_b_out_2 = 143'd3400000000; + 40'b????1???_????????_????????_????????_????????: accept_b_out_2 = 143'd3500000000; + 40'b???1????_????????_????????_????????_????????: accept_b_out_2 = 143'd3600000000; + 40'b??1?????_????????_????????_????????_????????: accept_b_out_2 = 143'd3700000000; + 40'b?1??????_????????_????????_????????_????????: accept_b_out_2 = 143'd3800000000; + 40'b1???????_????????_????????_????????_????????: accept_b_out_2 = 143'd3900000000; default: accept_b_out_2 = '1; endcase end @@ -275,50 +275,50 @@ module t; // Accept C: a 155-bit (W) selector decoded into outputs of three widths (I/Q/W result). wire [154:0] accept_c_in = 155'b1 << cyc[5:0]; - logic [ 5:0] accept_c_out_0, accept_c_ref_0; - logic [ 55:0] accept_c_out_1, accept_c_ref_1; + logic [5:0] accept_c_out_0, accept_c_ref_0; + logic [55:0] accept_c_out_1, accept_c_ref_1; logic [142:0] accept_c_out_2, accept_c_ref_2; always_comb begin casez (accept_c_in) - 155'b????????_????????_????????_????????_???????1 : accept_c_out_0 = 6'd00; - 155'b????????_????????_????????_????????_??????1? : accept_c_out_0 = 6'd01; - 155'b????????_????????_????????_????????_?????1?? : accept_c_out_0 = 6'd02; - 155'b????????_????????_????????_????????_????1??? : accept_c_out_0 = 6'd03; - 155'b????????_????????_????????_????????_???1???? : accept_c_out_0 = 6'd04; - 155'b????????_????????_????????_????????_??1????? : accept_c_out_0 = 6'd05; - 155'b????????_????????_????????_????????_?1?????? : accept_c_out_0 = 6'd06; - 155'b????????_????????_????????_????????_1??????? : accept_c_out_0 = 6'd07; - 155'b????????_????????_????????_???????1_???????? : accept_c_out_0 = 6'd08; - 155'b????????_????????_????????_??????1?_???????? : accept_c_out_0 = 6'd09; - 155'b????????_????????_????????_?????1??_???????? : accept_c_out_0 = 6'd10; + 155'b????????_????????_????????_????????_???????1: accept_c_out_0 = 6'd00; + 155'b????????_????????_????????_????????_??????1?: accept_c_out_0 = 6'd01; + 155'b????????_????????_????????_????????_?????1??: accept_c_out_0 = 6'd02; + 155'b????????_????????_????????_????????_????1???: accept_c_out_0 = 6'd03; + 155'b????????_????????_????????_????????_???1????: accept_c_out_0 = 6'd04; + 155'b????????_????????_????????_????????_??1?????: accept_c_out_0 = 6'd05; + 155'b????????_????????_????????_????????_?1??????: accept_c_out_0 = 6'd06; + 155'b????????_????????_????????_????????_1???????: accept_c_out_0 = 6'd07; + 155'b????????_????????_????????_???????1_????????: accept_c_out_0 = 6'd08; + 155'b????????_????????_????????_??????1?_????????: accept_c_out_0 = 6'd09; + 155'b????????_????????_????????_?????1??_????????: accept_c_out_0 = 6'd10; default: accept_c_out_0 = '1; endcase casez (accept_c_in) - 155'b????????_????????_????????_????????_???????1 : accept_c_out_1 = 56'd0000; - 155'b????????_????????_????????_????????_??????1? : accept_c_out_1 = 56'd0100; - 155'b????????_????????_????????_????????_?????1?? : accept_c_out_1 = 56'd0200; - 155'b????????_????????_????????_????????_????1??? : accept_c_out_1 = 56'd0300; - 155'b????????_????????_????????_????????_???1???? : accept_c_out_1 = 56'd0400; - 155'b????????_????????_????????_????????_??1????? : accept_c_out_1 = 56'd0500; - 155'b????????_????????_????????_????????_?1?????? : accept_c_out_1 = 56'd0600; - 155'b????????_????????_????????_????????_1??????? : accept_c_out_1 = 56'd0700; - 155'b????????_????????_????????_???????1_???????? : accept_c_out_1 = 56'd0800; - 155'b????????_????????_????????_??????1?_???????? : accept_c_out_1 = 56'd0900; - 155'b????????_????????_????????_?????1??_???????? : accept_c_out_1 = 56'd1000; + 155'b????????_????????_????????_????????_???????1: accept_c_out_1 = 56'd0000; + 155'b????????_????????_????????_????????_??????1?: accept_c_out_1 = 56'd0100; + 155'b????????_????????_????????_????????_?????1??: accept_c_out_1 = 56'd0200; + 155'b????????_????????_????????_????????_????1???: accept_c_out_1 = 56'd0300; + 155'b????????_????????_????????_????????_???1????: accept_c_out_1 = 56'd0400; + 155'b????????_????????_????????_????????_??1?????: accept_c_out_1 = 56'd0500; + 155'b????????_????????_????????_????????_?1??????: accept_c_out_1 = 56'd0600; + 155'b????????_????????_????????_????????_1???????: accept_c_out_1 = 56'd0700; + 155'b????????_????????_????????_???????1_????????: accept_c_out_1 = 56'd0800; + 155'b????????_????????_????????_??????1?_????????: accept_c_out_1 = 56'd0900; + 155'b????????_????????_????????_?????1??_????????: accept_c_out_1 = 56'd1000; default: accept_c_out_1 = '1; endcase casez (accept_c_in) - 155'b????????_????????_????????_????????_???????1 : accept_c_out_2 = 143'd0000000000; - 155'b????????_????????_????????_????????_??????1? : accept_c_out_2 = 143'd0100000000; - 155'b????????_????????_????????_????????_?????1?? : accept_c_out_2 = 143'd0200000000; - 155'b????????_????????_????????_????????_????1??? : accept_c_out_2 = 143'd0300000000; - 155'b????????_????????_????????_????????_???1???? : accept_c_out_2 = 143'd0400000000; - 155'b????????_????????_????????_????????_??1????? : accept_c_out_2 = 143'd0500000000; - 155'b????????_????????_????????_????????_?1?????? : accept_c_out_2 = 143'd0600000000; - 155'b????????_????????_????????_????????_1??????? : accept_c_out_2 = 143'd0700000000; - 155'b????????_????????_????????_???????1_???????? : accept_c_out_2 = 143'd0800000000; - 155'b????????_????????_????????_??????1?_???????? : accept_c_out_2 = 143'd0900000000; - 155'b????????_????????_????????_?????1??_???????? : accept_c_out_2 = 143'd1000000000; + 155'b????????_????????_????????_????????_???????1: accept_c_out_2 = 143'd0000000000; + 155'b????????_????????_????????_????????_??????1?: accept_c_out_2 = 143'd0100000000; + 155'b????????_????????_????????_????????_?????1??: accept_c_out_2 = 143'd0200000000; + 155'b????????_????????_????????_????????_????1???: accept_c_out_2 = 143'd0300000000; + 155'b????????_????????_????????_????????_???1????: accept_c_out_2 = 143'd0400000000; + 155'b????????_????????_????????_????????_??1?????: accept_c_out_2 = 143'd0500000000; + 155'b????????_????????_????????_????????_?1??????: accept_c_out_2 = 143'd0600000000; + 155'b????????_????????_????????_????????_1???????: accept_c_out_2 = 143'd0700000000; + 155'b????????_????????_????????_???????1_????????: accept_c_out_2 = 143'd0800000000; + 155'b????????_????????_????????_??????1?_????????: accept_c_out_2 = 143'd0900000000; + 155'b????????_????????_????????_?????1??_????????: accept_c_out_2 = 143'd1000000000; default: accept_c_out_2 = '1; endcase end @@ -332,10 +332,10 @@ module t; always_comb begin accept_d_out = '1; casez (accept_d_in) - 40'b????????_????????_????????_????????_???????1 : accept_d_out = 5'd0; - 40'b????????_????????_????????_????????_??????1? : accept_d_out = 5'd1; - 40'b????????_????????_????????_????????_?????1?? : accept_d_out = 5'd2; - 40'b????????_????????_????????_????????_????1??? : accept_d_out = 5'd3; + 40'b????????_????????_????????_????????_???????1: accept_d_out = 5'd0; + 40'b????????_????????_????????_????????_??????1?: accept_d_out = 5'd1; + 40'b????????_????????_????????_????????_?????1??: accept_d_out = 5'd2; + 40'b????????_????????_????????_????????_????1???: accept_d_out = 5'd3; endcase end assign accept_d_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; @@ -346,11 +346,12 @@ module t; always_comb begin accept_e_out = '1; casez (accept_e_in) - 40'b????????_????????_????????_????????_???????1 : accept_e_out = 5'd0; - 40'b????????_????????_????????_????????_??????1? : accept_e_out = 5'd1; - 40'b????????_????????_????????_????????_?????1?? : accept_e_out = 5'd2; - 40'b????????_????????_????????_????????_????1??? : accept_e_out = 5'd3; - default: begin end + 40'b????????_????????_????????_????????_???????1: accept_e_out = 5'd0; + 40'b????????_????????_????????_????????_??????1?: accept_e_out = 5'd1; + 40'b????????_????????_????????_????????_?????1??: accept_e_out = 5'd2; + 40'b????????_????????_????????_????????_????1???: accept_e_out = 5'd3; + default: begin + end endcase end assign accept_e_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; @@ -361,12 +362,14 @@ module t; always_comb begin accept_f_out = '1; casez (accept_f_in) - 40'b????????_????????_????????_????????_???1???? : begin end - 40'b????????_????????_????????_????????_???????1 : accept_f_out = 5'd0; - 40'b????????_????????_????????_????????_??????1? : accept_f_out = 5'd1; - 40'b????????_????????_????????_????????_?????1?? : accept_f_out = 5'd2; - 40'b????????_????????_????????_????????_????1??? : accept_f_out = 5'd3; - default: begin end + 40'b????????_????????_????????_????????_???1????: begin + end + 40'b????????_????????_????????_????????_???????1: accept_f_out = 5'd0; + 40'b????????_????????_????????_????????_??????1?: accept_f_out = 5'd1; + 40'b????????_????????_????????_????????_?????1??: accept_f_out = 5'd2; + 40'b????????_????????_????????_????????_????1???: accept_f_out = 5'd3; + default: begin + end endcase end assign accept_f_ref = cyc[4:0] <= 5'd3 ? 5'(cyc[4:0]) : 5'h1f; @@ -377,10 +380,10 @@ module t; always_ff @(posedge clk) begin accept_g_out <= '1; casez (accept_g_in) - 24'b????????_????????_???????1 : accept_g_out <= 6'd0; - 24'b????????_????????_??????1? : accept_g_out <= 6'd1; - 24'b????????_????????_?????1?? : accept_g_out <= 6'd2; - 24'b????????_????????_????1??? : accept_g_out <= 6'd3; + 24'b????????_????????_???????1: accept_g_out <= 6'd0; + 24'b????????_????????_??????1?: accept_g_out <= 6'd1; + 24'b????????_????????_?????1??: accept_g_out <= 6'd2; + 24'b????????_????????_????1???: accept_g_out <= 6'd3; endcase end always_ff @(posedge clk) @@ -389,16 +392,22 @@ module t; // Accept H: multiple outputs from one decoder, some items assigning only a subset. wire [23:0] accept_h_in = 24'b1 << cyc[4:0]; - logic [ 5:0] accept_h_out_0, accept_h_ref_0; + logic [5:0] accept_h_out_0, accept_h_ref_0; logic [11:0] accept_h_out_1, accept_h_ref_1; always_comb begin accept_h_out_0 = '1; accept_h_out_1 = '1; casez (accept_h_in) - 24'b????????_????????_???????1 : begin accept_h_out_0 = 6'd0; accept_h_out_1 = 12'h001; end - 24'b????????_????????_??????1? : accept_h_out_0 = 6'd1; - 24'b????????_????????_?????1?? : accept_h_out_1 = 12'h004; - 24'b????????_????????_????1??? : begin accept_h_out_0 = 6'd3; accept_h_out_1 = 12'h008; end + 24'b????????_????????_???????1: begin + accept_h_out_0 = 6'd0; + accept_h_out_1 = 12'h001; + end + 24'b????????_????????_??????1?: accept_h_out_0 = 6'd1; + 24'b????????_????????_?????1??: accept_h_out_1 = 12'h004; + 24'b????????_????????_????1???: begin + accept_h_out_0 = 6'd3; + accept_h_out_1 = 12'h008; + end endcase end assign accept_h_ref_0 = cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 @@ -408,16 +417,28 @@ module t; // Accept I: multiple outputs from one decoder, non-blocking assignments. wire [23:0] accept_i_in = 24'b1 << cyc[4:0]; - logic [ 5:0] accept_i_out_0, accept_i_ref_0; + logic [5:0] accept_i_out_0, accept_i_ref_0; logic [11:0] accept_i_out_1, accept_i_ref_1; always_ff @(posedge clk) begin accept_i_out_0 <= '1; accept_i_out_1 <= '1; casez (accept_i_in) - 24'b????????_????????_???????1 : begin accept_i_out_0 <= 6'd0; accept_i_out_1 <= 12'h001; end - 24'b????????_????????_??????1? : begin accept_i_out_0 <= 6'd1; accept_i_out_1 <= 12'h002; end - 24'b????????_????????_?????1?? : begin accept_i_out_0 <= 6'd2; accept_i_out_1 <= 12'h004; end - 24'b????????_????????_????1??? : begin accept_i_out_0 <= 6'd3; accept_i_out_1 <= 12'h008; end + 24'b????????_????????_???????1: begin + accept_i_out_0 <= 6'd0; + accept_i_out_1 <= 12'h001; + end + 24'b????????_????????_??????1?: begin + accept_i_out_0 <= 6'd1; + accept_i_out_1 <= 12'h002; + end + 24'b????????_????????_?????1??: begin + accept_i_out_0 <= 6'd2; + accept_i_out_1 <= 12'h004; + end + 24'b????????_????????_????1???: begin + accept_i_out_0 <= 6'd3; + accept_i_out_1 <= 12'h008; + end endcase end always_ff @(posedge clk) begin @@ -434,10 +455,10 @@ module t; always_comb begin accept_j_out = '1; casez (accept_j_in) - 24'b????????_????????_???????x : accept_j_out = 6'd9; // X never matches in 2-state, skipped - 24'b????????_????????_??????1? : accept_j_out = 6'd1; - 24'b????????_????????_?????1?? : accept_j_out = 6'd2; - 24'b????????_????????_????1??? : accept_j_out = 6'd3; + 24'b????????_????????_???????x: accept_j_out = 6'd9; // X never matches in 2-state, skipped + 24'b????????_????????_??????1?: accept_j_out = 6'd1; + 24'b????????_????????_?????1??: accept_j_out = 6'd2; + 24'b????????_????????_????1???: accept_j_out = 6'd3; endcase end // verilator lint_on CASEWITHX @@ -450,14 +471,13 @@ module t; always_ff @(posedge clk) begin accept_k_out <= '1; casez (accept_k_in) - 24'b????????_????????_???????1 : accept_k_out <= 6'd0; - 24'b????????_????????_??????1? : accept_k_out <= 6'd1; - 24'b????????_????????_?????1?? : accept_k_out <= 6'd2; - 24'b????????_????????_????1??? : accept_k_out <= 6'd3; + 24'b????????_????????_???????1: accept_k_out <= 6'd0; + 24'b????????_????????_??????1?: accept_k_out <= 6'd1; + 24'b????????_????????_?????1??: accept_k_out <= 6'd2; + 24'b????????_????????_????1???: accept_k_out <= 6'd3; endcase end - always_ff @(posedge clk) - accept_k_ref <= 6'd2; + always_ff @(posedge clk) accept_k_ref <= 6'd2; assign accept_k_in = 24'b100; // The cases below are intentionally NOT converted to a decoder. @@ -468,8 +488,8 @@ module t; always_comb begin reject_a_out = '1; casez (reject_a_in) - 24'b????????_????????_???????1 : reject_a_out = 6'd0; - 24'b????????_????????_??????1? : reject_a_out = 6'd1; + 24'b????????_????????_???????1: reject_a_out = 6'd0; + 24'b????????_????????_??????1?: reject_a_out = 6'd1; endcase end assign reject_a_ref = cyc[4:0] == 5'd0 ? 6'd0 : cyc[4:0] == 5'd1 ? 6'd1 : ~6'd0; @@ -483,9 +503,9 @@ module t; always_comb begin reject_b_out = 6'h2a; casez (reject_b_in) - 24'b????????_????????_???????x : reject_b_out = 6'd0; - 24'b????????_????????_??????x? : reject_b_out = 6'd1; - 24'b????????_????????_?????x?? : reject_b_out = 6'd2; + 24'b????????_????????_???????x: reject_b_out = 6'd0; + 24'b????????_????????_??????x?: reject_b_out = 6'd1; + 24'b????????_????????_?????x??: reject_b_out = 6'd2; endcase end // verilator lint_on CASEWITHX From 78d96d23ee40fb0ad87e41ede3286a182ebe48d7 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 20 Jun 2026 06:45:51 -0400 Subject: [PATCH 63/89] Commentary (#7809) --- docs/guide/connecting.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/connecting.rst b/docs/guide/connecting.rst index 67cad4ef3..2876ae765 100644 --- a/docs/guide/connecting.rst +++ b/docs/guide/connecting.rst @@ -179,7 +179,7 @@ DPI Example In the SYSTEMC example above, if you wanted to import C++ functions into Verilog, put in our.v: -.. code-block:: +.. code-block:: sv import "DPI-C" function int add (input int a, input int b); @@ -213,7 +213,7 @@ function name for the import, but note it must be escaped. .. code-block:: sv - export "DPI-C" function integer \$myRand; + import "DPI-C" function integer \$myRand; initial $display("myRand=%d", $myRand()); From 047d6e03d9d7c12f4cd08a248257a95fe4e66455 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 20 Jun 2026 07:47:44 -0400 Subject: [PATCH 64/89] Tests: Add t_sys_file_scan_delay (#4811) --- test_regress/t/t_sys_file_scan_delay.dat | 4 ++ test_regress/t/t_sys_file_scan_delay.py | 18 ++++++++ test_regress/t/t_sys_file_scan_delay.v | 54 ++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 test_regress/t/t_sys_file_scan_delay.dat create mode 100755 test_regress/t/t_sys_file_scan_delay.py create mode 100644 test_regress/t/t_sys_file_scan_delay.v diff --git a/test_regress/t/t_sys_file_scan_delay.dat b/test_regress/t/t_sys_file_scan_delay.dat new file mode 100644 index 000000000..b85d75e37 --- /dev/null +++ b/test_regress/t/t_sys_file_scan_delay.dat @@ -0,0 +1,4 @@ +00000010 +00000011 + +00000012 diff --git a/test_regress/t/t_sys_file_scan_delay.py b/test_regress/t/t_sys_file_scan_delay.py new file mode 100755 index 000000000..3cc73805c --- /dev/null +++ b/test_regress/t/t_sys_file_scan_delay.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_sys_file_scan_delay.v b/test_regress/t/t_sys_file_scan_delay.v new file mode 100644 index 000000000..f1db0a34e --- /dev/null +++ b/test_regress/t/t_sys_file_scan_delay.v @@ -0,0 +1,54 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2024 by David Harris. +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); +// verilog_format: on + +module t; + + int fd, code; + string line; + int siglines; + + localparam SIGNATURESIZE = 5000000; + logic [31:0] sig32[0:SIGNATURESIZE]; + logic [31:0] parsed; + string signame; + + initial begin + signame = "t/t_sys_file_scan_delay.dat"; + + fd = $fopen(signame, "r"); + siglines = 0; + if (fd == 0) $display("Unable to read %s", signame); + else begin + $display("Read %s", signame); + while (!$feof( + fd + )) begin + code = $fgets(line, fd); + if (code != 0) begin + if (line.len() > 1) begin + if ($sscanf(line, "%x", sig32[siglines]) != 0) begin + $display("sig32[%1d] = %x line: ", siglines, sig32[siglines], line); + siglines = siglines + 1; // increment if line is not blank + end + end + end + end + $fclose(fd); + end + + `checkh(sig32[0], 32'h10); + `checkh(sig32[1], 32'h11); + `checkh(sig32[2], 32'h12); + $display("*-* All Finished *-*"); + $finish; + end + +endmodule From e269b914b21857aa1127aba7c43b0df4e3edbbe8 Mon Sep 17 00:00:00 2001 From: Igor Zaworski Date: Sat, 20 Jun 2026 23:23:05 +0200 Subject: [PATCH 65/89] Support NBAs in initial blocks (#7754) --- docs/gen/ex_FINALDLY_faulty.rst | 7 ++++ docs/gen/ex_FINALDLY_msg.rst | 6 +++ docs/guide/warnings.rst | 18 ++++++++- src/V3Active.cpp | 26 +++---------- src/V3AstAttr.h | 12 +++--- src/V3AstNodeOther.h | 4 ++ src/V3Delayed.cpp | 37 +++++++++++++++++-- src/V3Error.h | 15 ++++---- src/V3SenExprBuilder.h | 4 +- test_regress/t/t_altera_lpm.v | 2 +- test_regress/t/t_finaldly_bad.out | 5 +++ ...nitial_dlyass_bad.py => t_finaldly_bad.py} | 13 +++++-- test_regress/t/t_finaldly_bad.v | 10 +++++ test_regress/t/t_fork_dynscope_out.py | 2 +- test_regress/t/t_initial_dlyass.py | 2 +- test_regress/t/t_initial_dlyass_bad.out | 11 ------ test_regress/t/t_lint_historical.v | 1 + test_regress/t/t_procedure_always_nba.py | 25 +++++++++++++ test_regress/t/t_procedure_initial_nba.py | 25 +++++++++++++ test_regress/t/t_procedure_nba.v | 37 +++++++++++++++++++ 20 files changed, 207 insertions(+), 55 deletions(-) create mode 100644 docs/gen/ex_FINALDLY_faulty.rst create mode 100644 docs/gen/ex_FINALDLY_msg.rst create mode 100644 test_regress/t/t_finaldly_bad.out rename test_regress/t/{t_initial_dlyass_bad.py => t_finaldly_bad.py} (50%) create mode 100644 test_regress/t/t_finaldly_bad.v delete mode 100644 test_regress/t/t_initial_dlyass_bad.out create mode 100755 test_regress/t/t_procedure_always_nba.py create mode 100755 test_regress/t/t_procedure_initial_nba.py create mode 100644 test_regress/t/t_procedure_nba.v diff --git a/docs/gen/ex_FINALDLY_faulty.rst b/docs/gen/ex_FINALDLY_faulty.rst new file mode 100644 index 000000000..78c456ad4 --- /dev/null +++ b/docs/gen/ex_FINALDLY_faulty.rst @@ -0,0 +1,7 @@ +.. comment: generated by t_finaldly_bad +.. code-block:: sv + :linenos: + :emphasize-lines: 2 + + bit foo; + final foo <= 1; // <--- Error diff --git a/docs/gen/ex_FINALDLY_msg.rst b/docs/gen/ex_FINALDLY_msg.rst new file mode 100644 index 000000000..4178e2669 --- /dev/null +++ b/docs/gen/ex_FINALDLY_msg.rst @@ -0,0 +1,6 @@ +.. comment: generated by t_finaldly_bad +.. code-block:: + + %Error-FINALDLY: example.v:1:13 Non-blocking assignment '<=' in final block + 9 | final foo <= 1; + | ^~ diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 209a52ebb..ce3a0d58c 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -883,6 +883,22 @@ List Of Warnings with a newline." +.. option:: FINALDLY + + Error issued when a non-blocking assignment `<=` is used in a + `final` block. + + This error can be disabled. If disabled, the assignment will be + executed as a `=` blocking assignment. + + Faulty example: + + .. include:: ../../docs/gen/ex_FINALDLY_faulty.rst + + Results in: + + .. include:: ../../docs/gen/ex_FINALDLY_msg.rst + .. option:: FSMMULTI Warns that the same always block contains multiple enum-typed case @@ -1179,7 +1195,7 @@ List Of Warnings .. option:: INITIALDLY - .. TODO better example + Historical, never issued since version 5.050. Warns that the code has a delayed assignment inside of an ``initial`` or ``final`` block. If this message is suppressed, Verilator will convert diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 995f4fd5c..13cd270b4 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -351,7 +351,7 @@ public: class ActiveDlyVisitor final : public VNVisitor { public: - enum CheckType : uint8_t { CT_SEQ, CT_COMB, CT_INITIAL, CT_SUSPENDABLE }; + enum CheckType : uint8_t { CT_COMB, CT_FINAL }; private: // MEMBERS @@ -359,15 +359,9 @@ private: // VISITORS void visit(AstAssignDly* nodep) override { - // Non-blocking assignments are OK in sequential processes - if (m_check == CT_SEQ || m_check == CT_SUSPENDABLE) return; - // Issue appropriate warning - if (m_check == CT_INITIAL) { - nodep->v3warn(INITIALDLY, - "Non-blocking assignment '<=' in initial/final block\n" - << nodep->warnMore() - << "... This will be executed as a blocking assignment '='!"); + if (m_check == CT_FINAL) { + nodep->v3warn(FINALDLY, "Non-blocking assignment '<=' in final block"); } else { nodep->v3warn(COMBDLY, "Non-blocking assignment '<=' in combinational logic process\n" @@ -465,11 +459,7 @@ class ActiveVisitor final : public VNVisitor { wantactivep->addStmtsp(nodep); // Warn and convert any delayed assignments - { - ActiveDlyVisitor{nodep, !m_clockedProcess ? ActiveDlyVisitor::CT_COMB - : oldsentreep ? ActiveDlyVisitor::CT_SEQ - : ActiveDlyVisitor::CT_SUSPENDABLE}; - } + if (!m_clockedProcess) ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_COMB}; // Delete sensitivity list if (oldsentreep) VL_DO_DANGLING(oldsentreep->deleteTree(), oldsentreep); @@ -509,17 +499,11 @@ class ActiveVisitor final : public VNVisitor { void visit(AstInitialStatic* nodep) override { moveUnderSpecial(nodep); } void visit(AstInitial* nodep) override { - const bool timedInitial - = v3Global.opt.timing().isSetTrue() && nodep->exists([](const AstNode* const subp) { - return VN_IS(subp, Delay) || VN_IS(subp, EventControl); - }); - const ActiveDlyVisitor dlyvisitor{nodep, timedInitial ? ActiveDlyVisitor::CT_SUSPENDABLE - : ActiveDlyVisitor::CT_INITIAL}; visitSenItems(nodep); moveUnderSpecial(nodep); } void visit(AstFinal* nodep) override { - const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL}; + const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_FINAL}; moveUnderSpecial(nodep); } void visit(AstCoverToggle* nodep) override { moveUnderSpecial(nodep); } diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 096923300..afca1bcc2 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -1428,6 +1428,7 @@ public: ET_EVENT, // VlEventBase::isFired // Involving an expression ET_TRUE, + ET_INITIAL_NBA, // Event that is fired initially and never again // ET_COMBO, // Sensitive to all combo inputs to this block ET_COMBO_STAR, // Sensitive to all combo inputs to this block (from .*) @@ -1446,6 +1447,7 @@ public: true, // ET_NEGEDGE true, // ET_EVENT true, // ET_TRUE + true, // ET_INITIAL_NBA false, // ET_COMBO false, // ET_COMBO_STAR @@ -1469,14 +1471,14 @@ public: } const char* ascii() const { static const char* const names[] - = {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", "COMBO", - "COMBO_STAR", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; + = {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", "ET_INITIAL_NBA", + "COMBO", "COMBO_STAR", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; return names[m_e]; } const char* verilogKwd() const { - static const char* const names[] - = {"[changed]", "edge", "posedge", "negedge", "[event]", "[true]", "*", - "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; + static const char* const names[] = { + "[changed]", "edge", "posedge", "negedge", "[event]", "[true]", "[initial_nba]", + "*", "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; return names[m_e]; } // Return true iff this and the other have mutually exclusive transitions diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index e3bbcde14..19f4d07ee 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1753,6 +1753,7 @@ public: class Combo {}; // for constructor type-overload selection class Static {}; // for constructor type-overload selection class Initial {}; // for constructor type-overload selection + class InitialNBA {}; // for constructor type-overload selection class Final {}; // for constructor type-overload selection class Never {}; // for constructor type-overload selection AstSenItem(FileLine* fl, VEdgeType edgeType, AstNodeExpr* senp, AstNodeExpr* condp = nullptr) @@ -1770,6 +1771,9 @@ public: AstSenItem(FileLine* fl, Initial) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_INITIAL} {} + AstSenItem(FileLine* fl, InitialNBA) + : ASTGEN_SUPER_SenItem(fl) + , m_edgeType{VEdgeType::ET_INITIAL_NBA} {} AstSenItem(FileLine* fl, Final) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_FINAL} {} diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index 1f6758629..f2ed71ebc 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -275,6 +275,7 @@ class DelayedVisitor final : public VNVisitor { bool m_inSuspendableOrFork = false; // True in suspendable processes and forks bool m_ignoreBlkAndNBlk = false; // Suppress delayed assignment BLKANDNBLK bool m_inNonCombLogic = false; // We are in non-combinational logic + bool m_needsInitialTrigger = false; // Whether a NodeProcedure needs a initial trigger AstVarRef* m_currNbaLhsRefp = nullptr; // Current NBA LHS variable reference // STATE - during NBA conversion (after visit) @@ -291,6 +292,7 @@ class DelayedVisitor final : public VNVisitor { VDouble0 m_nSchemeValueQueuesWhole; // Number of variables using Scheme::ValueQueueWhole VDouble0 m_nSchemeValueQueuesPartial; // Number of variables using Scheme::ValueQueuePartial VDouble0 m_nSharedSetFlags; // "Set" flags actually shared by Scheme::FlagShared variables + VDouble0 m_nInitialNBA; // Number of procedural blocks with initial NBA // METHODS @@ -999,6 +1001,12 @@ class DelayedVisitor final : public VNVisitor { m_writeRefs(nodep->varScopep()).emplace_back(nodep, nonBlocking, m_inNonCombLogic); } + template + static bool isProcedureWithSentreep(const AstNodeProcedure* const nodep) { + const Procedure_T* const procedurep = AstNode::cast(nodep); + return procedurep && procedurep->sentreep(); + } + // VISITORS void visit(AstNetlist* nodep) override { iterateChildren(nodep); @@ -1112,21 +1120,36 @@ class DelayedVisitor final : public VNVisitor { iterateChildren(nodep); } void visit(AstNodeProcedure* nodep) override { + VL_RESTORER(m_needsInitialTrigger); const size_t firstNBAAddedIndex = m_nbas.size(); { VL_RESTORER(m_inSuspendableOrFork); VL_RESTORER(m_procp); VL_RESTORER(m_ignoreBlkAndNBlk); VL_RESTORER(m_inNonCombLogic); - m_inSuspendableOrFork = nodep->isSuspendable(); + // When we are dealing with initial block we need to + // treat it as suspendable when we meet a NBA + m_inSuspendableOrFork = nodep->isSuspendable() || VN_IS(nodep, Initial); m_procp = nodep; - if (m_inSuspendableOrFork) { + if (nodep->isSuspendable()) { m_ignoreBlkAndNBlk = false; m_inNonCombLogic = true; } iterateChildren(nodep); } - if (m_timingDomains.empty()) return; + auto containsClocled = [](const AstSenItem* itemp) { + while (itemp) { + if (itemp->edgeType().clockedStmt()) return true; + itemp = VN_AS(itemp->nextp(), SenItem); + } + return false; + }; + const bool addInitialTrigger = m_needsInitialTrigger + && !(isProcedureWithSentreep(nodep) + || isProcedureWithSentreep(nodep) + || isProcedureWithSentreep(nodep)) + && !containsClocled(m_activep->sentreep()->sensesp()); + if (m_timingDomains.empty() && !addInitialTrigger) return; // There were some timing domains involved in the process. Add all of them as sensitivities // of all NBA targets in this process. Note this is a bit of a sledgehammer, we should only @@ -1135,6 +1158,11 @@ class DelayedVisitor final : public VNVisitor { // First gather all senItems AstSenItem* senItemp = nullptr; + if (addInitialTrigger) { + senItemp = new AstSenItem{nodep->fileline(), AstSenItem::InitialNBA{}}; + ++m_nInitialNBA; + } + for (const AstSenTree* const domainp : m_timingDomains) { if (domainp->sensesp()) senItemp = AstNode::addNext(senItemp, domainp->sensesp()->cloneTree(true)); @@ -1218,6 +1246,8 @@ class DelayedVisitor final : public VNVisitor { UASSERT_OBJ(m_inSuspendableOrFork || m_activep->hasClocked(), nodep, "<= assignment in non-clocked block, should have been converted in V3Active"); + m_needsInitialTrigger |= m_timingDomains.empty(); + // Record scope of this NBA nodep->user2p(m_scopep); @@ -1327,6 +1357,7 @@ public: V3Stats::addStat("NBA, variables using ValueQueuePartial scheme", m_nSchemeValueQueuesPartial); V3Stats::addStat("Optimizations, NBA flags shared", m_nSharedSetFlags); + V3Stats::addStat("Procedures needing initial NBA trigger", m_nInitialNBA); } }; diff --git a/src/V3Error.h b/src/V3Error.h index 6d4914329..01e7d6447 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -107,6 +107,7 @@ public: ENUMITEMWIDTH, // Error: enum item width mismatch ENUMVALUE, // Error: enum type needs explicit cast EOFNEWLINE, // End-of-file missing newline + FINALDLY, // Final delayed statement FSMMULTI, // Multiple FSM candidates in one always block FUNCTIMECTL, // Functions cannot have timing/delay/wait FUTURE, // Feature is under development and not yet supported @@ -227,8 +228,8 @@ public: "BSSPACE", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CONTASSREG", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED", - "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FSMMULTI", - "FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", + "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FINALDLY", + "FSMMULTI", "FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IEEEMAYDEPRECATE", "IFDEPTH", "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH", "LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", @@ -269,11 +270,11 @@ public: bool pretendError() const VL_MT_SAFE { return (m_e == ASSIGNIN || m_e == BADSTDPRAGMA || m_e == BADVLTPRAGMA || m_e == BLKANDNBLK || m_e == BLKLOOPINIT || m_e == CONTASSREG || m_e == ENCAPSULATED - || m_e == ENDLABEL || m_e == ENUMITEMWIDTH || m_e == ENUMVALUE || m_e == HIERPARAM - || m_e == FUNCTIMECTL || m_e == IMPURE || m_e == MODMISSING || m_e == NOTREDOP - || m_e == PARAMNODEFAULT || m_e == PINNOTFOUND || m_e == PKGNODECL - || m_e == PROCASSWIRE || m_e == PROTOTYPEMIS || m_e == SUPERNFIRST - || m_e == ZEROREPL); + || m_e == ENDLABEL || m_e == ENUMITEMWIDTH || m_e == ENUMVALUE || m_e == FINALDLY + || m_e == HIERPARAM || m_e == FUNCTIMECTL || m_e == IMPURE || m_e == MODMISSING + || m_e == NOTREDOP || m_e == PARAMNODEFAULT || m_e == PINNOTFOUND + || m_e == PKGNODECL || m_e == PROCASSWIRE || m_e == PROTOTYPEMIS + || m_e == SUPERNFIRST || m_e == ZEROREPL); } // Warnings to mention manual bool mentionManual() const VL_MT_SAFE { diff --git a/src/V3SenExprBuilder.h b/src/V3SenExprBuilder.h index 861f8a905..ac9abf196 100644 --- a/src/V3SenExprBuilder.h +++ b/src/V3SenExprBuilder.h @@ -78,7 +78,7 @@ private: // Check if expression contains a class member access that could be null // (e.g., accessing an event through a class reference that may not be initialized) static bool hasClassMemberAccess(const AstNode* const exprp) { - return exprp->exists([](const AstNode* const nodep) { + return exprp && exprp->exists([](const AstNode* const nodep) { if (const AstMemberSel* const mselp = VN_CAST(nodep, MemberSel)) { // Check if the base expression is a class reference return mselp->fromp()->dtypep() @@ -294,6 +294,8 @@ private: } case VEdgeType::ET_TRUE: // return {currp(), false}; + case VEdgeType::ET_INITIAL_NBA: // + return {new AstConst{flp, AstConst::BitFalse{}}, true}; default: // LCOV_EXCL_START senItemp->v3fatalSrc("Unknown edge type"); return {nullptr, false}; diff --git a/test_regress/t/t_altera_lpm.v b/test_regress/t/t_altera_lpm.v index 967cd1dea..19d445364 100644 --- a/test_regress/t/t_altera_lpm.v +++ b/test_regress/t/t_altera_lpm.v @@ -49,7 +49,7 @@ //See also: https://github.com/twosigma/verilator_support // verilog_format: off -// verilator lint_off COMBDLY,INITIALDLY,LATCH,MULTIDRIVEN,UNSIGNED,WIDTH +// verilator lint_off COMBDLY,LATCH,MULTIDRIVEN,UNSIGNED,WIDTH // BEGINNING OF MODULE `timescale 1 ps / 1 ps diff --git a/test_regress/t/t_finaldly_bad.out b/test_regress/t/t_finaldly_bad.out new file mode 100644 index 000000000..01c50dacb --- /dev/null +++ b/test_regress/t/t_finaldly_bad.out @@ -0,0 +1,5 @@ +%Error-FINALDLY: t/t_finaldly_bad.v:9:13: Non-blocking assignment '<=' in final block + 9 | final foo <= 1; + | ^~ + ... For error description see https://verilator.org/warn/FINALDLY?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_initial_dlyass_bad.py b/test_regress/t/t_finaldly_bad.py similarity index 50% rename from test_regress/t/t_initial_dlyass_bad.py rename to test_regress/t/t_finaldly_bad.py index f8feb4a77..09d3c8e81 100755 --- a/test_regress/t/t_initial_dlyass_bad.py +++ b/test_regress/t/t_finaldly_bad.py @@ -4,14 +4,21 @@ # This program is free software; you can redistribute it and/or modify it # under the terms of either the GNU Lesser General Public License Version 3 # or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap test.scenarios('linter') -test.top_filename = "t/t_initial_dlyass.v" -test.lint(fails=True, expect_filename=test.golden_filename) +test.compile(fails=True, expect_filename=test.golden_filename) + +test.extract(in_filename=test.top_filename, + out_filename=test.root + "/docs/gen/ex_FINALDLY_faulty.rst", + lines="8-9") + +test.extract(in_filename=test.golden_filename, + out_filename=test.root + "/docs/gen/ex_FINALDLY_msg.rst", + lines="1-3") test.passes() diff --git a/test_regress/t/t_finaldly_bad.v b/test_regress/t/t_finaldly_bad.v new file mode 100644 index 000000000..81b5223ff --- /dev/null +++ b/test_regress/t/t_finaldly_bad.v @@ -0,0 +1,10 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + bit foo; + final foo <= 1; // <--- Error +endmodule diff --git a/test_regress/t/t_fork_dynscope_out.py b/test_regress/t/t_fork_dynscope_out.py index f478cb764..7ded63f3a 100755 --- a/test_regress/t/t_fork_dynscope_out.py +++ b/test_regress/t/t_fork_dynscope_out.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--binary -Wno-INITIALDLY"]) +test.compile(verilator_flags2=["--binary"]) test.execute() diff --git a/test_regress/t/t_initial_dlyass.py b/test_regress/t/t_initial_dlyass.py index 99d3b795b..3cc73805c 100755 --- a/test_regress/t/t_initial_dlyass.py +++ b/test_regress/t/t_initial_dlyass.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=['-Wno-INITIALDLY']) +test.compile() test.execute() diff --git a/test_regress/t/t_initial_dlyass_bad.out b/test_regress/t/t_initial_dlyass_bad.out deleted file mode 100644 index 54d3da81c..000000000 --- a/test_regress/t/t_initial_dlyass_bad.out +++ /dev/null @@ -1,11 +0,0 @@ -%Warning-INITIALDLY: t/t_initial_dlyass.v:17:7: Non-blocking assignment '<=' in initial/final block - : ... This will be executed as a blocking assignment '='! - 17 | a <= 22; - | ^~ - ... For warning description see https://verilator.org/warn/INITIALDLY?v=latest - ... Use "/* verilator lint_off INITIALDLY */" and lint_on around source to disable this message. -%Warning-INITIALDLY: t/t_initial_dlyass.v:18:7: Non-blocking assignment '<=' in initial/final block - : ... This will be executed as a blocking assignment '='! - 18 | b <= 33; - | ^~ -%Error: Exiting due to diff --git a/test_regress/t/t_lint_historical.v b/test_regress/t/t_lint_historical.v index 014c059a9..d92949dc9 100644 --- a/test_regress/t/t_lint_historical.v +++ b/test_regress/t/t_lint_historical.v @@ -40,6 +40,7 @@ module t; // verilator lint_off ENUMITEMWIDTH // verilator lint_off ENUMVALUE // verilator lint_off EOFNEWLINE + // verilator lint_off FINALDLY // verilator lint_off FUNCTIMECTL // verilator lint_off GENCLK // verilator lint_off GENUNNAMED diff --git a/test_regress/t/t_procedure_always_nba.py b/test_regress/t/t_procedure_always_nba.py new file mode 100755 index 000000000..a68ed3ea9 --- /dev/null +++ b/test_regress/t/t_procedure_always_nba.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.top_filename = "t/t_procedure_nba.v" + +test.compile(verilator_flags2=["--binary", "--stats", "-DPROCEDURE=always"]) + +test.file_grep(test.stats, r'Scheduling, \'act\' extra triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' pre triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' sense triggers\s+(\d+)', 3) +test.file_grep(test.stats, r'Procedures needing initial NBA trigger\s+(\d+)', 100) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_procedure_initial_nba.py b/test_regress/t/t_procedure_initial_nba.py new file mode 100755 index 000000000..528290717 --- /dev/null +++ b/test_regress/t/t_procedure_initial_nba.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.top_filename = "t/t_procedure_nba.v" + +test.compile(verilator_flags2=["--binary", "--stats", "-DPROCEDURE=initial"]) + +test.file_grep(test.stats, r'Scheduling, \'act\' extra triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' pre triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' sense triggers\s+(\d+)', 3) +test.file_grep(test.stats, r'Procedures needing initial NBA trigger\s+(\d+)', 100) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_procedure_nba.v b/test_regress/t/t_procedure_nba.v new file mode 100644 index 000000000..a5cbf19a5 --- /dev/null +++ b/test_regress/t/t_procedure_nba.v @@ -0,0 +1,37 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk; + initial clk = 0; + always #5 clk = ~clk; + bit [99:0][2:0] foo; + bit bar; + + always @(posedge clk) begin + bar <= ~bar; + #1; + end + + genvar i; + for (i = 0; i < 100; i=i+1) + `PROCEDURE begin + foo[i] <= 3; + if (foo[i] !== 0) $stop; + @(posedge clk); + if (foo[i] !== 3) $stop; + foo[i] = 2; + if (foo[i] !== 2) $stop; + #1; + if (foo[i] !== 2) $stop; + foo[i] = 0; + end + + initial #100 begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From e1f1a50327c097edcb739ee437a14deac83c4e92 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 21 Jun 2026 08:49:11 +0100 Subject: [PATCH 66/89] Fix assertion when loop unrolling failed (#7810) Partial fix for #7810 --- src/V3Unroll.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/V3Unroll.cpp b/src/V3Unroll.cpp index ce677324b..b5413e8b6 100644 --- a/src/V3Unroll.cpp +++ b/src/V3Unroll.cpp @@ -375,10 +375,12 @@ class UnrollOneVisitor final : VNVisitor { process(nodep); } void visit(AstJumpGo* nodep) override { + if (!m_ok) return; // Remove trailing dead code if (nodep->nextp()) pushDeletep(nodep->nextp()->unlinkFrBackWithNext()); } void visit(AstLoop* nodep) override { + if (!m_ok) return; m_bindings.checkpoint(); std::pair pair = UnrollOneVisitor::apply(m_stats, m_bindings, nodep); From b92bf860164618cd91766879980367a15b6f8c68 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 21 Jun 2026 10:14:53 -0400 Subject: [PATCH 67/89] Commentary: Changes update --- Changes | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changes b/Changes index 5458cd2bd..438a8c265 100644 --- a/Changes +++ b/Changes @@ -27,6 +27,7 @@ Verilator 5.049 devel * Add `--coverage-per-instance` (#7636). [Yogish Sekhar] * Add NOTREDOP error on reduction and negation operators (#7417) (#7623) (#7624). * Add hierarchy-aware reporting to `verilator_coverage` (#7657). [Yogish Sekhar] +* Add FINALDLY error (#7754). [Igor Zaworski, Antmicro Ltd.] * Deprecate isolate_assignments attribute (#7774) (#7144). [Geza Lore, Testorrent USA, Inc.] * Improve `--coverage-fsm` (#7490) (#7529) (#7561) (#7573) (#7619). [Yogish Sekhar] * Change `+verilator+seed` to default to 1, and 0 to randomly select (#7325) (#7516). [Miguel] @@ -67,6 +68,7 @@ Verilator 5.049 devel * Support `s_until` and `s_until_with`(#7722). [Artur Bieniek, Antmicro Ltd.] * Support covergroup runtime model Phase A1 (#7728). [Matthew Ballance] * Support reduction XOR/AND operations in constraints (#7753). [Kornel Uriasz, Antmicro Ltd.] +* Support NBAs in initial blocks (#7754). [Igor Zaworski, Antmicro Ltd.] * Support assertion control system tasks in classes and interfaces (#7761). [Yilou Wang] * Support cover sequence statement (#7764). [Yilou Wang] * Support unpacked struct stream (#7767). [Nick Brereton] @@ -181,6 +183,7 @@ Verilator 5.049 devel * Fix randomization of dynamic arrays of objects (#7790). [Ryszard Rozak, Antmicro Ltd.] * Fix `$bits` on unpacked structs (#4521) (#7796). [Nick Brereton] * Fix randomize() with skipping derived pre/post_randomize (#7799). [Yilou Wang] +* Fix assertion when loop unrolling failed (#7810). [Geza Lore, Testorrent USA, Inc.] Verilator 5.048 2026-04-26 From 5fc03ae91341a2402f09cb3c6e3ca6d2f8e033f5 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 21 Jun 2026 10:15:47 -0400 Subject: [PATCH 68/89] Commentary: Make RST documents round-trip clean. No output change intended. --- README.rst | 60 ++++++++++--------- docs/guide/connecting.rst | 22 +++---- docs/guide/contributing.rst | 5 +- docs/guide/contributors.rst | 32 +++++----- docs/guide/examples.rst | 8 +-- docs/guide/faq.rst | 28 ++++----- docs/guide/files.rst | 113 ++++++++++++++++++----------------- docs/guide/install-cmake.rst | 10 ++-- docs/guide/install.rst | 2 - docs/guide/languages.rst | 32 +++++----- docs/guide/overview.rst | 12 ++-- docs/guide/simulating.rst | 36 +++++------ docs/guide/verilating.rst | 28 ++++----- docs/internals.rst | 58 +++++++++--------- 14 files changed, 226 insertions(+), 220 deletions(-) diff --git a/README.rst b/README.rst index 8dd591838..d51837df2 100644 --- a/README.rst +++ b/README.rst @@ -8,52 +8,56 @@ .. |badge1| image:: https://img.shields.io/badge/Website-Verilator.org-181717.svg :target: https://verilator.org + .. |badge2| image:: https://img.shields.io/badge/License-LGPL%20v3-blue.svg :target: https://www.gnu.org/licenses/lgpl-3.0 + .. |badge3| image:: https://img.shields.io/badge/License-Artistic%202.0-0298c3.svg :target: https://opensource.org/licenses/Artistic-2.0 + .. |badge4| image:: https://repology.org/badge/tiny-repos/verilator.svg?header=distro%20packages :target: https://repology.org/project/verilator/versions + .. |badge5| image:: https://img.shields.io/docker/pulls/verilator/verilator :target: https://hub.docker.com/r/verilator/verilator + .. |badge7| image:: https://github.com/verilator/verilator/workflows/build/badge.svg :target: https://github.com/verilator/verilator/actions?query=workflow%3Abuild + .. |badge8| image:: https://img.shields.io/github/actions/workflow/status/verilator/verilator/rtlmeter.yml?branch=master&event=schedule&label=benchmarks :target: https://verilator.github.io/verilator-rtlmeter-results - Welcome to Verilator ==================== .. list-table:: - * - **Welcome to Verilator, the fastest Verilog/SystemVerilog simulator.** - * Accepts Verilog or SystemVerilog - * Performs lint code-quality checks - * Compiles into multithreaded C++, or SystemC - * Creates JSON to front-end your own tools + - - **Welcome to Verilator, the fastest Verilog/SystemVerilog simulator.** + - Accepts Verilog or SystemVerilog + - Performs lint code-quality checks + - Compiles into multithreaded C++, or SystemC + - Creates JSON to front-end your own tools - |Logo| - * - |verilator multithreaded performance| + - - |verilator multithreaded performance| - **Fast** - * Outperforms many closed-source commercial simulators - * Single- and multithreaded output models - * - **Widely Used** - * Wide industry and academic deployment - * Out-of-the-box support from Arm and RISC-V vendor IP - * Over 700 contributors + - Outperforms many closed-source commercial simulators + - Single- and multithreaded output models + - - **Widely Used** + - Wide industry and academic deployment + - Out-of-the-box support from Arm and RISC-V vendor IP + - Over 700 contributors - |verilator usage| - * - |verilator community| + - - |verilator community| - **Community Driven & Openly Licensed** - * Guided by the `CHIPS Alliance`_ and `Linux Foundation`_ - * Open, and free as in both speech and beer - * More simulation for your verification budget - * - **Commercial Support Available** - * Commercial support contracts - * Design support contracts - * Enhancement contracts + - Guided by the `CHIPS Alliance`_ and `Linux Foundation`_ + - Open, and free as in both speech and beer + - More simulation for your verification budget + - - **Commercial Support Available** + - Commercial support contracts + - Design support contracts + - Enhancement contracts - |verilator support| - What Verilator Does =================== @@ -78,7 +82,6 @@ SDF annotation, or mixed-signal simulation. However, if you are looking for a path to migrate SystemVerilog to C++/SystemC, or want high-speed simulation, Verilator is the tool for you. - Performance =========== @@ -97,7 +100,6 @@ Mentor ModelSim/Questa, Synopsys VCS, VTOC, and Pragmatic CVer/CVC). But, Verilator is open-sourced, so you can spend on computes rather than licenses. Thus, Verilator gives you the best simulation cycles/dollar. - Installation & Documentation ============================ @@ -116,7 +118,6 @@ For more information: - `Verilator issues `_ - Support ======= @@ -133,7 +134,6 @@ Verilator also supports and encourages commercial support models and organizations; please see `Verilator Commercial Support `_. - Related Projects ================ @@ -150,7 +150,6 @@ Related Projects - `Surfer `_ - Web or offline waveform viewer for Verilator traces. - Open License ============ @@ -162,10 +161,17 @@ the terms of either the GNU Lesser General Public License Version 3 or the Perl Artistic License Version 2.0. See the documentation for more details. .. _chips alliance: https://chipsalliance.org + .. _icarus verilog: https://steveicarus.github.io/iverilog + .. _linux foundation: https://www.linuxfoundation.org + .. |Logo| image:: https://www.veripool.org/img/verilator_256_200_min.png + .. |verilator multithreaded performance| image:: https://www.veripool.org/img/verilator_multithreaded_performance_bg-min.png + .. |verilator usage| image:: https://www.veripool.org/img/verilator_usage_400x200-min.png + .. |verilator community| image:: https://www.veripool.org/img/verilator_community_400x125-min.png + .. |verilator support| image:: https://www.veripool.org/img/verilator_support_400x125-min.png diff --git a/docs/guide/connecting.rst b/docs/guide/connecting.rst index 2876ae765..1d714ce4e 100644 --- a/docs/guide/connecting.rst +++ b/docs/guide/connecting.rst @@ -29,13 +29,13 @@ The generated model class file manages all internal state required by the model, and exposes the following interface that allows interaction with the model: -* Top level IO ports are exposed as references to the appropriate internal +- Top level IO ports are exposed as references to the appropriate internal equivalents. -* Public top level module instances are exposed as pointers to allow access +- Public top level module instances are exposed as pointers to allow access to ``/* verilator public */`` items. -* The root of the design hierarchy (as in SystemVerilog ``$root``) is +- The root of the design hierarchy (as in SystemVerilog ``$root``) is exposed via the ``rootp`` member pointer to allow access to model internals, including ``/* verilator public_flat */`` items. @@ -59,22 +59,22 @@ This means that user code that accesses internal signals in the model (likely including ``/* verilator public_flat */`` signals, as they are often inlined into the root scope) will need to be updated as follows: -* No change required for accessing top level IO signals. These are directly +- No change required for accessing top level IO signals. These are directly accessible in the model class via references. -* No change required for accessing ``/* verilator public */`` items. These +- No change required for accessing ``/* verilator public */`` items. These are directly accessible via sub-module pointers in the model class. -* Accessing any other internal members, including - ``/* verilator public_flat */`` items requires the following changes: +- Accessing any other internal members, including ``/* verilator + public_flat */`` items requires the following changes: - * Additionally include :file:`{prefix}___024root.h`. This header defines + - Additionally include :file:`{prefix}___024root.h`. This header defines type of the ``rootp`` pointer within the model class. Note the ``__024`` substring is the Verilator escape sequence for the ``$`` character, i.e.: ``rootp`` points to the Verilated SystemVerilog ``$root`` scope. - * Replace ``modelp->internal->member`` references with + - Replace ``modelp->internal->member`` references with ``modelp->rootp->internal->member`` references, which contain one additional indirection via the ``rootp`` pointer. @@ -503,9 +503,9 @@ described above is just a wrapper which calls these two functions. 3. If using delays and :vlopt:`--timing`, there are two additional methods the user should call: - * ``designp->eventsPending()``, which returns ``true`` if there are any + - ``designp->eventsPending()``, which returns ``true`` if there are any delayed events pending, - * ``designp->nextTimeSlot()``, which returns the simulation time of the + - ``designp->nextTimeSlot()``, which returns the simulation time of the next delayed event. This method can only be called if ``designp->eventsPending()`` returned ``true``. diff --git a/docs/guide/contributing.rst b/docs/guide/contributing.rst index 3a2e57e81..8f834d5a9 100644 --- a/docs/guide/contributing.rst +++ b/docs/guide/contributing.rst @@ -87,7 +87,8 @@ Please refer to `sv-bugpoint README `_ for more information on how to use `sv-bugpoint`. -.. Contributing -.. ============ +.. + Contributing + ============ .. include:: ../CONTRIBUTING.rst diff --git a/docs/guide/contributors.rst b/docs/guide/contributors.rst index 2d31c91c8..37d7ef9f1 100644 --- a/docs/guide/contributors.rst +++ b/docs/guide/contributors.rst @@ -30,10 +30,10 @@ Alliance `_, and `Antmicro Ltd Previous major corporate sponsors of Verilator, by providing significant contributions of time or funds include: Antmicro Ltd., Atmel Corporation, Compaq Corporation, Digital Equipment Corporation, Embecosm Ltd., Fractile -Ltd., Hicamp Systems, Intel Corporation, Marvell Inc., Mindspeed Technologies -Inc., MicroTune Inc., picoChip Designs Ltd., Sun Microsystems Inc., Nauticus -Networks Inc., SiCortex Inc, Shunyao CAD, Tenstorrent USA, Inc. and Western -Digital Inc. +Ltd., Hicamp Systems, Intel Corporation, Marvell Inc., Mindspeed +Technologies Inc., MicroTune Inc., picoChip Designs Ltd., Sun Microsystems +Inc., Nauticus Networks Inc., SiCortex Inc, Shunyao CAD, Tenstorrent USA, +Inc. and Western Digital Inc. The contributors of major functionality are: Jeremy Bennett, Krzysztof Bieganski, Byron Bradley, Lane Brooks, John Coiner, Duane Galbi, Arkadiusz @@ -143,18 +143,18 @@ Krzysztof Obłonczek, Danny Oler, Andreas Olofsson, Baltazar Ortiz, Aleksander Osman, Don Owen, Tim Paine, Deepa Palaniappan, James Pallister, Vassilis Papaefstathiou, Sanggyu Park, Brad Parker, Risto Pejašinović, Seth Pellegrino, Joel Peltonen, Morten Borup Petersen, Dan Petrisko, Thanh Tung -Pham, Wesley Piard, Maciej Piechotka, David Pierce, Cody Piersall, -T. Platz, Michael Platzer, Dominic Plunkett, Nolan Poe, Tuomas Poikela, -George Polack, David Poole, Michael Popoloski, Roman Popov, Aylon Chaim -Porat, Oron Port, Rich Porter, Rick Porter, Stefan Post, Niranjan Prabhu, -Damien Pretet, Harald Pretl, Bill Pringlemeir, Usha Priyadharshini, Mark -Jackson Pulver, Prateek Puri, Nikolay Puzanov, Han Qi, Jiacheng Qian, -Marshal Qiao, Raynard Qiao, Yujia Qiao, Jasen Qin, Frank Qiu, Nandu Raj, -Kamil Rakoczy, Danilo Ramos, Drew Ranck, Chris Randall, Anton Rapp, Josh -Redford, Odd Magne Reitan, Frédéric Requin, Wajahat Riaz, Dustin Richmond, -Samuel Riedel, Alberto Del Rio, Eric Rippey, Narcis Rodas, Oleg Rodionov, -Ludwig Rogiers, Paul Rolfe, Michail Rontionov, Arjen Roodselaar, Arthur -Rosa, Tobias Rosenkranz, Yernagula Roshit, Diego Roux, Ryszard Rozak, Dan +Pham, Wesley Piard, Maciej Piechotka, David Pierce, Cody Piersall, T. +Platz, Michael Platzer, Dominic Plunkett, Nolan Poe, Tuomas Poikela, George +Polack, David Poole, Michael Popoloski, Roman Popov, Aylon Chaim Porat, +Oron Port, Rich Porter, Rick Porter, Stefan Post, Niranjan Prabhu, Damien +Pretet, Harald Pretl, Bill Pringlemeir, Usha Priyadharshini, Mark Jackson +Pulver, Prateek Puri, Nikolay Puzanov, Han Qi, Jiacheng Qian, Marshal Qiao, +Raynard Qiao, Yujia Qiao, Jasen Qin, Frank Qiu, Nandu Raj, Kamil Rakoczy, +Danilo Ramos, Drew Ranck, Chris Randall, Anton Rapp, Josh Redford, Odd +Magne Reitan, Frédéric Requin, Wajahat Riaz, Dustin Richmond, Samuel +Riedel, Alberto Del Rio, Eric Rippey, Narcis Rodas, Oleg Rodionov, Ludwig +Rogiers, Paul Rolfe, Michail Rontionov, Arjen Roodselaar, Arthur Rosa, +Tobias Rosenkranz, Yernagula Roshit, Diego Roux, Ryszard Rozak, Dan Ruelas-Petrisko, Luca Rufer, Huang Rui, Graham Rushton, Jan Egil Ruud, Denis Rystsov, Pawel Sagan, Robert Sammelson, Adrian Sampson, John Sanguinetti, Josep Sans, Dave Sargeant, Luca Sasselli, Philippe Sauter, diff --git a/docs/guide/examples.rst b/docs/guide/examples.rst index 1702ac513..35fead694 100644 --- a/docs/guide/examples.rst +++ b/docs/guide/examples.rst @@ -10,10 +10,10 @@ Examples This section covers the following examples: -* :ref:`Example Create-Binary Execution` -* :ref:`Example C++ Execution` -* :ref:`Example SystemC Execution` -* :ref:`Examples in the Distribution` +- :ref:`Example Create-Binary Execution` +- :ref:`Example C++ Execution` +- :ref:`Example SystemC Execution` +- :ref:`Examples in the Distribution` .. toctree:: :maxdepth: 1 diff --git a/docs/guide/faq.rst b/docs/guide/faq.rst index e0b184385..96245574c 100644 --- a/docs/guide/faq.rst +++ b/docs/guide/faq.rst @@ -83,17 +83,17 @@ the licenses for details. Some examples: -* Any SystemVerilog or other input fed into Verilator remains your own. +- Any SystemVerilog or other input fed into Verilator remains your own. -* Any of your VPI/DPI C++ routines that Verilator calls remain your own. +- Any of your VPI/DPI C++ routines that Verilator calls remain your own. -* Any of your main() C++ code that calls into Verilator remains your own. +- Any of your main() C++ code that calls into Verilator remains your own. -* If you change Verilator itself, for example, changing or adding a file +- If you change Verilator itself, for example, changing or adding a file under the src/ directory in the repository, you must make the source code available under the GNU Lesser Public License. -* If you change a header Verilator provides, for example, under include/ in +- If you change a header Verilator provides, for example, under include/ in the repository, you must make the source code available under the GNU Lesser Public License. @@ -385,33 +385,33 @@ example of how to do this. How do I get faster build times? """""""""""""""""""""""""""""""" -* When running make, pass the make variable VM_PARALLEL_BUILDS=1, so that +- When running make, pass the make variable VM_PARALLEL_BUILDS=1, so that builds occur in parallel. Note this is now set by default if an output file is large enough to be split due to the :vlopt:`--output-split` option. -* Verilator emits any infrequently executed "cold" routines into separate +- Verilator emits any infrequently executed "cold" routines into separate __Slow.cpp files. This can accelerate compilation as optimization can be disabled on these routines. See the OPT_FAST and OPT_SLOW make variables and :ref:`Benchmarking & Optimization`. -* Use a recent compiler. Newer compilers tend to be faster. +- Use a recent compiler. Newer compilers tend to be faster. -* Compile in parallel on many machines and use caching; see the web for the +- Compile in parallel on many machines and use caching; see the web for the ccache, sccache, distcc, or icecream packages. ccache will skip GCC runs between identical source builds, even across different users. If ccache was installed when Verilator was built, it is used, or see OBJCACHE environment variable to override this. Also see the :vlopt:`--output-split` option and :ref: `Profiling ccache efficiency`. -* To reduce the compile time of classes that use a Verilated module (e.g., +- To reduce the compile time of classes that use a Verilated module (e.g., a top CPP file) you may wish to add a :option:`/*verilator&32;no_inline_module*/` metacomment to your top-level module. This will decrease the amount of code in the model's Verilated class, improving compile times of any instantiating top-level C++ code, at a relatively small cost of execution performance. -* Use :ref:`hierarchical verilation`. +- Use :ref:`hierarchical verilation`. Why do so many files need to recompile when I add a signal? @@ -499,12 +499,12 @@ equal, the best performance is when Verilator sees all of the design. So, look at the hierarchy of your design, labeling instances as to if they are SystemC or Verilog. Then: -* A module with only SystemC instances below must be SystemC. +- A module with only SystemC instances below must be SystemC. -* A module with a mix of Verilog and SystemC instances below must be +- A module with a mix of Verilog and SystemC instances below must be SystemC. (As Verilator cannot connect to lower-level SystemC instances.) -* A module with only Verilog instances below can be either, but for best +- A module with only Verilog instances below can be either, but for best performance should be Verilog. (The exception is if you have a design that is instantiated many times; in this case, Verilating one of the lower modules and instantiating that Verilated instances multiple times diff --git a/docs/guide/files.rst b/docs/guide/files.rst index d50b291df..c2dfb5004 100644 --- a/docs/guide/files.rst +++ b/docs/guide/files.rst @@ -43,140 +43,141 @@ For --cc/--sc, it creates: .. list-table:: - * - *{prefix}*.json + - - *{prefix}*.json - JSON build definition compiling (from --make json) - * - *{prefix}*.mk + - - *{prefix}*.mk - Make include file for compiling (from --make gmake) - * - *{prefix}*\ _classes.mk + - - *{prefix}*\ _classes.mk - Make include file with class names (from --make gmake) - * - *{prefix}*.h + - - *{prefix}*.h - Model header - * - *{prefix}*.cpp + - - *{prefix}*.cpp - Model C++ file - * - *{prefix}*\ ___024root.h + - - *{prefix}*\ ___024root.h - Top-level internal header file (from SystemVerilog $root) - * - *{prefix}*\ ___024root.cpp + - - *{prefix}*\ ___024root.cpp - Top-level internal C++ file (from SystemVerilog $root) - * - *{prefix}*\ ___024root\ *{__n}*.cpp + - - *{prefix}*\ ___024root\ *{__n}*.cpp - Additional top-level internal C++ files - * - *{prefix}*\ ___024root__Slow\ *{__n}*.cpp + - - *{prefix}*\ ___024root__Slow\ *{__n}*.cpp - Infrequent cold routines - * - *{prefix}*\ ___024root__Trace\ *{__n}*.cpp + - - *{prefix}*\ ___024root__Trace\ *{__n}*.cpp - Wave file generation code (from --trace-\*) - * - *{prefix}*\ ___024root__Trace__Slow\ *{__n}*.cpp + - - *{prefix}*\ ___024root__Trace__Slow\ *{__n}*.cpp - Wave file generation code (from --trace-\*) - * - *{prefix}*\ __Dpi.h + - - *{prefix}*\ __Dpi.h - DPI import and export declarations (from --dpi) - * - *{prefix}*\ __Dpi.cpp + - - *{prefix}*\ __Dpi.cpp - Global DPI export wrappers (from --dpi) - * - *{prefix}*\ __Dpi_Export\ *{__n}*.cpp + - - *{prefix}*\ __Dpi_Export\ *{__n}*.cpp - DPI export wrappers scoped to this particular model (from --dpi) - * - *{prefix}*\ __Inlines.h + - - *{prefix}*\ __Inlines.h - Inline support functions - * - *{prefix}*\ __Syms.h + - - *{prefix}*\ __Syms.h - Global symbol table header - * - *{prefix}*\ __Syms.cpp + - - *{prefix}*\ __Syms.cpp - Global symbol table C++ - * - *{prefix}{each_verilog_module}*.h + - - *{prefix}{each_verilog_module}*.h - Lower level internal header files - * - *{prefix}{each_verilog_module}*.cpp + - - *{prefix}{each_verilog_module}*.cpp - Lower level internal C++ files - * - *{prefix}{each_verilog_module}{__n}*.cpp + - - *{prefix}{each_verilog_module}{__n}*.cpp - Additional lower C++ files For --hierarchical mode, it creates: .. list-table:: - * - V\ *{hier_block}*\ / + - - V\ *{hier_block}*/ - Directory to Verilate each hierarchical block (from --hierarchical) - * - *{prefix}*\ __hierVer.d + - - *{prefix}*\ __hierVer.d - Make dependencies of the top module (from --hierarchical) - * - *{prefix}*\ _hier.mk + - - *{prefix}*\ _hier.mk - Make file for hierarchical blocks (from --make gmake) - * - *{prefix}*\ __hierMkJsonArgs.f + - - *{prefix}*\ __hierMkJsonArgs.f - Arguments for hierarchical Verilation (from --make json) - * - *{prefix}*\ __hierMkArgs.f + - - *{prefix}*\ __hierMkArgs.f - Arguments for hierarchical Verilation (from --make gmake) - * - *{prefix}*\ __hierParameters.v + - - *{prefix}*\ __hierParameters.v - Module parameters for hierarchical blocks - * - *{prefix}*\ __hier.dir - - Directory to store .dot, .vpp, .tree of top module (from --hierarchical) + - - *{prefix}*\ __hier.dir + - Directory to store .dot, .vpp, .tree of top module (from + --hierarchical) In specific debug and other modes, it also creates: .. list-table:: - * - *{prefix}*.sarif + - - *{prefix}*.sarif - SARIF diagnostics (from --diagnostics-sarif) - * - *{prefix}*.tree.json + - - *{prefix}*.tree.json - JSON tree information (from --json-only) - * - *{prefix}*.tree.meta.json + - - *{prefix}*.tree.meta.json - JSON tree metadata (from --json-only) - * - *{prefix}*\ __cdc.txt + - - *{prefix}*\ __cdc.txt - Clock Domain Crossing checks (from --cdc) - * - *{prefix}*\ __stats.txt + - - *{prefix}*\ __stats.txt - Statistics (from --stats) - * - *{prefix}*\ __idmap.txt + - - *{prefix}*\ __idmap.txt - Symbol demangling (from --protect-ids) - * - *{prefix}*\ __ver.d + - - *{prefix}*\ __ver.d - Make dependencies (from -MMD) - * - *{prefix}*\ __verFiles.dat + - - *{prefix}*\ __verFiles.dat - Timestamps (from --skip-identical) - * - *{prefix}{misc}*.dot + - - *{prefix}{misc}*.dot - Debugging graph files (from --debug) - * - *{prefix}{misc}*.tree + - - *{prefix}{misc}*.tree - Debugging files (from --debug) - * - *{prefix}*\ __inputs.vpp + - - *{prefix}*\ __inputs.vpp - Pre-processed verilog for all files (from --debug) - * - *{prefix}*\ _ *{each_verilog_base_filename}*.vpp + - - *{prefix}*\ _ *{each_verilog_base_filename}*.vpp - Pre-processed verilog for each file (from --debug) After running Make, the C++ compiler may produce the following: .. list-table:: - * - verilated{misc}*.d + - - verilated{misc}*.d - Intermediate dependencies - * - verilated{misc}*.o + - - verilated{misc}*.o - Intermediate objects - * - {mod_prefix}{misc}*.d + - - {mod_prefix}{misc}*.d - Intermediate dependencies - * - {mod_prefix}{misc}*.o + - - {mod_prefix}{misc}*.o - Intermediate objects - * - *{prefix}*\ + - - *{prefix}*\ - Final executable (from --exe) - * - lib\ *{prefix}*.a + - - lib\ *{prefix}*.a - Final archive (default lib mode) - * - libverilated.a + - - libverilated.a - Runtime for verilated model (default lib mode) - * - *{prefix}*\ __ALL.a + - - *{prefix}*\ __ALL.a - Library of all Verilated objects - * - *{prefix}*\ __ALL.cpp + - - *{prefix}*\ __ALL.cpp - Include of all code for single compile - * - *{prefix}{misc}*.d + - - *{prefix}{misc}*.d - Intermediate dependencies - * - *{prefix}{misc}*.o + - - *{prefix}{misc}*.o - Intermediate objects The Verilated executable may produce the following: .. list-table:: - * - coverage.dat + - - coverage.dat - Code coverage output, and default input filename for :command:`verilator_coverage` - * - gmon.out + - - gmon.out - GCC/clang code profiler output, often fed into :command:`verilator_profcfunc` - * - profile.vlt + - - profile.vlt - --prof-pgo data file for :ref:`Thread PGO` - * - profile_exec.dat + - - profile_exec.dat - --prof-exec data file for :command:`verilator_gantt` Verilator_gantt may produce the following: .. list-table:: - * - profile_exec.vcd + - - profile_exec.vcd - Gantt report waveform output diff --git a/docs/guide/install-cmake.rst b/docs/guide/install-cmake.rst index 56723842b..6d840a552 100644 --- a/docs/guide/install-cmake.rst +++ b/docs/guide/install-cmake.rst @@ -17,16 +17,16 @@ Linux). Quick Install ============= -1. Install Python for your platform from https://www.python.org/downloads/. -2. Install CMake for your platform from https://cmake.org/download/ or +#. Install Python for your platform from https://www.python.org/downloads/. +#. Install CMake for your platform from https://cmake.org/download/ or build it from source. -3. If the compiler of your choice is MSVC, then install +#. If the compiler of your choice is MSVC, then install https://visualstudio.microsoft.com/downloads/. If the compiler of your choice is Clang, then install https://releases.llvm.org/download.html or build it from source. -4. For flex and bison use https://github.com/lexxmark/winflexbison to build +#. For flex and bison use https://github.com/lexxmark/winflexbison to build and install. -5. For build on Windows using MSVC set environment variable WIN_FLEX_BISON +#. For build on Windows using MSVC set environment variable WIN_FLEX_BISON to install directory. For build on Windows/Linux/OS-X using ninja set the environment variable FLEX_INCLUDE to the directory containing FlexLexer.h and ensure that flex/bison is available within the PATH. diff --git a/docs/guide/install.rst b/docs/guide/install.rst index 59b29b2cf..99d8f66a5 100644 --- a/docs/guide/install.rst +++ b/docs/guide/install.rst @@ -385,12 +385,10 @@ the files: make install - .. Docker Build Environment .. include:: ../../ci/docker/buildenv/README.rst - .. Docker Run Environment .. include:: ../../ci/docker/run/README.rst diff --git a/docs/guide/languages.rst b/docs/guide/languages.rst index b0d2f57f5..b2bd17e69 100644 --- a/docs/guide/languages.rst +++ b/docs/guide/languages.rst @@ -103,11 +103,11 @@ Time With :vlopt:`--timing`, all timing controls are supported: -* delay statements, -* event control statements not only at the top of a process, -* intra-assignment timing controls, -* net delays, -* ``wait`` statements, +- delay statements, +- event control statements not only at the top of a process, +- intra-assignment timing controls, +- net delays, +- ``wait`` statements, as well as all flavors of ``fork``. @@ -129,26 +129,26 @@ simulation (perhaps using :vlopt:`--build`) and run it. With :vlopt:`--no-timing`, all timing controls cause the :option:`NOTIMING` error, except: -* delay statements - they are ignored (as they are in synthesis), though they - do issue a :option:`STMTDLY` warning, -* intra-assignment timing controls - they are ignored, though they do issue +- delay statements - they are ignored (as they are in synthesis), though + they do issue a :option:`STMTDLY` warning, +- intra-assignment timing controls - they are ignored, though they do issue an :option:`ASSIGNDLY` warning, -* net delays - they are ignored, -* event controls at the top of the procedure, +- net delays - they are ignored, +- event controls at the top of the procedure, Forks cause this error as well, except: -* forks with no statements, -* ``fork..join`` or ``fork..join_any`` with one statement, -* forks with :vlopt:`--bbox-unsup`. +- forks with no statements, +- ``fork..join`` or ``fork..join_any`` with one statement, +- forks with :vlopt:`--bbox-unsup`. If neither :vlopt:`--timing` nor :vlopt:`--no-timing` is specified, all timing controls cause the :option:`NEEDTIMINGOPT` error, except event controls at the top of the process. Forks cause this error as well, except: -* forks with no statements, -* ``fork..join`` or ``fork..join_any`` with one statement, -* forks with :vlopt:`--bbox-unsup`. +- forks with no statements, +- ``fork..join`` or ``fork..join_any`` with one statement, +- forks with :vlopt:`--bbox-unsup`. Timing controls and forks can also be ignored in specific files or parts of files. The :option:`/*verilator&32;timing_off*/` and diff --git a/docs/guide/overview.rst b/docs/guide/overview.rst index 7bd01c1b8..bf57423a0 100644 --- a/docs/guide/overview.rst +++ b/docs/guide/overview.rst @@ -48,9 +48,9 @@ The best place to get started is to try the :ref:`Examples`. uses the shorthand, e.g., "IEEE 1364-2005", to refer to the, e.g., 2005 version of this standard. -.. [#] SystemVerilog is defined by the `Institute of Electrical and - Electronics Engineers (IEEE) Standard for SystemVerilog - Unified - Hardware Design, Specification, and Verification Language`, Standard - 1800, released in 2005, 2009, 2012, 2017, and 2023. The Verilator - documentation uses the shorthand e.g., "IEEE 1800-2023", to refer to - the, e.g., 2023 version of this standard. +.. [#] SystemVerilog is defined by the `Institute of Electrical and Electronics + Engineers (IEEE) Standard for SystemVerilog - Unified Hardware Design, + Specification, and Verification Language`, Standard 1800, released in + 2005, 2009, 2012, 2017, and 2023. The Verilator documentation uses the + shorthand e.g., "IEEE 1800-2023", to refer to the, e.g., 2023 version of + this standard. diff --git a/docs/guide/simulating.rst b/docs/guide/simulating.rst index 168a02414..6ace6014d 100644 --- a/docs/guide/simulating.rst +++ b/docs/guide/simulating.rst @@ -210,7 +210,8 @@ For simple coverage points, use the ``cover property`` construct: DefaultClock: cover property (@(posedge clk) cyc==3); -This adds a coverage point that tracks whether the condition has been observed. +This adds a coverage point that tracks whether the condition has been +observed. .. _covergroup coverage: @@ -268,13 +269,12 @@ deeply nested control recovery, or cross-module state alias tracing. The following metacomments may be attached to the state variable to steer the extracted coverage model: -- ``/*verilator fsm_state*/`` forces the variable to be treated as - FSM state. -- ``/*verilator fsm_reset_arc*/`` marks reset transitions as - user-visible reset arcs instead of defaulting to a hidden reset-only - summary. -- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch - arcs that would otherwise be skipped by the conservative extractor. +- ``/*verilator fsm_state*/`` forces the variable to be treated as FSM + state. +- ``/*verilator fsm_reset_arc*/`` marks reset transitions as user-visible + reset arcs instead of defaulting to a hidden reset-only summary. +- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch arcs that + would otherwise be skipped by the conservative extractor. State registers may also be wrapped by a transparent instance, for example a project flop wrapper or primitive. Such wrappers must be described @@ -433,11 +433,11 @@ coverage point insertions into the model and collect the coverage data. To get the coverage data from the model, write the coverage with either: -1. Using :vlopt:`--binary` or :vlopt:`--main`, and Verilator will dump +#. Using :vlopt:`--binary` or :vlopt:`--main`, and Verilator will dump coverage when the test completes to the filename specified with :vlopt:`+verilator+coverage+file+\`. -2. In the user wrapper code, typically at the end once a test passes, call +#. In the user wrapper code, typically at the end once a test passes, call ``Verilated::threadContextp()->coveragep()->write`` with an argument of the filename for the coverage data file to write coverage data to (typically "logs/coverage.dat"). @@ -502,12 +502,12 @@ how execution time is distributed in a verilated model. With the :vlopt:`--prof-exec` option, Verilator will: -* Add code to the Verilated model to record execution flow. +- Add code to the Verilated model to record execution flow. -* Add code to save profiling data in non-human-friendly form to the file +- Add code to save profiling data in non-human-friendly form to the file specified with :vlopt:`+verilator+prof+exec+file+\`. -* In multithreaded models, add code to record each macro-task's start and +- In multithreaded models, add code to record each macro-task's start and end time across several calls to eval. (What is a macro-task? See the Verilator internals document (:file:`docs/internals.rst` in the distribution.) @@ -607,8 +607,8 @@ There are two forms of profile-guided optimizations. Unfortunately, for best results, they must each be performed from the highest level code to the lowest, which means performing them separately and in this order: -* :ref:`Thread PGO` -* :ref:`Compiler PGO` +- :ref:`Thread PGO` +- :ref:`Compiler PGO` Other forms of PGO may be supported in the future, such as clock and reset toggle rate PGO, branch prediction PGO, statement execution time PGO, or @@ -674,7 +674,7 @@ multithreaded models. Please see the appropriate compiler documentation to use PGO with GCC or Clang. The process in GCC 10 was as follows: -1. Compile the Verilated model with the compiler's "-fprofile-generate" +#. Compile the Verilated model with the compiler's "-fprofile-generate" flag: .. code-block:: bash @@ -685,10 +685,10 @@ Clang. The process in GCC 10 was as follows: Or, if calling make yourself, add -fprofile-generate appropriately to your Makefile. -2. Run your simulation. This will create \*.gcda file(s) in the same +#. Run your simulation. This will create \*.gcda file(s) in the same directory as the source files. -3. Recompile the model with -fprofile-use. The compiler will read the +#. Recompile the model with -fprofile-use. The compiler will read the \*.gcda file(s). For GCC: diff --git a/docs/guide/verilating.rst b/docs/guide/verilating.rst index 65a6f2d07..6a856248d 100644 --- a/docs/guide/verilating.rst +++ b/docs/guide/verilating.rst @@ -8,21 +8,21 @@ Verilating Verilator may be used in five major ways: -* With the :vlopt:`--binary` option, Verilator will translate the design +- With the :vlopt:`--binary` option, Verilator will translate the design into an executable, via generating C++ and compiling it. See :ref:`Binary, C++ and SystemC Generation`. -* With the :vlopt:`--cc` or :vlopt:`--sc` options, Verilator will translate +- With the :vlopt:`--cc` or :vlopt:`--sc` options, Verilator will translate the design into C++ or SystemC code, respectively. See :ref:`Binary, C++ and SystemC Generation`. -* With the :vlopt:`--lint-only` option, Verilator will lint the design to +- With the :vlopt:`--lint-only` option, Verilator will lint the design to check for warnings but will not typically create any output files. -* With the :vlopt:`--json-only` option, Verilator will create JSON output +- With the :vlopt:`--json-only` option, Verilator will create JSON output that may be used to feed into other user-designed tools. -* With the :vlopt:`-E` option, Verilator will preprocess the code according +- With the :vlopt:`-E` option, Verilator will preprocess the code according to IEEE preprocessing rules and write the output to standard out. This is useful to feed other tools and to debug how "\`define" statements are expanded. @@ -128,9 +128,9 @@ Usage Users need to mark one or more moderate-size modules as hierarchy block(s). There are two ways to mark a module: -* Write :option:`/*verilator&32;hier_block*/` metacomment in HDL code. +- Write :option:`/*verilator&32;hier_block*/` metacomment in HDL code. -* Add a :option:`hier_block` line in the :ref:`Verilator Control Files`. +- Add a :option:`hier_block` line in the :ref:`Verilator Control Files`. Then pass the :vlopt:`--hierarchical` option to Verilator. @@ -146,28 +146,28 @@ Limitations Hierarchy blocks have some limitations, including: -* Internals of the hierarchy block cannot be accessed using dot (.) from +- Internals of the hierarchy block cannot be accessed using dot (.) from the upper module(s) or other hierarchy blocks, except that ports of a hierarchy block instance can be accessed from the directly enclosing nested hierarchy block, or from the top level non-hierarchical portions of the design if not a nested hierarchy block. -* Modport cannot be used at the hierarchical block boundary. +- Modport cannot be used at the hierarchical block boundary. -* The simulation speed is likely not as fast as flat Verilation, in which +- The simulation speed is likely not as fast as flat Verilation, in which all modules are globally scheduled. -* Generated clocks may not work correctly if generated in the hierarchical +- Generated clocks may not work correctly if generated in the hierarchical model and passed into another hierarchical model or the top module. -* Delays are not allowed in hierarchy blocks. +- Delays are not allowed in hierarchy blocks. But, the following usage is supported: -* Nested hierarchy blocks. A hierarchy block may instantiate other +- Nested hierarchy blocks. A hierarchy block may instantiate other hierarchy blocks. -* Parameterized hierarchy block. Parameters of a hierarchy block can be +- Parameterized hierarchy block. Parameters of a hierarchy block can be overridden using ``#(.param_name(value))`` construct. diff --git a/docs/internals.rst b/docs/internals.rst index ab5754dd0..fb3b5281d 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -113,9 +113,9 @@ pointer to the ``AstNode`` currently being processed. There are notable sub-hierarchies of the ``AstNode`` sub-types, namely: -1. All AST nodes representing data types derive from ``AstNodeDType``. +#. All AST nodes representing data types derive from ``AstNodeDType``. -2. All AST nodes representing expressions (i.e.: anything that stands for, +#. All AST nodes representing expressions (i.e.: anything that stands for, or evaluates to a value) derive from ``AstNodeExpr``. @@ -666,23 +666,23 @@ The second visitor in ``V3Timing.cpp``, ``TimingControlVisitor``, uses the information provided by ``TimingSuspendableVisitor`` and transforms each timing control into a ``co_await``. -* event controls are turned into ``co_await`` on a trigger scheduler's +- event controls are turned into ``co_await`` on a trigger scheduler's ``trigger`` method. The awaited trigger scheduler is the one corresponding to the sentree referenced by the event control. This sentree is also referenced by the ``AstCAwait`` node, to be used later by the static scheduling code. -* if an event control waits on a local variable or class member, it uses a +- if an event control waits on a local variable or class member, it uses a local trigger which it evaluates inline. It awaits a dynamic trigger scheduler multiple times: for trigger evaluation, updates, and resumption. The dynamic trigger scheduler is responsible for resuming the coroutine at the correct point of evaluation. -* delays are turned into ``co_await`` on a delay scheduler's ``delay`` +- delays are turned into ``co_await`` on a delay scheduler's ``delay`` method. The created ``AstCAwait`` nodes also reference a special sentree related to delays, to be used later by the static scheduling code. -* ``join`` and ``join_any`` are turned into ``co_await`` on a +- ``join`` and ``join_any`` are turned into ``co_await`` on a ``VlForkSync``'s ``join`` method. Each forked process gets a ``VlForkSync::done`` call at the end. @@ -732,22 +732,22 @@ event `a` was called first - which is necessary to know. There are two functions for managing timing logic called by ``_eval()``: -* ``_timing_ready()``, which commits all coroutines whose triggers were not +- ``_timing_ready()``, which commits all coroutines whose triggers were not set in the current iteration, -* ``_timing_resume()``, which calls `resume()` on all trigger and delay +- ``_timing_resume()``, which calls `resume()` on all trigger and delay schedulers whose triggers were set in the current iteration. Thanks to this separation a coroutine: -* awaiting a trigger cannot be suspended and resumed in the same iteration +- awaiting a trigger cannot be suspended and resumed in the same iteration (``test_regress/t/t_timing_eval_act.v``) - which is necessary to make Verilator more predictable; this is the reason for introduction of 3rd stage in `VlTriggerScheduler` and thanks to this it is guaranteed that downstream logic will be evaluated before resumption (assuming that the coroutine wasn't already triggered in previous iteration); -* cannot be resumed before it is suspended - +- cannot be resumed before it is suspended - ``test_regress/t/t_event_control_double_excessive.v``; -* firing cannot cannot be lost +- firing cannot cannot be lost (``test_regress/t/t_event_control_double_lost.v``) - which is possible when triggers are not evaluated right before awaiting. @@ -1300,15 +1300,15 @@ the ```` field is `` : ``, where ```` will be used as the base name of the generated operand accessors, and ```` is one of: -1. An ``AstNode`` sub-class, defining the operand to be of that type, +#. An ``AstNode`` sub-class, defining the operand to be of that type, always no-null, and with an always null ``nextp()``. That is, the child node is always present, and is a single ``AstNode`` (as opposed to a list). -2. ``Optional[]``. This is just like in point 1 above, +#. ``Optional[]``. This is just like in point 1 above, but defines the child node to be optional, meaning it may be null. -3. ``List[AstNode sub-class]`` describes a list operand, which means the +#. ``List[AstNode sub-class]`` describes a list operand, which means the child node may have a non-null ``nextp()`` and in addition the child itself may be null, representing an empty list. @@ -1395,7 +1395,7 @@ calling ``accept`` on ``AstIf`` will look in turn for: There are three ways data is passed between visitor functions. -1. A visitor-class member variable. This is generally for passing "parent" +#. A visitor-class member variable. This is generally for passing "parent" information down to children. ``m_modp`` is a common example. It's set to NULL in the constructor, where that node (``AstModule`` visitor) sets it, then the children are iterated, then it's cleared. Children under an @@ -1405,7 +1405,7 @@ There are three ways data is passed between visitor functions. visitor; otherwise exiting the lower for will lose the upper for's setting. -2. User attributes. Each ``AstNode`` (**Note.** The AST node, not the +#. User attributes. Each ``AstNode`` (**Note.** The AST node, not the visitor) has five user attributes, which may be accessed as an integer using the ``user1()`` through ``user4()`` methods, or as a pointer (of type ``AstNUser``) using the ``user1p()`` through ``user4p()`` methods @@ -1436,7 +1436,7 @@ There are three ways data is passed between visitor functions. so it's ok to call fairly often. For example, it's commonly called on every module. -3. Parameters can be passed between the visitors in close to the "normal" +#. Parameters can be passed between the visitors in close to the "normal" function caller to callee way. This is the second ``vup`` parameter of type ``AstNUser`` that is ignored on most of the visitor functions. V3Width does this, but it proved messier than the above and is @@ -2126,19 +2126,19 @@ To print a node: ``src/.gdbinit`` and ``src/.gdbinit.py`` define handy utilities for working with JSON AST dumps. For example: -* ``jstash nodep`` - Perform a JSON AST dump and save it into GDB value +- ``jstash nodep`` - Perform a JSON AST dump and save it into GDB value history (e.g. ``$1``) -* ``jtree nodep`` - Perform a JSON AST dump and pretty print it using +- ``jtree nodep`` - Perform a JSON AST dump and pretty print it using ``astsee_verilator``. -* ``jtree $1`` - Pretty print a dump that was previously saved by +- ``jtree $1`` - Pretty print a dump that was previously saved by ``jstash``. -* ``jtree nodep -d '.file, .timeunit'`` - Perform a JSON AST dump, filter +- ``jtree nodep -d '.file, .timeunit'`` - Perform a JSON AST dump, filter out some fields and pretty print it. -* ``jtree 0x55555613dca0`` - Pretty print using address literal (rather +- ``jtree 0x55555613dca0`` - Pretty print using address literal (rather than actual pointer). -* ``jtree $1 nodep`` - Diff ``nodep`` against an older dump. +- ``jtree $1 nodep`` - Diff ``nodep`` against an older dump. A detailed description of ``jstash`` and ``jtree`` can be displayed using ``gdb``'s ``help`` command. @@ -2244,25 +2244,25 @@ Adding a New Feature Generally, what would you do to add a new feature? -1. File an issue (if there isn't already) so others know what you're +#. File an issue (if there isn't already) so others know what you're working on. -2. Make a testcase in the test_regress/t/t_EXAMPLE format, see `Testing`. +#. Make a testcase in the test_regress/t/t_EXAMPLE format, see `Testing`. -3. If grammar changes are needed, look at the IEEE 1800-2023 Appendix A, as +#. If grammar changes are needed, look at the IEEE 1800-2023 Appendix A, as src/verilog.y generally follows the same rule layout. -4. If a new Ast type is needed, add it to the appropriate V3AstNode*.h. +#. If a new Ast type is needed, add it to the appropriate V3AstNode*.h. Follow the convention described above about the AstNode type hierarchy. Ordering of definitions is enforced by ``astgen``. -5. Now you can run ``test_regress/t/t_.py --debug`` and it'll +#. Now you can run ``test_regress/t/t_.py --debug`` and it'll probably fail, but you'll see a ``test_regress/obj_dir/t_/*.tree`` file which you can examine to see if the parsing worked. See also the sections above on debugging. -6. Modify the later visitor functions to process the new feature as needed. +#. Modify the later visitor functions to process the new feature as needed. Adding a New Pass From bcaa110f60e309056e36610122f3ec3367cd0a3e Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 21 Jun 2026 18:31:56 +0100 Subject: [PATCH 69/89] Optimize generated function inlining (#7811) Previously V3InlineCFuncs inlined call sites but never deleted the now dead callees. Also missed a lot of opportunities due to evaluation order. Rewrite using a graph based algorithm, using only a single traversal of the netlist. This is clearer, more accurate, and faster at compile time. Also add a clean -fno-inline-cfuncs disable. Setting the limits to 0 still disables inlining, except of empty functions, which can be inlined with 0 limits (they are no ops). It will also prune unused functions without -fno-inline-cfuncs. Pass now also respects `--output-split` --- docs/guide/exe_verilator.rst | 40 +- src/V3InlineCFuncs.cpp | 585 ++++++++++++------ src/V3Options.cpp | 2 + src/V3Options.h | 2 + src/V3Trace.cpp | 1 + src/V3TraceDecl.cpp | 2 + src/Verilator.cpp | 8 +- test_regress/t/t_display_merge.py | 2 +- test_regress/t/t_flag_csplit_eval.py | 2 +- test_regress/t/t_hier_block_chained.py | 4 +- test_regress/t/t_hier_block_perf.py | 4 +- test_regress/t/t_inst_tree_inl0_pub1.py | 4 +- test_regress/t/t_json_only_debugcheck.py | 2 +- test_regress/t/t_opt_inline_cfuncs.py | 14 +- test_regress/t/t_opt_inline_cfuncs_args.py | 20 + test_regress/t/t_opt_inline_cfuncs_args.v | 36 ++ test_regress/t/t_opt_inline_cfuncs_dup.py | 20 + test_regress/t/t_opt_inline_cfuncs_dup.v | 30 + test_regress/t/t_opt_inline_cfuncs_off.py | 10 +- .../t/t_opt_inline_cfuncs_threshold.py | 13 +- test_regress/t/t_opt_inline_cfuncs_trace.py | 24 + test_regress/t/t_protect_ids_key.out | 7 +- test_regress/t/t_timing_debug1.py | 2 +- test_regress/t/t_timing_debug2.py | 4 +- test_regress/t/t_timing_eval_act.out | 1 + test_regress/t/t_timing_eval_act.py | 2 +- test_regress/t/t_verilated_debug.out | 1 + test_regress/t/t_verilated_debug.py | 2 +- 28 files changed, 612 insertions(+), 232 deletions(-) create mode 100755 test_regress/t/t_opt_inline_cfuncs_args.py create mode 100644 test_regress/t/t_opt_inline_cfuncs_args.v create mode 100755 test_regress/t/t_opt_inline_cfuncs_dup.py create mode 100644 test_regress/t/t_opt_inline_cfuncs_dup.v create mode 100755 test_regress/t/t_opt_inline_cfuncs_trace.py diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 4e302f06d..c35cd3e57 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -771,10 +771,24 @@ Summary: .. option:: -fno-inline + Rarely needed. Disable module inlining. + +.. option:: -fno-inline-cfuncs + + Rarely needed. Disable inlining of small generated C++ functions into their + callers. + + This optimization is automatically disabled when :vlopt:`--prof-cfuncs` is + used. + .. option:: -fno-inline-funcs + Rarely needed. Disable inlining of SystemVerilog functions and tasks. + .. option:: -fno-inline-funcs-eager + Rarely needed. Disable eager inlining of SystemVerilog functions and tasks. + .. option:: -fno-life .. option:: -fno-life-post @@ -976,27 +990,21 @@ Summary: .. option:: --inline-cfuncs - Inline small C++ function (internal AstCFunc) calls directly into their - callers when the function has at most nodes. This reduces - function call overhead when :vlopt:`--output-split-cfuncs` places - functions in separate compilation units that the C++ compiler cannot - inline. + Tune the inlining of small generated C++ function. Functions no bigger than + nodes will be inlined if possible. The default is 20. - Set to 0 to disable this optimization. The default is 20. - - This optimization is automatically disabled when :vlopt:`--prof-cfuncs` - or :vlopt:`--trace` is used. + See also :vlopt:`--inline-cfuncs-product` and :vlopt:`-fno-inline-cfuncs`. .. option:: --inline-cfuncs-product - Tune the inlining of C++ function (internal AstCFunc) calls for larger - functions. When a function is too large to always inline (exceeds - :vlopt:`--inline-cfuncs` threshold), it may still be inlined if the - function size multiplied by the number of call sites is at most . + Tune the inlining of small generated C++ function. If a function's node + count multiplied by the number of calls is not bigger than , the + function will be inlined if possible. - This allows functions that are called only once or twice to be inlined - even if they exceed the small function threshold. Set to 0 to only inline - functions below the :vlopt:`--inline-cfuncs` threshold. The default is 200. + This allows functions that are called only once or twice to be inlined even + if they exceed the small function threshold. The default is 200. + + See also :vlopt:`--inline-cfuncs` and :vlopt:`-fno-inline-cfuncs`. .. option:: --inline-mult diff --git a/src/V3InlineCFuncs.cpp b/src/V3InlineCFuncs.cpp index 99efbc201..cdfc24062 100644 --- a/src/V3InlineCFuncs.cpp +++ b/src/V3InlineCFuncs.cpp @@ -15,14 +15,13 @@ //************************************************************************* // V3InlineCFuncs's Transformations: // -// For each CCall to a small CFunc: -// - Check if function is eligible for inlining (small enough, same scope) -// - Clone local variables with unique names to avoid collisions -// - Replace CCall with cloned function body statements +// Build a bipartite call graph containing function and call site vertices, +// then iterate functions leaf to root, inlining if size heuristics are met. +// Finally, remove unused functions. // // Two tunables control inlining: -// --inline-cfuncs : Always inline if size <= n (default 20) -// --inline-cfuncs-product : Also inline if size * call_count <= n (default 200) +// --inline-cfuncs : Inline if size <= n +// --inline-cfuncs-product : Also inline if size * call_count <= n // //************************************************************************* @@ -31,231 +30,465 @@ #include "V3InlineCFuncs.h" #include "V3AstUserAllocator.h" +#include "V3ExecGraph.h" +#include "V3Graph.h" #include "V3Stats.h" -#include #include VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### -// Helper visitor to check if a CFunc contains C statements -// Uses clearOptimizable pattern for debugging +// Bipartite call graph containing function and call site vertices -class CFuncInlineCheckVisitor final : public VNVisitorConst { - // STATE - bool m_optimizable = true; // True if function can be inlined - string m_whyNot; // Reason why not optimizable - AstNode* m_whyNotNodep = nullptr; // Node that caused non-optimizable +class InlineCFuncsFunctionVertex; +class InlineCFuncsCallSiteVertex; - // METHODS - void clearOptimizable(AstNode* nodep, const string& why) { - if (m_optimizable) { - m_optimizable = false; - m_whyNot = why; - m_whyNotNodep = nodep; - UINFO(9, "CFunc not inlineable: " << why); - if (nodep) UINFO(9, ": " << nodep); - UINFO(9, ""); - } - } +class InlineCFuncsCallGraph final : public V3Graph { +public: + InlineCFuncsCallGraph() + : V3Graph{} {} + ~InlineCFuncsCallGraph() override = default; - // VISITORS - void visit(AstCStmt* nodep) override { clearOptimizable(nodep, "contains AstCStmt"); } - void visit(AstCExpr* nodep) override { clearOptimizable(nodep, "contains AstCExpr"); } - void visit(AstCStmtUser* nodep) override { clearOptimizable(nodep, "contains AstCStmtUser"); } - void visit(AstCExprUser* nodep) override { clearOptimizable(nodep, "contains AstCExprUser"); } - void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } + void addEdge(InlineCFuncsFunctionVertex& from, InlineCFuncsCallSiteVertex& top); + void addEdge(InlineCFuncsCallSiteVertex& from, InlineCFuncsFunctionVertex& top); +}; + +class EitherVertex VL_NOT_FINAL : public V3GraphVertex { + VL_RTTI_IMPL(EitherVertex, V3GraphVertex) +protected: + explicit EitherVertex(InlineCFuncsCallGraph& graph) + : V3GraphVertex{&graph} {} +}; + +class InlineCFuncsFunctionVertex final : public EitherVertex { + VL_RTTI_IMPL(InlineCFuncsFunctionVertex, EitherVertex) + AstCFunc* const m_cfuncp; // The function + const char* m_noInlineWyp = nullptr; // First reason the function should not be inlined + const char* m_keepWyp = nullptr; // Why the function should not be removed + size_t m_size = 0; // The size of the function public: - // CONSTRUCTORS - explicit CFuncInlineCheckVisitor(AstCFunc* cfuncp) { iterateConst(cfuncp); } + InlineCFuncsFunctionVertex(InlineCFuncsCallGraph& graph, AstCFunc* cfuncp) + : EitherVertex{graph} + , m_cfuncp{cfuncp} {} + ~InlineCFuncsFunctionVertex() override = default; // ACCESSORS - bool optimizable() const { return m_optimizable; } - string whyNot() const { return m_whyNot; } - AstNode* whyNotNodep() const { return m_whyNotNodep; } + AstCFunc* cfuncp() const { return m_cfuncp; } + size_t size() const { return m_size; } + void sizeInc(size_t value = 1) { m_size += value; } + bool noInline() const { return m_noInlineWyp; } + void setNoInline(const char* whyp) { + if (!m_noInlineWyp) m_noInlineWyp = whyp; + } + bool keep() const { return m_keepWyp; } + void setKeep(const char* whyp) { + if (!m_keepWyp) m_keepWyp = whyp; + } + std::string dotColor() const override { + return m_noInlineWyp ? "red" : m_keepWyp ? "orange" : "black"; + } + + // debug + FileLine* fileline() const override { return m_cfuncp->fileline(); } + std::string dotShape() const override { return "box"; } + std::string name() const override VL_MT_STABLE { + std::string str = cvtToHex(m_cfuncp); + str += "\n" + m_cfuncp->name(); + str += "\nsize: " + std::to_string(m_size); + if (m_noInlineWyp) str += "\nNoInline: "s + m_noInlineWyp; + if (m_keepWyp) str += "\nKeep: "s + m_keepWyp; + return str; + } }; +class InlineCFuncsCallSiteVertex final : public EitherVertex { + VL_RTTI_IMPL(InlineCFuncsCallSiteVertex, EitherVertex) + AstCCall* const m_callp; // The call site + const char* m_noInlineWyp = nullptr; // First reason the function should not be inlined + +public: + InlineCFuncsCallSiteVertex(InlineCFuncsCallGraph& graph, AstCCall* callp) + : EitherVertex{graph} + , m_callp{callp} {} + ~InlineCFuncsCallSiteVertex() override = default; + + // ACCESSORS + AstCCall* callp() const { return m_callp; } + bool noInline() const { return m_noInlineWyp; } + void setNoInline(const char* whyp) { + if (!m_noInlineWyp) m_noInlineWyp = whyp; + } + + // debug + FileLine* fileline() const override { return m_callp->fileline(); } + std::string dotColor() const override { return m_noInlineWyp ? "red" : "black"; } + std::string dotShape() const override { return "ellipse"; } + std::string name() const override VL_MT_STABLE { + std::string str = cvtToHex(m_callp); + if (m_noInlineWyp) str += "\nNoInline: "s + m_noInlineWyp; + return str; + } +}; + +void InlineCFuncsCallGraph::addEdge(InlineCFuncsFunctionVertex& caller, + InlineCFuncsCallSiteVertex& callsite) { + UASSERT_OBJ(callsite.inEmpty(), &callsite, "Call site should have at most one incoming edge"); + new V3GraphEdge{this, &caller, &callsite, 1, true}; // Can cut caller -> callsite +} +void InlineCFuncsCallGraph::addEdge(InlineCFuncsCallSiteVertex& callsite, + InlineCFuncsFunctionVertex& callee) { + UASSERT_OBJ(callsite.outEmpty(), &callsite, "Call site should have at most one outgoing edge"); + new V3GraphEdge{this, &callsite, &callee, 1, false}; +} + //###################################################################### class InlineCFuncsVisitor final : public VNVisitor { // NODE STATE - // AstCFunc::user1() -> vector of AstCCall* pointing to this function - // AstCFunc::user2() -> bool: true if checked for C statements - // AstCFunc::user3() -> bool: true if contains C statements (not inlineable) + // AstCFunc::user1p() -> InlineCFuncsFunctionVertex*, the function vertex + // AstCCall::user1p() -> InlineCFuncsCallSiteVertex*, the call site vertex + // AstVar::user2p() -> AstVar*, the cloned inlined local variable const VNUser1InUse m_user1InUse; - const VNUser2InUse m_user2InUse; - const VNUser3InUse m_user3InUse; - AstUser1Allocator> m_callSites; // STATE - VDouble0 m_statInlined; // Statistic tracking - const int m_threshold1; // Size threshold: always inline if size <= this - const int m_threshold2; // Product threshold: inline if size * calls <= this - AstCFunc* m_callerFuncp = nullptr; // Current caller function - // Tuples of (StmtExpr to replace, CFunc to inline from, caller func for vars) - std::vector> m_toInline; + InlineCFuncsCallGraph m_graph; // The call graph + VDouble0 m_statCallsInlined; // Number of calls inlined + VDouble0 m_statFuncsInlined; // Number of functions inlined at least once + VDouble0 m_statFuncsRemoved; // Number of fully-inlined functions removed + // Size threshold: always inline if size <= this + const size_t m_sizeThreshold = v3Global.opt.inlineCFuncs(); + // Product threshold: inline if size * calls <= this + const size_t m_prodThreshold = v3Global.opt.inlineCFuncsProduct(); + // Maximum size of caller to consider inlining into + const size_t m_maxSizeCFunc = []() -> size_t { + int maxCFunc = v3Global.opt.outputSplitCFuncs(); + int maxFile = v3Global.opt.outputSplit(); + if (maxCFunc <= 0) maxCFunc = std::numeric_limits::max(); + if (maxFile <= 0) maxFile = std::numeric_limits::max(); + return std::min(maxCFunc, maxFile); + }(); + const size_t m_maxSizeTrace = []() -> size_t { + int maxTrace = v3Global.opt.outputSplitCTrace(); + int maxFile = v3Global.opt.outputSplit(); + if (maxTrace <= 0) maxTrace = std::numeric_limits::max(); + if (maxFile <= 0) maxFile = std::numeric_limits::max(); + return std::min(maxTrace, maxFile); + }(); + InlineCFuncsFunctionVertex* m_cfuncVtxp = nullptr; // Vertex of currently iterated function + bool m_inExecGraph = false; // True while inside an AstExecGraph subtree // METHODS - - // Check if a function contains any $c() calls (user or internal) - // Results are cached in user2/user3 for efficiency - bool containsCStatements(AstCFunc* cfuncp) { - if (!cfuncp->user2()) { - // Not yet checked - run the check visitor - cfuncp->user2(true); // Mark as checked - const CFuncInlineCheckVisitor checker{cfuncp}; - cfuncp->user3(!checker.optimizable()); // Store result (true = contains C stmts) - } - return cfuncp->user3(); + InlineCFuncsFunctionVertex* getInlineCFuncsFunctionVertexp(AstCFunc* cfuncp) { + if (!cfuncp->user1p()) cfuncp->user1p(new InlineCFuncsFunctionVertex{m_graph, cfuncp}); + return cfuncp->user1u().to(); } - // Check if a function is eligible for inlining into caller - bool isInlineable(const AstCFunc* callerp, AstCFunc* cfuncp) { - // Must be in the same scope (same class) to access the same members - if (callerp->scopep() != cfuncp->scopep()) return false; + InlineCFuncsCallSiteVertex* getInlineCFuncsCallSiteVertexp(AstCCall* callp) { + if (!callp->user1p()) callp->user1p(new InlineCFuncsCallSiteVertex{m_graph, callp}); + return callp->user1u().to(); + } - // Check for $c() calls that might use 'this' - if (containsCStatements(cfuncp)) return false; + AstCLocalScope* inlineCall(AstCFunc* const callerp, // + AstCCall* const callp, // + AstCFunc* const calleep, // + const size_t seqNum) { + UINFO(6, "Inlining CFunc " << calleep->name() << " into " << callerp->name() + << " at call site " << callp); - // Check it's a void function (not a coroutine) - if (cfuncp->rtnTypeVoid() != "void") return false; + AstNodeStmt* const callSitep = VN_AS(callp->backp(), StmtExpr); + ++m_statCallsInlined; - // Don't inline functions marked dontCombine (e.g. trace, entryPoint) - if (cfuncp->dontCombine()) return false; - - // Don't inline entry point functions - if (cfuncp->entryPoint()) return false; - - // Must have statements to inline - if (!cfuncp->stmtsp()) return false; - - // Check size thresholds - const size_t funcSize = cfuncp->nodeCount(); - - // Always inline if small enough - if (funcSize <= static_cast(m_threshold1)) return true; - - // Also inline if size * call_count is reasonable - const size_t callCount = m_callSites(cfuncp).size(); - if (callCount > 0 && funcSize * callCount <= static_cast(m_threshold2)) { - return true; + // Callee might be empty, just delete the call + if (!calleep->stmtsp()) { + VL_DO_DANGLING(pushDeletep(callSitep->unlinkFrBack()), callSitep); + return nullptr; } - return false; + // Replace call site with a local scope + FileLine* const flp = callSitep->fileline(); + AstCLocalScope* const lscopep = new AstCLocalScope{flp, nullptr}; + callSitep->replaceWith(lscopep); + VL_DO_DANGLING(pushDeletep(callSitep), callSitep); + lscopep->addStmtsp(new AstComment{flp, "Inlined CFunc: " + calleep->name()}); + + // Although it's in a local scope, we still make names of cloned locals unique + const std::string varPrefix + = "__Vinline_" + std::to_string(seqNum) + "_" + calleep->name() + "_"; + + // AstVar::user2p() -> AstVar*, the cloned inlined local variable + const VNUser2InUse user2InUse; + + // Clone local variables, add them to the local scope + for (AstVar* varp = calleep->varsp(); varp; varp = VN_AS(varp->nextp(), Var)) { + AstVar* const newVarp = varp->cloneTree(false); + newVarp->name(varPrefix + varp->name()); + lscopep->addStmtsp(newVarp); + varp->user2p(newVarp); + } + + // Clone the function body + AstNode* const bodyp = calleep->stmtsp()->cloneTree(true); + lscopep->addStmtsp(bodyp); + + // Retarget local variable references to the cloned locals + // Rename locals defined in the body, TODO: there should be none after #6280 + // Reset vertex pointers on calls + bodyp->foreachAndNext([&](AstNode* nodep) { + if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) { + if (AstVar* const varp = VN_AS(refp->varp()->user2p(), Var)) refp->varp(varp); + } else if (AstVar* const varp = VN_CAST(nodep, Var)) { + varp->name(varPrefix + varp->name()); + } else if (AstCCall* const callp = VN_CAST(nodep, CCall)) { + callp->user1p(nullptr); + } + }); + + // Return the local scope + return lscopep; + } + + void doInlining() { + // Need to gather vertices as we are changing the graph + std::vector m_fVtxps; + for (V3GraphVertex& vtx : m_graph.vertices()) { + if (InlineCFuncsFunctionVertex* const fVtxp = vtx.cast()) { + m_fVtxps.emplace_back(fVtxp); + } + } + + // Iterate functions leaf to root + for (InlineCFuncsFunctionVertex* const calleeVtxp : vlstd::reverse_view(m_fVtxps)) { + // Should we inline this function? + if (calleeVtxp->noInline()) continue; // Told not to + + // Check size heuristics + const bool doIt = [&]() { + // Inline if small enough + if (calleeVtxp->size() <= m_sizeThreshold) return true; + // Inline if not too much bloat + const size_t nCalls = calleeVtxp->inEdges().size(); + if (nCalls * calleeVtxp->size() <= m_prodThreshold) return true; + // Otherwise don't inline + return false; + }(); + if (!doIt) continue; + + // Ok, attempt to inline call sites + size_t nInlined = 0; + for (const V3GraphEdge* const edgep : calleeVtxp->inEdges().unlinkable()) { + InlineCFuncsCallSiteVertex* const callVtxp + = edgep->fromp()->as(); + + AstCFunc* const calleep = calleeVtxp->cfuncp(); + AstCCall* const callp = callVtxp->callp(); + UINFO(6, "Consider inlining " << calleep->name() << " at call site " << callp); + // Should we inline this call site? + if (callVtxp->noInline()) continue; // Told not to + if (callVtxp->inEmpty()) continue; // Don't know where it's called from + + // Pick up the caller + UASSERT_OBJ(callVtxp->inSize1(), callVtxp->callp(), + "Expected exactly one input edge for call site"); + InlineCFuncsFunctionVertex* const callerVtxp + = callVtxp->inEdges().frontp()->fromp()->as(); + AstCFunc* const callerp = callerVtxp->cfuncp(); + + // Don't make a function bigger than the limit + const size_t limit = callerp->isTrace() ? m_maxSizeTrace : m_maxSizeCFunc; + if (callerVtxp->size() + calleeVtxp->size() > limit) continue; + + // Can't do it if it's in a different scope, self pointers differ + if (callerp->scopep() != calleep->scopep()) continue; + + // Inline it + if (!nInlined) ++m_statFuncsInlined; + AstNode* const inlinedp = inlineCall(callerp, callp, calleep, nInlined++); + + // Need to adjust the graph: + // 1. Delete inlined call site + VL_DO_DANGLING(callVtxp->unlinkDelete(&m_graph), callVtxp); + // 2. Add new inlined call sites - also increments size of caller + VL_RESTORER(m_cfuncVtxp); + m_cfuncVtxp = callerVtxp; + if (inlinedp) iterateChildrenConst(inlinedp); + } + } + } + + void removeUnusedFuncs() { + // Iterate root to leaves + for (V3GraphVertex* const vtxp : m_graph.vertices().unlinkable()) { + InlineCFuncsFunctionVertex* const fVtxp = vtxp->cast(); + if (!fVtxp) continue; + // Keep if still called + if (!fVtxp->inEmpty()) continue; + // Keep for other reasons + if (fVtxp->keep()) continue; + + AstCFunc* const funcp = fVtxp->cfuncp(); + UINFO(6, "Removing unused CFunc " << funcp); + ++m_statFuncsRemoved; + + // Unlink all call sites + for (const V3GraphEdge* const edgep : vtxp->outEdges().unlinkable()) { + edgep->top()->unlinkEdges(&m_graph); + } + // Delete function vertex + vtxp->unlinkDelete(&m_graph); + // Delete the function + VL_DO_DANGLING(pushDeletep(funcp->unlinkFrBack()), funcp); + } + + // Delete inlined/deleted call site vertices (for debugging only) + for (V3GraphVertex* const vtxp : m_graph.vertices().unlinkable()) { + InlineCFuncsCallSiteVertex* const cVtxp = vtxp->cast(); + if (!cVtxp) continue; + if (!cVtxp->inEmpty()) continue; + if (!cVtxp->outEmpty()) continue; + vtxp->unlinkDelete(&m_graph); + } } // VISITORS - void visit(AstCCall* nodep) override { - iterateChildren(nodep); - - AstCFunc* const cfuncp = nodep->funcp(); - if (!cfuncp) return; - - // Track call site for call counting - m_callSites(cfuncp).emplace_back(nodep); - } - void visit(AstCFunc* nodep) override { - VL_RESTORER(m_callerFuncp); - m_callerFuncp = nodep; - iterateChildren(nodep); + // Create the function vertex + InlineCFuncsFunctionVertex* const vtxp = getInlineCFuncsFunctionVertexp(nodep); + + // Check if the function itself is not inlineable + if (nodep->rtnTypeVoid() != "void") vtxp->setNoInline("Not void"); + if (nodep->dpiImportPrototype()) vtxp->setNoInline("DPI import prototype"); + if (nodep->recursive()) vtxp->setNoInline("Recursive"); + if (nodep->argsp()) vtxp->setNoInline("Has arguments"); + if (nodep->isVirtual()) vtxp->setNoInline("Virtual method"); + + // Check if the function should not be removed + if (nodep->entryPoint()) vtxp->setKeep("Entry point"); + if (nodep->dpiImportPrototype()) vtxp->setKeep("DPI import prototype"); + if (nodep->dpiExportDispatcher()) vtxp->setKeep("DPI export implementation"); + if (nodep->isVirtual()) vtxp->setKeep("Virtual method"); + + // Iterate children + VL_RESTORER(m_cfuncVtxp); + m_cfuncVtxp = vtxp; + iterateChildrenConst(nodep); } - void visit(AstNodeModule* nodep) override { - // Process per module for better cache behavior - m_toInline.clear(); + // Inlineable calls + void visit(AstCCall* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + AstCFunc* const calleep = nodep->funcp(); - // Phase 1: Collect call sites within this module - iterateChildren(nodep); + // Create the call site vertex + InlineCFuncsCallSiteVertex* const vtxp = getInlineCFuncsCallSiteVertexp(nodep); - // Phase 2: Determine which calls to inline - collectInlineCandidates(nodep); + // Check if the call site is not inlineable + if (!VN_IS(nodep->backp(), StmtExpr)) vtxp->setNoInline("Not in statement position"); + if (m_inExecGraph) vtxp->setNoInline("In ExecGraph"); + if (calleep->isVirtual()) vtxp->setNoInline("Virtual method"); - // Phase 3: Perform inlining for this module - doInlining(); + // Add caller/callee edges + if (m_cfuncVtxp) m_graph.addEdge(*m_cfuncVtxp, *vtxp); + m_graph.addEdge(*vtxp, *getInlineCFuncsFunctionVertexp(calleep)); + + // Iterate children + iterateChildrenConst(nodep); } - void visit(AstNode* nodep) override { iterateChildren(nodep); } + // Nodes that reference functions/calls + void visit(AstNetlist* nodep) override { + UASSERT_OBJ(!nodep->evalp(), nodep, "evalp should not be null at this stage"); + UASSERT_OBJ(!nodep->evalNbap(), nodep, "evalNbap should be null at this stage"); + iterateChildrenConst(nodep); + } - // Collect calls that should be inlined within this module - void collectInlineCandidates(AstNodeModule* modp) { - for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { - AstCFunc* const callerp = VN_CAST(stmtp, CFunc); - if (!callerp) continue; + void visit(AstNodeCCall* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + getInlineCFuncsFunctionVertexp(nodep->funcp())->setKeep("Called elsewhere"); + iterateChildrenConst(nodep); + } - callerp->foreach([&](AstCCall* callp) { - AstCFunc* const cfuncp = callp->funcp(); - if (!cfuncp) return; - if (!isInlineable(callerp, cfuncp)) return; + void visit(AstAddrOfCFunc* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + getInlineCFuncsFunctionVertexp(nodep->funcp())->setKeep("Referenced by AddressOfCFunc"); + iterateChildrenConst(nodep); + } - // Walk up to find the containing StmtExpr - AstNode* stmtNodep = callp; - while (stmtNodep && !VN_IS(stmtNodep, StmtExpr) && !VN_IS(stmtNodep, CFunc)) { - stmtNodep = stmtNodep->backp(); - } + // Nodes preventing inlining + void visit(AstTraceDecl* nodep) override { + // Referenced by TraceInc + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains TraceDecl"); - AstStmtExpr* const stmtExprp = VN_CAST(stmtNodep, StmtExpr); - if (!stmtExprp) return; - - m_toInline.emplace_back(stmtExprp, cfuncp, callerp); - }); + if (AstCCall* const callp = nodep->dtypeCallp()) { + getInlineCFuncsCallSiteVertexp(callp)->setNoInline("Referenced by TraceDecl"); } + iterateChildrenConst(nodep); + } + void visit(AstExecGraph* nodep) override { + // AstExecGraph is not cloneable, so can't inline the containing function + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains ExecGraph"); + // Also mark functions referenced in the dependency graph + for (const V3GraphVertex& vtx : nodep->depGraphp()->vertices()) { + getInlineCFuncsFunctionVertexp(vtx.as()->funcp()) + ->setKeep("MTask function"); + } + VL_RESTORER(m_inExecGraph); + m_inExecGraph = true; + iterateChildrenConst(nodep); + } + void visit(AstCStmt* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CStmt"); + iterateChildrenConst(nodep); + } + void visit(AstCExpr* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CExpr"); + iterateChildrenConst(nodep); + } + void visit(AstCStmtUser* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CStmtUser"); + iterateChildrenConst(nodep); + } + void visit(AstCExprUser* nodep) override { + // Can reference anything in text + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CExprUser"); + iterateChildrenConst(nodep); + } + void visit(AstCReturn* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CReturn"); + iterateChildrenConst(nodep); } - // Perform the actual inlining after iteration is complete - void doInlining() { - for (const auto& tuple : m_toInline) { - AstStmtExpr* const stmtExprp = std::get<0>(tuple); - AstCFunc* const cfuncp = std::get<1>(tuple); - AstCFunc* const callerp = std::get<2>(tuple); - - UINFO(6, "Inlining CFunc " << cfuncp->name() << " into " << callerp->name()); - ++m_statInlined; - - // Clone local variables with unique names to avoid collisions - std::map varMap; - for (AstVar* varp = cfuncp->varsp(); varp; varp = VN_AS(varp->nextp(), Var)) { - const string newName = "__Vinline_" + cfuncp->name() + "_" + varp->name(); - AstVar* const newVarp = varp->cloneTree(false); - newVarp->name(newName); - callerp->addVarsp(newVarp); - varMap[varp] = newVarp; - } - - // Clone the function body - AstNode* const bodyp = cfuncp->stmtsp()->cloneTree(true); - - // Retarget variable references to the cloned variables - // Must iterate all sibling statements, not just the first - if (!varMap.empty()) { - for (AstNode* stmtp = bodyp; stmtp; stmtp = stmtp->nextp()) { - stmtp->foreach([&](AstVarRef* refp) { - auto it = varMap.find(refp->varp()); - if (it != varMap.end()) refp->varp(it->second); - }); - } - } - - // Replace the statement with the inlined body - stmtExprp->addNextHere(bodyp); - VL_DO_DANGLING(stmtExprp->unlinkFrBack()->deleteTree(), stmtExprp); - } + // Base node + void visit(AstNode* nodep) override { + if (m_cfuncVtxp) m_cfuncVtxp->sizeInc(); + iterateChildrenConst(nodep); } public: // CONSTRUCTORS - explicit InlineCFuncsVisitor(const AstNetlist* nodep) - : m_threshold1{v3Global.opt.inlineCFuncs()} - , m_threshold2{v3Global.opt.inlineCFuncsProduct()} { - // Don't inline when profiling or tracing - if (v3Global.opt.profCFuncs() || v3Global.opt.trace()) return; - // Process modules one at a time for better cache behavior - iterateAndNextNull(nodep->modulesp()); + explicit InlineCFuncsVisitor(AstNetlist* nodep) { + // Phase 1: Build call graph + iterateConst(nodep); + // Make acyclic in case there is recursion + m_graph.acyclic(V3GraphEdge::followAlwaysTrue); + // Order vertices (any topological order is fine) + m_graph.order(); + if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-graph"); + // Phase 2: Inline calls + doInlining(); + if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-inlined"); + // Phase 3: Remove unused functions + removeUnusedFuncs(); + if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-kept"); } ~InlineCFuncsVisitor() override { - V3Stats::addStat("Optimizations, Inlined CFuncs", m_statInlined); + V3Stats::addStat("Optimizations, Inline CFuncs, calls inlined", m_statCallsInlined); + V3Stats::addStat("Optimizations, Inline CFuncs, functions inlined", m_statFuncsInlined); + V3Stats::addStat("Optimizations, Inline CFuncs, functions removed", m_statFuncsRemoved); } }; @@ -264,6 +497,8 @@ public: void V3InlineCFuncs::inlineAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); + // Don't inline when profiling per-function (it would lose granularity) + if (v3Global.opt.profCFuncs()) return; { InlineCFuncsVisitor{nodep}; } // Destruct before checking V3Global::dumpCheckGlobalTree("inlinecfuncs", 0, dumpTreeEitherLevel() >= 6); } diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 22f96ca7e..a23a408b2 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1497,6 +1497,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, m_fIcoChangeDetect.setTrueOrFalse(flag); }); DECL_OPTION("-finline", FOnOff, &m_fInline); + DECL_OPTION("-finline-cfuncs", FOnOff, &m_fInlineCFuncs); DECL_OPTION("-finline-funcs", FOnOff, &m_fInlineFuncs); DECL_OPTION("-finline-funcs-eager", FOnOff, &m_fInlineFuncsEager); DECL_OPTION("-flife", FOnOff, &m_fLife); @@ -2371,6 +2372,7 @@ void V3Options::optimize(int level) { m_fExpand = flag; m_fGate = flag; m_fInline = flag; + m_fInlineCFuncs = flag; m_fLife = flag; m_fLifePost = flag; m_fLocalize = flag; diff --git a/src/V3Options.h b/src/V3Options.h index 71a32c31b..6894f8312 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -415,6 +415,7 @@ private: // main switch: -fno-ico-change-detect: input change detection optimization VOptionBool m_fIcoChangeDetect{VOptionBool::OPT_DEFAULT_TRUE}; bool m_fInline; // main switch: -fno-inline: module inlining + bool m_fInlineCFuncs; // main switch: -fno-inline-cfuncs: inline small C functions bool m_fInlineFuncs = true; // main switch: -fno-inline-funcs: function inlining bool m_fInlineFuncsEager = true; // main switch: -fno-inline-funcs-eager: don't inline eagerly bool m_fLife; // main switch: -fno-life: variable lifetime @@ -753,6 +754,7 @@ public: bool fGate() const { return m_fGate; } VOptionBool fIcoChangeDetect() const { return m_fIcoChangeDetect; } bool fInline() const { return m_fInline; } + bool fInlineCFuncs() const { return m_fInlineCFuncs; } bool fInlineFuncs() const { return m_fInlineFuncs; } bool fInlineFuncsEager() const { return m_fInlineFuncsEager; } bool fLife() const { return m_fLife; } diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index 2a7bb37e3..8cee034fa 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -1094,6 +1094,7 @@ class TraceVisitor final : public VNVisitor { // Create the trace registration function m_regFuncp = new AstCFunc{m_topScopep->fileline(), "trace_register", m_topScopep}; m_regFuncp->argTypes(v3Global.opt.traceClassBase() + "* tracep"); + m_regFuncp->entryPoint(true); m_regFuncp->isTrace(true); m_regFuncp->slow(true); m_regFuncp->isStatic(false); diff --git a/src/V3TraceDecl.cpp b/src/V3TraceDecl.cpp index 820a314c7..1a1ad6b89 100644 --- a/src/V3TraceDecl.cpp +++ b/src/V3TraceDecl.cpp @@ -980,6 +980,7 @@ public: AstCFunc* rootFuncp = nullptr; if (!v3Global.opt.libCreate().empty()) { rootFuncp = newCFunc(flp, "trace_init_root"); + rootFuncp->entryPoint(true); for (size_t i = 0; i < m_topScopeRootFuncCount; ++i) { AstCCall* const callp = new AstCCall{flp, topScopeFuncps.at(i)}; callp->dtypeSetVoid(); @@ -1017,6 +1018,7 @@ public: // Set name of top level function AstCFunc* const topFuncp = m_topFuncps.front(); topFuncp->name("trace_init_top"); + topFuncp->entryPoint(true); if (rootFuncp && v3Global.opt.debugCheck()) checkCallsRecurse(rootFuncp); checkCalls(topFuncp); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index b37336a52..3d7da1ca3 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -582,8 +582,14 @@ static void process() { // Must be after all Sel/array index based optimizations V3Reloop::reloopAll(v3Global.rootp()); } + } - if (v3Global.opt.inlineCFuncs()) { + // These are no longer needed, remove references before CFunc inlining + v3Global.rootp()->evalp(nullptr); + v3Global.rootp()->evalNbap(nullptr); + + if (!v3Global.opt.lintOnly() && !v3Global.opt.serializeOnly()) { + if (v3Global.opt.fInlineCFuncs()) { // Inline small CFuncs to reduce function call overhead V3InlineCFuncs::inlineAll(v3Global.rootp()); } diff --git a/test_regress/t/t_display_merge.py b/test_regress/t/t_display_merge.py index 999f03507..205550676 100755 --- a/test_regress/t/t_display_merge.py +++ b/test_regress/t/t_display_merge.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator_st') -test.compile(verilator_flags2=["--stats", "--inline-cfuncs", "0"]) +test.compile(verilator_flags2=["--stats", "-fno-inline-cfuncs"]) test.execute(expect_filename=test.golden_filename) diff --git a/test_regress/t/t_flag_csplit_eval.py b/test_regress/t/t_flag_csplit_eval.py index 1b88de3bb..8311f5d9b 100755 --- a/test_regress/t/t_flag_csplit_eval.py +++ b/test_regress/t/t_flag_csplit_eval.py @@ -23,7 +23,7 @@ def check_evals(): test.error("Too few _eval functions found: " + str(got)) -test.compile(v_flags2=["--output-split 1 --output-split-cfuncs 20"], +test.compile(v_flags2=["--output-split 1 --output-split-cfuncs 20 -fno-inline-cfuncs"], verilator_make_gmake=False) # Slow to compile, so skip it) check_evals() diff --git a/test_regress/t/t_hier_block_chained.py b/test_regress/t/t_hier_block_chained.py index fb17db8e6..41e89faab 100755 --- a/test_regress/t/t_hier_block_chained.py +++ b/test_regress/t/t_hier_block_chained.py @@ -31,9 +31,9 @@ test.compile(v_flags2=[ if test.vltmt: test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule count\s+(\d+)', 3) + r'Optimizations, Thread schedule count\s+(\d+)', 4) test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule total tasks\s+(\d+)', 5) + r'Optimizations, Thread schedule total tasks\s+(\d+)', 6) test.execute() diff --git a/test_regress/t/t_hier_block_perf.py b/test_regress/t/t_hier_block_perf.py index d91e3475e..ea6d40c8b 100755 --- a/test_regress/t/t_hier_block_perf.py +++ b/test_regress/t/t_hier_block_perf.py @@ -35,9 +35,9 @@ test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "_ if test.vltmt: test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule count\s+(\d+)', 1) + r'Optimizations, Thread schedule count\s+(\d+)', 2) test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt", - r'Optimizations, Thread schedule total tasks\s+(\d+)', 2) + r'Optimizations, Thread schedule total tasks\s+(\d+)', 3) test.execute(all_run_flags=[ "+verilator+prof+exec+start+2", diff --git a/test_regress/t/t_inst_tree_inl0_pub1.py b/test_regress/t/t_inst_tree_inl0_pub1.py index 31d27a095..be831d2c5 100755 --- a/test_regress/t/t_inst_tree_inl0_pub1.py +++ b/test_regress/t/t_inst_tree_inl0_pub1.py @@ -14,8 +14,8 @@ test.top_filename = "t/t_inst_tree.v" default_vltmt_threads = test.get_default_vltmt_threads test.compile( - # Disable --inline-cfuncs so functions exist to be combined - verilator_flags2=['--stats', '--inline-cfuncs', '0', test.t_dir + "/" + test.name + ".vlt"], + # Disable CFunc inlining so functions exist to be combined + verilator_flags2=['--stats', '-fno-inline-cfuncs', test.t_dir + "/" + test.name + ".vlt"], # Force 3 threads even if we have fewer cores threads=(default_vltmt_threads if test.vltmt else 1)) diff --git a/test_regress/t/t_json_only_debugcheck.py b/test_regress/t/t_json_only_debugcheck.py index 3137b8632..aeef0a17b 100755 --- a/test_regress/t/t_json_only_debugcheck.py +++ b/test_regress/t/t_json_only_debugcheck.py @@ -16,7 +16,7 @@ test.top_filename = "t/t_enum_type_methods.v" out_filename = test.obj_dir + "/V" + test.name + ".tree.json" test.compile(verilator_flags2=[ - '--no-std', '--debug-check', '--no-json-edit-nums', '--flatten', '--inline-cfuncs', '0' + '--no-std', '--debug-check', '--no-json-edit-nums', '--flatten', '-fno-inline-cfuncs' ], verilator_make_gmake=False, make_top_shell=False, diff --git a/test_regress/t/t_opt_inline_cfuncs.py b/test_regress/t/t_opt_inline_cfuncs.py index 7981f48c3..1821f5ec5 100755 --- a/test_regress/t/t_opt_inline_cfuncs.py +++ b/test_regress/t/t_opt_inline_cfuncs.py @@ -9,17 +9,17 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('vlt_all') -# Use --output-split-cfuncs to create small functions that can be inlined -# Also test --inline-cfuncs-product option test.compile(verilator_flags2=[ - "--stats", "--binary", "--output-split-cfuncs", "1", "--inline-cfuncs-product", "200" + "--stats", "--binary", "--inline-cfuncs-product", "200", "--dumpi-V3InlineCFuncs", "9" ]) -# Verify inlining happened with exact count -test.file_grep(test.stats, r'Optimizations, Inlined CFuncs\s+(\d+)', 39) - test.execute() +if test.vlt: + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 7) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 7) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 7) + test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_args.py b/test_regress/t/t_opt_inline_cfuncs_args.py new file mode 100755 index 000000000..05fb055dd --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_args.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') + +test.compile(verilator_flags2=["--stats", "--binary"]) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]') + +test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_args.v b/test_regress/t/t_opt_inline_cfuncs_args.v new file mode 100644 index 000000000..d94ac40c4 --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_args.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire clk +); + + integer cyc = 0; + reg [31:0] acc; + + task automatic add_pair(input [31:0] a, input [31:0] b, inout [31:0] sum); + // verilator no_inline_task + sum = sum + a + b; + endtask + + always @(posedge clk) begin + cyc <= cyc + 1; + acc = 0; + add_pair(cyc[31:0], 32'd1, acc); // + cyc + 1 + add_pair(32'd1000, cyc[31:0], acc); // + 1000 + cyc + // acc = (cyc + 1) + (1000 + cyc) = 2*cyc + 1001 + if (cyc > 1) begin + if (acc !== (2 * cyc[31:0] + 32'd1001)) begin + $write("%%Error: cyc=%0d acc=%0d expected %0d\n", cyc, acc, 2 * cyc + 1001); + $stop; + end + end + if (cyc == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_inline_cfuncs_dup.py b/test_regress/t/t_opt_inline_cfuncs_dup.py new file mode 100755 index 000000000..05fb055dd --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_dup.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') + +test.compile(verilator_flags2=["--stats", "--binary"]) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]') + +test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_dup.v b/test_regress/t/t_opt_inline_cfuncs_dup.v new file mode 100644 index 000000000..2a39fa47e --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_dup.v @@ -0,0 +1,30 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire clk +); + + integer cyc = 0; + + task automatic tick(); + // verilator no_inline_task + automatic time t = $time; + $display("TICK: %0t", t); + endtask + + always @(posedge clk) begin + cyc <= cyc + 1; + tick(); + tick(); + tick(); + tick(); + if (cyc == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_opt_inline_cfuncs_off.py b/test_regress/t/t_opt_inline_cfuncs_off.py index 8b045e8ca..7e7361d8c 100755 --- a/test_regress/t/t_opt_inline_cfuncs_off.py +++ b/test_regress/t/t_opt_inline_cfuncs_off.py @@ -9,15 +9,13 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('vlt_all') test.top_filename = "t/t_opt_inline_cfuncs.v" -# Disable inlining with --inline-cfuncs 0 -test.compile(verilator_flags2=["--stats", "--binary", "--inline-cfuncs", "0"]) - -# Verify inlining did NOT happen (stat doesn't exist when pass is skipped) -test.file_grep_not(test.stats, r'Optimizations, Inlined CFuncs\s+[1-9]') +test.compile(verilator_flags2=["--stats", "--binary", "-fno-inline-cfuncs"]) test.execute() +test.file_grep_not(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+[1-9]') + test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_threshold.py b/test_regress/t/t_opt_inline_cfuncs_threshold.py index 677dee894..c7c6713b4 100755 --- a/test_regress/t/t_opt_inline_cfuncs_threshold.py +++ b/test_regress/t/t_opt_inline_cfuncs_threshold.py @@ -9,17 +9,16 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('vlt_all') -# Use thresholds that guarantee rejection to test the "return false" path in isInlineable() -# --inline-cfuncs 1: pass still runs (not skipped) -# --inline-cfuncs-product 0: guarantees all functions rejected (node_count * call_count > 0 always) test.compile(verilator_flags2=[ - "--stats", "--binary", "--inline-cfuncs", "1", "--inline-cfuncs-product", "0" + "--stats", "--binary", "--inline-cfuncs", "0", "--inline-cfuncs-product", "0" ]) -test.file_grep(test.stats, r'Optimizations, Inlined CFuncs\s+(\d+)', 0) - test.execute() +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 0) + test.passes() diff --git a/test_regress/t/t_opt_inline_cfuncs_trace.py b/test_regress/t/t_opt_inline_cfuncs_trace.py new file mode 100755 index 000000000..515aa4510 --- /dev/null +++ b/test_regress/t/t_opt_inline_cfuncs_trace.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') +test.top_filename = "t/t_opt_inline_cfuncs.v" + +test.compile(verilator_flags2=["--stats", "--binary", "--trace", "--inline-cfuncs-product", "200"]) + +if test.vlt: + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, calls inlined\s+(\d+)', 8) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions inlined\s+(\d+)', 7) + test.file_grep(test.stats, r'Optimizations, Inline CFuncs, functions removed\s+(\d+)', 9) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_protect_ids_key.out b/test_regress/t/t_protect_ids_key.out index 110f826c1..a9a6d264e 100644 --- a/test_regress/t/t_protect_ids_key.out +++ b/test_regress/t/t_protect_ids_key.out @@ -16,13 +16,11 @@ - - - + @@ -36,13 +34,10 @@ - - - diff --git a/test_regress/t/t_timing_debug1.py b/test_regress/t/t_timing_debug1.py index 94534429f..a8648a97a 100755 --- a/test_regress/t/t_timing_debug1.py +++ b/test_regress/t/t_timing_debug1.py @@ -13,7 +13,7 @@ test.scenarios('vlt_all') test.top_filename = "t/t_timing_sched.v" test.compile( - verilator_flags2=["--binary", "--timing", "--inline-cfuncs", "0", "-CFLAGS", "-DVL_DEBUG"]) + verilator_flags2=["--binary", "--timing", "-fno-inline-cfuncs", "-CFLAGS", "-DVL_DEBUG"]) test.execute(all_run_flags=["+verilator+debug"]) diff --git a/test_regress/t/t_timing_debug2.py b/test_regress/t/t_timing_debug2.py index 18a53b76f..e5bb01e3c 100755 --- a/test_regress/t/t_timing_debug2.py +++ b/test_regress/t/t_timing_debug2.py @@ -12,8 +12,8 @@ import vltest_bootstrap test.scenarios('vlt_all') test.top_filename = "t/t_timing_class.v" -# Disable --inline-cfuncs so debug traces show all function entries -test.compile(verilator_flags2=["--exe --main --timing --inline-cfuncs 0"]) +# Disable CFunc inlining so debug traces show all function entries +test.compile(verilator_flags2=["--exe --main --timing -fno-inline-cfuncs"]) test.execute(all_run_flags=["+verilator+debug"]) diff --git a/test_regress/t/t_timing_eval_act.out b/test_regress/t/t_timing_eval_act.out index 1d71dde4f..46417dac7 100644 --- a/test_regress/t/t_timing_eval_act.out +++ b/test_regress/t/t_timing_eval_act.out @@ -4,6 +4,7 @@ -V{t#,#}+ Vt_timing_eval_act___024root___eval_debug_assertions -V{t#,#}+ Initial -V{t#,#}+ Vt_timing_eval_act___024root___eval_static +-V{t#,#}+ Vt_timing_eval_act___024root___eval_static__TOP -V{t#,#}+ Vt_timing_eval_act___024root___timing_ready -V{t#,#}+ Vt_timing_eval_act___024root___eval_initial -V{t#,#}+ Vt_timing_eval_act___024root___eval_initial__TOP__Vtiming__0 diff --git a/test_regress/t/t_timing_eval_act.py b/test_regress/t/t_timing_eval_act.py index 787745ab3..46dae2791 100755 --- a/test_regress/t/t_timing_eval_act.py +++ b/test_regress/t/t_timing_eval_act.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('vlt') -test.compile(verilator_flags2=["--binary", "--runtime-debug"]) +test.compile(verilator_flags2=["--binary", "--runtime-debug", "-fno-inline-cfuncs"]) test.file_grep( test.obj_dir + "/" + test.vm_prefix + "___024root__0.cpp", r'void\s+' + test.vm_prefix + diff --git a/test_regress/t/t_verilated_debug.out b/test_regress/t/t_verilated_debug.out index 1d7867bab..7d98054ea 100644 --- a/test_regress/t/t_verilated_debug.out +++ b/test_regress/t/t_verilated_debug.out @@ -37,6 +37,7 @@ internalsDump: -V{t#,#}+ Vt_verilated_debug___024root___eval_phase__nba -V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act -V{t#,#}+ Vt_verilated_debug___024root___eval_nba +-V{t#,#}+ Vt_verilated_debug___024root___nba_sequent__TOP__0 *-* All Finished *-* -V{t#,#}+ Vt_verilated_debug___024root___trigger_clear__act -V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act diff --git a/test_regress/t/t_verilated_debug.py b/test_regress/t/t_verilated_debug.py index dbc82d1c1..e880f1866 100755 --- a/test_regress/t/t_verilated_debug.py +++ b/test_regress/t/t_verilated_debug.py @@ -12,7 +12,7 @@ import vltest_bootstrap test.scenarios('vlt_all') test.verilated_debug = True -test.compile(verilator_flags2=[]) +test.compile(verilator_flags2=['-fno-inline-cfuncs']) test.execute() From 78a2dc0738a655dd52f0cc6efbd4afc6a4a24ceb Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 21 Jun 2026 13:53:00 -0400 Subject: [PATCH 70/89] CI/Makefile: Auto format .rst files (#7816) Maintainers: Note this requires a `make venv` to get docstrfmt==2.2.0 --- Makefile.in | 16 +++++++++++++++- python-dev-requirements.txt | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index 6bcbdbaa5..80db572be 100644 --- a/Makefile.in +++ b/Makefile.in @@ -546,6 +546,13 @@ PY_FILES = \ # Python files, test_regress tests PY_TEST_FILES = test_regress/t/*.py +# reStructuredText Sphinx files +RST_FILES = \ + *.rst \ + */*.rst \ + ci/docker/*/*.rst \ + docs/guide/*.rst \ + # YAML files YAML_FILES = \ .*.yaml \ @@ -558,7 +565,7 @@ YAML_FILES = \ # Format format: - $(MAKE) -j 5 format-c format-cmake format-exec format-md format-py format-yaml + $(MAKE) -j 5 format-c format-cmake format-exec format-md format-py format-rst format-yaml BEAUTYSH = beautysh BEAUTYSH_FLAGS = --indent-size 2 @@ -609,6 +616,13 @@ format-py yapf: $(YAPF) --version $(YAPF) $(YAPF_FLAGS) $(PY_FILES) +DOCSTRFMT = docstrfmt +DOCSTRFMT_FLAGS = --line-length 75 --indent-width 3 --keep-blanks --ordered-marker "\#" --preserve-adornments --no-center-section-titles + +format-rst: + $(DOCSTRFMT) --version + $(DOCSTRFMT) $(DOCSTRFMT_FLAGS) $(RST_FILES) + YAMLFIX = YAMLFIX_WHITELINES=1 YAMLFIX_LINE_LENGTH=200 YAMLFIX_preserve_quotes=true yamlfix YAMLFIX_FLAGS = diff --git a/python-dev-requirements.txt b/python-dev-requirements.txt index d6bcba7b1..3e33332cc 100644 --- a/python-dev-requirements.txt +++ b/python-dev-requirements.txt @@ -19,6 +19,7 @@ breathe==4.36.0 compiledb==0.10.7 distro==1.9.0 +docstrfmt==2.2.0 gersemi==0.23.1 mbake==1.4.3 mdformat==1.0.0 From fbea10b42792794b0ffd2a8785b9abd881947f0c Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 21 Jun 2026 18:56:40 +0100 Subject: [PATCH 71/89] Internals: Fix dumping of fast-only statistics after emit --- src/Verilator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 3d7da1ca3..21fe83b5e 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -811,7 +811,7 @@ static bool verilate(const string& argString) { V3Os::filesystemFlushBuildDir(v3Global.opt.makeDir()); if (v3Global.opt.hierTop()) V3Os::filesystemFlushBuildDir(v3Global.opt.hierTopDataDir()); if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "WroteAll"); - if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "WroteFast"); + if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "WroteFast", true); // Final writing shouldn't throw warnings, but... V3Error::abortIfWarnings(); From eafe9636cf58a74a6a90b6127821b2f4fab8940f Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 21 Jun 2026 22:17:36 +0100 Subject: [PATCH 72/89] Internals: Dump Ast expression pattern statistics like Dfg (#7818) Remove the expression combination counts from the default stats file, and add a new `--dump-ast-patterns` option, which will dump new `*_ast_patterns_*.txt` files. These contain the expression combinations in a similar S-expression format as Dfg already produces with `--dump-dfg-stats`. These dumps are not produced by just `--stats` as they are fairly expensive to compute. Currently the new option will dump at two points: just before we change to C types via widthMin usage, and just before emit. --- bin/verilator | 1 + docs/guide/exe_verilator.rst | 4 + src/CMakeLists.txt | 3 +- src/Makefile_obj.in | 2 +- src/V3Ast.cpp | 150 +++++++++++++++ src/V3AstNodeExpr.h | 3 + src/V3AstPatterns.h | 34 ++++ src/V3Dfg.cpp | 3 + src/V3DfgDumpPatterns.cpp | 92 --------- src/V3DfgPasses.h | 3 +- src/V3Options.h | 3 + src/V3PatternStats.cpp | 129 +++++++++++++ src/V3Stats.cpp | 20 -- src/Verilator.cpp | 5 + test_regress/t/t_ast_dump_patterns.out | 251 +++++++++++++++++++++++++ test_regress/t/t_ast_dump_patterns.py | 19 ++ test_regress/t/t_ast_dump_patterns.v | 26 +++ 17 files changed, 633 insertions(+), 115 deletions(-) create mode 100644 src/V3AstPatterns.h delete mode 100644 src/V3DfgDumpPatterns.cpp create mode 100644 src/V3PatternStats.cpp create mode 100644 test_regress/t/t_ast_dump_patterns.out create mode 100755 test_regress/t/t_ast_dump_patterns.py create mode 100644 test_regress/t/t_ast_dump_patterns.v diff --git a/bin/verilator b/bin/verilator index 10176f45c..57fd60507 100755 --- a/bin/verilator +++ b/bin/verilator @@ -438,6 +438,7 @@ detailed descriptions of these arguments. --diagnostics-sarif-output Set SARIF diagnostics output file --dpi-hdr-only Only produce the DPI header file --dump- Enable dumping everything in source file + --dump-ast-patterns Enable dumping Ast pattern statistics --dump-defines Show preprocessor defines with -E --dump-dfg Enable dumping DfgGraphs to .dot files --dump-dfg-patterns Enable dumping Dfg pattern statistics diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index c35cd3e57..952500400 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -491,6 +491,10 @@ Summary: Rarely needed - for developer use. Enable all dumping in the given source file at level 3. +.. option:: --dump-ast-patterns + + Rarely needed. Enable dumping AstNodeExpr pattern statistics. + .. option:: --dump-defines With :vlopt:`-E`, suppress normal output, and instead print a list of diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 469456b45..90a0d3ab4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,6 +51,7 @@ set(HEADERS V3AstNodeExpr.h V3AstNodeOther.h V3AstNodeStmt.h + V3AstPatterns.h V3AstUserAllocator.h V3Begin.h V3Branch.h @@ -250,7 +251,6 @@ set(COMMON_SOURCES V3DfgDataType.cpp V3DfgDecomposition.cpp V3DfgDfgToAst.cpp - V3DfgDumpPatterns.cpp V3DfgOptimizer.cpp V3DfgPasses.cpp V3DfgPeephole.cpp @@ -327,6 +327,7 @@ set(COMMON_SOURCES V3ParseGrammar.cpp V3ParseImp.cpp V3ParseLex.cpp + V3PatternStats.cpp V3PreProc.cpp V3PreShell.cpp V3Premit.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index eb3b1d50a..f5a728063 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -264,7 +264,6 @@ RAW_OBJS_PCH_ASTNOMT = \ V3DfgDataType.o \ V3DfgDecomposition.o \ V3DfgDfgToAst.o \ - V3DfgDumpPatterns.o \ V3DfgOptimizer.o \ V3DfgPasses.o \ V3DfgPeephole.o \ @@ -314,6 +313,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3OrderProcessDomains.o \ V3OrderSerial.o \ V3Param.o \ + V3PatternStats.o \ V3Premit.o \ V3ProtectLib.o \ V3RandSequence.o \ diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 9fe36df38..8c9d6198e 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -1745,6 +1745,156 @@ AstNodeDType* AstNode::getCommonClassTypep(AstNode* node1p, AstNode* node2p) { return nullptr; } +//###################################################################### +// Renders the canonical pattern S-expression for a single AstNode + +class VNPatternString final { + std::ostream& m_os; + + std::map m_internedConsts; // Interned constants + std::map m_internedWordWidths; // Interned widths + std::map m_internedWideWidths; // Interned widths + + // Whether to dump the widhtMin as well + const bool m_dumpWidthMin = v3Global.widthMinUsage() != VWidthMinUsage::MATCHES_WIDTH; + + static std::string toLetters(size_t value, bool lowerCase = false) { + const char base = lowerCase ? 'a' : 'A'; + std::string s; + do { s += static_cast(base + value % 26); } while (value /= 26); + return s; + } + + const std::string& internConst(const AstConst& nodep) { + const auto pair = m_internedConsts.emplace(nodep.num().ascii(false), ""); + if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1); + return pair.first->second; + } + + const std::string& internWordWidth(int value) { + const auto pair = m_internedWordWidths.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true); + return pair.first->second; + } + + const std::string& internWideWidth(int value) { + const auto pair = m_internedWideWidths.emplace(value, ""); + if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1); + return pair.first->second; + } + + // True if the node has no operands (e.g. a VarRef or Const) + static bool isLeaf(const AstNode* nodep) { + const VNTypeInfo& typeInfo = VNType::typeInfo(nodep->type()); + for (const VNTypeInfo::OpEn& opType : typeInfo.m_opType) { + if (opType != VNTypeInfo::OP_UNUSED) return false; + } + return true; + } + + // Render operand, return true if not unused + void renderOperand(VNTypeInfo::OpEn opType, const AstNode* const opp, uint32_t depth) { + switch (opType) { + case VNTypeInfo::OP_UNUSED: // + break; + case VNTypeInfo::OP_USED: // + m_os << ' '; + render(opp, depth); + break; + case VNTypeInfo::OP_LIST: + m_os << " ["; + for (const AstNode* nodep = opp; nodep; nodep = nodep->nextp()) { + if (nodep != opp) m_os << ", "; + render(opp, depth); + } + m_os << ']'; + break; + case VNTypeInfo::OP_OPTIONAL: + if (opp) { + m_os << " "; + render(opp, depth); + } else { + m_os << " nil"; + } + break; + } + } + + // Render the node into the stream. + void render(const AstNode* nodep, uint32_t depth) { + if (const AstConst* const constp = VN_CAST(nodep, Const)) { + // Base case 1: constant + if (constp->isZero()) { + m_os << "(CONST ZERO)"; + } else if (constp->isEqAllOnes()) { + m_os << "(CONST ONES)"; + } else { + m_os << "(CONST #" << internConst(*constp) << ')'; + } + } else if (isLeaf(nodep)) { + // Base case 2: expression with no operands (e.g. a variable reference) + m_os << '(' << nodep->typeName() << ')'; + } else if (depth == 0) { + // Base case 3: deep expression + m_os << '_'; + } else { + // Recursively print an S-expression for the expression + m_os << '('; + // Name + m_os << nodep->typeName(); + // Operands + const VNTypeInfo& typeInfo = VNType::typeInfo(nodep->type()); + renderOperand(typeInfo.m_opType[0], nodep->op1p(), depth - 1); + renderOperand(typeInfo.m_opType[1], nodep->op2p(), depth - 1); + renderOperand(typeInfo.m_opType[2], nodep->op3p(), depth - 1); + renderOperand(typeInfo.m_opType[3], nodep->op4p(), depth - 1); + // S-expression end + m_os << ')'; + } + + // Annotate type + m_os << ':'; + const AstNodeDType* const dtypep = nodep->dtypep() ? nodep->dtypep()->skipRefp() : nullptr; + if (!dtypep) { + m_os << '?'; + } else if (dtypep->isCompound() || VN_IS(dtypep, UnpackArrayDType)) { + dtypep->dumpSmall(m_os); + } else { + const int width = nodep->width(); + if (width == 1) { + m_os << '1'; + } else if (width <= VL_QUADSIZE) { + m_os << internWordWidth(width); + } else { + m_os << internWideWidth(width); + } + if (m_dumpWidthMin) { + m_os << '/'; + const int widthMin = nodep->widthMin(); + if (widthMin == 1) { + m_os << '1'; + } else if (widthMin <= VL_QUADSIZE) { + m_os << internWordWidth(widthMin); + } else { + m_os << internWideWidth(widthMin); + } + } + } + } + +public: + VNPatternString(std::ostream& os, const AstNode* nodep, uint32_t depth) + : m_os{os} { + render(nodep, depth); + } +}; + +std::string AstNodeExpr::patternString(uint32_t depth) const { + std::ostringstream oss; + VNPatternString{oss, this, depth}; + return oss.str(); +} + //###################################################################### // VNDeleter diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 91d689227..85c5b0892 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -73,6 +73,9 @@ public: // Returns an error message if widthMin() is not correct otherwise returns nullptr like // broken() virtual const char* widthMismatch() const VL_MT_STABLE { return nullptr; } + + // S-expression inspired dump of node and operands for debugging + std::string patternString(uint32_t depth = 0) const; }; class AstNodeBiop VL_NOT_FINAL : public AstNodeExpr { // Binary expression diff --git a/src/V3AstPatterns.h b/src/V3AstPatterns.h new file mode 100644 index 000000000..451a518d4 --- /dev/null +++ b/src/V3AstPatterns.h @@ -0,0 +1,34 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Dump AstNodeExpr patterns for analysis +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3ASTPATTERNS_H_ +#define VERILATOR_V3ASTPATTERNS_H_ + +#include "config_build.h" +#include "verilatedos.h" + +class AstNetlist; + +//============================================================================ + +class V3AstPatterns final { +public: + // Accumulate the patterns in the netlist, and dump the collected + // statistics to '__ast_patterns_.txt'. + static void dumpAll(const AstNetlist* nodep, const std::string& suffix) VL_MT_DISABLED; +}; + +#endif // Guard diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index cb18ba704..fc6be54ce 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -959,6 +959,9 @@ void DfgVertex::unlinkDelete(DfgGraph& dfg) { delete this; } +//###################################################################### +// Renders the canonical pattern S-expression for a single DfgVertex + class DfgPatternString final { std::ostream& m_os; diff --git a/src/V3DfgDumpPatterns.cpp b/src/V3DfgDumpPatterns.cpp deleted file mode 100644 index 18611f180..000000000 --- a/src/V3DfgDumpPatterns.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Implementations of simple passes over DfgGraph -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of either the GNU Lesser General Public License Version 3 -// or the Perl Artistic License Version 2.0. -// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#ifndef VERILATOR_V3DFGPATTERNSTATS_H_ -#define VERILATOR_V3DFGPATTERNSTATS_H_ - -#include "V3Dfg.h" -#include "V3DfgPasses.h" -#include "V3File.h" - -#include - -class V3DfgPatternStats final { - static constexpr uint32_t MIN_PATTERN_DEPTH = 1; - static constexpr uint32_t MAX_PATTERN_DEPTH = 4; - - // Maps from pattern to the number of times it appears, for each pattern depth - std::vector> m_patterCounts{MAX_PATTERN_DEPTH + 1}; - - void dump(std::ostream& os) { - using Line = std::pair; - for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { - os << "DFG patterns with depth " << i << '\n'; - - // Pick up pattern accumulators with given depth - auto& patternCounts = m_patterCounts[i]; - - // Remove patterns also present at shallower depths - for (uint32_t j = MIN_PATTERN_DEPTH; j < i; ++j) { - for (const auto& pair : m_patterCounts[j]) patternCounts.erase(pair.first); - } - - // Sort patterns, first by descending frequency, then lexically - std::vector lines; - lines.reserve(patternCounts.size()); - for (const auto& pair : patternCounts) lines.emplace_back(pair); - std::sort(lines.begin(), lines.end(), [](const Line& a, const Line& b) { - if (a.second != b.second) return a.second > b.second; - return a.first < b.first; - }); - - // Print each pattern - for (const auto& line : lines) { - os << ' ' << std::setw(12) << std::right << line.second; - os << ' ' << std::left << line.first << '\n'; - } - - // Trailing new-line to separate sections - os << '\n'; - } - } - -public: - V3DfgPatternStats() = default; - ~V3DfgPatternStats() { - // File to dump to - const std::string filename - = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + "__dfg_patterns.txt"; - // Open, write, close - const std::unique_ptr ofp{V3File::new_ofstream(filename)}; - if (ofp->fail()) v3fatal("Can't write file: " << filename); - dump(*ofp); - } - - void accumulate(const DfgGraph& dfg) { - dfg.forEachVertex([&](const DfgVertex& vtx) { - for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { - m_patterCounts[i][vtx.patternString(i)] += 1; - } - }); - } -}; - -void V3DfgPasses::dumpPatterns(const std::vector>& graphs) { - V3DfgPatternStats patternStats; - for (auto& cp : graphs) patternStats.accumulate(*cp); -} - -#endif diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index 2984c6bd7..d0d8e62d8 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -67,7 +67,8 @@ void regularize(DfgGraph&, V3DfgRegularizeContext&) VL_MT_DISABLED; // Convert DfgGraph back into Ast, and insert converted graph back into the Ast. void dfgToAst(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; // Dump the patterns in the given graphs -void dumpPatterns(const std::vector>&) VL_MT_DISABLED; +void dumpPatterns(const std::vector>&, + const std::string& suffix = "") VL_MT_DISABLED; //=========================================================================== // Intermediate/internal operations diff --git a/src/V3Options.h b/src/V3Options.h index 6894f8312..f3dd59863 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -552,6 +552,9 @@ public: bool decorationNodes() const VL_MT_SAFE { return m_decorationNodes; } bool diagnosticsSarif() const VL_MT_SAFE { return m_diagnosticsSarif; } bool dpiHdrOnly() const { return m_dpiHdrOnly; } + bool dumpAstPatterns() const { + return m_dumpLevel.count("ast-patterns") && m_dumpLevel.at("ast-patterns"); + } bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); } bool dumpDfgPatterns() const { return m_dumpLevel.count("dfg-patterns") && m_dumpLevel.at("dfg-patterns"); diff --git a/src/V3PatternStats.cpp b/src/V3PatternStats.cpp new file mode 100644 index 000000000..8d4d5a63d --- /dev/null +++ b/src/V3PatternStats.cpp @@ -0,0 +1,129 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Dump data structure pattern frequencies for analysis +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "V3PchAstNoMT.h" + +#include "V3Ast.h" +#include "V3AstPatterns.h" +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3File.h" + +#include +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +//============================================================================ +// Accumulates and dumps the pattern statistics + +class V3PatternStats VL_NOT_FINAL { +public: + static constexpr uint32_t MIN_PATTERN_DEPTH = 1; + static constexpr uint32_t MAX_PATTERN_DEPTH = 4; + +private: + const std::string m_what; // Description of what is being dumped + // Maps from pattern to the number of times it appears, for each pattern depth + std::vector> m_patternCounts{MAX_PATTERN_DEPTH + 1}; + + void dump(std::ostream& os) { + using Line = std::pair; + for (uint32_t i = MIN_PATTERN_DEPTH; i <= MAX_PATTERN_DEPTH; ++i) { + os << m_what << " patterns with depth " << i << '\n'; + + // Pick up pattern accumulators with given depth + auto& patternCounts = m_patternCounts[i]; + + // Remove patterns also present at shallower depths + for (uint32_t j = MIN_PATTERN_DEPTH; j < i; ++j) { + for (const auto& pair : m_patternCounts[j]) patternCounts.erase(pair.first); + } + + // Sort patterns, first by descending frequency, then lexically + std::vector lines; + lines.reserve(patternCounts.size()); + for (const auto& pair : patternCounts) lines.emplace_back(pair); + std::sort(lines.begin(), lines.end(), [](const Line& a, const Line& b) { + if (a.second != b.second) return a.second > b.second; + return a.first < b.first; + }); + + // Print each pattern + for (const auto& line : lines) { + os << ' ' << std::setw(12) << std::right << line.second; + os << ' ' << std::left << line.first << '\n'; + } + + // Trailing new-line to separate sections + os << '\n'; + } + } + +public: + V3PatternStats(const std::string& what) + : m_what{what} {} + ~V3PatternStats() = default; + + void accumulate(const std::string& pattern, uint32_t depth) { + m_patternCounts[depth][pattern] += 1; + } + + void dumpToFile(const std::string& filename) { + // Open, write, close + const std::unique_ptr ofp{V3File::new_ofstream(filename)}; + if (ofp->fail()) v3fatal("Can't write file: " << filename); + dump(*ofp); + } +}; + +//============================================================================ +// V3AstPatterns top level + +void V3AstPatterns::dumpAll(const AstNetlist* nodep, const std::string& suffix) { + UINFO(2, __FUNCTION__ << ":"); + V3PatternStats patternStats{"AST"}; + nodep->foreach([&](const AstNodeExpr* exprp) { + for (uint32_t i = V3PatternStats::MIN_PATTERN_DEPTH; + i <= V3PatternStats::MAX_PATTERN_DEPTH; ++i) { + patternStats.accumulate(exprp->patternString(i), i); + } + }); + const std::string fileName = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + + "__ast_patterns_" + suffix + ".txt"; + patternStats.dumpToFile(fileName); + V3Global::dumpCheckGlobalTree("astpatterns", 0, false, false); +} + +//============================================================================ +// V3DfgPasses top level + +void V3DfgPasses::dumpPatterns(const std::vector>& graphs, + const std::string& suffix) { + V3PatternStats patternStats{"DFG"}; + for (auto& cp : graphs) { + cp->forEachVertex([&](const DfgVertex& vtx) { + for (uint32_t i = V3PatternStats::MIN_PATTERN_DEPTH; + i <= V3PatternStats::MAX_PATTERN_DEPTH; ++i) { + patternStats.accumulate(vtx.patternString(i), i); + } + }); + } + const std::string fileName = v3Global.opt.hierTopDataDir() + "/" + v3Global.opt.prefix() + + "__dfg_patterns" + suffix + ".txt"; + patternStats.dumpToFile(fileName); +} diff --git a/src/V3Stats.cpp b/src/V3Stats.cpp index e1cd949ae..8abe9254b 100644 --- a/src/V3Stats.cpp +++ b/src/V3Stats.cpp @@ -35,15 +35,12 @@ class StatsVisitor final : public VNVisitorConst { struct Counters final { // Nodes of given type std::array m_statTypeCount{}; - // Nodes of given type with given type immediate child - std::array, VNType::NUM_TYPES()> m_statAbove{}; // Prediction of given type std::array m_statPred{}; }; // STATE const bool m_fastOnly; // When true, consider only fast functions - const AstNodeExpr* m_parentExprp = nullptr; // Parent expression Counters m_counters; // The actual counts we will display Counters m_dumpster; // Alternate buffer to make discarding parts of the tree easier Counters* m_accump; // The currently active accumulator @@ -75,14 +72,6 @@ class StatsVisitor final : public VNVisitorConst { countThenIterateChildren(nodep); } - void visit(AstNodeExpr* nodep) override { - // Count expression combinations - if (m_parentExprp) ++m_accump->m_statAbove[m_parentExprp->type()][nodep->type()]; - VL_RESTORER(m_parentExprp); - m_parentExprp = nodep; - countThenIterateChildren(nodep); - } - void visit(AstNodeIf* nodep) override { // Track prediction ++m_accump->m_statPred[nodep->branchPred()]; @@ -148,15 +137,6 @@ public: } } - // Expression combinations - for (size_t t1 = 0; t1 < VNType::NUM_TYPES(); ++t1) { - for (size_t t2 = 0; t2 < VNType::NUM_TYPES(); ++t2) { - if (const uint64_t c = m_counters.m_statAbove[t1][t2]) { - addStat("Expr combination, " + typeName(t1) + " over " + typeName(t2), c); - } - } - } - // Branch predictions for (int t = 0; t < VBranchPred::_ENUM_END; ++t) { if (const uint64_t c = m_counters.m_statPred[t]) { diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 21fe83b5e..b1f0a9af0 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -22,6 +22,7 @@ #include "V3AssertNfa.h" #include "V3AssertPre.h" #include "V3Ast.h" +#include "V3AstPatterns.h" #include "V3Begin.h" #include "V3Branch.h" #include "V3Broken.h" @@ -541,6 +542,8 @@ static void process() { V3Const::constifyAll(v3Global.rootp()); V3Dead::deadifyAll(v3Global.rootp()); + if (v3Global.opt.dumpAstPatterns()) V3AstPatterns::dumpAll(v3Global.rootp(), "prec"); + // Here down, widthMin() is the Verilog width, and width() is the C++ width // Bits between widthMin() and width() are irrelevant, but may be non-zero. v3Global.widthMinUsage(VWidthMinUsage::VERILOG_WIDTH); @@ -635,6 +638,8 @@ static void process() { } } + if (v3Global.opt.dumpAstPatterns()) V3AstPatterns::dumpAll(v3Global.rootp(), "emit"); + // Output the text if (!v3Global.opt.lintOnly() && !v3Global.opt.serializeOnly() && !v3Global.opt.dpiHdrOnly()) { diff --git a/test_regress/t/t_ast_dump_patterns.out b/test_regress/t/t_ast_dump_patterns.out new file mode 100644 index 000000000..4d2ea4bb2 --- /dev/null +++ b/test_regress/t/t_ast_dump_patterns.out @@ -0,0 +1,251 @@ +AST patterns with depth 1 + 126 (CONST #A):a/a + 54 (VARREF):a/b + 36 (CCAST (VARREF):a/b):a/b + 34 (AND (CONST #A):a/a _:a/1):a/1 + 29 (VARREF):a/a + 23 (CONST ZERO):a/a + 21 (VARREF):(w64)u[1:0] + 20 (CCAST _:a/1):a/1 + 18 (AND (CONST #A):a/a _:a/a):a/a + 18 (SHIFTR _:a/b (CONST #A):a/a):a/1 + 17 (VARREF):a/1 + 15 (SHIFTL _:a/1 (CONST #A):a/a):a/a + 14 (NEGATE _:a/1):a/a + 12 (AND (CONST #A):a/a _:a/b):a/b + 12 (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b + 12 (NOT _:a/b):a/b + 12 (VARREF):(w64)u[0:0] + 11 (OR _:a/a _:a/b):a/c + 9 (OR _:a/a _:a/1):a/b + 9 (VARREF):(G/str) + 8 (CCAST _:a/a):a/a + 8 (CCAST _:a/a):b/b + 8 (CRESET):a/a + 8 (NOT _:a/a):a/a + 8 (REDXOR _:a/b):a/1 + 7 (SHIFTL _:a/b (CONST #A):a/a):a/a + 6 (AND _:a/b _:a/b):a/b + 6 (REDXOR _:a/a):b/1 + 5 (CCAST _:a/a):b/1 + 5 (OR _:a/a _:a/a):a/a + 4 (ADD _:a/a (VARREF):a/a):a/a + 4 (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):a/a):b/b + 4 (CCAST (CONST #A):a/a):a/a + 4 (CCAST (VARREF):a/1):a/1 + 4 (CCAST _:a/1):b/1 + 4 (CONST #A):(G/str) + 4 (CONST #A):a/1 + 4 (NEGATE _:a/1):a/b + 4 (SHIFTL _:a/a (CONST #A):b/b):a/a + 3 (AND (VARREF):a/a (CONST #A):b/b):a/a + 3 (CONST ZERO):a/1 + 3 (CRESET):(w64)u[0:0] + 3 (CRESET):(w64)u[1:0] + 3 (NOT _:a/1):a/1 + 3 (OR (CONST #A):a/a _:a/a):a/a + 2 (CCALL [(VARREF):(w64)u[0:0], (VARREF):(w64)u[0:0]]):a/a + 2 (CCALL [(VARREF):(w64)u[0:0]]):a/1 + 2 (CCALL [(VARREF):(w64)u[1:0], (VARREF):(w64)u[1:0]]):a/a + 2 (CCALL [(VARREF):(w64)u[1:0]]):a/1 + 2 (CCALL []):a/1 + 2 (CRESET):(G/str) + 2 (GT (CONST #A):a/a (VARREF):a/a):a/1 + 2 (LT (CONST #A):a/a (VARREF):a/a):a/1 + 2 (NEQ _:a/b _:a/b):a/1 + 2 (OR _:a/a _:a/a):a/b + 2 (REDXOR (VARREF):a/b):a/1 + 2 (REDXOR _:a/a):a/1 + 2 (REDXOR _:a/b):c/1 + 2 (SHIFTR _:a/a (CONST #A):b/b):a/a + 1 (ARRAYSEL (VARREF):(w64)u[0:0] (VARREF):a/a):b/b + 1 (ARRAYSEL (VARREF):(w64)u[1:0] (CONST #A):a/a):b/b + 1 (ARRAYSEL (VARREF):(w64)u[1:0] (VARREF):a/a):b/b + 1 (CCAST _:a/1):b/b + 1 (CCAST _:a/b):a/b + 1 (CCAST _:a/b):c/c + 1 (CRESET):1/1 + 1 (NEQ _:a/b _:a/b):a/c + 1 (VARREF):1/1 + +AST patterns with depth 2 + 18 (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1 + 18 (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1 + 18 (SHIFTR (CCAST (VARREF):a/b):a/b (CONST #A):a/a):a/1 + 14 (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a + 12 (NOT (CCAST (VARREF):a/b):a/b):a/b + 10 (NEGATE (CCAST _:a/1):a/1):a/a + 8 (CCAST (CCAST _:a/a):a/a):b/b + 8 (CCAST (NOT _:a/a):a/a):a/a + 8 (NOT (NEGATE _:a/1):a/a):a/a + 8 (OR (AND (CONST #A):a/a _:a/a):a/a (AND (CONST #B):a/a _:a/1):a/1):a/b + 8 (REDXOR (AND (CONST #A):a/a _:a/b):a/b):a/1 + 6 (AND (CONST #A):a/a (AND _:a/b _:a/b):a/b):a/b + 6 (AND (NOT _:a/b):a/b (NOT _:a/b):a/b):a/b + 6 (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c + 6 (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a + 5 (AND (CONST #A):a/a (CCAST _:b/b):a/1):a/1 + 4 (ADD (CCAST (CONST #A):a/a):a/a (VARREF):a/a):a/a + 4 (AND (CONST #A):a/a (NEGATE _:a/1):a/b):a/b + 4 (AND (CONST #A):a/a (REDXOR _:a/b):a/1):a/1 + 4 (AND (CONST #A):a/a (REDXOR _:b/b):a/1):a/1 + 4 (CCAST (CCAST _:a/1):a/1):b/1 + 4 (NEGATE (CCAST _:a/1):a/1):a/b + 4 (NEGATE (CCAST _:a/1):b/1):b/b + 4 (REDXOR (NEGATE _:a/1):a/a):b/1 + 4 (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b + 4 (SHIFTL (REDXOR _:a/b):a/1 (CONST #A):a/a):a/a + 3 (AND (CONST #A):a/a (NOT _:a/1):a/1):a/1 + 3 (OR (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):b/b):a/a):a/a + 2 (AND (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):b/b):a/a):a/a + 2 (AND (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):b/b):a/a):a/a + 2 (AND (CONST #A):a/a (OR _:a/a _:a/a):a/b):a/b + 2 (CCAST (SHIFTR _:a/a (CONST #A):b/b):a/a):b/1 + 2 (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1 + 2 (NOT (CCAST _:a/1):a/1):a/1 + 2 (OR (OR _:a/a _:a/a):a/a (OR _:a/a _:a/b):a/c):a/d + 2 (OR (SHIFTL _:a/a (CONST #A):b/b):a/a (CCAST _:b/b):a/a):a/a + 2 (OR (SHIFTL _:a/a (CONST #A):b/b):a/a (CCAST _:b/b):a/a):a/c + 2 (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/c):a/d + 2 (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (SHIFTL _:a/b (CONST #B):a/a):a/a):a/a + 2 (REDXOR (AND (CONST #A):a/a _:a/b):a/b):c/1 + 2 (REDXOR (NEGATE _:a/1):a/a):a/1 + 2 (REDXOR (OR _:a/a _:a/a):a/a):b/1 + 2 (SHIFTL (CCAST (VARREF):a/1):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR (VARREF):a/b):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR _:a/a):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR _:a/a):b/1 (CONST #A):b/b):b/b + 2 (SHIFTL (REDXOR _:a/b):c/1 (CONST #A):c/c):c/c + 2 (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #A):a/a):b/b + 1 (CCAST (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):a/a):b/b):a/1 + 1 (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST #A):a/a):b/b):a/1 + 1 (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b):a/1 + 1 (CCAST (CCALL [(VARREF):(w64)u[0:0]]):a/1):a/1 + 1 (CCAST (CCALL [(VARREF):(w64)u[1:0]]):a/1):a/1 + 1 (CCAST (CCAST (VARREF):a/1):a/1):b/b + 1 (CCAST (CCAST _:a/b):a/b):c/c + 1 (CCAST (OR _:a/a _:a/b):a/c):a/c + 1 (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/c + 1 (NOT (CCAST (VARREF):a/1):a/1):a/1 + 1 (OR (AND (CONST #A):a/a _:a/a):a/a (CCAST _:b/1):a/a):a/a + 1 (OR (SHIFTL _:a/1 (CONST #A):a/a):a/a (NEQ _:a/b _:a/b):a/1):a/c + 1 (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/b):a/c + 1 (SHIFTL (NEQ _:a/b _:a/b):a/1 (CONST #A):a/a):a/a + 1 (SHIFTL (NEQ _:a/b _:a/b):a/c (CONST #A):a/a):a/a + +AST patterns with depth 3 + 18 (AND (CONST #A):a/a (SHIFTR (CCAST (VARREF):a/b):a/b (CONST #A):a/a):a/1):a/1 + 18 (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1 + 10 (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/a + 8 (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b + 8 (CCAST (NOT (NEGATE _:a/1):a/a):a/a):a/a + 8 (NOT (NEGATE (CCAST _:a/1):a/1):a/a):a/a + 6 (AND (CONST #A):a/a (AND (NOT _:a/b):a/b (NOT _:a/b):a/b):a/b):a/b + 6 (AND (NOT (CCAST (VARREF):a/b):a/b):a/b (NOT (CCAST (VARREF):a/b):a/b):a/b):a/b + 6 (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a _:a/a):a/a (AND (CONST #C):a/a _:a/1):a/1):a/b):a/c + 6 (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #B):a/a):a/a + 4 (AND (CONST #A):a/a (NEGATE (CCAST _:a/1):a/1):a/b):a/b + 4 (AND (CONST #A):a/a (REDXOR (AND (CONST #B):a/a _:a/b):a/b):a/1):a/1 + 4 (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/b):a/1 (CONST #B):a/a):a/a):a/a + 4 (CCAST (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):b/1 + 4 (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/b + 4 (NEGATE (CCAST (CCAST _:a/1):a/1):b/1):b/b + 4 (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR _:a/b):a/1):a/1):a/c + 4 (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR _:b/b):a/1):a/1):a/c + 4 (REDXOR (AND (CONST #A):a/a (AND _:a/b _:a/b):a/b):a/b):a/1 + 4 (REDXOR (AND (CONST #A):a/a (NEGATE _:a/1):a/b):a/b):a/1 + 4 (REDXOR (NEGATE (CCAST _:a/1):b/1):b/b):a/1 + 4 (SHIFTL (CCAST (CCAST _:a/a):a/a):b/b (CONST #A):a/a):b/b + 4 (SHIFTL (REDXOR (AND (CONST #A):a/a _:a/b):a/b):a/1 (CONST #B):a/a):a/a + 2 (AND (CONST #A):a/a (NOT (CCAST _:a/1):a/1):a/1):a/1 + 2 (AND (CONST #A):a/a (OR (SHIFTL _:a/a (CONST #B):b/b):a/a (CCAST _:b/b):a/a):a/c):a/c + 2 (AND (CONST #A):a/a (REDXOR (NEGATE _:b/1):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (OR _:b/b _:b/b):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (SHIFTL (CCAST (VARREF):a/1):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (VARREF):a/b):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/a):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/b):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/c):a/1 (CONST #B):a/a):a/a):a/a + 2 (CCAST (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #A):a/a):b/b):a/1 + 2 (OR (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (SHIFTL _:a/b (CONST #B):a/a):a/a):a/a (OR (SHIFTL _:a/b (CONST #C):a/a):a/a (OR _:a/a _:a/1):a/c):a/d):a/e + 2 (OR (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST _:a/a):a/a):b/b):b/b + 2 (OR (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST _:a/a):a/a):b/b):b/c + 2 (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a (OR (AND (CONST #A):a/a _:a/a):a/a (AND (CONST #B):a/a _:a/1):a/1):a/b):a/d + 2 (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a (SHIFTL (OR _:a/a _:a/b):a/c (CONST #B):a/a):a/a):a/a + 2 (REDXOR (AND (CONST #A):a/a (OR _:a/a _:a/a):a/b):a/b):c/1 + 2 (REDXOR (NEGATE (CCAST _:a/1):a/1):a/a):a/1 + 2 (REDXOR (OR (SHIFTL _:a/a (CONST #A):b/b):a/a (CCAST _:b/b):a/a):a/a):b/1 + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a _:a/b):a/b):c/1 (CONST #B):c/c):c/c + 2 (SHIFTL (REDXOR (NEGATE _:a/1):a/a):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR (NEGATE _:a/1):a/a):b/1 (CONST #A):b/b):b/b + 1 (AND (CONST #A):a/a (CCAST (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST #A):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (SHIFTR _:b/b (CONST #A):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (SHIFTR _:b/b (CONST #B):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (NOT (CCAST (VARREF):a/1):a/1):a/1):a/1 + 1 (CCAST (CCAST (OR _:a/a _:a/b):a/c):a/c):d/d + 1 (CCAST (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/b):a/c):a/c + 1 (NOT (CCAST (CCALL [(VARREF):(w64)u[0:0]]):a/1):a/1):a/1 + 1 (NOT (CCAST (CCALL [(VARREF):(w64)u[1:0]]):a/1):a/1):a/1 + 1 (OR (AND (CONST #A):a/a (ARRAYSEL (VARREF):(w64)u[0:0] (CONST ZERO):b/b):a/a):a/a (CCAST (CCAST (VARREF):b/1):b/1):a/a):a/a + 1 (OR (SHIFTL (NEQ _:a/b _:a/b):a/1 (CONST #A):a/a):a/a (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1):a/c + 1 (OR (SHIFTL (NEQ _:a/b _:a/b):a/c (CONST #A):a/a):a/a (OR (SHIFTL _:a/1 (CONST #B):a/a):a/a (NEQ _:a/b _:a/b):a/1):a/c):a/b + 1 (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1 (CONST #A):a/a):a/a + 1 (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/c (CONST #A):a/a):a/a + +AST patterns with depth 4 + 18 (CCAST (AND (CONST #A):a/a (SHIFTR (CCAST (VARREF):a/b):a/b (CONST #A):a/a):a/1):a/1):a/1 + 10 (NEGATE (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1):a/a + 8 (CCAST (CCAST (NOT (NEGATE _:a/1):a/a):a/a):a/a):b/b + 8 (CCAST (NOT (NEGATE (CCAST _:a/1):a/1):a/a):a/a):a/a + 8 (NOT (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/a):a/a + 6 (AND (CONST #A):a/a (AND (NOT (CCAST (VARREF):a/b):a/b):a/b (NOT (CCAST (VARREF):a/b):a/b):a/b):a/b):a/b + 4 (AND (CONST #A):a/a (NEGATE (CCAST (AND (CONST #B):a/a _:a/1):a/1):a/1):a/b):a/b + 4 (AND (CONST #A):a/a (SHIFTL (REDXOR (AND (CONST #B):a/a _:a/b):a/b):a/1 (CONST #C):a/a):a/a):a/a + 4 (CCAST (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1):c/1 + 4 (NEGATE (CCAST (AND (CONST #A):a/a (SHIFTR _:a/b (CONST #A):a/a):a/1):a/1):a/1):a/c + 4 (NEGATE (CCAST (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):b/1):b/b + 4 (REDXOR (AND (CONST #A):a/a (AND (NOT _:a/b):a/b (NOT _:a/b):a/b):a/b):a/b):a/1 + 4 (REDXOR (AND (CONST #A):a/a (NEGATE (CCAST _:a/1):a/1):a/b):a/b):a/1 + 4 (REDXOR (NEGATE (CCAST (CCAST _:a/1):a/1):b/1):b/b):a/1 + 4 (SHIFTL (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b (CONST #A):a/a):b/b + 4 (SHIFTL (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a _:a/a):a/a (AND (CONST #C):a/a _:a/1):a/1):a/b):a/c (CONST #D):a/a):a/a + 2 (AND (CONST #A):a/a (OR (SHIFTL (CCAST _:b/b):a/a (CONST #B):b/b):a/a (CCAST (CCAST _:b/b):b/b):a/a):a/c):a/c + 2 (AND (CONST #A):a/a (REDXOR (AND (CONST #B):a/a (AND _:a/b _:a/b):a/b):a/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (AND (CONST #B):a/a (NEGATE _:a/1):a/b):a/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (NEGATE (CCAST _:a/1):b/1):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (REDXOR (OR (SHIFTL _:b/b (CONST #B):a/a):b/b (CCAST _:a/a):b/b):b/b):a/1):a/1 + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (AND (CONST #B):b/b _:b/c):b/c):a/1 (CONST #C):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (NEGATE _:a/1):a/a):a/1 (CONST #B):a/a):a/a):a/a + 2 (AND (CONST #A):a/a (SHIFTL (REDXOR (NEGATE _:b/1):b/b):a/1 (CONST #B):a/a):a/a):a/a + 2 (OR (AND (CONST #A):a/a (SHIFTL (CCAST (VARREF):a/1):a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:a/b):a/1):a/1):a/c):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR (VARREF):a/b):a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:a/b):a/1):a/1):a/c):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/a):a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:b/b):a/1):a/1):a/c):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/b):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (AND (CONST #C):a/a _:a/b):a/b):a/1):a/1):a/c + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:a/b):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (AND (CONST #C):a/a _:a/c):a/c):a/1):a/1):a/d + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/b):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (OR _:b/b _:b/b):b/b):a/1):a/1):a/c + 2 (OR (AND (CONST #A):a/a (SHIFTL (REDXOR _:b/c):a/1 (CONST #B):a/a):a/a):a/a (AND (CONST #B):a/a (REDXOR (NEGATE _:b/1):b/b):a/1):a/1):a/d + 2 (OR (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #A):a/a):a/a (SHIFTL (OR _:a/a _:a/b):a/c (CONST #B):a/a):a/a):a/a (OR (SHIFTL (OR _:a/a _:a/b):a/c (CONST #C):a/a):a/a (OR (AND (CONST #C):a/a _:a/a):a/a (AND (CONST #D):a/a _:a/1):a/1):a/b):a/d):a/e + 2 (OR (SHIFTL (CCAST (CCAST _:a/a):a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b):b/b + 2 (OR (SHIFTL (CCAST (CCAST _:a/a):a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST (NOT _:a/a):a/a):a/a):b/b):b/c + 2 (OR (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #B):a/a):a/a (OR (AND (CONST #B):a/a (SHIFTL _:a/1 (CONST #C):a/a):a/a):a/a (AND (CONST #C):a/a (REDXOR _:d/d):a/1):a/1):a/b):a/e + 2 (OR (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #B):a/a):a/a (SHIFTL (OR (AND (CONST #A):a/a _:a/a):a/a (OR _:a/a _:a/1):a/b):a/c (CONST #C):a/a):a/a):a/a + 2 (REDXOR (AND (CONST #A):a/a (OR (SHIFTL _:a/a (CONST #B):b/b):a/a (CCAST _:b/b):a/a):a/c):a/c):b/1 + 2 (REDXOR (NEGATE (CCAST (AND (CONST #A):a/a _:a/1):a/1):a/1):a/a):a/1 + 2 (REDXOR (OR (SHIFTL (CCAST _:a/a):b/b (CONST #A):a/a):b/b (CCAST (CCAST _:a/a):a/a):b/b):b/b):a/1 + 2 (SHIFTL (OR (AND (CONST #A):a/a (SHIFTL _:a/1 (CONST #B):a/a):a/a):a/a (OR (AND (CONST #B):a/a _:a/a):a/a (AND (CONST #C):a/a _:a/1):a/1):a/b):a/c (CONST #B):a/a):a/a + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a (AND _:a/b _:a/b):a/b):a/b):a/1 (CONST #B):a/a):a/a + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a (NEGATE _:a/1):a/b):a/b):a/1 (CONST #B):a/a):a/a + 2 (SHIFTL (REDXOR (AND (CONST #A):a/a (OR _:a/a _:a/a):a/b):a/b):c/1 (CONST #B):c/c):c/c + 2 (SHIFTL (REDXOR (NEGATE (CCAST _:a/1):a/1):a/a):a/1 (CONST #A):a/a):a/a + 2 (SHIFTL (REDXOR (NEGATE (CCAST _:a/1):b/1):b/b):a/1 (CONST #A):a/a):a/a + 1 (AND (CONST #A):a/a (CCAST (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #A):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (CCAST (SHIFTR (ARRAYSEL (VARREF):(w64)u[1:0] (CONST ZERO):a/a):b/b (CONST #B):a/a):b/b):a/1):a/1 + 1 (AND (CONST #A):a/a (NOT (CCAST (CCALL [(VARREF):(w64)u[0:0]]):a/1):a/1):a/1):a/1 + 1 (AND (CONST #A):a/a (NOT (CCAST (CCALL [(VARREF):(w64)u[1:0]]):a/1):a/1):a/1):a/1 + 1 (CCAST (CCAST (OR (SHIFTL _:a/b (CONST #A):a/a):a/a (OR _:a/a _:a/1):a/b):a/c):a/c):d/d + 1 (CCAST (OR (SHIFTL (NEQ _:a/b _:a/b):a/c (CONST #A):a/a):a/a (OR (SHIFTL _:a/1 (CONST #B):a/a):a/a (NEQ _:a/b _:a/b):a/1):a/c):a/b):a/b + 1 (OR (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1 (CONST #A):a/a):a/a (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1):a/c + 1 (OR (SHIFTL (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/c (CONST #A):a/a):a/a (OR (SHIFTL (NEQ _:a/b _:a/b):a/1 (CONST #B):a/a):a/a (NEQ (CCAST (VARREF):a/b):a/b (CCAST (VARREF):a/b):a/b):a/1):a/c):a/b + diff --git a/test_regress/t/t_ast_dump_patterns.py b/test_regress/t/t_ast_dump_patterns.py new file mode 100755 index 000000000..d1bf14b33 --- /dev/null +++ b/test_regress/t/t_ast_dump_patterns.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--dump-ast-patterns", "--no-skip-identical"]) + +test.files_identical(test.obj_dir + "/" + test.vm_prefix + "__ast_patterns_emit.txt", + test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_ast_dump_patterns.v b/test_regress/t/t_ast_dump_patterns.v new file mode 100644 index 000000000..dc96474fb --- /dev/null +++ b/test_regress/t/t_ast_dump_patterns.v @@ -0,0 +1,26 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire [3:0] a, + input wire [3:0] b, + input wire [3:0] c, + output wire [10:0] o +); + wire [3:0] x = ~a & ~b; + wire [3:0] y = ~b & ~c; + wire [3:0] z = ~c & ~a; + wire [0:0] w1 = x[0]; + wire [7:0] w8 = {8{x[1]}}; + wire [15:0] w16 = {2{w8}}; + wire [31:0] w32 = {2{w16}}; + wire [63:0] w64a = {2{w32}}; + wire [63:0] w64b = {2{~w32}}; + wire [62:0] w63 = 63'({2{~w32}}); + wire [95:0] w96 = 96'(w64a); + + assign o = {^x, ^y, ^z, ^w1, ^w8, ^w16, ^w32, ^w64a, ^w64b, ^w63, ^w96}; +endmodule From fed922a538dbd12cab981220c24c53388a21ef2a Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 21 Jun 2026 23:23:52 +0100 Subject: [PATCH 73/89] Internals: Compact stage statistics table --- src/V3Stats.cpp | 7 +++++-- src/V3StatsReport.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/V3Stats.cpp b/src/V3Stats.cpp index 8abe9254b..dfa27b99e 100644 --- a/src/V3Stats.cpp +++ b/src/V3Stats.cpp @@ -41,6 +41,7 @@ class StatsVisitor final : public VNVisitorConst { // STATE const bool m_fastOnly; // When true, consider only fast functions + bool m_empty = true; // Netlist is empty Counters m_counters; // The actual counts we will display Counters m_dumpster; // Alternate buffer to make discarding parts of the tree easier Counters* m_accump; // The currently active accumulator @@ -51,6 +52,7 @@ class StatsVisitor final : public VNVisitorConst { // METHODS void countThenIterateChildren(AstNode* nodep) { ++m_accump->m_statTypeCount[nodep->type()]; + if (nodep->type() != VNType::Netlist) m_empty = false; iterateChildrenConst(nodep); } @@ -93,6 +95,7 @@ public: , m_accump{fastOnly ? &m_dumpster : &m_counters} { UINFO(9, "Starting stats, fastOnly=" << fastOnly); iterateConst(nodep); + if (m_empty) return; // Shorthand const auto addStat = [&](const std::string& name, double count, unsigned precision = 0) { @@ -127,13 +130,13 @@ public: addStat("Node count, " + typeName(t), count); } } - addStat("Node memory TOTAL (MiB)", totalNodeMemoryUsage >> 20); + addStat("Node mem TOTAL (MiB)", totalNodeMemoryUsage >> 20); // Node Memory usage for (size_t t = 0; t < VNType::NUM_TYPES(); ++t) { if (const uint64_t count = m_counters.m_statTypeCount[t]) { const double share = 100.0 * count * typeSize(t) / totalNodeMemoryUsage; - addStat("Node memory share (%), " + typeName(t), share, 2); + addStat("Node mem %, " + typeName(t), share, 2); } } diff --git a/src/V3StatsReport.cpp b/src/V3StatsReport.cpp index 32556cb0a..1bf7c512d 100644 --- a/src/V3StatsReport.cpp +++ b/src/V3StatsReport.cpp @@ -119,11 +119,11 @@ class StatsReport final { // Header os << " Stat " << std::left << std::setw(maxWidth - 5 - 2) << ""; - for (const string& i : stages) os << " " << std::left << std::setw(9) << i; + for (const string& i : stages) os << " " << std::left << std::setw(9) << i; os << '\n'; os << " -------- " << std::left << std::setw(maxWidth - 5 - 2) << ""; for (auto it = stages.begin(); it != stages.end(); ++it) { - os << " " << std::left << std::setw(9) << "-------"; + os << " " << std::left << std::setw(9) << "-------"; } // os<name(); } while (col < stages.size() && stages.at(col) != repp->stage()) { - os << std::setw(11) << ""; + os << std::setw(10) << ""; col++; } repp->dump(os); @@ -191,7 +191,7 @@ StatsReport::StatColl StatsReport::s_allStats; // V3Statstic class void V3Statistic::dump(std::ofstream& os) const { - os << " " << std::right << std::fixed << std::setprecision(precision()) << std::setw(9) + os << " " << std::right << std::fixed << std::setprecision(precision()) << std::setw(9) << value(); } From b37c84109dc362c59ff308c62795b4f1889e1900 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 22 Jun 2026 10:06:26 +0100 Subject: [PATCH 74/89] Optimize staticly known oversize shifts (#7806) The non *Ovr flavours of AstShift* have better downstream constant folding, so keep using those if proven safe. Fold overshifts explicitly instead of introducing *Ovr shifts. --- src/V3Premit.cpp | 70 +++++++++++++++++++------------ test_regress/t/t_opt_const.py | 2 +- test_regress/t/t_opt_const_dfg.py | 2 +- 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 9fcd89ddf..c5a9fa9d1 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -139,34 +139,52 @@ class PremitVisitor final : public VNVisitor { } void visitShift(AstNodeBiop* nodep) { - // Shifts of > 32/64 bits in C++ will wrap-around and generate non-0s UINFO(4, " ShiftFix " << nodep); - const AstConst* const shiftp = VN_CAST(nodep->rhsp(), Const); - if (shiftp && shiftp->num().mostSetBitP1() > 32) { - shiftp->v3warn( - E_UNSUPPORTED, - "Unsupported: Shifting of by over 32-bit number isn't supported." - << " (This isn't a shift of 32 bits, but a shift of 2^32, or 4 billion!)\n"); - } - if (nodep->widthMin() <= 64 // Else we'll use large operators which work right - // C operator's width must be < maximum shift which is - // based on Verilog width - && nodep->width() < (1LL << nodep->rhsp()->widthMin())) { - AstNode* newp; - if (VN_IS(nodep, ShiftL)) { - newp = new AstShiftLOvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(), - nodep->rhsp()->unlinkFrBack()}; - } else if (VN_IS(nodep, ShiftR)) { - newp = new AstShiftROvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(), - nodep->rhsp()->unlinkFrBack()}; - } else { - UASSERT_OBJ(VN_IS(nodep, ShiftRS), nodep, "Bad case"); - newp = new AstShiftRSOvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(), - nodep->rhsp()->unlinkFrBack()}; + UASSERT_OBJ(VN_IS(nodep, ShiftL) || VN_IS(nodep, ShiftR) || VN_IS(nodep, ShiftRS), nodep, + "Bad case"); + // Shift larger than the width of the type (overshift) is undefined behavour in C++ + // (in practice will shift by the wrapped shift amount). These are requierd to go to + // zero/msbs, so replacing them here. + FileLine* const flp = nodep->fileline(); + if (const AstConst* const shiftp = VN_CAST(nodep->rhsp(), Const)) { + // Shift amount known to be constant. If oversized shift, replace with zero/msbs. + // Otherwise we can leave the original shifts which have better constant folding + // than the *Ovr versions. + const bool isOversized = shiftp->num().mostSetBitP1() > 32 // + || (shiftp->num().toSQuad() >= nodep->width()); + if (isOversized) { + AstNodeExpr* newp = nullptr; + if (VN_IS(nodep, ShiftRS)) { + AstNodeExpr* const lhsp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const msbp = new AstSel{flp, lhsp, nodep->width() - 1, 1}; + newp = new AstExtendS{flp, msbp, nodep->width()}; + } else { + newp = new AstConst{flp, AstConst::DTyped{}, nodep->dtypep()}; + } + nodep->replaceWithKeepDType(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } else { + // Shift amount not known at compile time. Convert to *Ovr version. Don't need to do + // if it would use a wide operation which works correctly at runtime, of if the max + // value of the shift amount is less than the with of the shifted value. + if (nodep->widthMin() <= VL_QUADSIZE + && (nodep->width() < (1LL << nodep->rhsp()->widthMin()))) { + AstNodeExpr* const lhsp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); + AstNodeExpr* newp = nullptr; + if (VN_IS(nodep, ShiftL)) { + newp = new AstShiftLOvr{flp, lhsp, rhsp}; + } else if (VN_IS(nodep, ShiftR)) { + newp = new AstShiftROvr{flp, lhsp, rhsp}; + } else { + newp = new AstShiftRSOvr{flp, lhsp, rhsp}; + } + nodep->replaceWithKeepDType(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; } - nodep->replaceWithKeepDType(newp); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - return; } iterateChildren(nodep); checkNode(nodep); diff --git a/test_regress/t/t_opt_const.py b/test_regress/t/t_opt_const.py index 9a49ab61d..e4d50d945 100755 --- a/test_regress/t/t_opt_const.py +++ b/test_regress/t/t_opt_const.py @@ -16,7 +16,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "-fno-dfg", "--stats", test. test.execute() if test.vlt: - test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 48) + test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 50) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_opt_const_dfg.py b/test_regress/t/t_opt_const_dfg.py index 6ad2d3549..41f6a2976 100755 --- a/test_regress/t/t_opt_const_dfg.py +++ b/test_regress/t/t_opt_const_dfg.py @@ -18,7 +18,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "--stats", test.pli_filename test.execute() if test.vlt: - test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 38) + test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 40) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1) test.passes() From 9670dabcfee97b4b4712cd033c6de6bec1f57e65 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 22 Jun 2026 10:04:42 +0100 Subject: [PATCH 75/89] Internals: Keep separate stats for constant pool variables --- src/V3Stats.cpp | 54 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/src/V3Stats.cpp b/src/V3Stats.cpp index dfa27b99e..a1ad5edb2 100644 --- a/src/V3Stats.cpp +++ b/src/V3Stats.cpp @@ -46,6 +46,9 @@ class StatsVisitor final : public VNVisitorConst { Counters m_dumpster; // Alternate buffer to make discarding parts of the tree easier Counters* m_accump; // The currently active accumulator std::vector m_statVarWidths; // Variables of given width + std::vector m_constPoolConsts; // Constant pool constants of given width + // Constant pool tables of given width and depth + std::map, uint64_t> m_constPoolTables; std::vector> m_statVarWidthNames; // Var names of given width @@ -59,15 +62,30 @@ class StatsVisitor final : public VNVisitorConst { // VISITORS void visit(AstVar* nodep) override { if (nodep->dtypep()) { - if (m_statVarWidths.size() <= static_cast(nodep->width())) { - m_statVarWidths.resize(nodep->width() + 5); - if (v3Global.opt.statsVars()) { // - m_statVarWidthNames.resize(nodep->width() + 5); + if (nodep->constPoolEntry()) { + // Count constant pool entries + if (AstUnpackArrayDType* const uatp = VN_CAST(nodep->dtypep(), UnpackArrayDType)) { + const int width = uatp->subDTypep()->width(); + const int depth = uatp->elementsConst(); + ++m_constPoolTables[{width, depth}]; + } else { + if (m_constPoolConsts.size() <= static_cast(nodep->width())) { + m_constPoolConsts.resize(nodep->width() + 5); + } + ++m_constPoolConsts.at(nodep->width()); + } + } else { + // Count proper variables + if (m_statVarWidths.size() <= static_cast(nodep->width())) { + m_statVarWidths.resize(nodep->width() + 5); + if (v3Global.opt.statsVars()) { // + m_statVarWidthNames.resize(nodep->width() + 5); + } + } + ++m_statVarWidths.at(nodep->width()); + if (v3Global.opt.statsVars()) { + ++m_statVarWidthNames.at(nodep->width())[nodep->prettyName()]; } - } - ++m_statVarWidths.at(nodep->width()); - if (v3Global.opt.statsVars()) { - ++m_statVarWidthNames.at(nodep->width())[nodep->prettyName()]; } } @@ -118,6 +136,26 @@ public: } } + // Constant pool constants + for (size_t i = 0; i < m_constPoolConsts.size(); ++i) { + const uint64_t count = m_constPoolConsts.at(i); + if (!count) continue; + std::stringstream ss; + ss << "Vars Const, width " << std::setw(5) << std::dec << i; + addStat(ss.str(), count); + } + + // Constant pool tables + for (const auto& it : m_constPoolTables) { + const int depth = it.first.second; + const int width = it.first.first; + const int count = it.second; + std::ostringstream ss; + ss << "Vars Table, width " << std::setw(5) << std::dec << width // + << " x " << std::setw(5) << std::dec << depth; + addStat(ss.str(), count); + } + // Node types (also total memory usage) const auto typeName = [](size_t t) { return std::string{VNType{static_cast(t)}.ascii()}; }; From 7765738e11f917a5af457c3adbb05e8d7365ec8a Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 22 Jun 2026 16:42:22 +0100 Subject: [PATCH 76/89] Internals: Do not generate redundant masking on word selects (#7819) newAstWordSelClone returns a clean word and these And mask out exactly the bits that are zero after the shift. --- src/V3Expand.cpp | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/src/V3Expand.cpp b/src/V3Expand.cpp index 267360e15..f58474273 100644 --- a/src/V3Expand.cpp +++ b/src/V3Expand.cpp @@ -206,31 +206,19 @@ class ExpandVisitor final : public VNVisitor { static AstNodeExpr* newWordGrabShift(FileLine* fl, int word, AstNodeExpr* lhsp, int shift) { // Extract the expression to grab the value for the specified word, if it's the shift // of shift bits from lhsp - AstNodeExpr* newp; // Negative word numbers requested for lhs when it's "before" what we want. // We get a 0 then. const int othword = word - shift / VL_EDATASIZE; - AstNodeExpr* const llowp = newAstWordSelClone(lhsp, othword); - if (const int loffset = VL_BITBIT_E(shift)) { - AstNodeExpr* const lhip = newAstWordSelClone(lhsp, othword - 1); - const int nbitsonright = VL_EDATASIZE - loffset; // bits that end up in lword - newp = new AstOr{ - fl, - new AstAnd{fl, new AstConst{fl, AstConst::SizedEData{}, VL_MASK_E(loffset)}, - new AstShiftR{fl, lhip, - new AstConst{fl, static_cast(nbitsonright)}, - VL_EDATASIZE}}, - new AstAnd{fl, - new AstConst{fl, AstConst::SizedEData{}, - static_cast(~VL_MASK_E(loffset))}, - new AstShiftL{fl, llowp, - new AstConst{fl, static_cast(loffset)}, - VL_EDATASIZE}}}; - newp = V3Const::constifyEditCpp(newp); - } else { - newp = llowp; - } - return newp; + AstNodeExpr* const lop = newAstWordSelClone(lhsp, othword); + const uint32_t loShift = VL_BITBIT_E(shift); + if (!loShift) return lop; + AstNodeExpr* const hip = newAstWordSelClone(lhsp, othword - 1); + const uint32_t hiShift = VL_EDATASIZE - loShift; // Complement offset + AstNodeExpr* const newp + = new AstOr{fl, // + new AstShiftR{fl, hip, new AstConst{fl, hiShift}, VL_EDATASIZE}, + new AstShiftL{fl, lop, new AstConst{fl, loShift}, VL_EDATASIZE}}; + return V3Const::constifyEditCpp(newp); } // Return expression indexing the word that contains 'lsbp' + the given word offset From 729794bc0ead62cafcade3b5920012ae39c6a268 Mon Sep 17 00:00:00 2001 From: Saksham Date: Mon, 22 Jun 2026 22:11:48 +0530 Subject: [PATCH 77/89] Fix CASEINCOMPLETE for all uncovered enum items (#7815) (#7817) Fixes #7815. --- docs/CONTRIBUTORS | 1 + src/V3Case.cpp | 19 +++++++++++-------- test_regress/t/t_case_enum_incomplete_bad.out | 2 +- test_regress/t/t_case_enum_incomplete_bad.v | 1 - .../t/t_case_enum_incomplete_wildcard_bad.out | 6 +++--- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 877011165..a22db1f07 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -252,6 +252,7 @@ Rowan Goemans Rupert Swarbrick Ryan Ziegler Ryszard Rozak +Saksham Gupta Samuel Riedel Sean Cross Sebastien Van Cauwenberghe diff --git a/src/V3Case.cpp b/src/V3Case.cpp index c4d4fbc21..54ce1ff7e 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -284,6 +284,8 @@ class CaseVisitor final : public VNVisitor { // Returns true iff the case items cover all enum values/patterns. bool checkExhaustiveEnum(const AstCase* const nodep, const AstEnumDType* const enump) { const uint32_t numCases = 1UL << nodep->exprp()->width(); + bool fullyCovered = true; + string missingItems; for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) { const auto& match = matchPattern(nodep, VN_AS(eip->valuep(), Const)); const uint32_t mask = match.first.toUInt(); @@ -293,16 +295,17 @@ class CaseVisitor final : public VNVisitor { for (uint32_t i = 0; i < numCases; ++i) { if ((i & mask) != bits) continue; // This case is not for this enum value if (m_caseDetails.records[i].itemp) continue; // Covered case - // Warn unless unique0 case which allows no-match - if (!nodep->unique0Pragma()) { - nodep->v3warn(CASEINCOMPLETE, - "Enum item " << eip->prettyNameQ() << " not covered by case"); - } - // TODO: warn for all uncovered enum values, not just the first - return false; // enum has uncovered value by case items + if (!missingItems.empty()) missingItems += ", "; + missingItems += eip->prettyNameQ(); + fullyCovered = false; + break; } } - return true; // enum is fully covered + if (!missingItems.empty() && !nodep->unique0Pragma()) { + nodep->v3warn(CASEINCOMPLETE, + "Enum item(s) not covered by case: " << missingItems); + } + return fullyCovered; // enum is fully covered } // Check and warn if case items are not complete over all possible values. diff --git a/test_regress/t/t_case_enum_incomplete_bad.out b/test_regress/t/t_case_enum_incomplete_bad.out index 6fa08e122..6f3986c1d 100644 --- a/test_regress/t/t_case_enum_incomplete_bad.out +++ b/test_regress/t/t_case_enum_incomplete_bad.out @@ -1,4 +1,4 @@ -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_bad.v:18:12: Enum item 'S1' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_bad.v:18:12: Enum item(s) not covered by case: 'S1', 'S2' 18 | unique case (state) | ^~~~ ... For warning description see https://verilator.org/warn/CASEINCOMPLETE?v=latest diff --git a/test_regress/t/t_case_enum_incomplete_bad.v b/test_regress/t/t_case_enum_incomplete_bad.v index d5d4dabab..6f5dfd6c0 100644 --- a/test_regress/t/t_case_enum_incomplete_bad.v +++ b/test_regress/t/t_case_enum_incomplete_bad.v @@ -17,7 +17,6 @@ module t; unique case (state) S0: $stop; - S2: $stop; endcase end endmodule diff --git a/test_regress/t/t_case_enum_incomplete_wildcard_bad.out b/test_regress/t/t_case_enum_incomplete_wildcard_bad.out index 289933112..59e2f5b81 100644 --- a/test_regress/t/t_case_enum_incomplete_wildcard_bad.out +++ b/test_regress/t/t_case_enum_incomplete_wildcard_bad.out @@ -1,12 +1,12 @@ -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:26:12: Enum item 'S10' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:26:12: Enum item(s) not covered by case: 'S10', 'SX0' 26 | unique case (state) | ^~~~ ... For warning description see https://verilator.org/warn/CASEINCOMPLETE?v=latest ... Use "/* verilator lint_off CASEINCOMPLETE */" and lint_on around source to disable this message. -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:30:12: Enum item 'S00' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:30:12: Enum item(s) not covered by case: 'S00', 'SX0', 'S0X' 30 | unique case (state) | ^~~~ -%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:35:12: Enum item 'S10' not covered by case +%Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:35:12: Enum item(s) not covered by case: 'S10', 'SX0' 35 | unique casez (state) | ^~~~~ %Warning-CASEINCOMPLETE: t/t_case_enum_incomplete_wildcard_bad.v:40:5: Case values incompletely covered (example pattern 0x3) From 515c4282f4475c8eb56f977a753ae97acade0f4b Mon Sep 17 00:00:00 2001 From: github action Date: Mon, 22 Jun 2026 16:47:50 +0000 Subject: [PATCH 78/89] Apply 'make format' [ci skip] --- src/V3Case.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 54ce1ff7e..146f81a89 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -302,8 +302,7 @@ class CaseVisitor final : public VNVisitor { } } if (!missingItems.empty() && !nodep->unique0Pragma()) { - nodep->v3warn(CASEINCOMPLETE, - "Enum item(s) not covered by case: " << missingItems); + nodep->v3warn(CASEINCOMPLETE, "Enum item(s) not covered by case: " << missingItems); } return fullyCovered; // enum is fully covered } From bf50baefee644dcbb1636cba7304cb22bdeacb54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 12:48:29 -0400 Subject: [PATCH 79/89] CI: Bump actions/checkout from 6 to 7 in the everything group (#7821) --- .github/workflows/build-test.yml | 2 +- .github/workflows/contributor.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/docker.yml | 2 +- .github/workflows/format.yml | 2 +- .github/workflows/pages.yml | 4 ++-- .github/workflows/reusable-build.yml | 2 +- .github/workflows/reusable-lint-py.yml | 2 +- .github/workflows/reusable-rtlmeter-build.yml | 2 +- .github/workflows/reusable-rtlmeter-run.yml | 2 +- .github/workflows/rtlmeter.yml | 10 +++++----- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index d9fea2352..6f5de6e36 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -154,7 +154,7 @@ jobs: CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_LIMIT_MULTIPLE: 0.95 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: path: repo - name: Cache $CCACHE_DIR diff --git a/.github/workflows/contributor.yml b/.github/workflows/contributor.yml index 02549bf76..afb5b9fe5 100644 --- a/.github/workflows/contributor.yml +++ b/.github/workflows/contributor.yml @@ -16,5 +16,5 @@ jobs: name: "'docs/CONTRIBUTORS' was signed" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - run: test_regress/t/t_dist_contributors.py diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 1601a67d7..d4934c92b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -74,7 +74,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Download code coverage data uses: actions/download-artifact@v8 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 278a8aeb9..37c35f8f8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Extract context variables run: | diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index eefc544a1..6716b4f48 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -21,7 +21,7 @@ jobs: CI_COMMIT: ${{ github.sha }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Install packages for build diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 3718abc11..bc314b298 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -38,7 +38,7 @@ jobs: pr-run-ids: ${{ steps.build.outputs.pr-run-ids }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Build pages id: build env: @@ -70,7 +70,7 @@ jobs: if: ${{ github.repository == 'verilator/verilator' }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 # Use the Verilator CI app to post the comment - name: Generate access token id: generate-token diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 7138a7cce..2601765e1 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -61,7 +61,7 @@ jobs: CCACHE_MAXSIZE: 1000M # Per build matrix entry (* 5 = 5000M in total) steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: path: repo ref: ${{ inputs.sha }} diff --git a/.github/workflows/reusable-lint-py.yml b/.github/workflows/reusable-lint-py.yml index f4ad9b6e8..fa8405693 100644 --- a/.github/workflows/reusable-lint-py.yml +++ b/.github/workflows/reusable-lint-py.yml @@ -27,7 +27,7 @@ jobs: name: Sub-lint | Python steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: path: repo diff --git a/.github/workflows/reusable-rtlmeter-build.yml b/.github/workflows/reusable-rtlmeter-build.yml index 321033a72..b6c87a6dc 100644 --- a/.github/workflows/reusable-rtlmeter-build.yml +++ b/.github/workflows/reusable-rtlmeter-build.yml @@ -60,7 +60,7 @@ jobs: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }} - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: path: repo ref: ${{ inputs.sha }} diff --git a/.github/workflows/reusable-rtlmeter-run.yml b/.github/workflows/reusable-rtlmeter-run.yml index ebbaffef8..a5dee222c 100644 --- a/.github/workflows/reusable-rtlmeter-run.yml +++ b/.github/workflows/reusable-rtlmeter-run.yml @@ -73,7 +73,7 @@ jobs: sudo apt install ccache mold libfl-dev libjemalloc-dev libsystemc-dev - name: Checkout RTLMeter - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: "verilator/rtlmeter" path: rtlmeter diff --git a/.github/workflows/rtlmeter.yml b/.github/workflows/rtlmeter.yml index b36646698..af79a9a0f 100644 --- a/.github/workflows/rtlmeter.yml +++ b/.github/workflows/rtlmeter.yml @@ -45,7 +45,7 @@ jobs: cases: ${{ steps.cases.outputs.cases }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Startup id: start @@ -208,7 +208,7 @@ jobs: run: echo "tags=$(jq -r 'keys | map(sub("^run-"; "")) | join(" ")' <<< '${{ toJSON(needs) }}')" >> "$GITHUB_OUTPUT" - name: Checkout RTLMeter - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: "verilator/rtlmeter" path: rtlmeter @@ -298,7 +298,7 @@ jobs: repositories: verilator-rtlmeter-results permission-contents: write - name: Checkout verilator-rtlmeter-results - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: "verilator/verilator-rtlmeter-results" token: ${{ steps.generate-token.outputs.token }} @@ -331,7 +331,7 @@ jobs: actions: read steps: - name: Checkout RTLMeter - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: "verilator/rtlmeter" path: rtlmeter @@ -341,7 +341,7 @@ jobs: run: make venv - name: Checkout Verilator - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: path: verilator From c927f05f357f2e12b7f2fd24fc1f601e3f92316a Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Mon, 22 Jun 2026 17:25:59 -0400 Subject: [PATCH 80/89] Update fst from upstream (#6771 partial) --- include/fstcpp/fstcpp_assertion.h | 6 ++++++ include/fstcpp/fstcpp_variable_info.h | 4 ++-- include/fstcpp/fstcpp_writer.cpp | 8 ++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/fstcpp/fstcpp_assertion.h b/include/fstcpp/fstcpp_assertion.h index e9606f665..8a4d549ad 100644 --- a/include/fstcpp/fstcpp_assertion.h +++ b/include/fstcpp/fstcpp_assertion.h @@ -81,6 +81,12 @@ std::abort(); \ } +#define FST_FAIL_STRING(s) \ + do { \ + std::cerr << (s) << std::endl; \ + std::abort(); \ + } while (0) + // We turn on all DCHECKs to CHECKs temporarily for better safety. #if 1 # define FST_DCHECK(a) FST_CHECK(a) diff --git a/include/fstcpp/fstcpp_variable_info.h b/include/fstcpp/fstcpp_variable_info.h index 470bb8ef1..0074705b1 100644 --- a/include/fstcpp/fstcpp_variable_info.h +++ b/include/fstcpp/fstcpp_variable_info.h @@ -335,10 +335,10 @@ public: // Double variables should not use these array-based emitValueChange overloads. // We implement them to satisfy the VairableInfo::dispatchHelper template instantiation. void emitValueChange(uint64_t, const uint32_t *, EncodingType) { - throw std::runtime_error("emitValueChange(uint32_t*) not supported for Double"); + FST_FAIL_STRING("emitValueChange(uint32_t*) not supported for Double"); } void emitValueChange(uint64_t, const uint64_t *, EncodingType) { - throw std::runtime_error("emitValueChange(uint64_t*) not supported for Double"); + FST_FAIL_STRING("emitValueChange(uint64_t*) not supported for Double"); } void dumpInitialBits(std::vector &buf) const { diff --git a/include/fstcpp/fstcpp_writer.cpp b/include/fstcpp/fstcpp_writer.cpp index 2756e23b9..386a76162 100644 --- a/include/fstcpp/fstcpp_writer.cpp +++ b/include/fstcpp/fstcpp_writer.cpp @@ -193,7 +193,7 @@ Handle Writer::createVar( // (void)type; // (void)svt; // (void)sdt; -// throw std::runtime_error("TODO"); +// FST_FAIL_STRING("TODO"); // return 0; // } // LCOV_EXCL_STOP @@ -353,11 +353,7 @@ void compressUsingZlib( uncompressed_size, level ); - if (z_status != Z_OK) { - throw std::runtime_error( - "Failed to compress data with zlib, error code: " + std::to_string(z_status) - ); - } + FST_CHECK_EQ(z_status, Z_OK); compressed_data.resize(compressed_bound); } From 87bebbb7320d050ca9d1910f37fb34dd17a5a39e Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Tue, 23 Jun 2026 00:51:41 +0200 Subject: [PATCH 81/89] Support global $assertcontrol (#7807) Signed-off-by: Artur Bieniek --- include/verilated.cpp | 73 ++++-- include/verilated.h | 27 ++- src/V3Assert.cpp | 205 +++++++++++------ src/V3AssertNfa.cpp | 186 +++++++++++++-- src/V3AstNodeStmt.h | 7 +- src/V3EmitCHeaders.cpp | 16 +- test_regress/t/t_assert_basic.v | 24 ++ test_regress/t/t_assert_ctl_arg.cpp | 55 +++++ test_regress/t/t_assert_ctl_arg_unsup.out | 14 +- test_regress/t/t_assert_ctl_arg_unsup.v | 4 +- test_regress/t/t_assert_ctl_concurrent.v | 45 ++-- ..._unsup.py => t_assert_ctl_pass_actions.py} | 8 +- test_regress/t/t_assert_ctl_pass_actions.v | 214 ++++++++++++++++++ .../t/t_assert_ctl_type_runtime_bad.out | 1 + .../t/t_assert_ctl_type_runtime_bad.py | 18 ++ .../t/t_assert_ctl_type_runtime_bad.v | 20 ++ test_regress/t/t_assert_disabled.py | 4 +- test_regress/t/t_assert_disabled.v | 34 +++ test_regress/t/t_assert_opt_check.py | 2 +- test_regress/t/t_assertcontrol.py | 18 ++ test_regress/t/t_assertcontrol.v | 113 +++++++++ test_regress/t/t_assertcontrol_noinl.py | 19 ++ test_regress/t/t_prop_followed_by.v | 3 + test_regress/t/t_property_sexpr_cov.v | 8 + test_regress/t/t_sequence_within.v | 8 +- 25 files changed, 978 insertions(+), 148 deletions(-) rename test_regress/t/{t_assert_ctl_unsup.py => t_assert_ctl_pass_actions.py} (70%) create mode 100644 test_regress/t/t_assert_ctl_pass_actions.v create mode 100644 test_regress/t/t_assert_ctl_type_runtime_bad.out create mode 100755 test_regress/t/t_assert_ctl_type_runtime_bad.py create mode 100644 test_regress/t/t_assert_ctl_type_runtime_bad.v create mode 100644 test_regress/t/t_assert_disabled.v create mode 100755 test_regress/t/t_assertcontrol.py create mode 100644 test_regress/t/t_assertcontrol.v create mode 100755 test_regress/t/t_assertcontrol_noinl.py diff --git a/include/verilated.cpp b/include/verilated.cpp index fcfbab58a..c58dde7f4 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -3035,20 +3035,7 @@ void VerilatedContext::assertOn(bool flag) VL_MT_SAFE { } bool VerilatedContext::assertOnGet(VerilatedAssertType_t type, VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE { - // Check if selected directive type bit in the assertOn is enabled for assertion type. - // Note: it is assumed that this is checked only for one type at the time. - - // Flag unspecified assertion types as disabled. - if (type == 0) return false; - - // Get index of 3-bit group guarding assertion type status. - // Since the assertOnGet is generated __always__ for a single assert type, we assume that only - // a single bit will be set. Thus, ceil log2 will work fine. - VL_DEBUG_IFDEF(assert((type & (type - 1)) == 0);); - const IData typeMaskPosition = VL_CLOG2_I(type); - - // Check if directive type bit is enabled in corresponding assertion type bits. - return m_s.m_assertOn & (directive << (typeMaskPosition * ASSERT_DIRECTIVE_TYPE_MASK_WIDTH)); + return assertCtlGet(VerilatedAssertCtlQuery::ASSERT_CTL_ON, type, directive); } uint32_t VerilatedContext::assertOnMask(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_PURE { @@ -3072,6 +3059,7 @@ void VerilatedContext::assertCtl(uint32_t controlType, VerilatedAssertType_t typ // IEEE 1800-2023 Table 20-5 control_type. Lock freezes the On/Off state of the // selected bits until Unlock; On/Off/Kill leave locked bits unchanged. const uint32_t mask = assertOnMask(types, directives); + const uint32_t lockedMask = mask & ~m_s.m_assertLock; switch (controlType) { case 1: // Lock m_s.m_assertLock |= mask; @@ -3080,15 +3068,64 @@ void VerilatedContext::assertCtl(uint32_t controlType, VerilatedAssertType_t typ m_s.m_assertLock &= ~mask; break; case 3: // On - m_s.m_assertOn |= mask & ~m_s.m_assertLock; + m_s.m_assertOn |= lockedMask; break; case 4: // Off - case 5: // Kill - m_s.m_assertOn &= ~(mask & ~m_s.m_assertLock); + m_s.m_assertOn &= ~lockedMask; break; - default: // 6..11 (Pass/Fail/Vacuous action control) are not modeled; ignore + case 5: { // Kill + m_s.m_assertOn &= ~lockedMask; + for (int slot = 0; slot < static_cast(ASSERT_CONTROL_SLOT_COUNT); ++slot) { + if (VL_BITISSET_I(lockedMask, slot)) { m_s.m_assertKill[slot]++; } + } break; } + case 6: // PassOn + m_s.m_assertPassOnVacuous |= lockedMask; + m_s.m_assertPassOnNonvacuous |= lockedMask; + break; + case 7: // PassOff + m_s.m_assertPassOnVacuous &= ~lockedMask; + m_s.m_assertPassOnNonvacuous &= ~lockedMask; + break; + case 8: // FailOn + m_s.m_assertFailOn |= lockedMask; + break; + case 9: // FailOff + m_s.m_assertFailOn &= ~lockedMask; + break; + case 10: // NonvacuousOn + m_s.m_assertPassOnNonvacuous |= lockedMask; + break; + case 11: // VacuousOff + m_s.m_assertPassOnVacuous &= ~lockedMask; + break; + default: + VL_WARN_MT("", 0, "", + ("Bad $assertcontrol control_type '" + std::to_string(controlType) + + "' (IEEE 1800-2023 Table 20-5)") + .c_str()); + } +} +uint32_t +VerilatedContext::assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type, + VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE { + const uint32_t mask = assertOnMask(type, directive); + if (!mask) return 0; + switch (query) { // LCOV_EXCL_BR_LINE + case VerilatedAssertCtlQuery::ASSERT_CTL_ON: return (m_s.m_assertOn & mask) != 0; + case VerilatedAssertCtlQuery::ASSERT_CTL_KILL: + assert(mask && (mask & (mask - 1)) == 0); + return m_s.m_assertKill[VL_CLOG2_I(mask)]; + case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_VACUOUS: + return (m_s.m_assertPassOnVacuous & mask) != 0; + case VerilatedAssertCtlQuery::ASSERT_CTL_PASS_ON_NONVACUOUS: + return (m_s.m_assertPassOnNonvacuous & mask) != 0; + case VerilatedAssertCtlQuery::ASSERT_CTL_FAIL_ON: return (m_s.m_assertFailOn & mask) != 0; + default: // LCOV_EXCL_START + VL_FATAL_MT("", 0, "", "Internal: Bad assertCtlGet query"); + VL_UNREACHABLE; + } // LCOV_EXCL_STOP } void VerilatedContext::calcUnusedSigs(bool flag) VL_MT_SAFE { const VerilatedLockGuard lock{m_mutex}; diff --git a/include/verilated.h b/include/verilated.h index f8075567e..a4535f2b4 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -175,6 +175,15 @@ enum class VerilatedAssertDirectiveType : uint8_t { DIRECTIVE_TYPE_COVER = (1 << 1), DIRECTIVE_TYPE_ASSUME = (1 << 2), }; + +/// Runtime query selector for assertion-control state +enum class VerilatedAssertCtlQuery : uint8_t { + ASSERT_CTL_ON, + ASSERT_CTL_KILL, + ASSERT_CTL_PASS_ON_VACUOUS, + ASSERT_CTL_PASS_ON_NONVACUOUS, + ASSERT_CTL_FAIL_ON, +}; using VerilatedAssertType_t = std::underlying_type::type; using VerilatedAssertDirectiveType_t = std::underlying_type::type; @@ -356,9 +365,10 @@ private: static constexpr size_t ASSERT_ON_WIDTH = ASSERT_DIRECTIVE_TYPE_MASK_WIDTH * std::numeric_limits::digits + 1; - // Build the m_assertOn/m_assertLock bit mask for the given assertion x directive types. + // Build the assertion-control bit mask for the given assertion x directive types. static uint32_t assertOnMask(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_PURE; + static constexpr size_t ASSERT_CONTROL_SLOT_COUNT = ASSERT_ON_WIDTH - 1; protected: // TYPES @@ -381,6 +391,13 @@ protected: std::atomic m_assertLock{0}; // Locked assertion bits (IEEE 1800-2023 20.11 // Lock/Unlock); same layout as m_assertOn. While // a bit is locked, On/Off/Kill leave it unchanged. + std::atomic m_assertPassOnVacuous{ + std::numeric_limits::max()}; // Enabled vacuous pass actions + std::atomic m_assertPassOnNonvacuous{ + std::numeric_limits::max()}; // Enabled nonvacuous pass actions + std::atomic m_assertFailOn{ + std::numeric_limits::max()}; // Enabled fail actions + std::array, ASSERT_CONTROL_SLOT_COUNT> m_assertKill{}; bool m_calcUnusedSigs = false; // Waves file on, need all signals calculated bool m_fatalOnError = true; // Fatal on $stop/non-fatal error bool m_fatalOnVpiError = true; // Fatal on vpi error/unsupported @@ -490,11 +507,13 @@ public: /// Clear enabled status for given assertion types void assertOnClear(VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE; - /// Apply a $assertcontrol control_type (IEEE 1800-2023 Table 20-5) to the given - /// assertion and directive types: 1=Lock, 2=Unlock, 3=On, 4=Off, 5=Kill. Locked - /// bits are left unchanged by On/Off/Kill. Other control_type values are ignored. + /// Apply assertion control for given control, assertion, and directive types void assertCtl(uint32_t controlType, VerilatedAssertType_t types, VerilatedAssertDirectiveType_t directives) VL_MT_SAFE; + /// Get assertion-control runtime state. Boolean queries return 0/1, Kill returns + /// the generation count. + uint32_t assertCtlGet(VerilatedAssertCtlQuery query, VerilatedAssertType_t type, + VerilatedAssertDirectiveType_t directive) const VL_MT_SAFE; /// Return if calculating of unused signals (for traces) bool calcUnusedSigs() const VL_MT_SAFE { return m_s.m_calcUnusedSigs; } /// Enable calculation of unused signals (for traces) diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 18df21153..8d4bf5275 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -226,12 +226,23 @@ class AssertVisitor final : public VNVisitor { bool m_inRestrict = false; // True inside restrict assertion AstNode* m_passsp = nullptr; // Current pass statement AstNode* m_failsp = nullptr; // Current fail statement + AstNodeCoverOrAssert* m_assertp = nullptr; // Current assertion AstFinal* m_finalp = nullptr; // Current final block // Map from (expression, senTree) to AstAlways that computes delayed values of the expression std::unordered_map, std::unordered_map, AstAlways*>> m_modExpr2Sen2DelayedAlwaysp; // METHODS + static string assertCtlGetCall(const char* query, VAssertType type, + VAssertDirectiveType directiveType) { + return "vlSymsp->_vm_contextp__->assertCtlGet(VerilatedAssertCtlQuery::"s + query + ", "s + + std::to_string(type) + ", "s + std::to_string(directiveType) + ")"s; + } + static const char* assertPassOnQuery(bool vacuous) { + static constexpr const char* queries[2] + = {"ASSERT_CTL_PASS_ON_NONVACUOUS", "ASSERT_CTL_PASS_ON_VACUOUS"}; + return queries[vacuous]; + } static AstNodeExpr* assertOnCond(FileLine* fl, VAssertType type, VAssertDirectiveType directiveType) { // cppcheck-suppress missingReturn @@ -251,9 +262,7 @@ class AssertVisitor final : public VNVisitor { case VAssertDirectiveType::ASSUME: { if (v3Global.opt.assertOn()) { return new AstCExpr{fl, AstCExpr::Pure{}, - "vlSymsp->_vm_contextp__->assertOnGet("s + std::to_string(type) - + ", "s + std::to_string(directiveType) + ")"s, - 1}; + assertCtlGetCall("ASSERT_CTL_ON", type, directiveType), 1}; } return new AstConst{fl, AstConst::BitFalse{}}; } @@ -269,6 +278,31 @@ class AssertVisitor final : public VNVisitor { } VL_UNREACHABLE; } + static string assertActionControlPrefix(VAssertDirectiveType directiveType) { + const int controlled = !!(static_cast(directiveType) + & (static_cast(VAssertDirectiveType::ASSERT) + | static_cast(VAssertDirectiveType::COVER) + | static_cast(VAssertDirectiveType::ASSUME))); + const int checkRuntime = controlled & static_cast(v3Global.opt.assertOn()); + return "("s + std::to_string(controlled ^ 1) + " || ("s + std::to_string(checkRuntime) + + " && "s; + } + static AstNodeExpr* assertPassOnCond(FileLine* fl, VAssertType type, + VAssertDirectiveType directiveType, bool vacuous) { + return new AstCExpr{fl, AstCExpr::Pure{}, + assertActionControlPrefix(directiveType) + + assertCtlGetCall(assertPassOnQuery(vacuous), type, directiveType) + + "))"s, + 1}; + } + static AstNodeExpr* assertFailOnCond(FileLine* fl, VAssertType type, + VAssertDirectiveType directiveType) { + return new AstCExpr{fl, AstCExpr::Pure{}, + assertActionControlPrefix(directiveType) + + assertCtlGetCall("ASSERT_CTL_FAIL_ON", type, directiveType) + + "))"s, + 1}; + } string assertDisplayMessage(const AstNode* nodep, const string& prefix, const string& message, VDisplayType severity) { if (severity == VDisplayType::DT_ERROR || severity == VDisplayType::DT_FATAL) { @@ -280,12 +314,6 @@ class AssertVisitor final : public VNVisitor { + cvtToStr(nodep->fileline()->lineno()) + ": %m" + ((message != "") ? ": " : "") + message + "\n"); } - // Default assertion_type and directive_type when the argument is omitted - // (IEEE 1800-2023 20.11): assertion_type defaults to all types, directive_type - // to Assert|Cover|Assume. - static constexpr uint8_t DEFAULT_DIRECTIVE_TYPES = VAssertDirectiveType::ASSERT - | VAssertDirectiveType::COVER - | VAssertDirectiveType::ASSUME; void replaceDisplay(AstDisplay* nodep, const string& prefix) { nodep->fmtp()->text( assertDisplayMessage(nodep, prefix, nodep->fmtp()->text(), nodep->displayType())); @@ -327,8 +355,8 @@ class AssertVisitor final : public VNVisitor { } static AstIf* newIfAssertOn(AstNode* bodyp, VAssertDirectiveType directiveType, VAssertType type = VAssertType::INTERNAL) { - // Add a internal if to check assertions are on. - // Don't make this a AND term, as it's unlikely to need to test this. + // Add an internal if to check assertions are on. + // Don't make this an AND term, as it's unlikely to need to test this. FileLine* const fl = bodyp->fileline(); AstNodeExpr* const condp = assertOnCond(fl, type, directiveType); @@ -337,6 +365,28 @@ class AssertVisitor final : public VNVisitor { newp->user2(true); // Mark as an assertOn() check return newp; } + static AstNodeStmt* newIfAssertPassOn(AstNode* bodyp, VAssertDirectiveType directiveType, + VAssertType type, bool vacuous) { + // Add an internal if to check assertion passOn is enabled. + // Don't make this an AND term, as it's unlikely to need to test this. + FileLine* const fl = bodyp->fileline(); + AstNodeExpr* const condp = assertPassOnCond(fl, type, directiveType, vacuous); + AstNodeIf* const newp = new AstIf{fl, condp, bodyp}; + newp->isBoundsCheck(true); // To avoid LATCH warning + newp->user1(true); // Don't assert/cover this if + return newp; + } + static AstNodeStmt* newIfAssertFailOn(AstNode* bodyp, VAssertDirectiveType directiveType, + VAssertType type) { + // Add an internal if to check assertion failOn is enabled. + // Don't make this an AND term, as it's unlikely to need to test this. + FileLine* const fl = bodyp->fileline(); + AstNodeExpr* const condp = assertFailOnCond(fl, type, directiveType); + AstNodeIf* const newp = new AstIf{fl, condp, bodyp}; + newp->isBoundsCheck(true); // To avoid LATCH warning + newp->user1(true); // Don't assert/cover this if + return newp; + } static AstIf* assertCond(const AstNodeCoverOrAssert* nodep, AstNodeExpr* propp, AstNode* passsp, AstNode* failsp) { @@ -493,6 +543,7 @@ class AssertVisitor final : public VNVisitor { if (failsp) failsp->unlinkFrBackWithNext(); bool selfDestruct = false; + bool passspGated = false; if (const AstCover* const snodep = VN_CAST(nodep, Cover)) { ++m_statCover; if (!v3Global.opt.coverageUser()) { @@ -504,6 +555,9 @@ class AssertVisitor final : public VNVisitor { covincp->unlinkFrBackWithNext(); // next() might have AstAssign for trace if (message != "") covincp->declp()->comment(message); if (passsp) { + passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(), + /*vacuous=*/false); + passspGated = true; passsp = AstNode::addNext(covincp, passsp); } else { passsp = covincp; @@ -524,8 +578,10 @@ class AssertVisitor final : public VNVisitor { VL_RESTORER(m_passsp); VL_RESTORER(m_failsp); + VL_RESTORER(m_assertp); m_passsp = passsp; m_failsp = failsp; + m_assertp = nodep; iterate(nodep->propp()); AstNode* propExprp; @@ -537,6 +593,15 @@ class AssertVisitor final : public VNVisitor { propExprp = nodep->propp()->unlinkFrBack(); } FileLine* const flp = nodep->fileline(); + bool passspAlreadyGated = false; + if (passsp && VN_IS(passsp, If)) passspAlreadyGated = VN_AS(passsp, If)->user1(); + if (passsp && !passspGated && !passspAlreadyGated && !VN_IS(propExprp, PExpr)) { + passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(), + /*vacuous=*/false); + } + if (failsp && !VN_IS(propExprp, PExpr)) { + failsp = newIfAssertFailOn(failsp, nodep->directive(), nodep->userType()); + } AstNode* bodysp = assertBody(nodep, propExprp, passsp, failsp); if (disablep) bodysp = new AstIf{flp, new AstLogNot{flp, disablep}, bodysp}; // Add assertOn check last, for better combining @@ -870,8 +935,11 @@ class AssertVisitor final : public VNVisitor { if (nodep->pass() && m_passsp) { // Cover adds COVERINC by AstNode::addNext, thus need to clone next too. stmtsp = m_passsp->cloneTree(true); + stmtsp = newIfAssertPassOn(stmtsp, m_assertp->directive(), m_assertp->userType(), + nodep->vacuous()); } else if (!nodep->pass() && m_failsp) { stmtsp = m_failsp->cloneTree(true); + stmtsp = newIfAssertFailOn(stmtsp, m_assertp->directive(), m_assertp->userType()); } if (stmtsp) { stmtsp->foreachAndNext([](AstNodeVarRef* const refp) { @@ -978,66 +1046,69 @@ class AssertVisitor final : public VNVisitor { } void visit(AstAssertCtl* nodep) override { iterateChildren(nodep); + + bool assertTypeConst = true; + if (!nodep->assertTypesp()) { + nodep->ctlAssertTypes(VAssertType{ALL_ASSERT_TYPES}); + } else if (const AstConst* const assertTypesp = VN_CAST(nodep->assertTypesp(), Const)) { + nodep->ctlAssertTypes(VAssertType{assertTypesp->toSInt()}); + } else { + assertTypeConst = false; + } + + bool controlTypeConst = false; + if (const AstConst* const constp = VN_CAST(nodep->controlTypep(), Const)) { + nodep->ctlType(constp->toSInt()); + controlTypeConst = true; + } + if (controlTypeConst + && (nodep->ctlType() < VAssertCtlType::LOCK + || nodep->ctlType() > VAssertCtlType::VACUOUS_OFF)) { + nodep->unlinkFrBack(); + nodep->v3error("Bad $assertcontrol control_type '" + << cvtToStr(static_cast(nodep->ctlType())) + << "' (IEEE 1800-2023 Table 20-5)"); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + if (assertTypeConst && nodep->ctlAssertTypes() != ALL_ASSERT_TYPES + && nodep->ctlAssertTypes().containsAny(VAssertType::UNIQUE | VAssertType::UNIQUE0 + | VAssertType::PRIORITY)) { + nodep->v3warn(E_UNSUPPORTED, "Unsupported: assert control assertion_type"); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } + + bool directiveTypeConst = true; + if (!nodep->directiveTypesp()) { + nodep->ctlDirectiveTypes(VAssertDirectiveType::ASSERT | VAssertDirectiveType::ASSUME + | VAssertDirectiveType::COVER); + } else if (const AstConst* const directiveTypesp + = VN_CAST(nodep->directiveTypesp(), Const)) { + nodep->ctlDirectiveTypes(VAssertDirectiveType{directiveTypesp->toSInt()}); + } else { + directiveTypeConst = false; + } + if (!directiveTypeConst) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: non-const assert directive type expression"); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } + FileLine* const fl = nodep->fileline(); - - // control_type, assertion_type and directive_type are integer expressions - // (IEEE 1800-2023 20.11) and may be non-constant; they are evaluated at runtime - // by VerilatedContext::assertCtl. The levels and scope/assertion-list arguments - // are not modeled -- control applies to the whole context. - // When control_type is a compile-time constant, reject the not-yet-modeled - // action-control codes (Table 20-5 values 6..11) and out-of-range values with a - // clear error rather than emitting a runtime no-op. - if (const AstConst* const controlp = VN_CAST(nodep->controlTypep(), Const)) { - const int32_t control = controlp->toSInt(); - if (control < VAssertCtlType::LOCK || control > VAssertCtlType::VACUOUS_OFF) { - nodep->unlinkFrBack(); - nodep->v3warn(EC_ERROR, "Bad $assertcontrol control_type '" - << control << "' (IEEE 1800-2023 Table 20-5)"); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - return; - } - if (control >= VAssertCtlType::PASS_ON) { - nodep->unlinkFrBack(); - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: $assertcontrol control_type '" << control << "'"); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - return; - } - } - - // When assertion_type is a compile-time constant, reject values that cannot be - // filtered at runtime because unique/priority violations use bare assertOn() rather - // than a per-type assertOnGet() call (IEEE 1800-2023 Table 20-6). + UINFO(9, "Generating assertctl for a module: " << m_modp); + AstCStmt* const newp = new AstCStmt{fl}; + newp->add("vlSymsp->_vm_contextp__->assertCtl("); + newp->add(nodep->controlTypep()->unlinkFrBack()); + newp->add(", "); if (nodep->assertTypesp()) { - if (const AstConst* const typecp = VN_CAST(nodep->assertTypesp(), Const)) { - if (typecp->toUInt() - & (VAssertType::EXPECT | VAssertType::UNIQUE | VAssertType::UNIQUE0 - | VAssertType::PRIORITY)) { - nodep->unlinkFrBack(); - nodep->v3warn(E_UNSUPPORTED, "Unsupported: assert control assertion_type"); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - return; - } - } - } - - UINFO(9, "Generating assertctl in module: " << m_modp); - AstCStmt* const callp = new AstCStmt{fl, "vlSymsp->_vm_contextp__->assertCtl("}; - callp->add(nodep->controlTypep()->unlinkFrBack()); - callp->add(", "); - if (AstNodeExpr* const typesp = nodep->assertTypesp()) { - callp->add(typesp->unlinkFrBack()); + newp->add(nodep->assertTypesp()->unlinkFrBack()); } else { - callp->add(std::to_string(ALL_ASSERT_TYPES)); + newp->add(std::to_string(ALL_ASSERT_TYPES)); } - callp->add(", "); - if (AstNodeExpr* const directivesp = nodep->directiveTypesp()) { - callp->add(directivesp->unlinkFrBack()); - } else { - callp->add(std::to_string(DEFAULT_DIRECTIVE_TYPES)); - } - callp->add(");\n"); - nodep->replaceWith(callp); + newp->add(", " + std::to_string(nodep->ctlDirectiveTypes()) + ");\n"); + nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void visit(AstAssertIntrinsic* nodep) override { // diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index 222d88128..b6ec6964f 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -198,6 +198,78 @@ static AstNodeExpr* sampled(AstNodeExpr* exprp) { return sp; } +static string assertCtlGetCall(const char* query, VAssertType type, + VAssertDirectiveType directiveType) { + return "vlSymsp->_vm_contextp__->assertCtlGet(VerilatedAssertCtlQuery::"s + query + ", "s + + std::to_string(type) + ", "s + std::to_string(directiveType) + ")"s; +} + +static const char* assertPassOnQuery(bool vacuous) { + static constexpr const char* queries[2] + = {"ASSERT_CTL_PASS_ON_NONVACUOUS", "ASSERT_CTL_PASS_ON_VACUOUS"}; + return queries[vacuous]; +} + +static AstNodeExpr* assertOnCond(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType) { + if (!v3Global.opt.assertOn()) { return new AstConst{flp, AstConst::BitFalse{}}; } + return new AstCExpr{flp, AstCExpr::Pure{}, + assertCtlGetCall("ASSERT_CTL_ON", type, directiveType), 1}; +} + +static AstNodeExpr* assertKillGet(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType) { + return new AstCExpr{flp, AstCExpr::Pure{}, + assertCtlGetCall("ASSERT_CTL_KILL", type, directiveType), 32}; +} + +static string assertActionControlPrefix(VAssertDirectiveType directiveType) { + const int controlled = !!(static_cast(directiveType) + & (static_cast(VAssertDirectiveType::ASSERT) + | static_cast(VAssertDirectiveType::COVER) + | static_cast(VAssertDirectiveType::ASSUME))); + const int checkRuntime = controlled & static_cast(v3Global.opt.assertOn()); + return "("s + std::to_string(controlled ^ 1) + " || ("s + std::to_string(checkRuntime) + + " && "s; +} + +static AstNodeExpr* assertPassOnCond(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType, bool vacuous) { + return new AstCExpr{flp, AstCExpr::Pure{}, + assertActionControlPrefix(directiveType) + + assertCtlGetCall(assertPassOnQuery(vacuous), type, directiveType) + + "))"s, + 1}; +} + +static AstNodeExpr* assertFailOnCond(FileLine* flp, VAssertType type, + VAssertDirectiveType directiveType) { + return new AstCExpr{flp, AstCExpr::Pure{}, + assertActionControlPrefix(directiveType) + + assertCtlGetCall("ASSERT_CTL_FAIL_ON", type, directiveType) + "))"s, + 1}; +} + +static AstIf* newPassOnIf(FileLine* flp, AstNodeExpr* firep, AstNode* bodyp, VAssertType type, + VAssertDirectiveType directiveType, bool vacuous) { + AstNodeExpr* const condp + = new AstLogAnd{flp, firep, assertPassOnCond(flp, type, directiveType, vacuous)}; + AstIf* const ifp = new AstIf{flp, condp, bodyp}; + ifp->isBoundsCheck(true); + ifp->user1(true); + return ifp; +} + +static AstNodeStmt* newIfAssertFailOn(AstNode* bodyp, VAssertDirectiveType directiveType, + VAssertType type) { + FileLine* const flp = bodyp->fileline(); + AstNodeExpr* const condp = assertFailOnCond(flp, type, directiveType); + AstIf* const ifp = new AstIf{flp, condp, bodyp}; + ifp->isBoundsCheck(true); + ifp->user1(true); + return ifp; +} + //###################################################################### // NFA Builder @@ -1280,6 +1352,9 @@ class SvaNfaLowering final { AstNodeExpr* matchCondp; // Final boolean match condition (may be nullptr) AstVar* disableCntVarp; // disable counter var (may be nullptr) AstVar* snapshotVarp; // disable snapshot var (may be nullptr) + VAssertType assertType; // Assertion type for control tasks + VAssertDirectiveType directiveType; // Directive type for control tasks + AstVar* killVarp; // Last observed kill generation SvaGraph& graph; // NFA graph }; @@ -1298,6 +1373,15 @@ class SvaNfaLowering final { if (!ap) return bp; return new AstLogOr{flp, ap, bp}; } + static AstNodeExpr* killActive(LowerCtx& c) { + return new AstNeq{c.flp, new AstVarRef{c.flp, c.killVarp, VAccess::READ}, + assertKillGet(c.flp, c.assertType, c.directiveType)}; + } + static AstNodeExpr* notKillActive(LowerCtx& c) { return new AstLogNot{c.flp, killActive(c)}; } + static AstNodeExpr* gateNotKill(LowerCtx& c, AstNodeExpr* exprp) { + if (!exprp) return nullptr; + return new AstLogAnd{c.flp, exprp, notKillActive(c)}; + } // Phase 3 output signals struct SignalSet final { @@ -1337,6 +1421,7 @@ class SvaNfaLowering final { UASSERT_OBJ(nextStatep, c.vtx[i], "Registered vertex has no clocked incoming contribution"); + nextStatep = gateNotKill(c, nextStatep); AstAssignDly* const assignp = new AstAssignDly{ c.flp, new AstVarRef{c.flp, c.vtx[i]->datap()->stateVarp, VAccess::WRITE}, @@ -1407,7 +1492,8 @@ class SvaNfaLowering final { = new AstEq{c.flp, new AstVarRef{c.flp, cntp, VAccess::READ}, new AstConst{c.flp, AstConst::WidthedValue{}, 32, counterMax}}; - AstNodeExpr* const donep = new AstLogOr{c.flp, matchedNowp, counterAtEndp}; + AstNodeExpr* const donep = new AstLogOr{ + c.flp, killActive(c), new AstLogOr{c.flp, matchedNowp, counterAtEndp}}; AstAssignDly* const clearActivep = new AstAssignDly{c.flp, new AstVarRef{c.flp, activep, VAccess::WRITE}, @@ -1427,7 +1513,8 @@ class SvaNfaLowering final { = new AstAssignDly{c.flp, new AstVarRef{c.flp, cntp, VAccess::WRITE}, new AstConst{c.flp, AstConst::WidthedValue{}, 32, 0u}}; setActivep->addNext(resetCountp); - AstIf* const startIfp = new AstIf{c.flp, incomingp, setActivep, nullptr}; + AstIf* const startIfp + = new AstIf{c.flp, gateNotKill(c, incomingp), setActivep, nullptr}; AstIf* const topIfp = new AstIf{c.flp, new AstVarRef{c.flp, activep, VAccess::READ}, doneIfp, startIfp}; @@ -1486,13 +1573,22 @@ class SvaNfaLowering final { AstIf* const setRIfp = new AstIf{c.flp, gateRp, setRp, nullptr}; setLIfp->addNext(setRIfp); - AstIf* const topp = new AstIf{ - c.flp, c.vtx[ai]->datap()->stateSigp->cloneTreePure(false), clearLp, setLIfp}; + AstNodeExpr* const clearCondp = new AstLogOr{ + c.flp, killActive(c), c.vtx[ai]->datap()->stateSigp->cloneTreePure(false)}; + AstIf* const topp = new AstIf{c.flp, clearCondp, clearLp, setLIfp}; m_modp->addStmtsp( new AstAlways{c.flp, VAlwaysKwd::ALWAYS, c.senTreep->cloneTree(false), topp}); } } + void emitKillAckNba(LowerCtx& c) { + AstAssignDly* const ackp + = new AstAssignDly{c.flp, new AstVarRef{c.flp, c.killVarp, VAccess::WRITE}, + assertKillGet(c.flp, c.assertType, c.directiveType)}; + m_modp->addStmtsp(new AstAlways{c.flp, VAlwaysKwd::ALWAYS, c.senTreep->cloneTree(false), + new AstIf{c.flp, killActive(c), ackp, nullptr}}); + } + // Phase 3/3a/3b: Compute terminal match/reject signals, required-step reject, // throughout-drop reject; clean up intermediate state signals. // Phase 3: terminalActive and rejectBase from Links to matchVertex. @@ -1610,7 +1706,7 @@ class SvaNfaLowering final { condp = tep->m_condp->cloneTreePure(false); } AstNodeExpr* const notCondp = new AstLogNot{c.flp, condp}; - AstNodeExpr* const failp = new AstLogAnd{c.flp, srcSigp, notCondp}; + AstNodeExpr* const failp = gateNotKill(c, new AstLogAnd{c.flp, srcSigp, notCondp}); if (outRequiredStepSrcsp) { outRequiredStepSrcsp->push_back(failp->cloneTreePure(false)); } @@ -1618,6 +1714,9 @@ class SvaNfaLowering final { } computeThroughoutReject(c, sigs); + sigs.terminalActivep = gateNotKill(c, sigs.terminalActivep); + sigs.rejectBasep = gateNotKill(c, sigs.rejectBasep); + sigs.throughoutRejectp = gateNotKill(c, sigs.throughoutRejectp); // Clean up intermediate state signals. These are orphan subtrees // (never linked into the enclosing AST); deleteTree() is immediate @@ -1826,7 +1925,9 @@ public: AstNodeExpr** outMatchpp = nullptr, AstVar* disableCntVarp = nullptr, AstVar* snapshotVarp = nullptr, std::vector* outRequiredStepSrcsp = nullptr, - std::vector* outPerMidSrcsp = nullptr) { + std::vector* outPerMidSrcsp = nullptr, + VAssertType assertType = VAssertType::INTERNAL, + VAssertDirectiveType directiveType = VAssertDirectiveType::INTERNAL) { const std::string baseName = m_names.get(""); // Number vertices with sequential colors for array indexing. @@ -1859,6 +1960,10 @@ public: } AstNodeDType* const u32DTypep = m_modp->findBasicDType(VBasicDTypeKwd::UINT32); + AstVar* const killVarp + = new AstVar{flp, VVarType::MODULETEMP, baseName + "__kill", u32DTypep}; + killVarp->lifetime(VLifetime::STATIC_EXPLICIT); + m_modp->addStmtsp(killVarp); for (int i = 0; i < N; ++i) { if (vtx[i]->m_isAndCombiner) { const std::string base = baseName + "__a" + std::to_string(i); @@ -1899,9 +2004,9 @@ public: } // Build lowering context for phase sub-functions. - LowerCtx c{flp, N, vtx, edges, startIdx, - matchIdx, senTreep, disableExprp, matchCondp, disableCntVarp, - snapshotVarp, graph}; + LowerCtx c{flp, N, vtx, edges, startIdx, + matchIdx, senTreep, disableExprp, matchCondp, disableCntVarp, + snapshotVarp, assertType, directiveType, killVarp, graph}; // Phase 1: Resolve combinational Links via fixed-point propagation. resolveLinks(c, triggerExprp); @@ -1910,6 +2015,7 @@ public: emitStateRegisterNba(c); emitCounterFsmNba(c); emitAndCombinerDoneLatchNba(c); + emitKillAckNba(c); // Phase 3/3a/3b: Compute terminal match/reject signals (cleans up stateSig). const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp, outPerMidSrcsp); @@ -2176,6 +2282,38 @@ class AssertNfaVisitor final : public VNVisitor { return parts; } + static bool canSplitImplicationPassActions(const PropertyParts& parts) { + UASSERT(parts.hasImplication, + "Implication pass action split requested without implication"); + UASSERT(parts.triggerExprp, "Implication pass action split requested without trigger"); + // Direct vacuous/nonvacuous classification uses the antecedent value in the current + // assertion attempt. Leave delayed antecedents on the existing NFA pass path. + return !hasMultiCycleExpr(parts.triggerExprp); + } + + static void splitImplicationPassActions(AstAssert* assertp, const PropertyParts& parts, + AstNodeExpr* nonvacuousMatchp) { + FileLine* const flp = assertp->fileline(); + + AstNode* const passsp = assertp->passsp()->unlinkFrBackWithNext(); + AstNode* splitsp = nullptr; + + if (!parts.isFollowedBy) { + AstNodeExpr* const vacuousp + = new AstLogNot{flp, sampled(parts.triggerExprp->cloneTreePure(false))}; + AstNode* const vacuousBodyp = passsp->cloneTree(false); + splitsp = newPassOnIf(flp, vacuousp, vacuousBodyp, assertp->userType(), + assertp->directive(), /*vacuous=*/true); + } + + AstIf* const nonvacuousp + = newPassOnIf(flp, nonvacuousMatchp, passsp, assertp->userType(), assertp->directive(), + /*vacuous=*/false); + splitsp = splitsp ? AstNode::addNext(splitsp, nonvacuousp) + : static_cast(nonvacuousp); + assertp->addPasssp(splitsp); + } + // Allocate disable-iff counter + snapshot vars and unlink the original // disable expression from the PropSpec. Returns {cntp, snapp} or // {nullptr, nullptr} if no counter is needed. @@ -2311,7 +2449,11 @@ class AssertNfaVisitor final : public VNVisitor { cumulativeOrp->cloneTreePure(false)}; m_modp->addStmtsp( new AstAlways{flp, VAlwaysKwd::ALWAYS, perSrcSenTreep->cloneTree(false), - new AstIf{flp, condp, failsp->cloneTree(true), nullptr}}); + new AstIf{flp, condp, + newIfAssertFailOn(failsp->cloneTree(true), + assertWithFailp->directive(), + assertWithFailp->userType()), + nullptr}}); cumulativeOrp = new AstLogOr{flp, cumulativeOrp, srcp->cloneTreePure(false)}; } VL_DO_DANGLING(pushDeletep(cumulativeOrp), cumulativeOrp); @@ -2510,8 +2652,11 @@ class AssertNfaVisitor final : public VNVisitor { const bool disableExprUnlinked = disableCntVarp && disableExprp; AstAssert* const assertAssertp = VN_CAST(assertp, Assert); - const bool needMatch - = !isCover && !parts.hasImplication && assertAssertp && assertAssertp->passsp(); + const bool splitImplicationPasssp = assertAssertp && assertAssertp->passsp() + && parts.hasImplication + && canSplitImplicationPassActions(parts); + const bool needMatch = assertAssertp && assertAssertp->passsp() + && (!parts.hasImplication || splitImplicationPasssp); AstNodeExpr* matchExprp = nullptr; AstAssert* const assertWithFailp = VN_CAST(assertp, Assert); @@ -2525,12 +2670,14 @@ class AssertNfaVisitor final : public VNVisitor { // coverp / isCoverSeq are computed earlier (passed to SvaNfaBuilder). std::vector perMidSrcs; - AstNodeExpr* const alwaysTriggerp = new AstConst{flp, AstConst::BitTrue{}}; + AstNodeExpr* const alwaysTriggerp + = assertOnCond(flp, assertp->userType(), assertp->directive()); AstNodeExpr* const outputExprp = m_loweringp->lower( flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover, disableExprp ? disableExprp->cloneTreePure(false) : nullptr, negated, needMatch ? &matchExprp : nullptr, disableCntVarp, snapshotVarp, - needPerSrcFail ? &requiredStepSrcs : nullptr, isCoverSeq ? &perMidSrcs : nullptr); + needPerSrcFail ? &requiredStepSrcs : nullptr, isCoverSeq ? &perMidSrcs : nullptr, + assertp->userType(), assertp->directive()); AstSenTree* const perSrcSenTreep = (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr; @@ -2540,8 +2687,15 @@ class AssertNfaVisitor final : public VNVisitor { if (disableExprUnlinked) VL_DO_DANGLING(pushDeletep(disableExprp), disableExprp); if (result.finalCondp && !result.finalCondp->backp()) pushDeletep(result.finalCondp); - attachMatchHandlers(flp, assertAssertp, assertWithFailp, needMatch ? matchExprp : nullptr, - perSrcSenTreep, requiredStepSrcs); + if (splitImplicationPasssp) { + splitImplicationPassActions(assertAssertp, parts, matchExprp); + matchExprp = nullptr; + } else { + attachMatchHandlers(flp, assertAssertp, assertWithFailp, + needMatch ? matchExprp : nullptr, perSrcSenTreep, + requiredStepSrcs); + matchExprp = nullptr; + } if (isCoverSeq && perMidSrcs.size() > 1) { // Clone AstCover (N-1) times, each gated by its own per-mid signal. diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 46c49b515..25de3ad08 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -942,13 +942,16 @@ public: class AstPExprClause final : public AstNodeStmt { const bool m_pass; // True if will be replaced by passing assertion clause, false for // assertion failure clause + const bool m_vacuous; // True if pass is vacuous public: ASTGEN_MEMBERS_AstPExprClause; - explicit AstPExprClause(FileLine* fl, bool pass = true) + explicit AstPExprClause(FileLine* fl, bool pass = true, bool vacuous = false) : ASTGEN_SUPER_PExprClause(fl) - , m_pass{pass} {} + , m_pass{pass} + , m_vacuous{vacuous} {} bool pass() const { return m_pass; } + bool vacuous() const { return m_vacuous; } }; class AstPrintTimeScale final : public AstNodeStmt { // Parents: stmtlist diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 524a39222..2a3197112 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -419,20 +419,14 @@ class EmitCHeader final : public EmitCConstInit { puts("return !(*this == rhs);\n}\n"); putns(sdtypep, "\nbool operator<(const " + EmitCUtil::prefixNameProtect(sdtypep) + "& rhs) const {\n"); - puts("return "); - puts("std::tie("); for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { - if (itemp != sdtypep->membersp()) puts(", "); - putns(itemp, itemp->nameProtect()); + putns(itemp, "if (" + itemp->nameProtect() + " < rhs." + itemp->nameProtect() + + ") return true;\n"); + putns(itemp, "if (rhs." + itemp->nameProtect() + " < " + itemp->nameProtect() + + ") return false;\n"); } - puts(")\n < std::tie("); - for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; - itemp = VN_AS(itemp->nextp(), MemberDType)) { - if (itemp != sdtypep->membersp()) puts(", "); - putns(itemp, "rhs." + itemp->nameProtect()); - } - puts(");\n"); + puts("return false;\n"); puts("}\n"); puts("};\n"); puts("template <>\n"); diff --git a/test_regress/t/t_assert_basic.v b/test_regress/t/t_assert_basic.v index 7d96ac19c..55360519d 100644 --- a/test_regress/t/t_assert_basic.v +++ b/test_regress/t/t_assert_basic.v @@ -13,6 +13,24 @@ module t ( integer cyc; initial cyc = 1; wire [7:0] cyc_copy = cyc[7:0]; + typedef enum logic { CAST_ONE = 1'b1 } cast_e; + cast_e cast_dst; + integer action_hits = 0; + + assert property (@(posedge clk) 1'b1) + if (cyc >= 0) action_hits++; + + assert property (@(posedge clk) 1'b1) + action_hits++; + else + action_hits--; + + cover property (@(posedge clk) 1'b1) + action_hits++; + + initial begin + $cast(cast_dst, 1); + end always @(negedge clk) begin AssertionFalse1 : assert (cyc < 100); @@ -26,6 +44,12 @@ module t ( if (cyc != 0) begin cyc <= cyc + 1; toggle <= !cyc[0]; + assert (cyc >= 0) + if (cyc >= 0) action_hits++; + assert (cyc >= 0) + action_hits++; + else + action_hits--; if (cyc == 7) assert (cyc[0] == cyc[1]); // bug743 if (cyc == 9) begin `ifdef FAILING_ASSERTIONS diff --git a/test_regress/t/t_assert_ctl_arg.cpp b/test_regress/t/t_assert_ctl_arg.cpp index 7e386b5b0..684db6436 100644 --- a/test_regress/t/t_assert_ctl_arg.cpp +++ b/test_regress/t/t_assert_ctl_arg.cpp @@ -82,6 +82,61 @@ void verilatedTest() { contextp->assertOn(false); // Now everything is disabled TEST_CHECK_Z(contextp->assertOn()); + + // Unified runtime query getter + using Query = VerilatedAssertCtlQuery; + constexpr uint32_t LOCK = 1; + constexpr uint32_t UNLOCK = 2; + constexpr uint32_t ON = 3; + constexpr uint32_t OFF = 4; + constexpr uint32_t KILL = 5; + constexpr uint32_t PASS_ON = 6; + constexpr uint32_t PASS_OFF = 7; + constexpr uint32_t FAIL_ON = 8; + constexpr uint32_t FAIL_OFF = 9; + constexpr uint32_t NONVACUOUS_ON = 10; + constexpr uint32_t VACUOUS_OFF = 11; + constexpr uint32_t TYPE = 1; + constexpr uint32_t DIRECTIVE = 1; + + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, 0, DIRECTIVE)); + + contextp->assertCtl(LOCK, TYPE, DIRECTIVE); + contextp->assertCtl(ON, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(UNLOCK, TYPE, DIRECTIVE); + contextp->assertCtl(ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(OFF, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + + const uint32_t killBefore = contextp->assertCtlGet(Query::ASSERT_CTL_KILL, TYPE, DIRECTIVE); + contextp->assertCtl(KILL, TYPE, DIRECTIVE); + TEST_CHECK_EQ(contextp->assertCtlGet(Query::ASSERT_CTL_KILL, TYPE, DIRECTIVE), killBefore + 1); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_ON, TYPE, DIRECTIVE)); + + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + contextp->assertCtl(PASS_OFF, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + + contextp->assertCtl(NONVACUOUS_ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + contextp->assertCtl(PASS_ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + contextp->assertCtl(VACUOUS_OFF, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_NONVACUOUS, TYPE, DIRECTIVE)); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_PASS_ON_VACUOUS, TYPE, DIRECTIVE)); + + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_FAIL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(FAIL_OFF, TYPE, DIRECTIVE); + TEST_CHECK_Z(contextp->assertCtlGet(Query::ASSERT_CTL_FAIL_ON, TYPE, DIRECTIVE)); + contextp->assertCtl(FAIL_ON, TYPE, DIRECTIVE); + TEST_CHECK_NZ(contextp->assertCtlGet(Query::ASSERT_CTL_FAIL_ON, TYPE, DIRECTIVE)); } int main(int argc, char** argv) { verilatedTest(); diff --git a/test_regress/t/t_assert_ctl_arg_unsup.out b/test_regress/t/t_assert_ctl_arg_unsup.out index da5e1145d..8325ccdf1 100644 --- a/test_regress/t/t_assert_ctl_arg_unsup.out +++ b/test_regress/t/t_assert_ctl_arg_unsup.out @@ -1,14 +1,18 @@ %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:15:5: Unsupported: assert control assertion_type - 15 | $assertcontrol(OFF, EXPECT); + : ... note: In instance 't' + 15 | $assertcontrol(OFF, UNIQUE); | ^~~~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:16:5: Unsupported: assert control assertion_type - 16 | $assertcontrol(OFF, UNIQUE); + : ... note: In instance 't' + 16 | $assertcontrol(OFF, UNIQUE0); | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:17:5: Unsupported: assert control assertion_type - 17 | $assertcontrol(OFF, UNIQUE0); + : ... note: In instance 't' + 17 | $assertcontrol(OFF, PRIORITY); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:18:5: Unsupported: assert control assertion_type - 18 | $assertcontrol(OFF, PRIORITY); +%Error-UNSUPPORTED: t/t_assert_ctl_arg_unsup.v:18:5: Unsupported: non-const assert directive type expression + : ... note: In instance 't' + 18 | $assertcontrol(OFF, 1, directive_type); | ^~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assert_ctl_arg_unsup.v b/test_regress/t/t_assert_ctl_arg_unsup.v index b041652f2..c82410860 100644 --- a/test_regress/t/t_assert_ctl_arg_unsup.v +++ b/test_regress/t/t_assert_ctl_arg_unsup.v @@ -6,15 +6,15 @@ module t; let OFF = 4; - let EXPECT = 16; let UNIQUE = 32; let UNIQUE0 = 64; let PRIORITY = 128; + logic directive_type = 1'b1; initial begin - $assertcontrol(OFF, EXPECT); $assertcontrol(OFF, UNIQUE); $assertcontrol(OFF, UNIQUE0); $assertcontrol(OFF, PRIORITY); + $assertcontrol(OFF, 1, directive_type); end endmodule diff --git a/test_regress/t/t_assert_ctl_concurrent.v b/test_regress/t/t_assert_ctl_concurrent.v index 3cdda2bc1..c7d62648a 100644 --- a/test_regress/t/t_assert_ctl_concurrent.v +++ b/test_regress/t/t_assert_ctl_concurrent.v @@ -7,32 +7,49 @@ module t; bit clock = 1'b0; - bit reset = 1'b0; + bit start = 1'b0; + bit done = 1'b0; + int concurrent_fails = 0; initial begin + $asserton; + + @(negedge clock); + start = 1'b1; + done = 1'b0; + + @(negedge clock); + start = 1'b0; $assertkill; + $asserton; - #10 reset = 1'b1; - $display("%t: deassert reset %d", $time, reset); + @(posedge clock); + #1; + if (concurrent_fails != 0) $stop; - #40 $asserton; + @(negedge clock); + start = 1'b1; + done = 1'b0; - reset = 1'b0; - $display("%t: deassert reset %d", $time, reset); + @(negedge clock); + start = 1'b0; - #200 $display("%t: finish", $time); + @(posedge clock); + #1; + if (concurrent_fails != 1) $stop; + + $assertcontrol(5, 1, 1); + $asserton; + + $display("%t: finish", $time); $write("*-* All Finished *-*\n"); $finish; - end - always #10 clock = ~clock; - reg r = 1'b0; - - always @(posedge clock) if (reset) r <= 1'b1; + always #5 clock = ~clock; assert_test : - assert property (@(posedge clock) (reset | r)) - else $error("%t: assertion triggered", $time); + assert property (@(posedge clock) start |-> ##1 done) + else concurrent_fails++; endmodule diff --git a/test_regress/t/t_assert_ctl_unsup.py b/test_regress/t/t_assert_ctl_pass_actions.py similarity index 70% rename from test_regress/t/t_assert_ctl_unsup.py rename to test_regress/t/t_assert_ctl_pass_actions.py index da00b062f..fdd1c0d4d 100755 --- a/test_regress/t/t_assert_ctl_unsup.py +++ b/test_regress/t/t_assert_ctl_pass_actions.py @@ -4,13 +4,15 @@ # This program is free software; you can redistribute it and/or modify it # under the terms of either the GNU Lesser General Public License Version 3 # or the Perl Artistic License Version 2.0. -# SPDX-FileCopyrightText: 2024 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap -test.scenarios('linter') +test.scenarios('vlt') -test.lint(verilator_flags2=['--assert'], fails=True, expect_filename=test.golden_filename) +test.compile(verilator_flags2=["--binary --assert"]) + +test.execute() test.passes() diff --git a/test_regress/t/t_assert_ctl_pass_actions.v b/test_regress/t/t_assert_ctl_pass_actions.v new file mode 100644 index 000000000..3768f4bd1 --- /dev/null +++ b/test_regress/t/t_assert_ctl_pass_actions.v @@ -0,0 +1,214 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin \ + $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", \ + `__FILE__, `__LINE__, (gotv), (expv), `"gotv`", `"expv`"); \ + `stop; \ +end while (0) +// verilog_format: on + +`ifdef VERILATOR +// The '$c(1)' is there to prevent inlining of the signal by V3Gate. +`define IMPURE_ONE ($c(1)) +`else +// Use standard $random. The chance of getting 2 consecutive zeroes is negligible. +`define IMPURE_ONE (|($random | $random)) +`endif + +interface AssertCtlIface; + bit run_initial = 0; + bit initial_done = 0; + int fails = 0; + + function void clear(); + fails = 0; + endfunction + function void assert_off(); + $assertcontrol(4, 2, 1); + endfunction + function void assert_on(); + $assertcontrol(3, 2, 1); + endfunction + function void fail_check(); + assert (0) `stop; else fails++; + endfunction + function void run_checks(); + assert_off(); + fail_check(); + assert_on(); + fail_check(); + endfunction + + initial begin + wait (run_initial); + run_checks(); + initial_done = 1; + end +endinterface + +module t; + bit clk = 0; + int imm_passes = 0; + int imm_fails = 0; + int vacuous_passes = 0; + int nonvacuous_passes = 0; + int concurrent_fails = 0; + int class_fails = 0; + + class AssertCtlClass; + function void assert_off(); + $assertcontrol(4, 2, 1); + endfunction + function void assert_on(); + $assertcontrol(3, 2, 1); + endfunction + function void fail_check(); + assert (0) `stop; else class_fails++; + endfunction + endclass + + AssertCtlClass assert_ctl_class; + AssertCtlIface assert_ctl_iface(); + virtual AssertCtlIface v_assert_ctl_iface = assert_ctl_iface; + + always #5 clk = !clk; + + default clocking @(posedge clk); + endclocking + + assert property (@(posedge clk) 1'b0 |-> ##1 1'b1) begin + vacuous_passes++; + end else + `stop; + + assert property (@(posedge clk) 1'b1 |-> ##1 1'b1) begin + nonvacuous_passes++; + end else + `stop; + + assert property (@(posedge clk) 1'b1 |-> ##1 1'b0) begin + end else + concurrent_fails++; + + task automatic tick_and_check(input int exp_vacuous, input int exp_nonvacuous, + input int exp_concurrent_fails); + @(posedge clk); + #2; + `checkd(vacuous_passes, exp_vacuous); + `checkd(nonvacuous_passes, exp_nonvacuous); + `checkd(concurrent_fails, exp_concurrent_fails); + endtask + + initial begin + assert_ctl_class = new; + + $assertcontrol(4, 16, 1); + $assertcontrol(5, 16, 1); + $assertcontrol(3*`IMPURE_ONE, 2*`IMPURE_ONE); + + $assertcontrol(3, 255, 7); + $assertcontrol(6, 255, 7); + $assertcontrol(8, 255, 7); + + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 1); + + $assertcontrol(1, 2, 1); + $assertcontrol(4, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 2); + + $assertcontrol(2, 2, 1); + $assertcontrol(4, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 2); + + $assertcontrol(3, 2, 1); + + $assertcontrol(1, 2, 1); + $assertcontrol(7, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 3); + + $assertcontrol(2, 2, 1); + $assertcontrol(7, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 3); + + $assertcontrol(10, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 4); + + $assertcontrol(11, 2, 1); + assert (1) imm_passes++; else `stop; + `checkd(imm_passes, 5); + + $assertcontrol(6, 2, 1); + + assert (0) `stop; else imm_fails++; + `checkd(imm_fails, 1); + + $assertcontrol(1, 2, 1); + $assertcontrol(9, 2, 1); + assert (0) `stop; else imm_fails++; + `checkd(imm_fails, 2); + + $assertcontrol(2, 2, 1); + $assertcontrol(9, 2, 1); + assert (0) `stop; else imm_fails++; + `checkd(imm_fails, 2); + + $assertcontrol(8, 2, 1); + assert (0) `stop; else imm_fails++; + `checkd(imm_fails, 3); + + assert_ctl_class.assert_off(); + assert_ctl_class.fail_check(); + `checkd(class_fails, 0); + + assert_ctl_class.assert_on(); + assert_ctl_class.fail_check(); + `checkd(class_fails, 1); + + assert_ctl_iface.run_initial = 1; + wait (assert_ctl_iface.initial_done); + `checkd(assert_ctl_iface.fails, 1); + + assert_ctl_iface.clear(); + assert_ctl_iface.run_checks(); + `checkd(assert_ctl_iface.fails, 1); + + assert_ctl_iface.clear(); + v_assert_ctl_iface.run_checks(); + `checkd(assert_ctl_iface.fails, 1); + + $assertcontrol(9, 1, 1); + + tick_and_check(1, 0, 0); + tick_and_check(2, 1, 0); + + $assertcontrol(11, 1, 1); + tick_and_check(2, 2, 0); + + $assertcontrol(7, 1, 1); + tick_and_check(2, 2, 0); + + $assertcontrol(10, 1, 1); + tick_and_check(2, 3, 0); + + $assertcontrol(6, 1, 1); + tick_and_check(3, 4, 0); + + $assertcontrol(8, 1, 1); + tick_and_check(4, 5, 1); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_assert_ctl_type_runtime_bad.out b/test_regress/t/t_assert_ctl_type_runtime_bad.out new file mode 100644 index 000000000..aaa9ff404 --- /dev/null +++ b/test_regress/t/t_assert_ctl_type_runtime_bad.out @@ -0,0 +1 @@ +%Warning: Bad $assertcontrol control_type '100' (IEEE 1800-2023 Table 20-5) diff --git a/test_regress/t/t_assert_ctl_type_runtime_bad.py b/test_regress/t/t_assert_ctl_type_runtime_bad.py new file mode 100755 index 000000000..84d6702d8 --- /dev/null +++ b/test_regress/t/t_assert_ctl_type_runtime_bad.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary', '--assert']) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_assert_ctl_type_runtime_bad.v b/test_regress/t/t_assert_ctl_type_runtime_bad.v new file mode 100644 index 000000000..d35421daf --- /dev/null +++ b/test_regress/t/t_assert_ctl_type_runtime_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +`ifdef VERILATOR +// The '$c1(1)' is there to prevent inlining of the signal by V3Gate +`define IMPURE_ONE ($c(1)) +`else +// Use standard $random (chaces of getting 2 consecutive zeroes is zero). +`define IMPURE_ONE (|($random | $random)) +`endif + +module t; + initial begin + $assertcontrol(100*`IMPURE_ONE); + $finish; + end +endmodule diff --git a/test_regress/t/t_assert_disabled.py b/test_regress/t/t_assert_disabled.py index 3a4d31c50..390d36ef3 100755 --- a/test_regress/t/t_assert_disabled.py +++ b/test_regress/t/t_assert_disabled.py @@ -10,9 +10,9 @@ import vltest_bootstrap test.scenarios('simulator') -test.top_filename = "t/t_assert_on.v" +test.top_filename = "t/t_assert_disabled.v" -test.compile(verilator_flags2=['--no-assert']) +test.compile(verilator_flags2=['--no-assert', '--timing']) test.execute() diff --git a/test_regress/t/t_assert_disabled.v b/test_regress/t/t_assert_disabled.v new file mode 100644 index 000000000..447253c68 --- /dev/null +++ b/test_regress/t/t_assert_disabled.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t ( + input clk +); + + integer action_hits = 0; + integer cyc = 0; + + assert property (@(posedge clk) ##1 1'b1) + action_hits++; + else + action_hits--; + + always @(posedge clk) begin + cyc++; + assert (0); + if (cyc == 4) begin + `checkd(action_hits, 0); + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_assert_opt_check.py b/test_regress/t/t_assert_opt_check.py index 884e2876c..62b5a95d0 100755 --- a/test_regress/t/t_assert_opt_check.py +++ b/test_regress/t/t_assert_opt_check.py @@ -16,6 +16,6 @@ test.compile(verilator_flags2=['--binary', '--stats']) test.execute(check_finished=True) test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 3) -test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 15) +test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 16) test.passes() diff --git a/test_regress/t/t_assertcontrol.py b/test_regress/t/t_assertcontrol.py new file mode 100755 index 000000000..fdd1c0d4d --- /dev/null +++ b/test_regress/t/t_assertcontrol.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary --assert"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assertcontrol.v b/test_regress/t/t_assertcontrol.v new file mode 100644 index 000000000..26f9d360b --- /dev/null +++ b/test_regress/t/t_assertcontrol.v @@ -0,0 +1,113 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin \ + $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", \ + `__FILE__, `__LINE__, (gotv), (expv), `"gotv`", `"expv`"); \ + `stop; \ +end while (0) +// verilog_format: on + +module t; + let LOCK = 1; + let UNLOCK = 2; + let ON = 3; + let OFF = 4; + let PASS_ON = 6; + let PASS_OFF = 7; + let FAIL_ON = 8; + let FAIL_OFF = 9; + + let SIMPLE_IMMEDIATE = 2; + let ASSERT = 1; + + int pass_count = 0; + int fail_count = 0; + int ctl_type = 0; + + task automatic run_pass(); + assert (1) begin + pass_count++; + end else + `stop; + endtask + + task automatic run_fail(); + assert (0) + `stop; + else begin + fail_count++; + end + endtask + + initial begin + $assertcontrol(ON, 255, 7); + $assertcontrol(PASS_ON, 255, 7); + $assertcontrol(FAIL_ON, 255, 7); + + run_pass(); + `checkd(pass_count, 1); + + // Lock preserves the enabled checking state against OFF. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + ctl_type = OFF; + $assertcontrol(ctl_type, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 2); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(OFF, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 2); + + // Lock preserves the disabled checking state against ON. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(ON, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 2); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(ON, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 3); + + // Lock also preserves pass-action state. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(PASS_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 4); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(PASS_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_pass(); + `checkd(pass_count, 4); + + $assertcontrol(PASS_ON, SIMPLE_IMMEDIATE, ASSERT); + + run_fail(); + `checkd(fail_count, 1); + + // Lock also preserves fail-action state. + $assertcontrol(LOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(FAIL_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_fail(); + `checkd(fail_count, 2); + + $assertcontrol(UNLOCK, SIMPLE_IMMEDIATE, ASSERT); + $assertcontrol(FAIL_OFF, SIMPLE_IMMEDIATE, ASSERT); + run_fail(); + `checkd(fail_count, 2); + + $assertcontrol(FAIL_ON, SIMPLE_IMMEDIATE, ASSERT); + run_fail(); + `checkd(fail_count, 3); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_assertcontrol_noinl.py b/test_regress/t/t_assertcontrol_noinl.py new file mode 100755 index 000000000..cbc5575bf --- /dev/null +++ b/test_regress/t/t_assertcontrol_noinl.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') +test.top_filename = "t_assertcontrol.v" + +test.compile(verilator_flags2=["--binary --assert --fno-inline"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_prop_followed_by.v b/test_regress/t/t_prop_followed_by.v index 0fea33f5b..c1e5778a6 100644 --- a/test_regress/t/t_prop_followed_by.v +++ b/test_regress/t/t_prop_followed_by.v @@ -25,9 +25,12 @@ module t ( integer impl_f = 0; integer nimp_f = 0; integer wide_f = 0; + integer action_hits = 0; // Smoke: trivially-true forms must compile and never fail. assert property (@(posedge clk) 1'b1 #-# 1'b1); + assert property (@(posedge clk) 1'b1 #-# 1'b1) + action_hits++; assert property (@(posedge clk) 0 |-> (0 #-# 0)); assert property (@(posedge clk) 0 |-> (0 #=# 0)); diff --git a/test_regress/t/t_property_sexpr_cov.v b/test_regress/t/t_property_sexpr_cov.v index 59fa3ddc9..ef58a1fd8 100644 --- a/test_regress/t/t_property_sexpr_cov.v +++ b/test_regress/t/t_property_sexpr_cov.v @@ -52,4 +52,12 @@ module t ( /*AUTOARG*/ cover property (@(posedge clk) ##3 val[0] && val[1]) $display("[%0t] concurrent cover, fileline:%0d", $time, `__LINE__); + + integer action_hits = 0; + + assert property (@(posedge clk) ##1 1'b1) + action_hits++; + + assert property (@(posedge clk) (val[0] ##1 val[1]) |-> 1'b1) + action_hits++; endmodule diff --git a/test_regress/t/t_sequence_within.v b/test_regress/t/t_sequence_within.v index a8ec28b25..093b7afcb 100644 --- a/test_regress/t/t_sequence_within.v +++ b/test_regress/t/t_sequence_within.v @@ -91,6 +91,8 @@ module t ( (a ##3 b) intersect ((c ##1 d) within (a ##3 b))) count_p10 <= count_p10 + 1; + initial $assertvacuousoff; + always_ff @(posedge clk) begin cyc <= cyc + 1; crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; @@ -103,11 +105,11 @@ module t ( // p1/p2/p5 use |->; the NFA currently fires the pass action on // vacuous passes too, so counts are inflated vs. Questa. Pre-existing // engine-wide behavior, not within-specific. - `checkd(count_p1, 89); // Questa: 23 - `checkd(count_p2, 89); // Questa: 44 + `checkd(count_p1, 23); // Questa: 23 + `checkd(count_p2, 44); // Questa: 44 `checkd(count_p3, 26); // Questa: 20 `checkd(count_p4, 24); // Questa: 22 - `checkd(count_p5, 89); // Questa: 26 + `checkd(count_p5, 26); // Questa: 26 `checkd(count_p6, 21); // Questa: 16 `checkd(count_p7, 15); // Questa: 9 `checkd(count_p8, 15); // Questa: 4 From 756bcf574241ffa673ddf9d3e7a4eab9fdb0eab1 Mon Sep 17 00:00:00 2001 From: Matthew Ballance Date: Mon, 22 Jun 2026 19:14:43 -0700 Subject: [PATCH 82/89] Fix '$' as unsupported coverpoint-bin range bounds (#7750) (#7825) --- src/V3Covergroup.cpp | 86 +++++++++------- test_regress/t/t_covergroup_array_bins.out | 16 +++ test_regress/t/t_covergroup_array_bins.v | 103 +++++++++++++++++++ test_regress/t/t_covergroup_autobins_bad.out | 90 +++++++++------- test_regress/t/t_covergroup_autobins_bad.v | 11 ++ 5 files changed, 229 insertions(+), 77 deletions(-) diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 0d43e9f60..054f27c34 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -655,22 +655,54 @@ class FunctionalCoverageVisitor final : public VNVisitor { return refp; } - // Individual equality targets of an array bin (bins b[N] = {values/ranges}), in order. - std::vector extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp) { + // Individual equality targets of an array bin (bins b[] = {values/ranges}), in order. + // An open-ended bound ('$', AstUnbounded) resolves to the coverpoint domain: '[lo:$]' + // covers [lo:maxVal] and '[$:hi]' covers [0:hi]. One target is produced per value; a + // range whose resolved size would exceed COVER_BINS_LIMIT (e.g. an open '[lo:$]' over a + // wide coverpoint) is unsupported -- emits COVERIGN, sets unsupportedOut, yields nothing. + std::vector extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp, + bool& unsupportedOut) { + unsupportedOut = false; + const int width = exprp->width(); + const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1); std::vector values; for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { if (AstInsideRange* const irp = VN_CAST(rangep, InsideRange)) { - AstConst* const minp = VN_CAST(V3Const::constifyEdit(irp->lhsp()), Const); - AstConst* const maxp = VN_CAST(V3Const::constifyEdit(irp->rhsp()), Const); - if (!minp || !maxp) { + AstNodeExpr* const lhsp = V3Const::constifyEdit(irp->lhsp()); + AstNodeExpr* const rhsp = V3Const::constifyEdit(irp->rhsp()); + const bool loUnb = VN_IS(lhsp, Unbounded); + const bool hiUnb = VN_IS(rhsp, Unbounded); + AstConst* const minp = VN_CAST(lhsp, Const); + AstConst* const maxp = VN_CAST(rhsp, Const); + if ((!minp && !loUnb) || (!maxp && !hiUnb)) { arrayBinp->v3error("Non-constant expression in array bins range; " "range bounds must be constants"); return values; } - for (int val = minp->toSInt(); val <= maxp->toSInt(); ++val) - values.push_back(new AstConst{irp->fileline(), AstConst::WidthedValue{}, - static_cast(exprp->width()), - static_cast(val)}); + if ((minp && minp->num().isFourState()) || (maxp && maxp->num().isFourState())) { + arrayBinp->v3error("Four-state (x/z) value in array bins range bound; " + "range bounds must be two-state constants"); + return values; + } + const uint64_t lo = loUnb ? 0 : minp->toUQuad(); + const uint64_t hi = hiUnb ? maxVal : maxp->toUQuad(); + if (hi < lo) continue; // empty range contributes no bins + // Guard against a '$'-bounded or otherwise huge range exploding the bin count. + const uint64_t span = hi - lo; // == valueCount - 1 (no overflow: hi >= lo) + if (span >= static_cast(COVER_BINS_LIMIT) + || values.size() + span + 1 > static_cast(COVER_BINS_LIMIT)) { + arrayBinp->v3warn(COVERIGN, "Unsupported: array 'bins' covering more than " + << COVER_BINS_LIMIT + << " values (e.g. an open '[lo:$]' range over " + "a wide coverpoint); bin ignored"); + unsupportedOut = true; + for (AstNodeExpr* const vp : values) VL_DO_DANGLING(pushDeletep(vp), vp); + values.clear(); + return values; + } + for (uint64_t v = lo; v <= hi; ++v) + values.push_back(new AstConst{irp->fileline(), AstConst::WidthedValue{}, width, + static_cast(v)}); } else { values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); } @@ -744,7 +776,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { continue; } if (cbinp->isArray()) { // value array: bins b[N] = {...} -> b[0]..b[N-1] - std::vector values = extractArrayValues(cbinp, exprp); + bool unsupported = false; + std::vector values = extractArrayValues(cbinp, exprp, unsupported); + if (unsupported) continue; // bin ignored (COVERIGN emitted); reserve no slot namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast(values.size()))); for (AstNodeExpr* valuep : values) { emitConvHitIf(coverpointp, cbinp, cpVarp, idx++, @@ -1056,34 +1090,10 @@ class FunctionalCoverageVisitor final : public VNVisitor { int atLeastValue) { UINFO(4, " Generating array bins for: " << arrayBinp->name()); - // Extract all values from the range list - std::vector values; - for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { - if (AstInsideRange* const insideRangep = VN_CAST(rangep, InsideRange)) { - // For InsideRange [min:max], create bins for each value - AstNodeExpr* const minp = V3Const::constifyEdit(insideRangep->lhsp()); - AstNodeExpr* const maxp = V3Const::constifyEdit(insideRangep->rhsp()); - AstConst* const minConstp = VN_CAST(minp, Const); - AstConst* const maxConstp = VN_CAST(maxp, Const); - if (minConstp && maxConstp) { - const int minVal = minConstp->toSInt(); - const int maxVal = maxConstp->toSInt(); - UINFO(6, " Expanding InsideRange [" << minVal << ":" << maxVal << "]"); - for (int val = minVal; val <= maxVal; ++val) { - values.push_back(new AstConst{insideRangep->fileline(), - AstConst::WidthedValue{}, - (int)exprp->width(), (uint32_t)val}); - } - } else { - arrayBinp->v3error("Non-constant expression in array bins range; " - "range bounds must be constants"); - return; - } - } else { - // Single value - should be an expression - values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); - } - } + // Extract all values from the range list (resolves '$', caps/ignores huge ranges) + bool unsupported = false; + std::vector values = extractArrayValues(arrayBinp, exprp, unsupported); + if (unsupported) return; // bin ignored (COVERIGN emitted) // Create a separate bin for each value int index = 0; diff --git a/test_regress/t/t_covergroup_array_bins.out b/test_regress/t/t_covergroup_array_bins.out index ccbbbc297..903c2b307 100644 --- a/test_regress/t/t_covergroup_array_bins.out +++ b/test_regress/t/t_covergroup_array_bins.out @@ -10,3 +10,19 @@ cg3.cp.range_sized[0]: 1 cg3.cp.range_sized[1]: 1 cg3.cp.range_sized[2]: 1 cg3.cp.range_sized[3]: 0 +cg4.cp.all_vals[0]: 1 +cg4.cp.all_vals[1]: 1 +cg4.cp.all_vals[2]: 1 +cg4.cp.all_vals[3]: 0 +cg5.cp.hi_vals[0]: 1 +cg5.cp.hi_vals[1]: 0 +cg6.cp.lo_open[0]: 1 +cg6.cp.lo_open[1]: 0 +cg7.cp.rev[0]: 1 +cg7.cp.rev[1]: 0 +cg8.cp.w[0]: 0 +cg8.cp.w[1]: 1 +cg9.__cross7.cumulative_x_lo [cross]: 1 +cg9.__cross7.ok_x_lo [cross]: 1 +cg9.cpA.ok: 1 +cg9.cpB.lo: 1 diff --git a/test_regress/t/t_covergroup_array_bins.v b/test_regress/t/t_covergroup_array_bins.v index 84033319d..209e1b549 100644 --- a/test_regress/t/t_covergroup_array_bins.v +++ b/test_regress/t/t_covergroup_array_bins.v @@ -13,6 +13,8 @@ module t; bit [7:0] data; + bit [1:0] sel; + bit [63:0] wide; covergroup cg; coverpoint data { @@ -38,14 +40,79 @@ module t; } endgroup + // cg4: array bins with '$' (open range) - '$' resolves to the coverpoint domain max. + // For 2-bit sel, {[0:$]} == {[0:3]}: one bin per value -> 4 bins (issue #7750). + covergroup cg4; + cp: coverpoint sel { + bins all_vals[] = {[0 : $]}; + } + endgroup + + // cg5: lower-open range {[lo:$]} == {[lo:maxVal]} -> bins for 2 and 3 + covergroup cg5; + cp: coverpoint sel { + bins hi_vals[] = {[2 : $]}; + } + endgroup + + // cg6: upper-open range {[$:hi]} == {[0:hi]} -> bins for 0 and 1 + covergroup cg6; + cp: coverpoint sel { + bins lo_open[] = {[$ : 1]}; + } + endgroup + + // cg7: a reversed range {[hi:lo]} (hi 2 bins total. + covergroup cg7; + cp: coverpoint data { + bins rev[] = {[3 : 1], 5, 7}; + } + endgroup + + // cg8: wide (>= 64-bit) coverpoint, exercising the 64-bit domain-max path + covergroup cg8; + cp: coverpoint wide { + bins w[] = {[0 : 1]}; + } + endgroup + + // cg9: two ranges that are each under COVER_BINS_LIMIT (1000) but whose + // cumulative size exceeds it. The first range populates the value list, the + // second trips the running-total guard -> COVERIGN, the whole bin is ignored. + // cpA is crossed, so it is non-convertible and routes through the legacy + // per-bin generateArrayBins() path (exercising its unsupported-bin guard). + covergroup cg9; + cpA: coverpoint wide { + bins cumulative[] = {[0 : 500], [0 : 500]}; + bins ok = {5}; + } + cpB: coverpoint sel { + bins lo = {1}; + } + cross cpA, cpB; + endgroup + initial begin cg cg_inst; cg2 cg2_inst; cg3 cg3_inst; + cg4 cg4_inst; + cg5 cg5_inst; + cg6 cg6_inst; + cg7 cg7_inst; + cg8 cg8_inst; + cg9 cg9_inst; cg_inst = new(); cg2_inst = new(); cg3_inst = new(); + cg4_inst = new(); + cg5_inst = new(); + cg6_inst = new(); + cg7_inst = new(); + cg8_inst = new(); + cg9_inst = new(); // Hit first array bin value (1) data = 1; @@ -94,6 +161,42 @@ module t; cg3_inst.sample(); `checkr(cg3_inst.get_inst_coverage(), 75.0); + // Hit cg4 '$' bins ([0:$] == [0:3], 4 bins): cover 3 of 4 + sel = 0; + cg4_inst.sample(); + `checkr(cg4_inst.get_inst_coverage(), 25.0); + sel = 1; + cg4_inst.sample(); + `checkr(cg4_inst.get_inst_coverage(), 50.0); + sel = 2; + cg4_inst.sample(); + `checkr(cg4_inst.get_inst_coverage(), 75.0); + + // Hit cg5 lower-open bins ([2:$] == [2:3], 2 bins): cover 1 of 2 + sel = 2; + cg5_inst.sample(); + `checkr(cg5_inst.get_inst_coverage(), 50.0); + + // Hit cg6 upper-open bins ([$:1] == [0:1], 2 bins): cover 1 of 2 + sel = 0; + cg6_inst.sample(); + `checkr(cg6_inst.get_inst_coverage(), 50.0); + + // Hit cg7 bins (reversed [3:1] -> no bins; 5 and 7 -> 2 bins): cover 1 of 2 + data = 5; + cg7_inst.sample(); + `checkr(cg7_inst.get_inst_coverage(), 50.0); + + // Hit cg8 wide bins ([0:1], 2 bins): cover 1 of 2 + wide = 1; + cg8_inst.sample(); + `checkr(cg8_inst.get_inst_coverage(), 50.0); + + // Exercise cg9 (crossed cpA with an ignored cumulative array bin, legacy path) + wide = 5; + sel = 1; + cg9_inst.sample(); + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_autobins_bad.out b/test_regress/t/t_covergroup_autobins_bad.out index bb8c92918..2e5bb49e5 100644 --- a/test_regress/t/t_covergroup_autobins_bad.out +++ b/test_regress/t/t_covergroup_autobins_bad.out @@ -1,76 +1,88 @@ -%Error: t/t_covergroup_autobins_bad.v:17:12: Automatic bins array size must be a constant +%Error: t/t_covergroup_autobins_bad.v:18:12: Automatic bins array size must be a constant : ... note: In instance 't' - 17 | bins auto[size_var]; + 18 | bins auto[size_var]; | ^~~~ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_covergroup_autobins_bad.v:24:12: Automatic bins array size must be >= 1, got 0 +%Error: t/t_covergroup_autobins_bad.v:25:12: Automatic bins array size must be >= 1, got 0 : ... note: In instance 't' - 24 | bins auto[0]; + 25 | bins auto[0]; | ^~~~ -%Error: t/t_covergroup_autobins_bad.v:31:12: Automatic bins array size of 1001 exceeds limit of 1000 +%Error: t/t_covergroup_autobins_bad.v:32:12: Automatic bins array size of 1001 exceeds limit of 1000 : ... note: In instance 't' - 31 | bins auto[1001]; + 32 | bins auto[1001]; | ^~~~ -%Error: t/t_covergroup_autobins_bad.v:43:26: Non-constant expression in bin value list; values must be constants +%Error: t/t_covergroup_autobins_bad.v:44:26: Non-constant expression in bin value list; values must be constants : ... note: In instance 't' - 43 | ignore_bins ign = {size_var}; + 44 | ignore_bins ign = {size_var}; | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:44:32: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:45:32: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 44 | ignore_bins ign_range = {[0:size_var]}; + 45 | ignore_bins ign_range = {[0:size_var]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:38:12: Non-constant expression in array bins range; range bounds must be constants - : ... note: In instance 't' - 38 | bins b[] = {[size_var:size_var]}; - | ^ %Error: t/t_covergroup_autobins_bad.v:39:12: Non-constant expression in array bins range; range bounds must be constants : ... note: In instance 't' - 39 | bins b_mixed[] = {[0:size_var]}; + 39 | bins b[] = {[size_var:size_var]}; + | ^ +%Error: t/t_covergroup_autobins_bad.v:40:12: Non-constant expression in array bins range; range bounds must be constants + : ... note: In instance 't' + 40 | bins b_mixed[] = {[0:size_var]}; | ^~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:40:23: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:41:23: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 40 | bins b_range = {[size_var:4]}; + 41 | bins b_range = {[size_var:4]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:41:24: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:42:24: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 41 | bins b_range2 = {[0:size_var]}; + 42 | bins b_range2 = {[0:size_var]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:42:18: Non-constant expression in bin range; values must be constants +%Error: t/t_covergroup_autobins_bad.v:43:18: Non-constant expression in bin range; values must be constants : ... note: In instance 't' - 42 | bins b2 = {size_var}; + 43 | bins b2 = {size_var}; | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:43:26: Non-constant expression in bin range; values must be constants +%Error: t/t_covergroup_autobins_bad.v:44:26: Non-constant expression in bin range; values must be constants : ... note: In instance 't' - 43 | ignore_bins ign = {size_var}; + 44 | ignore_bins ign = {size_var}; | ^~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:51:25: Ignoring unsupported: non-constant 'option.at_least'; using default value +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:52:25: Ignoring unsupported: non-constant 'option.at_least'; using default value : ... note: In instance 't' - 51 | option.at_least = size_var; + 52 | option.at_least = size_var; | ^~~~~~~~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Error: t/t_covergroup_autobins_bad.v:61:31: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:62:31: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 61 | ignore_bins ign_nclo = {[size_var:4]}; + 62 | ignore_bins ign_nclo = {[size_var:4]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:58:20: Four-state (x/z) value in bin range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:59:20: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 58 | bins b_xz = {[4'bxxxx:4'hF]}; + 59 | bins b_xz = {[4'bxxxx:4'hF]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:59:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants - : ... note: In instance 't' - 59 | ignore_bins ign_xz_lo = {[4'bxxxx:4'hF]}; - | ^ %Error: t/t_covergroup_autobins_bad.v:60:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 60 | ignore_bins ign_xz_hi = {[4'h0:4'bzzzz]}; + 60 | ignore_bins ign_xz_lo = {[4'bxxxx:4'hF]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:62:23: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:61:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 62 | bins b_nc_ub = {[size_var:$]}; - | ^ -%Error: t/t_covergroup_autobins_bad.v:63:23: Four-state (x/z) value in bin range bound; range bounds must be two-state constants + 61 | ignore_bins ign_xz_hi = {[4'h0:4'bzzzz]}; + | ^ +%Error: t/t_covergroup_autobins_bad.v:63:23: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 63 | bins b_xz_ub = {[4'bxxxx:$]}; + 63 | bins b_nc_ub = {[size_var:$]}; | ^ +%Error: t/t_covergroup_autobins_bad.v:64:23: Four-state (x/z) value in bin range bound; range bounds must be two-state constants + : ... note: In instance 't' + 64 | bins b_xz_ub = {[4'bxxxx:$]}; + | ^ +%Error: t/t_covergroup_autobins_bad.v:65:12: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants + : ... note: In instance 't' + 65 | bins b_xz_arr[] = {[4'bxxxx:4'hF]}; + | ^~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:66:12: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants + : ... note: In instance 't' + 66 | bins b_xz_arr_hi[] = {[4'h0:4'bzzzz]}; + | ^~~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:73:12: Unsupported: array 'bins' covering more than 1000 values (e.g. an open '[lo:$]' range over a wide coverpoint); bin ignored + : ... note: In instance 't' + 73 | bins b_huge[] = {[0:$]}; + | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_autobins_bad.v b/test_regress/t/t_covergroup_autobins_bad.v index 8a19c0c8e..db8a48fd5 100644 --- a/test_regress/t/t_covergroup_autobins_bad.v +++ b/test_regress/t/t_covergroup_autobins_bad.v @@ -10,6 +10,7 @@ module t; int size_var; logic [3:0] cp_expr; + logic [15:0] cp_wide; // Error: array size must be a constant covergroup cg1; @@ -61,6 +62,15 @@ module t; ignore_bins ign_nclo = {[size_var:4]}; // non-constant lower bound bins b_nc_ub = {[size_var:$]}; // non-constant lower bound, open-ended '$' upper bins b_xz_ub = {[4'bxxxx:$]}; // four-state lower bound, open-ended '$' upper + bins b_xz_arr[] = {[4'bxxxx:4'hF]}; // four-state lower bound (array-bins path) + bins b_xz_arr_hi[] = {[4'h0:4'bzzzz]}; // four-state upper bound (array-bins path) + } + endgroup + + // Warning (COVERIGN): array bins range exceeds COVER_BINS_LIMIT + covergroup cg6; + cp1: coverpoint cp_wide { + bins b_huge[] = {[0:$]}; // open '[lo:$]' over 16-bit coverpoint exceeds bin limit } endgroup @@ -70,6 +80,7 @@ module t; cg3 cg3_inst = new; cg4 cg4_inst = new; cg5 cg5_inst = new; + cg6 cg6_inst = new; initial $finish; endmodule From c76c94ef16cc95a4ab84f406718cf7f4c567a5b3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 23 Jun 2026 04:22:35 +0100 Subject: [PATCH 83/89] Optimize additional expression patterns in V3Const (#7824) - Masking that returns known zero - More general Sel over Extend --- src/V3Const.cpp | 89 +++++++++++++++++++++++++++++++---- src/V3Number.cpp | 8 ++++ src/V3Number.h | 1 + test_regress/t/t_opt_const.py | 2 +- 4 files changed, 90 insertions(+), 10 deletions(-) diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 739313ff8..203fef45a 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -1417,6 +1417,45 @@ class ConstVisitor final : public VNVisitor { return false; } + bool matchMaskedZero(const AstAnd* nodep) { + // Turn masking of known zero bits into constant zero. Commonly appears after V3Expand. + const AstConst* const maskp = VN_AS(nodep->lhsp(), Const); + const AstNodeExpr* const rhsp = nodep->rhsp(); + const uint32_t msbP1 = maskp->num().mostSetBitP1(); + if (!msbP1) return false; // Don't rewrite, separate rule matches for this + const uint32_t msb = msbP1 - 1; + const uint32_t lsb = maskp->num().leastSetBitP1() - 1; + + if (const AstShiftL* const shiftp = VN_CAST(rhsp, ShiftL)) { + // 'a << S' forces the low S bits to zero + if (AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) { + return scp->num().fitsInUInt() // + && (scp->num().toUInt() > msb); + } + } + if (const AstShiftR* const shiftp = VN_CAST(rhsp, ShiftR)) { + // 'a >> S' forces the high S bits to zero. Check against the width of the shifted + // operand, V3Expand can create shifts wider than their inputs + if (AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) { + return scp->num().fitsInUInt() + && (lsb + scp->num().toUInt() + >= static_cast(shiftp->lhsp()->widthMin())); + } + } + if (const AstMul* const mulp = VN_CAST(rhsp, Mul)) { + // 'C * a' forces the low N bits to zero where 'C' has low zero bits + if (AstConst* const cp = VN_CAST(mulp->lhsp(), Const)) { + return cp->num().leastSetBitP1() > msb + 1; + } + } + if (const AstExtend* const extendp = VN_CAST(rhsp, Extend)) { + // Zero-extension forces the bits above the source width to zero + return lsb >= static_cast(extendp->lhsp()->width()); + } + + return false; + } + bool matchBitOpTree(AstNodeExpr* nodep) { if (nodep->widthMin() != 1) return false; if (!v3Global.opt.fConstBitOpTree()) return false; @@ -1564,16 +1603,46 @@ class ConstVisitor final : public VNVisitor { && static_cast(nodep->widthConst()) == nodep->fromp()->width()); } bool operandSelExtend(AstSel* nodep) { - // A pattern created by []'s after offsets have been removed - // SEL(EXTEND(any,width,...),(width-1),0) -> ... - // Since select's return unsigned, this is always an extend - // cppcheck-suppress constVariablePointer // children unlinked below + if (!m_doV) return false; AstExtend* const extendp = VN_CAST(nodep->fromp(), Extend); - if (!(m_doV && extendp && VN_IS(nodep->lsbp(), Const) && nodep->lsbConst() == 0 - && static_cast(nodep->widthConst()) == extendp->lhsp()->width())) - return false; - VL_DO_DANGLING(replaceWChild(nodep, extendp->lhsp()), nodep); - return true; + if (!extendp) return false; + AstConst* const lsbp = VN_CAST(nodep->lsbp(), Const); + if (!lsbp) return false; + const int width = nodep->widthConst(); + const int lsb = lsbp->toSInt(); + const int msb = lsb + width - 1; + AstNodeExpr* const lhsp = extendp->lhsp(); + if (!lhsp->isPure()) return false; + + // Selecting the entire extended expression, replace with that + if (lsb == 0 && msb == lhsp->width() - 1) { + lhsp->unlinkFrBack(); + nodep->replaceWithKeepDType(lhsp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + // Select entirely in the extended part - replace with zero + if (lsb >= lhsp->width()) { + replaceZero(nodep); + return true; + } + // Select entirely in the extended expression - replace with select from that + if (msb < lhsp->width()) { + lhsp->unlinkFrBack(); + lsbp->unlinkFrBack(); + nodep->replaceWithKeepDType(new AstSel{nodep->fileline(), lhsp, lsbp, width}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + // Select straddles both sides, but is just a shorter extend + if (lsb == 0) { + lhsp->unlinkFrBack(); + nodep->replaceWithKeepDType(new AstExtend{nodep->fileline(), lhsp}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } + + return false; } bool operandSelBiLower(AstSel* nodep) { // SEL(ADD(a,b),(width-1),0) -> ADD(SEL(a),SEL(b)) @@ -4223,6 +4292,8 @@ class ConstVisitor final : public VNVisitor { // Zero on one side or the other TREEOP ("AstAdd {$lhsp.isZero, $rhsp}", "replaceWRhs(nodep)"); TREEOP ("AstAnd {$lhsp.isZero, $rhsp, $rhsp.isPure}", "replaceZero(nodep)"); // Can't use replaceZeroChkPure as we make this pattern in ChkPure + // Masking that always yields zero + TREEOP ("AstAnd {$lhsp.castConst, matchMaskedZero(nodep)}", "replaceZeroChkPure(nodep, $rhsp)"); // This visit function here must allow for short-circuiting. TREEOPS("AstLogAnd {$lhsp.isZero}", "replaceZero(nodep)"); TREEOP ("AstLogAnd{$lhsp.isZero, $rhsp}", "replaceZero(nodep)"); diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 58bb2740b..a9c505359 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -1308,6 +1308,14 @@ uint32_t V3Number::mostSetBitP1() const { } return 0; } + +uint32_t V3Number::leastSetBitP1() const { + for (int bit = 0; bit < width(); ++bit) { + if (!bitIs0(bit)) return bit + 1; + } + return 0; +} + //====================================================================== V3Number& V3Number::opBitsNonXZ(const V3Number& lhs) { // 0/1->1, X/Z->0 diff --git a/src/V3Number.h b/src/V3Number.h index df726e496..3791f2f19 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -728,6 +728,7 @@ public: uint32_t countBits(const V3Number& ctrl1, const V3Number& ctrl2, const V3Number& ctrl3) const; uint32_t countOnes() const; uint32_t mostSetBitP1() const; // Highest bit set + 1, e.g. for 16 return 5, for 0 return 0 + uint32_t leastSetBitP1() const; // Lowest bit set + 1, e.g. for 14 return 2, for 0 return 0 // Operators bool operator<(const V3Number& rhs) const { return isLtXZ(rhs); } diff --git a/test_regress/t/t_opt_const.py b/test_regress/t/t_opt_const.py index e4d50d945..fa2e1cfee 100755 --- a/test_regress/t/t_opt_const.py +++ b/test_regress/t/t_opt_const.py @@ -16,7 +16,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "-fno-dfg", "--stats", test. test.execute() if test.vlt: - test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 50) + test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 46) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1) test.passes() From 6fbc7042a5007cbe101e75342c35aff0ec4f2c84 Mon Sep 17 00:00:00 2001 From: Nick Brereton <85175726+nbstrike@users.noreply.github.com> Date: Tue, 23 Jun 2026 07:04:51 -0400 Subject: [PATCH 84/89] Support VPI access to unpacked struct members (#7823) --- include/verilated.cpp | 22 ++ include/verilated.h | 10 +- include/verilated_sym_props.h | 29 +- include/verilated_vpi.cpp | 336 ++++++++++++++++-- src/V3AstNodeDType.h | 1 + src/V3AstNodes.cpp | 14 +- src/V3EmitCSyms.cpp | 193 +++++++++-- test_regress/t/t_vpi_var.cpp | 626 ++++++++++++++++++++++++++++++++++ test_regress/t/t_vpi_var.v | 98 +++++- 9 files changed, 1267 insertions(+), 62 deletions(-) diff --git a/include/verilated.cpp b/include/verilated.cpp index c58dde7f4..9cbb58ffc 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -3957,6 +3957,7 @@ std::unique_ptr VerilatedModel::traceConfig() const { retu // cppcheck-suppress unusedFunction // Used by applications uint32_t VerilatedVarProps::entSize() const VL_MT_SAFE { + if (m_entSize) return m_entSize; uint32_t size = 1; switch (vltype()) { case VLVT_PTR: size = sizeof(void*); break; @@ -4073,6 +4074,27 @@ VerilatedVar* VerilatedScope::varInsert(const char* namep, void* datap, bool isP return &(m_varsp->find(namep)->second); } +VerilatedVar* VerilatedScope::varInsertSized(const char* namep, void* datap, bool isParam, + VerilatedVarType vltype, int vlflags, int udims, + uint32_t entSize...) VL_MT_UNSAFE { + if (!m_varsp) m_varsp = new VerilatedVarNameMap; + VerilatedVar var(namep, datap, vltype, static_cast(vlflags), udims, 0, + isParam, entSize); + + va_list ap; + va_start(ap, entSize); + for (int i = 0; i < udims; ++i) { + const int msb = va_arg(ap, int); + const int lsb = va_arg(ap, int); + var.m_unpacked[i].m_left = msb; + var.m_unpacked[i].m_right = lsb; + } + va_end(ap); + + m_varsp->emplace(namep, std::move(var)); + return &(m_varsp->find(namep)->second); +} + VerilatedVar* VerilatedScope::forceableVarInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, void* forceReadSignalData, diff --git a/include/verilated.h b/include/verilated.h index a4535f2b4..59ae1ad8c 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -137,7 +137,9 @@ enum VerilatedVarType : uint8_t { VLVT_UINT64, // AKA QData VLVT_WDATA, // AKA VlWide VLVT_STRING, // C++ string - VLVT_REAL // AKA double + VLVT_REAL, // AKA double + VLVT_STRUCT, // SystemVerilog unpacked struct + VLVT_UNION // SystemVerilog unpacked union }; enum VerilatedVarFlags : uint32_t { @@ -154,7 +156,8 @@ enum VerilatedVarFlags : uint32_t { VLVF_CONTINUOUSLY = (1 << 11), // Is continously assigned VLVF_FORCEABLE = (1 << 12), // Forceable VLVF_SIGNED = (1 << 13), // Signed integer - VLVF_BITVAR = (1 << 14) // Four state bit (vs two state logic) + VLVF_BITVAR = (1 << 14), // Four state bit (vs two state logic) + VLVF_NET = (1 << 15) // Net object }; // IEEE 1800-2023 Table 20-6 @@ -781,6 +784,9 @@ public: // But internals only - called from verilated modules, VerilatedSyms void exportInsert(int finalize, const char* namep, void* cb) VL_MT_UNSAFE; VerilatedVar* varInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, int udims, int pdims, ...) VL_MT_UNSAFE; + VerilatedVar* varInsertSized(const char* namep, void* datap, bool isParam, + VerilatedVarType vltype, int vlflags, int udims, uint32_t entSize, + ...) VL_MT_UNSAFE; VerilatedVar* forceableVarInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, void* forceReadSignalData, const char* forceReadSignalName, diff --git a/include/verilated_sym_props.h b/include/verilated_sym_props.h index c124fb9e9..11e2fe8b8 100644 --- a/include/verilated_sym_props.h +++ b/include/verilated_sym_props.h @@ -76,6 +76,7 @@ class VerilatedVarProps VL_NOT_FINAL { const uint32_t m_magic; // Magic number const VerilatedVarType m_vltype; // Data type const VerilatedVarFlags m_vlflags; // Direction + const uint32_t m_entSize; // Element size in bytes, or 0 to derive from type std::vector m_unpacked; // Unpacked array ranges std::vector m_packed; // Packed array ranges VerilatedRange m_packedDpi; // Flattened packed array range @@ -104,10 +105,12 @@ class VerilatedVarProps VL_NOT_FINAL { // CONSTRUCTORS protected: friend class VerilatedScope; - VerilatedVarProps(VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims) + VerilatedVarProps(VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, + uint32_t entSize = 0) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags{vlflags} { + , m_vlflags{vlflags} + , m_entSize{entSize} { // Only preallocate the ranges initUnpacked(udims, nullptr); initPacked(pdims, nullptr); @@ -119,12 +122,14 @@ public: VerilatedVarProps(VerilatedVarType vltype, int vlflags) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) {} // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} {} VerilatedVarProps(VerilatedVarType vltype, int vlflags, Unpacked, int udims, const int* ulims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initUnpacked(udims, ulims); } // With packed @@ -132,14 +137,16 @@ public: VerilatedVarProps(VerilatedVarType vltype, int vlflags, Packed, int pdims, const int* plims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initPacked(pdims, plims); } VerilatedVarProps(VerilatedVarType vltype, int vlflags, Unpacked, int udims, const int* ulims, Packed, int pdims, const int* plims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initUnpacked(udims, ulims); initPacked(pdims, plims); } @@ -164,6 +171,7 @@ public: bool isDpiCLayout() const { return ((m_vlflags & VLVF_DPI_CLAY) != 0); } bool isSigned() const { return ((m_vlflags & VLVF_SIGNED) != 0); } bool isBitVar() const { return ((m_vlflags & VLVF_BITVAR) != 0); } + bool isNet() const { return ((m_vlflags & VLVF_NET) != 0); } int udims() const VL_MT_SAFE { return m_unpacked.size(); } int pdims() const VL_MT_SAFE { return m_packed.size(); } int dims() const VL_MT_SAFE { return pdims() + udims(); } @@ -265,6 +273,8 @@ protected: // CONSTRUCTORS VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam); + VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, + VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, uint32_t entSize); VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, std::unique_ptr forceControlSignals); @@ -296,6 +306,13 @@ inline VerilatedVar::VerilatedVar(const char* namep, void* datap, VerilatedVarTy , m_datap{datap} , m_namep{namep} , m_isParam{isParam} {} +inline VerilatedVar::VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, + VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, + uint32_t entSize) + : VerilatedVarProps{vltype, vlflags, udims, pdims, entSize} + , m_datap{datap} + , m_namep{namep} + , m_isParam{isParam} {} inline VerilatedVar::VerilatedVar( const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, diff --git a/include/verilated_vpi.cpp b/include/verilated_vpi.cpp index 8abbc5205..65afe20f3 100644 --- a/include/verilated_vpi.cpp +++ b/include/verilated_vpi.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,25 @@ constexpr unsigned VL_VPI_LINE_SIZE_ = 8192; //====================================================================== // Implementation +static const char* _vl_vpi_find_unescaped_dot(const char* posp) { + for (; *posp; ++posp) { + if (*posp == '\\') { + while (*posp && *posp != ' ') ++posp; + if (!*posp) return nullptr; + } else if (*posp == '.') { + return posp; + } + } + return nullptr; +} + +static std::string _vl_vpi_member_local_name(const char* const namep) { + const char* localp = namep; + const char* posp = namep; + while ((posp = _vl_vpi_find_unescaped_dot(posp))) localp = ++posp; + return localp; +} + // Base VPI handled object class VerilatedVpio VL_NOT_FINAL { // CONSTANTS @@ -201,6 +221,9 @@ public: } const VerilatedVar* varp() const { return m_varp; } const VerilatedScope* scopep() const { return m_scopep; } + bool isStructOrUnion() const { + return varp()->vltype() == VLVT_STRUCT || varp()->vltype() == VLVT_UNION; + } // Returns the number of the currently indexed dimension (starting at -1 for none). int32_t indexedDim() const { return m_indexedDim; } // Returns whether the currently indexed dimension is unpacked. @@ -363,6 +386,8 @@ class VerilatedVpioVar VL_NOT_FINAL : public VerilatedVpioVarBase { uint32_t m_entSize = 0; // memoized variable size uint32_t m_bitOffset = 0; int32_t m_partselBits = -1; // Part-select width, -1 means no part-select active + std::string m_name; + std::string m_fullNameOverride; protected: void* m_varDatap = nullptr; // varp()->datap() adjusted for array entries @@ -373,6 +398,17 @@ public: : VerilatedVpioVarBase{varp, scopep} { m_entSize = varp->entSize(); m_varDatap = varp->datap(); + if (_vl_vpi_find_unescaped_dot(varp->name())) { + m_name = _vl_vpi_member_local_name(varp->name()); + } + } + VerilatedVpioVar(const VerilatedVar* varp, const VerilatedScope* scopep, void* datap, + const std::string& name, const std::string& fullname) + : VerilatedVpioVarBase{varp, scopep} { + m_entSize = varp->entSize(); + m_varDatap = datap; + m_name = name; + m_fullNameOverride = fullname; } explicit VerilatedVpioVar(const VerilatedVpioVar* vop) : VerilatedVpioVarBase{vop} { @@ -380,6 +416,8 @@ public: m_entSize = vop->m_entSize; m_varDatap = vop->m_varDatap; m_index = vop->m_index; + m_name = vop->m_name; + m_fullNameOverride = vop->m_fullNameOverride; m_partselBits = vop->m_partselBits; m_bitOffset = vop->m_bitOffset; // Not copying m_prevDatap, must be nullptr @@ -395,15 +433,22 @@ public: uint32_t bitOffset() const override { return m_bitOffset; } int32_t partselBits() const { return m_partselBits; } uint32_t bitSize() const { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return 0; if (m_partselBits >= 0) return static_cast(m_partselBits); return VerilatedVpioVarBase::bitSize(); } uint32_t size() const override { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return 0; if (m_partselBits >= 0) return static_cast(m_partselBits); return VerilatedVpioVarBase::size(); } + const VerilatedRange* rangep() const override { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return nullptr; + return VerilatedVpioVarBase::rangep(); + } uint32_t entSize() const { return m_entSize; } const std::vector& index() const { return m_index; } + const char* name() const override { return m_name.empty() ? varp()->name() : m_name.c_str(); } // Create a part-selected view of this variable with the given bit range [hi:lo]. VerilatedVpioVar* withPartSelect(int32_t hi, int32_t lo) const { if (isIndexedDimUnpacked()) return nullptr; @@ -456,22 +501,43 @@ public: return ret; } + VerilatedVpioVar* withMember(const VerilatedVar* memberVarp) const { + const char* const parentName = varp()->name(); + const std::string memberName = memberVarp->name(); + const size_t parentLen = std::strlen(parentName); + + void* const parentDatap = varp()->datap(); + void* const memberDatap = memberVarp->datap(); + if (VL_UNLIKELY(!parentDatap) || VL_UNLIKELY(!memberDatap)) return nullptr; + const auto offset + = static_cast(memberDatap) - static_cast(parentDatap); + + const std::string localName = _vl_vpi_member_local_name(memberVarp->name()); + + return new VerilatedVpioVar{memberVarp, scopep(), + static_cast(varDatap()) + offset, localName, + std::string{fullname()} + memberName.substr(parentLen)}; + } uint32_t type() const override { uint32_t type; // TODO have V3EmitCSyms.cpp put vpiType directly into constant table switch (varp()->vltype()) { case VLVT_REAL: type = vpiRealVar; break; case VLVT_STRING: type = vpiStringVar; break; + case VLVT_STRUCT: type = varp()->isNet() ? vpiStructNet : vpiStructVar; break; + case VLVT_UNION: type = varp()->isNet() ? vpiUnionNet : vpiUnionVar; break; default: type = varp()->isBitVar() ? vpiBitVar : vpiReg; break; } - if (isIndexedDimUnpacked()) return vpiRegArray; + if (isIndexedDimUnpacked()) + return isStructOrUnion() && varp()->isNet() ? vpiNetArray : vpiRegArray; return type; } const char* fullname() const override { - static thread_local std::string t_out; - t_out = std::string{scopep()->name()} + "." + name(); - for (auto idx : index()) t_out += "[" + std::to_string(idx) + "]"; - return t_out.c_str(); + m_fullname = m_fullNameOverride.empty() + ? std::string{scopep()->name()} + "." + varp()->name() + : m_fullNameOverride; + for (auto idx : index()) m_fullname += "[" + std::to_string(idx) + "]"; + return m_fullname.c_str(); } void* prevDatap() const { return m_prevDatap; } void* varDatap() const override { return m_varDatap; } @@ -590,25 +656,63 @@ public: } }; +class VerilatedVpioMemberIter final : public VerilatedVpio { + const VerilatedScope* const m_scopep; + const VerilatedVarNameMap* const m_varsp; + VerilatedVarNameMap::const_iterator m_it; + VerilatedVpioVar* m_varp; + const std::string m_namePrefix; + bool m_started = false; + + static std::string namePrefix(const VerilatedVpioVar* vop) { + return std::string{vop->varp()->name()} + "."; + } + + vpiHandle atEnd() { + delete this; // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + return nullptr; + } + +public: + explicit VerilatedVpioMemberIter(const VerilatedVpioVar* vop) + : m_scopep{vop->scopep()} + , m_varsp{m_scopep->varsp()} + , m_varp{new VerilatedVpioVar{vop}} + , m_namePrefix{namePrefix(vop)} {} + ~VerilatedVpioMemberIter() override { VL_DO_CLEAR(delete m_varp, m_varp = nullptr); } + // cppcheck-suppress duplInheritedMember + static VerilatedVpioMemberIter* castp(vpiHandle h) { + return dynamic_cast(reinterpret_cast(h)); + } + uint32_t type() const override { return vpiIterator; } + vpiHandle dovpi_scan() override { + if (VL_UNLIKELY(!m_varsp)) return atEnd(); + if (VL_UNLIKELY(!m_started)) { + m_it = m_varsp->begin(); + m_started = true; + } else if (VL_LIKELY(m_it != m_varsp->end())) { + ++m_it; + } + for (; m_it != m_varsp->end(); ++m_it) { + const char* const name = m_it->second.name(); + if (std::strncmp(name, m_namePrefix.c_str(), m_namePrefix.length()) != 0) continue; + // Only direct members, not grandchildren + if (_vl_vpi_find_unescaped_dot(name + m_namePrefix.length())) continue; + VerilatedVpioVar* const memberp = m_varp->withMember(&(m_it->second)); + if (VL_UNLIKELY(!memberp)) continue; + return memberp->castVpiHandle(); + } + return atEnd(); + } +}; + class VerilatedVpioModule final : public VerilatedVpioScope { public: explicit VerilatedVpioModule(const VerilatedScope* modulep) : VerilatedVpioScope{modulep} { // Look for '.' not inside escaped identifier - const std::string scopename = m_fullname; - std::string::size_type pos = std::string::npos; - size_t i = 0; - while (i < scopename.length()) { - if (scopename[i] == '\\') { - while (i < scopename.length() && scopename[i] != ' ') ++i; - ++i; // Proc ' ', it should always be there. Then grab '.' on next cycle - } else { - while (i < scopename.length() && scopename[i] != '.') ++i; - if (i < scopename.length()) pos = i++; - } - } - if (VL_UNLIKELY(pos == std::string::npos)) m_toplevel = true; + if (VL_UNLIKELY(!_vl_vpi_find_unescaped_dot(m_fullname))) m_toplevel = true; } // cppcheck-suppress duplInheritedMember static VerilatedVpioModule* castp(vpiHandle h) { @@ -1770,6 +1874,8 @@ const char* VerilatedVpiError::strFromVpiObjType(PLI_INT32 vpiVal) VL_PURE { }; // clang-format on if (VL_UNCOVERABLE(vpiVal < 0)) return names[0]; + // vpiUnionNet is outside the otherwise contiguous SystemVerilog object type range. + if (vpiVal == vpiUnionNet) return "vpiUnionNet"; if (vpiVal <= vpiAutomatics) return names[vpiVal]; if (vpiVal >= vpiPackage && vpiVal <= vpiPropFormalDecl) return sv_names1[(vpiVal - vpiPackage)]; @@ -2135,6 +2241,7 @@ void VerilatedVpiError::selfTest() VL_MT_UNSAFE_ONE { SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiEnumVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiStructVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUnionVar); + SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUnionNet); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiBitVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClassObj); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiChandleVar); @@ -2404,6 +2511,162 @@ static bool vl_vpi_parse_indices(std::string& name, std::vector& indi return true; } +static const VerilatedScope* _vl_vpi_top_port_scopep(const VerilatedScope* const scopep) { + if (VL_UNLIKELY(!scopep)) return nullptr; + if (scopep->type() != VerilatedScope::SCOPE_MODULE) return nullptr; + if (std::strcmp(scopep->name(), "TOP") == 0) return nullptr; + if (_vl_vpi_find_unescaped_dot(scopep->name())) return nullptr; + return Verilated::threadContextp()->scopeFind("TOP"); +} + +static bool _vl_vpi_find_dotted_var(const std::string& scopename, const std::string& basename, + const VerilatedScope*& scopep, const VerilatedVar*& varp, + std::string& fullname) { + if (scopename.empty()) return false; + + // Unpacked struct/union members are exposed as synthetic vars whose names contain dots + // (e.g. "mystruct.member"), so the boundary between the scope and the variable name is + // ambiguous. Walk the boundary leftward, moving one scope segment at a time onto the front + // of the dotted variable name, and try each candidate scope. An exhausted scope means the + // variable lives in the toplevel "TOP" scope. + std::string dottedName = basename; + std::string dottedScope = scopename; + while (true) { + const std::string::size_type lastDot = dottedScope.rfind('.'); + dottedName = (lastDot == std::string::npos ? dottedScope : dottedScope.substr(lastDot + 1)) + + "." + dottedName; + dottedScope.resize(lastDot == std::string::npos ? 0 : lastDot); + scopep = Verilated::threadContextp()->scopeFind(dottedScope.empty() ? "TOP" + : dottedScope.c_str()); + if (scopep) { + if (const VerilatedScope* const topScopep = _vl_vpi_top_port_scopep(scopep)) { + if (const VerilatedVar* const topVarp = topScopep->varFind(dottedName.c_str())) { + scopep = topScopep; + varp = topVarp; + fullname = dottedScope.empty() ? dottedName : dottedScope + "." + dottedName; + return true; + } + } + varp = scopep->varFind(dottedName.c_str()); + if (varp) return true; + } + if (lastDot == std::string::npos) return false; + } +} + +static VerilatedVpioVar* _vl_vpi_handle_member_by_name(const std::string& name, + const VerilatedVpioVar* vop) { + const VerilatedScope* const scopep = vop->scopep(); + if (VL_UNLIKELY(!scopep)) return nullptr; + const std::string memberName = std::string{vop->varp()->name()} + "." + name; + const VerilatedVar* const memberVarp = scopep->varFind(memberName.c_str()); + if (!memberVarp) return nullptr; + return vop->withMember(memberVarp); +} + +static VerilatedVpioVar* _vl_vpi_handle_apply_indices(VerilatedVpioVar* vop, + const std::vector& indices) { + for (const PLI_INT32 index : indices) { + VerilatedVpioVar* const nextVop = vop->withIndex(index); + VL_DO_CLEAR(delete vop, vop = nullptr); + if (!nextVop) return nullptr; + vop = nextVop; + } + return vop; +} + +static bool _vl_vpi_parse_optional_indices(std::string& name, std::vector& indices) { + indices.clear(); + if (name.empty()) return false; + if (name.back() != ']') return true; + + VlVpiBitRange bitRange; + return vl_vpi_parse_indices(name, indices, &bitRange) && !bitRange.valid; +} + +static void _vl_vpi_split_dotted_name(const std::string& name, std::vector& parts) { + parts.clear(); + const char* const namep = name.c_str(); + const char* beginp = namep; + const char* posp = beginp; + while ((posp = _vl_vpi_find_unescaped_dot(posp))) { + parts.emplace_back(beginp, posp - beginp); + beginp = ++posp; + } + parts.emplace_back(beginp); +} + +static VerilatedVpioVar* +_vl_vpi_handle_indexed_member_from_scope(const VerilatedScope* const scopep, + const std::vector& parts, + const size_t firstPart) { + if (VL_UNLIKELY(!scopep) || VL_UNLIKELY(firstPart >= parts.size())) return nullptr; + + std::string baseName = parts[firstPart]; + std::vector indices; + if (!_vl_vpi_parse_optional_indices(baseName, indices)) return nullptr; + + const VerilatedScope* varScopep = scopep; + const VerilatedVar* baseVarp = nullptr; + std::string fullnameOverride; + if (const VerilatedScope* const topScopep = _vl_vpi_top_port_scopep(scopep)) { + if (const VerilatedVar* const topVarp = topScopep->varFind(baseName.c_str())) { + varScopep = topScopep; + baseVarp = topVarp; + fullnameOverride = std::string{scopep->name()} + "." + baseName; + } + } + if (!baseVarp) baseVarp = scopep->varFind(baseName.c_str()); + if (!baseVarp) return nullptr; + + VerilatedVpioVar* baseVop + = fullnameOverride.empty() + ? new VerilatedVpioVar{baseVarp, varScopep} + : new VerilatedVpioVar{baseVarp, varScopep, baseVarp->datap(), + _vl_vpi_member_local_name(baseVarp->name()), + fullnameOverride}; + VerilatedVpioVar* vop = _vl_vpi_handle_apply_indices(baseVop, indices); + if (!vop) return nullptr; + + for (size_t i = firstPart + 1; i < parts.size(); ++i) { + std::string memberName = parts[i]; + if (!_vl_vpi_parse_optional_indices(memberName, indices)) { + VL_DO_CLEAR(delete vop, vop = nullptr); + return nullptr; + } + + VerilatedVpioVar* const memberVop = _vl_vpi_handle_member_by_name(memberName, vop); + VL_DO_CLEAR(delete vop, vop = nullptr); + if (!memberVop) return nullptr; + + vop = _vl_vpi_handle_apply_indices(memberVop, indices); + if (!vop) return nullptr; + } + return vop; +} + +static VerilatedVpioVar* +_vl_vpi_handle_dotted_indexed_member_by_name(const std::string& scopeAndName) { + std::vector parts; + _vl_vpi_split_dotted_name(scopeAndName, parts); + if (parts.size() < 2) return nullptr; + + for (size_t firstPart = parts.size() - 1; firstPart > 0; --firstPart) { + std::string scopeName = parts[0]; + for (size_t i = 1; i < firstPart; ++i) scopeName += "." + parts[i]; + const VerilatedScope* const scopep + = Verilated::threadContextp()->scopeFind(scopeName.c_str()); + if (!scopep) continue; + if (VerilatedVpioVar* const vop + = _vl_vpi_handle_indexed_member_from_scope(scopep, parts, firstPart)) { + return vop; + } + } + + const VerilatedScope* const topScopep = Verilated::threadContextp()->scopeFind("TOP"); + return _vl_vpi_handle_indexed_member_from_scope(topScopep, parts, 0); +} + // for obtaining handles vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { @@ -2425,7 +2688,9 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { const VerilatedVar* varp = nullptr; const VerilatedScope* scopep; + std::string fullnameOverride; const VerilatedVpioScope* const voScopep = VerilatedVpioScope::castp(scope); + const VerilatedVpioVar* const voVarp = VerilatedVpioVar::castp(scope); if (0 == std::strncmp(scopeAndName.c_str(), "$root.", std::strlen("$root."))) { scopeAndName.erase(0, std::strlen("$root.")); @@ -2433,6 +2698,12 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { const bool scopeIsPackage = VerilatedVpioPackage::castp(scope) != nullptr; scopeAndName = std::string{voScopep->fullname()} + (scopeIsPackage ? "" : ".") + scopeAndName; + } else if (voVarp && voVarp->isStructOrUnion()) { + if (VerilatedVpioVar* const memberp + = _vl_vpi_handle_member_by_name(scopeAndName, voVarp)) { + return memberp->castVpiHandle(); + } + scopeAndName = std::string{voVarp->fullname()} + "." + scopeAndName; } { // This doesn't yet follow the hierarchy in the proper way @@ -2491,8 +2762,18 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { } if (!varp) { scopep = Verilated::threadContextp()->scopeFind(scopename.c_str()); - if (!scopep) return nullptr; - varp = scopep->varFind(basename.c_str()); + if (scopep) { varp = scopep->varFind(basename.c_str()); } + // Unpacked struct members are exposed as synthetic variables with dotted names. + // Exact unindexed member names can be found directly; indexed member paths need the + // component walker so array indices are applied before member offsets. + if (!varp + && !_vl_vpi_find_dotted_var(scopename, basename, scopep, varp, fullnameOverride)) { + if (VerilatedVpioVar* const memberp + = _vl_vpi_handle_dotted_indexed_member_by_name(scopeAndName)) { + return memberp->castVpiHandle(); + } + return nullptr; + } } } if (!varp) return nullptr; @@ -2501,6 +2782,11 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { vpiHandle resultHandle; if (varp->isParam()) { resultHandle = (new VerilatedVpioParam{varp, scopep})->castVpiHandle(); + } else if (!fullnameOverride.empty()) { + resultHandle + = (new VerilatedVpioVar{varp, scopep, varp->datap(), + _vl_vpi_member_local_name(varp->name()), fullnameOverride}) + ->castVpiHandle(); } else { resultHandle = (new VerilatedVpioVar{varp, scopep})->castVpiHandle(); } @@ -2642,6 +2928,11 @@ vpiHandle vpi_iterate(PLI_INT32 type, vpiHandle object) { if (vop) return ((new VerilatedVpioRegIter{vop})->castVpiHandle()); return nullptr; } + case vpiMember: { + const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object); + if (!vop || !vop->isStructOrUnion()) return nullptr; + return ((new VerilatedVpioMemberIter{vop})->castVpiHandle()); + } case vpiParameter: { const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object); if (VL_UNLIKELY(!vop)) return nullptr; @@ -2734,6 +3025,11 @@ PLI_INT32 vpi_get(PLI_INT32 property, vpiHandle object) { if (VL_UNLIKELY(!vop)) return vpiUndefined; return vop->varp()->isSigned(); } + case vpiPacked: { + const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object); + if (VL_LIKELY(vop && vop->isStructOrUnion())) return 0; + [[fallthrough]]; + } default: VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported property %s, nothing will be returned", __func__, VerilatedVpiError::strFromVpiProp(property)); diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 4600911d2..fcb2183ba 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -174,6 +174,7 @@ public: bool isStreamableFixedAggregate() const; bool containsUnpackedStruct() const; int widthStream() const; + string vlEnumType() const; // Return VerilatedVarType: VLVT_UINT32, etc static int uniqueNumInc() { return ++s_uniqueNum; } const char* charIQWN() const { return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I"); diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 85b077053..ec26f24a9 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -703,9 +703,14 @@ string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string& return ostatic + dtypep()->cType(oname, forFunc, asRef); } -string AstVar::vlEnumType() const { +string AstNodeDType::vlEnumType() const { string arg; - const AstBasicDType* const bdtypep = basicp(); + const AstNodeDType* dtypep = skipRefp(); + while (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + dtypep = adtypep->subDTypep()->skipRefp(); + } + const AstBasicDType* const bdtypep = dtypep->basicp(); + const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType); const bool strtype = bdtypep && bdtypep->keyword() == VBasicDTypeKwd::STRING; if (bdtypep && bdtypep->keyword() == VBasicDTypeKwd::CHARPTR) { return "VLVT_PTR"; @@ -715,6 +720,8 @@ string AstVar::vlEnumType() const { arg += "VLVT_STRING"; } else if (isDouble()) { arg += "VLVT_REAL"; + } else if (sdtypep && !sdtypep->packed()) { + arg += VN_IS(sdtypep, StructDType) ? "VLVT_STRUCT" : "VLVT_UNION"; } else if (widthMin() <= 8) { arg += "VLVT_UINT8"; } else if (widthMin() <= 16) { @@ -730,6 +737,8 @@ string AstVar::vlEnumType() const { return arg; } +string AstVar::vlEnumType() const { return dtypep()->vlEnumType(); } + string AstVar::vlEnumDir() const { string out; if (isInout()) { @@ -759,6 +768,7 @@ string AstVar::vlEnumDir() const { if (AstBasicDType* const basicp = dtypep()->skipRefp()->basicp()) { if (basicp->keyword() == VBasicDTypeKwd::BIT) out += "|VLVF_BITVAR"; } + if (isNet()) out += "|VLVF_NET"; return out; } diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index 79add9f3b..bcf79c164 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -176,26 +176,26 @@ class EmitCSyms final : EmitCBaseVisitorConst { return pos != std::string::npos ? scpname.substr(pos + 1) : scpname; } - static std::tuple getDimensions(const AstVar* const varp) { + static std::tuple getDimensions(const AstNodeDType* const rootDtypep) { int pdim = 0; int udim = 0; std::string bounds; - if (const AstBasicDType* const basicp = varp->basicp()) { - // Range is always first, it's not in "C" order - for (AstNodeDType* dtypep = varp->dtypep(); dtypep;) { - // Skip AstRefDType/AstTypedef, or return same node - dtypep = dtypep->skipRefp(); - if (const AstNodeArrayDType* const adtypep = VN_CAST(dtypep, NodeArrayDType)) { - bounds += " ,"; - bounds += std::to_string(adtypep->left()); - bounds += ","; - bounds += std::to_string(adtypep->right()); - if (VN_IS(dtypep, PackArrayDType)) - pdim++; - else - udim++; - dtypep = adtypep->subDTypep(); - } else { + // Range is always first, it's not in "C" order + for (const AstNodeDType* dtypep = rootDtypep; dtypep;) { + // Skip AstRefDType/AstTypedef, or return same node + dtypep = dtypep->skipRefp(); + if (const AstNodeArrayDType* const adtypep = VN_CAST(dtypep, NodeArrayDType)) { + bounds += " ,"; + bounds += std::to_string(adtypep->left()); + bounds += ","; + bounds += std::to_string(adtypep->right()); + if (VN_IS(dtypep, PackArrayDType)) + pdim++; + else + udim++; + dtypep = adtypep->subDTypep(); + } else { + if (const AstBasicDType* const basicp = dtypep->basicp()) { if (basicp->isRanged()) { bounds += " ,"; bounds += std::to_string(basicp->left()); @@ -203,13 +203,33 @@ class EmitCSyms final : EmitCBaseVisitorConst { bounds += std::to_string(basicp->right()); pdim++; } - break; // AstBasicDType - nothing below, 1 } + break; // Non-array leaf } } return {pdim, udim, bounds}; } + static std::tuple getDimensions(const AstVar* const varp) { + return getDimensions(varp->dtypep()); + } + + static int getUnpackedElements(const AstNodeDType* rootDtypep) { + int elements = 1; + for (const AstNodeDType* dtypep = rootDtypep; dtypep;) { + dtypep = dtypep->skipRefp(); + const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType); + if (!adtypep) break; + elements *= adtypep->elementsConst(); + dtypep = adtypep->subDTypep(); + } + return elements; + } + + static bool needsEmittedEntSize(const std::string& vlEnumType) { + return vlEnumType == "VLVT_STRUCT" || vlEnumType == "VLVT_UNION"; + } + static std::pair isForceControlSignal(const AstVar* const signalVarp) { // __VforceRd should not show up here because it is never public, but just in case it does, // it should be skipped because forceableVarInsert creates its VerilatedVar. @@ -225,15 +245,47 @@ class EmitCSyms final : EmitCBaseVisitorConst { return std::pair{false, ""}; } + static void appendVarProperties(std::string& stmt, const std::string& vlEnumType, + const std::string& vlEnumDir, const int udim, const int pdim, + const std::string& bounds, const std::string& entSize = "") { + stmt += vlEnumType; // VLVT_UINT32 etc + stmt += ", "; + stmt += vlEnumDir; // VLVD_IN etc + stmt += ", "; + stmt += std::to_string(udim); + if (!entSize.empty()) { + stmt += ", "; + stmt += entSize; + } else { + stmt += ", "; + stmt += std::to_string(pdim); + } + stmt += bounds; + stmt += ")"; + } + static std::string memberVlEnumDir(const AstVar* const varp, + const AstNodeDType* const dtypep) { + std::string out = "((" + varp->vlEnumDir() + ") & ~(VLVF_SIGNED|VLVF_BITVAR))"; + const AstNodeDType* const skipDTypep = dtypep->skipRefp(); + if (skipDTypep->isSigned()) out += "|VLVF_SIGNED"; + if (const AstBasicDType* const basicp = skipDTypep->basicp()) { + if (basicp->keyword() == VBasicDTypeKwd::BIT) out += "|VLVF_BITVAR"; + } + return out; + } + static std::string insertVarStatement(const ScopeVarData& svd, const AstScope* const scopep, const AstVar* const varp, const int udim, const int pdim, const std::string& bounds) { - std::string stmt; - stmt += protect("__Vscopep_" + svd.m_scopeName) + "->varInsert(\""; - stmt += V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty)) + '"'; - const std::string varName = VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." + protect(varp->name()); + const std::string vlEnumType = varp->vlEnumType(); + const bool needsEntSize = needsEmittedEntSize(vlEnumType); + + std::string stmt; + stmt += protect("__Vscopep_" + svd.m_scopeName); + stmt += needsEntSize ? "->varInsertSized(\"" : "->varInsert(\""; + stmt += V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty)) + '"'; if (!varp->isParam()) { stmt += ", &("; @@ -250,18 +302,87 @@ class EmitCSyms final : EmitCBaseVisitorConst { stmt += "))), true, "; } - stmt += varp->vlEnumType(); // VLVT_UINT32 etc - stmt += ", "; - stmt += varp->vlEnumDir(); // VLVD_IN etc - stmt += ", "; - stmt += std::to_string(udim); - stmt += ", "; - stmt += std::to_string(pdim); - stmt += bounds; - stmt += ")"; + const std::string entSize = needsEntSize + ? "sizeof(" + varName + ") / " + + std::to_string(getUnpackedElements(varp->dtypep())) + : ""; + appendVarProperties(stmt, vlEnumType, varp->vlEnumDir(), udim, pdim, bounds, entSize); return stmt; } + static std::string insertDTypeVarStatement(const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyName, + const std::string& cName, + const AstNodeDType* const dtypep, const int udim, + const int pdim, const std::string& bounds) { + const std::string vlEnumType = dtypep->vlEnumType(); + const bool needsEntSize = needsEmittedEntSize(vlEnumType); + std::string stmt; + stmt += protect("__Vscopep_" + svd.m_scopeName); + stmt += needsEntSize ? "->varInsertSized(\"" : "->varInsert(\""; + stmt += V3OutFormatter::quoteNameControls(prettyName) + '"'; + stmt += ", &("; + stmt += VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()); + stmt += "."; + stmt += cName; + stmt += "), false, "; + const std::string varName + = VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." + cName; + const std::string entSize + = needsEntSize + ? "sizeof(" + varName + ") / " + std::to_string(getUnpackedElements(dtypep)) + : ""; + appendVarProperties(stmt, vlEnumType, memberVlEnumDir(svd.m_varp, dtypep), udim, pdim, + bounds, entSize); + return stmt; + } + + static void addUOrStructMemberVars(std::vector& stmts, const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyPrefix, const std::string& cPrefix, + const AstNodeUOrStructDType* const sdtypep) { + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + const AstNodeDType* const itemDTypep = itemp->dtypep(); + const std::string prettyName + = prettyPrefix + "." + AstNode::vpiName(itemp->shortName()); + const std::string cName = cPrefix + "." + itemp->nameProtect(); + const std::tuple dimensions = getDimensions(itemDTypep); + const int pdim = std::get<0>(dimensions); + const int udim = std::get<1>(dimensions); + const std::string bounds = std::get<2>(dimensions); + stmts.emplace_back(insertDTypeVarStatement(svd, scopep, prettyName, cName, itemDTypep, + udim, pdim, bounds) + + ";"); + if (const AstNodeUOrStructDType* const subp + = VN_CAST(itemDTypep->skipRefp(), NodeUOrStructDType)) { + if (!subp->packed()) + addUOrStructMemberVars(stmts, svd, scopep, prettyName, cName, subp); + } else if (VN_IS(itemDTypep->skipRefp(), UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, prettyName, cName, + itemDTypep); + } + } + } + + static void addUnpackedArrayUOrStructMemberVars(std::vector& stmts, + const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyPrefix, + const std::string& cPrefix, + const AstNodeDType* const dtypep) { + const AstNodeDType* const skipDTypep = dtypep->skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(skipDTypep, UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, prettyPrefix, cPrefix + "[0]", + adtypep->subDTypep()); + } else if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(skipDTypep, NodeUOrStructDType)) { + if (!sdtypep->packed()) + addUOrStructMemberVars(stmts, svd, scopep, prettyPrefix, cPrefix, sdtypep); + } + } + std::string insertForceableVarStatement(const ScopeVarData& svd, const AstScope* const scopep, const AstVar* const varp, const int udim, const int pdim, const std::string& bounds) { @@ -1061,6 +1182,16 @@ std::vector EmitCSyms::getSymCtorStmts() { const std::string stmt = insertVarStatement(svd, scopep, varp, udim, pdim, bounds) + ";"; add(stmt); + if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(varp->dtypeSkipRefp(), NodeUOrStructDType)) { + if (!sdtypep->packed()) { + addUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty, + protect(varp->name()), sdtypep); + } + } else if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty, + protect(varp->name()), varp->dtypep()); + } } } } diff --git a/test_regress/t/t_vpi_var.cpp b/test_regress/t/t_vpi_var.cpp index 3afaf92ca..7c0eac4f4 100644 --- a/test_regress/t/t_vpi_var.cpp +++ b/test_regress/t/t_vpi_var.cpp @@ -38,6 +38,8 @@ #endif +#include + #ifdef VERILATOR #include "verilated.h" #endif @@ -258,6 +260,626 @@ int _mon_check_big() { return 0; } +int _mon_check_unpacked_struct_members() { + const char* p; + PLI_INT32 d; + t_vpi_value tmpValue; + + // unpacked struct port members + tmpValue.format = vpiIntVal; + { + TestVpiHandle vh101 = VPI_HANDLE("unpacked_struct_port"); + CHECK_RESULT_NZ(vh101); + d = vpi_get(vpiType, vh101); + CHECK_RESULT(d, vpiStructVar); + CHECK_RESULT(vpi_get(vpiSize, vh101), 0); + CHECK_RESULT(vpi_get(vpiPacked, vh101), 0); + CHECK_RESULT_Z(vpi_handle(vpiLeftRange, vh101)); + p = vpi_get_str(vpiType, vh101); + CHECK_RESULT_CSTR(p, "vpiStructVar"); + + TestVpiHandle vh102 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh102); + CHECK_RESULT(vpi_get(vpiType, vh102), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh102), 7); + p = vpi_get_str(vpiName, vh102); + CHECK_RESULT_CSTR(p, "member_a"); + p = vpi_get_str(vpiFullName, vh102); + CHECK_RESULT_CSTR(p, "t.unpacked_struct_port.member_a"); + CHECK_RESULT_NZ(vpi_handle(vpiLeftRange, vh102)); + CHECK_RESULT_Z(vpi_iterate(vpiMember, vh102)); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"member_a", vh102)); + + TestVpiHandle vh103 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_b", NULL); + CHECK_RESULT_NZ(vh103); + CHECK_RESULT(vpi_get(vpiType, vh103), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh103), 1); + + TestVpiHandle vh104 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh101); + CHECK_RESULT_NZ(vh104); + CHECK_RESULT(vpi_get(vpiType, vh104), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh104), 16); + + CHECK_RESULT_Z(vpi_iterate(vpiMember, nullptr)); + TestVpiHandle vh105 = vpi_iterate(vpiMember, vh101); + CHECK_RESULT_NZ(vh105); + CHECK_RESULT(vpi_get(vpiType, vh105), vpiIterator); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh105)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh105.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x35; + vpi_put_value(vh102, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh102, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x35); + + putValue.value.integer = 0x4321; + vpi_put_value(vh104, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh104, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x4321); + + // Members resolve even without the toplevel scope prefix + TestVpiHandle vh106 + = vpi_handle_by_name((PLI_BYTE8*)"unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh106); + CHECK_RESULT(vpi_get(vpiType, vh106), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh106), 7); + vpi_get_value(vh106, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x35); + + TestVpiHandle vh107 + = vpi_handle_by_name((PLI_BYTE8*)"$root.t.unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh107); + CHECK_RESULT(vpi_get(vpiType, vh107), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh107), 7); + + TestVpiHandle vh108 = VPI_HANDLE(""); + CHECK_RESULT_NZ(vh108); + TestVpiHandle vh109 + = vpi_handle_by_name((PLI_BYTE8*)"unpacked_struct_port.member_b", vh108); + CHECK_RESULT_NZ(vh109); + CHECK_RESULT(vpi_get(vpiType, vh109), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh109), 1); + + TestVpiHandle vh100 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_c[7:0]", NULL); + CHECK_RESULT_NZ(vh100); + CHECK_RESULT(vpi_get(vpiType, vh100), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh100), 8); + + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port[7:0]", NULL)); + + CHECK_RESULT_Z( + vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.no_such_member", NULL)); + } + + // Synthetic member vars preserve type-derived flags from the member dtype + { + TestVpiHandle vh114 = VPI_HANDLE("member_flags_struct_signal"); + CHECK_RESULT_NZ(vh114); + CHECK_RESULT(vpi_get(vpiType, vh114), vpiStructVar); + + TestVpiHandle vh115 = vpi_handle_by_name((PLI_BYTE8*)"unsigned_member", vh114); + CHECK_RESULT_NZ(vh115); + CHECK_RESULT(vpi_get(vpiType, vh115), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh115), 7); + CHECK_RESULT(vpi_get(vpiSigned, vh115), 0); + + TestVpiHandle vh116 = vpi_handle_by_name((PLI_BYTE8*)"signed_member", vh114); + CHECK_RESULT_NZ(vh116); + CHECK_RESULT(vpi_get(vpiType, vh116), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh116), 7); + CHECK_RESULT(vpi_get(vpiSigned, vh116), 1); + + TestVpiHandle vh117 = vpi_handle_by_name((PLI_BYTE8*)"bit_member", vh114); + CHECK_RESULT_NZ(vh117); + CHECK_RESULT(vpi_get(vpiType, vh117), vpiBitVar); + CHECK_RESULT(vpi_get(vpiSize, vh117), 1); + CHECK_RESULT(vpi_get(vpiSigned, vh117), 0); + } + + // unpacked union port members + { + TestVpiHandle vh110 = VPI_HANDLE("unpacked_union_port"); + CHECK_RESULT_NZ(vh110); + CHECK_RESULT(vpi_get(vpiType, vh110), vpiUnionVar); + CHECK_RESULT(vpi_get(vpiSize, vh110), 0); + CHECK_RESULT(vpi_get(vpiPacked, vh110), 0); + p = vpi_get_str(vpiType, vh110); + CHECK_RESULT_CSTR(p, "vpiUnionVar"); + + TestVpiHandle vh111 = vpi_handle_by_name((PLI_BYTE8*)"union_byte0", vh110); + CHECK_RESULT_NZ(vh111); + CHECK_RESULT(vpi_get(vpiType, vh111), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh111), 8); + + TestVpiHandle vh112 = vpi_iterate(vpiMember, vh110); + CHECK_RESULT_NZ(vh112); + std::set unionMembers; + while (TestVpiHandle member = vpi_scan(vh112)) { + unionMembers.insert(vpi_get_str(vpiName, member)); + } + vh112.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(unionMembers.count("union_byte0"), 1); + CHECK_RESULT(unionMembers.count("union_byte1"), 1); + } + + // nested unpacked struct port members + { + TestVpiHandle vh120 = VPI_HANDLE("nested_struct_port"); + CHECK_RESULT_NZ(vh120); + CHECK_RESULT(vpi_get(vpiType, vh120), vpiStructVar); + + // The nested struct member is itself a struct + TestVpiHandle vh121 = vpi_handle_by_name((PLI_BYTE8*)"outer_inner", vh120); + CHECK_RESULT_NZ(vh121); + CHECK_RESULT(vpi_get(vpiType, vh121), vpiStructVar); + CHECK_RESULT(vpi_get(vpiSize, vh121), 0); + + // Leaf reachable via the full hierarchical name + TestVpiHandle vh122 + = vpi_handle_by_name((PLI_BYTE8*)"t.nested_struct_port.outer_inner.inner_x", NULL); + CHECK_RESULT_NZ(vh122); + CHECK_RESULT(vpi_get(vpiType, vh122), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh122), 4); + + // Leaf reachable relative to the intermediate struct handle + TestVpiHandle vh123 = vpi_handle_by_name((PLI_BYTE8*)"inner_y", vh121); + CHECK_RESULT_NZ(vh123); + CHECK_RESULT(vpi_get(vpiType, vh123), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh123), 4); + + // Iterating the outer struct yields direct members only, not grandchildren + TestVpiHandle vh124 = vpi_iterate(vpiMember, vh120); + CHECK_RESULT_NZ(vh124); + std::set nestedMembers; + while (TestVpiHandle member = vpi_scan(vh124)) { + nestedMembers.insert(vpi_get_str(vpiName, member)); + } + vh124.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(nestedMembers.count("outer_a"), 1); + CHECK_RESULT(nestedMembers.count("outer_inner"), 1); + CHECK_RESULT(nestedMembers.count("inner_x"), 0); + + TestVpiHandle vh125 = vpi_handle_by_name((PLI_BYTE8*)"t.sub.subsig1", NULL); + CHECK_RESULT_NZ(vh125); + CHECK_RESULT(vpi_get(vpiType, vh125), vpiReg); + + // Write through a leaf of the nested struct + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xa; + vpi_put_value(vh122, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh122, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xa); + } + + // unpacked struct port with multidimensional packed-array members + { + TestVpiHandle vh130 = VPI_HANDLE("struct_with_packed_arrays_port"); + CHECK_RESULT_NZ(vh130); + CHECK_RESULT(vpi_get(vpiType, vh130), vpiStructVar); + + TestVpiHandle vh131 = vpi_handle_by_name((PLI_BYTE8*)"packed_matrix", vh130); + CHECK_RESULT_NZ(vh131); + CHECK_RESULT(vpi_get(vpiType, vh131), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh131), 512); + + TestVpiHandle vh132 = vpi_handle_by_index(vh131, 2); + CHECK_RESULT_NZ(vh132); + CHECK_RESULT(vpi_get(vpiSize, vh132), 32); + + TestVpiHandle vh133 = vpi_handle_by_index(vh132, 2); + CHECK_RESULT_NZ(vh133); + CHECK_RESULT(vpi_get(vpiSize, vh133), 8); + + TestVpiHandle vh134 = vpi_handle_by_name((PLI_BYTE8*)"reverse_matrix", vh130); + CHECK_RESULT_NZ(vh134); + CHECK_RESULT(vpi_get(vpiType, vh134), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh134), 128); + + TestVpiHandle vh135 = vpi_handle_by_index(vh134, -2); + CHECK_RESULT_NZ(vh135); + CHECK_RESULT(vpi_get(vpiSize, vh135), 8); + } + + // port array of unpacked structs + { + TestVpiHandle vh140 = VPI_HANDLE("struct_array_port"); + CHECK_RESULT_NZ(vh140); + CHECK_RESULT(vpi_get(vpiType, vh140), vpiRegArray); + + TestVpiHandle vh141 = vpi_handle_by_index(vh140, 0); + CHECK_RESULT_NZ(vh141); + CHECK_RESULT(vpi_get(vpiType, vh141), vpiStructVar); + + TestVpiHandle vh142 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh141); + CHECK_RESULT_NZ(vh142); + CHECK_RESULT(vpi_get(vpiType, vh142), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh142), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x6789; + vpi_put_value(vh142, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh142, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6789); + + TestVpiHandle vh144 = vpi_handle_by_index(vh140, 1); + CHECK_RESULT_NZ(vh144); + TestVpiHandle vh145 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh144); + CHECK_RESULT_NZ(vh145); + putValue.value.integer = 0x2468; + vpi_put_value(vh145, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh145, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x2468); + vpi_get_value(vh142, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6789); + + TestVpiHandle vh146 + = vpi_handle_by_name((PLI_BYTE8*)"struct_array_port[1].member_c", nullptr); + CHECK_RESULT_NZ(vh146); + vpi_get_value(vh146, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x2468); + + TestVpiHandle vh143 = vpi_iterate(vpiMember, vh141); + CHECK_RESULT_NZ(vh143); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh143)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh143.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + } + + // multidimensional port array of unpacked structs + { + TestVpiHandle vh150 = VPI_HANDLE("struct_matrix_port"); + CHECK_RESULT_NZ(vh150); + CHECK_RESULT(vpi_get(vpiType, vh150), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh150), 6); + + TestVpiHandle vh151 = vpi_handle_by_index(vh150, 1); + CHECK_RESULT_NZ(vh151); + CHECK_RESULT(vpi_get(vpiType, vh151), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh151), 3); + + TestVpiHandle vh152 = vpi_handle_by_index(vh151, 2); + CHECK_RESULT_NZ(vh152); + CHECK_RESULT(vpi_get(vpiType, vh152), vpiStructVar); + + TestVpiHandle vh153 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh152); + CHECK_RESULT_NZ(vh153); + CHECK_RESULT(vpi_get(vpiType, vh153), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh153), 16); + p = vpi_get_str(vpiName, vh153); + CHECK_RESULT_CSTR(p, "member_c"); + p = vpi_get_str(vpiFullName, vh153); + { + const std::string expectedFullName + = std::string{vpi_get_str(vpiFullName, vh152)} + ".member_c"; + CHECK_RESULT_CSTR(p, expectedFullName.c_str()); + } + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x1357; + vpi_put_value(vh153, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh153, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x1357); + + TestVpiHandle vh154 + = vpi_handle_by_name((PLI_BYTE8*)"struct_matrix_port[1][2].member_c", nullptr); + CHECK_RESULT_NZ(vh154); + vpi_get_value(vh154, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x1357); + } + + // unpacked struct net port members + { + TestVpiHandle vh190 = VPI_HANDLE("wire_unpacked_struct_port"); + CHECK_RESULT_NZ(vh190); + CHECK_RESULT(vpi_get(vpiType, vh190), vpiStructNet); + CHECK_RESULT(vpi_get(vpiPacked, vh190), 0); + p = vpi_get_str(vpiType, vh190); + CHECK_RESULT_CSTR(p, "vpiStructNet"); + + TestVpiHandle vh191 = vpi_handle_by_name((PLI_BYTE8*)"member_a", vh190); + CHECK_RESULT_NZ(vh191); + CHECK_RESULT(vpi_get(vpiType, vh191), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh191), 7); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x25; + vpi_put_value(vh191, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh191, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x25); + + TestVpiHandle vh192 = vpi_iterate(vpiMember, vh190); + CHECK_RESULT_NZ(vh192); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh192)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh192.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + } + + // net array of unpacked structs + { + TestVpiHandle vh193 = VPI_HANDLE("wire_struct_array_port"); + CHECK_RESULT_NZ(vh193); + CHECK_RESULT(vpi_get(vpiType, vh193), vpiNetArray); + + TestVpiHandle vh194 = vpi_handle_by_index(vh193, 0); + CHECK_RESULT_NZ(vh194); + CHECK_RESULT(vpi_get(vpiType, vh194), vpiStructNet); + CHECK_RESULT(vpi_get(vpiPacked, vh194), 0); + + TestVpiHandle vh195 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh194); + CHECK_RESULT_NZ(vh195); + CHECK_RESULT(vpi_get(vpiType, vh195), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh195), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x579b; + vpi_put_value(vh195, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh195, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x579b); + } + + // non-port array of unpacked structs + { + TestVpiHandle vh160 = VPI_HANDLE("struct_array_signal"); + CHECK_RESULT_NZ(vh160); + CHECK_RESULT(vpi_get(vpiType, vh160), vpiRegArray); + + TestVpiHandle vh161 = vpi_handle_by_index(vh160, 1); + CHECK_RESULT_NZ(vh161); + CHECK_RESULT(vpi_get(vpiType, vh161), vpiStructVar); + + TestVpiHandle vh162 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh161); + CHECK_RESULT_NZ(vh162); + CHECK_RESULT(vpi_get(vpiType, vh162), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh162), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xace0; + vpi_put_value(vh162, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh163 = vpi_handle_by_name( + const_cast(TestSimulator::rooted("struct_array_signal[1].member_c")), + nullptr); + CHECK_RESULT_NZ(vh163); + vpi_get_value(vh163, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xace0); + } + + // array of unpacked structs with unpacked-array members + { + TestVpiHandle vh170 = VPI_HANDLE("parent_struct_array"); + CHECK_RESULT_NZ(vh170); + CHECK_RESULT(vpi_get(vpiType, vh170), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh170), 2); + + TestVpiHandle vh171 = vpi_handle_by_index(vh170, 0); + CHECK_RESULT_NZ(vh171); + CHECK_RESULT(vpi_get(vpiType, vh171), vpiStructVar); + + TestVpiHandle vh172 = vpi_handle_by_index(vh170, 1); + CHECK_RESULT_NZ(vh172); + CHECK_RESULT(vpi_get(vpiType, vh172), vpiStructVar); + + TestVpiHandle vh173 = vpi_handle_by_name((PLI_BYTE8*)"tail_array", vh171); + CHECK_RESULT_NZ(vh173); + CHECK_RESULT(vpi_get(vpiType, vh173), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh173), 4); + + TestVpiHandle vh174 = vpi_handle_by_index(vh173, 3); + CHECK_RESULT_NZ(vh174); + CHECK_RESULT(vpi_get(vpiType, vh174), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh174), 8); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xaa; + vpi_put_value(vh174, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh17a = vpi_handle_by_name((PLI_BYTE8*)"trailing_children", vh171); + CHECK_RESULT_NZ(vh17a); + CHECK_RESULT(vpi_get(vpiType, vh17a), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh17a), 2); + + TestVpiHandle vh17b = vpi_handle_by_index(vh17a, 2); + CHECK_RESULT_NZ(vh17b); + CHECK_RESULT(vpi_get(vpiType, vh17b), vpiStructVar); + + TestVpiHandle vh17c = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh17b); + CHECK_RESULT_NZ(vh17c); + CHECK_RESULT(vpi_get(vpiType, vh17c), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh17c), 8); + putValue.value.integer = 0x11; + vpi_put_value(vh17c, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh175 = vpi_handle_by_name((PLI_BYTE8*)"scalar", vh172); + CHECK_RESULT_NZ(vh175); + CHECK_RESULT(vpi_get(vpiType, vh175), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh175), 16); + putValue.value.integer = 0x55cc; + vpi_put_value(vh175, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh175, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x55cc); + + vpi_get_value(vh174, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xaa); + vpi_get_value(vh17c, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x11); + + TestVpiHandle vh176 = vpi_handle_by_name((PLI_BYTE8*)"children", vh172); + CHECK_RESULT_NZ(vh176); + CHECK_RESULT(vpi_get(vpiType, vh176), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh176), 2); + + TestVpiHandle vh177 = vpi_handle_by_index(vh176, 3); + CHECK_RESULT_NZ(vh177); + CHECK_RESULT(vpi_get(vpiType, vh177), vpiStructVar); + + TestVpiHandle vh178 = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh177); + CHECK_RESULT_NZ(vh178); + CHECK_RESULT(vpi_get(vpiType, vh178), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh178), 8); + putValue.value.integer = 0x5a; + vpi_put_value(vh178, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh178, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x5a); + + TestVpiHandle vh179 + = vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].children[3].child_leaf")), + nullptr); + CHECK_RESULT_NZ(vh179); + vpi_get_value(vh179, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x5a); + + TestVpiHandle vh17d = vpi_handle_by_name((PLI_BYTE8*)"trailing_children", vh172); + CHECK_RESULT_NZ(vh17d); + TestVpiHandle vh17e = vpi_handle_by_index(vh17d, 2); + CHECK_RESULT_NZ(vh17e); + TestVpiHandle vh17f = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh17e); + CHECK_RESULT_NZ(vh17f); + putValue.value.integer = 0x6b; + vpi_put_value(vh17f, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh17f, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6b); + + TestVpiHandle vh17g + = vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].trailing_children[2].child_leaf")), + nullptr); + CHECK_RESULT_NZ(vh17g); + vpi_get_value(vh17g, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6b); + + CHECK_RESULT_Z(vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].children[3:2].child_leaf")), + nullptr)); + } + + // array of unpacked structs with members requiring different C++ alignments + { + TestVpiHandle vh180 = VPI_HANDLE("aligned_struct_array"); + CHECK_RESULT_NZ(vh180); + CHECK_RESULT(vpi_get(vpiType, vh180), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh180), 2); + + TestVpiHandle vh181 = vpi_handle_by_index(vh180, 1); + CHECK_RESULT_NZ(vh181); + CHECK_RESULT(vpi_get(vpiType, vh181), vpiStructVar); + + TestVpiHandle vh182 = vpi_handle_by_name((PLI_BYTE8*)"word_member", vh181); + CHECK_RESULT_NZ(vh182); + CHECK_RESULT(vpi_get(vpiType, vh182), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh182), 32); + + TestVpiHandle vh183 = vpi_handle_by_name((PLI_BYTE8*)"quad_member", vh181); + CHECK_RESULT_NZ(vh183); + CHECK_RESULT(vpi_get(vpiType, vh183), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh183), 64); + + TestVpiHandle vh184 = vpi_handle_by_name((PLI_BYTE8*)"real_member", vh181); + CHECK_RESULT_NZ(vh184); + CHECK_RESULT(vpi_get(vpiType, vh184), vpiRealVar); + + TestVpiHandle vh185 = vpi_handle_by_name((PLI_BYTE8*)"string_member", vh181); + CHECK_RESULT_NZ(vh185); + CHECK_RESULT(vpi_get(vpiType, vh185), vpiStringVar); + + TestVpiHandle vh186 = vpi_handle_by_name((PLI_BYTE8*)"nested_member", vh181); + CHECK_RESULT_NZ(vh186); + CHECK_RESULT(vpi_get(vpiType, vh186), vpiStructVar); + + TestVpiHandle vh187 = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh186); + CHECK_RESULT_NZ(vh187); + CHECK_RESULT(vpi_get(vpiType, vh187), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh187), 8); + } + + // Array stride for a struct whose C++ size needs tail padding after a nested struct + { + TestVpiHandle vh188a = VPI_HANDLE("alignment_stride_array"); + CHECK_RESULT_NZ(vh188a); + CHECK_RESULT(vpi_get(vpiType, vh188a), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh188a), 2); + + TestVpiHandle vh188b = vpi_handle_by_index(vh188a, 1); + CHECK_RESULT_NZ(vh188b); + CHECK_RESULT(vpi_get(vpiType, vh188b), vpiStructVar); + + TestVpiHandle vh188c = vpi_handle_by_name((PLI_BYTE8*)"tail", vh188b); + CHECK_RESULT_NZ(vh188c); + CHECK_RESULT(vpi_get(vpiType, vh188c), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh188c), 8); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xc3; + vpi_put_value(vh188c, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh188c, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xc3); + } + + TestVpiHandle vh188 = VPI_HANDLE("nested_struct_port"); + CHECK_RESULT_NZ(vh188); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"outer_inner.no_such", vh188)); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.\\no.such escaped.member", nullptr)); + + // Unpacked struct member with an escaped identifier containing a dot + { + TestVpiHandle vh200 = VPI_HANDLE("escaped_member_struct_signal"); + CHECK_RESULT_NZ(vh200); + CHECK_RESULT(vpi_get(vpiType, vh200), vpiStructVar); + + TestVpiHandle vh201 = vpi_handle_by_name((PLI_BYTE8*)"\\a.b ", vh200); + CHECK_RESULT_NZ(vh201); + CHECK_RESULT(vpi_get(vpiType, vh201), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh201), 8); + + TestVpiHandle vh202 + = vpi_handle_by_name((PLI_BYTE8*)"t.escaped_member_struct_signal.\\a.b ", nullptr); + CHECK_RESULT_NZ(vh202); + CHECK_RESULT(vpi_get(vpiType, vh202), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh202), 8); + + TestVpiHandle vh203 = vpi_iterate(vpiMember, vh200); + CHECK_RESULT_NZ(vh203); + std::set escapedMembers; + while (TestVpiHandle member = vpi_scan(vh203)) { + escapedMembers.insert(vpi_get_str(vpiName, member)); + } + vh203.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(escapedMembers.count("\\a.b "), 1); + CHECK_RESULT(escapedMembers.count("plain_after"), 1); + } + + return 0; +} + int _mon_check_var() { TestVpiHandle vh1 = VPI_HANDLE("onebit"); CHECK_RESULT_NZ(vh1); @@ -1543,6 +2165,9 @@ extern "C" int mon_check() { printf("-mon_check()\n"); #endif +#if !defined(T_VPI_VAR2) && !defined(T_VPI_VAR3) + if (int status = _mon_check_unpacked_struct_members()) return status; +#endif if (int status = _mon_check_mcd()) return status; if (int status = _mon_check_callbacks()) return status; if (int status = _mon_check_value_callbacks()) return status; @@ -1607,6 +2232,7 @@ int main(int argc, char** argv) { uint64_t sim_time = 1100; contextp->debug(0); contextp->commandArgs(argc, argv); + VerilatedVpi::selfTest(); const std::unique_ptr topp{new VM_PREFIX{contextp.get(), // Note null name - we're flattening it out diff --git a/test_regress/t/t_vpi_var.v b/test_regress/t/t_vpi_var.v index e115a1c4d..ea66b0712 100644 --- a/test_regress/t/t_vpi_var.v +++ b/test_regress/t/t_vpi_var.v @@ -16,7 +16,9 @@ module t (/*AUTOARG*/ // Outputs x, // Inputs - clk, a + clk, a, unpacked_struct_port, unpacked_union_port, nested_struct_port, + struct_array_port, struct_matrix_port, struct_with_packed_arrays_port, + wire_unpacked_struct_port, wire_struct_array_port ); `ifdef VERILATOR @@ -54,6 +56,99 @@ extern "C" int mon_check(); // verilator lint_on ASCRANGE reg unpacked_only[7:0]; + typedef struct { + logic [6:0] member_a; + logic member_b; + logic [15:0] member_c; + } unpacked_struct_t; + + input unpacked_struct_t unpacked_struct_port /*verilator public_flat_rw*/; + input wire unpacked_struct_t wire_unpacked_struct_port /*verilator public_flat_rw*/; + + typedef union { + logic [7:0] union_byte0; + logic [7:0] union_byte1; + } unpacked_union_t; + + input unpacked_union_t unpacked_union_port /*verilator public_flat_rw*/; + + typedef struct { + logic [3:0] inner_x; + logic [3:0] inner_y; + } inner_struct_t; + + typedef struct { + logic [7:0] outer_a; + inner_struct_t outer_inner; + } nested_struct_t; + + input nested_struct_t nested_struct_port /*verilator public_flat_rw*/; + + input unpacked_struct_t struct_array_port [1:0] /*verilator public_flat_rw*/; + input unpacked_struct_t struct_matrix_port [1:0][2:0] /*verilator public_flat_rw*/; + input wire unpacked_struct_t wire_struct_array_port [1:0] /*verilator public_flat_rw*/; + unpacked_struct_t struct_array_signal [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [6:0] unsigned_member; + logic signed [6:0] signed_member; + bit bit_member; + } member_flags_struct_t; + + member_flags_struct_t member_flags_struct_signal /*verilator public_flat_rw*/; + + typedef struct { + logic [7:0] child_leaf; + } child_struct_t; + + typedef struct { + logic [15:0] scalar; + child_struct_t children [3:2]; + logic [7:0] tail_array [2:5]; + child_struct_t trailing_children [3:2]; + } parent_struct_t; + + parent_struct_t parent_struct_array [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [7:0] \a.b ; + logic [7:0] plain_after; + } escaped_member_struct_t; + + escaped_member_struct_t escaped_member_struct_signal /*verilator public_flat_rw*/; + + typedef struct { + logic [31:0] word_member; + logic [63:0] quad_member; + real real_member; + string string_member; + child_struct_t nested_member; + } aligned_struct_t; + + aligned_struct_t aligned_struct_array [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [63:0] quad_leaf; + logic [7:0] byte_tail; + } stride_inner_struct_t; + + typedef struct { + logic [7:0] lead; + stride_inner_struct_t nested; + logic [7:0] tail; + } stride_outer_struct_t; + + stride_outer_struct_t alignment_stride_array [1:0] /*verilator public_flat_rw*/; + + // verilator lint_off ASCRANGE + typedef struct { + logic [0:15][0:3][7:0] packed_matrix; + logic [8:-7][3:-4] reverse_matrix; + } struct_with_packed_arrays_t; + // verilator lint_on ASCRANGE + + input struct_with_packed_arrays_t struct_with_packed_arrays_port /*verilator public_flat_rw*/; + reg [7:0] text_byte /*verilator public_flat_rw @(posedge clk) */; reg [15:0] text_half /*verilator public_flat_rw @(posedge clk) */; reg [31:0] text_word /*verilator public_flat_rw @(posedge clk) */; @@ -166,6 +261,7 @@ extern "C" int mon_check(); if (text != "lorem ipsum") $stop; if (str1 != "something a lot longer than hello") $stop; if (real1 > 123456.7895 || real1 < 123456.7885 ) $stop; + if (alignment_stride_array[1].tail != 8'hc3) $stop; end always @(posedge clk) begin From d5c040d8e60c18700b2f1173c9c301643543d70e Mon Sep 17 00:00:00 2001 From: Jakub Wasilewski Date: Tue, 23 Jun 2026 15:47:55 +0200 Subject: [PATCH 85/89] Fix skewed dist operator for arrays (#7802) --- src/V3Randomize.cpp | 158 +++++++++++++--- test_regress/t/t_constraint_dist.v | 17 +- .../t/t_constraint_dist_foreach_if.py | 21 ++ test_regress/t/t_constraint_dist_foreach_if.v | 133 +++++++++++++ test_regress/t/t_constraint_dist_range.py | 21 ++ test_regress/t/t_constraint_dist_range.v | 179 ++++++++++++++++++ 6 files changed, 497 insertions(+), 32 deletions(-) create mode 100755 test_regress/t/t_constraint_dist_foreach_if.py create mode 100644 test_regress/t/t_constraint_dist_foreach_if.v create mode 100755 test_regress/t/t_constraint_dist_range.py create mode 100644 test_regress/t/t_constraint_dist_range.v diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 52a750a6a..36699526d 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -2144,10 +2144,15 @@ class ConstraintExprVisitor final : public VNVisitor { nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp}); } else { iterateAndNextNull(nodep->bodyp()); - nodep->replaceWith(new AstBegin{fl, "", - new AstForeach{fl, nodep->headerp()->unlinkFrBack(), - nodep->bodyp()->unlinkFrBackWithNext()}, - true}); + AstNode* bodyp = nodep->bodyp()->unlinkFrBackWithNext(); + // Prepend bucket preamble stmts stored by lowerDistConstraints (foreach case) + if (AstNode* const preamblep = nodep->user3p()) { + preamblep->addNext(bodyp); + bodyp = preamblep; + nodep->user3p(nullptr); + } + nodep->replaceWith(new AstBegin{ + fl, "", new AstForeach{fl, nodep->headerp()->unlinkFrBack(), bodyp}, true}); } VL_DO_DANGLING(nodep->deleteTree(), nodep); } @@ -3076,6 +3081,7 @@ class RandomizeVisitor final : public VNVisitor { // AstVar::user3() -> bool. Handled in constraints // AstClass::user3p() -> AstVar*. Constrained randomizer variable // AstConstraint::user3p() -> AstTask*. Pointer to resize procedure + // AstConstraintForeach::user3p() -> AstNode*. Dist bucket preamble stmts (foreach case) // AstClass::user4p() -> AstVar*. Constraint mode state variable // AstVar::user4p() -> AstVar*. Size variable for constrained queues // AstMemberSel::user2p() -> AstNodeModule*. Pointer to containing module @@ -4431,20 +4437,41 @@ class RandomizeVisitor final : public VNVisitor { // Replace AstDist with weighted bucket selection via AstConstraintIf chain. // Supports both constant and variable weight expressions. - void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp) { + void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp, + AstConstraintForeach* foreachp = nullptr) { + // When inside a foreach, bucket preamble stmts are stored in foreachp->user3p() + // (as a linked list) so visit(AstConstraintForeach*) can inject them into the + // real AstForeach body. Outside a foreach, they go directly into taskp. + AstNode* foreachTailp = nullptr; + auto addStmt = [&](AstNode* nodep) { + if (foreachp) { + if (!foreachTailp) { + foreachp->user3p(nodep); + foreachTailp = nodep; + } else { + foreachTailp->addNext(nodep); + foreachTailp = nodep; + } + } else { + taskp->addStmtsp(nodep); + } + }; + for (AstNode *nextip, *itemp = constrItemsp; itemp; itemp = nextip) { nextip = itemp->nextp(); // Recursively handle ConstraintIf nodes (dist can be inside if/else) if (AstConstraintIf* const cifp = VN_CAST(itemp, ConstraintIf)) { - if (cifp->thensp()) lowerDistConstraints(taskp, cifp->thensp()); - if (cifp->elsesp()) lowerDistConstraints(taskp, cifp->elsesp()); + if (cifp->thensp()) // LCOV_EXCL_LINE + lowerDistConstraints(taskp, cifp->thensp(), foreachp); // LCOV_EXCL_LINE + if (cifp->elsesp()) lowerDistConstraints(taskp, cifp->elsesp(), foreachp); continue; } // Recursively handle ConstraintForeach nodes (dist can be inside foreach) if (AstConstraintForeach* const cfep = VN_CAST(itemp, ConstraintForeach)) { - if (cfep->bodyp()) lowerDistConstraints(taskp, cfep->bodyp()); + if (cfep->bodyp()) // LCOV_EXCL_LINE + lowerDistConstraints(taskp, cfep->bodyp(), cfep); // LCOV_EXCL_LINE continue; } @@ -4464,7 +4491,7 @@ class RandomizeVisitor final : public VNVisitor { AstConstraintIf* const liftedp = liftLogIfChainToConstraintIf(topLogIfp); constrExprp->replaceWith(liftedp); VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp); - lowerDistConstraints(taskp, liftedp->thensp()); + lowerDistConstraints(taskp, liftedp->thensp(), foreachp); continue; } } @@ -4526,6 +4553,47 @@ class RandomizeVisitor final : public VNVisitor { continue; } + // IEEE 1800-2023 18.5.3: values not in the distribution must never appear. + // Build the union of all non-zero-weight ranges as a single hard ConstraintExpr + AstNodeExpr* unionExprp = nullptr; + for (const auto& bucket : buckets) { + AstNodeExpr* memberp; + if (const AstInsideRange* const irp = VN_CAST(bucket.rangep, InsideRange)) { + // (distExpr >= lo) && (distExpr <= hi); signed comparisons for signed vars + const bool isSigned = distp->exprp()->isSigned(); + AstNodeExpr* const distExprGtep = distp->exprp()->cloneTreePure(false); + AstNodeExpr* const distExprLtep = distp->exprp()->cloneTreePure(false); + distExprGtep->user1(true); + distExprLtep->user1(true); + AstNodeExpr* const gep + = isSigned ? static_cast(new AstGteS{ + fl, distExprGtep, irp->lhsp()->cloneTreePure(false)}) + : static_cast(new AstGte{ + fl, distExprGtep, irp->lhsp()->cloneTreePure(false)}); + AstNodeExpr* const lep + = isSigned ? static_cast(new AstLteS{ + fl, distExprLtep, irp->rhsp()->cloneTreePure(false)}) + : static_cast(new AstLte{ + fl, distExprLtep, irp->rhsp()->cloneTreePure(false)}); + gep->user1(true); + lep->user1(true); + memberp = new AstLogAnd{fl, gep, lep}; + } else { + // distExpr == val + AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); + distExprCopyp->user1(true); + memberp = new AstEq{fl, distExprCopyp, bucket.rangep->cloneTreePure(false)}; + } + memberp->user1(true); + if (!unionExprp) { + unionExprp = memberp; + } else { + unionExprp = new AstLogOr{fl, memberp, unionExprp}; + unionExprp->user1(true); + } + } + AstConstraintExpr* const membershipp = new AstConstraintExpr{fl, unionExprp}; + // Build totalWeight expression: w[0] + w[1] + ... + w[N-1] AstNodeExpr* totalWeightExprp = nullptr; for (auto& bucket : buckets) { @@ -4547,8 +4615,8 @@ class RandomizeVisitor final : public VNVisitor { totalVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); totalVarp->funcLocal(true); totalVarp->isInternal(true); - taskp->addStmtsp(totalVarp); - taskp->addStmtsp( + addStmt(totalVarp); + addStmt( new AstAssign{fl, new AstVarRef{fl, totalVarp, VAccess::WRITE}, totalWeightExprp}); // bucketVar = (rand64() % totalWeight) + 1 @@ -4559,11 +4627,11 @@ class RandomizeVisitor final : public VNVisitor { bucketVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); bucketVarp->funcLocal(true); bucketVarp->isInternal(true); - taskp->addStmtsp(bucketVarp); + addStmt(bucketVarp); - AstNodeExpr* randp = new AstRand{fl, nullptr, false}; + AstNodeExpr* const randp = new AstRand{fl, nullptr, false}; randp->dtypeSetUInt64(); - taskp->addStmtsp(new AstAssign{ + addStmt(new AstAssign{ fl, new AstVarRef{fl, bucketVarp, VAccess::WRITE}, new AstAdd{ fl, new AstConst{fl, AstConst::Unsized64{}, 1}, @@ -4588,27 +4656,59 @@ class RandomizeVisitor final : public VNVisitor { for (int i = static_cast(buckets.size()) - 1; i >= 0; --i) { AstNodeExpr* constraintExprp; if (const AstInsideRange* const irp = VN_CAST(buckets[i].rangep, InsideRange)) { - AstNodeExpr* const exprCopy1p = distp->exprp()->cloneTreePure(false); - exprCopy1p->user1(true); - AstNodeExpr* const exprCopy2p = distp->exprp()->cloneTreePure(false); - exprCopy2p->user1(true); - AstGte* const gtep - = new AstGte{fl, exprCopy1p, irp->lhsp()->cloneTreePure(false)}; - gtep->user1(true); - AstLte* const ltep - = new AstLte{fl, exprCopy2p, irp->rhsp()->cloneTreePure(false)}; - ltep->user1(true); - constraintExprp = new AstLogAnd{fl, gtep, ltep}; + // Pick distExpr = lo + rand64() % (hi - lo + 1) for a uniform value in range + AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); + distExprCopyp->user1(true); + const int distWidth = distp->exprp()->width(); + // Compute range size in 64-bit to avoid overflow + const AstConst* const lopC = VN_CAST(irp->lhsp(), Const); + const AstConst* const hipC = VN_CAST(irp->rhsp(), Const); + AstNodeExpr* rangeSzp; + if (lopC && hipC) { + const uint64_t rsz = hipC->toUQuad() - lopC->toUQuad() + 1; + rangeSzp = new AstConst{fl, AstConst::Unsized64{}, rsz}; + } else { + const bool isSigned = irp->lhsp()->isSigned(); + AstNodeExpr* const lo64p + = isSigned + ? static_cast( + new AstExtendS{fl, irp->lhsp()->cloneTreePure(false), 64}) + : static_cast( + new AstExtend{fl, irp->lhsp()->cloneTreePure(false), 64}); + lo64p->dtypeSetUInt64(); + AstNodeExpr* const hi64p + = isSigned + ? static_cast( + new AstExtendS{fl, irp->rhsp()->cloneTreePure(false), 64}) + : static_cast( + new AstExtend{fl, irp->rhsp()->cloneTreePure(false), 64}); + hi64p->dtypeSetUInt64(); + rangeSzp = new AstAdd{fl, new AstConst{fl, AstConst::Unsized64{}, 1ULL}, + new AstSub{fl, hi64p, lo64p}}; + } + AstNodeExpr* const rand64p = new AstRand{fl, nullptr, false}; + rand64p->dtypeSetUInt64(); + // offset = rand64() % rangeSize (result in [0, rangeSize-1]) + AstNodeExpr* const offset64p = new AstModDiv{fl, rand64p, rangeSzp}; + // Truncate offset to dist expression width, then add lo + AstNodeExpr* const offsetp = new AstCCast{fl, offset64p, distWidth}; + AstNodeExpr* const lop = irp->lhsp()->cloneTreePure(false); + AstNodeExpr* const valuep = new AstAdd{fl, lop, offsetp}; + valuep->dtypeFrom(distp->exprp()); + constraintExprp = new AstEq{fl, distExprCopyp, valuep}; constraintExprp->user1(true); } else { - AstNodeExpr* const exprCopyp = distp->exprp()->cloneTreePure(false); - exprCopyp->user1(true); + AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); + distExprCopyp->user1(true); constraintExprp - = new AstEq{fl, exprCopyp, buckets[i].rangep->cloneTreePure(false)}; + = new AstEq{fl, distExprCopyp, buckets[i].rangep->cloneTreePure(false)}; constraintExprp->user1(true); } AstConstraintExpr* const thenp = new AstConstraintExpr{fl, constraintExprp}; + // Per IEEE 18.5.3: weights are a preference, not a hard constraint. + // The solver may discard this when it conflicts with other constraints. + thenp->isSoft(true); if (!chainp) { chainp = thenp; @@ -4622,6 +4722,8 @@ class RandomizeVisitor final : public VNVisitor { if (chainp) { constrExprp->replaceWith(chainp); VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp); + // Hard membership precedes the soft bucket chain in the constraint list. + chainp->addHereThisAsNext(membershipp); } // Clean up nodes used only as clone templates (never inserted into tree) diff --git a/test_regress/t/t_constraint_dist.v b/test_regress/t/t_constraint_dist.v index 7d27d8748..3b62845a3 100644 --- a/test_regress/t/t_constraint_dist.v +++ b/test_regress/t/t_constraint_dist.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2024 Antmicro Ltd +// SPDX-FileCopyrightText: 2026 Antmicro // SPDX-License-Identifier: CC0-1.0 `define check_rand(cl, field, cond) \ @@ -11,7 +11,7 @@ begin \ if (!bit'(cl.randomize())) $stop; \ prev_result = longint'(field); \ if (!(cond)) $stop; \ - repeat(9) begin \ + repeat(100) begin \ longint result; \ if (!bit'(cl.randomize())) $stop; \ result = longint'(field); \ @@ -33,8 +33,12 @@ class C; }; constraint distinside { z dist {que}; - w dist {arr}; - }; + w dist {arr}; }; endclass + +class DistNarrow; + rand bit [3:0] x; + constraint c1 { x dist {[4'd1:4'd9] := 1}; } + constraint c2 { x > 4'd5; } endclass module t; @@ -45,6 +49,11 @@ module t; `check_rand(c, c.y, 5 <= c.y && c.y <= 6); `check_rand(c, c.z, 3 <= c.z && c.z <= 5); `check_rand(c, c.w, 5 <= c.w && c.w <= 7); + begin + DistNarrow dn; + dn = new; + `check_rand(dn, dn.x, 6 <= dn.x && dn.x <= 9); + end $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_constraint_dist_foreach_if.py b/test_regress/t/t_constraint_dist_foreach_if.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dist_foreach_if.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_dist_foreach_if.v b/test_regress/t/t_constraint_dist_foreach_if.v new file mode 100644 index 000000000..3ae315711 --- /dev/null +++ b/test_regress/t/t_constraint_dist_foreach_if.v @@ -0,0 +1,133 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// Test that dist constraints nested inside if / -> inside foreach produce +// values only within the declared distribution and cover all buckets. + +// verilog_format: off +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); +// verilog_format: on + +// foreach (a[i]) if (gate) a[i] dist {...} +class ClsIf; + rand bit [3:0] a [4]; + bit gate; + constraint c { + foreach (a[i]) { + if (gate == 1'b1) { + a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 }; + } + } + } +endclass + +// foreach (a[i]) gate -> a[i] dist {...} +class ClsImpl; + rand bit [3:0] a [4]; + bit gate; + constraint c { + foreach (a[i]) { + gate -> (a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 }); + } + } +endclass + +// foreach (a[i]) gateA -> (gateB -> a[i] dist {...}) -- doubly-nested implication +class ClsImplChained; + rand bit [3:0] a [4]; + bit gateA, gateB; + constraint c { + foreach (a[i]) { + gateA -> (gateB -> (a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 })); + } + } +endclass + +module t; + initial begin + // Test if form + begin + static ClsIf obj = new(); + int seen_zero, seen_nonzero; + obj.gate = 1'b1; + seen_zero = 0; + seen_nonzero = 0; + repeat (100) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4) begin + $write("%%Error: %s:%0d: if: value out of dist range: %0d\n", + `__FILE__, `__LINE__, obj.a[i]); + $stop; + end + if (obj.a[i] == 0) seen_zero++; + else seen_nonzero++; + end + end + if (seen_zero == 0 || seen_nonzero == 0) begin + $write("%%Error: %s:%0d: dist inside foreach+if: not all buckets hit (zero=%0d nonzero=%0d)\n", + `__FILE__, `__LINE__, seen_zero, seen_nonzero); + $stop; + end + end + + // Test -> (implication) form + begin + static ClsImpl obj = new(); + int seen_zero, seen_nonzero; + obj.gate = 1'b1; + seen_zero = 0; + seen_nonzero = 0; + repeat (100) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4) begin + $write("%%Error: %s:%0d: ->: value out of dist range: %0d\n", + `__FILE__, `__LINE__, obj.a[i]); + $stop; + end + if (obj.a[i] == 0) seen_zero++; + else seen_nonzero++; + end + end + if (seen_zero == 0 || seen_nonzero == 0) begin + $write("%%Error: %s:%0d: dist inside foreach+->: not all buckets hit (zero=%0d nonzero=%0d)\n", + `__FILE__, `__LINE__, seen_zero, seen_nonzero); + $stop; + end + end + + // Test doubly-nested -> (chained implication) form + begin + static ClsImplChained obj = new(); + int seen_zero, seen_nonzero; + obj.gateA = 1'b1; + obj.gateB = 1'b1; + seen_zero = 0; + seen_nonzero = 0; + repeat (100) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4) begin + $write("%%Error: %s:%0d: ->->: value out of dist range: %0d\n", + `__FILE__, `__LINE__, obj.a[i]); + $stop; + end + if (obj.a[i] == 0) seen_zero++; + else seen_nonzero++; + end + end + if (seen_zero == 0 || seen_nonzero == 0) begin + $write("%%Error: %s:%0d: dist inside foreach+->->: not all buckets hit (zero=%0d nonzero=%0d)\n", + `__FILE__, `__LINE__, seen_zero, seen_nonzero); + $stop; + end + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_dist_range.py b/test_regress/t/t_constraint_dist_range.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dist_range.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_dist_range.v b/test_regress/t/t_constraint_dist_range.v new file mode 100644 index 000000000..fbddaf0a1 --- /dev/null +++ b/test_regress/t/t_constraint_dist_range.v @@ -0,0 +1,179 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0); +`define check_tol(gotv,expv) `check_range((gotv), (expv)*(100-TOL_PCT)/100, (expv)*(100+TOL_PCT)/100) +// verilog_format: on + +// Scalar: uniform over [0:9] (10% each) +class DistScalarRange; + rand bit [3:0] x; + constraint c { x dist {[4'd0:4'd9] := 1}; } +endclass + +// Array foreach: uniform over [0:4] (20% each per element) +class DistForeachUniform; + rand bit [2:0] a[5]; + constraint c { foreach (a[i]) a[i] dist {[3'd0:3'd4] := 1}; } +endclass + +// Array foreach: mixed single + range +// a[i] dist {0 := 5, [1:9] := 1} total weight = 5+9 = 14 +// 0: 5/14 ~= 35.7%, 1..9: 1/14 ~= 7.1% each +class DistForeachMixed; + rand bit [3:0] a[5]; + constraint c { foreach (a[i]) a[i] dist {4'd0 := 5, [4'd1:4'd9] := 1}; } +endclass + +// Scalar signed int: uniform over negative range [-9:0] (10% each) +class DistNegRange; + rand int x; + constraint c { x dist {[-9:0] := 1}; } +endclass + +// Non-constant unsigned range bounds: uniform over [lo_val:hi_val] = [2:7] (6 values) +class DistVarRangeUnsigned; + rand bit [3:0] x; + bit [3:0] lo_val = 4'd2, hi_val = 4'd7; + constraint c { x dist {[lo_val:hi_val] := 1}; } +endclass + +// Mixed const/non-const bounds: lo is constant, hi is a variable [1:hi_val] = [1:7] +class DistMixedBounds; + rand bit [3:0] x; + bit [3:0] hi_val = 4'd7; + constraint c { x dist {[4'd1:hi_val] := 1}; } +endclass + +module t; + parameter int N = 2000; // randomize() calls per test + parameter int TOL_PCT = 30; // +-% tolerance on expected counts + + initial begin + + // --- T1: scalar uniform [0:9] --- + // 10 values, expected N/10 each + begin + automatic DistScalarRange obj = new(); + int cnt[10]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x > 4'd9) begin + $write("%%Error: x=%0d outside valid range [0:9]\n", obj.x); + `stop; + end + cnt[obj.x]++; + end + foreach (cnt[v]) + `check_tol(cnt[v], N/10) + end + + // --- T2: array foreach uniform [0:4], 5 elements * N calls --- + // total element samples = N*5; 5 values -> expected N each + begin + automatic DistForeachUniform obj = new(); + int cnt[5]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 3'd4) begin + $write("%%Error: a[%0d]=%0d outside valid range [0:4]\n", i, obj.a[i]); + `stop; + end + cnt[obj.a[i]]++; + end + end + foreach (cnt[v]) + `check_tol(cnt[v], N) + end + + // --- T3: array foreach mixed {0:=5, [1:9]:=1}, 5 elements * N calls --- + // total element samples = N*5; total weight = 5+9 = 14 + // v=0: expected N*5*5/14 + // v=1..9: expected N*5*1/14 + begin + automatic DistForeachMixed obj = new(); + int cnt[10]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + foreach (obj.a[i]) begin + if (obj.a[i] > 4'd9) begin + $write("%%Error: a[%0d]=%0d outside valid range [0:9]\n", i, obj.a[i]); + `stop; + end + cnt[obj.a[i]]++; + end + end + `check_tol(cnt[0], N*5*5/14) + for (int v = 1; v <= 9; v++) + `check_tol(cnt[v], N*5*1/14) + end + + // --- T4: signed int, uniform over negative range [-9:0] --- + // 10 values, expected N/10 each + // cnt[v] = count of (x == v-9), so v=0 -> x=-9, v=9 -> x=0 + begin + automatic DistNegRange obj = new(); + int cnt[10]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x < -9 || obj.x > 0) begin + $write("%%Error: x=%0d outside valid range [-9:0]\n", obj.x); + `stop; + end + cnt[obj.x + 9]++; + end + foreach (cnt[v]) + `check_tol(cnt[v], N/10) + end + + // --- T5: non-constant unsigned range bounds [lo_val:hi_val] = [2:7] --- + // 6 values, expected N/6 each + begin + automatic DistVarRangeUnsigned obj = new(); + int cnt[16]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x < 4'd2 || obj.x > 4'd7) begin + $write("%%Error: x=%0d outside valid range [2:7]\n", obj.x); + `stop; + end + cnt[obj.x]++; + end + for (int v = 2; v <= 7; v++) + `check_tol(cnt[v], N/6) + end + + // --- T6: mixed const/non-const bounds [4'd1:hi_val] = [1:7] --- + // 7 values, expected N/7 each + begin + automatic DistMixedBounds obj = new(); + int cnt[16]; + foreach (cnt[v]) cnt[v] = 0; + repeat (N) begin + `checkd(obj.randomize(), 1) + if (obj.x < 4'd1 || obj.x > 4'd7) begin + $write("%%Error: x=%0d outside valid range [1:7]\n", obj.x); + `stop; + end + cnt[obj.x]++; + end + for (int v = 1; v <= 7; v++) + `check_tol(cnt[v], N/7) + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 0cd13f80c93d277750868c60b1eefbfa8763378e Mon Sep 17 00:00:00 2001 From: Igor Zaworski Date: Tue, 23 Jun 2026 20:07:15 +0200 Subject: [PATCH 86/89] Fix nested class split crash (#7826) Signed-off-by: Igor Zaworski --- src/V3SplitVar.cpp | 8 ++++- test_regress/t/t_class_nested_split_var.py | 18 ++++++++++ test_regress/t/t_class_nested_split_var.v | 40 ++++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100755 test_regress/t/t_class_nested_split_var.py create mode 100644 test_regress/t/t_class_nested_split_var.v diff --git a/src/V3SplitVar.cpp b/src/V3SplitVar.cpp index 95bd05954..917123be6 100644 --- a/src/V3SplitVar.cpp +++ b/src/V3SplitVar.cpp @@ -477,7 +477,13 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl { UINFO(4, "Start checking " << nodep->prettyNameQ()); if (!VN_IS(nodep, Module)) { UINFO(4, "Skip " << nodep->prettyNameQ()); - nodep->foreach([this](AstVarXRef* const nodep) { handleVarXRef(nodep); }); + nodep->foreach([this](AstNodeVarRef* const nodep) { + if (AstVarXRef* const varXRefp = VN_CAST(nodep, VarXRef)) { + handleVarXRef(varXRefp); + } else if (m_modp) { + iterate(VN_AS(nodep, VarRef)); + } + }); return; } UASSERT_OBJ(!m_modp, m_modp, "Nested module declaration"); diff --git a/test_regress/t/t_class_nested_split_var.py b/test_regress/t/t_class_nested_split_var.py new file mode 100755 index 000000000..c87a1e49f --- /dev/null +++ b/test_regress/t/t_class_nested_split_var.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_nested_split_var.v b/test_regress/t/t_class_nested_split_var.v new file mode 100644 index 000000000..c4124f775 --- /dev/null +++ b/test_regress/t/t_class_nested_split_var.v @@ -0,0 +1,40 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +`ifdef VERILATOR +// The '$c(1)' is there to prevent inlining of the signal by V3Gate. +`define IMPURE_ONE ($c(1)) +`else +// Use standard $random. The chance of getting 2 consecutive zeroes is negligible. +`define IMPURE_ONE (|($random | $random)) +`endif + +module t; + bit [2:0] y; + bit [2:0] z; + assign z[0] = 1'b1; + assign z[1] = !(y[0]); + assign z[2] = !(|y[1:0]); + class Foo; + bit foo; + + task run(); + foo = `IMPURE_ONE; + if (z !== 3'b001) begin + $error("Failed: got %0b, expected %0b", z, 3'b001); + end + if (foo != 1'b1) $stop; + endtask + endclass + Foo test; + initial begin + static Foo foo = new; + #10 y = 3'b111; + #1 foo.run(); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 2baca68f86d42457403c7ec1c1940a4e81598274 Mon Sep 17 00:00:00 2001 From: Tom Jackson Date: Wed, 24 Jun 2026 00:43:31 +0000 Subject: [PATCH 87/89] Fix class/var named identically to an enclosing-scope type (#7827) (#7828) Fixes #7827. --- docs/CONTRIBUTORS | 1 + src/V3LinkDot.cpp | 10 +++- test_regress/t/t_class_member_shadow_type.py | 18 +++++++ test_regress/t/t_class_member_shadow_type.v | 53 ++++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100755 test_regress/t/t_class_member_shadow_type.py create mode 100644 test_regress/t/t_class_member_shadow_type.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index a22db1f07..44f7ecb2c 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -279,6 +279,7 @@ Tobias Jensen Tobias Rosenkranz Tobias Wölfel Todd Strader +Tom Jackson Tom Manner Tomasz Gorochowik Topa Topino diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index a6b038396..7dd5fe3ea 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -3562,12 +3562,18 @@ class LinkDotResolveVisitor final : public VNVisitor { } static VSymEnt* findIdFallbackSkipMemberDType(VSymEnt* lookp, const string& name) { + VSymEnt* shadowEntp = nullptr; // Shadowing variable: not a type, kept for error report while (lookp) { VSymEnt* const foundp = lookp->findIdFlat(name); - if (foundp && !VN_IS(foundp->nodep(), MemberDType)) return foundp; + if (foundp && !VN_IS(foundp->nodep(), MemberDType)) { + // A variable is not a type candidate (IEEE 1800-2023 6.18); skip it so an + // enclosing type is found, but keep it to preserve the "found: VAR" error. + if (!VN_IS(foundp->nodep(), Var)) return foundp; + if (!shadowEntp) shadowEntp = foundp; + } lookp = lookp->fallbackp(); } - return nullptr; + return shadowEntp; } void symIterateChildren(AstNode* nodep, VSymEnt* symp) { diff --git a/test_regress/t/t_class_member_shadow_type.py b/test_regress/t/t_class_member_shadow_type.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_class_member_shadow_type.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_member_shadow_type.v b/test_regress/t/t_class_member_shadow_type.v new file mode 100644 index 000000000..e0d11a0c7 --- /dev/null +++ b/test_regress/t/t_class_member_shadow_type.v @@ -0,0 +1,53 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// A data member/variable named identically to a type from an enclosing scope +// must resolve its type specifier against the enclosing-scope type, not against +// the just-declared variable (IEEE 1800-2023 6.18). This is the UVM RAL / +// peakrdl-regblock pattern: 'rand ;'. +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Tom Jackson +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +typedef logic [7:0] my_t; // type at $unit scope + +class C; + my_t my_t; // member named the same as its (outer-scope) type +endclass + +class D; + my_t a; // second use of the type name after the shadowing variable... + my_t my_t; // ... is also legal; both resolve to the $unit typedef +endclass + +// The exact RAL shape: class member named after its class type +class my_blk; + int x; +endclass + +class parent; + rand my_blk my_blk; +endclass + +module t; + my_t my_t; // also legal at module scope + + initial begin + static C c = new; + static parent p = new; + c.my_t = 8'hAB; + p.my_blk = new; + p.my_blk.x = 5; + my_t = 8'h12; + `checkh(c.my_t, 8'hAB); + `checkh(p.my_blk.x, 5); + `checkh(my_t, 8'h12); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 36d30d8fcb26f0347c88544064aba5008ca483a2 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Tue, 23 Jun 2026 21:21:07 -0400 Subject: [PATCH 88/89] CI: Remove unused passed step --- .github/workflows/build-test.yml | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 6f5de6e36..8b42e2306 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -299,30 +299,3 @@ jobs: lint-py: name: Lint Python uses: ./.github/workflows/reusable-lint-py.yml - - passed: - name: Test suite passed - if: always() - needs: - - build-2604-gcc - - build-2604-clang - - build-2404-gcc - - build-2404-clang - - build-2204-gcc - - build-osx-gcc - - build-osx-clang - - build-windows - - build-2404-gcc - - build-2404-clang - - test-2404-gcc - - test-2404-clang - - test-2204-gcc - - lint-py - - runs-on: ubuntu-24.04 - - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} From 7752625f49aae7886ea44b0bf98add5bcbff2eab Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Tue, 23 Jun 2026 21:24:21 -0400 Subject: [PATCH 89/89] CI: Pin actions to hashes --- .github/workflows/build-test.yml | 6 ++--- .github/workflows/contributor.yml | 2 +- .github/workflows/coverage.yml | 16 ++++++------ .github/workflows/docker.yml | 12 ++++----- .github/workflows/format.yml | 2 +- .github/workflows/pages.yml | 10 +++---- .github/workflows/reusable-build.yml | 6 ++--- .github/workflows/reusable-lint-py.yml | 2 +- .github/workflows/reusable-rtlmeter-build.yml | 6 ++--- .github/workflows/reusable-rtlmeter-run.yml | 12 ++++----- .github/workflows/reusable-test.yml | 6 ++--- .github/workflows/rtlmeter.yml | 26 +++++++++---------- 12 files changed, 53 insertions(+), 53 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 8b42e2306..d0dfeccb7 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -154,11 +154,11 @@ jobs: CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_LIMIT_MULTIPLE: 0.95 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo - name: Cache $CCACHE_DIR - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: ${{ env.CCACHE_DIR }} key: msbuild-msvc-cmake @@ -171,7 +171,7 @@ jobs: - name: Zip up repository run: Compress-Archive -LiteralPath install -DestinationPath verilator.zip - name: Upload zip archive - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: ${{ github.workspace }}/repo/verilator.zip name: verilator-win.zip diff --git a/.github/workflows/contributor.yml b/.github/workflows/contributor.yml index afb5b9fe5..4fef0fd66 100644 --- a/.github/workflows/contributor.yml +++ b/.github/workflows/contributor.yml @@ -16,5 +16,5 @@ jobs: name: "'docs/CONTRIBUTORS' was signed" runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - run: test_regress/t/t_dist_contributors.py diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d4934c92b..cc6b463b0 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -74,10 +74,10 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Download code coverage data - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: code-coverage-* path: obj_coverage @@ -90,7 +90,7 @@ jobs: find obj_coverage -type f | paste -sd, | sed "s/^/files=/" >> "$GITHUB_OUTPUT" - name: Upload to codecov.io - uses: codecov/codecov-action@v7 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7 with: disable_file_fixes: true disable_search: true @@ -114,7 +114,7 @@ jobs: sudo apt install lcov - name: Download repository archive - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ needs.build.outputs.archive }} path: ${{ github.workspace }} @@ -125,7 +125,7 @@ jobs: ls -lsha - name: Download code coverage data - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: code-coverage-* path: repo/obj_coverage @@ -172,14 +172,14 @@ jobs: fi - name: Upload report - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: repo/obj_coverage name: coverage-report - name: Upload notification if: ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: repo/notification name: pr-notification @@ -195,7 +195,7 @@ jobs: # Creating issues requires elevated privilege - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 37c35f8f8..f41c513a5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Extract context variables run: | @@ -54,7 +54,7 @@ jobs: - name: Docker meta id: docker_meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6 with: images: | ${{ vars.DOCKER_HUB_NAMESPACE }}/${{ env.image_name }} @@ -64,21 +64,21 @@ jobs: type=raw,value=latest,enable=${{ inputs.add_latest_tag == true }} - name: Set up QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 with: buildkitd-flags: --debug - name: Login to Docker Hub - uses: docker/login-action@v4 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 with: username: ${{ secrets.DOCKER_HUB_USER }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Build and Push to Docker - uses: docker/build-push-action@v7 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7 if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' with: context: ${{ env.build_context }} diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 6716b4f48..5a604fa73 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -21,7 +21,7 @@ jobs: CI_COMMIT: ${{ github.sha }} steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Install packages for build diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index bc314b298..59048157c 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -38,7 +38,7 @@ jobs: pr-run-ids: ${{ steps.build.outputs.pr-run-ids }} steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Build pages id: build env: @@ -48,7 +48,7 @@ jobs: ls -lsha tree -L 3 pages - name: Upload pages artifact - uses: actions/upload-pages-artifact@v5 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 with: path: pages @@ -61,7 +61,7 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages - uses: actions/deploy-pages@v5 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 notify: name: Notify @@ -70,11 +70,11 @@ jobs: if: ${{ github.repository == 'verilator/verilator' }} steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 # Use the Verilator CI app to post the comment - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 2601765e1..5d053cadb 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -61,14 +61,14 @@ jobs: CCACHE_MAXSIZE: 1000M # Per build matrix entry (* 5 = 5000M in total) steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo ref: ${{ inputs.sha }} fetch-depth: ${{ inputs.dev-gcov && '0' || '1' }} # Coverage flow needs full history - name: Cache $CCACHE_DIR - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 env: CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache with: @@ -93,7 +93,7 @@ jobs: echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT" - name: Upload repository archive - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: ${{ github.workspace }}/${{ steps.create-archive.outputs.archive }} name: ${{ steps.create-archive.outputs.archive }} diff --git a/.github/workflows/reusable-lint-py.yml b/.github/workflows/reusable-lint-py.yml index fa8405693..f37027aa7 100644 --- a/.github/workflows/reusable-lint-py.yml +++ b/.github/workflows/reusable-lint-py.yml @@ -27,7 +27,7 @@ jobs: name: Sub-lint | Python steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo diff --git a/.github/workflows/reusable-rtlmeter-build.yml b/.github/workflows/reusable-rtlmeter-build.yml index b6c87a6dc..cdfee423a 100644 --- a/.github/workflows/reusable-rtlmeter-build.yml +++ b/.github/workflows/reusable-rtlmeter-build.yml @@ -50,7 +50,7 @@ jobs: sudo apt install ccache mold help2man libfl-dev libjemalloc-dev libsystemc-dev - name: Use saved ccache - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: ccache key: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.sha }}-${{ github.run_id }}-${{ github.run_attempt }} @@ -60,7 +60,7 @@ jobs: rtlmeter-build-ccache-${{ inputs.runs-on }}-${{ inputs.cc }} - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: repo ref: ${{ inputs.sha }} @@ -89,7 +89,7 @@ jobs: echo "archive=$ARCHIVE" >> $GITHUB_OUTPUT - name: Upload Verilator installation archive - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: ${{ steps.create-archive.outputs.archive }} name: ${{ steps.create-archive.outputs.archive }} diff --git a/.github/workflows/reusable-rtlmeter-run.yml b/.github/workflows/reusable-rtlmeter-run.yml index a5dee222c..81aaad405 100644 --- a/.github/workflows/reusable-rtlmeter-run.yml +++ b/.github/workflows/reusable-rtlmeter-run.yml @@ -73,7 +73,7 @@ jobs: sudo apt install ccache mold libfl-dev libjemalloc-dev libsystemc-dev - name: Checkout RTLMeter - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/rtlmeter" path: rtlmeter @@ -84,7 +84,7 @@ jobs: - name: Use saved ccache if: ${{ env.CCACHE_DISABLE == 0 }} - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: ${{ env.CCACHE_DIR }} key: rtlmeter-run-ccache-${{ inputs.runs-on }}-${{ inputs.cc }}-${{ inputs.cases }}-${{ inputs.compileArgs }}-${{ github.run_id }}-${{ github.run_attempt }} @@ -95,7 +95,7 @@ jobs: ######################################################################## - name: Download Verilator installation archive - new - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.verilator-archive-new }} @@ -135,7 +135,7 @@ jobs: ./rtlmeter report --steps '*' --metrics '*' ../results-${{ steps.results.outputs.hash }}.json - name: Upload results - new - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: results-${{ steps.results.outputs.hash }}.json name: rtlmeter-${{ inputs.tag }}-results-${{ steps.results.outputs.hash }} @@ -157,7 +157,7 @@ jobs: - name: Download Verilator installation archive - old if: ${{ inputs.verilator-archive-old != '' }} - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.verilator-archive-old }} @@ -198,7 +198,7 @@ jobs: - name: Upload results - old if: ${{ inputs.verilator-archive-old != '' }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: reference-${{ steps.results.outputs.hash }}.json name: rtlmeter-${{ inputs.tag }}-reference-${{ steps.results.outputs.hash }} diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index 7f363f2d9..efc9c50fe 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -55,7 +55,7 @@ jobs: steps: - name: Download repository archive - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: ${{ inputs.archive }} path: ${{ github.workspace }} @@ -67,7 +67,7 @@ jobs: ls -lsha - name: Cache $CCACHE_DIR - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 env: CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache2 with: @@ -99,7 +99,7 @@ jobs: - name: Upload code coverage data if: ${{ inputs.dev-gcov }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: ${{ github.workspace }}/repo/obj_coverage/verilator-${{ inputs.suite }}.info name: code-coverage-${{ inputs.suite }} diff --git a/.github/workflows/rtlmeter.yml b/.github/workflows/rtlmeter.yml index af79a9a0f..1848d4ec8 100644 --- a/.github/workflows/rtlmeter.yml +++ b/.github/workflows/rtlmeter.yml @@ -45,7 +45,7 @@ jobs: cases: ${{ steps.cases.outputs.cases }} steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - name: Startup id: start @@ -208,7 +208,7 @@ jobs: run: echo "tags=$(jq -r 'keys | map(sub("^run-"; "")) | join(" ")' <<< '${{ toJSON(needs) }}')" >> "$GITHUB_OUTPUT" - name: Checkout RTLMeter - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/rtlmeter" path: rtlmeter @@ -232,7 +232,7 @@ jobs: ./rtlmeter collate ../all-results-$tag/*.json > ../all-results-$tag.json done - name: Upload combined results - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: all-results-*.json name: all-results @@ -259,7 +259,7 @@ jobs: done - name: Upload reference results if: ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: all-reference-*.json name: all-reference @@ -278,19 +278,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Download combined results - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: all-results path: results - name: Upload published results - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: results/*.json name: published-results # Pushing to verilator/verilator-rtlmeter-results requires elevated permissions - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} @@ -298,7 +298,7 @@ jobs: repositories: verilator-rtlmeter-results permission-contents: write - name: Checkout verilator-rtlmeter-results - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/verilator-rtlmeter-results" token: ${{ steps.generate-token.outputs.token }} @@ -331,7 +331,7 @@ jobs: actions: read steps: - name: Checkout RTLMeter - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: repository: "verilator/rtlmeter" path: rtlmeter @@ -341,7 +341,7 @@ jobs: run: make venv - name: Checkout Verilator - uses: actions/checkout@v7 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: path: verilator @@ -367,13 +367,13 @@ jobs: echo ${{ github.event.number }} > ../notification-artifact/pr-number.txt - name: Upload report - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: report-artifact name: rtlmeter-report - name: Upload notification - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: notification-artifact name: pr-notification @@ -392,7 +392,7 @@ jobs: # Creating issues requires elevated privilege - name: Generate access token id: generate-token - uses: actions/create-github-app-token@v3.2.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }}