From a1a8b9624ca5f018171749547c12201bf49f20c6 Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Tue, 21 Apr 2026 17:54:42 +0200 Subject: [PATCH] Support IEEE-compliant force/release handling (#7391) --- include/verilated_force.h | 258 +++ src/V3AstAttr.h | 10 + src/V3EmitCHeaders.cpp | 1 + src/V3Force.cpp | 1509 ++++++++++------- src/V3Global.h | 3 + src/V3Premit.cpp | 4 + test_regress/t/t_dfg_break_cycles.v | 2 +- test_regress/t/t_force.v | 94 + test_regress/t/t_force_chained.out | 4 - test_regress/t/t_force_chained.py | 2 +- test_regress/t/t_force_chained.v | 7 +- test_regress/t/t_force_cond.py | 18 + test_regress/t/t_force_cond.v | 35 + .../t/t_force_forceable_readwrite_unsup.out | 5 + ...y => t_force_forceable_readwrite_unsup.py} | 4 +- .../t/t_force_forceable_readwrite_unsup.v | 19 + test_regress/t/t_force_forceable_rhs_ref.py | 18 + test_regress/t/t_force_forceable_rhs_ref.v | 49 + test_regress/t/t_force_multi.v | 54 +- test_regress/t/t_force_nested_struct.py | 19 + test_regress/t/t_force_nested_struct.v | 48 + test_regress/t/t_force_nested_struct2.py | 19 + test_regress/t/t_force_nested_struct2.v | 35 + test_regress/t/t_force_port_inline.v | 118 +- test_regress/t/t_force_readwrite_unsup.out | 3 - test_regress/t/t_force_unpacked_unsup.out | 14 - test_regress/t/t_force_unpacked_unsup.v | 84 - test_regress/t/t_forceable_public_flat.py | 1 - test_regress/t/t_forceable_public_flat.v | 2 +- test_regress/t/t_forceable_string_bad.out | 4 - test_regress/t/t_verilated_all.v | 3 +- 31 files changed, 1667 insertions(+), 779 deletions(-) create mode 100644 include/verilated_force.h delete mode 100644 test_regress/t/t_force_chained.out create mode 100755 test_regress/t/t_force_cond.py create mode 100644 test_regress/t/t_force_cond.v create mode 100644 test_regress/t/t_force_forceable_readwrite_unsup.out rename test_regress/t/{t_force_unpacked_unsup.py => t_force_forceable_readwrite_unsup.py} (85%) create mode 100644 test_regress/t/t_force_forceable_readwrite_unsup.v create mode 100755 test_regress/t/t_force_forceable_rhs_ref.py create mode 100644 test_regress/t/t_force_forceable_rhs_ref.v create mode 100755 test_regress/t/t_force_nested_struct.py create mode 100644 test_regress/t/t_force_nested_struct.v create mode 100755 test_regress/t/t_force_nested_struct2.py create mode 100644 test_regress/t/t_force_nested_struct2.v delete mode 100644 test_regress/t/t_force_unpacked_unsup.out delete mode 100644 test_regress/t/t_force_unpacked_unsup.v diff --git a/include/verilated_force.h b/include/verilated_force.h new file mode 100644 index 000000000..0a65a8b35 --- /dev/null +++ b/include/verilated_force.h @@ -0,0 +1,258 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Code available from: https://verilator.org +// +// Copyright 2026-2026 by Wilson Snyder. This program is free software; you can +// redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +/// +/// \file +/// \brief Verilator: Runtime support for force/release statements +/// +/// This file provides runtime data structures for efficient dynamic +/// resolution of force/release statements. A sorted list of active +/// forces is maintained that can be efficiently queried and modified +/// at runtime. +/// +//************************************************************************* + +#ifndef VERILATOR_VERILATED_FORCE_H_ +#define VERILATOR_VERILATED_FORCE_H_ + +#include "verilatedos.h" + +#include +#include +#include +#include +#include + +template +using VlForceBaseType = typename std::remove_cv::type>::type; + +// VlForceRead - Helper functions to read a forced value +// +// These functions combine original value with forced values based on +// VlForceVec entries. +// This achieves O(k) complexity where k = number of active forces. + +template +struct VlForceTypeInfo final { + using Type = VlForceBaseType; + static constexpr bool bitwise + = std::is_integral::value || std::is_enum::value || VlIsVlWide::value; + static constexpr bool unpackedArray = false; +}; + +template +struct VlForceArrayIndexer final { + static constexpr std::size_t size = 1; + + static T& elem(T& value, std::size_t) { return value; } +}; + +template +struct VlForceArrayIndexer> final { + static constexpr std::size_t size = N * VlForceArrayIndexer::size; + + static auto& elem(VlUnpacked& array, std::size_t index) { + constexpr std::size_t subSize = VlForceArrayIndexer::size; + return VlForceArrayIndexer::elem(array[index / subSize], index % subSize); + } +}; + +template +struct VlForceTypeInfo> final { + using Type = VlUnpacked; + static constexpr bool bitwise = false; + static constexpr bool unpackedArray = true; +}; + +template ::value> +struct VlForceStorageTypeOf final { + using type = typename std::make_unsigned::type; +}; + +template +struct VlForceStorageTypeOf final { + using type = typename std::make_unsigned::type>::type; +}; + +template +using VlForceStorageType = typename VlForceStorageTypeOf>::type; + +//============================================================================= +// VlForceVec - Vector of active force entries for a signal +// +// This class maintains a sorted vector of non-overlapping force entries. +// When a new force is added, it removes or trims existing entries that +// overlap with the new range. +// +// The generated code will: +// 1. Use addForce/release to update the active forces +// 2. Call a generated read function that iterates entries and evaluates RHS + +class VlForceVec final { +private: + struct Entry final { + int m_lsb; // Inclusive lower bit + int m_msb; // Inclusive upper bit + 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; } + }; + + std::vector m_entries; // Sorted by msb, non-overlapping + + std::vector::iterator trimEntries(int lsb, int msb) { + auto it = std::lower_bound(m_entries.begin(), m_entries.end(), lsb, + [](const Entry& e, int bit) { return e.m_msb < bit; }); + while (it != m_entries.end() && it->m_lsb <= msb) { + if (it->m_lsb < lsb && it->m_msb > msb) { + const Entry right{msb + 1, it->m_msb, it->m_rhsLsb, it->m_rhsDatap}; + it->m_msb = lsb - 1; + return m_entries.insert(++it, right); + } + if (it->m_lsb < lsb) { + it->m_msb = lsb - 1; + ++it; + continue; + } + if (it->m_msb > msb) { + it->m_lsb = msb + 1; + return it; + } + it = m_entries.erase(it); + } + return it; + } + + static QData extractRhsChunk(const Entry& entry, int rhsLsb, int width) { + assert(width > 0 && width <= VL_QUADSIZE); + assert(rhsLsb >= 0); + + const QData mask = static_cast(VL_MASK_Q(width)); + const int rhsWidth = entry.m_msb - entry.m_rhsLsb + 1; + if (rhsWidth <= VL_QUADSIZE) { + const QData rhsVal = static_cast(*static_cast(entry.m_rhsDatap)); + return (rhsVal >> rhsLsb) & mask; + } + + const EData* const rhswp = static_cast(entry.m_rhsDatap); + return VL_SEL_QWII(rhsWidth, rhswp, rhsLsb, width) & mask; + } + + template + static T applyBits(T cur, const Entry& entry, int lsb, int width, int rhsLsb) { + const T lowMask = static_cast(VL_MASK_Q(width)); + const T mask = static_cast(lowMask << lsb); + const T rhsBits = static_cast( + (static_cast(extractRhsChunk(entry, rhsLsb, width)) & lowMask) << lsb); + return static_cast((cur & ~mask) | (rhsBits & mask)); + } + + template + static typename std::enable_if::value, T>::type applyEntry(T result, + const Entry& entry) { + EData* const reswp = result.data(); + const int lword = VL_BITWORD_E(entry.m_lsb); + const int hword = VL_BITWORD_E(entry.m_msb); + for (int word = lword; word <= hword; ++word) { + const int wordLsb = word * VL_EDATASIZE; + const int segLsb = std::max(entry.m_lsb, wordLsb); + const int segMsb = std::min(entry.m_msb, wordLsb + VL_EDATASIZE - 1); + const int segWidth = segMsb - segLsb + 1; + const int bitOffset = segLsb - wordLsb; + const int rhsLsb = segLsb - entry.m_rhsLsb; + reswp[word] = applyBits(reswp[word], entry, bitOffset, segWidth, rhsLsb); + } + return result; + } + + template + static typename std::enable_if::value && VlForceTypeInfo::bitwise, T>::type + applyEntry(T result, const Entry& entry) { + using U = VlForceStorageType; + const int width = entry.m_msb - entry.m_lsb + 1; + const int bits = static_cast(sizeof(U) * 8); + const int rhsLsb = entry.m_lsb - entry.m_rhsLsb; + const QData rhsChunk = extractRhsChunk(entry, rhsLsb, width); + if (width >= bits) return static_cast(static_cast(rhsChunk)); + return static_cast( + applyBits(static_cast(result), entry, entry.m_lsb, width, rhsLsb)); + } + + template + static typename std::enable_if::bitwise, T>::type + applyEntry(T result, const Entry& entry) { + static_cast(result); + return *static_cast*>(entry.m_rhsDatap); + } + +public: + VlForceVec() = default; + + template + T read(T val) const { + if VL_CONSTEXPR_CXX17 (VlForceTypeInfo::unpackedArray) { + // Handling the case of a nested flattened array using recursion + using ElemRef + = decltype(VlForceArrayIndexer::elem(val, 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(val, uidx) = rhsBasep[rhsIndex]; + } + } + return val; + } + + for (const auto& entry : m_entries) { val = applyEntry(val, entry); } + return val; + } + + template + 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]; + } + return origVal; + } + + void addForce(int lsb, int msb, const void* rhsDatap, int rhsLsb) { + assert(lsb <= msb); + assert(rhsDatap); + assert(rhsLsb <= lsb); + + auto it = trimEntries(lsb, msb); + m_entries.insert(it, {lsb, msb, rhsLsb, rhsDatap}); + } + + void release(int lsb, int msb) { + assert(lsb <= msb); + trimEntries(lsb, msb); + } + + void touch() {} +}; + +#endif // guard diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 5105fe73a..fb453d5d6 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -809,6 +809,11 @@ public: EVENT_FIRE, EVENT_IS_FIRED, EVENT_IS_TRIGGERED, + FORCE_ADD, + FORCE_READ, + FORCE_READ_INDEX, + FORCE_RELEASE, + FORCE_TOUCH, FORK_DONE, FORK_INIT, FORK_JOIN, @@ -955,6 +960,11 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) { {EVENT_FIRE, "fire", false}, \ {EVENT_IS_FIRED, "isFired", true}, \ {EVENT_IS_TRIGGERED, "isTriggered", true}, \ + {FORCE_ADD, "addForce", false}, \ + {FORCE_READ, "read", true}, \ + {FORCE_READ_INDEX, "readIndex", true}, \ + {FORCE_RELEASE, "release", false}, \ + {FORCE_TOUCH, "touch", false}, \ {FORK_DONE, "done", false}, \ {FORK_INIT, "init", false}, \ {FORK_JOIN, "join", false}, \ diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 62787c757..eb1b43d36 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -663,6 +663,7 @@ class EmitCHeader final : public EmitCConstInit { if (v3Global.opt.coverage()) puts("#include \"verilated_cov.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"); std::set cuse_set; auto add_to_cuse_set = [&](string s) { cuse_set.insert(s); }; diff --git a/src/V3Force.cpp b/src/V3Force.cpp index 727494130..c4a5f5fe0 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -1,6 +1,6 @@ // -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* -// DESCRIPTION: Verilator: Covert forceable signals, process force/release +// DESCRIPTION: Verilator: Convert forceable signals, process force/release // // Code available from: https://verilator.org // @@ -15,31 +15,21 @@ //************************************************************************* // V3Force's Transformations: // -// For each forceable net with name "": -// add 3 extra signals: -// - __VforceRd: a net with same type as signal -// - __VforceEn: a var with same type as signal, which is the bitwise force enable -// - __VforceVal: a var with same type as signal, which is the forced value -// add an initial statement: -// initial __VforceEn = 0; -// add a continuous assignment: -// assign __VforceRd = __VforceEn ? __VforceVal : ; -// replace all READ references to with a read reference to _VforceRd +// For each forceable var/net "": +// - Create __VforceVec (VlForceVec) to track active force ranges +// - Create __VforceRHS vars to hold RHS shadow values +// - Add continuous assignments: __VforceRHS = RHS // -// Replace each AstAssignForce with 3 assignments: -// - __VforceEn = 1 -// - __VforceVal = -// - __VforceRd = +// For each `force [range] = ` with ID: +// - __VforceVec.addForce(lsb, msb, &__VforceRHS, rhsLsb) // -// Replace each AstRelease with 1 or 2 assignments: -// - __VforceEn = 0 -// - __VforceRd = // iff lhs is a net +// For each `release [range]`: +// - If not continuously driven: = VlForceVec::read(, __VforceVec) +// - __VforceVec.release(lsb, msb) // -// After each WRITE of forced LHS -// reevaluate __VforceRd to support immediate force/release +// For each read of : +// - Replace with: VlForceVec::read(, __VforceVec) // -// After each WRITE of forced RHS -// reevaluate __VforceVal to support VarRef rollback after release //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT @@ -51,522 +41,565 @@ VL_DEFINE_DEBUG_FUNCTIONS; -//###################################################################### -// Convert force/release statements and signals marked 'forceable' - class ForceState final { - constexpr static int ELEMENTS_MAX = 1000; - // TYPES - struct ForceComponentsVar final { - AstVar* const m_rdVarp; // New variable to replace read references with - AstVar* const m_valVarp; // Forced value - AstVar* const m_enVarp; // Force enabled signal - explicit ForceComponentsVar(AstVar* varp) - : m_rdVarp{new AstVar{varp->fileline(), VVarType::WIRE, varp->name() + "__VforceRd", - varp->dtypep()}} - , m_valVarp{new AstVar{varp->fileline(), VVarType::VAR, varp->name() + "__VforceVal", - varp->dtypep()}} - , m_enVarp{new AstVar{varp->fileline(), VVarType::VAR, varp->name() + "__VforceEn", - getEnVarpDTypep(varp)}} { - m_rdVarp->addNext(m_enVarp); - m_rdVarp->addNext(m_valVarp); - varp->addNextHere(m_rdVarp); +public: + struct ForceRange VL_NOT_FINAL { + int m_rangeLsb = 0; // VlForceVec range: bit index or array element index + int m_rangeMsb = 0; + int m_padLsb = 0; // Bit positions for RHS padding + int m_padMsb = 0; + }; + + struct ForceInfo final : ForceRange { + // MEMBERS + int m_forceId = 0; // Unique (per signal) variable of this force assignment + bool m_hasArraySel = false; // If this has an array select on LHS + AstVarScope* m_rhsVarVscp = nullptr; // Scope of the var containing RHSID + AstNodeExpr* m_rhsExprp = nullptr; // Expression on RHS of this force assignment + + ForceInfo() = default; + ForceInfo(int rangeLsb, int rangeMsb, int padLsb, int padMsb, int forceId, + bool hasArraySel, AstVarScope* rhsVarVscp, AstNodeExpr* rhsExprp) + : m_forceId{forceId} + , m_hasArraySel{hasArraySel} + , m_rhsVarVscp{rhsVarVscp} + , m_rhsExprp{rhsExprp} { + m_rangeLsb = rangeLsb; + m_rangeMsb = rangeMsb; + m_padLsb = padLsb; + m_padMsb = padMsb; } }; -public: - struct ForceComponentsVarScope final { - AstVarScope* const m_rdVscp; // New variable to replace read references with - AstVarScope* const m_valVscp; // Forced value - AstVarScope* const m_enVscp; // Force enabled signal - V3UniqueNames m_iterNames; // Names for loop iteration variables - explicit ForceComponentsVarScope(AstVarScope* vscp, ForceComponentsVar& fcv) - : m_rdVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_rdVarp}} - , m_valVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_valVarp}} - , m_enVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_enVarp}} - , m_iterNames{"__VForceIter"} { - m_rdVscp->addNext(m_enVscp); - m_rdVscp->addNext(m_valVscp); - vscp->addNextHere(m_rdVscp); + struct VarForceInfo final { + AstVarScope* m_forceVecVscp = nullptr; + AstVarScope* m_forceRdVscp = nullptr; + AstVarScope* m_forceEnVscp = nullptr; + AstVarScope* m_forceValVscp = nullptr; + AstVarScope* m_varVscp = nullptr; + AstScope* m_scopep = nullptr; + std::unordered_map m_forces; + std::unordered_map m_forcePathToIndex; + int m_nextForcePathIndex = 1; // Start at 1 so 0 can be the base path (whole signal) - FileLine* const flp = vscp->fileline(); - - // Add initialization of the enable signal - AstActive* const activeInitp = new AstActive{ - flp, "force-init", new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Static{}}}}; - activeInitp->senTreeStorep(activeInitp->sentreep()); - vscp->scopep()->addBlocksp(activeInitp); - - // Create statements that update __Rd variable. - // These nodes will be copied and used also for __En initialization - AstVarRef* const rdRefp = new AstVarRef{flp, m_rdVscp, VAccess::WRITE}; - std::vector assigns; - AstNodeStmt* const rdUpdateStmtsp - = getForcedUpdateStmtsRecursep(rdRefp, vscp, rdRefp, assigns); - - // To use these statements for __En initialization, replace references to __Rd with - // ones to __En and replace assignments RHS with 0 - AstNodeStmt* const enInitStmtsp = rdUpdateStmtsp->cloneTree(true); - for (size_t i = 0; i < assigns.size(); i++) { - // Save copies, because clonep() works only after the last cloneTree - assigns[i] = assigns[i]->clonep(); + private: + static void buildForcePathKeyRecurse(AstNodeExpr* nodep, string& out) { + if (VN_IS(nodep, VarRef)) return; + if (const AstSel* const selp = VN_CAST(nodep, Sel)) { + buildForcePathKeyRecurse(selp->fromp(), out); + out += "|S" + cvtToStr(selp->lsbConst()) + ":" + cvtToStr(selp->widthConst()); + return; } - for (AstAssign* const assignp : assigns) { - AstVarRef* const lhsVarRefp - = VN_AS(AstNodeVarRef::varRefLValueRecurse(assignp->lhsp()), VarRef); - lhsVarRefp->replaceWith(new AstVarRef{flp, m_enVscp, VAccess::WRITE}); - lhsVarRefp->deleteTree(); - assignp->rhsp()->unlinkFrBack()->deleteTree(); - const V3Number zero{m_enVscp, assignp->lhsp()->dtypep()->width()}; - assignp->rhsp(new AstConst{flp, zero}); - } - activeInitp->addStmtsp(new AstInitial{flp, enInitStmtsp}); - { // Add the combinational override - // Explicitly list dependencies for update. - // Note: rdVscp is also needed to retrigger assignment for the first time. - AstSenItem* const itemsp = new AstSenItem{ - flp, VEdgeType::ET_CHANGED, new AstVarRef{flp, m_rdVscp, VAccess::READ}}; - itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, - new AstVarRef{flp, m_valVscp, VAccess::READ}}); - itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, - new AstVarRef{flp, m_enVscp, VAccess::READ}}); - AstVarRef* const origp = new AstVarRef{flp, vscp, VAccess::READ}; - ForceState::markNonReplaceable(origp); - itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, origp}); - AstActive* const activep - = new AstActive{flp, "force-update", new AstSenTree{flp, itemsp}}; - activep->senTreeStorep(activep->sentreep()); - - activep->addStmtsp( - new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, rdUpdateStmtsp}); - vscp->scopep()->addBlocksp(activep); + if (AstStructSel* const selp = VN_CAST(nodep, StructSel)) { + AstNodeExpr* const fromp = selp->fromp(); + buildForcePathKeyRecurse(fromp, out); + out += "|T" + selp->name(); + return; } + nodep->v3fatalSrc("Unsupported: opaque force path selector " + << nodep->prettyTypeName()); } - AstNodeStmt* getForcedUpdateStmtsRecursep(AstNodeExpr* const lhsp, AstVarScope* const vscp, - AstVarRef* const lhsVarRefp, - std::vector& assigns) { - // Create stataments that update values of __Rd variable. - // lhsp is either a reference to that variable or ArraySel or MemberSel on it. - // lhsVarRefp is a reference to that variable in lhsp subtree. - // assigns is a vector to which all assignments to __Rd are added. - FileLine* const flp = lhsp->fileline(); - const AstNodeDType* const lhsDtypep = lhsp->dtypep()->skipRefp(); - if (lhsDtypep->isIntegralOrPacked() || VN_IS(lhsDtypep, BasicDType)) { - AstAssign* const assignp - = new AstAssign{flp, lhsp, forcedUpdate(vscp, lhsp, lhsVarRefp)}; - assigns.push_back(assignp); - return assignp; - } else if (const AstStructDType* const structDtypep - = VN_CAST(lhsDtypep, StructDType)) { - AstNodeStmt* stmtsp = nullptr; - bool firstIter = true; - for (AstMemberDType* mdtp = structDtypep->membersp(); mdtp; - mdtp = VN_AS(mdtp->nextp(), MemberDType)) { - AstNodeExpr* const lhsCopyp = firstIter ? lhsp : lhsp->cloneTreePure(false); - AstVarRef* const lhsVarRefCopyp - = firstIter ? lhsVarRefp : lhsVarRefp->clonep(); - AstStructSel* const structSelp = new AstStructSel{flp, lhsCopyp, mdtp->name()}; - structSelp->dtypep(mdtp); - AstNodeStmt* const memberStmtp - = getForcedUpdateStmtsRecursep(structSelp, vscp, lhsVarRefCopyp, assigns); - stmtsp = firstIter ? memberStmtp : stmtsp->addNext(memberStmtp); - firstIter = false; - } - return stmtsp; - } else if (const AstUnpackArrayDType* const arrayDtypep - = VN_CAST(lhsDtypep, UnpackArrayDType)) { - AstVar* const loopVarp - = new AstVar{flp, VVarType::MODULETEMP, - m_iterNames.get(m_rdVscp->varp()->name()), VFlagBitPacked{}, 32}; - m_rdVscp->varp()->addNext(loopVarp); - AstVarScope* const loopVarScopep - = new AstVarScope{flp, m_rdVscp->scopep(), loopVarp}; - m_rdVscp->addNext(loopVarScopep); - AstVarRef* const readRefp = new AstVarRef{flp, loopVarScopep, VAccess::READ}; - AstNodeStmt* const currInitp = new AstAssign{ - flp, new AstVarRef{flp, loopVarScopep, VAccess::WRITE}, new AstConst{flp, 0}}; - AstLoop* const currWhilep = new AstLoop{flp}; - currInitp->addNextHere(currWhilep); - AstLoopTest* const loopTestp = new AstLoopTest{ - flp, currWhilep, - new AstNeq{ - flp, readRefp, - new AstConst{flp, static_cast(arrayDtypep->elementsConst())}}}; - currWhilep->addStmtsp(loopTestp); - AstArraySel* const lhsSelp - = new AstArraySel{flp, lhsp, readRefp->cloneTree(false)}; - AstNodeStmt* const loopBodyp - = getForcedUpdateStmtsRecursep(lhsSelp, vscp, lhsVarRefp, assigns); - currWhilep->addStmtsp(loopBodyp); - AstAssign* const currIncrp = new AstAssign{ - flp, new AstVarRef{flp, loopVarScopep, VAccess::WRITE}, - new AstAdd{flp, readRefp->cloneTree(false), new AstConst{flp, 1}}}; - currWhilep->addStmtsp(currIncrp); - return currInitp; - } else { - lhsDtypep->v3fatalSrc("Unhandled type"); - return nullptr; // LCOV_EXCL_LINE - } + + static string forcePathKey(AstNodeExpr* nodep) { + string out; + buildForcePathKeyRecurse(nodep, out); + return out; } - static AstNodeExpr* wrapIntoExprp(AstVarRef* const refp, AstNodeExpr* const exprp, - AstVarRef* const varRefToReplacep) { - // Return a copy of exprp in which varRefToReplacep is replaced with refp - if (exprp == varRefToReplacep) { - return refp; - } else { - AstNodeExpr* const copiedExprp = exprp->cloneTreePure(false); - AstNode* const oldRefp = varRefToReplacep->clonep(); - varRefToReplacep->clonep()->replaceWith(refp); - oldRefp->deleteTree(); - return copiedExprp; - } + + public: + int getOrCreateForcePathIndex(AstNodeExpr* nodep) { + const auto pair + = m_forcePathToIndex.emplace(forcePathKey(nodep), m_nextForcePathIndex); + if (pair.second) ++m_nextForcePathIndex; + return pair.first->second; } - AstNodeExpr* forcedUpdate(AstVarScope* const vscp, AstNodeExpr* exprp = nullptr, - AstVarRef* const varRefToReplacep = nullptr) const { - FileLine* const flp = vscp->fileline(); - AstVarRef* origRefp = new AstVarRef{flp, vscp, VAccess::READ}; - ForceState::markNonReplaceable(origRefp); - AstNodeExpr* const origExprp = wrapIntoExprp(origRefp, exprp, varRefToReplacep); - AstNodeExpr* const enExprp = wrapIntoExprp(new AstVarRef{flp, m_enVscp, VAccess::READ}, - exprp, varRefToReplacep); - AstNodeExpr* const valExprp = wrapIntoExprp( - new AstVarRef{flp, m_valVscp, VAccess::READ}, exprp, varRefToReplacep); - if (ForceState::isRangedDType(vscp)) { - return new AstOr{ - flp, new AstAnd{flp, enExprp, valExprp}, - new AstAnd{flp, new AstNot{flp, enExprp->cloneTreePure(false)}, origExprp}}; - } - return new AstCond{flp, enExprp, valExprp, origExprp}; + + int findForcePathIndex(AstNodeExpr* nodep) const { + const auto it = m_forcePathToIndex.find(forcePathKey(nodep)); + return it != m_forcePathToIndex.end() ? it->second : -1; } }; + struct ArraySelInfo final { + std::vector m_sels; + bool m_hasBitSel = false; + }; + + struct ForceRangeInfo final : ForceRange { + bool m_hasArraySel = false; + ArraySelInfo m_arrayInfo; + }; + private: // NODE STATE - // AstNodeDType::user1p -> AstNodeDType*, dtype created for __En variables - // AstVar::user1p -> ForceComponentsVar* instance (via m_forceComponentsVar) - // AstVarScope::user1p -> ForceComponentsVarScope* instance (via m_forceComponentsVarScope) - // AstVarRef::user2 -> Flag indicating not to replace reference - // AstAssign::user2 -> Flag indicating that assignment was created for AstRelease - // AstVarScope::user3p -> AstAssign*, the assignment __VforceVal = + // AstVarRef::user1 -> Flag indicating not to replace reference + // AstAssignForce::user2 -> true if force is synthetic (externally forceable) const VNUser1InUse m_user1InUse; const VNUser2InUse m_user2InUse; - const VNUser3InUse m_user3InUse; - AstUser1Allocator m_forceComponentsVar; - AstUser1Allocator m_forceComponentsVarScope; - std::unordered_map, std::vector>> - m_valVscps; - // `valVscp` force components of a forced RHS - static size_t checkIfDTypeSupportedRecurse(const AstNodeDType* const dtypep, - const AstVar* const varp) { - // Checks if force stmt is supported on all subtypes - // and returns number of unpacked elements - const AstNodeDType* const dtp = dtypep->skipRefp(); - if (const AstUnpackArrayDType* const udtp = VN_CAST(dtp, UnpackArrayDType)) { - const size_t elemsInSubDType = checkIfDTypeSupportedRecurse(udtp->subDTypep(), varp); - return udtp->elementsConst() * elemsInSubDType; - } else if (const AstStructDType* const sdtp = VN_CAST(dtp, StructDType)) { - size_t elemCount = 0; - for (const AstMemberDType* mdtp = sdtp->membersp(); mdtp; - mdtp = VN_AS(mdtp->nextp(), MemberDType)) { - elemCount += checkIfDTypeSupportedRecurse(mdtp->subDTypep(), varp); - } - return elemCount; - } else if (const AstBasicDType* const bdtp = VN_CAST(dtp, BasicDType)) { - if (bdtp->isString() || bdtp->isEvent() || bdtp->keyword() == VBasicDTypeKwd::CHANDLE - || bdtp->keyword() == VBasicDTypeKwd::TIME) { - varp->v3warn(E_UNSUPPORTED, "Forcing variable of unsupported type: " - << varp->dtypep()->prettyTypeName()); - } - return 1; - } else if (!dtp->isIntegralOrPacked()) { - varp->v3warn(E_UNSUPPORTED, "Forcing variable of unsupported type: " - << varp->dtypep()->prettyTypeName()); - return 1; - } else { - // All packed types are supported - return 1; - } - } - static AstNodeDType* getEnVarpDTypep(AstVar* const varp) { - AstNodeDType* const origDTypep = varp->dtypep()->skipRefp(); - if (origDTypep->user1p()) return VN_AS(origDTypep->user1p(), NodeDType); - const size_t unpackElemNum = checkIfDTypeSupportedRecurse(origDTypep, varp); - if (unpackElemNum > ELEMENTS_MAX) { - varp->v3warn(E_UNSUPPORTED, "Unsupported: Force of variable with " - ">= " - << ELEMENTS_MAX << " unpacked elements"); - return origDTypep; - } - return getEnVarpDTypeRecursep(varp, origDTypep); - } - static AstNodeDType* getEnVarpDTypeRecursep(AstVar* const varp, AstNodeDType* const dtypep) { - if (dtypep->user1p()) return VN_AS(dtypep->user1p(), NodeDType); - if (AstNodeArrayDType* const arrp = VN_CAST(dtypep, NodeArrayDType)) { - AstNodeDType* const subDTypep = arrp->subDTypep()->skipRefp(); - AstNodeDType* const enSubDTypep = getEnVarpDTypeRecursep(varp, subDTypep); - if (subDTypep != enSubDTypep) { - AstNodeArrayDType* enArrp; - if (VN_IS(arrp, UnpackArrayDType)) { - enArrp = new AstUnpackArrayDType{arrp->fileline(), enSubDTypep, - arrp->rangep()->cloneTree(false)}; - } else if (VN_IS(arrp, PackArrayDType)) { - enArrp = new AstPackArrayDType{arrp->fileline(), enSubDTypep, - arrp->rangep()->cloneTree(false)}; - } else { - varp->v3fatalSrc("Unsupported: Force of variable of unhandled data type"); - return dtypep; - } - dtypep->user1p(enArrp); - v3Global.rootp()->typeTablep()->addTypesp(enArrp); - return enArrp; - } else { - dtypep->user1p(dtypep); - return dtypep; - } - } else if (AstBasicDType* const basicp = VN_CAST(dtypep, BasicDType)) { - if (basicp->isBit()) { - dtypep->user1p(dtypep); - return dtypep; - } else { - AstNodeDType* const bitDtp = varp->findBitRangeDType( - basicp->declRange(), basicp->elements(), VSigning::UNSIGNED); - dtypep->user1p(bitDtp); - return bitDtp; - } - } else if (AstNodeUOrStructDType* const structp = VN_CAST(dtypep, NodeUOrStructDType)) { - std::vector enMemberDTypes; - bool changed = false; - for (AstMemberDType* mdtp = structp->membersp(); mdtp; - mdtp = VN_AS(mdtp->nextp(), MemberDType)) { - AstNodeDType* const subMdtp = mdtp->subDTypep()->skipRefp(); - AstNodeDType* const enSubMdtp = getEnVarpDTypeRecursep(varp, subMdtp); - if (subMdtp != enSubMdtp) { - changed = true; - AstMemberDType* const enMdtp - = new AstMemberDType{mdtp->fileline(), mdtp->name(), enSubMdtp}; - enMdtp->dtypep(enSubMdtp); - enMemberDTypes.push_back(enMdtp); - } else { - enMemberDTypes.push_back(mdtp->cloneTreePure(false)); - } - } - if (changed) { - const bool packed = structp->packed(); - AstNodeUOrStructDType* enStructp; - if (VN_IS(structp, StructDType)) { - enStructp = new AstStructDType{structp->fileline(), - packed ? VSigning::SIGNED : VSigning::NOSIGN}; - } else if (VN_IS(structp, UnionDType) && packed) { - const AstUnionDType* const unionp = VN_AS(structp, UnionDType); - enStructp = new AstUnionDType{unionp->fileline(), unionp->isSoft(), - unionp->isTagged(), VSigning::SIGNED}; - } else { - varp->v3fatalSrc("Unsupported: Force of variable of unhandled data type"); - return dtypep; - } - int width = 0; - if (packed) { - for (const auto& memberp : enMemberDTypes) { - enStructp->addMembersp(memberp); - const int memberWidth = memberp->width(); - if (VN_IS(structp, StructDType)) { - width += memberWidth; - } else { - width = std::max(width, memberWidth); - } - } - } else { - for (const auto& memberp : enMemberDTypes) enStructp->addMembersp(memberp); - width = 1; - } - v3Global.rootp()->typeTablep()->addTypesp(enStructp); - enStructp->name(structp->name() + "__VforceEn_t"); - enStructp->dtypep(enStructp); - enStructp->widthForce(width, width); - enStructp->classOrPackagep(structp->classOrPackagep()); - dtypep->user1p(enStructp); - AstTypedef* const typedefp - = new AstTypedef{enStructp->fileline(), enStructp->name(), enStructp, - VN_IS(enStructp->classOrPackagep(), Class)}; - varp->addNextHere(typedefp); - return enStructp; - } else { - for (const auto& memberp : enMemberDTypes) memberp->deleteTree(); - dtypep->user1p(dtypep); - return dtypep; - } - } - varp->v3fatalSrc("Unsupported: Force of variable of unhandled data type"); - return dtypep; - } + std::unordered_map m_varInfo; public: - // CONSTRUCTORS ForceState() = default; VL_UNCOPYABLE(ForceState); // STATIC METHODS - static bool isRangedDType(const AstNode* const nodep) { - // If ranged we need a multibit enable to support bit-by-bit part-select forces, - // otherwise forcing a real or other opaque dtype and need a single bit enable. + static AstConst* makeZeroConst(AstNode* nodep, int width) { + V3Number zero{nodep, width}; + zero.setAllBits0(); + return new AstConst{nodep->fileline(), zero}; + } + + static AstConst* makeConst32(FileLine* flp, int value) { + return new AstConst{flp, AstConst::WidthedValue{}, 32, static_cast(value)}; + } + + static AstConst* makeRangeMaskConst(AstNode* nodep, int width, int lsb, int msb) { + V3Number mask{nodep, width}; + mask.setAllBits0(); + for (int bit = lsb; bit <= msb; ++bit) mask.setBit(bit, 1); + return new AstConst{nodep->fileline(), mask}; + } + + static AstNodeExpr* zeroPadToBaseWidth(AstNodeExpr* exprp, int baseWidth, int padLsb, + int padMsb) { + if (baseWidth <= 0) return exprp; + const int lowPad = padLsb; + const int highPad = baseWidth - (padMsb + 1); + if (lowPad > 0) { + exprp = new AstConcat{exprp->fileline(), exprp, makeZeroConst(exprp, lowPad)}; + } + if (highPad > 0) { + exprp = new AstConcat{exprp->fileline(), makeZeroConst(exprp, highPad), exprp}; + } + return exprp; + } + + static bool isUnpackedArrayDType(const AstNodeDType* dtypep) { + return VN_IS(dtypep->skipRefp(), UnpackArrayDType); + } + + static bool isBitwiseDType(AstNode* nodep) { const AstBasicDType* const basicp = nodep->dtypep()->skipRefp()->basicp(); - return basicp && basicp->isRanged(); - } - static bool isNotReplaceable(const AstVarRef* const nodep) { return nodep->user2(); } - static void markNonReplaceable(AstVarRef* const nodep) { nodep->user2SetOnce(); } - - // Get all ValVscps for a VarScope - const std::vector* getValVscps(AstVarRef* const refp) const { - auto it = m_valVscps.find(refp->varScopep()); - if (it != m_valVscps.end()) return &(it->second.second); - return nullptr; + return basicp && !basicp->isDouble() && !basicp->isString() && !basicp->isOpaque(); } - // Add a ValVscp for a VarScope - void addValVscp(AstVarRef* const refp, AstVarScope* const valVscp) { - if (m_valVscps[refp->varScopep()].first.find(valVscp) - != m_valVscps[refp->varScopep()].first.end()) - return; - m_valVscps[refp->varScopep()].first.emplace(valVscp); - m_valVscps[refp->varScopep()].second.push_back(valVscp); + static AstNodeExpr* castToNodeDType(AstNodeExpr* exprp, AstNode* dtypeFromp) { + const AstNodeDType* const dtypep = dtypeFromp->dtypep()->skipRefp(); + const AstBasicDType* const basicp = dtypep->basicp(); + if (!basicp || basicp->isDouble() || basicp->isString() || basicp->isOpaque() + || dtypep->isWide() || isUnpackedArrayDType(dtypep)) { + return exprp; + } + return new AstCCast{exprp->fileline(), exprp, dtypeFromp}; } - // METHODS - const ForceComponentsVarScope& getForceComponents(AstVarScope* vscp) { - AstVar* const varp = vscp->varp(); - return m_forceComponentsVarScope(vscp, vscp, m_forceComponentsVar(varp, varp)); + static bool isNotReplaceable(const AstVarRef* const nodep) { return nodep->user1(); } + static void markNonReplaceable(AstVarRef* const nodep) { nodep->user1SetOnce(); } + + static bool isOpaquePathSelector(const AstNode* nodep) { + return VN_IS(nodep, Sel) || VN_IS(nodep, NodeSel) || VN_IS(nodep, StructSel); } - ForceComponentsVarScope* tryGetForceComponents(AstVarRef* nodep) const { - return m_forceComponentsVarScope.tryGet(nodep->varScopep()); + + static bool isOutermostOpaquePathSelector(const AstNode* nodep) { + if (!isOpaquePathSelector(nodep)) return false; + const AstNode* const backp = nodep->backp(); + return !backp || !isOpaquePathSelector(backp); } - void setValVscpAssign(AstVarScope* valVscp, AstAssign* rhsExpr) { valVscp->user3p(rhsExpr); } - AstAssign* getValVscpAssign(AstVarScope* valVscp) const { - return VN_CAST(valVscp->user3p(), Assign); + + static AstVarRef* getOneVarRef(AstNodeExpr* forceStmtp) { + AstNode* const basep = AstArraySel::baseFromp(forceStmtp, true); + if (AstSampled* sampledp = VN_CAST(basep, Sampled)) + if (AstNodeExpr* exprp = VN_CAST(sampledp->exprp(), NodeExpr)) + return getOneVarRef(exprp); + AstVarRef* const varRefp = VN_CAST(basep, VarRef); + UASSERT_OBJ(varRefp, forceStmtp, "`force` assignment has no VarRef on LHS"); + return varRefp; + } + + ForceRangeInfo getForceRangeInfo(AstNodeExpr* lhsp, AstVar* varp, + bool requireConstRangeSelect) { + ForceRangeInfo info; + info.m_arrayInfo = getArraySelInfo(lhsp); + info.m_hasArraySel = !info.m_arrayInfo.m_sels.empty(); + + info.m_padMsb = isBitwiseDType(varp) ? (varp->width() - 1) : 0; + + if (const AstSel* const outerSelp = VN_CAST(lhsp, Sel)) { + int totalLsb = 0; + for (AstNodeExpr* curp = lhsp; const AstSel* const selp = VN_CAST(curp, Sel); + curp = selp->fromp()) { + if (requireConstRangeSelect) { + UASSERT_OBJ(VN_IS(selp->lsbp(), Const), lhsp, + "Unsupported: force on non-const range select"); + } + totalLsb += selp->lsbConst(); + } + info.m_padLsb = totalLsb; + info.m_padMsb = totalLsb + outerSelp->widthConst() - 1; + } + + info.m_rangeLsb = info.m_padLsb; + info.m_rangeMsb = info.m_padMsb; + if (info.m_hasArraySel) { + // Unpacked array selections are tracked as a single flattened element index + // inside VlForceVec, regardless of how many unpacked dimensions the source uses. + std::vector indices; + indices.reserve(info.m_arrayInfo.m_sels.size()); + for (AstArraySel* const selp : info.m_arrayInfo.m_sels) { + UASSERT_OBJ(VN_IS(selp->bitp(), Const), selp, + "Unsupported: force on non-constant array select"); + indices.push_back(VN_AS(selp->bitp(), Const)->toSInt()); + } + info.m_rangeLsb = flattenIndex(indices, arraySelDimSizes(info.m_arrayInfo)); + + info.m_rangeMsb = info.m_rangeLsb; + } else if (isUnpackedArrayDType(varp->dtypep())) { + lhsp->v3fatalSrc("Whole unpacked-array force/release should have been lowered via " + "element selections"); + } + if (!isBitwiseDType(varp) && !info.m_hasArraySel && !VN_IS(lhsp, VarRef)) { + // Non-bitwise member/struct paths cannot use a real bit range, so map each distinct + // source path onto a synthetic index in VlForceVec and use that index consistently + // for force, release, and readback. + VarForceInfo& varInfo = getOrCreateVarInfo(varp); + const int index = varInfo.getOrCreateForcePathIndex(lhsp); + info.m_rangeLsb = index; + info.m_rangeMsb = index; + } + return info; + } + + AstNodeExpr* createForceReadCall(const VarForceInfo& varInfo, FileLine* flp, VCMethod method, + AstNodeExpr* originalExprp, AstNode* dtypeFromp, + AstNodeExpr* indexExprp) const { + UASSERT(varInfo.m_forceVecVscp, "No forceVec for forced variable"); + + originalExprp->foreach( + [](AstVarRef* const refp) { ForceState::markNonReplaceable(refp); }); + AstNodeExpr* const origValp = castToNodeDType(originalExprp, dtypeFromp); + + AstCMethodHard* const callp = new AstCMethodHard{ + flp, new AstVarRef{flp, varInfo.m_forceVecVscp, VAccess::READ}, method, origValp}; + if (indexExprp) callp->addPinsp(indexExprp); + callp->dtypeFrom(dtypeFromp); + return callp; + } + + AstNodeStmt* createForceRdUpdateStmt(const VarForceInfo& varInfo) const { + UASSERT(varInfo.m_forceRdVscp, "No forceRd for forced variable"); + UASSERT(varInfo.m_varVscp, "No base var scope for forced variable"); + FileLine* const flp = varInfo.m_varVscp->fileline(); + AstNodeExpr* readExprp = nullptr; + AstVarRef* const baseRefp = new AstVarRef{flp, varInfo.m_varVscp, VAccess::READ}; + markNonReplaceable(baseRefp); + AstNodeExpr* const enRefp = new AstVarRef{flp, varInfo.m_forceEnVscp, VAccess::READ}; + AstNodeExpr* const valRefp = new AstVarRef{flp, varInfo.m_forceValVscp, VAccess::READ}; + if (isBitwiseDType(varInfo.m_varVscp->varp())) { + readExprp = new AstOr{ + flp, new AstAnd{flp, enRefp, valRefp}, + new AstAnd{flp, new AstNot{flp, enRefp->cloneTreePure(false)}, baseRefp}}; + } else { + readExprp = new AstCond{flp, enRefp, valRefp, baseRefp}; + } + + return new AstAssign{flp, new AstVarRef{flp, varInfo.m_forceRdVscp, VAccess::WRITE}, + readExprp}; + } + + VarForceInfo& getOrCreateVarInfo(AstVar* varp) { return m_varInfo[varp]; } + + const VarForceInfo* getVarInfo(AstVar* varp) const { + const auto it = m_varInfo.find(varp); + return it != m_varInfo.end() ? &it->second : nullptr; + } + + void addForceAssignment(AstVar* varp, AstVarScope* vscp, AstNodeExpr* rhsExprp, + AstAssignForce* forceStmtp, int rangeLsb, int rangeMsb, int padLsb, + int padMsb, bool hasArraySel) { + v3Global.setUsesForce(); + varp->setForcedByCode(); + + VarForceInfo& info = getOrCreateVarInfo(varp); + if (!info.m_scopep) info.m_scopep = vscp->scopep(); + const int forceId = info.m_forces.size(); + FileLine* const flp = varp->fileline(); + AstScope* const scopep = vscp->scopep(); + // Allocate one force vector per variable, no matter how many individual force + // statements later target slices/elements of that variable. + if (!info.m_forceVecVscp) { + AstCDType* const forceVecDtypep = new AstCDType{flp, "VlForceVec"}; + v3Global.rootp()->typeTablep()->addTypesp(forceVecDtypep); + + AstVar* const forceVecVarp + = new AstVar{flp, VVarType::MEMBER, varp->name() + "__VforceVec", forceVecDtypep}; + forceVecVarp->funcLocal(false); + forceVecVarp->isInternal(true); + varp->addNextHere(forceVecVarp); + info.m_forceVecVscp = new AstVarScope{flp, scopep, forceVecVarp}; + scopep->addVarsp(info.m_forceVecVscp); + } + + info.m_forces.emplace(forceStmtp, ForceInfo{rangeLsb, rangeMsb, padLsb, padMsb, forceId, + hasArraySel, nullptr, rhsExprp}); + + UINFO(3, "Added force ID " << forceId << " for " << varp->name() << " [" << rangeMsb << ":" + << rangeLsb << "]\n"); + } + + static void collectArraySelInfo(AstNodeExpr* exprp, ArraySelInfo& info) { + if (const auto* const selp = VN_CAST(exprp, Sel)) { + info.m_hasBitSel = true; + collectArraySelInfo(selp->fromp(), info); + } else if (auto* const selp = VN_CAST(exprp, ArraySel)) { + collectArraySelInfo(selp->fromp(), info); + info.m_sels.push_back(selp); + } else if (const auto* const memberp = VN_CAST(exprp, StructSel)) { + collectArraySelInfo(memberp->fromp(), info); + } + } + + static ArraySelInfo getArraySelInfo(AstNodeExpr* exprp) { + ArraySelInfo info; + collectArraySelInfo(exprp, info); + return info; + } + + static std::vector arraySelDimSizes(const ArraySelInfo& info) { + std::vector dims; + dims.reserve(info.m_sels.size()); + for (AstArraySel* const selp : info.m_sels) { + AstNodeDType* const dtypep = selp->fromp()->dtypep()->skipRefp(); + const AstUnpackArrayDType* const arrayp = VN_CAST(dtypep, UnpackArrayDType); + UASSERT_OBJ(arrayp, selp, "Array select is not on unpacked array"); + dims.push_back(arrayp->declRange().elements()); + } + return dims; + } + + static int flattenIndex(const std::vector& indices, const std::vector& dimSizes) { + UASSERT(indices.size() == dimSizes.size(), "Array index and dimension size mismatch"); + int index = 0; + int stride = 1; + for (int i = static_cast(indices.size()) - 1; i >= 0; --i) { + index += indices[i] * stride; + stride *= dimSizes[i]; + } + return index; + } + + static AstNodeExpr* buildFlattenIndexExpr(FileLine* flp, const ArraySelInfo& info) { + const std::vector dimSizes = arraySelDimSizes(info); + std::vector constIndices; + constIndices.reserve(info.m_sels.size()); + for (AstArraySel* const selp : info.m_sels) { + constIndices.push_back(VN_AS(selp->bitp(), Const)->toSInt()); + } + return makeConst32(flp, flattenIndex(constIndices, dimSizes)); + } + + static AstNodeExpr* buildRhsDataExpr(FileLine* flp, const ForceInfo& finfo) { + UASSERT(finfo.m_rhsVarVscp, "RHS var scope not assigned"); + return new AstVarRef{flp, finfo.m_rhsVarVscp, VAccess::READ}; + } + + void finalizeRhsVars() { + for (auto& it : m_varInfo) { + AstVar* const varp = it.first; + VarForceInfo& info = it.second; + if (info.m_forces.empty()) continue; + + AstScope* const scopep = info.m_scopep; + UASSERT_OBJ(scopep, varp, "Missing scope for force RHS vars"); + + FileLine* const flp = varp->fileline(); + // Process force entries in stable force-id order. + std::vector forceps; + forceps.reserve(info.m_forces.size()); + for (auto& fit : info.m_forces) forceps.push_back(&fit.second); + std::sort(forceps.begin(), forceps.end(), + [](const ForceInfo* ap, const ForceInfo* bp) { + return ap->m_forceId < bp->m_forceId; + }); + + for (ForceInfo* const finfop : forceps) { + ForceInfo& finfo = *finfop; + UASSERT_OBJ(finfo.m_rhsExprp, varp, "Missing RHS expression for ForceInfo"); + + // Create per-force temporary storage for the captured RHS value. + AstVar* const rhsVarp + = new AstVar{flp, VVarType::VAR, + varp->name() + "__VforceRHS" + std::to_string(finfo.m_forceId), + finfo.m_rhsExprp->dtypep()}; + rhsVarp->noSubst(true); + rhsVarp->sigPublic(true); + rhsVarp->setForcedByCode(); + varp->addNextHere(rhsVarp); + finfo.m_rhsVarVscp = new AstVarScope{flp, scopep, rhsVarp}; + scopep->addVarsp(finfo.m_rhsVarVscp); + + // Build assignments for RHS capture. Public/forceable signals with __VforceRd + // already have an explicit force-read update path, so they do not need the + // forceVec.touch() ordering edge here. + // always_comb begin + // forceRHS[id] = rhsExpr; + // forceVec.touch(); // Only without __VforceRd + // end + AstAssign* const rhsAssignp = new AstAssign{ + flp, new AstVarRef{flp, finfo.m_rhsVarVscp, VAccess::WRITE}, finfo.m_rhsExprp}; + + if (!info.m_forceRdVscp) { + // touch() is intentionally a semantic no-op at runtime: it creates an + // explicit use/ordering edge from the RHS-capture logic to the force vector + // so later optimization/scheduling passes keep this update path connected. + AstCMethodHard* const touchCallp = new AstCMethodHard{ + flp, new AstVarRef{flp, info.m_forceVecVscp, VAccess::WRITE}, + VCMethod::FORCE_TOUCH}; + touchCallp->dtypeSetVoid(); + AstNodeStmt* const touchStmtp = touchCallp->makeStmt(); + rhsAssignp->addNextHere(touchStmtp); + } + + // Run both updates in a combinational always block. + AstAlways* const alwaysp + = new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, rhsAssignp}; + AstSenTree* const senTreep + = new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Combo{}}}; + AstActive* const activep = new AstActive{flp, "force-rhs-update", senTreep}; + activep->senTreeStorep(activep->sentreep()); + activep->addStmtsp(alwaysp); + scopep->addBlocksp(activep); + } + + if (info.m_forceRdVscp) { + AstActive* const activeInitp = new AstActive{ + flp, "force-init", + new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Static{}}}}; + activeInitp->senTreeStorep(activeInitp->sentreep()); + AstAssign* const initEnp + = new AstAssign{flp, new AstVarRef{flp, info.m_forceEnVscp, VAccess::WRITE}, + makeZeroConst(varp, info.m_forceEnVscp->width())}; + initEnp->addNextHere(createForceRdUpdateStmt(info)); + activeInitp->addStmtsp(new AstInitial{flp, initEnp}); + scopep->addBlocksp(activeInitp); + + AstSenItem* itemsp = nullptr; + auto addSenItem = [&](AstVarScope* vscp) { + if (!vscp) return; + AstSenItem* const nextp = new AstSenItem{ + flp, VEdgeType::ET_CHANGED, new AstVarRef{flp, vscp, VAccess::READ}}; + if (itemsp) { + itemsp->addNext(nextp); + } else { + itemsp = nextp; + } + }; + addSenItem(info.m_forceEnVscp); + addSenItem(info.m_forceValVscp); + AstVarRef* const origSenRefp = new AstVarRef{flp, info.m_varVscp, VAccess::READ}; + markNonReplaceable(origSenRefp); + AstSenItem* const origItemp + = new AstSenItem{flp, VEdgeType::ET_CHANGED, origSenRefp}; + if (!itemsp) varp->v3fatalSrc("force-rd-update missing force-enable sen item"); + itemsp->addNext(origItemp); + for (ForceInfo* const finfop : forceps) addSenItem(finfop->m_rhsVarVscp); + + AstActive* const activep + = new AstActive{flp, "force-rd-update", new AstSenTree{flp, itemsp}}; + activep->senTreeStorep(activep->sentreep()); + activep->addStmtsp(new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, + createForceRdUpdateStmt(info)}); + scopep->addBlocksp(activep); + } + } + } + + const ForceInfo& getForceInfo(AstAssignForce* forceStmtp) const { + AstVar* varp = getOneVarRef(forceStmtp->lhsp())->varp(); + auto it = m_varInfo.find(varp); + UASSERT(it != m_varInfo.end(), "Force info not found for variable"); + auto it2 = it->second.m_forces.find(forceStmtp); + UASSERT(it2 != it->second.m_forces.end(), "Force statement not found"); + return it2->second; + } + + AstNodeExpr* createForceReadExpression(const VarForceInfo& varInfo, + AstVarRef* originalRefp) const { + FileLine* const flp = originalRefp->fileline(); + return createForceReadCall(varInfo, flp, VCMethod::FORCE_READ, + originalRefp->cloneTreePure(false), originalRefp->varp(), + nullptr); + } + + AstNodeExpr* createForceReadIndexExpression(const VarForceInfo& varInfo, + AstNodeExpr* originalExprp, + AstNodeExpr* indexExprp) const { + FileLine* const flp = originalExprp->fileline(); + return createForceReadCall(varInfo, flp, VCMethod::FORCE_READ_INDEX, + originalExprp->cloneTreePure(false), originalExprp, indexExprp); + } + + static AstNodeExpr* rebuildSelPath(AstNodeExpr* pathp, AstNodeExpr* baseExprp) { + if (const AstSel* const selp = VN_CAST(pathp, Sel)) { + AstNodeExpr* const fromp = rebuildSelPath(selp->fromp(), baseExprp); + AstSel* const outp + = new AstSel{selp->fileline(), fromp, selp->lsbConst(), selp->widthConst()}; + outp->dtypeFrom(selp); + return outp; + } + return baseExprp; } }; -class ForceConvertVisitor final : public VNVisitor { - // STATE +//###################################################################### +// ForceDiscoveryVisitor - Discover force statements + +class ForceDiscoveryVisitor final : public VNVisitorConst { ForceState& m_state; - // STATIC METHODS - // Replace each AstNodeVarRef in the given 'nodep' that writes a variable by transforming the - // referenced AstVarScope with the given function. - static void transformWritenVarScopes(AstNode* nodep, - std::function f) { - UASSERT_OBJ(nodep->backp(), nodep, "Must have backp, otherwise will be lost if replaced"); - nodep->foreach([&f](AstNodeVarRef* refp) { - if (refp->access() != VAccess::WRITE) return; - // TODO: this is not strictly speaking safe for some complicated lvalues, eg.: - // 'force foo[a(cnt)] = 1;', where 'cnt' is an out parameter, but it will - // do for now... - refp->replaceWith( - new AstVarRef{refp->fileline(), f(refp->varScopep()), VAccess::WRITE}); - VL_DO_DANGLING(refp->deleteTree(), refp); - }); - } - - // VISITORS - void visit(AstNode* nodep) override { iterateChildren(nodep); } - void visit(AstAssignForce* nodep) override { - // The AstAssignForce node will be removed for sure - FileLine* const flp = nodep->fileline(); - AstNodeExpr* const lhsp = nodep->lhsp(); // The LValue we are forcing - AstNodeExpr* const rhsp = nodep->rhsp(); // The value we are forcing it to - VNRelinker relinker; - nodep->unlinkFrBack(&relinker); - VL_DO_DANGLING(pushDeletep(nodep), nodep); + if (nodep->user2()) return; // External force statements are pre-registered. + UINFO(2, "Discovering force statement: " << nodep << "\n"); - // Set corresponding enable signals to ones - V3Number ones{lhsp, ForceState::isRangedDType(lhsp) ? lhsp->width() : 1}; - ones.setAllBits1(); - AstAssign* const setEnp - = new AstAssign{flp, lhsp->cloneTreePure(false), new AstConst{rhsp->fileline(), ones}}; - transformWritenVarScopes(setEnp->lhsp(), [this](AstVarScope* vscp) { - return m_state.getForceComponents(vscp).m_enVscp; - }); - // Set corresponding value signals to the forced value - AstAssign* const setValp - = new AstAssign{flp, lhsp->cloneTreePure(false), rhsp->cloneTreePure(false)}; - transformWritenVarScopes(setValp->lhsp(), [this, rhsp, setValp](AstVarScope* vscp) { - AstVarScope* const valVscp = m_state.getForceComponents(vscp).m_valVscp; - m_state.setValVscpAssign(valVscp, setValp); - rhsp->foreach([valVscp, this](AstVarRef* refp) { m_state.addValVscp(refp, valVscp); }); - return valVscp; - }); + AstVarRef* const lhsVarRefp = m_state.getOneVarRef(nodep->lhsp()); + AstVar* const forcedVarp = lhsVarRefp->varp(); + UASSERT(forcedVarp, "VarRef missing Varp"); - // Set corresponding read signal directly as well, in case something in the same - // process reads it later - AstAssign* const setRdp = new AstAssign{flp, lhsp->unlinkFrBack(), rhsp->unlinkFrBack()}; - transformWritenVarScopes(setRdp->lhsp(), [this](AstVarScope* vscp) { - return m_state.getForceComponents(vscp).m_rdVscp; - }); + // Resolve force bookkeeping range/padding for the LHS shape. + ForceState::ForceRangeInfo rangeInfo + = m_state.getForceRangeInfo(nodep->lhsp(), forcedVarp, true); - setEnp->addNext(setValp); - setEnp->addNext(setRdp); - relinker.relink(setEnp); - } + // 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); - void visit(AstRelease* nodep) override { - FileLine* const flp = nodep->fileline(); - AstNodeExpr* const lhsp = nodep->lhsp(); // The LValue we are releasing - // The AstRelease node will be removed for sure - VNRelinker relinker; - nodep->unlinkFrBack(&relinker); - VL_DO_DANGLING(pushDeletep(nodep), nodep); + // 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); }); - // Set corresponding enable signals to zero - V3Number zero{lhsp, ForceState::isRangedDType(lhsp) ? lhsp->width() : 1}; - zero.setAllBits0(); - AstAssign* const resetEnp - = new AstAssign{flp, lhsp->cloneTreePure(false), new AstConst{lhsp->fileline(), zero}}; - transformWritenVarScopes(resetEnp->lhsp(), [this](AstVarScope* vscp) { - return m_state.getForceComponents(vscp).m_enVscp; - }); + // 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); - // IEEE 1800-2023 10.6.2: When released, then if the variable is not driven by a continuous - // assignment and does not currently have an active procedural continuous assignment, the - // variable shall not immediately change value and shall maintain its current value until - // the next procedural assignment to the variable is executed. Releasing a variable that is - // driven by a continuous assignment or currently has an active assign procedural - // continuous assignment shall reestablish that assignment and schedule a reevaluation in - // the continuous assignment's scheduling region. - AstAssign* const resetRdp - = new AstAssign{flp, lhsp->unlinkFrBack(), lhsp->cloneTreePure(false)}; - resetRdp->user2(true); - AstVarRef* const refp = VN_AS(AstNodeVarRef::varRefLValueRecurse(lhsp), VarRef); - AstVarScope* const vscp = refp->varScopep(); - AstVarRef* const rhsRefp = refp->clonep(); - - if (vscp->varp()->isContinuously()) { - AstVarRef* const lhsRefp = new AstVarRef{ - refp->fileline(), m_state.getForceComponents(vscp).m_rdVscp, VAccess::WRITE}; - refp->replaceWith(lhsRefp); - VL_DO_DANGLING(refp->deleteTree(), refp); - rhsRefp->access(VAccess::READ); - ForceState::markNonReplaceable(rhsRefp); - } else { - if (rhsRefp->dtypep()->skipRefp()->isIntegralOrPacked()) { - // In this case var ref can be replaced with expression - rhsRefp->replaceWith(m_state.getForceComponents(vscp).forcedUpdate(vscp)); - VL_DO_DANGLING(rhsRefp->deleteTree(), rhsRefp); - } else { - AstNodeExpr* const origRhsp = resetRdp->rhsp(); - origRhsp->replaceWith( - m_state.getForceComponents(vscp).forcedUpdate(vscp, origRhsp, rhsRefp)); - VL_DO_DANGLING(origRhsp->deleteTree(), origRhsp); - } + // 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}; } - resetRdp->addNext(resetEnp); - relinker.relink(resetRdp); + m_state.addForceAssignment(forcedVarp, lhsVarRefp->varScopep(), rhsExprp, nodep, + rangeInfo.m_rangeLsb, rangeInfo.m_rangeMsb, rangeInfo.m_padLsb, + rangeInfo.m_padMsb, rangeInfo.m_hasArraySel); } void visit(AstVarScope* nodep) override { - // If this signal is marked externally forceable, create the public force signals if (nodep->varp()->isForceable()) { if (VN_IS(nodep->varp()->dtypeSkipRefp(), UnpackArrayDType)) { nodep->varp()->v3warn( @@ -576,43 +609,327 @@ class ForceConvertVisitor final : public VNVisitor { } const AstBasicDType* const bdtypep = nodep->varp()->basicp(); - const bool strtype = bdtypep && bdtypep->keyword() == VBasicDTypeKwd::STRING; - if (strtype) { + if (bdtypep && bdtypep->keyword() == VBasicDTypeKwd::STRING) { nodep->varp()->v3error( "Forcing strings is not permitted: " << nodep->varp()->name()); } - const ForceState::ForceComponentsVarScope& fc = m_state.getForceComponents(nodep); - fc.m_enVscp->varp()->sigUserRWPublic(true); - fc.m_valVscp->varp()->sigUserRWPublic(true); + // Create per-signal storage for force enable/value state. + AstVar* const varp = nodep->varp(); + FileLine* const flp = varp->fileline(); + AstVar* const rdVarp + = new AstVar{flp, VVarType::WIRE, varp->name() + "__VforceRd", varp->dtypep()}; + rdVarp->noSubst(true); + rdVarp->sigPublic(true); + AstNodeDType* const enDtypep + = ForceState::isBitwiseDType(varp) ? varp->dtypep() : varp->findBitDType(); + AstVar* const enVarp + = new AstVar{flp, VVarType::VAR, varp->name() + "__VforceEn", enDtypep}; + enVarp->sigUserRWPublic(true); + AstVar* const valVarp + = new AstVar{flp, VVarType::VAR, varp->name() + "__VforceVal", varp->dtypep()}; + valVarp->sigUserRWPublic(true); + varp->addNextHere(rdVarp); + varp->addNextHere(enVarp); + varp->addNextHere(valVarp); + AstVarScope* const rdVscp = new AstVarScope{flp, nodep->scopep(), rdVarp}; + AstVarScope* const enVscp = new AstVarScope{flp, nodep->scopep(), enVarp}; + AstVarScope* const valVscp = new AstVarScope{flp, nodep->scopep(), valVarp}; + nodep->scopep()->addVarsp(rdVscp); + nodep->scopep()->addVarsp(enVscp); + nodep->scopep()->addVarsp(valVscp); + + // Register force metadata so later transforms can find these helper vars. + ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(varp); + info.m_forceRdVscp = rdVscp; + info.m_forceEnVscp = enVscp; + info.m_forceValVscp = valVscp; + info.m_varVscp = nodep; + + // Build an update block triggered by force-enable changes. + AstSenItem* const itemsp = new AstSenItem{flp, VEdgeType::ET_CHANGED, + new AstVarRef{flp, enVscp, VAccess::READ}}; + AstActive* const activep + = new AstActive{flp, "force-update", new AstSenTree{flp, itemsp}}; + activep->senTreeStorep(activep->sentreep()); + + // Build expression selecting forced value when enabled, otherwise original value. + // forceExpr = (isBitwise) ? ((en & val) | (~en & orig)) : (en ? val : orig); + AstVarRef* const refp = new AstVarRef{flp, nodep, VAccess::READ}; + ForceState::markNonReplaceable(refp); + AstVarRef* const enRefp = new AstVarRef{flp, enVscp, VAccess::READ}; + AstVarRef* const valRefp = new AstVarRef{flp, valVscp, VAccess::READ}; + const AstBasicDType* const basicp = varp->dtypep()->skipRefp()->basicp(); + AstNodeExpr* const forceExprp + = basicp && basicp->isRanged() + ? static_cast(new AstOr{ + flp, new AstAnd{flp, enRefp, valRefp}, + new AstAnd{flp, new AstNot{flp, enRefp->cloneTreePure(false)}, refp}}) + : static_cast(new AstCond{flp, enRefp, valRefp, refp}); + AstAssignForce* const forceAssignp + = new AstAssignForce{flp, new AstVarRef{flp, nodep, VAccess::WRITE}, forceExprp}; + forceAssignp->user2(true); + activep->addStmtsp(new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, forceAssignp}); + nodep->scopep()->addBlocksp(activep); + + // Clone the RHS for tracking and preserve original var refs as non-replaceable. + AstNodeExpr* const rhsClonep = forceExprp->cloneTreePure(false); + rhsClonep->foreach([varp](AstVarRef* const refp) { + if (refp->varp() == varp) ForceState::markNonReplaceable(refp); + }); + + // Compute full assignment range (including unpacked arrays) for force bookkeeping. + const bool bitwiseVar = ForceState::isBitwiseDType(varp); + const int padMsb = bitwiseVar ? (varp->width() - 1) : 0; + int rangeLsb = 0; + int rangeMsb = padMsb; + if (ForceState::isUnpackedArrayDType(varp->dtypep())) { + nodep->v3fatalSrc("Forceable unpacked arrays should have been rejected earlier"); + } + m_state.addForceAssignment(varp, nodep, rhsClonep, forceAssignp, rangeLsb, rangeMsb, 0, + padMsb, false); } + iterateChildrenConst(nodep); } + void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } + +public: + explicit ForceDiscoveryVisitor(AstNetlist* nodep, ForceState& state) + : m_state{state} { + iterateAndNextConstNull(nodep->modulesp()); + } +}; + +//###################################################################### +// ForceConvertVisitor - Convert force/release statements + +class ForceConvertVisitor final : public VNVisitor { + ForceState& m_state; + + void visit(AstAssignForce* nodep) override { + UINFO(2, "Converting force statement: " << nodep << "\n"); + + AstNodeExpr* const lhsp = nodep->lhsp(); + AstVarRef* const lhsVarRefp = m_state.getOneVarRef(lhsp); + AstVar* const forcedVarp = lhsVarRefp->varp(); + + const ForceState::ForceInfo& info = m_state.getForceInfo(nodep); + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(forcedVarp); + UASSERT_OBJ(varInfo && varInfo->m_forceVecVscp, nodep, "Force info not set up"); + + FileLine* const flp = nodep->fileline(); + + // Assign RHS shadow value immediately so force takes effect in the same timestep. + UASSERT_OBJ(info.m_rhsVarVscp, nodep, "No RHS var for forced variable"); + AstAssign* const rhsAssignp + = new AstAssign{flp, new AstVarRef{flp, info.m_rhsVarVscp, VAccess::WRITE}, + nodep->rhsp()->cloneTreePure(false)}; + + AstAssign* valAssignp = nullptr; + AstAssign* enAssignp = nullptr; + const bool bitwiseForcedVar = ForceState::isBitwiseDType(forcedVarp); + // When an externally forceable signal is also forced in (System)Verilog code + // keep the public __VforceEn/__VforceVal signals in sync with the procedural force too. + // Don't do this for array selections; those are represented only in VlForceVec. + if (!nodep->user2() && varInfo->m_forceEnVscp && varInfo->m_forceValVscp + && !info.m_hasArraySel) { + AstNodeExpr* baseExprp = nodep->rhsp()->cloneTreePure(false); + baseExprp->foreach( + [](AstVarRef* const refp) { ForceState::markNonReplaceable(refp); }); + if (bitwiseForcedVar) { + baseExprp = ForceState::zeroPadToBaseWidth(baseExprp, forcedVarp->width(), + info.m_padLsb, info.m_padMsb); + } + if (bitwiseForcedVar) { + // forceVal = (forceVal & ~mask(range)) | (rhs_padded & mask(range)); + // forceEn = forceEn | mask(range); + AstConst* const maskConstp = ForceState::makeRangeMaskConst( + nodep, forcedVarp->width(), info.m_rangeLsb, info.m_rangeMsb); + AstNodeExpr* const valReadp + = new AstVarRef{flp, varInfo->m_forceValVscp, VAccess::READ}; + AstNodeExpr* const valWritep + = new AstVarRef{flp, varInfo->m_forceValVscp, VAccess::WRITE}; + AstNodeExpr* const notMaskp = new AstNot{flp, maskConstp}; + AstNodeExpr* const maskedOldp = new AstAnd{flp, valReadp, notMaskp}; + AstNodeExpr* const newValp = new AstOr{flp, maskedOldp, baseExprp}; + valAssignp = new AstAssign{flp, valWritep, newValp}; + + AstNodeExpr* const enReadp + = new AstVarRef{flp, varInfo->m_forceEnVscp, VAccess::READ}; + AstNodeExpr* const enWritep + = new AstVarRef{flp, varInfo->m_forceEnVscp, VAccess::WRITE}; + AstNodeExpr* const newEnp + = new AstOr{flp, enReadp, maskConstp->cloneTreePure(false)}; + enAssignp = new AstAssign{flp, enWritep, newEnp}; + } else { + AstConst* const oneConstp = ForceState::makeRangeMaskConst(nodep, 1, 0, 0); + AstNodeExpr* const rhsValp = ForceState::castToNodeDType(baseExprp, forcedVarp); + valAssignp = new AstAssign{ + flp, new AstVarRef{flp, varInfo->m_forceValVscp, VAccess::WRITE}, rhsValp}; + enAssignp = new AstAssign{ + flp, new AstVarRef{flp, varInfo->m_forceEnVscp, VAccess::WRITE}, oneConstp}; + } + } + + // Verilog pseudocode: + // forceVec.addForce(range_lsb, range_msb, &forceRHS[id], rhs_lsb); + AstNodeExpr* const rhsDatap = ForceState::buildRhsDataExpr(flp, info); + AstCExpr* const rhsAddrp = new AstCExpr{flp}; + rhsAddrp->add("&("); + rhsAddrp->add(rhsDatap); + rhsAddrp->add(")"); + AstCMethodHard* const addForceCallp = new AstCMethodHard{ + flp, new AstVarRef{flp, varInfo->m_forceVecVscp, VAccess::WRITE}, VCMethod::FORCE_ADD, + 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->dtypeSetVoid(); + AstNodeStmt* const stmtp = addForceCallp->makeStmt(); + + AstNode* tailp = rhsAssignp; + if (valAssignp) { + tailp->addNextHere(valAssignp); + tailp = valAssignp; + } + if (enAssignp) { + tailp->addNextHere(enAssignp); + tailp = enAssignp; + } + tailp->addNextHere(stmtp); + if (varInfo->m_forceRdVscp) { + stmtp->addNextHere(m_state.createForceRdUpdateStmt(*varInfo)); + } + nodep->replaceWith(rhsAssignp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + + void visit(AstRelease* nodep) override { + UINFO(2, "Converting release statement: " << nodep << "\n"); + + AstNodeExpr* const lhsp = nodep->lhsp(); + AstVarRef* const lhsVarRefp = m_state.getOneVarRef(lhsp); + AstVar* const releasedVarp = lhsVarRefp->varp(); + + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(releasedVarp); + if (!varInfo) { + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } + UASSERT_OBJ(!varInfo->m_forces.empty(), nodep, "Var info for variable with no forces"); + + FileLine* const flp = nodep->fileline(); + + const ForceState::ForceRangeInfo rangeInfo + = m_state.getForceRangeInfo(lhsp, releasedVarp, false); + + 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)); + releaseCallp->dtypeSetVoid(); + // forceVec.release(range_lsb, range_msb); + AstNodeStmt* const releasep = releaseCallp->makeStmt(); + + AstAssign* clearEnp = nullptr; + // Releases must also clear the external/public force-enable, but only for + // directly forceable variables and only for non-array-select cases that use that external + // force. + if (releasedVarp->isForceable() && varInfo->m_forceEnVscp && !rangeInfo.m_hasArraySel) { + AstNodeExpr* const enWritep + = new AstVarRef{flp, varInfo->m_forceEnVscp, VAccess::WRITE}; + if (ForceState::isBitwiseDType(releasedVarp)) { + const int varWidth = releasedVarp->width(); + if (rangeInfo.m_rangeLsb == 0 && rangeInfo.m_rangeMsb == varWidth - 1) { + clearEnp + = new AstAssign{flp, enWritep, ForceState::makeZeroConst(nodep, varWidth)}; + } else { + // forceEn = forceEn & ~mask(range); + AstNodeExpr* const enReadp + = new AstVarRef{flp, varInfo->m_forceEnVscp, VAccess::READ}; + AstConst* const maskConst = ForceState::makeRangeMaskConst( + nodep, varWidth, rangeInfo.m_rangeLsb, rangeInfo.m_rangeMsb); + AstNodeExpr* const newEnp + = new AstAnd{flp, enReadp, new AstNot{flp, maskConst}}; + clearEnp = new AstAssign{flp, enWritep, newEnp}; + } + } else { + clearEnp = new AstAssign{flp, enWritep, ForceState::makeZeroConst(nodep, 1)}; + } + } + + const AstSel* const selp = VN_CAST(lhsp, Sel); + AstNodeExpr* const basep = selp ? selp->fromp() : lhsp; + + AstNode* stmtListp = releasep; + if (clearEnp) { + clearEnp->addNextHere(stmtListp); + stmtListp = clearEnp; + } + + // IEEE 1800-2023 10.6.2: When released, if the variable is not continuously driven, + // it maintains its current value until the next procedural assignment. + if (!releasedVarp->isContinuously()) { + // Member/struct paths on non-bitwise types do not lower to a plain VarRef/bit range, + // so their current forced value is recovered via the same synthetic path index. + // if (!continuously_driven) lhs = force_read_current(lhs_path); + // forceVec.release(range); + const bool hasOpaquePath = !ForceState::isBitwiseDType(releasedVarp) + && !rangeInfo.m_hasArraySel && !VN_IS(lhsp, VarRef); + AstNodeExpr* forceReadp = nullptr; + if (hasOpaquePath) { + forceReadp = m_state.createForceReadIndexExpression( + *varInfo, lhsp, ForceState::makeConst32(flp, rangeInfo.m_rangeLsb)); + } else if (rangeInfo.m_hasArraySel) { + forceReadp = m_state.createForceReadIndexExpression( + *varInfo, basep, + ForceState::buildFlattenIndexExpr(flp, rangeInfo.m_arrayInfo)); + if (selp) { forceReadp = ForceState::rebuildSelPath(lhsp, forceReadp); } + } else { + forceReadp + = selp ? ForceState::rebuildSelPath( + lhsp, m_state.createForceReadExpression(*varInfo, lhsVarRefp)) + : m_state.createForceReadExpression(*varInfo, lhsVarRefp); + } + AstAssign* const assignp = new AstAssign{flp, lhsp->cloneTreePure(false), forceReadp}; + assignp->addNextHere(stmtListp); + stmtListp = assignp; + } + + if (varInfo->m_forceRdVscp) { + AstNode* tailp = stmtListp; + while (tailp->nextp()) tailp = tailp->nextp(); + tailp->addNextHere(m_state.createForceRdUpdateStmt(*varInfo)); + } + + nodep->replaceWith(stmtListp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + + void visit(AstNode* nodep) override { iterateChildren(nodep); } + public: - // CONSTRUCTOR - // cppcheck-suppress constParameterCallback ForceConvertVisitor(AstNetlist* nodep, ForceState& state) : m_state{state} { - // Transform all force and release statements iterateAndNextNull(nodep->modulesp()); } }; +//###################################################################### +// ForceReplaceVisitor - Replace variable reads with force-aware reads + class ForceReplaceVisitor final : public VNVisitor { - // STATE const ForceState& m_state; AstNodeStmt* m_stmtp = nullptr; bool m_inLogic = false; - bool m_releaseRhs = false; // Inside RHS of assignment created for release statement - // METHODS - void iterateLogic(AstNode* logicp) { + void iterateLogic(AstNode* nodep) { VL_RESTORER(m_inLogic); m_inLogic = true; - iterateChildren(logicp); + iterateChildren(nodep); } - // VISITORS void visit(AstNodeStmt* nodep) override { VL_RESTORER(m_stmtp); m_stmtp = nodep; @@ -620,18 +937,14 @@ class ForceReplaceVisitor final : public VNVisitor { } void visit(AstAssign* nodep) override { VL_RESTORER(m_stmtp); - VL_RESTORER(m_releaseRhs); m_stmtp = nodep; iterate(nodep->lhsp()); - m_releaseRhs = nodep->user2(); iterate(nodep->rhsp()); } void visit(AstCFunc* nodep) override { iterateLogic(nodep); } void visit(AstCoverToggle* nodep) override { iterateLogic(nodep); } void visit(AstNodeProcedure* nodep) override { iterateLogic(nodep); } void visit(AstAlways* nodep) override { - // TODO: this is the old behavioud prior to moving AssignW under Always. - // Review if this is appropriate or if we are missing something... if (nodep->keyword() == VAlwaysKwd::CONT_ASSIGN) { iterateChildren(nodep); return; @@ -639,92 +952,136 @@ class ForceReplaceVisitor final : public VNVisitor { iterateLogic(nodep); } void visit(AstSenItem* nodep) override { iterateLogic(nodep); } + void visit(AstArraySel* nodep) override { + if (nodep->backp() && VN_IS(nodep->backp(), ArraySel)) { + // Only the outermost unpacked array selection should become a force-aware read; + // inner nested selections are folded into the final flattened index. + iterateChildren(nodep); + return; + } + + AstVarRef* const baseRefp = m_state.getOneVarRef(nodep); + AstVar* const varp = baseRefp->varp(); + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + // Skip non-forceable reads, reads we intentionally protected earlier, and intermediate + // selections that still evaluate to an unpacked array rather than a scalar element. + if (ForceState::isNotReplaceable(baseRefp) || !varInfo + || !ForceState::isUnpackedArrayDType(varp->dtypep()) + || VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)) { + iterateChildren(nodep); + return; + } + + if (!baseRefp->access().isReadOnly()) { + iterateChildren(nodep); + return; + } + const ForceState::ArraySelInfo arrayInfo = ForceState::getArraySelInfo(nodep); + AstNodeExpr* const indexExprp + = ForceState::buildFlattenIndexExpr(nodep->fileline(), arrayInfo); + AstNodeExpr* const readExprp + = m_state.createForceReadIndexExpression(*varInfo, nodep, indexExprp); + nodep->replaceWith(readExprp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + void visit(AstVarRef* nodep) override { if (ForceState::isNotReplaceable(nodep)) return; + if (nodep->backp() && VN_IS(nodep->backp(), ArraySel)) return; - switch (nodep->access()) { - case VAccess::READ: { - // Replace VarRef from forced LHS with rdVscp. - if (ForceState::ForceComponentsVarScope* const fcp - = m_state.tryGetForceComponents(nodep)) { - nodep->varp(fcp->m_rdVscp->varp()); - nodep->varScopep(fcp->m_rdVscp); - } - break; - } - case VAccess::WRITE: { - if (!m_inLogic) return; - // Emit rdVscp update after each write to any VarRef on forced LHS. - if (ForceState::ForceComponentsVarScope* const fcp - = m_state.tryGetForceComponents(nodep)) { - FileLine* const flp = nodep->fileline(); - AstVarRef* const lhsRefp = new AstVarRef{flp, fcp->m_rdVscp, VAccess::WRITE}; - AstNodeExpr* lhsp; - AstNodeExpr* rhsp; - if (nodep->dtypep()->skipRefp()->isIntegralOrPacked()) { - rhsp = fcp->forcedUpdate(nodep->varScopep()); - lhsp = lhsRefp; - } else { - AstNodeExpr* wholeExprp = nodep; - while (VN_IS(wholeExprp->backp(), NodeExpr)) { - wholeExprp = VN_AS(wholeExprp->backp(), NodeExpr); - // wholeExprp should never be ExprStmt, because: - // * if nodep is inside stmtsp() of one, we should sooner get NodeStmt node - // * nodep should never be in resultp(), because it is a WRITE reference - // and resultp() should be an rvalue - UASSERT_OBJ(!VN_IS(wholeExprp, ExprStmt), nodep, "Unexpected AstExprStmt"); - } - lhsp = ForceState::ForceComponentsVarScope::wrapIntoExprp(lhsRefp, wholeExprp, - nodep); - rhsp = fcp->forcedUpdate(nodep->varScopep(), wholeExprp, nodep); + AstVar* const varp = nodep->varp(); + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + if (!varInfo) return; + + if (varInfo->m_forceRdVscp) { + if (nodep->access().isRW()) { + if (m_inLogic) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: Signals used via read-write reference cannot be " + "forced"); } - m_stmtp->addNextHere(new AstAssign{flp, lhsp, rhsp}); + return; } - // Emit valVscp update after each write to any VarRef on forced RHS. - if (!m_state.getValVscps(nodep)) break; - for (AstVarScope* const valVscp : *m_state.getValVscps(nodep)) { - FileLine* const flp = nodep->fileline(); - AstAssign* assignp = m_state.getValVscpAssign(valVscp); - UASSERT_OBJ(assignp, flp, "Missing stored assignment for forced valVscp"); - - assignp = assignp->cloneTreePure(false); - - assignp->rhsp()->foreach( - [](AstVarRef* refp) { ForceState::markNonReplaceable(refp); }); - - m_stmtp->addNextHere(assignp); + if (nodep->access().isReadOnly()) { + nodep->varp(varInfo->m_forceRdVscp->varp()); + nodep->varScopep(varInfo->m_forceRdVscp); + return; } - break; + if (m_inLogic && m_stmtp) { + m_stmtp->addNextHere(m_state.createForceRdUpdateStmt(*varInfo)); + } + return; } - default: - if (!m_inLogic) return; - if (m_state.tryGetForceComponents(nodep) || m_state.getValVscps(nodep)) { - nodep->v3warn( - E_UNSUPPORTED, - "Unsupported: Signals used via read-write reference cannot be forced"); - } - break; + + // For opaque member/struct paths we rewrite the outer path node instead of the base + // VarRef, so leave the base reference alone and let visit(AstNode*) handle it. + if (nodep->backp() && (VN_IS(nodep->backp(), Sel) || VN_IS(nodep->backp(), StructSel)) + && !ForceState::isBitwiseDType(nodep->varp()) + && !ForceState::isUnpackedArrayDType(nodep->varp()->dtypep())) { + return; + } + + UASSERT_OBJ(!varInfo->m_forces.empty(), nodep, "Var info for variable with no forces"); + + if (nodep->access().isRW()) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: Signals used via read-write reference cannot be forced"); + } else if (nodep->access().isReadOnly()) { + ForceState::markNonReplaceable(nodep); + AstNodeExpr* const readExprp = m_state.createForceReadExpression(*varInfo, nodep); + nodep->replaceWith(readExprp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); } } - void visit(AstNode* nodep) override { iterateChildren(nodep); } + void visit(AstNode* nodep) override { + if (ForceState::isOutermostOpaquePathSelector(nodep)) { + // Handle the whole opaque path at its outermost node so we can assign one stable + // synthetic force-path index to the full selection/member chain. + AstNodeExpr* const exprp = VN_AS(nodep, NodeExpr); + AstNode* const basep = AstArraySel::baseFromp(exprp, true); + AstVarRef* const baseRefp = VN_CAST(basep, VarRef); + if (baseRefp) { + AstVar* const varp = baseRefp->varp(); + if (!ForceState::isBitwiseDType(varp) + && !ForceState::isUnpackedArrayDType(varp->dtypep())) { + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + if (!ForceState::isNotReplaceable(baseRefp) && varInfo) { + const int forcePathIndex = varInfo->findForcePathIndex(exprp); + if (forcePathIndex >= 0) { + if (!baseRefp->access().isReadOnly()) return; + AstNodeExpr* const readExprp = m_state.createForceReadIndexExpression( + *varInfo, exprp, + ForceState::makeConst32(nodep->fileline(), forcePathIndex)); + nodep->replaceWith(readExprp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } + } + } + } + iterateChildren(nodep); + } public: - // CONSTRUCTOR explicit ForceReplaceVisitor(AstNetlist* nodep, const ForceState& state) : m_state{state} { - iterateChildren(nodep); + iterateAndNextNull(nodep->modulesp()); } }; //###################################################################### // +//###################################################################### +// V3Force - Main entry point + void V3Force::forceAll(AstNetlist* nodep) { - UINFO(2, __FUNCTION__ << ":"); + UINFO(2, __FUNCTION__ << ":\n"); if (!v3Global.hasForceableSignals()) return; - { - ForceState state; - { ForceConvertVisitor{nodep, state}; } - { ForceReplaceVisitor{nodep, state}; } - V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3); - } + ForceState state; + { ForceDiscoveryVisitor{nodep, state}; } + state.finalizeRhsVars(); + { ForceConvertVisitor{nodep, state}; } + { ForceReplaceVisitor{nodep, state}; } + V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3); } diff --git a/src/V3Global.h b/src/V3Global.h index 7b5c1aa18..44930c2aa 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -125,6 +125,7 @@ class V3Global final { bool m_usesProbDist = false; // Uses $dist_* bool m_usesStdPackage = false; // Design uses the std package bool m_usesTiming = false; // Design uses timing constructs + bool m_usesForce = false; // Design uses force/release statements bool m_usesZeroDelay = false; // Design uses #0 delay (or non-constant delay) bool m_hasForceableSignals = false; // Need to apply V3Force pass bool m_hasSystemCSections = false; // Has AstSystemCSection that need to be emitted @@ -205,6 +206,8 @@ public: void setUsesZeroDelay() { m_usesZeroDelay = true; } bool hasForceableSignals() const { return m_hasForceableSignals; } void setHasForceableSignals() { m_hasForceableSignals = true; } + bool usesForce() const { return m_usesForce; } + void setUsesForce() { m_usesForce = true; } bool hasSystemCSections() const VL_MT_SAFE { return m_hasSystemCSections; } void setHasSystemCSections() { m_hasSystemCSections = true; } V3HierGraph* hierGraphp() const { return m_hierGraphp; } diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 26104ae08..199e6f5b7 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -263,6 +263,10 @@ class PremitVisitor final : public VNVisitor { iterateChildren(nodep); checkNode(nodep); } + void visit(AstCMethodHard* nodep) override { + iterateChildren(nodep); + checkNode(nodep); + } void visit(AstCvtArrayToPacked* nodep) override { iterateChildren(nodep); checkNode(nodep); diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index 906bbda77..5de4c628b 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -403,9 +403,9 @@ module t ( wire logic [63:0] volatile_packed_out_of_cycle /* verilator forceable */ = rand_a; assign VOLATILE_PACKED_OUT_OF_CYCLE = volatile_packed_out_of_cycle ^ 64'(VOLATILE_PACKED_OUT_OF_CYCLE[63:1]); + wire logic [2:0] volatile_packed_in_cycle /* verilator forceable */; // verilator lint_off UNOPTFLAT `signal(VOLATILE_PACKED_IN_CYCLE, 3); - wire logic [2:0] volatile_packed_in_cycle /* verilator forceable */; assign volatile_packed_in_cycle = rand_a[2:0] ^ 3'(volatile_packed_in_cycle[2:1]); assign VOLATILE_PACKED_IN_CYCLE = volatile_packed_in_cycle; // verilator lint_on diff --git a/test_regress/t/t_force.v b/test_regress/t/t_force.v index 858b42ebb..8d08f5311 100644 --- a/test_regress/t/t_force.v +++ b/test_regress/t/t_force.v @@ -10,14 +10,29 @@ `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); // verilog_format: on +module t_assert; + logic clk; + logic zeroize; + logic [7:0] key_mem [0:0]; + assert property (@(posedge clk) zeroize |=> (key_mem[0] == 0)); + initial force zeroize = 0; +endmodule + module t ( input clk ); + t_assert t_assert_i(); + integer cyc = 0; + localparam logic [95:0] WIDE_INIT = 96'h12345678_9abcdef0_13579bdf; + localparam logic [94:0] WIDE_FORCE95 = {3'b101, 32'h12345678, 32'h89abcdef, 28'h2468ace}; reg [3:0] in; tri [3:0] bus = in; + logic [95:0] wide_src; + wire [95:0] wide_bus = wide_src; + logic [7:0] unpacked [0:3]; int never_driven; int never_forced; @@ -98,6 +113,25 @@ module t ( `checkh(bus, 4'b0101); end // + else if (cyc == 35) begin + force bus = 4'b1111; + end + else if (cyc == 36) begin + `checkh(bus, 4'b1111); + force bus[3:1] = 3'b000; + end + else if (cyc == 37) begin + `checkh(bus, 4'b0001); + release bus[2]; + end + else if (cyc == 38) begin + `checkh(bus, 4'b0101); + release bus; + end + else if (cyc == 39) begin + `checkh(bus, 4'b0101); + end + // else if (cyc == 40) begin r <= 1.25; end @@ -117,6 +151,66 @@ module t ( `checkr(r, 2.5); end // + else if (cyc == 50) begin + wide_src <= WIDE_INIT; + end + else if (cyc == 51) begin + `checkh(wide_bus, WIDE_INIT); + end + else if (cyc == 52) begin + force wide_bus[95:1] = WIDE_FORCE95; + end + else if (cyc == 53) begin + `checkh(wide_bus, {WIDE_FORCE95, WIDE_INIT[0]}); + end + else if (cyc == 54) begin + release wide_bus[95:1]; + end + else if (cyc == 55) begin + `checkh(wide_bus, WIDE_INIT); + end + // + else if (cyc == 60) begin + unpacked[0] <= 8'h10; + unpacked[1] <= 8'h20; + unpacked[2] <= 8'h30; + unpacked[3] <= 8'h40; + end + else if (cyc == 61) begin + `checkh(unpacked[0], 8'h10); + `checkh(unpacked[1], 8'h20); + `checkh(unpacked[2], 8'h30); + `checkh(unpacked[3], 8'h40); + end + else if (cyc == 62) begin + force unpacked[1] = 8'hb1; + force unpacked[2] = 8'hc2; + end + else if (cyc == 63) begin + `checkh(unpacked[0], 8'h10); + `checkh(unpacked[1], 8'hb1); + `checkh(unpacked[2], 8'hc2); + `checkh(unpacked[3], 8'h40); + end + else if (cyc == 64) begin + release unpacked[1]; + release unpacked[2]; + end + else if (cyc == 65) begin + `checkh(unpacked[0], 8'h10); + `checkh(unpacked[1], 8'hb1); + `checkh(unpacked[2], 8'hc2); + `checkh(unpacked[3], 8'h40); + unpacked[1] <= 8'h21; + unpacked[2] <= 8'h32; + end + else if (cyc == 66) begin + `checkh(unpacked[0], 8'h10); + `checkh(unpacked[1], 8'h21); + `checkh(unpacked[2], 8'h32); + `checkh(unpacked[3], 8'h40); + end + // else if (cyc == 99) begin $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_force_chained.out b/test_regress/t/t_force_chained.out deleted file mode 100644 index e52b51e2b..000000000 --- a/test_regress/t/t_force_chained.out +++ /dev/null @@ -1,4 +0,0 @@ -%Error: t/t_force_chained.v:30: got='h0 exp='h00000001 -%Error: t/t_force_chained.v:36: got='h0 exp='h00000002 -%Error: t/t_force_chained.v:43: got='h0 exp='h00000003 -%Error: t/t_force_chained.v:49: got='h0 exp='h00000003 diff --git a/test_regress/t/t_force_chained.py b/test_regress/t/t_force_chained.py index e9f4a44c9..3c6aa8922 100755 --- a/test_regress/t/t_force_chained.py +++ b/test_regress/t/t_force_chained.py @@ -13,6 +13,6 @@ test.scenarios('vlt') test.compile(verilator_flags2=["--binary"]) -test.execute(expect_filename=test.golden_filename) +test.execute() test.passes() diff --git a/test_regress/t/t_force_chained.v b/test_regress/t/t_force_chained.v index e197ee355..75a9b8082 100644 --- a/test_regress/t/t_force_chained.v +++ b/test_regress/t/t_force_chained.v @@ -4,10 +4,10 @@ // SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 -// verilog_format: off -`define stop // TODO -`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 +`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: off module t; reg [1:0] a; @@ -26,7 +26,6 @@ module t; #1; `checkh(a, 1); `checkh(b, 1); - // TODO implement inter-dependency resolution between force statements `checkh(c, 1); a = 2; diff --git a/test_regress/t/t_force_cond.py b/test_regress/t/t_force_cond.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_force_cond.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_force_cond.v b/test_regress/t/t_force_cond.v new file mode 100644 index 000000000..46f8ac3c9 --- /dev/null +++ b/test_regress/t/t_force_cond.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Antmicro. +// 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='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) + +`define IMPURE_ONE |($random | $random) + +module t; + reg [1:0] a = 2; + bit [1:0] b = 1; + bit [1:0] c = 0; + + initial begin + force a = b; + if (`IMPURE_ONE == 1) force a = c; + if (`IMPURE_ONE == 0) force a = b; + c = 3; + b = 2; + #1; + `checkh(a, 3); + if (`IMPURE_ONE == 1) force a = b; + if (`IMPURE_ONE == 0) force a = c; + c = 2; + b = 1; + #1; + `checkh(a, 1); + #1 $finish; + end + +endmodule diff --git a/test_regress/t/t_force_forceable_readwrite_unsup.out b/test_regress/t/t_force_forceable_readwrite_unsup.out new file mode 100644 index 000000000..b766afe41 --- /dev/null +++ b/test_regress/t/t_force_forceable_readwrite_unsup.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_force_forceable_readwrite_unsup.v:15:14: Unsupported: Signals used via read-write reference cannot be forced + 15 | take_ref(value); + | ^~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_force_unpacked_unsup.py b/test_regress/t/t_force_forceable_readwrite_unsup.py similarity index 85% rename from test_regress/t/t_force_unpacked_unsup.py rename to test_regress/t/t_force_forceable_readwrite_unsup.py index 382ad0d44..344a4e20a 100755 --- a/test_regress/t/t_force_unpacked_unsup.py +++ b/test_regress/t/t_force_forceable_readwrite_unsup.py @@ -4,12 +4,12 @@ # 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('simulator') +test.scenarios('vlt') test.lint(fails=True, expect_filename=test.golden_filename) diff --git a/test_regress/t/t_force_forceable_readwrite_unsup.v b/test_regress/t/t_force_forceable_readwrite_unsup.v new file mode 100644 index 000000000..b5b320971 --- /dev/null +++ b/test_regress/t/t_force_forceable_readwrite_unsup.v @@ -0,0 +1,19 @@ +// 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 value /* verilator forceable */; + + task take_ref(ref logic i); + // verilator no_inline_task + endtask + + initial begin + take_ref(value); + force value = 1'b1; + $finish; + end +endmodule diff --git a/test_regress/t/t_force_forceable_rhs_ref.py b/test_regress/t/t_force_forceable_rhs_ref.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_force_forceable_rhs_ref.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_forceable_rhs_ref.v b/test_regress/t/t_force_forceable_rhs_ref.v new file mode 100644 index 000000000..7ff6f01b8 --- /dev/null +++ b/test_regress/t/t_force_forceable_rhs_ref.v @@ -0,0 +1,49 @@ +// 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 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 ( + input clk +); + integer cyc = 0; + logic src_1 = 0; + logic [7:0] src_8 = 8'h10; + logic dst_1 /* verilator forceable */; + logic [7:0] dst_8 /* verilator forceable */; + + always @* dst_1 = src_1; + always @* dst_8 = src_8; + + always @(posedge clk) begin + cyc <= cyc + 1; + case (cyc) + 0: begin + force dst_1 = src_1; + force dst_8 = src_8 ^ 8'hf0; + `checkh(dst_1, 1'b0); + `checkh(dst_8, 8'he0); + end + 1: begin + release dst_1; + release dst_8; + src_1 = 1'b1; + src_8 = 8'h23; + end + 2: begin + `checkh(dst_1, 1'b1); + `checkh(dst_8, 8'h23); + $write("*-* All Finished *-*\n"); + $finish; + end + default: begin + end + endcase + end +endmodule diff --git a/test_regress/t/t_force_multi.v b/test_regress/t/t_force_multi.v index c856443e5..1479359f5 100644 --- a/test_regress/t/t_force_multi.v +++ b/test_regress/t/t_force_multi.v @@ -9,35 +9,35 @@ `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(/*AUTOARG*/ - // Inputs - input clk - ); +module t ( /*AUTOARG*/ + // Inputs + input clk +); - integer cyc = 0; + integer cyc = 0; - logic [3:0] busa; - logic [3:0] busb; + logic [3:0] busa; + logic [3:0] busb; - // Test loop - always @ (posedge clk) begin - cyc <= cyc + 1; - if (cyc == 0) begin - busa <= 4'b0101; - busb <= 4'b0111; - end - else if (cyc == 1) begin - force {busa, busb} = 8'b1111_1101; - end - else if (cyc == 2) begin - `checkh(busa, 4'b1111); - `checkh(busb, 4'b1101); - end - // - else if (cyc == 99) begin - $write("*-* All Finished *-*\n"); - $finish; - end - end + // Test loop + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 0) begin + busa <= 4'b0101; + busb <= 4'b0111; + end + else if (cyc == 1) begin + force {busa, busb} = 8'b1111_1101; + end + else if (cyc == 2) begin + `checkh(busa, 4'b1111); + `checkh(busb, 4'b1101); + end + // + else if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end endmodule diff --git a/test_regress/t/t_force_nested_struct.py b/test_regress/t/t_force_nested_struct.py new file mode 100755 index 000000000..1005aa93b --- /dev/null +++ b/test_regress/t/t_force_nested_struct.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-FileCopyrightText: 2025 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_force_nested_struct.v b/test_regress/t/t_force_nested_struct.v new file mode 100644 index 000000000..6e71d41a4 --- /dev/null +++ b/test_regress/t/t_force_nested_struct.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Antmicro. +// 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='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) +// verilog_format: on + +typedef struct { + logic [31:0] val; + logic [31:0] other; +} St1; + +typedef struct { + St1 inner; + logic [31:0] tail; +} St2; + +module m; + St2 st2; + St2 forced; + St2 snapshot; + initial begin + st2.inner.val = 32'h11; + st2.inner.other = 32'h12; + st2.tail = 32'h13; + forced.inner.val = 32'h21; + forced.inner.other = 32'h22; + forced.tail = 32'h23; + force st2 = forced; + snapshot = st2; + `checkh(snapshot.inner.val, 32'h21); + `checkh(snapshot.inner.val[0], 1'b1); + force st2.inner.val = 32'h30; + release st2.inner.val; + snapshot = st2; + `checkh(snapshot.inner.val, 32'h21); + `checkh(snapshot.inner.val[0], 1'b1); + `checkh(snapshot.inner.other, 32'h22); + `checkh(snapshot.tail, 32'h23); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_force_nested_struct2.py b/test_regress/t/t_force_nested_struct2.py new file mode 100755 index 000000000..c03142385 --- /dev/null +++ b/test_regress/t/t_force_nested_struct2.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2026 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-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_force_nested_struct2.v b/test_regress/t/t_force_nested_struct2.v new file mode 100644 index 000000000..aa156003a --- /dev/null +++ b/test_regress/t/t_force_nested_struct2.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Antmicro. +// 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='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) +// verilog_format: on + +typedef struct packed { + logic [31:0] value; +} Entry; + +typedef struct packed { + Entry [1:0][1:0] entries; +} DataBlock; + +module sub; + DataBlock data_block; +endmodule + +module t; + sub sub1 (); + logic [31:0] forced_value; + initial begin + forced_value = 32'h00000001; + force sub1.data_block.entries[0][0].value = forced_value[31:0]; + `checkh(sub1.data_block.entries[0][0].value[0], 1'b1); + `checkh(sub1.data_block.entries[0][0].value, 32'h00000001); + $finish; + end +endmodule diff --git a/test_regress/t/t_force_port_inline.v b/test_regress/t/t_force_port_inline.v index cb5eee2b3..27f7fbe18 100644 --- a/test_regress/t/t_force_port_inline.v +++ b/test_regress/t/t_force_port_inline.v @@ -9,69 +9,75 @@ `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 sub( - input wire [7:0] i, - output wire [7:0] o +module sub ( + input wire [7:0] i, + output wire [7:0] o ); - // Must inline this module - // verilator inline_module + // Must inline this module + // verilator inline_module - wire [7:0] m; - assign m = i; - assign o = m; + wire [7:0] m; + assign m = i; + assign o = m; endmodule module top; - // Variable input - reg [7:0] i = 8'h01; - reg [7:0] o_v; - sub sub_v(i, o_v); + // Variable input + reg [7:0] i = 8'h01; + reg [7:0] o_v; + sub sub_v ( + i, + o_v + ); - // Constant input - reg [7:0] o_c; - sub sub_c(8'h10, o_c); + // Constant input + reg [7:0] o_c; + sub sub_c ( + 8'h10, + o_c + ); - logic clk = 1'b0; - always #1 clk = ~clk; - int cyc = 0; + logic clk = 1'b0; + always #1 clk = ~clk; + int cyc = 0; - always @ (posedge clk) begin - cyc <= cyc + 1; - if (cyc == 1) begin - `checkh(i, 8'h01); - `checkh(sub_v.i, 8'h01); - `checkh(sub_v.m, 8'h01); - `checkh(sub_v.o, 8'h01); - `checkh(o_v, 8'h01); - `checkh(sub_c.i, 8'h10); - `checkh(sub_c.m, 8'h10); - `checkh(sub_c.o, 8'h10); - `checkh(o_c, 8'h10); - end - else if (cyc == 2) begin - force sub_v.i = 8'h02; - force sub_v.m = 8'h03; - force sub_v.o = 8'h04; - force sub_c.i = 8'h20; - force sub_c.m = 8'h30; - force sub_c.o = 8'h40; - end - else if (cyc == 3) begin - `checkh(i, 8'h01); - `checkh(sub_v.i, 8'h02); - `checkh(sub_v.m, 8'h03); - `checkh(sub_v.o, 8'h04); - `checkh(o_v, 8'h04); - `checkh(sub_c.i, 8'h20); - `checkh(sub_c.m, 8'h30); - `checkh(sub_c.o, 8'h40); - `checkh(o_c, 8'h40); - end - // - else if (cyc == 99) begin - $write("*-* All Finished *-*\n"); - $finish; - end - end + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) begin + `checkh(i, 8'h01); + `checkh(sub_v.i, 8'h01); + `checkh(sub_v.m, 8'h01); + `checkh(sub_v.o, 8'h01); + `checkh(o_v, 8'h01); + `checkh(sub_c.i, 8'h10); + `checkh(sub_c.m, 8'h10); + `checkh(sub_c.o, 8'h10); + `checkh(o_c, 8'h10); + end + else if (cyc == 2) begin + force sub_v.i = 8'h02; + force sub_v.m = 8'h03; + force sub_v.o = 8'h04; + force sub_c.i = 8'h20; + force sub_c.m = 8'h30; + force sub_c.o = 8'h40; + end + else if (cyc == 3) begin + `checkh(i, 8'h01); + `checkh(sub_v.i, 8'h02); + `checkh(sub_v.m, 8'h03); + `checkh(sub_v.o, 8'h04); + `checkh(o_v, 8'h04); + `checkh(sub_c.i, 8'h20); + `checkh(sub_c.m, 8'h30); + `checkh(sub_c.o, 8'h40); + `checkh(o_c, 8'h40); + end + // + else if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end endmodule diff --git a/test_regress/t/t_force_readwrite_unsup.out b/test_regress/t/t_force_readwrite_unsup.out index e8f793171..c13632bf3 100644 --- a/test_regress/t/t_force_readwrite_unsup.out +++ b/test_regress/t/t_force_readwrite_unsup.out @@ -2,7 +2,4 @@ 19 | cls.take_ref(a); | ^ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_force_readwrite_unsup.v:20:18: Unsupported: Signals used via read-write reference cannot be forced - 20 | cls.take_ref(b); - | ^ %Error: Exiting due to diff --git a/test_regress/t/t_force_unpacked_unsup.out b/test_regress/t/t_force_unpacked_unsup.out deleted file mode 100644 index 3d07924aa..000000000 --- a/test_regress/t/t_force_unpacked_unsup.out +++ /dev/null @@ -1,14 +0,0 @@ -%Error-UNSUPPORTED: t/t_force_unpacked_unsup.v:26:7: Unsupported: Force of variable with >= 1000 unpacked elements - 26 | bit big_array[40][40][40]; - | ^~~~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_force_unpacked_unsup.v:25:12: Unsupported: Force of variable with >= 1000 unpacked elements - 25 | struct_t s_array[3000]; - | ^~~~~~~ -%Error-UNSUPPORTED: t/t_force_unpacked_unsup.v:27:11: Forcing variable of unsupported type: REFDTYPE 'union_t' - 27 | union_t my_union; - | ^~~~~~~~ -%Error: Internal Error: t/t_force_unpacked_unsup.v:27:11: ../V3Force.cpp:#: Unsupported: Force of variable of unhandled data type - 27 | union_t my_union; - | ^~~~~~~~ - ... This fatal error may be caused by the earlier error(s); resolve those first. diff --git a/test_regress/t/t_force_unpacked_unsup.v b/test_regress/t/t_force_unpacked_unsup.v deleted file mode 100644 index 225f2cb2d..000000000 --- a/test_regress/t/t_force_unpacked_unsup.v +++ /dev/null @@ -1,84 +0,0 @@ -// 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 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) -`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); -// verilog_format: on - -module t ( - input clk -); - - integer cyc = 0; - - typedef struct {int x;} struct_t; - typedef union { - int x; - logic y; - } union_t; - - struct_t s_array[3000]; - bit big_array[40][40][40]; - union_t my_union; - - // Test loop - always @(posedge clk) begin - cyc <= cyc + 1; - if (cyc == 0) begin - big_array[1][2][3] <= 1; - s_array[1].x <= 1; - my_union.x <= 1; - end - else if (cyc == 1) begin - `checkr(big_array[1][2][3], 1); - `checkh(s_array[1].x, 1); - `checkh(my_union.x, 1); - end - else if (cyc == 2) begin - force big_array[1][2][3] = 0; - force s_array[1].x = 0; - force my_union.x = 0; - end - else if (cyc == 3) begin - `checkr(big_array[1][2][3], 0); - big_array[1][2][3] <= 1; - `checkh(s_array[1].x, 0); - s_array[1].x <= 1; - `checkh(my_union.x, 0); - my_union.x <= 1; - end - else if (cyc == 4) begin - `checkr(big_array[1][2][3], 0); - `checkh(s_array[1].x, 0); - `checkh(my_union.x, 0); - end - else if (cyc == 5) begin - release big_array[1][2][3]; - release s_array[1].x; - release my_union.x; - end - else if (cyc == 6) begin - `checkr(big_array[1][2][3], 0); - big_array[1][2][3] <= 1; - `checkh(s_array[1].x, 0); - s_array[1].x <= 1; - `checkh(my_union.x, 0); - my_union.x <= 1; - end - else if (cyc == 7) begin - `checkr(big_array[1][2][3], 1); - `checkh(s_array[1].x, 1); - `checkh(my_union.x, 1); - end - else if (cyc == 8) begin - $write("*-* All Finished *-*\n"); - $finish; - end - end - -endmodule diff --git a/test_regress/t/t_forceable_public_flat.py b/test_regress/t/t_forceable_public_flat.py index f18943111..8a355eab7 100755 --- a/test_regress/t/t_forceable_public_flat.py +++ b/test_regress/t/t_forceable_public_flat.py @@ -14,7 +14,6 @@ test.scenarios('vlt') test.compile() files = test.glob_some(test.obj_dir + "/" + test.vm_prefix + "*.h") -test.file_grep_any(files, r' u_sub__DOT__a__VforceRd') test.file_grep_any(files, r' u_sub__DOT__a__VforceEn') test.file_grep_any(files, r' u_sub__DOT__a__VforceVal') diff --git a/test_regress/t/t_forceable_public_flat.v b/test_regress/t/t_forceable_public_flat.v index e5b842be6..711bd3859 100644 --- a/test_regress/t/t_forceable_public_flat.v +++ b/test_regress/t/t_forceable_public_flat.v @@ -25,7 +25,7 @@ module t ( endmodule module sub ( - input a /* verilator forceable */ /* verilator public_flat */, + input a /* verilator forceable */ /* verilator public_flat */, input b, output c ); diff --git a/test_regress/t/t_forceable_string_bad.out b/test_regress/t/t_forceable_string_bad.out index 6735c1393..6fb21ca01 100644 --- a/test_regress/t/t_forceable_string_bad.out +++ b/test_regress/t/t_forceable_string_bad.out @@ -2,8 +2,4 @@ 8 | string str /*verilator forceable*/; | ^~~ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error-UNSUPPORTED: t/t_forceable_string_bad.v:8:10: Forcing variable of unsupported type: BASICDTYPE 'string' - 8 | string str /*verilator forceable*/; - | ^~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_verilated_all.v b/test_regress/t/t_verilated_all.v index 2d706bdd6..b9bc4ff61 100644 --- a/test_regress/t/t_verilated_all.v +++ b/test_regress/t/t_verilated_all.v @@ -17,7 +17,7 @@ module t ( int cyc; integer rand_result; integer seed = 123; - + integer frc; always @(posedge clk) begin cyc <= cyc + 1; if (cyc != 0) begin @@ -27,6 +27,7 @@ module t ( c = new; rand_result = c.randomize(); $display("rand: %x x: %x ", rand_result, c.x); // Get verilated_random.cpp + force frc=42; // Get verilated_force.h $write("*-* All Finished *-*\n"); $finish; end