Fixes #7563
This commit is contained in:
parent
125bdb45f5
commit
fa0f814686
|
|
@ -30,6 +30,18 @@
|
|||
#include <sstream>
|
||||
#include <streambuf>
|
||||
|
||||
// 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<<N)`, issue #7563) while keeping run-to-run
|
||||
// diversity on tightly coupled bits. Array/queue rand vars skip pinning
|
||||
// (per-element ranges aren't power-of-2 boundaries) and use the XOR rounds.
|
||||
#define _VL_SOLVER_HASH_LEN 1
|
||||
#define _VL_SOLVER_HASH_LEN_TOTAL 4
|
||||
|
||||
|
|
@ -516,6 +528,8 @@ bool VlRandomizer::next(VlRNG& rngr) {
|
|||
// Try hard + soft[0..N-1], then hard + soft[1..N-1], ..., then hard only.
|
||||
// First SAT phase wins. If hard-only is UNSAT, report via unsat-core.
|
||||
os << "(set-option :produce-models true)\n";
|
||||
// Lets the scalar pin path learn which free-bit assumptions conflict.
|
||||
os << "(set-option :produce-unsat-assumptions true)\n";
|
||||
os << "(set-logic QF_ABV)\n";
|
||||
os << "(define-fun __Vbv ((b Bool)) (_ BitVec 1) (ite b #b1 #b0))\n";
|
||||
os << "(define-fun __Vbool ((v (_ BitVec 1))) Bool (= #b1 v))\n";
|
||||
|
|
@ -616,15 +630,66 @@ bool VlRandomizer::next(VlRNG& rngr) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Pinned vars: salting cannot diversify, only burn solver calls.
|
||||
if (!m_checkOnly) {
|
||||
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;
|
||||
bool hasArray = false;
|
||||
for (const auto& var : m_vars) {
|
||||
if (var.second->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<bool> 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<bool> 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<int> 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<int> 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<N>" literals; collect each full integer run.
|
||||
std::vector<int> idxs;
|
||||
std::string num;
|
||||
for (const char c : line) {
|
||||
if (std::isdigit(static_cast<unsigned char>(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 == "");
|
||||
|
|
|
|||
|
|
@ -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<N>" literals named by (get-unsat-assumptions).
|
||||
std::vector<int> 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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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<<m_size)`. Each free bit
|
||||
// should be a fair coin flip; the pre-fix bug pinned them near the boundary
|
||||
// K-1 (140-180 ones, 70-90%). Band [70, 130] = [35%, 65%] is ~4.2 sigma off
|
||||
// the fair-50% mean (Binomial(200, 0.5)), so a uniform mechanism passes
|
||||
// ~99.7% per run while the boundary bias overruns the upper bound.
|
||||
localparam int unsigned TRIALS = 200;
|
||||
localparam int unsigned HI = 130;
|
||||
localparam int unsigned LO = 70;
|
||||
|
||||
initial begin
|
||||
r = new;
|
||||
for (int t = 0; t < TRIALS; t++) begin
|
||||
i = r.randomize();
|
||||
`checkd(i, 1);
|
||||
r.fa1.tally;
|
||||
r.fa15.tally;
|
||||
r.fa31.tally;
|
||||
r.fa32.tally;
|
||||
end
|
||||
// Symmetric 35-65% band per free bit. Master FAILs the upper bound.
|
||||
for (int b = 0; b < 15; b++) `check_le(r.fa15.m_ones[b], HI);
|
||||
for (int b = 0; b < 31; b++) `check_le(r.fa31.m_ones[b], HI);
|
||||
for (int b = 0; b < 32; b++) `check_le(r.fa32.m_ones[b], HI);
|
||||
for (int b = 0; b < 15; b++) if (r.fa15.m_ones[b] < LO) begin $write("%%Error: fa15[%0d] ones=%0d < %0d\n", b, r.fa15.m_ones[b], LO); `stop; end
|
||||
for (int b = 0; b < 31; b++) if (r.fa31.m_ones[b] < LO) begin $write("%%Error: fa31[%0d] ones=%0d < %0d\n", b, r.fa31.m_ones[b], LO); `stop; end
|
||||
for (int b = 0; b < 32; b++) if (r.fa32.m_ones[b] < LO) begin $write("%%Error: fa32[%0d] ones=%0d < %0d\n", b, r.fa32.m_ones[b], LO); `stop; end
|
||||
// High bits beyond m_size must remain 0.
|
||||
for (int b = 1; b < 64; b++) `checkd(r.fa1.m_ones[b], 0);
|
||||
for (int b = 15; b < 64; b++) `checkd(r.fa15.m_ones[b], 0);
|
||||
for (int b = 31; b < 64; b++) `checkd(r.fa31.m_ones[b], 0);
|
||||
for (int b = 32; b < 64; b++) `checkd(r.fa32.m_ones[b], 0);
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Loading…
Reference in New Issue