In reply to eda2k4:
Before answering this, let me just say that using #0 in your code is a very poor programming practice (even for the UVM base class library). You are just moving a race condition by one active-to-inactive iteration, not eliminating it. Seeing #0’s usually means they wrote their code to quickly, or did not have time to understand another piece of code and remove the race condition in the first place.
You are correct that all processes share one active event region. Verilog executes the active events in any order it chooses until the region is empty. Executing an event can create new events in any region, so the active region must drain empty before it can move to the next iteration. There is an inactive region for every scheduled delay. When the active region empties, the events #0 inactive region become the active region, and execution continues. If the #0 inactive region is empty, it looks at the next region (NBA) and that become the active region. And so on for all the other regions in the current time slot. Once all the regions for the current time slot are empty, it looks for the next time-slot for a non-empty region and makes that the active region and the current time jumps to that time-slot.
Simulation events only execute in the active region. A #n delay schedules an event into the inactive region of the time-slot n units away. ##n is just a shortcut for repeat (n) @(posedge clocking_block).