2

I came across code written by someone else, and I don't understand how it works?

 // Task A
 task sub_run_a();
    while ($time < 50us) begin
    #1us;
    $display("sub_run_a(): ping at time %d", $time);
  end
endtask : sub_run_a

// Task B
task sub_run_b();
  #5us;
  $display("sub_run_b() finished");
endtask : sub_run_b

// Task C
task sub_run_c();
  #10us;
  $display("sub_run_c() finished");
endtask : sub_run_c

And this is how the testbench is setup:

fork 
  fork
    sub_run_c();  
    sub_run_b();
  join
    sub_run_a();
join_any

When I look at the simulation result, it seems that all the tasks run in parallel, and I don't understand what is going on.

Isn't it that Task A should NOT start until task B and Task C are done?

But, this is not the case, since this is the output:

# KERNEL: sub_run_a(): ping at time                 1000
# KERNEL: sub_run_a(): ping at time                 2000
# KERNEL: sub_run_a(): ping at time                 3000
# KERNEL: sub_run_a(): ping at time                 4000
# KERNEL: sub_run_b() finished
# KERNEL: sub_run_a(): ping at time                 5000
# KERNEL: sub_run_a(): ping at time                 6000
# KERNEL: sub_run_a(): ping at time                 7000
# KERNEL: sub_run_a(): ping at time                 8000
# KERNEL: sub_run_a(): ping at time                 9000
# KERNEL: sub_run_c() finished
# KERNEL: sub_run_a(): ping at time                10000
# KERNEL: sub_run_a(): ping at time                11000
# KERNEL: sub_run_a(): ping at time                12000
# KERNEL: sub_run_a(): ping at time                13000
# KERNEL: sub_run_a(): ping at time                14000
# KERNEL: sub_run_a(): ping at time                15000
# KERNEL: sub_run_a(): ping at time                16000
# KERNEL: sub_run_a(): ping at time                17000
# KERNEL: sub_run_a(): ping at time                18000
# KERNEL: sub_run_a(): ping at time                19000
# KERNEL: sub_run_a(): ping at time                20000
# KERNEL: sub_run_a(): ping at time                21000
# KERNEL: sub_run_a(): ping at time                22000
# KERNEL: sub_run_a(): ping at time                23000
# KERNEL: sub_run_a(): ping at time                24000
# KERNEL: sub_run_a(): ping at time                25000
# KERNEL: sub_run_a(): ping at time                26000
# KERNEL: sub_run_a(): ping at time                27000
# KERNEL: sub_run_a(): ping at time                28000
# KERNEL: sub_run_a(): ping at time                29000
# KERNEL: sub_run_a(): ping at time                30000
# KERNEL: sub_run_a(): ping at time                31000
# KERNEL: sub_run_a(): ping at time                32000
# KERNEL: sub_run_a(): ping at time                33000
# KERNEL: sub_run_a(): ping at time                34000
# KERNEL: sub_run_a(): ping at time                35000
# KERNEL: sub_run_a(): ping at time                36000
# KERNEL: sub_run_a(): ping at time                37000
# KERNEL: sub_run_a(): ping at time                38000
# KERNEL: sub_run_a(): ping at time                39000
# KERNEL: sub_run_a(): ping at time                40000
# KERNEL: sub_run_a(): ping at time                41000
# KERNEL: sub_run_a(): ping at time                42000
# KERNEL: sub_run_a(): ping at time                43000
# KERNEL: sub_run_a(): ping at time                44000
# KERNEL: sub_run_a(): ping at time                45000
# KERNEL: sub_run_a(): ping at time                46000
# KERNEL: sub_run_a(): ping at time                47000
# KERNEL: sub_run_a(): ping at time                48000
# KERNEL: sub_run_a(): ping at time                49000
# KERNEL: sub_run_a(): ping at time                50000 

3 Answers 3

5

The outer fork starts 2 things at the same time:

  1. sub_run_a
  2. The inner fork

The inner fork in turn starts 2 things at the same time:

  1. sub_run_c
  2. sub_run_b

So, all 3 tasks start at the same time.

What's also interesting is that sub_run_a continues to display pings after the inner fork completes at 10us. This can be controlled with disable fork after the outer fork.

Sign up to request clarification or add additional context in comments.

Comments

0

The inner fork and sub_run_a are at the same depth, meaning the outer fork starts both the inner fork and sub_run_a at the same time. The inner fork will then proceed to start sub_run_b and sub_run_c.

So the inner fork has no effect on when the processes start, but it does have an overall effect. The inner fork with its join statement makes it so that the join_any statement will allow the main process to continue if either the sub_run_a process is finished or both the sub_run_b and sub_run_c processes are finished. Without the inner fork the outer process would continue if any of the three processes is finished.

You should see a difference in the behaviour if you place a $display() statement right after the join_any statement and try running it with and without the inner fork.

Comments

0

It is very similar to the begin ... end in the fork ... join

begin ... end in fork ... join_any

Suppose you had used begin .. end then :

fork 
  begin
    sub_run_c();  
    sub_run_b();
  end
  sub_run_a();
join_any

In this case, outer fork ... join_any will spawn 2 threads and it will come out of the fork ... join_any, as soon as any of the 2 threads are completed.

2 Threads by outer fork ... join_any -

  • sub_run_a()
  • begin .. end (Now begin ... end will execute 2 methods sub_run_c & sub_run_b sequentially)

sub_run_a() and sub_run_c() will be started at the same time, and sub_run_b will be started after sub_run_c gets completed.

fork ... join in fork ... join_any

Suppose you had used begin .. end then :

fork 
  fork
    sub_run_c();  
    sub_run_b();
  join
  sub_run_a();
join_any

In this case, outer fork ... join_any will spawn 2 threads and it will come out of the fork ... join_any, as soon as any of the 2 threads are completed.

2 Threads by outer fork ... join_any -

  • sub_run_a()
  • fork .. join (Now fork ... join will spawn 2 more threads sub_run_c & sub_run_b)

sub_run_a(), sub_run_c(), sub_run_b() all will be started at the same time.

In both cases, outer fork ... join_any will be completed, when either of the 2 spawned threads are completed. But in the case of nested begin ... end, 2 inner threads (sub_run_c & sub_run_b) will be executed sequentially, whereas for nested fork ... join, 2 inner threads will be executed concurrently.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.