A series of blog posts by Jan Decaluwe criticize Verilog for being "non-deterministic" and therefore fundamentally broken.This one has a code example that illustrates the fact. During a lively Twitter conversation about it I fleshed out the example and put it on EDA Playground so we could all view it and, even better, run it with all the simulators that EDA Playground provides and see what actually happens. Click here to see and run the example code yourself.
If you run the example you'll see that it behaves the same for all the simulators available on EDA Playground, except one. Jan explains that they are all compliant with the Verilog specification because the specification allows for either behavior. Jan expertly explains in this post how that can be.
Despite these clear examples and explanations I'm left with the feeling of, why should I care? Apparently a lot of other users of the Verilog language have the same feeling as me. I tried to see Jan's point and ask some honest questions in comments and on Twitter and I learned some more. My first question is, if we were to try and synthesize his example Verilog code, what kind of hardware would we get? Wouldn't it be non-deterministic in exactly the same way as the Verilog code? And therefore wouldn't the non-deterministic Verilog be an accurate model and not a sign of Verilog's brokenness? The tweets I got in reply agreed that nobody would design hardware like this and so that's a non-issue. See:
In other words, if you are writing your Verilog in normal RTL style then the non-determinism is not a problem. When writing Verilog that will not be synthesized (simulation-only code) people rightfully abandon the restrictions of RTL. As Chris pointed out, people could then fall into the trap of writing code like this example. I believe that's true, so let's look more closely at this example and see what's going on.
Each initial block is a process executing concurrently. The first process assigns a value to result and signals the second process that result can be read by assigning a value of one to ready. The second process blocks until the value of ready changes from zero to one. It then immediately reads the value of result. Now, I have some extensive experience writing embedded C-code with multiple threads and processes. In that world you would be insane to synchronize two threads using simple shared variables like this. That's because, similar to Verilog, you can't predict when your two threads will be scheduled and run by the OS, when interrupts will occur, and so forth. Instead you would use a synchronization construct provided by the operating system such as a semaphore or mailbox. So again I ask, why do we care about this Verilog non-determinism? Isn't it just the same as in other software environments?
I think the answer to my own question might be, no it's not the same in plain Verilog. Sure, SystemVerilog added semaphores and mailboxes (for just this reason, I assume) but plain Verilog does not have those. Using shared variables is the only way to synchronize and share information between processes (really?). If I'm not wrong then that is indeed a problem for those who want to write Verilog code at a higher level of abstraction than RTL. In fact, I'm starting to wonder about the body of verification code that my team has written at work. Do we have any cases of code like this that could suffer from Verilog's non-deterministic behavior? We are using SystemVerilog and the UVM with its TLM interfaces that give you safe ways to communicate between processes so probably not, but I can imagine where someone could be tempted to work outside the nice safe structure of the UVM.
I'm hopeful that others will read this and chime in with any needed clarifications, corrections, and help. I have some ideas for modifying the example code to make it safer that I will explore in a separate blog entry. Stay tuned.
UPDATE: I have written the follow-on post that shows the fix for this particular code example.