Best way to introduce delay between sequences

I have a scenario where in, I just provide a trigger(input control) signal to DUT and series of processes take place inside the DUT. And on TB side, i have to wait for as many clock(around 300,000 clks) before sending another trigger, to ensure that the outputs following the first trigger are generated.
Which is the recommended way to achieve?

  1. send a sequence with control(trigger) signals set to high and another sequence with control signals set to zero. In driver, wait for as many clocks in the drive task. But not sure this delay will be effective after sending 2nd sequence. Remember , coming across a situation wherein, simulation ended after the last sequence.
  2. Keep sending the sequences on every clock(even for delay period waiting for DUT processing) with control signal set to zero. That is what i did in some other case. But in this case, its a huge delay.
  3. Is there any way I can access the clock in sequence and introduce required delay(in terms of clocks) before sending next trigger?

Please let me know

In reply to uvmsd:

The sequencer/sequence works on the transaction level. This does not know anything about timing, clock cycles etc. And the sequencer is not sending anything. The driver is retrieving a seq_item from the sequencer. In the default implementation this interface is blocking, i.e. the driver is waiting until a seq_item is ready.
In your case you have to implement ‘your delay’ in the driver. I believe you have soething like an acknowledge indicating your DUT is ready and waiting for a new input.
After seeing this acknowledge you can execute ‘item_done’. Then the driver will get the next seq_item.

In reply to chr_sue:

@chr_sue, no, my DUT doesn’t have any indication. That’s the tricky part. But i think, i can come up with approximate number of clock cycles(using some parameters). Need to see. DuT generates addresses, wr_en for two different RAMs after an input trigger with some deliberate delays in between.

BTW, if at all there is an acknowledgement, how do we handle? Is it through an uvm_event from monitor to driver? Was just curious to know.
Thank you!

I came across a link related to above topic:

In reply to uvmsd:

A certain number of clock cycles can be also such an indication.

Following the link from Accellera is not a good coding practice because you are violating the TLM rules. This might also cause additional problems or additional waiting times.
If have never seen this in practice and I have never used this in projects and I’ll never, never recommend this.
You can control in all cases the delay from the driver. The monitor is also not a good idea because the monitor does not have any permission to stop the execution of the DUT. This would result in a loss of data.

If you are doing this in the driver, you do not need any uvm_event. Add to your drivers run_phase (at the end) only the number of clock cycles you have to wait.
Then call item_done and you can retrieve with get_next_item the next seq_item.

In reply to chr_sue:

I wasn’t either convinced with the inputs in above Accellera link. Hence posted the question here.

I didn’t mean to add any delay in monitor. I wanted to ask, if there is an acknowledgement from the DUT to get the next set of inputs, do we pass this acknowledgement from monitor to driver through an event? I wanted to check that.

Will look into possibilities of coming up with certain delay. Thank you!

In reply to uvmsd:

You can observe all pinlevel signals in the driver. If there is an acknowledgement you can do this from the driver. This is the easiest way. If you like complicated solutions you can observe the ack in the monitor and trigger an event which is considered in the driver (not recommended).

You can also add a random delay in the driver between 2 seq_items by randomizing a local variable.

In reply to chr_sue:

oh!yes. realized it later. Thank you!

In reply to chr_sue:

In reply to uvmsd:
A certain number of clock cycles can be also such an indication.

@chr_sue
I could come up with certain number of clocks to introduce delay in driver. Looks like i am kind of stuck with the monitor now.
Here are the thigs to follow for my TB:

  1. Write into RAM1 (800 bytes):
    **** I generated sequences with wr_addr, wr_en, wr_data

  2. Trigger a control module which reads data from the above RAM1, writes into another similar RAM2 (with huge interleaved delays)
    **** I generated sequences t set/reset the trigger bit
    **** based on the above trigger bit, I waited for a certain delay in the Driver

  3. Read the RAM2 and check the data integrity.

I have just started working on Monitor. Since there is a clk delay to read RAM2, i need to add 1 clk delay to get rd_data, without affecting inputs capture.
I am thinking something like below:


seq_item mon_items[$];
virtual task run_phase(uvm_phase phase);
  fork
    forever begin
      seq_item seq_item_collected=new;
     @(vif.cb_clk);
     // capture the RAM1 write inputs
      seq_item_collected.ram1_wr_addr= vif.ram1_wr_addr;
       :
       :
     if(vif.trigger)  begin //for the control_module
       //wait for **certain delay**  
       delay_done = '1;
      end
      if(delay_done)
        seq_item_collected.ram2_rd_addr= vif.ram2_rd_addr;
      mon_items.push(seq_item_collected);
    end //forever
    forever begin
      @(vif.cb_clk);
      @(vif.cb_clk);
       if(mon_items.size() > 0) begin
         seq_item seq_item_collected = mon_items.pop_front();

       seq_item_collected.ram2_rd_data= vif.cb_clk.ram2_rd_data;

       if(seq_item_collected.ram1_wr == 1 || delay_done)
         trans_collected_port.write(seq_item_collected);
       end
    end //forever
  join
endtask  

Its a rough code that i have thought.

  1. With above approach, i am apprehensive if all the certain delays(driver, monitor) would match and the sequences would occur as expected. And also the read to RAM2(rd_addr,rd_data) are placed at right time.
  2. Other way is sending an uvm_event from driver when actual read_addr are placed for RAM2 so that, driver and monitor are in sync.
    3.Add a control bit in seq_item, just to indicate its a read to RAM2. This can be used in monitor to sample the rd_data from RAM2. This is more clear than other two options. But is it good practice to add a bit in seq_item just for TB use?
    I have just started working on UVM TBs and don’t have any UVM code, guidelines or anyone who knows UVM at work.
    Just get thinking about the options. Please guide.

In reply to uvmsd:

A few questions to get the right understanding:
(1) you have sequence executing WR to RAM1; generates 800 seq_items with different addr and data. Correct?
(2) What kind of component is the control module you are triggering. Is this a Verilog module or is it a uvm_component belonging to your verification environment?

You can obeserve the internals of this control module. If it is a Verilog/SV module you can bind an observing component to the control module counting the numbers of reads and writes. If the reads and writes are complete you can trigger an event which is waited on in the driver and you can continue with your writes to RAM1

In reply to chr_sue:

1.yws. 800 sequences have different address and data for RAM1(writing into all the locations)
2. Yes, control module is a verilog module. I am sending two sequences which would ensure a pulse of trigger(high and low). This value will be set to zero in above 800 sequences.

What you have suggested matches to option 2 of above list. I think I have access to signals of control module through interface. Does the bind you mentioned is similar to bind used in assertions?
Thank you, will look into it.

In reply to uvmsd:

SystemVerilog has only 1 bind construct. You can use it to observe signals in your DUT and also drive signals if the DUT is written in SV. Binding assertions to your DUT is one application.

In reply to chr_sue:

Thank you chr_sue.
I was exploring uvm_events and came across a post with your reply(in this forum):

class driver1 extends uvm_driver #(my_tx);
uvm_event_pool ev_pool = uvm_event_pool::get_global_pool();
//...
task run_phase(uvm_phase phase);
uvm_event ev = ev_pool.get("driver1_ev");
ev.trigger(req);

endtask
//...
endclass

class driver2 extends uvm_driver #(my_tx);
uvm_event_pool ev_pool = uvm_event_pool::get_global_pool();
//...
task run_phase(uvm_phase phase);
uvm_event ev = ev_pool.get("driver1_ev");
ev.wait_trigger();
$cast(req, ev.get_trigger_data()); 
//...
endtask 
//...
endclass 

In driver1, I see:
ev.trigger(req);
But i dont see #type mentioned while declaring uvm_event ev. Isn’t it required? It takes any data type?
On driver2, I see:
$cast(req, ev.get_trigger_data());
req will hold the data from driver1, right?

And one more thing, with event_pool, we can access the events in any component including sequence, right?

In reply to uvmsd:

The uvm_event_pool is a construct useful for horizontal synchronization between different agents. If you are in the same agent you can simply use uvm_event.
With respect to your questions.

ev.trigger(req);

accepts as argument any uvm_object, like a seq_item because it is extended from uvm_object.

ev.wait_trigger();
$cast(req, ev.get_trigger_data());

get_trigger returns an object of uvm_object and we have to cast this to our seq_item type.

In reply to chr_sue:

Thank you so much!
I will remove global_pool for events. My understanding is that, we can use global-pool within same agent but it’s unnecessarily gets complicated.

I can use uvm_config_db to set(in driver)/get(in other modules) the event, right? Any other method recommended?

Thanks again

In reply to uvmsd:

Yes you can pass the event to the config_db and retrieve in another component. Or you are adding the vent to your virtual interface. Then you can simply trigger like a clock.

In reply to chr_sue:

Thank you!
Sorry, didn’t get the second part.
“Or you are adding the vent to your virtual interface. Then you can simply trigger like a clock.”

Any link to understand it better would be helpful. I have already implemented using configDb. But just wanted to know about the other option too.

In reply to uvmsd:

You can add to your SV interface which makes the connection between the DUT and the UVM environment an additional signal of type uvm_event. This will be triggered somewhere in a driver and caan be seen in the monitor for instance.

In reply to chr_sue:

Thanks Chris,
I could almost achieve what i wanted. Will keep the above option in my mind.

I have 2 questions, which i came across with this TB:(Please let me know if i need to raise these questions separately in the forum)

  1. In sequence, I was trying to wait for uvm_event(triggered in driver). But got a loading error for configdb check line. I removed it and added few extra sequences to take care of it. Can’t we use uvm_event in sequence?
class grp_ctrl_sequence extends uvm_sequence#(grp_ctrl_seq_item);
  grp_ctrl_seq_item req;
  uvm_event DELAY_DONE;
  virtual task body;
   //got a loading error for below
    if(!uvm_config_db#(uvm_event)::get(this, "", "driver_event", DELAY_DONE))
      `uvm_fatal("NO_UVM_EVENT",{"uvm event must be set for: ",get_full_name(),".driver_event"}); 
   :
   :
     // DELAY_DONE.wait_trigger();
   :
   :
 endtask
endclass
  1. There are multiple instances of below RAM in my design. read_addr input is provided by the TB with clocking block. So it has input skew. I did check the waveform to confirm the same. But the strange thing is, rd_data is available in the same clock(i.e. it behaves as if there is no input skew). Below is the code. Any idea, why does it behave so?
module RAM #(
    parameter DATA = 16,
    parameter ADDR = 4
) (
    input   logic                clk, 
    // Write Port
    input   logic                wr,
    input   logic    [ADDR-1:0]  wr_addr,
    input   logic    [DATA-1:0]  wr_data, 
    // Read Port
    input   logic    [ADDR-1:0]  rd_addr,
    output  logic    [DATA-1:0]  rd_data
); 
    // Shared memory
    logic [DATA-1:0] mem [(2**ADDR)-1:0];
    initial begin 
        for (int i = 0; i < 2**ADDR; i++) begin
            mem[i] = '0;
        end
    end     
    // Write Port
    always @(posedge clk)
        if(wr)
            mem[wr_addr] <= wr_data;     
    // Read Port
    logic [ADDR-1:0] rd_addr_reg;
    always @(posedge clk)
        rd_addr_reg <= rd_addr;
     
    assign rd_data = mem[rd_addr_reg]; 
endmodule

In reply to uvmsd:

There is limitation regarding the usage of the uvm_event. The problem with the uvm_event in the sequence is you do not know when the get is issued. Looks like you do in the sequence the get to the config_db before the set was done.
With respect to the uvm_components this does not happen because the run_phases are executed in parallel.

If you need the trigger in the sequence the event_pool is recommended.

With respect to your DUT model. It works as you have described. The read data are available in the same clock cycle where you pass the addr.
If you want to see the read data later you have to insert an additional clock edge.

In reply to chr_sue:

Thank you Chris!
Right now, I don’t really need event trigger in sequence.

Yes,you are right about DUT(RAM)behavior. TB should handle it that way when input(with respect to RTL) skew is non-zero.
Thank you!