diff --git a/src/V3LinkWith.cpp b/src/V3LinkWith.cpp index e83536de8..8ea84a3f4 100644 --- a/src/V3LinkWith.cpp +++ b/src/V3LinkWith.cpp @@ -107,7 +107,7 @@ class LinkWithVisitor final : public VNVisitor { } void visit(AstMemberSel* nodep) override { - if (m_inRandomizeWith && nodep->fromp()->isSame(m_currentRandomizeSelectp)) { + if (m_inRandomizeWith && nodep->fromp()->sameTree(m_currentRandomizeSelectp)) { // Replace member selects to the element // on which the randomize() is called with LambdaArgRef // This allows V3Randomize to work properly when diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 5cd52d62a..12cdba74d 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -753,6 +753,7 @@ class ConstraintExprVisitor final : public VNVisitor { // (used to format "%s.%s" for struct arrays) std::set& m_writtenVars; // Track which variable paths have write_var generated // (shared across all constraints) + std::set m_inlineWrittenVars; // Per-instance tracking for inline constraints std::set* m_sizeConstrainedArraysp = nullptr; // Arrays with size+element constraints // Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4") @@ -1133,14 +1134,17 @@ class ConstraintExprVisitor final : public VNVisitor { // else: Global constraints keep nodep alive for write_var processing relinker.relink(exprp); - // For global constraints: check if this specific path has been written - // For normal constraints: only call write_var if varp->user3() is not set - const bool alreadyWritten - = isGlobalConstrained ? m_writtenVars.count(smtName) > 0 : varp->user3(); + // For global constraints: check shared path-level set + // For inline constraints: check per-instance set (each __Vrandwith has own randomizer) + // For class-level constraints: check varp->user3() + const bool alreadyWritten = isGlobalConstrained ? m_writtenVars.count(smtName) > 0 + : m_inlineInitTaskp ? m_inlineWrittenVars.count(smtName) > 0 + : varp->user3(); const bool shouldWriteVar = !alreadyWritten; if (shouldWriteVar) { // Track this variable path as written if (isGlobalConstrained) m_writtenVars.insert(smtName); + if (m_inlineInitTaskp) m_inlineWrittenVars.insert(smtName); // For global constraints, delete nodep after processing if (isGlobalConstrained && !nodep->backp()) VL_DO_DANGLING(pushDeletep(nodep), nodep); diff --git a/test_regress/t/t_constraint_foreach_classref.v b/test_regress/t/t_constraint_foreach_classref.v index 797a1a1a7..b5a13b2fd 100644 --- a/test_regress/t/t_constraint_foreach_classref.v +++ b/test_regress/t/t_constraint_foreach_classref.v @@ -39,6 +39,7 @@ endclass module t; OuterDyn od; + OuterDyn od_arr[5]; OuterQueue oq; initial begin @@ -104,6 +105,29 @@ module t; end end + // === Test 4: Array of objects with inline constraint for sub-object members === + // Verifies member resolution AND runtime enforcement when randomize() + // target is array-indexed (regression test for verilator/verilator#7431) + foreach (od_arr[i]) begin + od_arr[i] = new(3); + assert(od_arr[i].randomize() with { + od_arr[i].items[0].val > 8'd10; + od_arr[i].items[0].val < 8'd200; + od_arr[i].items[0].tag > 0; + } != 0); + + if (!(od_arr[i].items[0].val > 10 && od_arr[i].items[0].val < 200)) begin + $display("FAIL: od_arr[%0d].items[0].val=%0d out of range", + i, od_arr[i].items[0].val); + $stop; + end + if (od_arr[i].items[0].tag == 0) begin + $display("FAIL: od_arr[%0d].items[0].tag=%0d should be > 0", + i, od_arr[i].items[0].tag); + $stop; + end + end + $write("*-* All Finished *-*\n"); $finish; end