Difference of reset/configure/main/shutdown phases and run phase

Are sub processes inside (pre/post_)reset/configure/main/shutdown phases are automatically killed when they finish?

Take the following TB for example.

`include "uvm_macros.svh"
module tb;
    import uvm_pkg::*;
        
    class my_test extends uvm_test;

        function new(string name, uvm_component parent);
            super.new(name, parent);
        endfunction

        `uvm_component_utils(my_test)

        virtual task run_phase(uvm_phase phase);
            phase.raise_objection(this, "Starting Run Phase");
            fork
                forever begin
                    `uvm_info(this.get_type_name(), "Run Phase", UVM_MEDIUM)
                    #100ns;
                end
            join_none
            #1us;
            phase.drop_objection (this, "Ending Run Phase");
        endtask

        virtual task main_phase(uvm_phase phase);
            phase.raise_objection(this, "Starting Main Phase");
            fork
                forever begin
                    `uvm_info(this.get_type_name(), "Main Phase", UVM_MEDIUM)
                    #100ns;
                end
            join_none
          //#1us;
            phase.drop_objection (this, "Ending Main Phase");
        endtask

    endclass

    initial run_test("my_test");

endmodule;

Then, the followng is gotten.

UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO tb.sv(17) @ 0: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 0: uvm_test_top [my_test] Main Phase
UVM_INFO tb.sv(17) @ 100: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(17) @ 200: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(17) @ 300: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(17) @ 400: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(17) @ 500: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(17) @ 600: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(17) @ 700: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(17) @ 800: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(17) @ 900: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(17) @ 1000: uvm_test_top [my_test] Run Phase

Message of main_phase() is printed only once. It looks forever loop inside main_phase() is killed when it finishes. It is not the case if run_phase() finishes immediately.

UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO tb.sv(17) @ 0: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 0: uvm_test_top [my_test] Main Phase
UVM_INFO tb.sv(17) @ 100: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 100: uvm_test_top [my_test] Main Phase
UVM_INFO tb.sv(17) @ 200: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 200: uvm_test_top [my_test] Main Phase
UVM_INFO tb.sv(17) @ 300: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 300: uvm_test_top [my_test] Main Phase
UVM_INFO tb.sv(17) @ 400: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 400: uvm_test_top [my_test] Main Phase
UVM_INFO tb.sv(17) @ 500: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 500: uvm_test_top [my_test] Main Phase
UVM_INFO tb.sv(17) @ 600: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 600: uvm_test_top [my_test] Main Phase
UVM_INFO tb.sv(17) @ 700: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 700: uvm_test_top [my_test] Main Phase
UVM_INFO tb.sv(17) @ 800: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 800: uvm_test_top [my_test] Main Phase
UVM_INFO tb.sv(17) @ 900: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 900: uvm_test_top [my_test] Main Phase
UVM_INFO tb.sv(17) @ 1000: uvm_test_top [my_test] Run Phase
UVM_INFO tb.sv(29) @ 1000: uvm_test_top [my_test] Main Phase

forever loop is alive even after the finish of run_phase() unlike main_phase().

In that sense, we cannot say main_phase() is same as run_phase()?

Here is EDA playground example.

UVM phase processes are terminated once there are no objections raised to the phase’s execution, and no sub-phases have raised objections. The main_phase is a sub-phase of the run_phase.

@Orimitsu
If I may add as well -
Please note that in your main_phase you used fork...join_none

Essentially, you spawned a new thread without any blocking statement after it. As a result, the fork...join_none block executes once and immediately falls through, so main_phase reaches its end right away.
This is exactly why, as you mentioned, “the message in main_phase() is printed only once.”

If the intent is to let the main_phase run for a un-bounded time, you might achieve it by replacing fork..join_none with fork..join
But it would not solve the issue either — since the inner thread is a forever loop, the task would hang indefinitely and the objection: phase.drop_objection (this, "Ending Main Phase"); would never be dropped…

Thanks @dave_59 , @MichaelP .
I misunderstood run_phase and (pre/post_)reset/configure/main/shutdown_phase are independent each other.
I understand the followings now.

  • (pre/post_)reset/configure/main/shutdown_phase are reagarded as sub phases (processes) of run_phase.
  • fork ... join_none sub processes of each phase task are terminated when the phase task ends.

Given these, the oberved weird bahavior of fork ... join_none processes in run_phase() and main_phase() are explained.

I’d like to add that, in fact, the run_phase and the 12 task phases (pre/post reset, etc.) run in parallel; however, the run_phase and the post_shutdown_phase have a mutual waiting relationship (referred to as siblings in UVM), meaning both must wait for the other to execute and drop all objections before they can exit.

So, regarding your question, main_phase and run_phase are not siblings and do not form a waiting relationship, so main_phase will terminate immediately. However, when you switch to post_shutdown_phase, post_shutdown_phase must wait for run_phase to execute and for all objections to be dropped before it exits. If you set a delay for printing within post_shutdown_phase, you will see the expected output.

BR

1 Like

Thank you for your comment, @doggkin.
I confirmed only post_shutdown_phase amongst sub phases is not terminated immediately.

      //virtual task main_phase(uvm_phase phase);
      //virtual task post_main_phase(uvm_phase phase);
      //virtual task pre_shutdown_phase(uvm_phase phase);
      //virtual task shutdown_phase(uvm_phase phase);
        virtual task post_shutdown_phase(uvm_phase phase);
            phase.raise_objection(this, {"Starting ", phase.get_name(), " Phase"});
            fork
                forever begin
                    `uvm_info(this.get_type_name(), {phase.get_name(), " Phase"}, UVM_MEDIUM)
                    #100ns;
                end
            join_none
            phase.drop_objection (this, {"Ending ", phase.get_name(), " Phase"});
        endtask

This gets 10 message printings of “post_shutdown Phase” while others don’t.