Support functions on RHS of force (#7491)

Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
This commit is contained in:
Artur Bieniek 2026-04-30 16:38:07 +02:00 committed by GitHub
parent dd75c4cd1b
commit ec03edcddd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 44 additions and 9 deletions

View File

@ -434,6 +434,7 @@ class TaskVisitor final : public VNVisitor {
int m_unconVarNum = 0; // Unique bad connection variable
// STATE - across all visitors
V3UniqueNames m_forceTmpNames; // For generating unique force-RHS helper names
DpiCFuncs m_dpiNames; // Map of all created DPI functions
VDouble0 m_statInlines; // Statistic tracking
VDouble0 m_statHierDpisWithCosts; // Statistic tracking
@ -1784,6 +1785,47 @@ class TaskVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
void visit(AstAssignForce* nodep) override {
// Force statements cannot be converted to always blocks outside of a logic block
// This causes function calls on RHS of force assignments to be improperly inlined and
// called just once. To prevent this, we create a temporary variable for each function
// reference on RHS. This variable is declared in the nearest scope and gets a continuous
// assignment of the function, so it can be converted to always and properly inlined This
// temporary variable becomes the RHS of the force assignment
std::vector<AstNodeFTaskRef*> refs;
nodep->rhsp()->foreach([&refs](AstNodeFTaskRef* refp) { refs.push_back(refp); });
for (AstNodeFTaskRef* const refp : refs) {
// Create the temporary variable and its scope
// Replicate the logic from V3Task, every function call gets
// a unique temp variable
AstVar* const interVarp = new AstVar{
nodep->fileline(), VVarType::VAR,
refp->name() + "__Vforcefuncout" + m_forceTmpNames.get(nodep), refp->dtypep()};
UASSERT_OBJ(m_modp->stmtsp(), m_modp, "Module should have statements in it");
m_modp->stmtsp()->addHereThisAsNext(interVarp);
AstVarScope* const interVscp = new AstVarScope{refp->fileline(), m_scopep, interVarp};
m_scopep->addVarsp(interVscp);
// Recompute the helper in a combo block so any inlined function body stays
// inside schedulable logic rather than spilling statements at module scope.
AstAssign* const assignp = new AstAssign{
nodep->fileline(), new AstVarRef{nodep->fileline(), interVscp, VAccess::WRITE},
nodep->rhsp()->cloneTreePure(false)};
AstSenTree* const senTreep = new AstSenTree{
nodep->fileline(), new AstSenItem{nodep->fileline(), AstSenItem::Combo{}}};
AstActive* const activep
= new AstActive{nodep->fileline(), "force-func-update", senTreep};
activep->senTreeStorep(activep->sentreep());
activep->addStmtsp(
new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS, nullptr, assignp});
m_scopep->addBlocksp(activep);
// Replace RHS of force assignment with the temporary variable
refp->replaceWith(new AstVarRef{nodep->fileline(), interVscp, VAccess::READ});
VL_DO_DANGLING(refp->deleteTree(), refp);
}
}
void visit(AstNodeForeach* nodep) override { // LCOV_EXCL_LINE
nodep->v3fatalSrc(
"Foreach statements should have been converted to while statements in V3Begin.cpp");

View File

@ -1,4 +0,0 @@
%Error: t/t_force_func.v:29: got='h0 exp='h00000001
%Error: t/t_force_func.v:37: got='h0 exp='h00000002
%Error: t/t_force_func.v:42: got='h0 exp='h00000003
%Error: t/t_force_func.v:46: got='h0 exp='h00000003

View File

@ -13,6 +13,6 @@ test.scenarios('vlt')
test.compile(verilator_flags2=["--binary"])
test.execute(expect_filename=test.golden_filename)
test.execute()
test.passes()

View File

@ -5,7 +5,7 @@
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop // TODO
`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
@ -31,9 +31,6 @@ module t;
a = 2;
#1;
`checkh(a, 2);
// TODO
// IEEE 1800-2023 10.6
// Assignment shall be reevaluated while the assign or force is in effect.
`checkh(b, 2);
a = 3;