From 6fc9089a7765f20ddfde557fa01d1085e1d07f7b Mon Sep 17 00:00:00 2001 From: Srinivasan Venkataramanan Date: Fri, 16 Jan 2026 19:12:09 +0530 Subject: [PATCH] Support `unique` constraints (on 1D static arrays) (#6810) (#6878) --- include/verilated_random.cpp | 22 ++++- include/verilated_random.h | 21 ++-- include/verilated_types.h | 5 + src/V3AstAttr.h | 2 + src/V3Randomize.cpp | 96 +++++++++++++++++-- test_regress/t/t_constraint_unsup_unq_arr.out | 23 +++++ test_regress/t/t_constraint_unsup_unq_arr.py | 16 ++++ test_regress/t/t_constraint_unsup_unq_arr.v | 66 +++++++++++++ test_regress/t/t_randomize.out | 4 - 9 files changed, 234 insertions(+), 21 deletions(-) create mode 100644 test_regress/t/t_constraint_unsup_unq_arr.out create mode 100755 test_regress/t/t_constraint_unsup_unq_arr.py create mode 100644 test_regress/t/t_constraint_unsup_unq_arr.v diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index b472d28df..fdbdd38bb 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -367,7 +367,26 @@ void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits) { } bool VlRandomizer::next(VlRNG& rngr) { - if (m_vars.empty()) return true; + if (m_vars.empty() && m_unique_arrays.empty()) return true; + for (const std::string& baseName : m_unique_arrays) { + const auto it = m_vars.find(baseName); + + // Look up the actual size we stored earlier + // const uint32_t size = m_unique_array_sizes[baseName]; + const uint32_t size = m_unique_array_sizes.at(baseName); + + if (it != m_vars.end()) { + std::string distinctExpr = "(__Vbv (distinct"; + for (uint32_t i = 0; i < size; ++i) { + char hexIdx[12]; + sprintf(hexIdx, "#x%08x", i); + distinctExpr += " (select " + it->first + " " + hexIdx + ")"; + } + distinctExpr += "))"; + m_constraints.push_back(distinctExpr); + } + } + std::iostream& os = getSolver(); if (!os) return false; @@ -384,6 +403,7 @@ bool VlRandomizer::next(VlRNG& rngr) { var.second->emitType(os); os << ")\n"; } + for (const std::string& constraint : m_constraints) { os << "(assert (= #b1 " << constraint << "))\n"; } diff --git a/include/verilated_random.h b/include/verilated_random.h index 31c5af1f1..94a75ba29 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -195,7 +195,6 @@ public: }; //============================================================================= - // Object holding constraints and variable references. class VlRandomizer VL_NOT_FINAL { // MEMBERS @@ -205,6 +204,8 @@ class VlRandomizer VL_NOT_FINAL { std::map> m_vars; // Solver-dependent // variables ArrayInfoMap m_arr_vars; // Tracks each element in array structures for iteration + std::vector m_unique_arrays; + std::map m_unique_array_sizes; const VlQueue* m_randmodep = nullptr; // rand_mode state; int m_index = 0; // Internal counter for key generation @@ -317,10 +318,10 @@ public: } // --- write_var to register variables --- - // Register scalar variable (non-struct, basic type) template - typename std::enable_if::value, void>::type + typename std::enable_if::value && !IsVlUnpacked::value, + void>::type write_var(T& var, int width, const char* name, int dimension, std::uint32_t randmodeIdx = std::numeric_limits::max()) { if (m_vars.find(name) != m_vars.end()) return; @@ -358,21 +359,23 @@ public: std::uint32_t randmodeIdx = std::numeric_limits::max()) { if (dimension > 0) record_struct_arr(var, name, dimension, {}, {}); } - // Register unpacked array of non-struct types template typename std::enable_if::value, void>::type - write_var(VlUnpacked& var, int width, const char* name, int dimension, + write_var(VlUnpacked& var, uint32_t width, const std::string& name, + uint32_t dimension, std::uint32_t randmodeIdx = std::numeric_limits::max()) { + if (m_vars.find(name) != m_vars.end()) return; + m_vars[name] = std::make_shared>>( name, width, &var, dimension, randmodeIdx); + if (dimension > 0) { m_index = 0; record_arr_table(var, name, dimension, {}, {}); } } - // Register unpacked array of structs template typename std::enable_if::value, void>::type @@ -416,6 +419,12 @@ public: ++m_index; } + // This is the "Sender" API for the generated code + void rand_unique(const std::string& name, uint32_t size) { + m_unique_arrays.push_back(name); + m_unique_array_sizes[name] = size; + } + // Recursively record all elements in an unpacked array template void record_arr_table(VlUnpacked& var, const std::string& name, int dimension, diff --git a/include/verilated_types.h b/include/verilated_types.h index 7a0c40bc7..2f6d5bd52 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -1612,6 +1612,11 @@ private: return a != b; } }; +// Trait to detect VlUnpacked types +template +struct IsVlUnpacked : std::false_type {}; +template +struct IsVlUnpacked> : std::true_type {}; template std::string VL_TO_STRING(const VlUnpacked& obj) { diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 0e511cb93..c1e92564a 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -787,6 +787,7 @@ public: RANDOMIZER_CLEARCONSTRAINTS, RANDOMIZER_CLEARALL, RANDOMIZER_HARD, + RANDOMIZER_UNIQUE, RANDOMIZER_WRITE_VAR, RNG_GET_RANDSTATE, RNG_SET_RANDSTATE, @@ -916,6 +917,7 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) { {RANDOMIZER_CLEARCONSTRAINTS, "clearConstraints", false}, \ {RANDOMIZER_CLEARALL, "clearAll", false}, \ {RANDOMIZER_HARD, "hard", false}, \ + {RANDOMIZER_UNIQUE, "rand_unique", false}, \ {RANDOMIZER_WRITE_VAR, "write_var", false}, \ {RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", true}, \ {RNG_SET_RANDSTATE, "__Vm_rng.set_randstate", false}, \ diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index ed90490c4..ba0c7360c 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -725,6 +725,7 @@ class ConstraintExprVisitor final : public VNVisitor { // AstMemberSel::user2p() -> AstNodeModule*. Pointer to containing module // VNuser3InUse m_inuser3; (Allocated for use in RandomizeVisitor) + AstClass* const m_classp; AstNodeFTask* const m_inlineInitTaskp; // Method to add write_var calls to // (may be null, then new() is used) AstVar* const m_genp; // VlRandomizer variable of the class @@ -1336,9 +1337,83 @@ class ConstraintExprVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } void visit(AstConstraintUnique* nodep) override { - nodep->v3warn(CONSTRAINTIGN, "Constraint expression ignored (unsupported)"); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + if (!m_classp) return; + + FileLine* const fl = nodep->fileline(); + + AstNodeFTask* const initTaskp = VN_AS(m_memberMap.findMember(m_classp, "new"), NodeFTask); + if (!initTaskp) return; + + AstVar* const genVarp = VN_AS(m_classp->user3p(), Var); + if (!genVarp) return; + + for (AstNode* itemp = nodep->rangesp(); itemp; itemp = itemp->nextp()) { + if (AstVarRef* const varRefp = VN_CAST(itemp, VarRef)) { + AstVar* const varp = varRefp->varp(); + + AstNodeDType* const dtypep = varp->dtypep()->skipRefp(); + if (AstUnpackArrayDType* const up = VN_CAST(dtypep, UnpackArrayDType)) { + AstRange* const rangep = up->rangep(); + if (!rangep || !VN_IS(rangep->leftp(), Const) + || !VN_IS(rangep->rightp(), Const)) { + nodep->v3warn( + CONSTRAINTIGN, + "Unsupported: Unique constraint on other than static arrays"); + continue; + } + + // Ensure it is ONLY 1-D by checking that the sub-type is NOT an array/queue + // We skip refs (typedefs) to see the actual underlying type + AstNodeDType* const subp = up->subDTypep()->skipRefp(); + if (VN_IS(subp, NodeArrayDType) || VN_IS(subp, QueueDType) + || VN_IS(subp, DynArrayDType)) { + nodep->v3warn( + CONSTRAINTIGN, + "Unsupported: Unique constraint on other than static arrays"); + continue; + } + } else { + nodep->v3warn(CONSTRAINTIGN, + "Unsupported: Unique constraint on other than static arrays"); + continue; + } + + AstCMethodHard* const wCallp = new AstCMethodHard{ + fl, new AstVarRef{fl, genVarp, VAccess::READ}, VCMethod::RANDOMIZER_WRITE_VAR}; + wCallp->addPinsp(new AstVarRef{fl, varp, VAccess::READ}); + wCallp->addPinsp(new AstConst{fl, AstConst::Unsized64{}, + static_cast(varp->dtypep()->width())}); + wCallp->addPinsp(new AstConst{fl, AstConst::String{}, varp->name()}); + wCallp->addPinsp(new AstConst{fl, 1}); // Dimension + + wCallp->dtypeSetVoid(); + initTaskp->addStmtsp(new AstStmtExpr{fl, wCallp}); + + uint32_t arraySize = 0; + if (AstUnpackArrayDType* const adtypep + = VN_CAST(varp->dtypep(), UnpackArrayDType)) { + arraySize = adtypep->elementsConst(); + } + if (arraySize > 100) { + nodep->v3warn(CONSTRAINTIGN, + "Unsupported: Unique constraint on static arrays of size > 100"); + continue; + } + + AstNodeExpr* const uPins = new AstConst{fl, AstConst::String{}, varp->name()}; + uPins->addNext(new AstConst{fl, arraySize}); + + AstCMethodHard* const uCallp + = new AstCMethodHard{fl, new AstVarRef{fl, genVarp, VAccess::READ}, + VCMethod::RANDOMIZER_UNIQUE, uPins}; + uCallp->dtypep(nodep->findVoidDType()); + initTaskp->addStmtsp(new AstStmtExpr{fl, uCallp}); + } + } + nodep->unlinkFrBack(); + VL_DO_DANGLING(pushDeletep(nodep), nodep); } + void visit(AstConstraintExpr* nodep) override { iterateChildren(nodep); if (m_wantSingle) { @@ -1440,10 +1515,11 @@ class ConstraintExprVisitor final : public VNVisitor { public: // CONSTRUCTORS - explicit ConstraintExprVisitor(VMemberMap& memberMap, AstNode* nodep, + explicit ConstraintExprVisitor(AstClass* classp, VMemberMap& memberMap, AstNode* nodep, AstNodeFTask* inlineInitTaskp, AstVar* genp, AstVar* randModeVarp, std::set& writtenVars) - : m_inlineInitTaskp{inlineInitTaskp} + : m_classp{classp} + , m_inlineInitTaskp{inlineInitTaskp} , m_genp{genp} , m_randModeVarp{randModeVarp} , m_memberMap{memberMap} @@ -2579,8 +2655,8 @@ class RandomizeVisitor final : public VNVisitor { resizeAllTaskp->addStmtsp(resizeTaskRefp->makeStmt()); } - ConstraintExprVisitor{m_memberMap, constrp->itemsp(), nullptr, - genp, randModeVarp, m_writtenVars}; + ConstraintExprVisitor{classp, m_memberMap, constrp->itemsp(), nullptr, + genp, randModeVarp, m_writtenVars}; if (constrp->itemsp()) { taskp->addStmtsp(wrapIfConstraintMode( nodep, constrp, constrp->itemsp()->unlinkFrBackWithNext())); @@ -2833,8 +2909,8 @@ class RandomizeVisitor final : public VNVisitor { AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext(); randomizeFuncp->addStmtsp(capturedTreep); { - ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, - stdrand, nullptr, m_writtenVars}; + ConstraintExprVisitor{nullptr, m_memberMap, capturedTreep, randomizeFuncp, + stdrand, nullptr, m_writtenVars}; } AstCExpr* const solverCallp = new AstCExpr{fl}; solverCallp->dtypeSetBit(); @@ -2961,8 +3037,8 @@ class RandomizeVisitor final : public VNVisitor { AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext(); randomizeFuncp->addStmtsp(capturedTreep); { - ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, - localGenp, randModeVarp, m_writtenVars}; + ConstraintExprVisitor{classp, m_memberMap, capturedTreep, randomizeFuncp, + localGenp, randModeVarp, m_writtenVars}; } // Call the solver and set return value diff --git a/test_regress/t/t_constraint_unsup_unq_arr.out b/test_regress/t/t_constraint_unsup_unq_arr.out new file mode 100644 index 000000000..9f96b4305 --- /dev/null +++ b/test_regress/t/t_constraint_unsup_unq_arr.out @@ -0,0 +1,23 @@ +%Warning-CONSTRAINTIGN: t/t_constraint_unsup_unq_arr.v:19:5: Unsupported: Unique constraint on static arrays of size > 100 + : ... note: In instance 't' + 19 | unique {uniq_val_arr_400}; + | ^~~~~~ + ... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest + ... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message. +%Warning-CONSTRAINTIGN: t/t_constraint_unsup_unq_arr.v:22:5: Unsupported: Unique constraint on other than static arrays + : ... note: In instance 't' + 22 | unique {uniq_val_darr}; + | ^~~~~~ +%Warning-CONSTRAINTIGN: t/t_constraint_unsup_unq_arr.v:23:5: Unsupported: Unique constraint on other than static arrays + : ... note: In instance 't' + 23 | unique {uniq_val_hash}; + | ^~~~~~ +%Warning-CONSTRAINTIGN: t/t_constraint_unsup_unq_arr.v:24:5: Unsupported: Unique constraint on other than static arrays + : ... note: In instance 't' + 24 | unique {uniq_val_queue}; + | ^~~~~~ +%Warning-CONSTRAINTIGN: t/t_constraint_unsup_unq_arr.v:25:5: Unsupported: Unique constraint on other than static arrays + : ... note: In instance 't' + 25 | unique {uniq_val_arr_mda}; + | ^~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_constraint_unsup_unq_arr.py b/test_regress/t/t_constraint_unsup_unq_arr.py new file mode 100755 index 000000000..6585af685 --- /dev/null +++ b/test_regress/t/t_constraint_unsup_unq_arr.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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('vlt') + +test.lint(fails=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_constraint_unsup_unq_arr.v b/test_regress/t/t_constraint_unsup_unq_arr.v new file mode 100644 index 000000000..f09101263 --- /dev/null +++ b/test_regress/t/t_constraint_unsup_unq_arr.v @@ -0,0 +1,66 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by AsFigo. +// SPDX-License-Identifier: CC0-1.0 +class UniqueMultipleArray; + rand bit [15:0] uniq_val_arr[4]; + rand bit [15:0] uniq_val_arr_400[400]; + rand bit [15:0] uniq_val_arr_mda[4][]; + rand bit [15:0] uniq_val_darr[]; + rand bit [15:0] uniq_val_hash[int]; + rand bit [15:0] uniq_val_queue[$]; + rand bit b1; + rand int array[2]; // 2,4,6 // TODO: add rand when supported + + // Constraint to ensure the elements in the array are unique + constraint unique_c { + unique {uniq_val_arr}; // Ensure unique values in the array + unique {uniq_val_arr_400}; // Ensure unique values in the array + } + constraint unique_c1 { + unique {uniq_val_darr}; // Ensure unique values in the array + unique {uniq_val_hash}; // Ensure unique values in the array + unique {uniq_val_queue}; // Ensure unique values in the array + unique {uniq_val_arr_mda}; // Ensure unique values in the array + unique { array[0], array[1] }; + } + // -------------------------------------------------- + // Explicit uniqueness checker (post-solve validation) + // -------------------------------------------------- + function bit check_unique(); + for (int i = 0; i < $size(uniq_val_arr); i++) begin + for (int j = i + 1; j < $size(uniq_val_arr); j++) begin + if (uniq_val_arr[i] == uniq_val_arr[j]) begin + $error("UNIQUENESS VIOLATION: uniq_val_arr[%0d] == uniq_val_arr[%0d] == 0x%h", i, j, uniq_val_arr[i]); + return 0; + end + end + end + return 1; + endfunction + + function void post_randomize(); + $display("Randomized values in uniq_val_arr: %p", uniq_val_arr); + + if (!check_unique()) begin + $fatal(1, "Post-randomize uniqueness check FAILED"); + end + foreach (uniq_val_arr[i]) begin + $display("uniq_val_arr[%0d] = 0x%h", i, uniq_val_arr[i]); + end + endfunction + +endclass : UniqueMultipleArray + +module t; + initial begin + // Create an instance of the UniqueMultipleArray class + UniqueMultipleArray array_instance = new(); + + // Attempt to randomize and verify the constraints + /* verilator lint_off WIDTHTRUNC */ + assert (array_instance.randomize()) + else $error("Randomization failed!"); + end +endmodule : t diff --git a/test_regress/t/t_randomize.out b/test_regress/t/t_randomize.out index 136639289..0bc25bf43 100644 --- a/test_regress/t/t_randomize.out +++ b/test_regress/t/t_randomize.out @@ -4,10 +4,6 @@ | ^~~~ ... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest ... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message. -%Warning-CONSTRAINTIGN: t/t_randomize.v:40:7: Constraint expression ignored (unsupported) - : ... note: In instance 't' - 40 | unique { array[0], array[1] }; - | ^~~~~~ %Warning-CONSTRAINTIGN: t/t_randomize.v:43:23: Constraint expression ignored (imperfect distribution) : ... note: In instance 't' 43 | constraint order { solve length before header; }