Wednesday, 29 October 2014

understanding scheduling scemantics from simulator perspective

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:
  1. 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.
  2. 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.
  3. 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.
  4. The statement attached to the initial block is a begin/end block. This means execute each statement inside the block serially.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. The initial block process will be put on the active queue and resume executing.
  11. 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.
  12. There are no more statements in the initial block, so that process is terminated.
  13. The always block resumes, executing the $display statement.
  14. The are no more statements in the always block, so it goes back to the beginning of the block.
  15. 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.
  16. 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.
 

Wednesday, 15 October 2014

Difference between @cb and @posedge IF.clk

Interface handle : IF
clocking block : cb
clock used in clocking block: clk

logically both appear to have same functionality. But there is a difference due to clocking block.

when interacting with clocking blocks, only use the clocking block event @IF.cb as the synchronizing event. That includes not using the wait() statement or any other @IF.cb.variable expressions.

There are two reasons fort this recommendation.

There is a race condition if you use the @(posedge clk) and try to read a sampled clocking block variable. The race is between reading the old sampled value and the new sampled value. This is describe in a note in section 14.4 Input and output skews of the 1800-2012 LRM.

Although not explicitly stated in the LRM, a consequence of eliminating the above race is that because clocking block event comes after all clocking block variables are updated, if you wait on a clock block variable followed by a wait on a clocking block event, both event may occur in the same time slot. so write your code as
@(IF.cb iff !IF.CB.reset); // instead of wait (!IF.cb.reset)
...
@(IF.cb) // guaranteed to be 1 cycle later.

Thursday, 9 October 2014

How to control or check the status of threads generated by fork

In a complex TB designs, if multiple fork-join blocks are involved, it becomes difficult to manage threads generated by all the forks. SV provides a way to check the status of these threads or to suspend or kill threads. ( we are aware of only 'wait fork' and 'disable fork' commands to control fork-join)

A process is a built-in class that allows one process to access and control another process once it has started.Users can declare variables of type process and safely pass them through tasks or incorporate them into other objects. The prototype for the process class is as follows:

class process;
   typedef enum { FINISHED, RUNNING, WAITING, SUSPENDED, KILLED }state;
   static function process self();
   function state status();
   function void kill();
   task await();
   function void suspend();
   function void resume();
   function void srandom( int seed );
   function string get_randstate();
   function void set_randstate( string state );
endclass


Objects of type process are created internally when processes are spawned. Users cannot create objects of type process; attempts to call new shall not create a new process and shall instead result in an error. The process class cannot be extended. Attempts to extend it shall result in a compilation error. Objects of type process are unique; they become available for reuse once the underlying process terminates and all references to the object are discarded.
The self() function returns a handle to the current process, that is, a handle to the process making the call. The status() function returns the process status, as defined by the state enumeration:

— FINISHED means the process terminated normally.
— RUNNING means the process is currently running (not in a blocking statement).
— WAITING means the process is waiting in a blocking statement.
— SUSPENDED means the process is stopped awaiting a resume.
— KILLED means the process was forcibly killed (via kill or disable).
The kill() function terminates the given process and all its subprocesses, that is, processes spawned using fork statements by the process being killed. If the process to be terminated is not blocked waiting on some other condition, such as an event, wait expression, or a delay, then the process shall be terminated at some unspecified time in the current time step.

The await() task allows one process to wait for the completion of another process. It shall be an error to call this task on the current process, i.e., a process cannot wait for its own completion.

The suspend() function allows a process to suspend either its own execution or that of another process. If the process to be suspended is not blocked waiting on some other condition, such as an event, wait expression, or a delay, then the process shall be suspended at some unspecified time in the current time step.
Calling this method more than once, on the same (suspended) process, has no effect.

The resume() function restarts a previously suspended process. Calling resume on a process that was suspended while blocked on another condition shall resensitize the process to the event expression or to wait for the wait condition to become true or for the delay to expire. If the wait condition is now true or the
original delay has transpired, the process is scheduled onto the Active or Reactive region to continue its execution in the current time step. Calling resume on a process that suspends itself causes the process to continue to execute at the statement following the call to suspend.

The methods kill() , await() , suspend() , and resume() shall be restricted to a process created by an initial procedure, always procedure, or fork block from one of those procedures.

The following example starts an arbitrary number of processes, as specified by the task argument N. Next, the task waits for the last process to start executing and then waits for the first process to terminate. At that point, the parent process forcibly terminates all forked processes that have not yet completed.



task automatic do_n_way( int N );
   process job[] = new [N];
   foreach (job[j])
   fork
      automatic int k = j;
      begin
         job[k] = process::self(); ... ;
      end
   join_none
  foreach (job[j])
     wait( job[j] != null ); // wait for all processes to start
      job[1].await(); // wait for first process to finish
  foreach (job[j]) begin
     if ( job[j].status != process::FINISHED )
     job[j].kill();
end
endtask

For more info: refer SV LRM

Wednesday, 8 October 2014

Familiar with static/dynamic task? then what is static process or dynamic process?

There are two kinds of processes in SystemVerilog:static and dynamic.
The SystemVerilog LRM defines a static process as one where "each time the process starts running, there is an end to the process." Another way of putting this is that static processes are created when the code is elaborated and persist until the end of simulation. Static processes come in several forms—each
always,always_comb,always_latch,always_ff and initial procedure is a separate static process as is every concurrent signal assignment.

On the other hand, dynamic processes are created at run-time and execute as independent threads from the processes that spawned them. They can be waited upon or disabled. Dynamic proc-esses come in the form of
fork..join_all,fork..join_none, and dynamic processes created by con-current assertions and cover properties. Dynamic processes allow a testbench to dynamically react to a design under test, control the flow of simulation, build high-level models,and respond to both testbench components and the design.

Tuesday, 7 October 2014

why a method using 'ref' as argument should be automaic?

As per SV guidelines, any method using 'ref' as keyword should be made automatic. Below are some key points to understand this.

1. methods inside a module/program block by default will have "static" life times.
2. methods defined inside a class by default will have "automatic" life time.

Consider an example below.

program main();
int a;
 
initial
begin
#10 a = 10;
#10 a = 20;
#10 a = 30;
#10 $finish;
end
 
task pass_by_val(int i);
forever
@i $display("pass_by_val: I is %0d",i);
endtask
 
 
task pass_by_ref(ref int i);
forever
@i $display("pass_by_ref: I is %0d",i);
endtask
 
initial
pass_by_val(a);
 
initial
pass_by_ref(a);
 
endprogram
 
 
This example has two static tasks.
In the example above, both tasks pass_by_val() and pass_by_ref() have static lifetimes. You could change the second initial block to
initial begin
      pass_by_val.i = 5;
      $display(pass_by_val.i); // will display 5
      fork 
         #11 pass_by_val(a); // a = 10, copied to pass_by_val.i
         #12 $display(pass_by_val.i); // will display "10"
         #30 pass_by_val.i = 2; // will display "pass_by_val: I is 2"
      join
  end
The $display at time 12 shows 10 because that was the value passed to i. The assignment statement at time 30 cause the @i event control to trigger and execut the $display inside pass_by_val(). If you change the third initial block to
initial begin
      pass_by_ref.i = 5;
      $display(pass_by_ref.i);
      pass_by_ref(a);
      ...
 
What variable is i referencing? We haven't called pass_by_ref yet, so 'a' is not referenced by i yet. This is just one of many problems where pass_by_ref.i could reference something that does not exist yet, or something that did exist, but no longer does. 
 
So, If task is static, we can access its variables hierarchically.
But, if arguments are "ref", and task is static, compiler
doesn't understand from where to take that ref value. So, if
task is defined as automatic, compiler doesn't map the ref
values until it is called. 
 
it is mandatory to make tasks/funtions automatic if they have
ref arguments and are being used in a module/program block. 
(in class, by default task/function is automatic).