Fix rand_mode(0) on sub-object members not preventing solver write-back (#7272)
This commit is contained in:
parent
316fb02c60
commit
8925762077
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}, \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue