Support forceable on unpacked array variables (#7677) (#7678)

Fixes #7677.
This commit is contained in:
Nikolai Kumar 2026-06-05 19:43:06 -05:00 committed by GitHub
parent 816ab67826
commit bc86701bec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 361 additions and 18 deletions

View File

@ -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);

View File

@ -251,6 +251,40 @@ public:
return varRefp;
}
static AstNodeExpr* buildNestedArraySel(FileLine* flp, AstNodeExpr* fromp,
const std::vector<int>& indicies) {
AstNodeExpr* curp = fromp;
for (const int idx : indicies) curp = new AstArraySel{flp, curp, idx};
return curp;
}
template <typename Fn>
static AstNodeStmt* foreachUnpackedLeaf(const std::vector<AstUnpackArrayDType*>& 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<int> 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<int>(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<AstUnpackArrayDType*> dims = arrDtypep->unpackDimensions();
return foreachUnpackedLeaf(
dims, [&](const std::vector<int>& 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<AstUnpackArrayDType*> dims
= arrDtypep->unpackDimensions();
const int innerWidth = dims.back()->subDTypep()->skipRefp()->width();
initStmtp = foreachUnpackedLeaf(
dims, [&](const std::vector<int>& 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<AstUnpackArrayDType*> 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<int>& 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;
}

View File

@ -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 <memory>
#include VM_PREFIX_INCLUDE
#include VM_PREFIX_ROOT_INCLUDE
int main(int argc, char** argv) {
const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};
const std::unique_ptr<VM_PREFIX> 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;
}

View File

@ -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

View File

@ -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_*"

View File

@ -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

View File

@ -5,5 +5,5 @@
// ======================================================================
module t;
logic unpacked[1] /*verilator forceable*/;
real unpacked[1] /*verilator forceable*/;
endmodule

View File

@ -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()

View File

@ -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()