-
Notifications
You must be signed in to change notification settings - Fork 174
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
Reconsider simulator interface #228
Comments
Comment by sbourdeauducq
The reason for this is to ensure determinism when there are multiple processes on the same clock communicating through signals. The result is independent of the order in which those process are run. |
Comment by sbourdeauducq Was this changed already? This prints from nmigen import *
from nmigen.back.pysim import *
class Dummy(Elaboratable):
def __init__(self):
self.x = Signal()
self.y = Signal()
def elaborate(self, platform):
m = Module()
m.d.sync += self.y.eq(self.x)
return m
if __name__ == "__main__":
dummy = Dummy()
with Simulator(dummy) as sim:
def process():
yield dummy.x.eq(1)
print((yield dummy.x))
yield dummy.x.eq(0)
print((yield dummy.x))
sim.add_clock(1e-6)
sim.add_sync_process(process)
sim.run() |
Comment by whitequark You aren't using |
Comment by sbourdeauducq Yes. It is a dummy signal to work around #262. |
Comment by whitequark
I didn't intend to change this. I think the reason you see the behavior you do is that I did not understand the design of the oMigen simulator, so I didn't implement it entirely correctly.
OK, so this works like VHDL. That's good. Unfortunately, the problem I outlined in this issue stands: not being able to write a single-cycle I've been thinking about this problem and came to the conclusion that there should probably be three kinds of simulator processes:
|
Comment by sbourdeauducq What about an option to run synchronous processes at twice the clock rate? For example, a synchronous process could yield a full cycle (default), or a high half-cycle, or a low half-cycle. At the beginning of a cycle, it could yield either a full cycle (default) or a high half-cycle. At the middle of a cycle it could yield only a low half-cycle. Doing otherwise raises an error, which ensures that processes are always "in phase" with the clock. This is backward compatible, can be deterministic using the VHDL-style behavior, and supports an arbitrary number of processes. |
Comment by whitequark The basic idea is sound, thanks. But, what happens if someone uses a negedge clock domain (#185) in the design? There are many valid reasons to do so, and even some FPGA primitives (Xilinx UltraRAM) trigger on both clock edges. So I think we should change it a bit: rather than use a half cycle, run all "sync" processes at the posedge (or the negedge) as it currently happens, and then run all "testbench" (or whatever they are called) processes simultaneously, a very small amount of time later (however much it takes for combinatorial logic to propagate, and for "comb" processes to run as well). |
Comment by jordens I would want to differentiate that What you actually want instead is to be able to differentiate between yielding to update the sync signals and yielding to settle the comb signals. |
Comment by whitequark In terms of the UI, currently This solves an important issue where something like |
Comment by jordens And this extends to multiple clock domains accordingly (sync cycles executed in the pattern defined by their timing beat and comb/delta cycles in between). A process should be able to specify what kind of cycle it wants to yield for: not just a bare |
Comment by jordens I don't think |
Comment by whitequark Can you provide some examples of those reasons? |
Comment by jordens Otherwise |
Comment by jordens E.g. if you don't have any synchronous clock domain. yield stb.eq(1)
yield # settle comb
assert (yield busy)
yield stb.eq(0)
yield # settle comb
assert not (yield busy) |
Comment by whitequark
Comb processes are something I wanted to introduce explicitly (that's the second oldest nMigen issue, #11). In my mind, they would require specifying the exact set of signals they will wait on during creation, and then always wait on exactly those signals; that way, they would have no choice but to be well behaved. However, that would not work for the snippet you show. And I think you are making a very good point with it. I'm inclined to agree that a separate command for comb settling is desirable. |
Comment by whitequark Perhaps a trio of |
Comment by jordens Yes. |
Comment by jordens The |
Comment by jordens Ah. But |
Comment by jordens Do you even need to differentiate |
Comment by whitequark
No, but for that matter, One option would be to get rid of |
Comment by whitequark
I'm confused by this paragraph. The current behavior is something like "settle, stop just before updating sync, run all processes", not "settle, update, settle". Or I misunderstand you. |
Comment by jordens Yes. |
Comment by jordens The current behavior of |
Comment by jordens And "stop just before updating sync" is in fact a NOP because after settling comb nothing would happen until the next sync update. |
Comment by whitequark You're right. We want the same thing but I was not describing it precisely. Thank you for this discussion. I now have a very clear picture of how the next iteration of the simulator should look like, and I am happy with every aspect of it. |
Comment by sbourdeauducq
A negedge clock domain is just like another clock domain but with the clock shifted 180 degrees. If simulation supports multiple clock domains (it should) then there is nothing really special about it, I think? |
Comment by whitequark
There is if there are two clock domains defined to be clocked by different edges of the same signal. Then you have the same problem in a single process that interacts with both. |
Comment by sbourdeauducq
Not if such processes are not allowed (and I think they should not be allowed: a synchronous process must be associated with a single clock signal and a single sensitivity edge). |
Comment by whitequark
Among other things this restriction would make it impossible to simulate ResetSynchronizer, which is something that nMigen does today. So it would be a step back. |
Hello, https://github.com/andresdemski/nmigen-yosim I know @whitequark is going to add an official cxxrtl support, take this just as an API proposal and don't look at the implementation please (i'm not C++ developer). Some interesting features of "yosim":
|
At the moment, I believe there is no need for a distinct API for More broadly, the major issue with the existing simulators that I see (the Verilog one, the VHDL one, the cxxsim one too to a slightly lesser degree) is that they offer you a very generic event-driven interface but they're not used for simulating generic event-driven systems--they're used to write testbenches for a single domain of synchronous logic, or sometimes, a few asynchronous domains with a small amount of CDC. That results in races. Verilog processes are inherently nondeterministic by design; VHDL is better if you're simulating logic alone, but it still does break in confusing ways if you're writing a testbench or perhaps a simulation model for a clock gate, where in both cases you might end up having to balance delta cycles. Adding capabilities like arbitrary edge triggers or fork/join parallelism increases the potential for race conditions. What I'd like to see in nMigen's simulator API instead is (a) a way to inject behavioral models into the simulation that make it easy to write race-free code and (b) a race condition detector, similar to ThreadSanitizer. By this point I believe I know how to solve (a); cxxrtl builds towards a solution to it by offering black boxes with a well-defined interface that guides the scheduler (the |
Is it going to be possible to write simulation model for peripherals like ddr memories, sensors or high speed communication protocols and generate representative waveforms? I know that you can use IDDR as a blackbox but you won't have a real waveform at the input for an end to end validation. |
Would it be good enough for you to use the vendor simulation models? |
yep, but the problem is how to generate data in both edges to feed the vendor iddr simulation model. |
Ah yes. Both |
While redoing the simulator interface it should also be considered to make |
Denominating from 0.3 for two reasons:
|
The titular issue has been solved by RFC 27 and more improvements will come in later RFCs. |
Issue by whitequark
Monday Sep 23, 2019 at 08:52 GMT
Originally opened as m-labs/nmigen#228
The oMigen simulator had a strange interface where user processes would run after a clock edge, but before synchronous signals assume their new value. Not only it is very hard to use and bug-prone (my simulation testing workflow, for example, involves adding
yield
until tests pass...), but it also makes certain patterns impossible! For example, let's consider theFIFOInterface.write()
simulation function:It has to take two clock cycles. This is because, after the first
yield
, it is not yet possible to read the new value ofself.w_rdy
; running(yield self.w_rdy)
would return the value from the previous cycle. To make sure it's updated, it is necessary to deassertself.w_en
, and waste another cycle doing nothing.Because of this, I've removed these methods from nMigen FIFOInterface in a57b76f.
I think we should look at prior art for cosimulation (cocotb?) and adopt a usage pattern that is easier to use. This is complicated by the fact that it is desirable to run oMigen testbenches unmodified.
The text was updated successfully, but these errors were encountered: