Hi,
I am facing a threading issue when a single register instance is accessed multiple times simultaneously. Given two separate threads scheduled at the same time and each calling the uvm_reg::write() task, I see the first one to execute the call does correctly take the semaphore on uvm_reg::write().XatomicX(1) task call, and the second thread correctly blocks when it attempts uvm_reg::write().XatomicX(1) task. The first thread, in the uvm_reg::do_write() task safely immediately returns on its uvm-reg::do_write().XatomicX(1) task call and correctly executes the bus transaction. The problem arises when the uvm_reg::do_write() task returns the semaphore in its XatomicX(0) call. This:
1 - immediately wakes Thread2 which takes the semaphore and assigns its process to the local m_process variable, then continues into the uvm_reg::do_write() and correctly executes its bus transaction;
2 - once Thread2 blocks while its bus transaction takes place, Thread1 returns from uvm_reg::do_write() and hits its uvm_reg::write().XatomicX(0) task call.
At this point in time, Thread2 correctly owns the semaphore and has its process reference stored in uvm_reg::m_process. However, when Thread1 calls XatomicX(0) just before returning from uvm_reg::write(), there is no check on why the semaphore is owned.
From UVM-1.2 uvm_reg.svh:
task uvm_reg::XatomicX(bit on);
process m_reg_process;
m_reg_process=process::self();
if (on) begin
// we don't care about this branch right now
end
else begin
// Maybe a key was put back in by a spurious call to reset()
void'(m_atomic.try_get(1));
m_atomic.put(1);
m_process = null;
end
endtask: XatomicX
Because Thread2 correctly owns the semaphore AND there no checking done on ownership when bit on == 0, Thread1 is able to incorrectly put back the semaphore and invalidate the m_process reference. (The spurious reset comment does not apply).
Because of my testbench setup, my test has to immediate write this same register with another value. So, Thread1 preforms a second write() call on the same register.
This causes warnings to be emitted from the field because the register is currently busy (in the bus transaction):
function void uvm_reg_field::set(...);
...
if (m_parent.is_busy()) begin
`uvm_warning("UVM/FLD/SET/BSY",
$sformatf("Setting the value of field \"%s\" while containing register \"%s\" is being accessed may result in loss of desired field value. A race condition between threads concurrently accessing the register model is the likely cause of the problem.",
get_name(), m_parent.get_full_name()))
end
...
endfunction
and it clobbers the register value in the RAL model. Because Thread2 is currently busy on bus transaction, when it returns it may perform the do_predict call while Thread1 is busy on its bus transaction for the same register. I'm not sure what will happen here.
I am thinking that the XatomicX should have a check on the process when bit on == 0, similar to when the bit on == 1, to ensure the clearer of the semaphore is the same owner who claimed it. However, I am not sure how this would affect other testbenches. For example, is it possible to split the write() task into separate threads when the driver returns responses separately (i.e., the uvm_reg_adapater.provides_responses == 1). Basically, is it possible to start a register transaction in one thread (taking the semaphore) and end it in another transaction?
Also, I see that there is a similar, though different solution than I'm suggesting, already posted (with an approved but not fixed mantis bug):
I also see that in my release of UVM-IEEE, the uvm_reg class has the exact same XatomicX locking mechanism implemented.
The only current solution I see that handles this outside of UVM and enables multi-threading access of the same register is with, yet another, semaphore that makes the internal uvm_reg semaphore all but moot. However, this is not desirable since I need to enforce a guideline on my team rather than have it built-in.
Am I missing something? Better approaches?
Thanks for your feedback!
Jeremy Ridgeway