Fixes #7408.
This commit is contained in:
parent
fd7a3f4a16
commit
6ba45d3383
|
|
@ -330,11 +330,13 @@ static size_t _vl_snprintf_string(std::string& str, const char* format,
|
|||
//===========================================================================
|
||||
// Process -- parts of std::process implementation
|
||||
|
||||
std::string VlProcess::randstate() const VL_MT_UNSAFE {
|
||||
return VlRNG::vl_thread_rng().get_randstate();
|
||||
}
|
||||
void VlProcess::randstate(const std::string& state) VL_MT_UNSAFE {
|
||||
VlRNG::vl_thread_rng().set_randstate(state);
|
||||
thread_local VlProcess* VlProcess::t_currentp = nullptr;
|
||||
|
||||
std::string VlProcess::randstate() const VL_MT_UNSAFE { return m_rng.get_randstate(); }
|
||||
void VlProcess::randstate(const std::string& state) VL_MT_UNSAFE { m_rng.set_randstate(state); }
|
||||
VlRNG& VlProcess::currentRng() VL_MT_SAFE {
|
||||
if (t_currentp) return t_currentp->m_rng;
|
||||
return VlRNG::vl_thread_rng();
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
@ -380,7 +382,8 @@ vl_rng_compute_new_state(const std::array<uint64_t, 2>& current_state) VL_PURE {
|
|||
}
|
||||
|
||||
VlRNG::VlRNG() VL_MT_SAFE {
|
||||
VlRNG& fromr = vl_thread_rng();
|
||||
// Seed from process RNG if in a process, else thread RNG (IEEE 1800-2023 18.14.1)
|
||||
VlRNG& fromr = VlProcess::currentRng();
|
||||
|
||||
const uint64_t s0 = vl_rng_result(fromr.m_state);
|
||||
fromr.m_state = vl_rng_compute_new_state(fromr.m_state);
|
||||
|
|
@ -405,6 +408,12 @@ uint64_t VlRNG::vl_thread_rng_rand64() VL_MT_SAFE {
|
|||
fromr.m_state = vl_rng_compute_new_state(fromr.m_state);
|
||||
return result;
|
||||
}
|
||||
uint64_t VlRNG::vl_current_rng_rand64() VL_MT_SAFE {
|
||||
VlRNG& fromr = VlProcess::currentRng();
|
||||
const uint64_t result = vl_rng_result(fromr.m_state);
|
||||
fromr.m_state = vl_rng_compute_new_state(fromr.m_state);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string VlRNG::get_randstate() const VL_MT_UNSAFE {
|
||||
// Though not stated in IEEE, assumption is the string must be printable
|
||||
|
|
|
|||
|
|
@ -215,17 +215,6 @@ inline bool VlClassRef<`systemc_class_name>::operator<(const VlClassRef<`systemc
|
|||
`endif
|
||||
// verilog_format: on
|
||||
|
||||
// When really implemented, srandom must operate on the process, but for
|
||||
// now rely on the srandom() that is automatically generated for all
|
||||
// classes.
|
||||
//
|
||||
// function void srandom(int seed);
|
||||
// endfunction
|
||||
|
||||
// The methods below access the common RNG, full support
|
||||
// of get_randstate/set_randstate requires accessing the RNG state
|
||||
// of the specified process (see IEEE 1800-2023, 18.14.), but as for
|
||||
// now processes do not have their own RNGs.
|
||||
function string get_randstate();
|
||||
// Initialize with $c to ensure it won't be constified
|
||||
string s = string'($c("0"));
|
||||
|
|
|
|||
|
|
@ -36,9 +36,12 @@ void VlCoroutineHandle::resume() {
|
|||
m_coro.destroy();
|
||||
} else {
|
||||
m_process->state(VlProcess::RUNNING);
|
||||
VlProcess::currentp(m_process.get());
|
||||
m_coro();
|
||||
VlProcess::currentp(nullptr);
|
||||
}
|
||||
} else {
|
||||
VlProcess::currentp(nullptr);
|
||||
m_coro();
|
||||
}
|
||||
m_coro = nullptr;
|
||||
|
|
|
|||
|
|
@ -110,6 +110,30 @@ constexpr IData VL_CLOG2_CE_Q(QData lhs) VL_PURE {
|
|||
return lhs <= 1 ? 0 : VL_CLOG2_CE_Q((lhs + 1) >> 1ULL) + 1;
|
||||
}
|
||||
|
||||
//===================================================================
|
||||
// Random
|
||||
|
||||
// Random Number Generator with internal state
|
||||
class VlRNG final {
|
||||
std::array<uint64_t, 2> m_state;
|
||||
|
||||
public:
|
||||
// The default constructor simply sets state, to avoid vl_rand64()
|
||||
// having to check for construction at each call
|
||||
// Alternative: seed with zero and check on rand64() call
|
||||
VlRNG() VL_MT_SAFE;
|
||||
explicit VlRNG(uint64_t seed) VL_PURE;
|
||||
void srandom(uint64_t n) VL_MT_UNSAFE;
|
||||
std::string get_randstate() const VL_MT_UNSAFE;
|
||||
void set_randstate(const std::string& state) VL_MT_UNSAFE;
|
||||
uint64_t rand64() VL_MT_UNSAFE;
|
||||
// Threadsafe, but requires use on vl_thread_rng or vl_current_rng
|
||||
static uint64_t vl_thread_rng_rand64() VL_MT_SAFE;
|
||||
static uint64_t vl_current_rng_rand64() VL_MT_SAFE;
|
||||
static VlRNG& vl_thread_rng() VL_MT_SAFE;
|
||||
};
|
||||
|
||||
//===================================================================
|
||||
// Metadata of processes
|
||||
using VlProcessRef = std::shared_ptr<VlProcess>;
|
||||
class VlForkSync;
|
||||
|
|
@ -123,6 +147,10 @@ class VlProcess final {
|
|||
VlForkSyncState* m_forkSyncOnKillp
|
||||
= nullptr; // Optional fork..join counter to decrement on kill
|
||||
bool m_forkSyncOnKillDone = false; // Ensure on-kill callback fires only once
|
||||
VlRNG m_rng; // Per-process RNG (IEEE 1800-2023 18.14)
|
||||
|
||||
// Thread-local current process pointer for hierarchical object seeding
|
||||
static thread_local VlProcess* t_currentp;
|
||||
|
||||
public:
|
||||
// TYPES
|
||||
|
|
@ -172,12 +200,23 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
// Random state (IEEE 1800-2023 9.7, 18.14)
|
||||
void srandom(uint64_t seed) VL_MT_UNSAFE { m_rng.srandom(seed); }
|
||||
std::string randstate() const VL_MT_UNSAFE;
|
||||
void randstate(const std::string& state) VL_MT_UNSAFE;
|
||||
|
||||
// Current process tracking for hierarchical object seeding
|
||||
static VlProcess* currentp() VL_MT_UNSAFE { return t_currentp; }
|
||||
static void currentp(VlProcess* processp) VL_MT_UNSAFE { t_currentp = processp; }
|
||||
// Return process RNG if in a process, else thread RNG
|
||||
static VlRNG& currentRng() VL_MT_SAFE;
|
||||
};
|
||||
|
||||
inline std::string VL_TO_STRING(const VlProcessRef&) { return std::string("process"); }
|
||||
|
||||
// Use process RNG if in a process, else thread RNG (IEEE 1800-2023 18.14)
|
||||
inline uint64_t vl_rand64() VL_MT_SAFE { return VlRNG::vl_current_rng_rand64(); }
|
||||
|
||||
//===================================================================
|
||||
// SystemVerilog event type
|
||||
|
||||
|
|
@ -244,30 +283,6 @@ inline std::string VL_TO_STRING(const VlEventBase& e) {
|
|||
return "triggered="s + (e.isTriggered() ? "true" : "false");
|
||||
}
|
||||
|
||||
//===================================================================
|
||||
// Random
|
||||
|
||||
// Random Number Generator with internal state
|
||||
class VlRNG final {
|
||||
std::array<uint64_t, 2> m_state;
|
||||
|
||||
public:
|
||||
// The default constructor simply sets state, to avoid vl_rand64()
|
||||
// having to check for construction at each call
|
||||
// Alternative: seed with zero and check on rand64() call
|
||||
VlRNG() VL_MT_SAFE;
|
||||
explicit VlRNG(uint64_t seed) VL_PURE;
|
||||
void srandom(uint64_t n) VL_MT_UNSAFE;
|
||||
std::string get_randstate() const VL_MT_UNSAFE;
|
||||
void set_randstate(const std::string& state) VL_MT_UNSAFE;
|
||||
uint64_t rand64() VL_MT_UNSAFE;
|
||||
// Threadsafe, but requires use on vl_thread_rng
|
||||
static uint64_t vl_thread_rng_rand64() VL_MT_SAFE;
|
||||
static VlRNG& vl_thread_rng() VL_MT_SAFE;
|
||||
};
|
||||
|
||||
inline uint64_t vl_rand64() VL_MT_SAFE { return VlRNG::vl_thread_rng_rand64(); }
|
||||
|
||||
// RNG for shuffle()
|
||||
class VlURNG final {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -388,8 +388,14 @@ public:
|
|||
});
|
||||
if (m_instantiatesOwnProcess) {
|
||||
AstCStmt* const vlprocp = new AstCStmt{nodep->fileline()};
|
||||
vlprocp->add("VlProcessRef vlProcess = std::make_shared<VlProcess>();");
|
||||
vlprocp->add("VlProcessRef vlProcess = std::make_shared<VlProcess>();\n");
|
||||
vlprocp->add("VlProcess::currentp(vlProcess.get());");
|
||||
nodep->stmtsp()->addHereThisAsNext(vlprocp);
|
||||
} else if (nodep->needProcess() && nodep->stmtsp()) {
|
||||
// Set current process so VlRNG() constructors in this function seed from it
|
||||
AstCStmt* const setProcessp = new AstCStmt{nodep->fileline()};
|
||||
setProcessp->add("VlProcess::currentp(vlProcess.get());");
|
||||
nodep->stmtsp()->addHereThisAsNext(setProcessp);
|
||||
}
|
||||
|
||||
for (AstNode* subnodep = nodep->argsp(); subnodep; subnodep = subnodep->nextp()) {
|
||||
|
|
|
|||
|
|
@ -5061,8 +5061,14 @@ AstFunc* V3Randomize::newSRandomFunc(VMemberMap& memberMap, AstClass* nodep) {
|
|||
funcp->isVirtual(false);
|
||||
basep->addMembersp(funcp);
|
||||
memberMap.insert(nodep, funcp);
|
||||
funcp->addStmtsp(new AstCStmt{basep->fileline(), "__Vm_rng.srandom(seed);"});
|
||||
basep->needRNG(true);
|
||||
// For std::process, seed the per-process RNG via m_process->srandom()
|
||||
// For regular classes, seed the per-object RNG via __Vm_rng
|
||||
if (basep->name() == "process") {
|
||||
funcp->addStmtsp(new AstCStmt{basep->fileline(), "__PVT__m_process->srandom(seed);"});
|
||||
} else {
|
||||
funcp->addStmtsp(new AstCStmt{basep->fileline(), "__Vm_rng.srandom(seed);"});
|
||||
basep->needRNG(true);
|
||||
}
|
||||
}
|
||||
return funcp;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(verilator_flags2=['--timing'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// 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 checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
||||
|
||||
// verilog_format: on
|
||||
|
||||
typedef byte unsigned uint8_t;
|
||||
|
||||
class D;
|
||||
rand uint8_t x;
|
||||
endclass
|
||||
|
||||
class test;
|
||||
|
||||
D d;
|
||||
|
||||
task run_phase;
|
||||
process p;
|
||||
uint8_t result;
|
||||
string randstate;
|
||||
|
||||
// Pass 1: seed process, create object, randomize, record result
|
||||
p = process::self();
|
||||
p.srandom(100);
|
||||
d = new;
|
||||
// Save randstate AFTER d=new (d=new advances process RNG per IEEE 18.14.1)
|
||||
randstate = p.get_randstate();
|
||||
`checkd(d.randomize(), 1);
|
||||
result = d.x;
|
||||
|
||||
// Pass 2: same seed -> same sequence -> same result
|
||||
p.srandom(100);
|
||||
d = new;
|
||||
`checks(p.get_randstate(), randstate);
|
||||
`checkd(d.randomize(), 1);
|
||||
`checkd(d.x, result);
|
||||
|
||||
// Pass 3: same seed, with intervening task call -> same result
|
||||
p.srandom(100);
|
||||
other_task();
|
||||
d = new;
|
||||
`checks(p.get_randstate(), randstate);
|
||||
`checkd(d.randomize(), 1);
|
||||
`checkd(d.x, result);
|
||||
endtask
|
||||
|
||||
task other_task;
|
||||
// verilator no_inline_task
|
||||
$display("Other task");
|
||||
endtask
|
||||
|
||||
endclass
|
||||
|
||||
module t;
|
||||
initial begin
|
||||
test c;
|
||||
c = new;
|
||||
c.run_phase();
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -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(verilator_flags2=['--timing'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// 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);
|
||||
|
||||
// verilog_format: on
|
||||
|
||||
typedef byte unsigned uint8_t;
|
||||
|
||||
class D;
|
||||
rand uint8_t x;
|
||||
endclass
|
||||
|
||||
module t;
|
||||
uint8_t result_a;
|
||||
uint8_t result_b;
|
||||
|
||||
initial begin
|
||||
// Test that two fork branches with same seed produce same results
|
||||
// (each branch gets its own process with independent RNG)
|
||||
fork
|
||||
begin
|
||||
process p;
|
||||
D d;
|
||||
p = process::self();
|
||||
p.srandom(42);
|
||||
d = new;
|
||||
`checkd(d.randomize(), 1);
|
||||
result_a = d.x;
|
||||
end
|
||||
begin
|
||||
process p;
|
||||
D d;
|
||||
p = process::self();
|
||||
p.srandom(42);
|
||||
d = new;
|
||||
`checkd(d.randomize(), 1);
|
||||
result_b = d.x;
|
||||
end
|
||||
join
|
||||
|
||||
// Both branches seeded identically -> same result
|
||||
`checkd(result_a, result_b);
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Loading…
Reference in New Issue