Using callbacks to change access to control fields within the same register

Hi! I have a register model containing multiple registers, as well as a CTRL register with multiple fields. This CTRL register contains a go_bsy field, which when is set, writes to every register/field, including itself should be ignored (you can’t write go_bsy back to 0, it is automatically deasserted by the DUT after a transaction or after reset).

I made a callback for this purpose that looks like this:

class spi_master_lock_reg_callback extends uvm_reg_cbs;

  uvm_reg_field protected_field;

  function new(string name, uvm_reg_field protected_field);
    super.new(name);
    this.protected_field = protected_field;
  endfunction

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

    if(kind == UVM_PREDICT_WRITE)
      return;

    if (value) begin
      void'(protected_field.set_access("RO"));
      `uvm_info("LOCKER", $sformatf("Locked field %s", protected_field.get_name()), UVM_LOW)
    end
  endfunction
endclass

It is instanced for each field and attached to the go_bsy field:

  foreach(ctrl_fields[i]) begin
    ctrl_locks[i] = new($sformatf("lock_%s", ctrl_fields[i].get_name()), ctrl_fields[i]);
    `uvm_reg_field_cb::add(ctrl_reg.go_bsy, ctrl_locks[i]);
  end
  // the same for other registers

The access is set to RO when I write go_bsy to 1, and it is set back to RW in my predictor after a transaction.
It works almost fine: I write go_bsy to 1 and any subsequent writes are ignored.
The issue is that if I have an operation where I write 1 to go_bsy and change any of the control fields at the same time, the other control fields do not update.

Note: it is interesting that fields that have their position before go_bsy (for example char_len’s position is reg[0:6] and go_bsy is at reg[8]), seem to update just fine. I already tried changing the order of the callbacks, so go_bsy’s is called last, but it didn’t work.

I think that the order of operations looks roughly like this:

// pseudocode
foreach (fields[i]) begin
    fields[i].apply_uvm_field_access_type();
    foreach (fields[i].registered_callbacks[j]) begin
        fields[i].registered_callbacks[j].post_predict(...);
    end
    fields[i].mirrored = predicted value
end

The fields happen to be ordered from bits low to high, which I think explains your observation that some fields get updated before the lock. Not sure if the ordering is promised by UVM spec.

If uvm_reg had support for post_predict, I would try that. Instead, you might try post_write for the register. Should work as long as all writes to the register are through the RAL.

I’m not sure if it’s bad form, but you could also simply modify your existing code to fork join_none and then #0 before calling set_access("RO")

Maybe there is a better solution, but these are the ones I know