From bc86701bec9ece1b898cccf60051ea072d6700f2 Mon Sep 17 00:00:00 2001 From: Nikolai Kumar Date: Fri, 5 Jun 2026 19:43:06 -0500 Subject: [PATCH] Support forceable on unpacked array variables (#7677) (#7678) Fixes #7677. --- include/verilated.cpp | 12 +- src/V3Force.cpp | 182 ++++++++++++++++++-- test_regress/t/t_forceable_unpacked.cpp | 50 ++++++ test_regress/t/t_forceable_unpacked.v | 73 ++++++++ test_regress/t/t_forceable_unpacked.vlt | 9 + test_regress/t/t_forceable_unpacked_bad.out | 6 +- test_regress/t/t_forceable_unpacked_bad.v | 2 +- test_regress/t/t_forceable_unpacked_cmt.py | 22 +++ test_regress/t/t_forceable_unpacked_vlt.py | 23 +++ 9 files changed, 361 insertions(+), 18 deletions(-) create mode 100644 test_regress/t/t_forceable_unpacked.cpp create mode 100644 test_regress/t/t_forceable_unpacked.v create mode 100644 test_regress/t/t_forceable_unpacked.vlt create mode 100755 test_regress/t/t_forceable_unpacked_cmt.py create mode 100755 test_regress/t/t_forceable_unpacked_vlt.py diff --git a/include/verilated.cpp b/include/verilated.cpp index 8c2896324..6bf868cad 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -4036,8 +4036,10 @@ VerilatedScope::forceableVarInsert(const char* namep, void* datap, bool isParam, va_list ap; va_start(ap, pdims); - assert(udims == 0); // Forcing unpacked arrays is unsupported (#4735) and should have been - // checked in V3Force already. + for (int i = 0; i < udims; ++i) { + forceReadSignal.m_unpacked[i].m_left = va_arg(ap, int); + forceReadSignal.m_unpacked[i].m_right = va_arg(ap, int); + } for (int i = 0; i < pdims; ++i) { const int msb = va_arg(ap, int); const int lsb = va_arg(ap, int); @@ -4055,8 +4057,10 @@ VerilatedScope::forceableVarInsert(const char* namep, void* datap, bool isParam, verilatedForceControlSignalsp = nullptr; va_start(ap, pdims); - assert(udims == 0); // Forcing unpacked arrays is unsupported (#4735) and should have been - // checked in V3Force already. + for (int i = 0; i < udims; ++i) { + var.m_unpacked[i].m_left = va_arg(ap, int); + var.m_unpacked[i].m_right = va_arg(ap, int); + } for (int i = 0; i < pdims; ++i) { const int msb = va_arg(ap, int); const int lsb = va_arg(ap, int); diff --git a/src/V3Force.cpp b/src/V3Force.cpp index ab2fda534..8298ba6e6 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -251,6 +251,40 @@ public: return varRefp; } + + static AstNodeExpr* buildNestedArraySel(FileLine* flp, AstNodeExpr* fromp, + const std::vector& indicies) { + AstNodeExpr* curp = fromp; + for (const int idx : indicies) curp = new AstArraySel{flp, curp, idx}; + return curp; + } + + template + static AstNodeStmt* foreachUnpackedLeaf(const std::vector& dims, + Fn buildLeaf) { + AstNodeStmt* headp = nullptr; + AstNodeStmt* tailp = nullptr; + if (dims.empty()) return nullptr; + int total = 1; + for (const AstUnpackArrayDType* const d : dims) total *= d->elementsConst(); + if (total <= 0) return nullptr; + std::vector idx(dims.size(), 0); + for (int flat = 0; flat < total; ++flat) { + AstNodeStmt* const stmtp = buildLeaf(idx, flat); + if (!headp) { + headp = stmtp; + } else { + tailp->addNext(stmtp); + } + tailp = stmtp; + for (int d = static_cast(dims.size()) - 1; d >= 0; --d) { + if (++idx[d] < dims[d]->elementsConst()) break; + idx[d] = 0; + } + } + return headp; + } + ForceRangeInfo getForceRangeInfo(AstNodeExpr* lhsp, AstVar* varp, bool requireConstRangeSelect) { ForceRangeInfo info; @@ -349,12 +383,16 @@ public: 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(); + AstVar* const varp = varInfo.m_varVscp->varp(); + if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) { + return createForceRdUpdateStmtUnpacked(varInfo); + } 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())) { + if (isBitwiseDType(varp)) { readExprp = new AstOr{ flp, new AstAnd{flp, enRefp, valRefp}, new AstAnd{flp, new AstNot{flp, enRefp->cloneTreePure(false)}, baseRefp}}; @@ -366,6 +404,30 @@ public: readExprp}; } + AstNodeStmt* createForceRdUpdateStmtUnpacked(const VarForceInfo& varInfo) const { + FileLine* const flp = varInfo.m_varVscp->fileline(); + AstVar* const varp = varInfo.m_varVscp->varp(); + AstUnpackArrayDType* const arrDtypep + = VN_AS(varp->dtypep()->skipRefp(), UnpackArrayDType); + const std::vector dims = arrDtypep->unpackDimensions(); + return foreachUnpackedLeaf( + dims, [&](const std::vector& idx, int /*flat*/) -> AstNodeStmt* { + AstVarRef* const baseRefp = new AstVarRef{flp, varInfo.m_varVscp, VAccess::READ}; + markNonReplaceable(baseRefp); + AstNodeExpr* const baseSelp = buildNestedArraySel(flp, baseRefp, idx); + AstNodeExpr* const enSelp = buildNestedArraySel( + flp, new AstVarRef{flp, varInfo.m_forceEnVscp, VAccess::READ}, idx); + AstNodeExpr* const valSelp = buildNestedArraySel( + flp, new AstVarRef{flp, varInfo.m_forceValVscp, VAccess::READ}, idx); + AstNodeExpr* const readExprp = new AstOr{ + flp, new AstAnd{flp, enSelp, valSelp}, + new AstAnd{flp, new AstNot{flp, enSelp->cloneTreePure(false)}, baseSelp}}; + AstNodeExpr* const rdLhsSelp = buildNestedArraySel( + flp, new AstVarRef{flp, varInfo.m_forceRdVscp, VAccess::WRITE}, idx); + return new AstAssign{flp, rdLhsSelp, readExprp}; + }); + } + VarForceInfo& getOrCreateVarInfo(AstVar* varp) { const auto it = m_varToId.find(varp); if (it != m_varToId.end()) return m_varInfos[it->second]; @@ -568,11 +630,25 @@ public: 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}); + AstNodeStmt* initStmtp = nullptr; + if (AstUnpackArrayDType* const arrDtypep + = VN_CAST(varp->dtypeSkipRefp(), UnpackArrayDType)) { + const std::vector dims + = arrDtypep->unpackDimensions(); + const int innerWidth = dims.back()->subDTypep()->skipRefp()->width(); + initStmtp = foreachUnpackedLeaf( + dims, [&](const std::vector& idx, int /*flat*/) -> AstNodeStmt* { + AstNodeExpr* const lhsp = buildNestedArraySel( + flp, new AstVarRef{flp, info.m_forceEnVscp, VAccess::WRITE}, idx); + return new AstAssign{flp, lhsp, makeZeroConst(varp, innerWidth)}; + }); + } else { + initStmtp = new AstAssign{ + flp, new AstVarRef{flp, info.m_forceEnVscp, VAccess::WRITE}, + makeZeroConst(varp, info.m_forceEnVscp->width())}; + } + initStmtp->addNext(createForceRdUpdateStmt(info)); + activeInitp->addStmtsp(new AstInitial{flp, initStmtp}); scopep->addBlocksp(activeInitp); AstSenItem* itemsp = nullptr; @@ -699,6 +775,92 @@ class ForceDiscoveryVisitor final : public VNVisitorConst { ForceState& m_state; bool m_inClockedActive = false; + void buildForceableUnpackedArray(AstVarScope* const nodep, + AstUnpackArrayDType* const arrDtypep) { + AstVar* const varp = nodep->varp(); + const std::vector dims = arrDtypep->unpackDimensions(); + UASSERT_OBJ(!dims.empty(), varp, + "buildForceableUnpackedArray called with non-unpacked dtype"); + const AstNodeDType* const leafDtypep = dims.back()->subDTypep()->skipRefp(); + const AstBasicDType* const innerBasicp = leafDtypep->basicp(); + const bool innerBitwise = innerBasicp && !innerBasicp->isDouble() + && !innerBasicp->isString() && !innerBasicp->isOpaque(); + if (!innerBitwise) { + varp->v3warn(E_UNSUPPORTED, + "Unsupported: Forcing unpacked arrays of non-bitwise inner type: " + << varp->name()); // (#4735) + return; + } + + FileLine* const flp = varp->fileline(); + const int innerWidth = leafDtypep->width(); + + AstVar* const rdVarp + = new AstVar{flp, VVarType::WIRE, varp->name() + "__VforceRd", varp->dtypep()}; + rdVarp->noSubst(true); + rdVarp->sigPublic(true); + AstVar* const enVarp + = new AstVar{flp, VVarType::WIRE, varp->name() + "__VforceEn", varp->dtypep()}; + enVarp->sigUserRWPublic(true); + AstVar* const valVarp + = new AstVar{flp, VVarType::WIRE, 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); + + ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(varp); + info.m_forceRdVscp = rdVscp; + info.m_forceEnVscp = enVscp; + info.m_forceValVscp = valVscp; + info.m_varVscp = nodep; + varp->user3p(rdVscp); + varp->user4p(enVscp); + nodep->user3p(valVscp); + + 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()); + + AstNodeStmt* const alwaysBodyHeadp = ForceState::foreachUnpackedLeaf( + dims, [&](const std::vector& idx, int flat) -> AstNodeStmt* { + AstVarRef* const origRefp = new AstVarRef{flp, nodep, VAccess::READ}; + ForceState::markNonReplaceable(origRefp); + AstNodeExpr* const origSelp = ForceState::buildNestedArraySel(flp, origRefp, idx); + AstNodeExpr* const enSelp = ForceState::buildNestedArraySel( + flp, new AstVarRef{flp, enVscp, VAccess::READ}, idx); + AstNodeExpr* const valSelp = ForceState::buildNestedArraySel( + flp, new AstVarRef{flp, valVscp, VAccess::READ}, idx); + AstNodeExpr* const forceExprp = new AstOr{ + flp, new AstAnd{flp, enSelp, valSelp}, + new AstAnd{flp, new AstNot{flp, enSelp->cloneTreePure(false)}, origSelp}}; + AstNodeExpr* const lhsSelp = ForceState::buildNestedArraySel( + flp, new AstVarRef{flp, nodep, VAccess::WRITE}, idx); + + AstAssignForce* const forceAssignp = new AstAssignForce{flp, lhsSelp, forceExprp}; + forceAssignp->user2(true); + + AstNodeExpr* const rhsClonep = forceExprp->cloneTreePure(false); + rhsClonep->foreach([varp](AstVarRef* const r) { + if (r->varp() == varp) ForceState::markNonReplaceable(r); + }); + m_state.addForceAssignment(varp, nodep, rhsClonep, forceAssignp, + /*rangeLsb=*/flat, /*rangeMsb=*/flat, + /*padLsb=*/0, /*padMsb=*/innerWidth - 1, + /*hasArraySel=*/true); + return forceAssignp; + }); + activep->addStmtsp(new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, alwaysBodyHeadp}); + nodep->scopep()->addBlocksp(activep); + } + void visit(AstAssignForce* nodep) override { if (nodep->user2()) return; // External force statements are pre-registered. UINFO(2, "Discovering force statement: " << nodep << "\n"); @@ -779,10 +941,10 @@ class ForceDiscoveryVisitor final : public VNVisitorConst { } } - if (VN_IS(nodep->varp()->dtypeSkipRefp(), UnpackArrayDType)) { - nodep->varp()->v3warn( - E_UNSUPPORTED, - "Unsupported: Forcing unpacked arrays: " << nodep->varp()->name()); // (#4735) + if (AstUnpackArrayDType* const arrDtypep + = VN_CAST(nodep->varp()->dtypeSkipRefp(), UnpackArrayDType)) { + buildForceableUnpackedArray(nodep, arrDtypep); + iterateChildrenConst(nodep); return; } diff --git a/test_regress/t/t_forceable_unpacked.cpp b/test_regress/t/t_forceable_unpacked.cpp new file mode 100644 index 000000000..d5cfd9d0b --- /dev/null +++ b/test_regress/t/t_forceable_unpacked.cpp @@ -0,0 +1,50 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Nikolai Kumar +// SPDX-License-Identifier: CC0-1.0 + +#include "verilatedos.h" + +#include "verilated.h" + +#include +#include VM_PREFIX_INCLUDE +#include VM_PREFIX_ROOT_INCLUDE + +int main(int argc, char** argv) { + const std::unique_ptr contextp{new VerilatedContext}; + const std::unique_ptr topp{new VM_PREFIX{"top"}}; + auto& en = topp->rootp->t__DOT__var_arr__VforceEn; + auto& val = topp->rootp->t__DOT__var_arr__VforceVal; + auto& en_a = topp->rootp->t__DOT__var_arr_a__VforceEn; + auto& val_a = topp->rootp->t__DOT__var_arr_a__VforceVal; + while (!contextp->gotFinish()) { + topp->clk = !topp->clk; + topp->eval(); + if (topp->clk) { + bool changed = true; + switch (topp->cyc) { + case 1: + en[0][1] = 0x3; val[0][1] = 0x3; + en_a[0][1] = 0x3; val_a[0][1] = 0x2; + break; + case 2: + en[1][0] = 0x3; val[1][0] = 0x2; + en_a[1][0] = 0x3; val_a[1][0] = 0x3; + break; + case 3: + en[0][1] = 0x0; en_a[0][1] = 0x0; + break; + case 4: + en[1][0] = 0x0; en_a[1][0] = 0x0; + break; + + default: changed = false; + } + if (changed) topp->eval(); + } + contextp->timeInc(5); + } + return 0; +} diff --git a/test_regress/t/t_forceable_unpacked.v b/test_regress/t/t_forceable_unpacked.v new file mode 100644 index 000000000..5016bdbef --- /dev/null +++ b/test_regress/t/t_forceable_unpacked.v @@ -0,0 +1,73 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Nikolai Kumar +// SPDX-License-Identifier: CC0-1.0 + +`define checkh(g,e) do if ((g) !==(e)) begin $write("%%Error: %s:%0d: got=%x exp=%x\n", `__FILE__,`__LINE__, (g),(e)); $stop; end while(0) + +`ifdef CMT + `define FORCEABLE /*verilator forceable*/ +`else + `define FORCEABLE +`endif +module t (input wire clk, output reg [31:0] cyc); + initial cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + reg [4:3] var_arr [7:6][5:4] `FORCEABLE; + //verilator lint_off ASCRANGE + reg [3:4] var_arr_a [6:7][4:5] `FORCEABLE; + + initial begin + var_arr[6][4] = 2'h1; var_arr[6][5] = 2'h2; + var_arr[7][4] = 2'h3; var_arr[7][5] = 2'h2; + var_arr_a[6][4] = 2'h2; var_arr_a[6][5] = 2'h1; + var_arr_a[7][4] = 2'h1; var_arr_a[7][5] = 2'h3; + end + + always @(posedge clk) case (cyc) + 0: begin + `checkh(var_arr[6][4], 2'h1); + `checkh(var_arr[6][5], 2'h2); + `checkh(var_arr[7][4], 2'h3); + `checkh(var_arr[7][5], 2'h2); + `checkh(var_arr_a[6][4], 2'h2); + `checkh(var_arr_a[6][5], 2'h1); + `checkh(var_arr_a[7][4], 2'h1); + `checkh(var_arr_a[7][5], 2'h3); + end + 1: begin + `checkh(var_arr[6][5], 2'h3); + `checkh(var_arr_a[6][5], 2'h2); + `checkh(var_arr[6][4], 2'h1); + `checkh(var_arr[7][4], 2'h3); + `checkh(var_arr_a[6][4], 2'h2); + `checkh(var_arr_a[7][4], 2'h1); + end + 2: begin + `checkh(var_arr[6][5], 2'h3); + `checkh(var_arr[7][4], 2'h2); + `checkh(var_arr_a[6][5], 2'h2); + `checkh(var_arr_a[7][4], 2'h3); + end + 3: begin + `checkh(var_arr[6][5], 2'h2); + `checkh(var_arr[7][4], 2'h2); + `checkh(var_arr_a[6][5], 2'h1); + `checkh(var_arr_a[7][4], 2'h3); + end + 4: begin + `checkh(var_arr[6][4], 2'h1); + `checkh(var_arr[6][5], 2'h2); + `checkh(var_arr[7][4], 2'h3); + `checkh(var_arr[7][5], 2'h2); + `checkh(var_arr_a[6][4], 2'h2); + `checkh(var_arr_a[6][5], 2'h1); + `checkh(var_arr_a[7][4], 2'h1); + `checkh(var_arr_a[7][5], 2'h3); + $write("*-* All Finished *-*\n"); + $finish; + end + endcase +endmodule diff --git a/test_regress/t/t_forceable_unpacked.vlt b/test_regress/t/t_forceable_unpacked.vlt new file mode 100644 index 000000000..7be6ca7b1 --- /dev/null +++ b/test_regress/t/t_forceable_unpacked.vlt @@ -0,0 +1,9 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Nikolai Kumar +// SPDX-License-Identifier: CC0-1.0 + +`verilator_config + +forceable -module "*" -var "var_*" diff --git a/test_regress/t/t_forceable_unpacked_bad.out b/test_regress/t/t_forceable_unpacked_bad.out index cdbd0181e..baedfde19 100644 --- a/test_regress/t/t_forceable_unpacked_bad.out +++ b/test_regress/t/t_forceable_unpacked_bad.out @@ -1,5 +1,5 @@ -%Error-UNSUPPORTED: t/t_forceable_unpacked_bad.v:8:9: Unsupported: Forcing unpacked arrays: t__DOT__unpacked - 8 | logic unpacked[1] /*verilator forceable*/; - | ^~~~~~~~ +%Error-UNSUPPORTED: t/t_forceable_unpacked_bad.v:8:8: Unsupported: Forcing unpacked arrays of non-bitwise inner type: t__DOT__unpacked + 8 | real unpacked[1] /*verilator forceable*/; + | ^~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_forceable_unpacked_bad.v b/test_regress/t/t_forceable_unpacked_bad.v index d48cc7e51..c100c487c 100644 --- a/test_regress/t/t_forceable_unpacked_bad.v +++ b/test_regress/t/t_forceable_unpacked_bad.v @@ -5,5 +5,5 @@ // ====================================================================== module t; - logic unpacked[1] /*verilator forceable*/; + real unpacked[1] /*verilator forceable*/; endmodule diff --git a/test_regress/t/t_forceable_unpacked_cmt.py b/test_regress/t/t_forceable_unpacked_cmt.py new file mode 100755 index 000000000..ca1b5e9ab --- /dev/null +++ b/test_regress/t/t_forceable_unpacked_cmt.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.pli_filename = "t/t_forceable_unpacked.cpp" +test.top_filename = "t/t_forceable_unpacked.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=['-DCMT=1', '--exe', test.pli_filename]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_forceable_unpacked_vlt.py b/test_regress/t/t_forceable_unpacked_vlt.py new file mode 100755 index 000000000..cf436261d --- /dev/null +++ b/test_regress/t/t_forceable_unpacked_vlt.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.pli_filename = "t/t_forceable_unpacked.cpp" +test.top_filename = "t/t_forceable_unpacked.v" + +test.compile( + make_top_shell=False, + make_main=False, + verilator_flags2=['--exe', test.pli_filename, test.t_dir + "/t_forceable_unpacked.vlt"]) + +test.execute() + +test.passes()