Your code is fine, though I agree with Greg that it's better style to use blocking assignments in the combinatorial process.
On your comments:
In very simplified terms, the scheduler contains 5 queues (SV has 17), but
you're only interested in 2 of them: the active event queue
, and the
nonblocking assignment update queue
. In a given simulation cycle, the
simulator processes the active event queue, and then the nonblocking update
queue. In general, this will create more events, and the simulator cycles
around the queues in a pre-determined order until there are no more events at
this simulation time. The simulator then moves on to the next time at which an
event is scheduled (at the next clock edge, for example).
Assume that 4 things happen 'at once': there is a change in fsm_state
, there
is a change in f(x)
, and both your NBA assignments are executed. As far as
the simulator is concerned, these 4 statements are executed in the same
simulation cycle, but in an undefined order. The definition of 'at once' is
rather complex, but assume that they all happen as a result of a clock edge,
with no ordering dependencies between the statements. The simulator is usually
single-threaded, so it will actually execute the 4 statements at different
real times, but it knows that all 4 are expected to happen at the same
simulation time.
The changes on fsm_state
and f(dff1)
are update events
, and are added to
the scheduler's active event queue
.
The RHS of the two NBAs are immediately evaluated, and the LHS updates are
added to the nonblocking assignment update queue
.
The simulator now sees a queue with 4 events on it. It executes the two active
events first, in an undefined order, so fsm_in
and fsm_out
get their new
values. If there are no more active events, it then executes the two
non-blocking updates, in an undefined order, so dff1
and fsm_state
now get
their new values.
In your case, the simulation cycle isn't finished, because the change on
fsm_in
is also an update event, and this triggers the evaluation/execution
of the always block which is sensitive to fsm_in
. This is an evaluation
event
. The sim executes the always block, immediately reads the new value of
fsm_in
, and adds the update of fsm_state
to the NBA assignment update
queue. If there are no active events, the assignment is then acted on, and
fsm_state
gets it's new value.
This goes on until there are no more events in this simulation cycle, and the
simulator then advances time, if something is scheduled in the future.
You could get all this from section 5 of the Verilog LRM, but it doesn't make
much sense. All the language was grafted on late in the standardisation
process and uses (VHDL) terminology that's not used elsewhere in the LRM. It
was also all added in such a way as to match the behaviour of Verilog-XL, and
specifically to document the non-determinism specific to XL, so don't expect
too much from it. NBAs weren't even added to the language till 1992, I
think. Don't bother with the SV LRM; there are many more queues, and the
common text has changed, just adding another level of confusion.
Cliff Cummings has a simplified description in one of his papers (on
non-blocking assignments), but read it with care. I'm pretty sure that the
description of the active event queue is incorrect (for RHS evaluation of
NBAs). If he was right, it would cause all sorts of problems; he presumably
got the description from an early version of the LRM.
Much the best thing to do is to look up any text on VHDL delta cycles. These
are easy to understand, and they work, and always have done, and they have
crept into Verilog over the years. The details are different, but you don't
need to know anything more than you'll find in delta cycles.