Hi everyone,
I’ve got a UVM driver that sends data to the DUT. Under some circumstances the DUT could request the stimulus to be halted if the RTL doesn’t have space available to store the next data beat.
In the UVM driver I’m finding a race condition. The driver sends data to the DUT, but when it’s about to send the data the value of the halt signal isn’t updated. This results in the RTL missing one of the data beats because it doesn’t have storage available for it.
I’ve tried using clocking blocks, but I haven’t been able to figure out why the driver doesn’t see the updated value of the halt signal.
I’ve pasted below the two “versions” of the UVM driver as well as the interface where it shows the clocking block.
Note that I’m not using any sequences and sequencer. I’m randomly generating data for the RTL in the driver.
Driver using clocking blocks:
class duplex_if_driver extends uvm_driver;
`uvm_component_utils(duplex_if_driver)
//Config object:
duplex_if_cfg m_cfg;
//Virtual interface:
virtual duplex_if_if #(addr_width_p, data_width_p) vif;
duplex_if_txn#(addr_width_p, data_width_p) duplex_txn_q[$];
bit drive_reads;
rand int unsigned delay;
bit disable_delay_constraint;
rand bit [data_width_p-1:0] data;
rand bit error;
bit disable_error_constraint;
bit previous_clk_finish;
int unsigned clk_counter;
int unsigned read_delay_q[$];
int unsigned dummy_halt;
constraint random_delay{
if(!disable_delay_constraint) {
delay dist{
[1:5] :/ 80,
[6:20] :/ 10,
[21:200]:/ 5,
[201:500]:/ 5
};
}
}
constraint random_error { //Most of the time the interface won't return an error
if(!disable_error_constraint) {
error dist {0 :/98, 1 :/ 2 };
}
}
function new(string name, uvm_component parent=null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
if(drive_reads)
reads();
else
writes();
endtask // run_phase
task reads();
bit active_read;
int unsigned previous_q_size;
forever begin
@(vif.cb_write);
vif.cb_write.finish <= '0;
vif.cb_write.error <= 'X;
vif.cb_write.data <= 'X;
vif.cb_write.b2b_rready <= 0;
dummy_halt = vif.cb_write.b2b_halt;
if(vif.reset_in==0) begin
previous_q_size = read_delay_q.size;
if(read_delay_q.size>0) begin
delay = read_delay_q[0];
if(delay==0 && vif.cb_write.b2b_halt==0) begin //when the simulator reaches here the value of this signal isn't updated yet
disable_delay_constraint = 1;
disable_error_constraint = 0;
active_read = 1'b0;
if(!this.randomize())
`uvm_fatal({"duplex_if_driver_",get_name()}, "Randomization Failure")
vif.cb_write.finish <= 1'b1;
vif.cb_write.error <= error;
vif.cb_write.data <= data;
read_delay_q.pop_front;
vif.cb_write.b2b_rready <= 1;
end
for(int i=0; i < read_delay_q.size ; i++)
if(read_delay_q[i]>0)
read_delay_q[i]--;
end
if(vif.cb_write.enable && (read_delay_q.size < mem_b2b_reads_p)) begin
disable_delay_constraint = 0;
disable_error_constraint = 1;
if(!this.randomize())
`uvm_fatal({"duplex_if_driver_",get_name()}, "Randomization Failure")
// active_read = 1'b1;
read_delay_q.push_back(delay);
end
previous_clk_finish = vif.cb_write.finish;
end // if (vif.reset_in ==0)
if (previous_q_size < mem_b2b_reads_p) vif.cb_write.b2b_rready <= 1;
clk_counter++;
end // forever begin
endtask // reads
endclass
Interface where the clocking blocks are defined:
interface duplex_if_if #(int addr_width_p=32,
int data_width_p=16
) (
input clk_in,
input reset_in
);
wire clk;
wire reset;
assign clk = clk_in;
assign reset = reset_in;
logic [addr_width_p-1:0] addr;
logic enable;
logic finish;
logic b2b_rready;
logic b2b_halt;
logic error;
logic [data_width_p-1:0] data;
clocking cb_write @(posedge clk);
default input #1step output #1step; // Sample signals (input) 1step before posedge, drive signals (output) 1step after posedge
output error, finish , b2b_rready, data;
input addr, enable, b2b_halt;
endclocking // cb_write
endinterface
Driver reading signal values straight from the interface:
class duplex_if_driver extends uvm_driver;
`uvm_component_utils(duplex_if_driver)
//Config object:
duplex_if_cfg m_cfg;
//Virtual interface:
virtual duplex_if_if #(addr_width_p, data_width_p) vif;
duplex_if_txn#(addr_width_p, data_width_p) duplex_txn_q[$];
bit drive_reads;
rand int unsigned delay;
bit disable_delay_constraint;
rand bit [data_width_p-1:0] data;
rand bit error;
bit disable_error_constraint;
bit previous_clk_finish;
int unsigned clk_counter;
int unsigned read_delay_q[$];
int unsigned dummy_halt;
constraint random_delay{
if(!disable_delay_constraint) {
delay dist{
[1:5] :/ 80,
[6:20] :/ 10,
[21:200]:/ 5,
[201:500]:/ 5
};
}
}
constraint random_error { //Most of the time the interface won't return an error
if(!disable_error_constraint) {
error dist {0 :/98, 1 :/ 2 };
}
}
function new(string name, uvm_component parent=null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
endfunction
task run_phase(uvm_phase phase);
super.run_phase(phase);
if(drive_reads)
reads();
else
writes();
endtask // run_phase
//non clocking
task reads();
bit active_read;
int unsigned previous_q_size;
forever begin
@(posedge vif.clk_in);
vif.finish = '0;
vif.error = 'X;
vif.data = 'X;
vif.b2b_rready = 0;
dummy_halt = vif.b2b_halt;
if(vif.reset_in==0) begin
previous_q_size = read_delay_q.size;
if(read_delay_q.size>0) begin
delay = read_delay_q[0];
if(delay==0 && vif.b2b_halt==0) begin //when the simulator reaches here the value of this signal isn't updated yet
disable_delay_constraint = 1;
disable_error_constraint = 0;
active_read = 1'b0;
if(!this.randomize())
`uvm_fatal({"duplex_if_driver_",get_name()}, "Randomization Failure")
vif.finish <= 1'b1;
vif.error <= error;
vif.data <= data;
read_delay_q.pop_front;
vif.b2b_rready <= 1;
end
for(int i=0; i < read_delay_q.size ; i++)
if(read_delay_q[i]>0)
read_delay_q[i]--;
end
if(vif.enable && (read_delay_q.size < mem_b2b_reads_p)) begin
disable_delay_constraint = 0;
disable_error_constraint = 1;
if(!this.randomize())
`uvm_fatal({"duplex_if_driver_",get_name()}, "Randomization Failure")
// active_read = 1'b1;
read_delay_q.push_back(delay);
end
previous_clk_finish = vif.finish;
end // if (vif.reset_in ==0)
if (previous_q_size < mem_b2b_reads_p) vif.b2b_rready <= 1;
clk_counter++;
end // forever begin
endtask // reads
endclass
Nasty fix without using clocking blocks that I really don’t like:
@(posedge vif.clk_in);
#1;
I’ve run the test using breakpoints and I see that by the time the UVM driver reaches the b2b_halt signal its value isn’t updated yet. Is there any way I can ensure that by the time the simulation reaches the check for the ‘halt’ signal the value corresponds to what the DUT it’s outputting?
Many thanks in advance, any help is very much appreciated :)
Antonio