From a6e21e0eb75272c6dfb978c4ce2384138292c7ea Mon Sep 17 00:00:00 2001 From: Aditya Shevade Date: Sat, 5 Sep 2026 11:06:57 -0700 Subject: [PATCH] Fix phased `solve...before` diversity (#8189) (#8194) --- include/verilated_random.cpp | 44 ++++++++++++++--- include/verilated_random.h | 5 +- .../t/t_randomize_solve_before_diversity.py | 21 +++++++++ .../t/t_randomize_solve_before_diversity.v | 47 +++++++++++++++++++ 4 files changed, 109 insertions(+), 8 deletions(-) create mode 100755 test_regress/t/t_randomize_solve_before_diversity.py create mode 100644 test_regress/t/t_randomize_solve_before_diversity.v diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index 38588b244..307a7a8b4 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -674,10 +674,38 @@ void VlRandomVar::set(const std::string& idx, const std::string& val) const { } } -void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits) { - const IData hash = VL_RANDOM_RNG_I(rngr) & ((1 << bits) - 1); +void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits, + const std::vector* layerVarsp) { + // layerVarsp scopes sampling to the current phase's own layer, so a + // phased solve's diversity constraint can't be built entirely out of a + // later phase's (still-unsolved) variable instead of this one's. + std::vector& vars = m_randomConstraintVars; + vars.clear(); int varBits = 0; - for (const auto& var : m_vars) varBits += var.second->totalWidth(); + if (layerVarsp) { + for (const auto& name : *layerVarsp) { + const auto it = m_vars.find(name); + // buildSolveLayers() only ever adds a name after confirming + // it's already in m_vars. + assert(it != m_vars.end()); + vars.push_back(it->second.get()); + varBits += it->second->totalWidth(); + } + } else { + for (const auto& var : m_vars) { + vars.push_back(var.second.get()); + varBits += var.second->totalWidth(); + } + } + + if (varBits == 0) { + // Nothing to sample (e.g. a still-unsized queue/dynamic array, in a + // layer or the whole class) -- tautology instead of the degenerate + // empty-operand expression the loop below would otherwise build. + os << "(= #b1 #b1)"; + return; + } + const IData hash = VL_RANDOM_RNG_I(rngr) & ((1 << bits) - 1); os << "(= #b"; for (int i = bits - 1; i >= 0; i--) os << (VL_BITISSET_I(hash, i) ? '1' : '0'); if (bits > 1) os << " (concat"; @@ -685,11 +713,12 @@ void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits) { IData varBitsLeft = varBits; IData varBitsWant = (varBits + 1) / 2; if (varBits > 2) os << " (bvxor"; - for (const auto& var : m_vars) { - for (int j = 0; j < var.second->totalWidth(); j++, varBitsLeft--) { + for (const auto& varp : vars) { // LCOV_EXCL_BR_LINE - reservoir-sampling below + // always forces its last pick before exhausting vars + for (int j = 0; j < varp->totalWidth(); j++, varBitsLeft--) { const bool doEmit = (VL_RANDOM_RNG_I(rngr) % varBitsLeft) < varBitsWant; if (doEmit) { - var.second->emitExtract(os, j); + varp->emitExtract(os, j); if (--varBitsWant == 0) break; } } @@ -1748,8 +1777,9 @@ bool VlRandomizer::solvePhaseValues(VlSolverSession& sess, VlRNG& rngr, // Try diversity: add random constraint, re-check. If sat, get // updated (more diverse) values. If unsat, keep baseline values. + // Scoped to this layer's own vars -- see randomConstraint's comment. os << "(assert "; - randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN); + randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN, &layerVars); os << ")\n"; os << "(check-sat)\n"; if (sess.readStatus() == VlSolverStatus::SAT) { diff --git a/include/verilated_random.h b/include/verilated_random.h index 682ff2823..6e8fafbdc 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -259,6 +259,8 @@ class VlRandomizer VL_NOT_FINAL { m_constraints_line; // fileline content of the constraint for unsat constraints std::vector m_softConstraints; // Soft constraints std::map> m_vars; // Solver-dependent + // Scratch buffer for randomConstraint(), reused across calls + std::vector m_randomConstraintVars; std::set m_disabledVars; // Variables with rand_mode off (skip write-back) // variables ArrayInfoMap m_arr_vars; // Tracks each element in array structures for iteration @@ -279,7 +281,8 @@ class VlRandomizer VL_NOT_FINAL { bool hasFrozenVar() const; // true if any var is currently rand_mode(0)-frozen // PRIVATE METHODS - void randomConstraint(std::ostream& os, VlRNG& rngr, int bits); + void randomConstraint(std::ostream& os, VlRNG& rngr, int bits, + const std::vector* layerVarsp = nullptr); // Fetch the model and write it into the registered variables. bool applyModel(VlSolverSession& sess); bool parseModel(std::istream& is, size_t requested); diff --git a/test_regress/t/t_randomize_solve_before_diversity.py b/test_regress/t/t_randomize_solve_before_diversity.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_solve_before_diversity.py @@ -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() diff --git a/test_regress/t/t_randomize_solve_before_diversity.v b/test_regress/t/t_randomize_solve_before_diversity.v new file mode 100644 index 000000000..e598dacc8 --- /dev/null +++ b/test_regress/t/t_randomize_solve_before_diversity.v @@ -0,0 +1,47 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Aditya Shevade +// 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 + +// IEEE 1800-2023 18.5.9's worked example for solve...before: s should land +// on 0/1 with ~50/50 probability, even though d shares a constraint with it. +class SolveBeforeDiversity; + rand bit s; + rand bit [7:0] d; + constraint c { + s -> d == 0; + } + constraint order { + solve s before d; + } +endclass + +module t; + parameter int N = 8000; // randomize() calls + parameter int TOL_PCT = 30; // +-% tolerance on expected count + + initial begin + int randomize_result; + automatic SolveBeforeDiversity obj = new(); + automatic int s_count = 0; + repeat (N) begin + randomize_result = obj.randomize(); + `checkd(randomize_result, 1); + if (obj.s) begin + `checkd(obj.d, 8'h00); + end + if (obj.s) s_count++; + end + `check_range(s_count, (N / 2) * (100 - TOL_PCT) / 100, (N / 2) * (100 + TOL_PCT) / 100); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule