This post is a deep dive into UVM objections implementation. Objections are a UVM mechanism to control test shutdown (or stopping shutdown really). It basically keeps things running until all important things stop running (i.e., 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;
Note 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)