-
Notifications
You must be signed in to change notification settings - Fork 670
Fix --x-initial and --x-assign random stability (#2662) #5958
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, makes sense, similar idea to #2662, thanks for working on this.
Please add a test that checks the randomization stability, e.g. that is calls compile and execute twice, and prints two random integers, then checks that the print outputs are the same.
include/verilated.cpp
Outdated
uint64_t VlRNG::rand64(bool reset, uint64_t salt) VL_MT_UNSAFE { | ||
if (reset) { return (m_state[0] + m_state[1]) ^ salt; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This appears a misuse of VlRNG::rand64. If you want a new seed you should be using srandom(...) instead. I presume this is because you used VL_RANDOM, I think instead we should remove use of VL_RANDOM and make a new function.
This will also be faster as existing VL_RANDOM outside of init won't need to initialize and hit the new branch you added here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I'll lose the reset
flag and just make a different method. But do be clear, I'm not trying to set the seed here, I'm trying to piggy back on the seed that was set and get a psuedo-random value that is a function of the seed and the variable (by way of salt
) without disturbing the state of VlRNG
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, ok. I think we should rely on only the original seed and hash of signal hierarchy.
Another thing to add to test is inset a new signal first in the second test.
Re: testing, is there a way to create multiple
Doing this fails because the suffixed obj dirs don't exist. If I work around that I run into other problems because |
There are a couple of tests that compile/execute within a single test more than once, but generally this is to be avoided as is hard to debug and won't parallelize. Instead please make 4 separate .py tests referring to the same Verilog file. |
Gotcha. I was hoping to avoid the golden files and just compare logs with each other. But yeah, that'll work too. |
2609ac3
to
3acdb15
Compare
Still a WIP, but the first commit is a test which can be run to demonstrate the issue. Question: when I use |
I see. It's an inlining difference. So I guess that's the question here. Should the random reset value be based on the longest possible Verilog path for the given variable? Or should it be based on just the variable name? If we go with the former, then inlining changes will perturb the randomization like I'm seeing here. But if we go with the latter, there's less entropy on these reset values. Thoughts? |
Maybe a hash of the module's scope, combined with a hash of the variable's name within module (not full scope). Accept that if inlining changes that may make it inconsistent; generally it'll take a pretty large change for that to flip. |
Sorry, doesn't
But there's only one VAR and one |
The scope's hash needs to be computed at runtime as "Hash(vlSelf->name())" this then gets combined with the verilation-time computed "Hash(AstVar->name())" that is presumably passed to runtime through VL_RESET_WITH_HASHED_NEW_WHATEVER_I. BTW the other reason to do at runtime is there might be multiple instantiations of the verilated module, and each should hash differently. So generated code is something like
|
Ah, OK. Thanks, I was missing the runtime part. I believe I'm with you now. |
Further along now but am seeking some advice on this:
Basically, Anyway, I'll try this next but any guidance is appreciated. |
Actually, this is short sighted ^. If I am able to get the scope name into
would get the same x randomization. I'm not immediately sure what to do about this but will continue poking at it. |
Probably the "right" model is for it to be seeded similar to how IEEE specifies the RNG key be passed as classes are created (see 18.14). However that might be a pain, it also requires a RNG in each class object which slows things down. An alternative is to use the class package's name() this is static for the class (what Another complication is we'd like thread stability, new() could be called from different threads and want same randomization. Perhaps then it's easiest to use the static class name and accept the same randomization for every object of a class. People that care about this should really be using either different seeds, or "rand" tags. Also the rand-reset stuff is really more for design randomization, classes are usually for the verif world where that design randomization doesn't apply and again people should be using "rand" and constraints for such classes. |
I made the class I didn't touch I removed Beyond that, everything is passing for me locally and I've run out of immediate things to do here. So I believe this is in a review-able state. There are a couple concepts in here at this point. Please let me know if it's desirable to chop any of this up. |
// Evaluate one clock posedge | ||
top->clk = 0; | ||
top->eval(); | ||
top->clk = 1; | ||
top->eval(); | ||
|
||
#if defined(T_X_ASSIGN_UNIQUE_0) || defined(T_X_ASSIGN_UNIQUE_1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why this test was previously expecting a "random" value during randReset()
of 0 or 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure perhaps mis-copied from another test.
@@ -2096,6 +2096,7 @@ V3Options::V3Options() { | |||
m_makeDir = "obj_dir"; | |||
m_unusedRegexp = "*unused*"; | |||
m_xAssign = "fast"; | |||
m_xInitial = "unique"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed that this parameter did not have the advertised default value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Looks like the code using it though treated "" as "unique".
1db7971
to
b40500a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, getting close.
I think the tests for stability need something to make sure that the outputs are compatible. My concern is we'll change something and update the golden files and not realize we've lost the similarity between them.
include/verilated.cpp
Outdated
if (Verilated::threadContextp()->randReset() == 0) return 0; | ||
if (Verilated::threadContextp()->randReset() == 1) return ~0; | ||
state[1] ^= state[0]; | ||
state[0] = (((state[0] << 55) | (state[0] >> 9)) ^ state[1] ^ (state[1] << 14)); | ||
state[1] = (state[1] << 36) | (state[1] >> 28); | ||
return state[0] + state[1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use the VlRNG class instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I switched to using VlRNG::rand64()
which then makes VL_SCOPED_RAND_RESET_*()
VL_MT_UNSAFE
. I'm unsure of the implications here. Is this the right way to go or do I need to make something akin to VlRNG::vl_thread_rng()
but for the global seed and which doesn't disturb the seed epoch?
src/V3EmitCFunc.cpp
Outdated
out += ", " + varNameProtected + suffix; | ||
if (!zeroit) { | ||
emitVarResetScopeHash(); | ||
uint64_t salt = std::hash<std::string>{}(varp->prettyName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uint64_t salt = std::hash<std::string>{}(varp->prettyName()); | |
const uint64_t salt = std::hash<std::string>{}(varp->prettyName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
C++ "Hash functions are only required to produce the same result for the same input within a single execution of a program". So use VString functions instead, making a new one if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a bummer.
I dropped a hash function in V3String
and also the runtime library. Is there something better to do here? I don't see any hash functions already associated with VString
. There is a SHA256 in there, but it speaks about 64B blocks and I don't think that's what we want for this application. There's also V3Hash
but that is intrinsically 32b. Also, is there a reasonable way to share code between the compiler and the runtime? I'm not seeing any examples.
Yeah, see One thing to note is that the single and multi-threaded versions do not compare. The x randomization values do, but the |
@wsnyder just circling back on this one. Anything else to do here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, missed this was ready. A few minor things then can merge.
2cbc7d8
to
8efb24b
Compare
…lator#2662) (verilator#5958).' See (verilator#6018)." This reverts commit 640339e.
…lator#2662) (verilator#5958).' See (verilator#6018)." This reverts commit 640339e.
…lator#2662) (verilator#5958).' See (verilator#6018)." This reverts commit 640339e.
We recently noticed that seeds were not portable between builds w/ and w/o
--trace
. This is because the two have different numbers ofVL_RAND_RESET_*()
calls. This is a proof of concept which, while not fully fleshed out, does resolve the issue for us (at least in limited testing).We can work around this by setting
--x-initial
and--x-assign
to0
. However, this is obviously not ideal.The basic idea is that since
VL_RAND_RESET_*()
call may be optimized away (or differently) between builds, we don't let these calls advance the PRNG stream. Instead we calculate salt values during verilation (based on the AST or RTL path or something stable) and run the PRNG algorithm on that without modifying its state.Open items:
*rand64()
, ifreset
is true return the next value per the algorithm after the state has been salted, but do not update the sate (instead of the simple XOR which is there now)--trace
I recognize that this isn't foolproof since one could:
And if
value
isn't used then I assume Verilator can prune it when we're running w/o--trace
. However, this scenario feels a lot more obvious / straightforward to deal with whereasx
randomization is very pervasive and implicit.Anyway, I wanted to get initial thoughts on this before I went further. Please advise.