diff --git a/include/verilated_force.h b/include/verilated_force.h index 0a65a8b35..2767200e7 100644 --- a/include/verilated_force.h +++ b/include/verilated_force.h @@ -158,22 +158,21 @@ private: 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); + static void applyEntry(WDataOutP reswp, const Entry& entry, int entryLsb, int entryMsb, + int lsbOffset) { + const int resLsb = entryLsb - lsbOffset; + const int resMsb = entryMsb - lsbOffset; + const int lword = VL_BITWORD_E(resLsb); + const int hword = VL_BITWORD_E(resMsb); 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 segLsb = std::max(resLsb, wordLsb); + const int segMsb = std::min(resMsb, wordLsb + VL_EDATASIZE - 1); const int segWidth = segMsb - segLsb + 1; const int bitOffset = segLsb - wordLsb; - const int rhsLsb = segLsb - entry.m_rhsLsb; + const int rhsLsb = lsbOffset + segLsb - entry.m_rhsLsb; reswp[word] = applyBits(reswp[word], entry, bitOffset, segWidth, rhsLsb); } - return result; } template @@ -196,6 +195,29 @@ private: return *static_cast*>(entry.m_rhsDatap); } + template + typename std::enable_if::value>::type applyEntries(T& val) const { + for (const auto& entry : m_entries) { + applyEntry(val.data(), entry, entry.m_lsb, entry.m_msb, 0); + } + } + + template + typename std::enable_if::value>::type applyEntries(T& val) const { + for (const auto& entry : m_entries) val = applyEntry(val, entry); + } + + void readSel(int lbits, WDataInP valp, WDataOutP reswp, int lsb, int width) const { + VL_SEL_WWII(width, lbits, reswp, valp, lsb, width); + const int msb = lsb + width - 1; + 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) { + applyEntry(reswp, *it, std::max(it->m_lsb, lsb), std::min(it->m_msb, msb), lsb); + ++it; + } + } + public: VlForceVec() = default; @@ -219,8 +241,7 @@ public: } return val; } - - for (const auto& entry : m_entries) { val = applyEntry(val, entry); } + applyEntries(val); return val; } @@ -238,6 +259,29 @@ public: return origVal; } + IData readSelI(int lbits, WDataInP valp, int lsb, int width) const { + IData result; + readSel(lbits, valp, &result, lsb, width); + result &= VL_MASK_I(width); + return result; + } + + QData readSelQ(int lbits, WDataInP valp, int lsb, int width) const { + QData result; + readSel(lbits, valp, reinterpret_cast(&result), lsb, width); + result &= VL_MASK_Q(width); + return result; + } + + template + VlWide readSelW(int lbits, WDataInP valp, int lsb, int width) const { + VlWide result; + WDataOutP const reswp = result.data(); + readSel(lbits, valp, reswp, lsb, width); + reswp[N_Words - 1] &= VL_MASK_E(width); + return result; + } + void addForce(int lsb, int msb, const void* rhsDatap, int rhsLsb) { assert(lsb <= msb); assert(rhsDatap); diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 69c2f909f..834b4e4d4 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -815,6 +815,7 @@ public: FORCE_ADD, FORCE_READ, FORCE_READ_INDEX, + FORCE_READ_SEL, FORCE_RELEASE, FORCE_TOUCH, FORK_DONE, @@ -968,6 +969,7 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) { {FORCE_ADD, "addForce", false}, \ {FORCE_READ, "read", true}, \ {FORCE_READ_INDEX, "readIndex", true}, \ + {FORCE_READ_SEL, "readSel", true}, \ {FORCE_RELEASE, "release", false}, \ {FORCE_TOUCH, "touch", false}, \ {FORK_DONE, "done", false}, \ diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 1427f096b..184046f70 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -723,6 +723,10 @@ public: iterateConst(nodep->fromp()); putns(nodep, nodep->usePtr() ? "->" : "."); putns(nodep, nodep->name()); + if (nodep->method() == VCMethod::FORCE_READ_SEL) { + emitIQW(nodep); + if (nodep->isWide()) puts("<" + cvtToStr(nodep->dtypep()->widthWords()) + ">"); + } puts("("); bool comma = false; int argNum = 0; diff --git a/src/V3Force.cpp b/src/V3Force.cpp index 8e0e7a584..7764cffcd 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -1088,6 +1088,33 @@ class ForceReplaceVisitor final : public VNVisitor { iterateLogic(nodep); } void visit(AstSenItem* nodep) override { iterateLogic(nodep); } + void visit(AstSel* nodep) override { + // Replace Sel on a wide with readSelI/Q/W to avoid materializing the full value + if (AstVarRef* const refp = VN_CAST(nodep->fromp(), VarRef)) { + if (!ForceState::isNotReplaceable(refp) && refp->access().isReadOnly()) { + AstVar* const varp = refp->varp(); + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + if (varInfo && !varInfo->m_forceRdVscp && !varInfo->m_forces.empty() + && ForceState::isBitwiseDType(varp) && varp->dtypep()->isWide()) { + FileLine* const flp = nodep->fileline(); + ForceState::markNonReplaceable(refp); + AstVarRef* const refClonep = refp->cloneTreePure(false); + ForceState::markNonReplaceable(refClonep); + AstCMethodHard* const callp = new AstCMethodHard{ + flp, new AstVarRef{flp, varInfo->m_forceVecVscp, VAccess::READ}, + VCMethod::FORCE_READ_SEL, ForceState::makeConst32(flp, varp->width())}; + callp->addPinsp(refClonep); + callp->addPinsp(nodep->lsbp()->cloneTreePure(false)); + callp->addPinsp(ForceState::makeConst32(flp, nodep->width())); + callp->dtypeFrom(nodep); + nodep->replaceWith(callp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } + } + visit(static_cast(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; diff --git a/test_regress/t/t_force_wide_sel.py b/test_regress/t/t_force_wide_sel.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_force_wide_sel.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_wide_sel.v b/test_regress/t/t_force_wide_sel.v new file mode 100644 index 000000000..2b88c7a14 --- /dev/null +++ b/test_regress/t/t_force_wide_sel.v @@ -0,0 +1,42 @@ +// 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 [127:0] sig; + + always @(posedge clk) begin + cyc <= cyc + 1; + sig <= '0; + + sig[127:64] <= '0; // write path + + if (cyc == 1) begin + force sig[31] = 1'b1; + force sig[32] = 1'b1; + end + else if (cyc == 3) begin + `checkh(sig[33:26], 8'h60); // width <= 8 + `checkh(sig[39:24], 16'h180); // 8 < width <= 16 + `checkh(sig[40:20], 21'h1800); // 16 < width <= 32 + `checkh(sig[51:20], 32'h1800); + `checkh(sig[29:0], 30'h0); + `checkh(sig[50:10], 41'h600000); // 32 < width <= 64 + `checkh(sig[73:10], 64'h600000); + `checkh(sig[100:5], (96'h1 << 26) | (96'h1 << 27)); // width > 64 + `checkh(sig[70:6], (65'h1 << 25) | (65'h1 << 26)); + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule