Logger Posted July 25, 2012 Report Posted July 25, 2012 I am planning on writing several uvm_reg_sequences to model firmware controlling my dut. I will have a top level virtual sequence which acts as a manager. It will start sequences, which will possibly in turn start other sub-sequences. Beings these are register sequences, it is highly likely that they have interrogated the design and learned something useful for my testbench. How should that get reported back to the manager? The problem is, I'm trying to create some vertically reusable register sequences. The block level team will define a Manager sequence, however it will not be vertically reusable. It will be specific to their testbench and not portable. I will develop my own Manager for the SoC level. Beings they will deliver their sequences to me, my manager class will know all about their sequences. But they will not know about my Manager class. I'd like to avoid crafting abstract classes to communicate, as it's unclear what the API should be at this stage. Illustration: Sequences: Manager(virtual), IrqHandler(parent), ProcessEventA(child), ProcessEventB(child) task Manager::body() fork begin IrqHandler irq_handler; wait_for_interrupt(); `uvm_do(irq_handler); // how do I determine which irq_handler sub-sequence ran, and get the information out? end fork // start some other sequences end join endtask task IrqHandler::body() ProcessEventA process_event_a; ProcessEventB process_event_b; model.irq_cause.read(...,cause,...); case (cause) EVENT_A: `uvm_do(process_event_a) EVENT_B: `uvm_do(process_event_ endcase endtask task ProcessEventA::body() model.some_status.read(...,status,...); // how do I report status back up to the Manager? endtask My proposed solution is to change the Manager and IrqHandler like this: task Manager::body() fork begin IrqHandler irq_handler; wait_for_interrupt(); `uvm_do(irq_handler); // add this foreach block foreach (irq_handler.m_called_sequences[i]) begin ProcessEventA process_event_a; ProcessEventB process_event_b; if ($cast(process_event_a,m_called_sequences[i])) // poke around inside process_event_a to get resulting status else if ($cast(process_event_b,m_called_sequences[i])) // poke around inside process_event_b to get resulting status end end fork // start some other sequences end join endtask class IrqHandler extends uvm_reg_sequence; uvm_reg_sequence m_called_sequences[$]; // <- add this virtual task body(); ProcessEventA process_event_a; ProcessEventB process_event_b; model.irq_cause.read(...,cause,...); case (cause) EVENT_A: begin `uvm_do(process_event_a) m_called_sequences.push_back(process_event_a); // <- add this end EVENT_B: begin `uvm_do(process_event_ m_called_sequences.push_back(process_event_; // <- add this end endcase endtask endclass Two thoughts on this. First, the change to my Manager is functional, but ugly. Second, shouldn't tracking sub-sequence execution like this be built into uvm_sequence_base? -Ryan Quote
Logger Posted July 26, 2012 Author Report Posted July 26, 2012 I have made a polished solution. Rather than having the parent thread explicitly push things onto a queue, I've implemented the post_do callback. And rather than adding that to every class I'd like to do that for, I made a base class that does it. I have rather obnoxiously named this my uvm_plus_pkg. `ifndef UVM_PLUS_PKG `define UVM_PLUS_PKG package uvm_plus_pkg; import uvm_pkg::*; // parameterized utility functions class uvm_type #(type TGT=uvm_object); typedef TGT queue[$]; // $cast doesn't handle $cast(lhs_q,rhs_q), so this does static function queue_cast(ref TGT lhs[$], const ref uvm_object rhs[$]); TGT tmp; lhs.delete(); foreach (rhs[i]) begin $cast(tmp,rhs[i]); lhs.push_back(tmp); end endfunction // cast // takes rhs queue and filters for items that are extensions of TGT static function queue queue_filter(const ref uvm_object rhs[$]); TGT tmp; uvm_object filtered[$]; filtered = rhs.find(x) with ($cast(tmp,x)); queue_cast(queue_filter,filtered); endfunction endclass typedef uvm_sequence_item uvm_sequence_item_queue[$]; // To make your parent sequence record completed items or subsequences, extend this class. class uvm_recording_sequence #(type REQ=uvm_sequence_item, type RSP=REQ) extends uvm_sequence#(REQ,RSP); local uvm_sequence_item_queue m_completed_items; function new ( string name = "SubSeq" ); super.new(name); endfunction: new function uvm_sequence_item_queue get_completed_items(); get_completed_items = m_completed_items; endfunction:get_completed_items // if you overload this, be sure to call super.pre_start() virtual task pre_start(); m_completed_items.delete(); endtask // if you overload this, be sure to call super.post_do() virtual function void post_do(uvm_sequence_item this_item); m_completed_items.push_back(this_item); endfunction endclass: uvm_recording_sequence // That last class replaces regular uvm_sequences, this typedef replaces uvm_reg_sequences typedef uvm_reg_sequence#(uvm_recording_sequence#(uvm_reg_item)) uvm_recording_reg_sequence; endpackage: uvm_plus_pkg // Convenience macro, gets completed items from src and returns a type filtered queue. `define uvm_completed_item_filter(T,tgt,src) \ begin \ uvm_object _ci[$]; \ _ci = src.get_completed_items(); \ tgt = uvm_type#(T)::queue_filter(_ci); \ end `endif // `ifndef UVM_PLUS_PKG I can then use that with sequences that look like this: class RalRwr extends uvm_reg_sequence; `uvm_object_utils(RalRwr) uvm_reg_pkg::ral_block_slave model; virtual task body(); bit [31:0] rdata; bit [31:0] wdata; uvm_status_e uvm_status; uvm_config_db#(uvm_reg_pkg::ral_block_slave)::get(null, "uvm_ral", "model", model); model.AUX1.read(uvm_status, rdata, .parent(this)); model.AUX1.write(uvm_status, rdata+1, .parent(this)); model.AUX1.read(uvm_status, wdata, .parent(this)); endtask // body virtual function void post_do(uvm_sequence_item this_item); this_item.print(); endfunction endclass // RWR class RalRw extends uvm_reg_sequence; `uvm_object_utils(RalRw) uvm_reg_pkg::ral_block_slave model; virtual task body(); bit [31:0] rdata; bit [31:0] wdata; uvm_status_e uvm_status; uvm_config_db#(uvm_reg_pkg::ral_block_slave)::get(null, "uvm_ral", "model", model); model.AUX1.read(uvm_status, rdata, .parent(this)); model.AUX1.write(uvm_status, rdata+1, .parent(this)); endtask // body virtual function void post_do(uvm_sequence_item this_item); this_item.print(); endfunction endclass // RW class RalParentSeq extends uvm_recording_reg_sequence; `uvm_object_utils(RalParentSeq) virtual task body(); RalRw rw; RalRwr rwr; `uvm_do(rw) `uvm_do(rwr) rwr = null; `uvm_do(rwr) endtask // body endclass And the code starting the RalParentSeq can interrogate this like this: rp = RalParentSeq::type_id::create("rp",this); rp.start(env.apb_master_agent.sqr); `uvm_completed_item_filter(RalRwr,rsq,rp); foreach (rsq[i]) begin rsq[i].print(); end So all-in-all the ugliness is fairly well hidden. I'm still not sure if UVM already had something in mind for allowing grandparents to interrogate grandchild sequence items. -Ryan Quote
Logger Posted July 27, 2012 Author Report Posted July 27, 2012 And now for a different approach. Rather than recording items, I'm promoting them. Such that my post_do function calls the parent's post_do. // Rather than recording items, this sequence promotes the post_do to the parent. // That means the parent sequence will be notified of sub-sequence items completing. class uvm_promoting_sequence #(type REQ=uvm_sequence_item, type RSP=REQ) extends uvm_sequence#(REQ,RSP); function new ( string name = "uvm_promoting_sequence" ); super.new(name); endfunction: new // if you overload this, be sure to call super.post_do() virtual function void post_do(uvm_sequence_item this_item); if (m_parent_sequence != null) m_parent_sequence.post_do(this_item); endfunction endclass: uvm_promoting_sequence // That last class replaces regular uvm_sequences, this typedef replaces uvm_reg_sequences typedef uvm_reg_sequence#(uvm_promoting_sequence#(uvm_reg_item)) uvm_promoting_reg_sequence; Now I write some sequences that use this like so: class RalPromoParentSeq extends uvm_promoting_reg_sequence; `uvm_object_utils(RalPromoParentSeq) virtual task body(); RalRw rw; RalRwr rwr; `uvm_do(rw) `uvm_do(rwr) rwr = null; `uvm_do(rwr) endtask // body endclass class RalManager extends uvm_reg_sequence; `uvm_object_utils(RalManager) virtual task body(); RalPromoParentSeq pp; `uvm_do(pp) endtask // body virtual function void post_do(uvm_sequence_item this_item); RalPromoParentSeq tst; if (!$cast(tst,this_item)) begin uvm_default_printer.knobs.prefix = "RalManager subseq pp executed: "; this_item.print(); uvm_default_printer.knobs.prefix = ""; end endfunction endclass The RalManager's post_do will now be called at the completion of each sub-sequence of RalPromoParentSeq. This allows RalPromoParentSeq to reactive in a more timely manner. -Ryan Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.