Jump to content
Sign in to follow this  
Logger

How do I use RAL with a non-blocking interface?

Recommended Posts

Using uvm_reg_sequece the completion model of the interface is abstracted. How do I retain control of deciding whether the register access is executed in a blocking vs non-blocking manner? I want the ability to write a uvm_reg_sequence that can do both types of accesses under user control.

-Ryan

Share this post


Link to post
Share on other sites

i think the recommended path is to make an extension container object and add there all additional properties for your reg2bus translation process. pass the object in the extension parameter of your register access. you could for instance add a field there "is_blocking" and use that field in your register adapter to make the downstream bus access blocking/non-blocking.

Share this post


Link to post
Share on other sites

That seems like a reasonable idea, however there is a more basic problem. RAL does not seem to support non-blocking protocol at all.

Here is the snip from uvm_reg_map::do_bus_write() where it executes the uvm_sequence_item. Notice the last line.

==================================================

rw.parent.start_item(bus_req,rw.prior);

if (rw.parent != null && i == 0)

rw.parent.mid_do(rw);

rw.parent.finish_item(bus_req);

bus_req.end_event.wait_on(); //// <- This prevents non-blocking behavior.

==================================================

Even if the driver calls item_done() before the transaction has completed, thus enabling non-blocking behavior. RAL is then calling end_event.wait_on(), forcing blocking behavior again.

-Ryan

Share this post


Link to post
Share on other sites

What I'd like to write is something like this:

=========================================

virtual task body();

uvm_status_e src_status, mask_status;

logic [31:0] src_value, mask_value, src_sample;

uvm_reg r;

src_value = 'ha5;

write_reg(model.blk.int_src, src_status, src_value);

mask_value = 'hb4;

write_reg(model.blk.int_mask, mask_status, mask_value);

// somehow wait for the last write to complete?

// model.blk.int_mask.updated_event.wait_on();

read_reg(model.blk.int_src, src_status, src_sample);

endtask

=========================================

However, using your advice I am able to achieve the desired stimulus by writing this:

=========================================

virtual task body();

uvm_status_e src_status, mask_status;

logic [31:0] src_value, mask_value, src_sample;

uvm_reg r;

fork

begin

src_value = 'ha5;

write_reg(model.blk.int_src, src_status, src_value);

end

begin

mask_value = 'hb4;

write_reg(model.blk.int_mask, mask_status, mask_value);

end

join

read_reg(model.blk.int_src, src_status, src_sample);

endtask

=========================================

It works, but unfortunately there is a race in that code. There's no gaurantee what order the forked threads will execute. Which means I have to further add more synchronization code to enforce the thread ordering. With a lot of registers to set, this becomes quite cumbersome. It would seem that RAL should provide blocking and non-blocking read and write functions. If non-blocking is used, RAL would fork a thread to get the response. The non-blocking read could return some kind of event object to wait on.

=========================================

virtual task body();

uvm_status_e src_status, mask_status;

logic [31:0] src_value, mask_value, src_sample;

uvm_reg r;

src_value = 'ha5;

write_reg_nb(model.blk.int_src, src_status, src_value);

mask_value = 'hb4;

write_reg(model.blk.int_mask, mask_status, mask_value);

read_cmpl_event = read_reg_nb(model.blk.int_src, src_status, src_value); // non_blocking so can be a function

// src_status, and src_value are invalid until after the following event fires

read_cmpl_event.wait_on();

// src_status and src_value are now valid.

endtask

=========================================

-Ryan

Share this post


Link to post
Share on other sites

I have created an extension to uvm_reg_sequence that hides the forking and thread ordering I was complaining about. However, am I being overly pessimistic about thread ordering? The LRM is explicit that

"At any time while evaluating a procedural statement, the simulator may suspend execution and place the partially completed event as a pending event in the event region. The effect of this is to allow the interleaving of process execution, although the order of interleaved execution is nondeterministic and not under control of the user."

But in practice, none of the simulators switch from one process to another willy-nilly. They typically don't switch unless the active process hits a blocking statement. The LRM really has too much flexibility here.

==========================================================

class bu_reg_sequence #(type BASE=uvm_sequence #(uvm_reg_item)) extends uvm_reg_sequence #(BASE);

local int next_thread_id = 0;

local mailbox#(int) mb = new;

local event mb_pop;

virtual function uvm_event read_reg_nb( input uvm_reg rg,

ref uvm_status_e status,

ref uvm_reg_data_t value,

input uvm_path_e path = UVM_DEFAULT_PATH,

input uvm_reg_map map = null,

input int prior = -1,

input uvm_object extension = null,

input string fname = "",

input int lineno = 0 );

int thread_id = next_thread_id++;

uvm_event done_event = new;

fork

begin

int selected_thread_id;

mb.put(thread_id);

mb.peek(selected_thread_id);

// gaurantee super.read_reg is called in the order read_reg_nb was called.

while (selected_thread_id != thread_id) begin

@(mb_pop);

mb.peek(selected_thread_id);

end

fork

super.read_reg(rg,status,value,path,map,prior,extension,fname,lineno);

begin

#0; // gaurantee call to super.read_reg happens before popping the thread id.

mb.get(selected_thread_id);

->mb_pop;

end

join

done_event.trigger();

end

join_none

return done_event;

endfunction // read_reg_nb

virtual task read_reg( input uvm_reg rg,

output uvm_status_e status,

output uvm_reg_data_t value,

input uvm_path_e path = UVM_DEFAULT_PATH,

input uvm_reg_map map = null,

input int prior = -1,

input uvm_object extension = null,

input string fname = "",

input int lineno = 0 );

// read_reg needs to be redefined to use the ordering mechanism provided by read_reg_nb

uvm_event done_event = read_reg_nb(rg,status,value,path,map,prior,extension,fname,lineno);

done_event.wait_on();

endtask // read_reg

virtual function uvm_event write_reg_nb( input uvm_reg rg,

ref uvm_status_e status,

input uvm_reg_data_t value,

input uvm_path_e path = UVM_DEFAULT_PATH,

input uvm_reg_map map = null,

input int prior = -1,

input uvm_object extension = null,

input string fname = "",

input int lineno = 0 );

int thread_id = next_thread_id++;

uvm_event done_event = new;

fork

begin

int selected_thread_id;

mb.put(thread_id);

mb.peek(selected_thread_id);

// gaurantee super.write_reg is called in the order write_reg_nb was called.

while (selected_thread_id != thread_id) begin

@(mb_pop);

mb.peek(selected_thread_id);

end

fork

super.write_reg(rg,status,value,path,map,prior,extension,fname,lineno);

begin

#0; // gaurantee call to super.read_reg happens before popping the thread id.

mb.get(selected_thread_id);

->mb_pop;

end

join

done_event.trigger();

end

join_none

return done_event;

endfunction // write_reg_nb

virtual task write_reg( input uvm_reg rg,

output uvm_status_e status,

input uvm_reg_data_t value,

input uvm_path_e path = UVM_DEFAULT_PATH,

input uvm_reg_map map = null,

input int prior = -1,

input uvm_object extension = null,

input string fname = "",

input int lineno = 0 );

// write_reg needs to be redefined to use the ordering mechanism provided by write_reg_nb

uvm_event done_event = write_reg_nb(rg,status,value,path,map,prior,extension,fname,lineno);

done_event.wait_on();

endtask // write_reg

endclass // bu_reg_sequence

===============================================================

That allows me to write the following in my sequence body.

===============================================================

// stack up two back to back AXI write transactions

src_value = 'ha5;

write_reg_nb(model.blk.int_src, src_status, src_value);

mask_value = 'hb4;

write_reg(model.blk.int_mask, mask_status, mask_value);

// last write was blocking, so the following code waits for that last write to complete.

// stack up two back to back AXI read transactions

read_reg_nb(model.blk.int_src, src_status, src_sample);

ev = read_reg_nb(model.blk.int_mask, mask_status, mask_sample);

ev.wait_on();

===============================================================

Seems like a nice use model that could be built into uvm_reg_sequence.

-Ryan

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×