Support per-process RNG for process::srandom() and object seeding (#7408) (#7415)

Fixes #7408.
This commit is contained in:
Yilou Wang 2026-04-13 19:58:53 +02:00 committed by GitHub
parent fd7a3f4a16
commit 6ba45d3383
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 236 additions and 44 deletions

View File

@ -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

View File

@ -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"));

View File

@ -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;

View File

@ -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:

View File

@ -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()) {

View File

@ -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;
}

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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