From 6ba45d3383eacac7d49dc8812488d8102589b034 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 13 Apr 2026 19:58:53 +0200 Subject: [PATCH] Support per-process RNG for process::srandom() and object seeding (#7408) (#7415) Fixes #7408. --- include/verilated.cpp | 21 +++++-- include/verilated_std.sv | 11 ---- include/verilated_timing.cpp | 3 + include/verilated_types.h | 63 ++++++++++++------- src/V3EmitCFunc.h | 8 ++- src/V3Randomize.cpp | 10 ++- test_regress/t/t_process_rand_state.py | 21 +++++++ test_regress/t/t_process_rand_state.v | 69 +++++++++++++++++++++ test_regress/t/t_process_rand_state_fork.py | 21 +++++++ test_regress/t/t_process_rand_state_fork.v | 53 ++++++++++++++++ 10 files changed, 236 insertions(+), 44 deletions(-) create mode 100755 test_regress/t/t_process_rand_state.py create mode 100644 test_regress/t/t_process_rand_state.v create mode 100755 test_regress/t/t_process_rand_state_fork.py create mode 100644 test_regress/t/t_process_rand_state_fork.v diff --git a/include/verilated.cpp b/include/verilated.cpp index eb31412b1..3e20be69d 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -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& 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 diff --git a/include/verilated_std.sv b/include/verilated_std.sv index 51695cf17..a8ea20692 100644 --- a/include/verilated_std.sv +++ b/include/verilated_std.sv @@ -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")); diff --git a/include/verilated_timing.cpp b/include/verilated_timing.cpp index 3a2272897..58883a08b 100644 --- a/include/verilated_timing.cpp +++ b/include/verilated_timing.cpp @@ -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; diff --git a/include/verilated_types.h b/include/verilated_types.h index e22ea47ca..8a3360016 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -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 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; 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 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: diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 6192c8c33..d9b6bb9e3 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -388,8 +388,14 @@ public: }); if (m_instantiatesOwnProcess) { AstCStmt* const vlprocp = new AstCStmt{nodep->fileline()}; - vlprocp->add("VlProcessRef vlProcess = std::make_shared();"); + vlprocp->add("VlProcessRef vlProcess = std::make_shared();\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()) { diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index c603eebbf..90904b61c 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -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; } diff --git a/test_regress/t/t_process_rand_state.py b/test_regress/t/t_process_rand_state.py new file mode 100755 index 000000000..a4c5f8ea1 --- /dev/null +++ b/test_regress/t/t_process_rand_state.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(verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_process_rand_state.v b/test_regress/t/t_process_rand_state.v new file mode 100644 index 000000000..0ad027605 --- /dev/null +++ b/test_regress/t/t_process_rand_state.v @@ -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 diff --git a/test_regress/t/t_process_rand_state_fork.py b/test_regress/t/t_process_rand_state_fork.py new file mode 100755 index 000000000..a4c5f8ea1 --- /dev/null +++ b/test_regress/t/t_process_rand_state_fork.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(verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_process_rand_state_fork.v b/test_regress/t/t_process_rand_state_fork.v new file mode 100644 index 000000000..fd75eac00 --- /dev/null +++ b/test_regress/t/t_process_rand_state_fork.v @@ -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