Jump to content

How to delay a sequence from a test


Recommended Posts

I have made it a standard practice to add a wait_cycles() task to all my sequencers to abstract this.  Then in my sequences I can simply write:

 

p_sequencer.wait_cycles(x);

 

This requires you use the `uvm_declare_p_sequencer() macro in your sequence.  Too bad that's not built into uvm_sequencer.

 

-Ryan

Link to comment
Share on other sites

hi,

 

i would say its good that p_sequencer isnt set by default in your sequence. it would create a strong typewise association between the seqeunce type and the sequencer type (as it was before uvm11). looking at the original issue i wouldnt put any low level cycle info info into the sequencer. the sequence/sequencer should delegate all low level bit cycle stuff to the driver and the virtual interface. 

 

 

I have made it a standard practice to add a wait_cycles() task to all my sequencers to abstract this.  Then in my sequences I can simply write:

 

p_sequencer.wait_cycles(x);

 

This requires you use the `uvm_declare_p_sequencer() macro in your sequence.  Too bad that's not built into uvm_sequencer.

 

-Ryan

Link to comment
Share on other sites

I kind of have a gripe against directly referring to the interface from a sequence for these common use case scenarios.  Different sequences on different interfaces can be doing the same thing, but implement it differently and it looks different.  m_sequencer is the window/proxy by which a sequence knows which interface it is operating on. I was just meaning to say a virtual wait_cycles() method in the uvm_sequencer_base, would make it possible for all sequences to call m_sequencer.wait_cycles(x) without having to refer directly to the interface.  That of course adds the burden of having to extend the base sequencer if you want to use that functionality. This would not create a strong type association.

 

Also it would be so nice if a sequence could call, m_sequencer.get_interface(), but because interfaces lack polymorphism that API is not possible.  I wish interfaces had some limited form of polymorphism.

 

hi,

 

i would say its good that p_sequencer isnt set by default in your sequence. it would create a strong typewise association between the seqeunce type and the sequencer type (as it was before uvm11). looking at the original issue i wouldnt put any low level cycle info info into the sequencer. the sequence/sequencer should delegate all low level bit cycle stuff to the driver and the virtual interface. 

 

 

Link to comment
Share on other sites

hi,

 

>I kind of have a gripe against directly referring to the interface from a sequence for these common use case scenarios

i didnt mean that the sequencer/sequence should reference the virtual interface. the physical connection/timing should only happen at the lowest level of actual HW/DUT interfacing.

 

the term "cycles" refers to low level HW clock related timing. as such it should be finally handled in one place. that is normally the driver. delegating to the sequencer seems counter intuitive. the sequencer does not necessarily have an idea of the physical interface which is finally operating. all it is aware of is a sequencing of transactions/subsequences and a channel to pass the transaction stream to. especially in layered scenarios the sequencer doesnt know anything about the low level timing.

 

 

>  m_sequencer is the window/proxy by which a sequence knows 

 

you should never utilize any type or class member matching "m_*" out of the uvm package. these fields/variables are implementation dependent and can go at any time. they are not part of the UVM standard and are pure artifacts of the reference implementation. unfortunately some names are accessible because of some limitations in SV. 

 

regards

/uwe

Link to comment
Share on other sites

I disagree with the approach of putting code into the config object on the basis that the config object is inteded conceptually for configuration and not operation. Don't mix abstractions.

 

In order to synchronize to a hardware signal event, I think it would be architecturally cleaner to add fields to the transaction type (or a derived type) and let the driver do the delay. Consider a transaction class:

typedef enum { read_e, write_e };
class bus_trans;
  rand oper_t m_oper; // read_e, write_e
  rand addr_t m_addr;
  rand data_t m_data;
endclass

if you add an attribute m_delay, you can have the driver use this to create an arbitrary delay.

class delayable_bus_trans_t extends bus_trans_t;
  int unsigned m_delay = 0; //< default no delay
endclass

The driver could do something like:

seq_item_port.get_next_item(req);
if (req.delay == 0) begin
  // Normal transaction
  end
else begin
  repeat(req.delay) @(vif.cb);
  end

Notice that the driver is not obligated to used every attribute (field) of the transaction. In my example, the driver only does the delay. Of course this depends on the requirements of the interface.

 

You could also have driver fork off a process to wait on the delay and send a response when the delay completed. This would allow other transactions from other sequences to be intermingled while the waiting. So the sequence would be waiting on a response matching the delay request. This depends on whether the delay should be blocking or not. We describe this in the advanced sequences portion of our UVM adopters class.

 

If unable to modify the driver, then I would suggest creating a "side" agent connected to the same interface with a sole purpose of monitoring clocks, and other synchronization. You can then have your sequence call a sub-sequence on the side-agent's sequencer with a transaction to request the delay information. This may seem a bit contrary to the usual notion of a driver, but it keeps the distinction of pin-level interface activity relegated to the driver rather than polluting configuration objects.

 

A monitor could also provide the information, but then you would need to connect an analysis port or add a callback to the sequencer. Remember that the sequence wants to be reusable. Tying it too closely to the hardware violates the separation of concerns and makes it less reusable.

 

This is just one of several approaches. You could also use uvm_event_pool to create a driver clock event, but I think it is less clean. Signal level tasks should be relegated to the driver (BFM) or monitor. 

Link to comment
Share on other sites

hi,

 

>I kind of have a gripe against directly referring to the interface from a sequence for these common use case scenarios

i didnt mean that the sequencer/sequence should reference the virtual interface. the physical connection/timing should only happen at the lowest level of actual HW/DUT interfacing.

 

the term "cycles" refers to low level HW clock related timing. as such it should be finally handled in one place. that is normally the driver. delegating to the sequencer seems counter intuitive. the sequencer does not necessarily have an idea of the physical interface which is finally operating. all it is aware of is a sequencing of transactions/subsequences and a channel to pass the transaction stream to. especially in layered scenarios the sequencer doesnt know anything about the low level timing.

 

 

Good points.

 

>  m_sequencer is the window/proxy by which a sequence knows 

 

you should never utilize any type or class member matching "m_*" out of the uvm package. these fields/variables are implementation dependent and can go at any time. they are not part of the UVM standard and are pure artifacts of the reference implementation. unfortunately some names are accessible because of some limitations in SV. 

 

regards

/uwe

 

I've been real lazy about directly referring to m_sequencer rather than using get_sequencer().  You point out a good reason to stop that.

 

 

I disagree with the approach of putting code into the config object on the basis that the config object is inteded conceptually for configuration and not operation. Don't mix abstractions.

 

I concur.

 

In order to synchronize to a hardware signal event, I think it would be architecturally cleaner to add fields to the transaction type (or a derived type) and let the driver do the delay. Consider a transaction class:

typedef enum { read_e, write_e };
class bus_trans;
  rand oper_t m_oper; // read_e, write_e
  rand addr_t m_addr;
  rand data_t m_data;
endclass

if you add an attribute m_delay, you can have the driver use this to create an arbitrary delay.

class delayable_bus_trans_t extends bus_trans_t;
  int unsigned m_delay = 0; //< default no delay
endclass

The driver could do something like:

seq_item_port.get_next_item(req);
if (req.delay == 0) begin
  // Normal transaction
  end
else begin
  repeat(req.delay) @(vif.cb);
  end

Notice that the driver is not obligated to used every attribute (field) of the transaction. In my example, the driver only does the delay. Of course this depends on the requirements of the interface.

 

You could also have driver fork off a process to wait on the delay and send a response when the delay completed. This would allow other transactions from other sequences to be intermingled while the waiting. So the sequence would be waiting on a response matching the delay request. This depends on whether the delay should be blocking or not. We describe this in the advanced sequences portion of our UVM adopters class.

 

This is just one of several approaches. You could also use uvm_event_pool to create a driver clock event, but I think it is less clean. Signal level tasks should be relegated to the driver (BFM) or monitor. 

 

I don't like this approach for two reasons:

 

First and foremost, the delay before or after a transaction is not part of that transaction (mixing abstractions agains), but it is part of the sequence containing that transaction.  Hence, the sequence needs some mechanism to perform delays.  And time based delays doesn't cut it, so it needs to be cycle delays.

 

Second, it requires you use a non-blocking completion model in your driver.  For an in-order atomic interface, the driver should not have to implement a non-blocking completion model to support interleaving of sequences.

 

This makes me think uvm_sequence_base should have a wait_cycles() task, and probably several other standard utility methods.  I agree the driver should be handling the actual implementation of these tasks, so somehow the library needs to connect the sequences wait_cycles() task to a corresponding one on the driver.

Link to comment
Share on other sites

Good points.

 

 

I've been real lazy about directly referring to m_sequencer rather than using get_sequencer().  You point out a good reason to stop that.

 

 

 

I concur.

 

 

I don't like this approach for two reasons:

 

First and foremost, the delay before or after a transaction is not part of that transaction (mixing abstractions agains), but it is part of the sequence containing that transaction.  Hence, the sequence needs some mechanism to perform delays.  And time based delays doesn't cut it, so it needs to be cycle delays.

 

Second, it requires you use a non-blocking completion model in your driver.  For an in-order atomic interface, the driver should not have to implement a non-blocking completion model to support interleaving of sequences.

 

This makes me think uvm_sequence_base should have a wait_cycles() task, and probably several other standard utility methods.  I agree the driver should be handling the actual implementation of these tasks, so somehow the library needs to connect the sequences wait_cycles() task to a corresponding one on the driver.

Timing is part of a transaction so adding delays to the driver is perfectly natural. Many agents have inter-transaction delays modified in the driver's in reaction to attributes of the transaction. Don't get stuck thinking of the transaction as purely a data related object. Transactions are communication, and events (e.g. clocks or acknowledgement signals) are definitely information to be communicated.

 

You could also use a uvm_event triggered from the monitor.

 

Uvm sequences should not be tied to the hardware signals in any manner. Uvm_events are ok.

 

Finally, your delay should be a random variable and then constrained as needed.

Link to comment
Share on other sites

Timing is part of a transaction so adding delays to the driver is perfectly natural. Many agents have inter-transaction delays modified in the driver's in reaction to attributes of the transaction. Don't get stuck thinking of the transaction as purely a data related object. Transactions are communication, and events (e.g. clocks or acknowledgement signals) are definitely information to be communicated.

 

You could also use a uvm_event triggered from the monitor.

 

Uvm sequences should not be tied to the hardware signals in any manner. Uvm_events are ok.

 

Finally, your delay should be a random variable and then constrained as needed.

 

So in the scenario where the spec says write register A, then wait 40 cycles before writing register B.  You propose modeling that 40 cycle delay at the end of A, or the beginning of B.  I know that is how a lot of models do it, but that doesn't make it right.

 

In the case of APB, I'd like to allow other sequences to write to other registers during that delay.  Which implies I now need to implement what otherwise would have been a simple in order atomic driver, as a non-blocking driver, so it can fork threads and return immediately.

 

In addition to that, the sequence using the APB transaction can no longer assume that the item is done after calling finish_item(), so it now also has to check the end_event.  Furthermore, by default, the sequencer automatically triggers begin_event and end_event unless you define UVM_DISABLE_AUTO_ITEM_RECORDING.  Which affects all sequencers not just the sequencer in question.

 

That will change with UVM 1.2, but for the time being this flow is broken.

Link to comment
Share on other sites

You don't have to implement a non-blocking driver to do what you propose. You just have to make sure that the sequencer schedules the transactions in the order you want (transaction A, background traffic, transaction B ) and the driver will pull them in that order. This is what a sequencer is supposed to do: arbitrate between multiple running sequences. It's also why UVM gives us all the funky stuff like sequence priority, grabbing, locking, etc.

Link to comment
Share on other sites

You don't have to implement a non-blocking driver to do what you propose. You just have to make sure that the sequencer schedules the transactions in the order you want (transaction A, background traffic, transaction B ) and the driver will pull them in that order. This is what a sequencer is supposed to do: arbitrate between multiple running sequences. It's also why UVM gives us all the funky stuff like sequence priority, grabbing, locking, etc.

 

What am I missing here?

 

Say you model an APB transaction that includes a cycles_before_delay member.  The driver will have to implement that like this:

seq_item_port.get_next_item( item );
// stall for item.cycles_before_delay;
// do transaction
seq_item_port.item_done( );

During that stall, no other transaction will be able to execute on that driver.  Now there was the suggestion of forking this, but you can't call get_next_item() again, until you first call item_done().  Calling item_done() before the transaction is actually completed, is a non-blocking completion model, as the sequence will return from finish_item() immediately. 

Link to comment
Share on other sites

Your driver can block. Any concurrency you might want to implement you can do at the sequence level:

class some_sequence extends uvm_sequence;
  // ...
   
  task body();
    `uvm_do(item_a)
    #100ns;
    `uvm_do(item_
  endtask
endclass

class some_other_sequence extends uvm_sequence;
  task body();
    forever
      `uvm_do(background_item)
  endtask
endclass


class some_top_sequences extends uvm_sequence;
  task body();
    some_sequence some_seq = new();
    some_other_sequence some_other_seq = new();

    fork
      some_seq.start();
      some_other_seq.start();
    join_any
  endtask
endclass

In the example above (off the top of my head, not compilable) what happens is that 2 sequences are started in parallel. item_a is going to get started first (or some background item before it, depending on how the threads are scheduled), then some background traffic and then item_b. But by writing it out I do understand what you meant - you can't just rely on blocking in the driver, you need timing information in your sequence as well to do this (regardless if you use 100ns as a delay or 100 cycles, etc.). I wasn't seeing the forest because of the trees.

Link to comment
Share on other sites

Hi,

 

I have a sequence running but i want to delay that sequence for 50 clk. How can i do it from the test.

Is reset_phase a good option in the test class? or is their another way out.

 

thanks

 

 

You could simply call a method on the top level environment to perform the wait. Implement the test (and virtual sequences) by means of a specific top level env, and you have direct access to whatever services you think is required for your setup. This can be done in a generic way so that the required glue can be easily reused, and also bring additional benefits (like common services for any test, sequence or top level env).
 
The actual implementation of the wait could be forwarding to a driver, a write to a blocking port connected to a driver, some form of event arrangement with a driver, or whatever. The test and sequences will continue to work as is should you choose to go for another implementation.
 
 
Erling
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...