From f097e8a34e9b769ccda9c249da9040945f8c7a12 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Thu, 11 Dec 2025 20:16:34 +0100 Subject: [PATCH] Fix write variable placement for global constraints (#6740) (#6750) (#6797) --- src/V3Randomize.cpp | 84 ++++++++++++++---- .../t/t_constraint_global_nested_member.py | 21 +++++ .../t/t_constraint_global_nested_member.v | 87 +++++++++++++++++++ 3 files changed, 176 insertions(+), 16 deletions(-) create mode 100755 test_regress/t/t_constraint_global_nested_member.py create mode 100755 test_regress/t/t_constraint_global_nested_member.v diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 1ba038bae..0d8468fb2 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -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)); } diff --git a/test_regress/t/t_constraint_global_nested_member.py b/test_regress/t/t_constraint_global_nested_member.py new file mode 100755 index 000000000..466368b3d --- /dev/null +++ b/test_regress/t/t_constraint_global_nested_member.py @@ -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() diff --git a/test_regress/t/t_constraint_global_nested_member.v b/test_regress/t/t_constraint_global_nested_member.v new file mode 100755 index 000000000..a2305e041 --- /dev/null +++ b/test_regress/t/t_constraint_global_nested_member.v @@ -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