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?
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.
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.
Is there any way I can access the clock in sequence and introduce required delay(in terms of clocks) before sending next trigger?
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.
@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!
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.
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!
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 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:
Write into RAM1 (800 bytes):
**** I generated sequences with wr_addr, wr_en, wr_data
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
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.
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.
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.
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
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.
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 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?
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.
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?
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.
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.
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)
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
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
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.