Why the call back post_predict is not call if you try to predict a register field with UVM_PREDICT_DIRECT?

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);