Fix write variable placement for global constraints (#6740) (#6750) (#6797)

This commit is contained in:
Yilou Wang 2025-12-11 20:16:34 +01:00 committed by GitHub
parent cfd22a95e2
commit f097e8a34e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 176 additions and 16 deletions

View File

@ -898,17 +898,12 @@ class ConstraintExprVisitor final : public VNVisitor {
AstNodeExpr* parentAccess = membersel->fromp()->cloneTree(false);
AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule);
AstVar* const effectiveRandModeVarp = VN_AS(varClassp->user2p(), Var);
if (effectiveRandModeVarp) {
AstMemberSel* randModeSel
= new AstMemberSel{varp->fileline(), parentAccess, effectiveRandModeVarp};
randModeSel->dtypep(effectiveRandModeVarp->dtypep());
randModeAccess = randModeSel;
} else {
UASSERT_OBJ(m_randModeVarp, nodep, "No m_randModeVarp");
randModeAccess = new AstVarRef{varp->fileline(),
VN_AS(m_randModeVarp->user2p(), NodeModule),
m_randModeVarp, VAccess::READ};
}
UASSERT_OBJ(effectiveRandModeVarp, nodep,
"Member-selected variable must have randmode in its class");
AstMemberSel* randModeSel
= new AstMemberSel{varp->fileline(), parentAccess, effectiveRandModeVarp};
randModeSel->dtypep(effectiveRandModeVarp->dtypep());
randModeAccess = randModeSel;
} else {
UASSERT_OBJ(m_randModeVarp, nodep, "No m_randModeVarp");
randModeAccess
@ -958,11 +953,21 @@ class ConstraintExprVisitor final : public VNVisitor {
dimension = 1;
}
methodp->dtypeSetVoid();
AstNodeModule* const classp = membersel ? VN_AS(membersel->user2p(), NodeModule)
: VN_AS(varp->user2p(), NodeModule);
AstNodeModule* classp;
if (membersel) {
// For membersel, find the root varref to get the class where randomize() is called
AstNode* rootNode = membersel->fromp();
while (AstMemberSel* nestedMemberSel = VN_CAST(rootNode, MemberSel)) {
rootNode = nestedMemberSel->fromp();
}
if (AstNodeVarRef* rootVarRef = VN_CAST(rootNode, NodeVarRef)) {
classp = VN_AS(rootVarRef->varp()->user2p(), NodeModule);
} else {
classp = VN_AS(membersel->user2p(), NodeModule);
}
methodp->addPinsp(membersel);
} else {
classp = VN_AS(varp->user2p(), NodeModule);
AstVarRef* const varRefp
= new AstVarRef{varp->fileline(), classp, varp, VAccess::WRITE};
varRefp->classOrPackagep(classOrPackagep);
@ -989,9 +994,14 @@ class ConstraintExprVisitor final : public VNVisitor {
}
AstNodeFTask* initTaskp = m_inlineInitTaskp;
if (!initTaskp) {
varp->user3(true); // Mark as set up in new()
initTaskp = VN_AS(m_memberMap.findMember(classp, "new"), NodeFTask);
UASSERT_OBJ(initTaskp, classp, "No new() in class");
varp->user3(true);
if (membersel) {
initTaskp = VN_AS(m_memberMap.findMember(classp, "randomize"), NodeFTask);
UASSERT_OBJ(initTaskp, classp, "No randomize() in class");
} else {
initTaskp = VN_AS(m_memberMap.findMember(classp, "new"), NodeFTask);
UASSERT_OBJ(initTaskp, classp, "No new() in class");
}
}
initTaskp->addStmtsp(methodp->makeStmt());
} else {
@ -2517,6 +2527,32 @@ class RandomizeVisitor final : public VNVisitor {
nodep, constrp, constrp->itemsp()->unlinkFrBackWithNext()));
}
});
// For derived classes: clone write_var calls from parent's randomize()
if (nodep->extendsp()) {
AstClass* parentClassp = nodep->extendsp()->classp();
while (parentClassp) {
AstFunc* const parentRandomizep
= VN_CAST(m_memberMap.findMember(parentClassp, "randomize"), Func);
if (parentRandomizep && parentRandomizep->stmtsp()) {
// Clone write_var statements from parent (stop at clearConstraints)
for (AstNode* stmtp = parentRandomizep->stmtsp(); stmtp;
stmtp = stmtp->nextp()) {
bool foundClearConstraints = false;
stmtp->foreach([&](AstCMethodHard* methodp) {
if (methodp->method() == VCMethod::RANDOMIZER_WRITE_VAR) {
randomizep->addStmtsp(stmtp->cloneTree(false));
} else if (methodp->method()
== VCMethod::RANDOMIZER_CLEARCONSTRAINTS) {
foundClearConstraints = true;
}
});
if (foundClearConstraints) break;
}
}
parentClassp
= parentClassp->extendsp() ? parentClassp->extendsp()->classp() : nullptr;
}
}
randomizep->addStmtsp(implementConstraintsClear(fl, genp));
AstTask* setupAllTaskp = getCreateConstraintSetupFunc(nodep);
AstTaskRef* const setupTaskRefp = new AstTaskRef{fl, setupAllTaskp, nullptr};
@ -2819,6 +2855,22 @@ class RandomizeVisitor final : public VNVisitor {
// Add constraints clearing code
if (classGenp) {
// Clone write_var calls from main randomize() for path-connected variables
AstFunc* const mainRandomizep
= VN_CAST(m_memberMap.findMember(classp, "randomize"), Func);
if (mainRandomizep && mainRandomizep->stmtsp()) {
for (AstNode* stmtp = mainRandomizep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
bool foundClearConstraints = false;
stmtp->foreach([&](AstCMethodHard* methodp) {
if (methodp->method() == VCMethod::RANDOMIZER_WRITE_VAR) {
randomizeFuncp->addStmtsp(stmtp->cloneTree(false));
} else if (methodp->method() == VCMethod::RANDOMIZER_CLEARCONSTRAINTS) {
foundClearConstraints = true;
}
});
if (foundClearConstraints) break;
}
}
randomizeFuncp->addStmtsp(
implementConstraintsClear(randomizeFuncp->fileline(), classGenp));
}

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-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,87 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
// Test case 1
class uvm_reg_field;
rand int m_value;
endclass
class reg_class;
rand int m_value;
rand uvm_reg_field _dummy;
constraint _dummy_is_reg {_dummy.m_value == m_value;}
function new();
_dummy = new;
endfunction
endclass
class block_class;
rand reg_class m_r;
function new();
m_r = new;
endfunction
endclass
class tb_test;
virtual task run_phase(int phase);
block_class regmodel;
regmodel = new;
// verilator lint_off IGNOREDRETURN
void'(regmodel.randomize() with {m_r.m_value == 32'hA5;});
// verilator lint_on IGNOREDRETURN
if (regmodel.m_r.m_value != 32'hA5) $stop;
endtask
endclass
// Test case 2
class axi_agent_config;
rand bit r_ready_delays;
constraint r_ready_delays_c {
r_ready_delays == 0;
}
endclass
class axi_env_config;
rand axi_agent_config axim_agt_cfg;
function new();
axim_agt_cfg = new;
endfunction
endclass
class axi_base_test;
axi_env_config axi_env_cfg;
virtual function void build_phase();
configure_axi_env();
endfunction
function void configure_axi_env();
axi_env_cfg = new;
endfunction
endclass
class axi_wrap_test extends axi_base_test;
function void configure_axi_env();
void'(axi_env_cfg.randomize());
endfunction
endclass
module t_constraint_global_nested_member;
initial begin
tb_test tb;
axi_wrap_test axi_t;
tb = new;
tb.run_phase(0);
axi_t = new();
axi_t.build_phase();
$write("*-* All Finished *-*\n");
$finish;
end
endmodule