This post is a deep dive into UVM objections implementation. Objections are UVM mechanism to control test shutdown(or stopping shutdown really).. It basically keeps things running until all important things stops running(ie objections are dropped).
raise_objection
and drop_objection
are used at the start and end of run_phase as follows
55 class test extends test_base;
56 `uvm_component_utils(test)
57
61
62 task run_phase(uvm_phase phase);
65
66 phase.raise_objection(this);
...
...
87 phase.drop_objection(this);
88 endtask
RANT: Before I started this, It seemed simple enough but it turned out to be more complicated than I thought. Actually, this is my third time trying to write this.
raise_objection and drop_objection Link to heading
raise_objections
is called from phase
. So, Jumping to raise_objection
defined in uvm_phase.svh
.
function void uvm_phase::raise_objection (uvm_object obj,
string description="",
int count=1);
phase_done.raise_objection(obj,description,count);
endfunction
phase_done
variable is of type uvm_objection
uvm_objection phase_done; // phase done objection
Jumping to uvm_objection.svh
, raise_objection
calls m_raise
virtual function void raise_objection (uvm_object obj=null,
string description="",
int count=1);
if(obj == null)
obj = m_top;
m_cleared = 0;
m_top_all_dropped = 0;
m_raise (obj, obj, description, count);
endfunction
m_raise
does a lot of things but mostly it does the bookkeeping for objections. In the snippet below, it increments the m_total_count
and m_source_count
398 function void m_raise (uvm_object obj,
399 uvm_object source_obj,
400 string description="",
401 int count=1);
402 int idx;
403 uvm_objection_context_object ctxt;
404
405 if (m_total_count.exists(obj))
406 m_total_count[obj] += count;
407 else
408 m_total_count[obj] = count;
409
410 if (source_obj==obj) begin
411 if (m_source_count.exists(obj))
412 m_source_count[obj] += count;
413 else
414 m_source_count[obj] = count;
415 end
Next, it calls raised
which calls comp.raised
on that component and triggers m_events
raised(obj, source_obj, description, count);
800 virtual function void raised (uvm_object obj,
801 uvm_object source_obj,
802 string description,
803 int count);
804 uvm_component comp;
805 if ($cast(comp,obj))
806 comp.raised(this, source_obj, description, count);
807 if (m_events.exists(obj))
808 ->m_events[obj].raised;
809 endfunction
Enough about raise_objection
, Let’s look at drop_objection
, It calls m_drop
which backtracks everything m_raise
does.
587 function void m_drop (uvm_object obj,
588 uvm_object source_obj,
589 string description="",
590 int count=1,
591 int in_top_thread=0);
592
600
601 if (obj == source_obj) begin
602 if (!m_source_count.exists(obj) || (count > m_source_count[obj])) begin
603 if(m_cleared)
604 return;
605 uvm_report_fatal("OBJTN_ZERO", {"Object \"", obj.get_full_name(),
606 "\" attempted to drop objection '",this.get_name(),"' count below zero"});
607 return;
608 end
609 m_source_count[obj] -= count;
610 end
611
612 m_total_count[obj] -= count;
613
The important part is updating m_scheduled_list
. I will circle back to this.
m_scheduled_list.push_back(ctxt);
Test Termination Link to heading
So far, I don’t know how the objection stops test termination. Naturally, I look into uvm_phase.svh
to understand the phase execution.
uvm_phase::execute_phase
forks several threads, One of those threads is WAIT_FOR_ALL_DROPPED
. basically, it checks m_top_all_dropped
and goes into wait_for
waiting for UVM_ALL_DROPPED
1197 if (!phase_done.m_top_all_dropped)
1198 phase_done.wait_for(UVM_ALL_DROPPED, top);
wait_for
is waiting for m_events to get all_dropped
880 m_events[obj].waiters++;
881 case (objt_event)
882 UVM_RAISED: @(m_events[obj].raised);
883 UVM_DROPPED: @(m_events[obj].dropped);
884 UVM_ALL_DROPPED: @(m_events[obj].all_dropped);
885 endcase
886
all_dropped
is the task that sends m_events[obj].all_dropped
trigger
829 // Function: all_dropped
830 //
831 // Objection callback that is called when a <drop_objection> has reached ~obj~,
832 // and the total count for ~obj~ goes to zero. This callback is executed
833 // after the drain time associated with ~obj~. The default implementation
834 // calls <uvm_component::all_dropped>.
836 virtual task all_dropped (uvm_object obj,
837 uvm_object source_obj,
838 string description,
839 int count);
840 uvm_component comp;
841 if($cast(comp,obj))
842 comp.all_dropped(this, source_obj, description, count);
843 if (m_events.exists(obj))
844 ->m_events[obj].all_dropped;
845 if (obj == m_top)
846 m_top_all_dropped = 1;
847 endtask
What calls all_dropped
? I will trace from the end here and work my way backward.
all_dropped
is called from m_forked_drain
all_dropped(obj,source_obj,description, count)
and m_forked_drain
is called from m_execute_scheduled_forks
697 `endif
698 // Execute the forked drain
699 objection.m_forked_drain(ctxt.obj, ctxt.source_obj, ctxt.description, ctxt.count, 1);
m_execute_scheduled_forks
waits for m_scheduled_list to have something.
662 // background process; when non
663 static task m_execute_scheduled_forks();
664 while(1) begin
665 wait(m_scheduled_list.size() != 0);
666 if(m_scheduled_list.size() != 0) begin
667 uvm_objection_context_object c;
668 uvm_objection o;
Notes that m_execute_scheduled_forks
is called from m_init_objections
which is called from
764 static function void m_init_objections();
765 fork
766 uvm_objection::m_execute_scheduled_forks();
767 join_none
768 endfunction
319 task uvm_root::run_test(string test_name="");
320
332 // Set up the process that decouples the thread that drops objections from
333 // the process that processes drop/all_dropped objections. Thus, if the
334 // original calling thread (the "dropper") gets killed, it does not affect
335 // drain-time and propagation of the drop up the hierarchy.
336 // Needs to be done in run_test since it needs to be in an
337 // initial block to fork a process.
338 uvm_objection::m_init_objections();
Back to m_drop
m m_scheduled_list is updated with ctxt
which activates the logic in m_execute_scheduled_forks
m_scheduled_list.push_back(ctxt)