Any simulator has an algorithm to execute any code. These algorithms are mainly designed with help of scheduling scemantics of verilog or system verilog. Below example shows a small verilog code consisting of two blocks that run concurrently. (initial- executes once, always- never ending loop)
module m1; reg r1; initial begin r1 = 1'b1; #5 r1 = 1'b0; end always@(r1) $display("Printing r1: %d",r1); endmodule
Below is a way to think of how a simulator can execute this code:
- At time 0, prior to the execution of any events, r1 has its default initial value; 1'b1. Had you written reg r1 = 1'b1; its initial value would have been 1'b1.
- All initial and always processes in the entire design are added to the active event queue. Processes implied by continuous assignments are also added to the queue. The order they are placed in the queue is indeterminate. You should never write any code that depends on any observed ordering. Also, the execution of individual statements within a process with respect to the event queue is not defined by the LRM. The LRM does guarantee ordering of statements within a begin/end block within one process, but the relative ordering of statements inbetween multiple processes is not determinate.
- Execution of the active queue continues until the active queue is empty. As has been stated, either the initial or always block may be picked to execute first. Lets start assuming the initial block first.
- The statement attached to the initial block is a begin/end block. This means execute each statement inside the block serially.
- The first statement of the begin end block is an assignment r1 = 1'b1. The assignment creates an update event for any processed sensitive to this update event that will be added to the end of the active queue. If nothing is sensitive to the update, nothing gets scheduled.
- The next statement has an delay control #5. This suspends the current process for 5 time units. The next assignment statement will be put on the inactive queue for 5 time unites later.
- The next event on the active queue is the always block. As mentioned before, there is no reason that this statement could not have started earlier. When simulators go through their optimizations, there is no way to predict the ordering between processes.
- The first statement in the always block has an event control @r1; wait for a change on r1. Since we assumed the initial block started first, the update to r1 has already happened, and this process will suspend waiting for another r1 event.
- The active event queue is now empty and all other queues in the current time slot are empty, so the simulator will advance time to the next scheduled time, 5 time units.
- The initial block process will be put on the active queue and resume executing.
- The next statement is an assignment which generate an update event to r1, and the always block process is put back on the active queue.
- There are no more statements in the initial block, so that process is terminated.
- The always block resumes, executing the $display statement.
- The are no more statements in the always block, so it goes back to the beginning of the block.
- The first statement in the always block has an event control @r1; wait for a change on r1. This process will suspend waiting for another r1 event.
- The active event queue is now empty and all other queues in the current time slot are empty, so the simulator will advance time to the next scheduled time, which does not exist, so the simulation terminates.