UVM Tutorial for Candy Lovers – 22. Phasing

When we created the jelly_bean_driver in Agent, we coded the build_phase function and the run_phase task, but who actually calls them? The answer is uvm_phase class.

UVM Phases

UVM has nine common phase classes (shown in yellow) and twelve run-time phase classes (shown in pink). These phase classes are derived from the uvm_topdown_phase, uvm_bottomup_phase, or uvm_task_phase classes, which in turn are derived from the uvm_phase class. The uvm_phase class has a virtual function called exec_func and a virtual task called exec_task. The phase classes derived from the uvm_topdown_phase and the uvm_bottomup_phase implement the exec_func, while the phase classes derived from the uvm_task_phase implement the exec_task. As shown in the diagram, each phase class calls the corresponding phase function or task of the uvm_component. For example, the exec_func of the uvm_build_phase class calls the build_phase function of the uvm_component, and the exec_task of the uvm_run_phase class calls the run_phase task of the uvm_component, etc.

UVM Phase Classes
UVM Phase Classes

Simplified Flow

The way UVM phases are implemented is rather complicated, but here is a simplified flow.

Simplified Flow
Simplified Flow
  1. We call run_test (if you don’t remember, see the line 17 of the top module in Tasting), which in turn calls the run_test task of the uvm_root class.
  2. The uvm_root calls the m_run_phases task of the uvm_phase class.
  3. For each phase, the execute_phase task is called.
  4. If the phase is a top-down or bottom-up phase, exec_func is called for each component.
  5. For example, the exec_func calls the build_phase function of each component.
  6. If the phase is a task phase, exec_task is called for each component.
  7. For example, the exec_task calls the main_phase task of each component.
  8. The uvm_phase checks if any objections are raised by the components. The phase_done is the uvm_objection object that the uvm_phase keeps track of the number of objections with. When we called phase.raise_objection() from inside the run_phase of the jelly_bean_test class (see the line 27 of the jelly_bean_test class in Tasting), phase_done.raise_objection() is called in the uvm_phase under the hood.
  9. If no objection is raised, all the processes started by the exec_task are killed. In other words, unless an objection is raised, the phase is immediately killed!
  10. The steps 3 to 9 repeat until all phases are executed.

I hope this article helped in your understanding of the UVM phasing.

37 thoughts on “UVM Tutorial for Candy Lovers – 22. Phasing”

  1. Hi Keisuke

    Thanks for this amazing blog. I was wondering that when you pass the argument (uvm_phase phase) during a phase call is it that you are passing a singleton instance of phase to synchronize all the various components across the testbench? I am just trying to understand the need for the phase handle as an argument while calling any of the phase methods. Thanks again. This blog is very helpful.

    Regards
    Dhaval

    1. Hi Dhaval,

      Thank you for your comment. Typical usage of the (singleton) phase argument is to raise/drop objection. Please see the run_phase task of the jelly_bean_test class in Tasting, for example. Phase synchronization is done in the uvm_phase class, not in the uvm_component.

      1. Hi Shimizu,

        My task is to use the VIP to verify the DUT. I have legacy tests that are written with run_phase. However, the VIP components have reset_phase and main_phase. I am confused as how to synchronize them to work properly. I’ll be gals if you can help me with this!!

        1. The run_phase and the run-time phases (such as the main_phase) are running in parallel. They are synchronized at the extract_phase only. You can think as if your VIP has the run_phase which is broken down into smaller tasks (the reset_phase and the main_phase). Most of the time, you would have no problem with mixing the run_phase and the main_phase.
          UVM Phases

          1. If utilizing the config_/reset_/main_phase’s as opposed to just managing all in the run_phase, is there a built-in event that the run_phase can use to know when in each phase? I dont think my brief review of the code shows any (although, UVM does know when its in each sub-phase).

            I know I can create an event as needed if there is a need for the run_phase to know. Example: run_phase does some things across all phases in the background, but would like to do something *after* the reset_phase. The reset_phase can evt.trigger() and the run_phase can wait for that at some point.

            I understand some schools of thought are to only use the run_phase, and to just use separate sequences (config, reset, etc). Anyway, want to know if its bad form to create a uvm_event and trigger it in the reset_phase and ‘wait’ for it somewhere in the run_phase.

  2. Hi Keisuke,

    I am a beginner in the ASIC verification field. Thank you very much for this blog for UVM phases. Can I know why the build phase is executed in top-down manner while other phases are executed in down-top manner?

    Regards,
    Vishal

    1. The build_phase has to be top-down because the parent component creates the child components (not the other way around). It also allows the parent component to configure the child components. On the other hand, the connect_phase, for example, is bottom-up. It allows the parent component to verify that the child components are connected properly.

      1. Hi Keisuke,

        We already have the components created during build phase. So would it matter if it was top-down or bottom-up for connect_phase.
        For example: The connection between the sequencer and driver is done in the agent, and normally this connection would be present in the connect_phase. What would happen in this scenario if connect phase was top-down?

        Thanks,
        Subash

  3. Hi,

    Once a phase of a particular component completes(say main_phase), will it wait for that phase of all the components to complete, to go to the next phase? Assuming I don’t raise and drop objection in that phase in any of the components. Could u also tell me how mixing run_phase with its sub phases work? For eg, I have run_phase in one component and main_phase in another. Which one will run first?

      • A phase of a component remains alive until all objections to the phase are dropped. So, a component will wait for the other components to complete as a result.
      • If no component raises an objection, that phase is immediately killed.
      • The run_phase and the run-time sub-phases (reset_phase, main_phase, etc.) are executed in parallel. They are synchronized at the extract_phase at the end.
      1. Hi Keisuke,
        Are the run_phase and run-time sub-phases in some ways equivalent?
        I mean, if I want to control run time simulation roughly just use run_phase, otherwise I can use run-time sub-phases to tune.

        Another question is can you give an example to start other run-time sub-phases?
        Eg, if I want to add some control in uvm_configure_phase, can I add following code in a uvm component?

        task configure_phase( uvm_phase phase );

        phase.raise_objection( .obj( this ) );
        /*
        specific code needed
        */
        phase.drop_objection( .obj( this ) );
        endtask

        Thanks in advance!

        Best Regards,
        Rui

        1. Your understanding is correct for both of the questions.

          • Unless you need granular synchronization between components, just use run_phase.
          • If you define your own configure_phase, it will be used and synchronized with the configure_phase of other components (other components will wait until your configure_phase finishes).
  4. Hi Keisuke san,

    Thanks for my article. I can understand why the driver with forever loop can be terminate m_phase_proc.kill, because it did not raise objection. Is the sequencer associated with that driver to raise objection to keep it loop running? Is it true that when all the sequences are finished, the sequencer will drop the objection and we can reach the m_phase_proc.kill? Many thanks.

    1. Correction. It is supposed to be “Thanks for your article to help to understand…” It is a terrible typo. I have learned a lot from you, I have followed your articles for a year. Thanks so much for your work.

  5. Hi Keisuke,

    Thank you for the pictures and the post.
    However, I am still not clear on what the difference between “new”, “create” and “build” is.
    What happens when a “new” is executed? is it only memory allocation for the object that is “yet” to be created or is the object actually created at this step as well? When is the “new” command executed?
    What happens in a “build” phase? The parent class creates the object and “remembers” the object handle for all further references to that object or something else?
    What happens when “create” is executed?

    Thank you.

    Shamanth Huddar

    1. Let’s look at the jelly_bean_agent in Agent as an example.

      • The new function creates an object of jelly_bean_agent. It also creates the handles for uvm_analysis_port, jelly_bean_sequencer, jelly_bean_driver, and jelly_bean_monitor. However, these handles are null because we have not created the objects for them yet.
      • The build_phase function creates the components mentioned above. We called new to create an object of uvm_analysis_port. However, we called create to create the other components in case we want to override the component type later.
      • The create function asks the UVM factory to create an object. The UVM factory knows which component to create even if the component type is overridden. The factory (or to be precise, uvm_component_registry) will call new on behalf of you.
      1. Hi Keisuke,

        Thank you for the reply. I think I understand the phasing much better now.
        Here’s my understanding, could you please confirm if it’s correct:

        1. build_phase is top-down:
        You must create the top-level object first to further create the objects inside it. That’s why build_phase is top-down.
        First, “new” of the top object, say “env”, creates the actual object of type “env” + only handles to all objects (not the objects themselves) that were declared in the same class.
        Second, “build_phase” function of the top level object, i.e. “env”, is executed. This means “creating” the actual “objects” for the above mentioned “handles” – Build phase of a class literally builds the object by creating it’s low-level objects using the “new” of those low-level objects.
        Third, “build_phase” function of the low-level objects is executed, ex: agent, and the above steps repeat for the agent and it’s low-level components like driver and monitor.
        This way all OBJECTS and their respective handles in the top-level objects, are created and the build_phase ends and is followed by connect_phase

        2. connect_phase is bottoms-up:
        Now that all objects have been created and their handles present in the upper levels, the lower level objects make the necessary connections , i.e. agent will connect the 1) driver’s seq_item_port with that of the sequencer’s and 2) monitor’s analysis port with the one in the agent itself.
        UVM then moves to complete the connect_phase of the upper level component – env, and pulls in the agent’s analysis port for further connection.

        Thank you once again.

          1. I am not sure about how “third” is initiated.

            UVM is just a methodology. It is supposed to behave like a regular System Verilog code.

            What starts this:
            “Third, “build_phase” function of the low-level objects is executed, ex: agent, and the above steps repeat for the agent and it’s low-level components like driver and monitor.”?

            “new” or “create” doesn’t start a build_phase.

          2. Component’s build_phase calls new or create to create its child(ren), not the other way around. The uvm_phase calls build_phase recursively starting from the component you specified in run_test task or by a command-line plusarg +UVM_TESTNAME.

      1. Hi,
        Thanks for the wonderful article.
        I have a doubt. I believe run_phase is bottom_up and time consuming (because it is a task).
        If that is the case, the run_phase of which bottom most component will get executed? Will the parent component run_phase wait till the child component run_phase gets over? Or the run_phase of all components starts in parallel?

  6. Hi Keisuke,
    I have a few questions about phasing,
    1) Dose run phase of all components run in parallel? I feel it need to run in parallel.
    2) Dose m_phase_proc.kill() wait until any single objection is pending? or dose it kill the tasks of the components that did not raise objection
    and wait for the components who have raised objection to drop theirs before killing the task?
    3) Monitor’s run_phase task as I see is in forever loop. If in my top testbench module, say I only need to observe and I just instantiate monitors in this top module,
    do I need to raise objection in the monitor’s run_phase() task? I don’t think this would work as because of forever loop, objection would never be dropped.
    How then I can ensure that monitor continues to observe through out simulation?
    4) How dose uvm_phase find the components that are instantiated?
    Regards,
    Gautam

      1. Yes, each component’s run_phase is executed in a separate process.
      2. The component’s process is killed only after all objections are dropped. All processes are killed at the same time regardless of component’s objection being raised or not.
      3. I would not use the objection in the monitor and let the other components (test, for example) control the objection.
      4. The uvm_phase traverses all the child components starting from the uvm_top. Note that any component whose parent is specified as null becomes a child of the uvm_top.

Leave a Reply to Rui Huang Cancel reply

Your email address will not be published. Required fields are marked *