diff --git a/Changes b/Changes index 624ac0552..23fb8fa80 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,7 @@ Verilator 5.009 devel * Add --public-params flag (#3990). [Andrew Nolte] * Add STATICVAR warning and convert to automatic (#4018) (#4027) (#4030). [Ryszard Rozak, Antmicro Ltd] * Support class extends of package::class. +* Support class srandom and class random stability. * Support method calls without parenthesis (#4034). [Ryszard Rozak, Antmicro Ltd] * Support complicated IEEE 'for' assignments. * Support $fopen as an expression. diff --git a/include/verilated.cpp b/include/verilated.cpp index bcc4f65e8..26f9e32fa 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -279,6 +279,52 @@ void VL_PRINTF_MT(const char* formatp, ...) VL_MT_SAFE { //=========================================================================== // Random -- Mostly called at init time, so not inline. +VlRNG::VlRNG() VL_MT_SAFE { + // Starting point for this new class comes from the global RNG + VlRNG& fromr = vl_thread_rng(); + m_state = fromr.m_state; + // Advance the *source* so it can later generate a new number + // Xoroshiro128+ algorithm + fromr.m_state[1] ^= fromr.m_state[0]; + fromr.m_state[0] = (((fromr.m_state[0] << 55) | (fromr.m_state[0] >> 9)) ^ fromr.m_state[1] + ^ (fromr.m_state[1] << 14)); + fromr.m_state[1] = (fromr.m_state[1] << 36) | (fromr.m_state[1] >> 28); +} +uint64_t VlRNG::rand64() VL_MT_UNSAFE { + // Xoroshiro128+ algorithm + const uint64_t result = m_state[0] + m_state[1]; + m_state[1] ^= m_state[0]; + m_state[0] = (((m_state[0] << 55) | (m_state[0] >> 9)) ^ m_state[1] ^ (m_state[1] << 14)); + m_state[1] = (m_state[1] << 36) | (m_state[1] >> 28); + return result; +} +uint64_t VlRNG::vl_thread_rng_rand64() VL_MT_SAFE { + VlRNG& fromr = vl_thread_rng(); + const uint64_t result = fromr.m_state[0] + fromr.m_state[1]; + fromr.m_state[1] ^= fromr.m_state[0]; + fromr.m_state[0] = (((fromr.m_state[0] << 55) | (fromr.m_state[0] >> 9)) ^ fromr.m_state[1] + ^ (fromr.m_state[1] << 14)); + fromr.m_state[1] = (fromr.m_state[1] << 36) | (fromr.m_state[1] >> 28); + return result; +} +void VlRNG::srandom(uint64_t n) VL_MT_UNSAFE { + m_state[0] = n; + m_state[1] = m_state[0]; + // Fix state as algorithm is slow to randomize if many zeros + // This causes a loss of ~ 1 bit of seed entropy, no big deal + if (VL_COUNTONES_I(m_state[0]) < 10) m_state[0] = ~m_state[0]; + if (VL_COUNTONES_I(m_state[1]) < 10) m_state[1] = ~m_state[1]; +} +// Unused: void VlRNG::set_randstate(const std::string& state) VL_MT_UNSAFE { +// Unused: if (VL_LIKELY(state.length() == sizeof(m_state))) { +// Unused: memcpy(m_state, state.data(), sizeof(m_state)); +// Unused: } +// Unused: } +// Unused: std::string VlRNG::get_randstate() const VL_MT_UNSAFE { +// Unused: std::string out{reinterpret_cast(&m_state), sizeof(m_state)}; +// Unused: return out; +// Unused: } + static uint32_t vl_sys_rand32() VL_MT_SAFE { // Return random 32-bits using system library. // Used only to construct seed for Verilator's PRNG. @@ -292,27 +338,23 @@ static uint32_t vl_sys_rand32() VL_MT_SAFE { #endif } -uint64_t vl_rand64() VL_MT_SAFE { - static thread_local uint64_t t_state[2]; +VlRNG& VlRNG::vl_thread_rng() VL_MT_SAFE { + static thread_local VlRNG t_rng{0}; static thread_local uint32_t t_seedEpoch = 0; // For speed, we use a thread-local epoch number to know when to reseed // A thread always belongs to a single context, so this works out ok if (VL_UNLIKELY(t_seedEpoch != VerilatedContextImp::randSeedEpoch())) { // Set epoch before state, to avoid race case with new seeding t_seedEpoch = VerilatedContextImp::randSeedEpoch(); - t_state[0] = Verilated::threadContextp()->impp()->randSeedDefault64(); - t_state[1] = t_state[0]; + // Same as srandom() but here as needs to be VL_MT_SAFE + t_rng.m_state[0] = Verilated::threadContextp()->impp()->randSeedDefault64(); + t_rng.m_state[1] = t_rng.m_state[0]; // Fix state as algorithm is slow to randomize if many zeros // This causes a loss of ~ 1 bit of seed entropy, no big deal - if (VL_COUNTONES_I(t_state[0]) < 10) t_state[0] = ~t_state[0]; - if (VL_COUNTONES_I(t_state[1]) < 10) t_state[1] = ~t_state[1]; + if (VL_COUNTONES_I(t_rng.m_state[0]) < 10) t_rng.m_state[0] = ~t_rng.m_state[0]; + if (VL_COUNTONES_I(t_rng.m_state[1]) < 10) t_rng.m_state[1] = ~t_rng.m_state[1]; } - // Xoroshiro128+ algorithm - const uint64_t result = t_state[0] + t_state[1]; - t_state[1] ^= t_state[0]; - t_state[0] = (((t_state[0] << 55) | (t_state[0] >> 9)) ^ t_state[1] ^ (t_state[1] << 14)); - t_state[1] = (t_state[1] << 36) | (t_state[1] >> 28); - return result; + return t_rng; } WDataOutP VL_RANDOM_W(int obits, WDataOutP outwp) VL_MT_SAFE { @@ -321,6 +363,12 @@ WDataOutP VL_RANDOM_W(int obits, WDataOutP outwp) VL_MT_SAFE { return outwp; } +WDataOutP VL_RANDOM_RNG_W(VlRNG& rngr, int obits, WDataOutP outwp) VL_MT_UNSAFE { + for (int i = 0; i < VL_WORDS_I(obits); ++i) outwp[i] = rngr.rand64(); + // Last word is unclean + return outwp; +} + IData VL_RANDOM_SEEDED_II(IData& seedr) VL_MT_SAFE { // $random - seed is a new seed to apply, then we return new seed Verilated::threadContextp()->randSeed(static_cast(seedr)); diff --git a/include/verilated_types.h b/include/verilated_types.h index cd00c5710..d506a54d6 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -136,14 +136,34 @@ public: }; inline std::string VL_TO_STRING(const VlEvent& e) { - return std::string("triggered=") + (e.isTriggered() ? "true" : "false"); + return std::string{"triggered="} + (e.isTriggered() ? "true" : "false"); } //=================================================================== -// Shuffle RNG +// Random -extern uint64_t vl_rand64() VL_MT_SAFE; +// 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; + VlRNG(uint64_t seed0) VL_MT_SAFE : m_state{0x12341234UL, seed0} {} + void srandom(uint64_t n) VL_MT_UNSAFE; + // Unused: std::string get_randstate() const VL_MT_UNSAFE; + // Unused: 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: using result_type = size_t; @@ -152,6 +172,11 @@ public: size_t operator()() { return VL_MASK_I(31) & vl_rand64(); } }; +// These require the class object to have the thread safety lock +inline IData VL_RANDOM_RNG_I(VlRNG& rngr) VL_MT_UNSAFE { return rngr.rand64(); } +inline QData VL_RANDOM_RNG_Q(VlRNG& rngr) VL_MT_UNSAFE { return rngr.rand64(); } +extern WDataOutP VL_RANDOM_RNG_W(VlRNG& rngr, int obits, WDataOutP outwp) VL_MT_UNSAFE; + //=================================================================== // Readmem/Writemem operation classes diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 575bac814..635b9edb4 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -1620,6 +1620,26 @@ public: bool reset() const { return m_reset; } bool urandom() const { return m_urandom; } }; +class AstRandRNG final : public AstNodeExpr { + // Random used in a class using VlRNG + // Return a random number, based upon width() +public: + AstRandRNG(FileLine* fl, AstNodeDType* dtp) + : ASTGEN_SUPER_RandRNG(fl) { + dtypep(dtp); + } + ASTGEN_MEMBERS_AstRandRNG; + string emitVerilog() override { return "%f$rngrandom()"; } + string emitC() override { + return isWide() ? "VL_RANDOM_RNG_%nq(__Vm_rng, %nw, %P)" // + : "VL_RANDOM_RNG_%nq(__Vm_rng)"; + } + bool cleanOut() const override { return false; } + bool isGateOptimizable() const override { return false; } + bool isPredictOptimizable() const override { return false; } + int instrCount() const override { return INSTR_COUNT_PLI; } + bool same(const AstNode* /*samep*/) const override { return true; } +}; class AstRose final : public AstNodeExpr { // Verilog $rose // @astgen op1 := exprp : AstNodeExpr diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index f137413fe..a9b1ecb10 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2116,6 +2116,7 @@ class AstClass final : public AstNodeModule { AstClassPackage* m_classOrPackagep = nullptr; // Class package this is under bool m_extended = false; // Is extension or extended by other classes bool m_interfaceClass = false; // Interface class + bool m_needRNG = false; // Need RNG, uses srandom/randomize bool m_virtual = false; // Virtual class void insertCache(AstNode* nodep); @@ -2148,9 +2149,13 @@ public: void isInterfaceClass(bool flag) { m_interfaceClass = flag; } bool isVirtual() const { return m_virtual; } void isVirtual(bool flag) { m_virtual = flag; } + bool needRNG() const { return m_needRNG; } + void needRNG(bool flag) { m_needRNG = flag; } // Return true if this class is an extension of base class (SLOW) // Accepts nullptrs static bool isClassExtendedFrom(const AstClass* refClassp, const AstClass* baseClassp); + // Return the lowest class extended from, or this class + AstClass* baseMostClassp(); }; class AstClassPackage final : public AstNodeModule { // The static information portion of a class (treated similarly to a package) diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index d9b8146ab..fd7d78fc5 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1455,6 +1455,13 @@ void AstClass::repairCache() { } } } +AstClass* AstClass::baseMostClassp() { + AstClass* basep = this; + while (basep->extendsp() && basep->extendsp()->classp()) { + basep = basep->extendsp()->classp(); + } + return basep; +} bool AstClass::isClassExtendedFrom(const AstClass* refClassp, const AstClass* baseClassp) { // TAIL RECURSIVE if (!refClassp || !baseClassp) return false; diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 432f2368b..83997f570 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -905,6 +905,9 @@ public: void visit(AstRand* nodep) override { emitOpName(nodep, nodep->emitC(), nodep->seedp(), nullptr, nullptr); } + void visit(AstRandRNG* nodep) override { + emitOpName(nodep, nodep->emitC(), nullptr, nullptr, nullptr); + } void visit(AstTime* nodep) override { puts("VL_TIME_UNITED_Q("); if (nodep->timeunit().isNone()) nodep->v3fatalSrc("$time has no units"); diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index bf11c92f3..63a06a956 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -117,7 +117,12 @@ class EmitCHeader final : public EmitCConstInit { emitCurrentList(); } void emitInternalVarDecls(const AstNodeModule* modp) { - if (!VN_IS(modp, Class)) { + if (const AstClass* const classp = VN_CAST(modp, Class)) { + if (classp->needRNG()) { + putsDecoration("\n// INTERNAL VARIABLES\n"); + puts("VlRNG __Vm_rng;\n"); + } + } else { // not class putsDecoration("\n// INTERNAL VARIABLES\n"); puts(symClassName() + "* const vlSymsp;\n"); } diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 28ba20462..f6e4c97fb 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -3102,6 +3102,8 @@ private: } } else if (VN_IS(nodep, New) && m_statep->forPrearray()) { // Resolved in V3Width + } else if (nodep->name() == "randomize" || nodep->name() == "srandom") { + // Resolved in V3Width } else if (nodep->dotted() == "") { if (nodep->pli()) { if (v3Global.opt.bboxSys()) { diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp index 55428d1df..df1872d19 100644 --- a/src/V3LinkResolve.cpp +++ b/src/V3LinkResolve.cpp @@ -115,7 +115,7 @@ private: if (m_underGenerate) nodep->underGenerate(true); // Remember the existing symbol table scope if (m_classp) { - if (nodep->name() == "randomize") { + if (nodep->name() == "randomize" || nodep->name() == "srandom") { nodep->v3error(nodep->prettyNameQ() << " is a predefined class method; redefinition not allowed" " (IEEE 1800-2017 18.6.3)"); diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 540d1526d..5cc257f7e 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -287,6 +287,10 @@ private: iterateChildren(nodep); checkNode(nodep); } + void visit(AstRandRNG* nodep) override { + iterateChildren(nodep); + checkNode(nodep); + } void visit(AstUCFunc* nodep) override { iterateChildren(nodep); checkNode(nodep); diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 1f50e83b3..34c90bf9a 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -188,15 +188,15 @@ private: AstVarRef* const tabRefp = new AstVarRef{fl, enumValueTabp(enumDtp), VAccess::READ}; tabRefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp()); - AstRand* const randp = new AstRand{fl, nullptr, false}; + AstRandRNG* const randp + = new AstRandRNG{fl, varrefp->findBasicDType(VBasicDTypeKwd::UINT32)}; AstNodeExpr* const moddivp = new AstModDiv{ fl, randp, new AstConst{fl, static_cast(enumDtp->itemCount())}}; - randp->dtypep(varrefp->findBasicDType(VBasicDTypeKwd::UINT32)); moddivp->dtypep(enumDtp); valp = new AstArraySel{fl, tabRefp, moddivp}; } else { - valp = new AstRand{fl, nullptr, false}; - valp->dtypep(memberp ? memberp->dtypep() : varrefp->varp()->dtypep()); + valp = new AstRandRNG{fl, + (memberp ? memberp->dtypep() : varrefp->varp()->dtypep())}; } return new AstAssign{fl, new AstSel{fl, varrefp, offset + (memberp ? memberp->lsb() : 0), @@ -374,6 +374,7 @@ void V3Randomize::randomizeNetlist(AstNetlist* nodep) { AstFunc* V3Randomize::newRandomizeFunc(AstClass* nodep) { AstFunc* funcp = VN_AS(nodep->findMember("randomize"), Func); if (!funcp) { + v3Global.useRandomizeMethods(true); AstNodeDType* const dtypep = nodep->findBitDType(32, 32, VSigning::SIGNED); // IEEE says int return of 0/1 AstVar* const fvarp = new AstVar{nodep->fileline(), VVarType::MEMBER, "randomize", dtypep}; @@ -387,6 +388,31 @@ AstFunc* V3Randomize::newRandomizeFunc(AstClass* nodep) { funcp->isVirtual(nodep->isExtended()); nodep->addMembersp(funcp); nodep->repairCache(); + AstClass* const basep = nodep->baseMostClassp(); + basep->needRNG(true); + } + return funcp; +} + +AstFunc* V3Randomize::newSRandomFunc(AstClass* nodep) { + AstClass* const basep = nodep->baseMostClassp(); + AstFunc* funcp = VN_AS(basep->findMember("srandom"), Func); + if (!funcp) { + v3Global.useRandomizeMethods(true); + AstNodeDType* const dtypep + = basep->findBitDType(32, 32, VSigning::SIGNED); // IEEE says argument 0/1 + AstVar* const ivarp = new AstVar{basep->fileline(), VVarType::MEMBER, "seed", dtypep}; + ivarp->lifetime(VLifetime::AUTOMATIC); + ivarp->funcLocal(true); + ivarp->direction(VDirection::INPUT); + funcp = new AstFunc{basep->fileline(), "srandom", ivarp, nullptr}; + funcp->dtypep(basep->findVoidDType()); + funcp->classMethod(true); + funcp->isVirtual(false); + basep->addMembersp(funcp); + basep->repairCache(); + funcp->addStmtsp(new AstCStmt{basep->fileline(), "__Vm_rng.srandom(seed);\n"}); + basep->needRNG(true); } return funcp; } diff --git a/src/V3Randomize.h b/src/V3Randomize.h index ff56f9a86..223575203 100644 --- a/src/V3Randomize.h +++ b/src/V3Randomize.h @@ -29,6 +29,7 @@ public: static void randomizeNetlist(AstNetlist* nodep); static AstFunc* newRandomizeFunc(AstClass* nodep); + static AstFunc* newSRandomFunc(AstClass* nodep); }; #endif // Guard diff --git a/src/V3Width.cpp b/src/V3Width.cpp index f805c2b2a..bd70ab255 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -224,6 +224,7 @@ private: // STATE WidthVP* m_vup = nullptr; // Current node state + AstClass* m_classp = nullptr; // Current class const AstCell* m_cellp = nullptr; // Current cell for arrayed instantiations const AstEnumItem* m_enumItemp = nullptr; // Current enum item const AstNodeFTask* m_ftaskp = nullptr; // Current function/task @@ -2596,6 +2597,8 @@ private: if (nodep->didWidthAndSet()) return; // Must do extends first, as we may in functions under this class // start following a tree of extends that takes us to other classes + VL_RESTORER(m_classp); + m_classp = nodep; userIterateAndNext(nodep->extendsp(), nullptr); userIterateChildren(nodep, nullptr); // First size all members nodep->repairCache(); @@ -2611,6 +2614,7 @@ private: // userIterateChildren(nodep->classp(), nullptr); } void visit(AstNodeModule* nodep) override { + // Visitor does not include AstClass - specialized visitor above VL_RESTORER(m_modp); m_modp = nodep; userIterateChildren(nodep, nullptr); @@ -3460,8 +3464,9 @@ private: // No need to width-resolve the class, as it was done when we did the child AstClass* const first_classp = adtypep->classp(); if (nodep->name() == "randomize") { - v3Global.useRandomizeMethods(true); V3Randomize::newRandomizeFunc(first_classp); + } else if (nodep->name() == "srandom") { + V3Randomize::newSRandomFunc(first_classp); } UASSERT_OBJ(first_classp, nodep, "Unlinked"); for (AstClass* classp = first_classp; classp;) { @@ -5504,6 +5509,21 @@ private: // For arguments, is assignment-like context; see IEEE rules in AstNodeAssign // Function hasn't been widthed, so make it so. UINFO(5, " FTASKREF " << nodep << endl); + if (nodep->name() == "randomize" || nodep->name() == "srandom") { + // TODO perhaps this should move to V3LinkDot + if (!m_classp) { + nodep->v3error("Calling implicit class method " << nodep->prettyNameQ() + << " without being under class"); + nodep->replaceWith(new AstConst{nodep->fileline(), 0}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + if (nodep->name() == "randomize") { + nodep->taskp(V3Randomize::newRandomizeFunc(m_classp)); + } else { + nodep->taskp(V3Randomize::newSRandomFunc(m_classp)); + } + } UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked"); if (nodep->didWidth()) return; userIterate(nodep->taskp(), nullptr); diff --git a/test_regress/t/t_randomize_method_bad.out b/test_regress/t/t_randomize_method_bad.out index 80f06eb60..421ba63cf 100644 --- a/test_regress/t/t_randomize_method_bad.out +++ b/test_regress/t/t_randomize_method_bad.out @@ -4,4 +4,7 @@ %Error: t/t_randomize_method_bad.v:14:18: 'randomize' is a predefined class method; redefinition not allowed (IEEE 1800-2017 18.6.3) 14 | function void randomize(int x); | ^~~~~~~~~ +%Error: t/t_randomize_method_bad.v:16:18: 'srandom' is a predefined class method; redefinition not allowed (IEEE 1800-2017 18.6.3) + 16 | function void srandom(int seed); + | ^~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_randomize_method_bad.v b/test_regress/t/t_randomize_method_bad.v index 72da52529..cae21a9b9 100644 --- a/test_regress/t/t_randomize_method_bad.v +++ b/test_regress/t/t_randomize_method_bad.v @@ -13,6 +13,8 @@ endclass class Cls2; function void randomize(int x); endfunction + function void srandom(int seed); + endfunction endclass module t (/*AUTOARG*/); diff --git a/test_regress/t/t_randomize_method_nclass_bad.out b/test_regress/t/t_randomize_method_nclass_bad.out new file mode 100644 index 000000000..6c48315e6 --- /dev/null +++ b/test_regress/t/t_randomize_method_nclass_bad.out @@ -0,0 +1,9 @@ +%Error: t/t_randomize_method_nclass_bad.v:9:7: Calling implicit class method 'randomize' without being under class + : ... In instance t + 9 | randomize(1); + | ^~~~~~~~~ +%Error: t/t_randomize_method_nclass_bad.v:10:7: Calling implicit class method 'srandom' without being under class + : ... In instance t + 10 | srandom(1); + | ^~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_randomize_method_nclass_bad.pl b/test_regress/t/t_randomize_method_nclass_bad.pl new file mode 100755 index 000000000..66fa61649 --- /dev/null +++ b/test_regress/t/t_randomize_method_nclass_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_randomize_method_nclass_bad.v b/test_regress/t/t_randomize_method_nclass_bad.v new file mode 100644 index 000000000..6b58cf27f --- /dev/null +++ b/test_regress/t/t_randomize_method_nclass_bad.v @@ -0,0 +1,12 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2023 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + initial begin + randomize(1); + srandom(1); + end +endmodule diff --git a/test_regress/t/t_randomize_srandom.pl b/test_regress/t/t_randomize_srandom.pl new file mode 100755 index 000000000..aabcde63e --- /dev/null +++ b/test_regress/t/t_randomize_srandom.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_randomize_srandom.v b/test_regress/t/t_randomize_srandom.v new file mode 100644 index 000000000..4fb3a3e8e --- /dev/null +++ b/test_regress/t/t_randomize_srandom.v @@ -0,0 +1,106 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2023 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`define stop $stop +`define checkeq(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) +`define checkne(gotv,expv) do if ((gotv) === (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0) + +class Cls; + bit [63:0] m_sum; + rand int m_r; + function void hash_init(); + m_sum = 64'h5aef0c8d_d70a4497; + endfunction + function void hash(int res); + $display(" res %x", res); + m_sum = {32'h0, res} ^ {m_sum[62:0], m_sum[63] ^ m_sum[2] ^ m_sum[0]}; + endfunction + + function bit [63:0] test1(); + Cls o; + // Affected by srandom + $display(" init for randomize"); + hash_init; + // TODO: Support this.randomize() + o = this; + void'(o.randomize()); + hash(m_r); + void'(o.randomize()); + hash(m_r); + return m_sum; + endfunction + + function bit [63:0] test2(int seed); + $display(" init for seeded randomize"); + hash_init; + this.srandom(seed); + void'(this.randomize()); + hash(m_r); + return m_sum; + endfunction + + function bit [63:0] test3(int seed); + $display(" init for seeded randomize"); + hash_init; + srandom(seed); + void'(randomize()); + hash(m_r); + return m_sum; + endfunction +endclass + +module t(/*AUTOARG*/); + + Cls ca; + Cls cb; + + bit [63:0] sa; + bit [63:0] sb; + + initial begin + // Each class gets different seed from same thread, + // so the randomization should be different + $display("New"); + ca = new; + cb = new; + + sa = ca.test1(); + sb = cb.test1(); + `checkne(sa, sb); // Could false-fail 2^-32 + + // Seed the classes to be synced + $display("Seed"); + ca.srandom(123); + cb.srandom(123); + + sa = ca.test1(); + sb = cb.test1(); + `checkeq(sa, sb); + + // Check using this + $display("this.srandom"); + sa = ca.test2(1); + sb = cb.test2(2); + `checkne(sa, sb); + + sa = ca.test2(3); + sb = cb.test2(3); + `checkeq(sa, sb); + + // Check using direct call + $display("srandom"); + sa = ca.test3(1); + sb = cb.test3(2); + `checkne(sa, sb); + + sa = ca.test3(3); + sb = cb.test3(3); + `checkeq(sa, sb); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule