Fixes #7563
This commit is contained in:
parent
125bdb45f5
commit
fa0f814686
|
|
@ -30,6 +30,18 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <streambuf>
|
#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 1
|
||||||
#define _VL_SOLVER_HASH_LEN_TOTAL 4
|
#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.
|
// 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.
|
// First SAT phase wins. If hard-only is UNSAT, report via unsat-core.
|
||||||
os << "(set-option :produce-models true)\n";
|
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 << "(set-logic QF_ABV)\n";
|
||||||
os << "(define-fun __Vbv ((b Bool)) (_ BitVec 1) (ite b #b1 #b0))\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";
|
os << "(define-fun __Vbool ((v (_ BitVec 1))) Bool (= #b1 v))\n";
|
||||||
|
|
@ -616,15 +630,66 @@ bool VlRandomizer::next(VlRNG& rngr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pinned vars: salting cannot diversify, only burn solver calls.
|
|
||||||
if (!m_checkOnly) {
|
if (!m_checkOnly) {
|
||||||
for (int i = 0; i < _VL_SOLVER_HASH_LEN_TOTAL && sat; ++i) {
|
bool hasArray = false;
|
||||||
os << "(assert ";
|
for (const auto& var : m_vars) {
|
||||||
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN);
|
if (var.second->dimension() > 0) {
|
||||||
os << ")\n";
|
hasArray = true;
|
||||||
os << "\n(check-sat)\n";
|
break;
|
||||||
sat = parseSolution(os, false);
|
}
|
||||||
(void)sat;
|
}
|
||||||
|
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";
|
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) {
|
bool VlRandomizer::parseSolution(std::iostream& os, bool log) {
|
||||||
std::string sat;
|
std::string sat;
|
||||||
do { std::getline(os, sat); } while (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);
|
void randomConstraint(std::ostream& os, VlRNG& rngr, int bits);
|
||||||
bool parseSolution(std::iostream& os, bool log = false);
|
bool parseSolution(std::iostream& os, bool log = false);
|
||||||
bool checkSat(std::iostream& os);
|
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 emitRandcExclusions(std::ostream& os) const; // Emit randc exclusion constraints
|
||||||
void recordRandcValues(); // Record solved randc values for future exclusion
|
void recordRandcValues(); // Record solved randc values for future exclusion
|
||||||
size_t hashConstraints() const;
|
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