Fix rand_mode(0) on sub-object members not preventing solver write-back (#7272)

This commit is contained in:
Yilou Wang 2026-03-17 20:09:14 +01:00 committed by GitHub
parent 316fb02c60
commit 8925762077
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 196 additions and 2 deletions

View File

@ -632,6 +632,7 @@ bool VlRandomizer::parseSolution(std::iostream& os, bool log) {
if (m_randmodep && !varr.randModeIdxNone()) {
if (!m_randmodep->at(varr.randModeIdx())) continue;
}
if (m_disabledVars.count(name)) continue;
if (!indices.empty()) {
std::ostringstream oss;
oss << varr.name();

View File

@ -31,6 +31,7 @@
#include <iomanip>
#include <iostream>
#include <ostream>
#include <set>
#include <sstream>
//=============================================================================
@ -212,7 +213,8 @@ class VlRandomizer VL_NOT_FINAL {
m_constraints_line; // fileline content of the constraint for unsat constraints
std::vector<std::string> m_softConstraints; // Soft constraints
std::map<std::string, std::shared_ptr<const VlRandomVar>> m_vars; // Solver-dependent
// variables
std::set<std::string> m_disabledVars; // Variables with rand_mode off (skip write-back)
// variables
ArrayInfoMap m_arr_vars; // Tracks each element in array structures for iteration
std::vector<std::string> m_unique_arrays;
std::map<std::string, uint32_t> m_unique_array_sizes;
@ -337,6 +339,12 @@ public:
"supported currently.");
}
// Mark a variable as rand_mode-disabled: solver keeps it in m_vars
// (so constraints still reference it) but skips write-back after solving.
void set_var_disabled(const char* name) { m_disabledVars.insert(name); }
// Clear disabled state for a variable
void clear_var_disabled(const char* name) { m_disabledVars.erase(name); }
// --- write_var to register variables ---
// Register scalar variable (non-struct, basic type)
template <typename T>

View File

@ -823,6 +823,8 @@ public:
RANDOMIZER_SOLVE_BEFORE,
RANDOMIZER_PIN_VAR,
RANDOMIZER_WRITE_VAR,
RANDOMIZER_SET_VAR_DISABLED,
RANDOMIZER_CLEAR_VAR_DISABLED,
RNG_GET_RANDSTATE,
RNG_SET_RANDSTATE,
SCHED_ANY_TRIGGERED,
@ -962,6 +964,8 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
{RANDOMIZER_SOLVE_BEFORE, "solveBefore", false}, \
{RANDOMIZER_PIN_VAR, "pin_var", false}, \
{RANDOMIZER_WRITE_VAR, "write_var", false}, \
{RANDOMIZER_SET_VAR_DISABLED, "set_var_disabled", false}, \
{RANDOMIZER_CLEAR_VAR_DISABLED, "clear_var_disabled", false}, \
{RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", true}, \
{RNG_SET_RANDSTATE, "__Vm_rng.set_randstate", false}, \
{SCHED_ANY_TRIGGERED, "anyTriggered", false}, \

View File

@ -4999,6 +4999,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
nodep->taskp(randFuncp);
nodep->classOrPackagep(VN_AS(m_modp, Class));
m_curSymp = m_statep->insertBlock(m_curSymp, nodep->name(), randFuncp, m_modp);
// Already linked to the class randomize function; skip findSymPrefixed
// which may find std::randomize and overwrite classOrPackagep
return;
}
if (m_insideClassExtParam) {
// The reference may point to a method declared in a super class, which is proved

View File

@ -1328,7 +1328,6 @@ class ConstraintExprVisitor final : public VNVisitor {
methodp->addPinsp(varnamep);
methodp->addPinsp(
new AstConst{varp->dtypep()->fileline(), AstConst::Unsized64{}, dimension});
// Don't pass randMode.index for global constraints with membersel
if (randMode.usesMode && !(isGlobalConstrained && membersel)) {
methodp->addPinsp(
new AstConst{varp->fileline(), AstConst::Unsized64{}, randMode.index});
@ -1347,7 +1346,52 @@ class ConstraintExprVisitor final : public VNVisitor {
UASSERT_OBJ(initTaskp, classp, "No new() in class");
}
}
// For globalConstrained sub-object members with rand_mode:
// Always call write_var (keeps variable in solver for constraint
// evaluation), but toggle disabled state so the solver skips
// write-back when rand_mode is off.
initTaskp->addStmtsp(methodp->makeStmt());
if (isGlobalConstrained && membersel && randMode.usesMode) {
AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule);
AstVar* const subRandModeVarp = VN_AS(varClassp->user2p(), Var);
if (subRandModeVarp) {
AstNodeExpr* const parentAccess = membersel->fromp()->cloneTree(false);
AstMemberSel* const randModeSel
= new AstMemberSel{varp->fileline(), parentAccess, subRandModeVarp};
randModeSel->dtypep(subRandModeVarp->dtypep());
AstCMethodHard* const atp
= new AstCMethodHard{varp->fileline(), randModeSel, VCMethod::ARRAY_AT,
new AstConst{varp->fileline(), randMode.index}};
atp->dtypeSetUInt32();
// rand_mode ON: clear disabled state
AstCMethodHard* const enablep = new AstCMethodHard{
varp->fileline(),
new AstVarRef{varp->fileline(), VN_AS(m_genp->user2p(), NodeModule),
m_genp, VAccess::READWRITE},
VCMethod::RANDOMIZER_CLEAR_VAR_DISABLED};
enablep->dtypeSetVoid();
AstNodeExpr* const ennp
= new AstCExpr{varp->fileline(), AstCExpr::Pure{},
"\"" + smtName + "\"", varp->width()};
ennp->dtypep(varp->dtypep());
enablep->addPinsp(ennp);
// rand_mode OFF: set disabled state
AstCMethodHard* const disablep = new AstCMethodHard{
varp->fileline(),
new AstVarRef{varp->fileline(), VN_AS(m_genp->user2p(), NodeModule),
m_genp, VAccess::READWRITE},
VCMethod::RANDOMIZER_SET_VAR_DISABLED};
disablep->dtypeSetVoid();
AstNodeExpr* const disnp
= new AstCExpr{varp->fileline(), AstCExpr::Pure{},
"\"" + smtName + "\"", varp->width()};
disnp->dtypep(varp->dtypep());
disablep->addPinsp(disnp);
AstIf* const ifp = new AstIf{varp->fileline(), atp, enablep->makeStmt(),
disablep->makeStmt()};
initTaskp->addStmtsp(ifp);
}
}
// If randc, also emit markRandc() for cyclic tracking
if (varp->isRandC()) {
AstCMethodHard* const markp = new AstCMethodHard{
@ -3087,6 +3131,29 @@ class RandomizeVisitor final : public VNVisitor {
}
}
});
// Also account for rand_mode indices from globalConstrained sub-objects.
// When constraints from sub-objects are flattened into this class's constraint
// setup functions, the generated code references this->__Vrandmode.at(index).
// We must ensure this class's __Vrandmode is initialized to cover those indices.
{
std::function<void(AstClass*)> findSubObjRandModes = [&](AstClass* subClassp) {
subClassp->foreachMember([&](AstClass*, AstNode* subMemberp) {
if (AstVar* const subVarp = VN_CAST(subMemberp, Var)) {
const RandomizeMode rm = {.asInt = subVarp->user1()};
if (!rm.usesMode) return;
const uint32_t needed = rm.index + 1;
if (needed > randModeCount) randModeCount = needed;
}
});
};
classp->foreachMember([&](AstClass*, AstVar* memberVarp) {
if (!memberVarp->globalConstrained()) return;
const AstNodeDType* const dtypep = memberVarp->dtypep()->skipRefp();
const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType);
if (!classRefp) return;
findSubObjRandModes(classRefp->classp());
});
}
if (hasConstraints) createRandomGenerator(classp);
if (randModeCount > 0) {
AstVar* const randModeVarp = getCreateRandModeVar(classp);

View File

@ -0,0 +1,21 @@
#!/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')
if not test.have_solver:
test.skip("No constraint solver installed")
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,90 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0);
// verilog_format: on
// Test: rand_mode(0) on nested sub-object members must prevent solver
// from overwriting the variable. Previously, write_var registered the
// variable unconditionally, so the solver wrote it back even when
// rand_mode was off.
class InnerClass;
rand bit [7:0] val1;
rand bit [7:0] val2;
rand bit [7:0] val3;
constraint inner_c {
val1 inside {[8'd10 : 8'd50]};
val2 inside {[8'd60 : 8'd100]};
val3 inside {[8'd110 : 8'd150]};
}
function new();
val1 = 0;
val2 = 0;
val3 = 0;
endfunction
endclass
class OuterClass;
rand InnerClass nested;
rand bit [7:0] outer_val;
constraint outer_c { outer_val inside {[8'd1 : 8'd20]}; }
function new();
nested = new();
outer_val = 0;
endfunction
endclass
module t;
initial begin
OuterClass obj;
obj = new;
// Test 1: Normal randomize
repeat (20) begin
`checkd(obj.randomize(), 1)
`check_range(obj.nested.val1, 8'd10, 8'd50)
`check_range(obj.nested.val2, 8'd60, 8'd100)
`check_range(obj.nested.val3, 8'd110, 8'd150)
`check_range(obj.outer_val, 8'd1, 8'd20)
end
// Test 2: rand_mode(0) on val1 -- must hold assigned value
void'(obj.nested.val1.rand_mode(0));
obj.nested.val1 = 8'd42;
repeat (20) begin
`checkd(obj.randomize(), 1)
`checkd(obj.nested.val1, 8'd42)
`check_range(obj.nested.val2, 8'd60, 8'd100)
`check_range(obj.nested.val3, 8'd110, 8'd150)
end
// Test 3: rand_mode(0) on val2 as well
void'(obj.nested.val2.rand_mode(0));
obj.nested.val2 = 8'd77;
repeat (20) begin
`checkd(obj.randomize(), 1)
`checkd(obj.nested.val1, 8'd42)
`checkd(obj.nested.val2, 8'd77)
`check_range(obj.nested.val3, 8'd110, 8'd150)
end
// Test 4: Re-enable both and verify randomization resumes
void'(obj.nested.val1.rand_mode(1));
void'(obj.nested.val2.rand_mode(1));
repeat (20) begin
`checkd(obj.randomize(), 1)
`check_range(obj.nested.val1, 8'd10, 8'd50)
`check_range(obj.nested.val2, 8'd60, 8'd100)
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule