diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index b35769d83..71f719722 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -30,6 +30,18 @@ #include #include +// Diversity (scalar rand vars): tie each free bit to a random target via a +// boolean assumption literal, then force the bits with (check-sat-assuming). +// If UNSAT, (get-unsat-assumptions) names the literals clashing with the +// feasible hard+soft base; drop one per round and recheck until SAT, which +// keeps the maximal set of bits compatible with the constraints. Only these +// private literals are assumed (never user constraints), and assumptions are +// ephemeral, so hard/soft/randc semantics are untouched and rounds need no +// push/pop or re-asserting. The surviving assumptions steer each free bit +// from the random target, spreading a wide range uniformly (fixing the +// boundary bias under `value < (1<dimension() > 0) { + hasArray = true; + break; + } + } + if (!hasArray) { + // Tie each free bit to a fresh random target via a boolean + // assumption literal a_k <=> (bit_k == target_k), then force the + // bits with (check-sat-assuming ...). If UNSAT, + // (get-unsat-assumptions) names the literals clashing with the + // feasible base; drop ONE per round so the maximal compatible + // set survives -- dropping a whole conflicting group at once + // would collapse the diversity of tightly coupled bits (one-hot, + // 2-value sets) onto the solver's fixed default. Assumptions are + // ephemeral, so rounds need no push/pop or re-asserting and the + // solver keeps its learned clauses. Each round drops >= 1 -> ends + // in <= npins rounds. + std::vector targets; + int npins = 0; + for (const auto& var : m_vars) { + const int w = var.second->totalWidth(); + for (int b = 0; b < w; b++) { + const bool target = (VL_RANDOM_RNG_I(rngr) & 1); + targets.push_back(target); + os << "(declare-fun a" << npins << " () Bool)\n"; + os << "(assert (= a" << npins << " (="; + var.second->emitExtract(os, b); + os << " #b" << (target ? '1' : '0') << ")))\n"; + ++npins; + } + } + std::vector dropped(npins, false); + for (int round = 0; round <= npins; ++round) { + os << "(check-sat-assuming ("; + for (int k = 0; k < npins; k++) + if (!dropped[k]) os << " a" << k; + os << "))\n"; + if (parseSolution(os, false)) break; + // get-unsat-assumptions only echoes still-active literals, + // so the first in-range index is a live conflicting bit. + const std::vector core = readUnsatAssumptions(os); + for (const int idx : core) + if (idx < npins) { + dropped[idx] = true; + break; + } + } + } else { + // Array present: original XOR-rounds path. + for (int i = 0; i < _VL_SOLVER_HASH_LEN_TOTAL && sat; ++i) { + os << "(assert "; + randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN); + os << ")\n"; + os << "\n(check-sat)\n"; + sat = parseSolution(os, false); + (void)sat; + } } } @@ -643,6 +708,25 @@ bool VlRandomizer::checkSat(std::iostream& os) { return result == "sat"; } +std::vector VlRandomizer::readUnsatAssumptions(std::iostream& os) { + os << "(get-unsat-assumptions)\n"; + std::string line; + do { std::getline(os, line); } while (line.empty()); + // The response lists only "a" literals; collect each full integer run. + std::vector idxs; + std::string num; + for (const char c : line) { + if (std::isdigit(static_cast(c))) { + num += c; + } else if (!num.empty()) { + idxs.push_back(std::stoi(num)); + num.clear(); + } + } + if (!num.empty()) idxs.push_back(std::stoi(num)); + return idxs; +} + bool VlRandomizer::parseSolution(std::iostream& os, bool log) { std::string sat; do { std::getline(os, sat); } while (sat == ""); diff --git a/include/verilated_random.h b/include/verilated_random.h index e646b6840..648ea560e 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -235,6 +235,8 @@ class VlRandomizer VL_NOT_FINAL { void randomConstraint(std::ostream& os, VlRNG& rngr, int bits); bool parseSolution(std::iostream& os, bool log = false); bool checkSat(std::iostream& os); + // Indices of the "a" literals named by (get-unsat-assumptions). + std::vector readUnsatAssumptions(std::iostream& os); void emitRandcExclusions(std::ostream& os) const; // Emit randc exclusion constraints void recordRandcValues(); // Record solved randc values for future exclusion size_t hashConstraints() const; diff --git a/test_regress/t/t_randomize_shift_distribution.py b/test_regress/t/t_randomize_shift_distribution.py new file mode 100755 index 000000000..56d461811 --- /dev/null +++ b/test_regress/t/t_randomize_shift_distribution.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(all_run_flags=["+verilator+seed+1"]) + +test.passes() diff --git a/test_regress/t/t_randomize_shift_distribution.v b/test_regress/t/t_randomize_shift_distribution.v new file mode 100644 index 000000000..3ac5addb3 --- /dev/null +++ b/test_regress/t/t_randomize_shift_distribution.v @@ -0,0 +1,84 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// 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_le(gotv,maxv) do if ((gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp<=%0d\n", `__FILE__,`__LINE__, (gotv), (maxv)); `stop; end while(0); +// verilog_format: on + +typedef logic unsigned [63:0] uvm_reg_data_t; + +class uvm_reg_field; + rand uvm_reg_data_t value; + int unsigned m_size; + int unsigned m_ones[64]; + constraint c_field_valid { + if (64 > m_size) { + value < (64'h1 << m_size); + } + } + function void configure(int unsigned size); + value = 0; + m_size = size; + endfunction + function void tally; + for (int b = 0; b < 64; b++) if (value[b]) m_ones[b]++; + endfunction +endclass + +class regA; + rand uvm_reg_field fa1, fa15, fa31, fa32; + function new; + fa1 = new; + fa15 = new; + fa31 = new; + fa32 = new; + fa1.configure(1); + fa15.configure(15); + fa31.configure(31); + fa32.configure(32); + endfunction +endclass + +module t; + regA r; + int unsigned i; + // 200 trials over uvm_reg_field-shaped `value < (1<