I also recently ran into this situation where I was surprised that post_predict() callback is not called when kind is UVM_PREDICT_DIRECT. I took a quick look at the last link Tudor posted, and I agree you don’t want an endless loop, but my situation is not like that and it would be ideal for a post_predict() UVM_PREDICT_DIRECT callback (but it doesn’t exist!!)
I am modeling the behavior of interrupts. When I detect some hardware condition, in my predictor code, I can call some_int.predict(value) and this is OK. But I also need to predict the upper level interrupt which is a combination of a few different interrupt bits, their respective mask bits, and my strategy was to define this logical function once in a callback, and hook it up to each of the “input” fields that make up the upper level interrupt. The code is below:
If anyone has a suggestion or a better/more efficient way I should be implementing this, I would greatly appreciate it! I don’t want to resort to a brute-force “hardware engineer” approach of creating a predictor component which has to run and sample every clock to call get_mirrored_value() from the input interrupts and masks and then call predict on the upper level interrupt, or some other ugly solution. Having my code run in a post_predict() callback every time any of the inputs change, whether it is the mask bits being updated by a write access, or the lower interrupts being updated (and a direct predict being called), seemed to be an efficient solution to me.
class reg_update_upper_interrupt_cb extends uvm_reg_cbs;
// references to related reg fields for this interrupt combine function
uvm_reg_field someint;
uvm_reg_field someintmask;
uvm_reg_field otherint;
uvm_reg_field otherintmask;
uvm_reg_field upperint;
function new(string name = "reg_update_upper_interrupt_cb");
super.new(name);
endfunction
// Give the callback a pointer its associated reg block
virtual function void configure(...my arguments...);
// Assign references to the fields that are inputs to upper interrupt
someint = <some_reg_block path to field>...
...
upperint = <some_reg_block path to field>...
endfunction
`uvm_object_utils(reg_update_upper_interrupt_cb)
virtual function void post_predict(input uvm_reg_field fld,
input uvm_reg_data_t previous,
inout uvm_reg_data_t value,
input uvm_predict_e kind,
input uvm_path_e path,
input uvm_reg_map map);
bit int_status; // combined interrupt request status value
// Note: mask bits will typically predicted with UVM_PREDICT_WRITE,
// the int bits will be DIRECT (assigned by my testbench!)
if (kind inside {UVM_PREDICT_WRITE, UVM_PREDICT_DIRECT}) begin
int_status = someint.get_mirrored_value() &&
!someintmask.get_mirrored_value() ||
otherint.get_mirrored_value() &&
!otherintmask.get_mirrored_value();
// Update the value of the upper interrupt based on calculation
upperint.predict(int_status);
end
endfunction
endclass : reg_update_upper_interrupt_cb
... Registering the callbacks
reg_update_upper_interrupt_cb upper_int_cb;
// Add to all "input" fields that contribute to upper level interrupt,
// but post_predict() doesn't trigger for someint and otherint because
// they are UVM_PREDICT_DIRECT :-(
uvm_reg_field_cb::add(someint, reg_update_upper_interrupt_cb);
uvm_reg_field_cb::add(someintmask, reg_update_upper_interrupt_cb);
uvm_reg_field_cb::add(otherint, reg_update_upper_interrupt_cb);
uvm_reg_field_cb::add(otherintmask, reg_update_upper_interrupt_cb);