Jump to content
Sign in to follow this  
pulzar

Using run phase and new UVM phases at the same time

Recommended Posts

My understanding is that, logically, run phase is just forked off in parallel with the new phasing system... I have two questions regarding this:

1) How does one work in a mixed environment where some components are using run_phase and others are using new phases? Run phase seems to end as soon as all components using run_phase are not objecting, even though some new components are running, say, configure_phase. Shouldn't run_phase run until all of new run phases have completed? Is there an easy way for me to do this?

2) I have a driver that drives transactions in both configure and main phases, and possibly any other in-between phase that any sequence/test decides to use. So, most run-time phases will have the same functionality, except for reset_phase, which needs to do something else. Is it "ok" to implement common functionality in run_phase and still implement reset_phase, or is mixing two phase systems considered bad?

Thanks!

Share this post


Link to post
Share on other sites

1) [snip] Shouldn't run_phase run until all of new run phases have completed

As I understand it, the run_phase does continue to run until all run phases have completed, even if there is no objections to end the run_phase. It will be ready to end, though, and raising an objection to end the phase will no longer take effect.

2) [snip] Is it "ok" to implement common functionality in run_phase and still implement reset_phase, or is mixing two phase systems considered bad?!

I guess this depends on what the run_phase does. You may have a problem if it runs sequences or does something else that should run to completion. By the way, why do you want to use the new phasing?

Erling

Share this post


Link to post
Share on other sites

Pulzar,

When mixing phase domains, you should think about when the next phase can begin, not when the current phase ends. The extract phase cannot being until both the shutdown and run phases are done.

You typically want the driver to have one active thread getting transactions and transferring it to the interface at a time. The driver won't know from which phase the sequence item was generated in.

Share this post


Link to post
Share on other sites
My understanding is that, logically, run phase is just forked off in parallel with the new phasing system...

correct

I have two questions regarding this:

1) How does one work in a mixed environment where some components are using run_phase and others are using new phases? Run phase seems to end as soon as all components using run_phase are not objecting, even though some new components are running, say, configure_phase. Shouldn't run_phase run until all of new run phases have completed? Is there an easy way for me to do this?

a phase can start when ALL predecessors have completed (through dropping their objection). until the successor starts all predecessors continue to run (even with their objection gone to zero). dropping the objection just means "from my point of view, i could continue with the next phase". so the "easy way" you are asking for is the builtin way.

2) I have a driver that drives transactions in both configure and main phases, and possibly any other in-between phase that any sequence/test decides to use. So, most run-time phases will have the same functionality, except for reset_phase, which needs to do something else. Is it "ok" to implement common functionality in run_phase and still implement reset_phase, or is mixing two phase systems considered bad?

Thanks!

- you should be using the runtime phases to start behaviour via the default_sequences hook only

- its ok to do things via run and the runtime phases. but you have to be aware that there are some constraints. for instance if you really want to jump phases (backward) then only the runtime phases can be affected. you cant jump the run_phase. if you do jumps then threads in run and the other phases are separate.

- the driver should drive independently of the phase (and potentially all the time if the protocol requires it).

Share this post


Link to post
Share on other sites

Thanks for the replies!

By the way, why do you want to use the new phasing?

I wanted to run an init sequence in configure_phase of the base test. That way, the other tests don't have to explicitly call it, and their main_phase starts automatically after the init is done.

You typically want the driver to have one active thread getting transactions and transferring it to the interface at a time. The driver won't know from which phase the sequence item was generated in.

- the driver should drive independently of the phase (and potentially all the time if the protocol requires it).

That makes sense from a logical point of view, but how does that fit into UVM phasing? I.e., since the drive is a component, it also has the configure, run, etc. phases, so how do I make one thread that runs through all of those phases? I have to put the "get_next_item / drive / item_done" loop into some phase, no?

Share this post


Link to post
Share on other sites

hi,

>That makes sense from a logical point of view, but how does that fit into UVM phasing? I.e., since the drive is a component, it also has the configure, run, etc. phases, so how do I make one

> thread that runs through all of those phases? I have to put the "get_next_item / drive / item_done" loop into some phase, no?

if you want it all time running hook it into the "run" phase.

Share this post


Link to post
Share on other sites

I wanted to run an init sequence in configure_phase of the base test. That way, the other tests don't have to explicitly call it, and their main_phase starts automatically after the init is done.

Since you have a common base class for the tests, you could simply have it implement the run() method and start the configure sequence there first thing, and then call a virtual task to be implemented by derived classes to start the test specific sequences from there.

[snip]so how do I make one thread that runs through all of those phases?

You could have the driver work in the run_phase, but you may have a problem if the driver has sections that should not be aborted in the middle. The phase_started/ready_to_end() interface instead of (or in addition to) the *_phase() interface may help in this regard, but I would consider simpler solutions first.

Erling

Share this post


Link to post
Share on other sites
Since you have a common base class for the tests, you could simply have it implement the run() method and start the configure sequence there first thing, and then call a virtual task to be implemented by derived classes to start the test specific sequences from there.

Even simpler: start the configuration sequence in the base_test::config_phase() task then test-specific sequences can be started in the test's main phase via a virtual sequencer's main_phase.default_sequence or by explictly starting them in a test's main_phase task().

You could have the driver work in the run_phase, but you may have a problem if the driver has sections that should not be aborted in the middle.

The run phase runs for the entire duration of the test hence it is NOT possible for the driver to be aborted if it is implemented in that phase (as is should). Alternatively, raise the current phase objection if you want to prevent being aborted and drop it for those times where stopping is OK.

Share this post


Link to post
Share on other sites

Even simpler: start the configuration sequence in the base_test::config_phase() task then test-specific sequences can be started in the test's main phase via a virtual sequencer's main_phase.default_sequence or by explictly starting them in a test's main_phase task().

Yeah, that's exactly what I'm doing. Now, is there some advantage of using default_sequence instead of explicitly creating and starting one from the test? I've seen someone write that one should always use default sequences, but I couldn't find any explanation as to why he felt that way..

Share this post


Link to post
Share on other sites

The advantage as that they are just that: default sequences. Thus a test need only worry about the non-default stuff it needs to do. if every test has to start everything, it will become a maintenance headache should the default behavior shared by many tests need to change.

Share this post


Link to post
Share on other sites

The run phase runs for the entire duration of the test hence it is NOT possible for the driver to be aborted if it is implemented in that phase (as is should). Alternatively, raise the current phase objection if you want to prevent being aborted and drop it for those times where stopping is OK.

I'm sorry for confusing people. I did re-test this, and it works as you point out. The only exception I found was jumping from a run phase to the report phase or beyond. The run_phase may be aborted inside an objection raise/drop pair in this case.

Erling

Share this post


Link to post
Share on other sites

The advantage as that they are just that: default sequences. Thus a test need only worry about the non-default stuff it needs to do. if every test has to start everything, it will become a maintenance headache should the default behavior shared by many tests need to change.

Agreed, of course, but that's just one of the mechanisms for specifying common test behaviour (another being, for example, a base test class, or classes, that do the same thing). I was more curious about running a sequence on a sequencer through the default sequence hook vs. explicitly starting it in some common code. Doing it explicitly feels like it produces more obvious and readable code vs. what feels like a back door way of getting a sequence started, no?

Share this post


Link to post
Share on other sites

Both are functionally the same. If you start a bunch of sequences in a base class, you can use the factory to replace them.

One advantage of using default sequences is that, should the sequencer not exists because the agent is now passive, the default sequence setting will still work (and be ignored) whereas the seq.start() statement won't be and the sequence will be started as a virtual sequence without a parent sequencer.

Share this post


Link to post
Share on other sites

One advantage of using default sequences is that, should the sequencer not exists because the agent is now passive, the default sequence setting will still work (and be ignored) whereas the seq.start() statement won't be and the sequence will be started as a virtual sequence without a parent sequencer.

Ah, good one. That is indeed convenient.

Share this post


Link to post
Share on other sites

As a user, experience has shown that default sequences can turn into a maintenance headache.

So is everything that relies on strings settings.

eg. :

uvm_config_db#(...)::set(this,"top_level_env.ubus_example_tb0.ubus0.masters[0].sequencer.main_phase","default_sequence", ....

Now if there are many of these statements, the strings being potentially very long, any typo or instance name mismatch will not be spotted : it may takes ages to spot a problem ... or never spot it. Legacy code is difficult to maintain and to bring up.

With seq.start(), an error is immediately noticed at compile time.

More generally any string based setting is a maintenance headache.

All the default behavior shared by many tests can very well be put in 1 base test, that all tests derive from.

Share this post


Link to post
Share on other sites

Even simpler: start the configuration sequence in the base_test::config_phase() task then test-specific sequences can be started in the test's main phase".

I wonder how this solution is going to help when things get less trivial. There will be activities that must run all the time, part of the time, overlapping, once, repeating etc. Not sure how to implement this with the *phase() api, which seems to be more like a ghost in the machine running a serialized fork fest through the simulation.

A reasonable development in this case could be a different config from one run to the next. Say, for example, that another test should do whatever the first test did, but now repeatedly, including the config. How would you do this if the config phase is modeled as a *phase() callback?

Erling

Share this post


Link to post
Share on other sites

If you need activity to span multiple phases, you have three options:

1) Implement it in uvm_component::run_phase();

2) Start the thread in uvm_component:: phase_started();

3) Create a user-defined phase that is concurrent to the spanned phase and implement it in that new phase's task

Edited by janick

Share this post


Link to post
Share on other sites

As a user, experience has shown that default sequences can turn into a maintenance headache.

Exactly. And (as discussed in an other thread) it isn't necessary to call seq.start() in the test either, that can be done at the environment top-level. That way, tests will not have to peek inside the environment. Same with virtual sequences, and thus virtual sequencers need not be connected to sub-sequencers. As an example, consider a virtual sequence running two sequences in parallel:

task body();
  fork
    RunSequence(Seq1::Create());
    RunSequence(Seq2::Create());
  join
endtask

Here, the inherited RunSequence() locates the sequence starter in the testbench, which in turn performs sequence to sequencer mapping and starts the sequence. Have used this technique for months now and it works like charm.

Erling

Share this post


Link to post
Share on other sites

1) Implement it in uvm_component::run_phase();

Problem with this is that activity being moved to the run phase will get the same starting point, and thus it may still be necessary to control the order of things by some other means.

2) Start the thread in uvm_component:: phase_started();

3) Create a user-defined phase that is concurrent to the spanned phase and implement it in that new phase's task

This is much more promising. I wonder why the phase*() interface is limited to components, though. It seems components does not actually need the *phase() interface, while any class could benefit from a phase*() interface. There may be good reasons for this being as it is, I don't know.

BTW, I did some experimenting with custom phases and managed to corrupt the phase DAG. It may be totally unrelated, but it seems there is a problem in uvm_phase::add() when adding a phase in between phases. There is a check for the before phase being a successor to the after phase, it goes like this:

if (after_phase.m_successors.exists(before_phase)) begin

after_phase.m_successors.delete(before_phase);

before_phase.m_successors.delete(after_phase);

end

One should think the after phase should be deleted from the before phase predecessors, not successors.

Erling

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×