文章目录
- 1. uvm_phase
- 2. uvm_phase的运行和调度
- 2.1. get_common_domain()
- 2.1.1. phase_type & phase_state
- 2.1.2. uvm_phase class
- 2.1.2.1. new()
- 2.1.2.1. uvm_common_phases
- 2.1.3. uvm_domain class
- 2.1.4. get_uvm_domain()
- 2.2. add()
- 2.3. execute_phase()
- 3. objection机制
- 3.1. uvm_objection class
- 3.2. raise_objection
- 3.3. drop_objection
- 3.4. drain_time
- 4. phase的高级应用
- 4.1. jump()
- 4.2. 创建新的domain
- 4.2.1. set_domain()
- 4.2.2. sync()
1. uvm_phase
UVM的phase机制秉承在不同时间做不同事情的设计哲学,主要提供了function和task两种类型的phase,在function phase中主要做一些实例化,各组件的连接以及仿真结束后的统计、report等工作,这些事情是不需要耗费仿真时间的;主要的仿真过程是在task phase即run_phase中运行的,与run_phase同步运行的还有12个run-time task phase, 这些细分的phase可以实现对仿真行为更加精细的控制,比如在reset_phase对DUT做reset,configure_phase对DUT做一些配置, 在main_phase中灌输激励等等,各个function phase是严格按照下图中从上到下的顺序来执行的(图中蓝色为function phase,绿色为task phase):
图1.1 uvm_phase graph
2. uvm_phase的运行和调度
我们知道,UVM通过phase的调度来进行不同阶段的仿真,那当我们跑一个testcase的时候,验证平台是怎么开始仿真的呢?我们一般会在平台的顶层module TB里边来调用run_test()开始仿真:
//uvm_root.svh
task uvm_root::run_test(string test_name="");...process phase_runner_proc;...if (test_name != "") beginuvm_coreservice_t cs = uvm_coreservice_t::get(); uvm_factory factory=cs.get_factory();if(m_children.exists("uvm_test_top")) beginuvm_report_fatal("TTINST","An uvm_test_top already exists via a previous call to run_test", UVM_NONE);#0; // forces shutdown because $finish is forkedend$cast(uvm_test_top, factory.create_component_by_name(test_name,"", "uvm_test_top", null));...fork begin// spawn the phase runner taskphase_runner_proc = process::self();uvm_phase::m_run_phases();endjoin_none#0; // let the phase runner startwait (m_phase_all_done == 1);// clean up after ourselvesphase_runner_proc.kill();...if (finish_on_completion)$finish;
endtask
在这个函数中UVM会通过传入的命令行参数+UVM_TESTNAME=xxx来获取testcase的名字,然后创建一个uvm_test_top的component节点,这个uvm_test_top句柄指向当前仿真testcase class的实例,随后会在其中例化env及整个平台hirearchy。此外,SV中有process进程的用法,uvm会用fork…join_none来启动一个进程phase_runner_proc,在其中调用m_run_phases(),在这个task中完成进行对phase的部分调度(之所以说是部分调度,在于其实还有一个background process在同步运行,下文会提到),同时uvm一直等待所有phase全部完成的信号m_phase_all_done,一旦该信号被拉高则杀掉phase_runner_proc进程,调用$finish结束本次仿真。
//uvm_phase.svh
class uvm_phase extends uvm_object;local static mailbox #(uvm_phase) m_phase_hopper = new();extern static task m_run_phases();...
endclasstask uvm_phase::m_run_phases();uvm_root top;uvm_coreservice_t cs;cs = uvm_coreservice_t::get();top = cs.get_root();// initiate by starting first phase in common domainbeginuvm_phase ph = uvm_domain::get_common_domain();void'(m_phase_hopper.try_put(ph));endforever beginuvm_phase phase;m_phase_hopper.get(phase);forkbeginphase.execute_phase();endjoin_none#0; // let the process start runningend
endtask
m_run_phases()是定义在uvm_phase类的静态task,其调用get_common_domain()来获得一个uvm_phase类型的句柄,然后将其放入邮箱m_phase_hopper中,这个邮箱是static类型的,存放的对象是uvm_phase类型,UVM平台中所有的phase实例对象会共用这一个邮箱。随后在forever循环中该邮箱会不停地被检查,一旦有新的phase放入,则马上取出来用fork…join_none启动新线程调用该phase的execute_phase()。
2.1. get_common_domain()
这个函数做的事情就是把所有的function phase和task phase按照先后顺序排排队,安排好先后顺序,按照图1.1中所示的那样编成一个domain,取个名字叫做"common",作为uvm default的domain。这个函数中先检查m_common_domain是否为null,我们可以大致猜到这是一个common_domain的实例句柄,很明显这里是采用了单例模式,若系统中已经存在这个实例,则直接返回该实例句柄。若是第一次调用该函数,m_common_domain为null,则调用new()函数创建一个句柄为domain,名字为"common"的实例,然后调用add()函数将8个function phase和run_phase按顺序加入,add()函数做的其实就是给这些phase排排队加入名为"common_domain"的phase运行图,我们后面会详细来看add()函数的实现。之后把该domain句柄分别存入以"common"为索引的数组m_domains和赋给m_common_domain。get_uvm_domain()实际上就是获取12个run-time task phase的运行图,最后会再次调用add()将其加入m_common_domain的运行图中,with_phase参数传入的是之前run_phase的句柄,表明12个run-time phase是以跟run_phase并行运行的方式加入到m_common_domain中。
static function uvm_domain get_common_domain();uvm_domain domain;uvm_phase schedule;if (m_common_domain != null)return m_common_domain;domain = new("common");domain.add(uvm_build_phase::get());domain.add(uvm_connect_phase::get());domain.add(uvm_end_of_elaboration_phase::get());domain.add(uvm_start_of_simulation_phase::get());domain.add(uvm_run_phase::get());domain.add(uvm_extract_phase::get());domain.add(uvm_check_phase::get());domain.add(uvm_report_phase::get());domain.add(uvm_final_phase::get());m_domains["common"] = domain;...m_common_domain = domain;domain = get_uvm_domain();m_common_domain.add(domain,.with_phase(m_common_domain.find(uvm_run_phase::get())));return m_common_domain;endfunction
2.1.1. phase_type & phase_state
为了更方便的对phase进行调度,UVM定义了不同的phase_type,如UVM_PHASE_NODE代表所有的(无论function/task phase)phase的一个实例(节点);UVM_PHASE_DOMAIN代表在其中定义了不同phase线性/并行运行关系的一组phase的运行图(类似于从上海发往北京的高铁调度时刻表,phase就像是运行在其间的一列列班车,哪个时间段该发行哪班车,都会清清楚楚记录在时刻表里,当然高铁不可能每一个时刻都会发出一班车,不像phase之间是无缝衔接的),其中可以包含许多的UVM_PHASE_NODE;而UVM_PHASE_SCHEDULE表示一个微缩版的UVM_PHASE_DOMAIN,它其中只定义了12个run-time task phase的运行图,等等。
//uvm_object_globals.svh
typedef enum { UVM_PHASE_IMP,UVM_PHASE_NODE,UVM_PHASE_TERMINAL,UVM_PHASE_SCHEDULE,UVM_PHASE_DOMAIN,UVM_PHASE_GLOBAL
} uvm_phase_type;
此外UVM还定义了phase_state,用来表示某phase当前的不同运行状态,如UVM_PHASE_SYNCING表示不同domain的run-time task phase正在进行同步,UVM_PHASE_EXECUTING表示当前phase正在执行,等等。
//uvm_object_globals.svhtypedef enum { UVM_PHASE_UNINITIALIZED = 0,UVM_PHASE_DORMANT = 1,UVM_PHASE_SCHEDULED = 2,UVM_PHASE_SYNCING = 4,UVM_PHASE_STARTED = 8,UVM_PHASE_EXECUTING = 16,UVM_PHASE_READY_TO_END = 32,UVM_PHASE_ENDED = 64,UVM_PHASE_CLEANUP = 128,UVM_PHASE_DONE = 256,UVM_PHASE_JUMPING = 512} uvm_phase_state;
2.1.2. uvm_phase class
2.1.2.1. new()
uvm_phase类继承自uvm_object,在其中定义了一些变量如m_phase_type和m_state, 分别是uvm_phase_type和uvm_phase_state类型的,uvm_phase类型的句柄m_parent指向的是该phase的上一个层级,其中还定义了uvm_phase类型的m_end_node,这个变量是用来表示一个uvm_domain和uvm_schedule的结束节点。此外,两个bit类型的联合数组以uvm_phase类型为索引,用来记录各个phase间的顺序关系,其中m_predecessors[]用来记录当前phase的所有前置phase,比如若当前phase是一个connect_phase,则build_phase在其之前运行,是它的一个前置phase,则connect_phase的实例中m_predecessors[build_phase] = 1;同理,m_successors[]用于记录当前phase的所有后继phase,对于一个build_phase实例来说,其m_successors[connect_phase] = 1;
//uvm_phase.svh
class uvm_phase extends uvm_object;protected uvm_phase_type m_phase_type;protected uvm_phase m_parent; // our 'schedule' node [or points 'up' one level]uvm_phase m_imp; // phase imp to call when we execute this nodelocal uvm_phase_state m_state;...protected bit m_predecessors[uvm_phase];protected bit m_successors[uvm_phase];protected uvm_phase m_end_node;...
endclassfunction uvm_phase::new(string name="uvm_phase",uvm_phase_type phase_type=UVM_PHASE_SCHEDULE,uvm_phase parent=null);super.new(name);m_phase_type = phase_type;// The common domain is the only thing that initializes m_state. All// other states are initialized by being 'added' to a schedule.if ((name == "common") &&(phase_type == UVM_PHASE_DOMAIN))m_state = UVM_PHASE_DORMANT;m_parent = parent;...if (parent == null && (phase_type == UVM_PHASE_SCHEDULE ||phase_type == UVM_PHASE_DOMAIN )) begin//m_parent = this;m_end_node = new({name,"_end"}, UVM_PHASE_TERMINAL, this);this.m_successors[m_end_node] = 1;m_end_node.m_predecessors[this] = 1;end
endfunction
来看uvm_phase类的new()函数,它有三个参数,在创建一个uvm_phase类型的实例时分别传入名字,phase_type和和parent,它分别把传入的phase_type和parent赋给m_phase_type和m_parent,若传入的phase_type是UVM_PHASE_SCHEDULE或UVM_PHASE_DOMAIN,表明当前正在创建一个多phase的运行图(domain/schedule),则调用new()创建一个m_end_node的实例对象,这个m_end_node的phase_type是UVM_PHASE_TERMINAL类型,parent就是这个运行图,然后把m_end_node作为这个运行图的后继phase记录下来,这段代码的意图就是一旦我们想创建一个新的domain/schedule对象,就需要先给它安排一个UVM_PHASE_TERMINAL类型的节点,无论运行图内部怎么调度这些phase,最终运行完毕总是要结束的。
2.1.2.1. uvm_common_phases
UVM的build_phase类和final_phase类继承自uvm_topdown_phase类,之所以叫"topdown"是因为在UVM平台hireachy各个component节点实例化的时候是自上而下执行的,与之相反其余的function phase类型都继承自uvm_bottomup_phase类,比如connect_phase的执行在UVM平台hireachy各个component节点间是自下而上执行。此外run_phase和12个run-time phase类都继承自uvm_task_phase类。这几种类型new()函数中phase_type参数都会传入UVM_PHASE_IMP来创建一个phase实例。其中的关键是traverse()函数,其中会根据当前phase执行的状态来调用当前component的不同函数,其中UVM预先定义了许多callback如phase_started()/phase_ended(),用户可以在component中来扩展这些callback,这些callback会在所有的phase(包括所有function和task phase)执行前后进行调用。我们注意到,在当前component的traverse()执行到最后的时候,会检查它所有的子节点并调用traverse()来以此执行此phase的内容,所以我们说继承自uvm_bottomup_phase的phase如build_phase的执行是自上而下的。与之相反,uvm_bottomup_phase类的traverse()函数是先在其所有子节点中调用traverse()执行,最后执行当前component,因而继承自uvm_bottomup_phase的phase如connect_phase的执行是自下而上的。uvm_task_phase的执行顺序与uvm_bottomup_phase类似,也是自下而上发起,但却是同时执行的,并不会等待子节点phase执行完毕才会执行上一级节点。此外,function phase在UVM_PHASE_EXECUTING状态,会调用execute()函数,这个函数会启动一个新进程并调用exec_func()。
//uvm_topdown_phase.svh
virtual class uvm_topdown_phase extends uvm_phase;// Function: new//// Create a new instance of a top-down phase//function new(string name);super.new(name,UVM_PHASE_IMP);endfunctionvirtual function void traverse(uvm_component comp,uvm_phase phase,uvm_phase_state state);string name;uvm_domain phase_domain = phase.get_domain();uvm_domain comp_domain = comp.get_domain();if (m_phase_trace)`uvm_info("PH_TRACE",$sformatf("topdown-phase phase=%s state=%s comp=%s comp.domain=%s phase.domain=%s",phase.get_name(), state.name(), comp.get_full_name(),comp_domain.get_name(),phase_domain.get_name()),UVM_DEBUG)if (phase_domain == uvm_domain::get_common_domain() ||phase_domain == comp_domain) begincase (state)UVM_PHASE_STARTED: begincomp.m_current_phase = phase;comp.m_apply_verbosity_settings(phase);comp.phase_started(phase);endUVM_PHASE_EXECUTING: beginif (!(phase.get_name() == "build" && comp.m_build_done)) beginuvm_phase ph = this; comp.m_phasing_active++;if (comp.m_phase_imps.exists(this))ph = comp.m_phase_imps[this];ph.execute(comp, phase);comp.m_phasing_active--;endendUVM_PHASE_READY_TO_END: begincomp.phase_ready_to_end(phase);endUVM_PHASE_ENDED: begincomp.phase_ended(phase);comp.m_current_phase = null;enddefault:`uvm_fatal("PH_BADEXEC","topdown phase traverse internal error")endcaseendif(comp.get_first_child(name))dotraverse(comp.get_child(name), phase, state);while(comp.get_next_child(name));endfunction// Function: execute//// Executes the top-down phase ~phase~ for the component ~comp~. //virtual function void execute(uvm_component comp,uvm_phase phase);// reseed this process for random stabilityprocess proc = process::self();proc.srandom(uvm_create_random_seed(phase.get_type_name(), comp.get_full_name()));comp.m_current_phase = phase;exec_func(comp,phase);endfunction
endclass //uvm_common_phases.svh
class uvm_build_phase extends uvm_topdown_phase;virtual function void exec_func(uvm_component comp, uvm_phase phase);comp.build_phase(phase); endfunctionlocal static uvm_build_phase m_inst;static const string type_name = "uvm_build_phase";// Function: get// Returns the singleton phase handle//static function uvm_build_phase get();if(m_inst == null)m_inst = new();return m_inst; endfunctionprotected function new(string name="build");super.new(name); endfunctionvirtual function string get_type_name();return type_name;endfunction
endclass
以uvm_build_phase为例,其实exec_func()就是调用了我们定义在某component的build_phase来执行。其中定义了uvm_build_phase类型的静态变量m_inst,代表这个phase的一个实例,当调用静态函数get()时会返回该唯一实例对象句柄。而task phase在UVM_PHASE_EXECUTING状态,也会调用execute()函数,这个函数会用fork…join_none启动一个新进程并调用exec_task(),在exec_task()返回前用m_num_procs_not_yet_returned来记录当前有多少个component在运行这个phase,以判断何时可以结束该phase。与function phase类似,exec_task()也是定义在task phase中调用当前component的同名task phase执行,get()函数也会返回该phase唯一实例对象句柄m_inst。
//uvm_task_phase.svh
virtual class uvm_task_phase extends uvm_phase...virtual function void execute(uvm_component comp,uvm_phase phase);forkbeginprocess proc;// reseed this process for random stabilityproc = process::self();proc.srandom(uvm_create_random_seed(phase.get_type_name(), comp.get_full_name()));phase.m_num_procs_not_yet_returned++;exec_task(comp,phase);phase.m_num_procs_not_yet_returned--;endjoin_noneendfunction
endclass
2.1.3. uvm_domain class
uvm_domain类继承自uvm_phase,其中定义了以字符串为索引的内容为uvm_domain类型的静态联合数组m_domains[],在uvm_domain的new()函数中首先调用super即uvm_phase类型的new()函数创建一个phase_type为UVM_PHASE_DOMAIN的实例,然后把传入的名字作为索引,将该实例对象存入m_domains[]。
//uvm_domain.svh
class uvm_domain extends uvm_phase;static local uvm_domain m_common_domain;static local uvm_domain m_domains[string];...function new(string name);super.new(name,UVM_PHASE_DOMAIN);if (m_domains.exists(name))`uvm_error("UNIQDOMNAM", $sformatf("Domain created with non-unique name '%s'", name))m_domains[name] = this;endfunction...
endclass
2.1.4. get_uvm_domain()
这个函数就是将12个run-time phases的运行顺序排好然后返回一个uvm_domain类型的运行图句柄m_uvm_domain。
//uvm_domain.svh
class uvm_domain extends uvm_phase;static local uvm_domain m_uvm_domain; // run-time static local uvm_phase m_uvm_schedule;...static function void add_uvm_phases(uvm_phase schedule);schedule.add(uvm_pre_reset_phase::get());schedule.add(uvm_reset_phase::get());schedule.add(uvm_post_reset_phase::get());schedule.add(uvm_pre_configure_phase::get());schedule.add(uvm_configure_phase::get());schedule.add(uvm_post_configure_phase::get());schedule.add(uvm_pre_main_phase::get());schedule.add(uvm_main_phase::get());schedule.add(uvm_post_main_phase::get());schedule.add(uvm_pre_shutdown_phase::get());schedule.add(uvm_shutdown_phase::get());schedule.add(uvm_post_shutdown_phase::get());endfunction// Function: get_uvm_domain//// Get a handle to the singleton ~uvm~ domain//static function uvm_domain get_uvm_domain();if (m_uvm_domain == null) beginm_uvm_domain = new("uvm");m_uvm_schedule = new("uvm_sched", UVM_PHASE_SCHEDULE);add_uvm_phases(m_uvm_schedule);m_uvm_domain.add(m_uvm_schedule);endreturn m_uvm_domain;endfunction
endclass
静态函数get_uvm_domain()首先分别创建了名为"uvm"和"uvm_sched"的对象并分别赋值给uvm_domain和uvm_phase类型的句柄m_uvm_domain和m_uvm_schedule,二者phase_type分别为UVM_PHASE_COMMON和UVM_PHASE_SCHEDULE,然后调用add_uvm_phases()函数,将12个run-time phases通过add()函数按先后顺序加入m_uvm_schedule中,此时m_uvm_schedule就是一个运行图,最后再将这个运行图schedule加入m_uvm_domain这个domain中,最后回到get_common_domain()中,把m_uvm_domain加入到m_common_domain中,同时设置with_phase为run_phase,就是把m_uvm_domain中的这12个run-time phase和run_phase同步运行。
2.2. add()
我们在之前调用add()函数时无外乎是四种情况,分别为
1, domain.add(uvm_build_phase::get()); //向uvm_common_domain中添加funtion phase和run_phase
2, schedule.add(uvm_pre_reset_phase::get()); //向m_uvm_schedule中添加12个run-time phases
3, m_uvm_domain.add(m_uvm_schedule); //将m_uvm_schedule加入m_uvm_domain
4, m_common_domain.add(domain,
.with_phase(m_common_domain.find(uvm_run_phase::get()))); //将包含12个run-time phases 的m_uvm_domain加入uvm_common_domain并使它们跟run_phase同步运行
来看add()函数,我们隐去了其中关于debug的部分:
function void uvm_phase::add(uvm_phase phase,uvm_phase with_phase=null,uvm_phase after_phase=null,uvm_phase before_phase=null);uvm_phase new_node, begin_node, end_node, tmp_node;uvm_phase_state_change state_chg;if (phase == null)`uvm_fatal("PH/NULL", "add: phase argument is null")if (with_phase != null && with_phase.get_phase_type() == UVM_PHASE_IMP) beginstring nm = with_phase.get_name();with_phase = find(with_phase);if (with_phase == null)`uvm_fatal("PH_BAD_ADD",{"cannot find with_phase '",nm,"' within node '",get_name(),"'"})endif (before_phase != null && before_phase.get_phase_type() == UVM_PHASE_IMP) beginstring nm = before_phase.get_name();before_phase = find(before_phase);if (before_phase == null)`uvm_fatal("PH_BAD_ADD",{"cannot find before_phase '",nm,"' within node '",get_name(),"'"})endif (after_phase != null && after_phase.get_phase_type() == UVM_PHASE_IMP) beginstring nm = after_phase.get_name();after_phase = find(after_phase);if (after_phase == null)`uvm_fatal("PH_BAD_ADD",{"cannot find after_phase '",nm,"' within node '",get_name(),"'"})endif (with_phase != null && (after_phase != null || before_phase != null))`uvm_fatal("PH_BAD_ADD","cannot specify both 'with' and 'before/after' phase relationships")if (before_phase == this || after_phase == m_end_node || with_phase == m_end_node)`uvm_fatal("PH_BAD_ADD","cannot add before begin node, after end node, or with end nodes")// If we are inserting a new "leaf node"if (phase.get_phase_type() == UVM_PHASE_IMP) beginuvm_task_phase tp;new_node = new(phase.get_name(),UVM_PHASE_NODE,this);new_node.m_imp = phase;begin_node = new_node;end_node = new_node;// The phase_done objection is only required// for task-based nodesif ($cast(tp, phase)) beginif (new_node.get_name() == "run") beginnew_node.phase_done = uvm_test_done_objection::get();endelse beginnew_node.phase_done = uvm_objection::type_id::create({phase.get_name(), "_objection"});endendend// We are inserting an existing scheduleelse beginbegin_node = phase;end_node = phase.m_end_node;phase.m_parent = this;end// If 'with_phase' is us, then insert node in parallel/*if (with_phase == this) beginafter_phase = this;before_phase = m_end_node;end*/// If no before/after/with specified, insert at end of this scheduleif (with_phase == null && after_phase == null && before_phase == null) beginbefore_phase = m_end_node;end// INSERT IN PARALLEL WITH 'WITH' PHASEif (with_phase != null) beginbegin_node.m_predecessors = with_phase.m_predecessors;end_node.m_successors = with_phase.m_successors;foreach (with_phase.m_predecessors[pred])pred.m_successors[begin_node] = 1;foreach (with_phase.m_successors[succ])succ.m_predecessors[end_node] = 1;end// INSERT BEFORE PHASEelse if (before_phase != null && after_phase == null) beginbegin_node.m_predecessors = before_phase.m_predecessors;end_node.m_successors[before_phase] = 1;foreach (before_phase.m_predecessors[pred]) beginpred.m_successors.delete(before_phase);pred.m_successors[begin_node] = 1;endbefore_phase.m_predecessors.delete();before_phase.m_predecessors[end_node] = 1;end// INSERT AFTER PHASEelse if (before_phase == null && after_phase != null) beginend_node.m_successors = after_phase.m_successors;begin_node.m_predecessors[after_phase] = 1;foreach (after_phase.m_successors[succ]) beginsucc.m_predecessors.delete(after_phase);succ.m_predecessors[end_node] = 1;endafter_phase.m_successors.delete();after_phase.m_successors[begin_node] = 1;end// IN BETWEEN 'BEFORE' and 'AFTER' PHASESelse if (before_phase != null && after_phase != null) beginif (!after_phase.is_before(before_phase)) begin`uvm_fatal("PH_ADD_PHASE",{"Phase '",before_phase.get_name(),"' is not before phase '",after_phase.get_name(),"'"})end// before and after? add 1 pred and 1 succbegin_node.m_predecessors[after_phase] = 1;end_node.m_successors[before_phase] = 1;after_phase.m_successors[begin_node] = 1;before_phase.m_predecessors[end_node] = 1;if (after_phase.m_successors.exists(before_phase)) beginafter_phase.m_successors.delete(before_phase);before_phase.m_predecessors.delete(after_phase);endend // if (before_phase != null && after_phase != null)// Transition nodes to DORMANT stateif (new_node == null)tmp_node = phase;elsetmp_node = new_node;state_chg = uvm_phase_state_change::type_id::create(tmp_node.get_name());state_chg.m_phase = tmp_node;state_chg.m_jump_to = null;state_chg.m_prev_state = tmp_node.m_state;tmp_node.m_state = UVM_PHASE_DORMANT;`uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(tmp_node, state_chg))
endfunction
对于第一种调用,函数首先会判断参数中phase是否为null,然后依次判断各个参数with_phase/after_phase/before_phase是否为null,我们可以略过这些语句。接下来判断传入的phase是否为UVM_PHASE_IMP类型,很明显uvm_build_phase::get()返回值符合这个条件,于是创建一个UVM_PHASE_NODE为phase_type的uvm_phase类型的新实例对象new_node,这个new_node的唯一对象句柄m_imp就指向当前传入的phase_type为UVM_PHASE_IMP的对象,这个phase_type为UVM_PHASE_NODE的实例句柄会替代参数传入的phase_type为UVM_PHASE_IMP的对象存入phase的运行图。接下来把new_node分别赋值给begin_node和end_node。接下来判断当前phase是否是一个task phase类型,若是,则在其中实例化uvm_objection的对象,这里暂且按下不表。接下来判断若with_phase/after_phase/before_phase均为null,则before_phase指向m_end_node,这个m_end_node就是当前调用add()函数的phase_type为UVM_PHASE_DOMAIN或者UVM_PHASE_SCHEDULE的结束节点。这里before_phase指向某个运行图(domain/schedule)的结束节点,意图很明显,就是要把当前phase加入到这个运行图的m_end_node节点之前,作为运行图的最后一个phase。后面的代码判断若before_phase不为null,则
1, 把当前运行图的m_end_node.m_predecessors[]作为新加入phase的m_predecessors[];
2, 把m_end_node作为新加入phase的m_successors,即phase.m_successors[m_end_node]=1;
3, 对于m_end_node的所有m_predecessors,删除m_end_node作为他们的m_successors的记录,并把新加入的phase作为它们的m_successors;
4, 把m_end_node的所有m_predecessors记录删除,并把新加入的phase作为其唯一的m_predecessors。
对于第一种情况,若调用domain.add(uvm_build_phase::get());之后,则运行图如下:
若依次调用add()添加所有function phase和run_phase到uvm_common_domain之后,所形成的运行图如如下:
第二种调用情况类似,是把12个run-time task phases加入到m_uvm_schedule中形成顺序运行图,如下:
接下来看第三种调用add()的方式:m_uvm_domain.add(m_uvm_schedule);将m_uvm_schedule加入到m_uvm_domain的运行图中,由于传入的phase参数不是UVM_PHASE_IMP类型,所以函数会直接将before_phase指向m_uvm_domain的m_end_node,将m_uvm_schedule放在m_uvm_domain::m_end_node之前:
第四种调用方式给参数with_phase传入了find()返回值,这个find()函数是定义在uvm_phase类中的函数,它会调用m_find_predecessor()和m_find_successor()函数遍历当前domain中的所有前置和后置phase,返回要查找phase的唯一实例句柄m_imp。当with_phase传入run_phase的实例句柄时,add()函数做了以下事情:
1, 把run_phase的所有m_predecessors作为m_uvm_domain的m_predecessors;
2, 把run_phase的所有m_successors作为m_uvm_domain::m_end_node的m_successors;
3, 对于run_phase所有的m_predecessors,把m_uvm_domain作为它们的m_successors;
4, 对于run_phase所有的m_successors,把m_uvm_domain::m_end_node作为它们的m_predecessors。
最终调用get_common_domain()所形成的m_common_domain的phase运行图如下:
2.3. execute_phase()
在task m_run_phases()中调用uvm_domain::get_common_domain()拿到UVM default domain m_common_domain的句柄ph后,会把ph放入邮箱m_phase_hopper,forever循环从邮箱中拿到ph后会启动fork…join_none线程调用execute_phase()。该task代码如下:
//uvm_phase.svh
task uvm_phase::execute_phase();uvm_task_phase task_phase;uvm_root top;uvm_phase_state_change state_chg;uvm_coreservice_t cs;cs = uvm_coreservice_t::get();top = cs.get_root();// If we got here by jumping forward, we must wait for// all its predecessor nodes to be marked DONE.// (the next conditional speeds this up)// Also, this helps us fast-forward through terminal (end) nodesforeach (m_predecessors[pred])wait (pred.m_state == UVM_PHASE_DONE);// If DONE (by, say, a forward jump), return immedif (m_state == UVM_PHASE_DONE)return;...// If we're a schedule or domain, then "fake" executionif (m_phase_type != UVM_PHASE_NODE) beginstate_chg.m_prev_state = m_state;m_state = UVM_PHASE_STARTED;`uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))#0;state_chg.m_prev_state = m_state;m_state = UVM_PHASE_EXECUTING;`uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))#0;endelse begin // PHASE NODE//---------// STARTED://---------state_chg.m_prev_state = m_state;m_state = UVM_PHASE_STARTED;`uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))m_imp.traverse(top,this,UVM_PHASE_STARTED);m_ready_to_end_count = 0 ; // reset the ready_to_end count when phase starts#0; // LET ANY WAITERS WAKE UP//if (m_imp.get_phase_type() != UVM_PHASE_TASK) beginif (!$cast(task_phase,m_imp)) begin//-----------// EXECUTING: (function phases)//-----------state_chg.m_prev_state = m_state;m_state = UVM_PHASE_EXECUTING;`uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))#0; // LET ANY WAITERS WAKE UPm_imp.traverse(top,this,UVM_PHASE_EXECUTING);endelse beginm_executing_phases[this] = 1;state_chg.m_prev_state = m_state;m_state = UVM_PHASE_EXECUTING;`uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))fork : master_phase_processbeginm_phase_proc = process::self();//-----------// EXECUTING: (task phases)//-----------task_phase.traverse(top,this,UVM_PHASE_EXECUTING);wait(0); // stay alive for later killendjoin_noneuvm_wait_for_nba_region(); //Give sequences, etc. a chance to object// Now wait for one of three criterion for end-of-phase.forkbegin // guardfork// JUMPbeginwait (m_premature_end);`UVM_PH_TRACE("PH/TRC/EXE/JUMP","PHASE EXIT ON JUMP REQUEST",this,UVM_DEBUG)end// WAIT_FOR_ALL_DROPPEDbeginbit do_ready_to_end ; // bit used for ready_to_end iterations// OVM semantic: don't end until objection raised or stop requestif (phase_done.get_objection_total(top) ||m_use_ovm_run_semantic && m_imp.get_name() == "run") beginif (!phase_done.m_top_all_dropped)phase_done.wait_for(UVM_ALL_DROPPED, top);`UVM_PH_TRACE("PH/TRC/EXE/ALLDROP","PHASE EXIT ALL_DROPPED",this,UVM_DEBUG)endelse beginif (m_phase_trace) `UVM_PH_TRACE("PH/TRC/SKIP","No objections raised, skipping phase",this,UVM_LOW)endwait_for_self_and_siblings_to_drop() ;do_ready_to_end = 1;//--------------// READY_TO_END://--------------while (do_ready_to_end) beginuvm_wait_for_nba_region(); // Let all siblings see no objections before traverse might raise another `UVM_PH_TRACE("PH_READY_TO_END","PHASE READY TO END",this,UVM_DEBUG)m_ready_to_end_count++;if (m_phase_trace)`UVM_PH_TRACE("PH_READY_TO_END_CB","CALLING READY_TO_END CB",this,UVM_HIGH)state_chg.m_prev_state = m_state;m_state = UVM_PHASE_READY_TO_END;`uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))if (m_imp != null)m_imp.traverse(top,this,UVM_PHASE_READY_TO_END);uvm_wait_for_nba_region(); // Give traverse targets a chance to object wait_for_self_and_siblings_to_drop();do_ready_to_end = (m_state == UVM_PHASE_EXECUTING) && (m_ready_to_end_count < max_ready_to_end_iter) ; //when we don't wait in task above, we drop out of while loopendend// TIMEOUTbeginif (this.get_name() == "run") beginif (top.phase_timeout == 0)wait(top.phase_timeout != 0);if (m_phase_trace)`UVM_PH_TRACE("PH/TRC/TO_WAIT", $sformatf("STARTING PHASE TIMEOUT WATCHDOG (timeout == %t)", top.phase_timeout), this, UVM_HIGH)`uvm_delay(top.phase_timeout)if ($time == `UVM_DEFAULT_TIMEOUT) beginif (m_phase_trace)`UVM_PH_TRACE("PH/TRC/TIMEOUT", "PHASE TIMEOUT WATCHDOG EXPIRED", this, UVM_LOW)foreach (m_executing_phases[p]) beginif ((p.phase_done != null) && (p.phase_done.get_objection_total() > 0)) beginif (m_phase_trace)`UVM_PH_TRACE("PH/TRC/TIMEOUT/OBJCTN", $sformatf("Phase '%s' has outstanding objections:\n%s", p.get_full_name(), p.phase_done.convert2string()),this,UVM_LOW)endend`uvm_fatal("PH_TIMEOUT",$sformatf("Default timeout of %0t hit, indicating a probable testbench issue",`UVM_DEFAULT_TIMEOUT))endelse beginif (m_phase_trace)`UVM_PH_TRACE("PH/TRC/TIMEOUT", "PHASE TIMEOUT WATCHDOG EXPIRED", this, UVM_LOW)foreach (m_executing_phases[p]) beginif ((p.phase_done != null) && (p.phase_done.get_objection_total() > 0)) beginif (m_phase_trace)`UVM_PH_TRACE("PH/TRC/TIMEOUT/OBJCTN", $sformatf("Phase '%s' has outstanding objections:\n%s", p.get_full_name(), p.phase_done.convert2string()),this,UVM_LOW)endend`uvm_fatal("PH_TIMEOUT",$sformatf("Explicit timeout of %0t hit, indicating a probable testbench issue",top.phase_timeout))endif (m_phase_trace)`UVM_PH_TRACE("PH/TRC/EXE/3","PHASE EXIT TIMEOUT",this,UVM_DEBUG)end // if (this.get_name() == "run")else beginwait (0); // never unblock for non-run phaseendend // if (m_phase_trace)join_anydisable fork;endjoin // guardendendm_executing_phases.delete(this);//---------// JUMPING://---------// If jump_to() was called then we need to kill all the successor// phases which may still be running and then initiate the new// phase. The return is necessary so we don't start new successor// phases. If we are doing a forward jump then we want to set the// state of this phase's successors to UVM_PHASE_DONE. This// will let us pretend that all the phases between here and there// were executed and completed. Thus any dependencies will be// satisfied preventing deadlocks.// GSA TBD insert new jump supportif (m_phase_type == UVM_PHASE_NODE) beginif(m_premature_end) beginif(m_jump_phase != null) begin state_chg.m_jump_to = m_jump_phase;`uvm_info("PH_JUMP",$sformatf("phase %s (schedule %s, domain %s) is jumping to phase %s",get_name(), get_schedule_name(), get_domain_name(), m_jump_phase.get_name()),UVM_MEDIUM);endelse begin`uvm_info("PH_JUMP",$sformatf("phase %s (schedule %s, domain %s) is ending prematurely",get_name(), get_schedule_name(), get_domain_name()),UVM_MEDIUM);end#0; // LET ANY WAITERS ON READY_TO_END TO WAKE UPif (m_phase_trace)`UVM_PH_TRACE("PH_END","ENDING PHASE PREMATURELY",this,UVM_HIGH)endelse begin// WAIT FOR PREDECESSORS: // WAIT FOR PREDECESSORS:// function phases onlyif (task_phase == null)m_wait_for_pred();end//-------// ENDED://-------// execute 'phase_ended' callbacksif (m_phase_trace)`UVM_PH_TRACE("PH_END","ENDING PHASE",this,UVM_HIGH)state_chg.m_prev_state = m_state;m_state = UVM_PHASE_ENDED;`uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))if (m_imp != null)m_imp.traverse(top,this,UVM_PHASE_ENDED);#0; // LET ANY WAITERS WAKE UP//---------// CLEANUP://---------// kill this phase's threadsstate_chg.m_prev_state = m_state;if(m_premature_end) m_state = UVM_PHASE_JUMPING;else m_state = UVM_PHASE_CLEANUP ;`uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))if (m_phase_proc != null) beginm_phase_proc.kill();m_phase_proc = null;end#0; // LET ANY WAITERS WAKE UPif (phase_done != null)phase_done.clear();end//------// DONE://------m_premature_end = 0 ;if(m_jump_fwd || m_jump_bkwd) beginif(m_jump_fwd) beginclear_successors(UVM_PHASE_DONE,m_jump_phase);endm_jump_phase.clear_successors();uvmkit_gettimeofday(phase_finish_time);void'(uvmkit_timeval_subtract(phase_diff_time, phase_finish_time, phase_start_time));`uvm_info("TIMED_PHASE",$sformatf("'%s' phase took a total of %f seconds",this.get_full_name(),uvmkit_timeval_to_real(phase_diff_time)),UVM_LOW)endelse beginif (m_phase_trace)`UVM_PH_TRACE("PH/TRC/DONE","Completed phase",this,UVM_LOW)state_chg.m_prev_state = m_state;m_state = UVM_PHASE_DONE;`uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))m_phase_proc = null;uvmkit_gettimeofday(phase_finish_time);void'(uvmkit_timeval_subtract(phase_diff_time, phase_finish_time, phase_start_time));`uvm_info("TIMED_PHASE",$sformatf("'%s' phase took a total of %f seconds",this.get_full_name(),uvmkit_timeval_to_real(phase_diff_time)),UVM_LOW)#0; // LET ANY WAITERS WAKE UPend#0; // LET ANY WAITERS WAKE UPif (phase_done != null)phase_done.clear();//-----------
// SCHEDULED:
//-----------if(m_jump_fwd || m_jump_bkwd) beginvoid'(m_phase_hopper.try_put(m_jump_phase));m_jump_phase = null;m_jump_fwd = 0;m_jump_bkwd = 0;uvmkit_gettimeofday(phase_finish_time);void'(uvmkit_timeval_subtract(phase_diff_time, phase_finish_time, phase_start_time));`uvm_info("TIMED_PHASE",$sformatf("'%s' phase took a total of %f seconds",this.get_full_name(),uvmkit_timeval_to_real(phase_diff_time)),UVM_LOW)end// If more successors, schedule them to run nowelse if (m_successors.size() == 0) begintop.m_phase_all_done=1;end else begin// execute all the successorsforeach (m_successors[succ]) beginif(succ.m_state < UVM_PHASE_SCHEDULED) beginstate_chg.m_prev_state = succ.m_state;state_chg.m_phase = succ;succ.m_state = UVM_PHASE_SCHEDULED;`uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(succ, state_chg))#0; // LET ANY WAITERS WAKE UPvoid'(m_phase_hopper.try_put(succ));if (m_phase_trace)`UVM_PH_TRACE("PH/TRC/SCHEDULED",{"Scheduled from phase ",get_full_name()},succ,UVM_LOW)endendendendtask
第一次调用这个task是句柄m_common_domain,首先我们需要等待调用这个task的phase的m_predecessors[]执行完毕,即它们的m_state 为UVM_PHASE_DONE,这里m_common_domain是没有任何前置phase的,所以略过。若当前phase的m_state是UVM_PHASE_DONE,表明已经执行过了,task会直接return。若当前phase_type不是UVM_PHASE_NODE,即对于domain/schedule这两张phase运行图来说,会进行"fake" execution,task只会将其m_state分别转换为UVM_PHASE_STARTED和UVM_PHASE_EXECUTING,而并不会真的做什么事情。在task的最后判断m_jump_fwd和m_jump_bkwd是否为1,这两个变量是跟domain的jump操作有关的,这里它们的值都是0,则对于当前phase的所有后续phase m_successors,判断其m_state是否已经走到了UVM_PHASE_SCHEDULED这一步,若没有,则将其m_state置为UVM_PHASE_SCHEDULED并分别将后续phase的句柄放入m_phase_hopper邮箱。对于m_common_domain来说,它的后续phase只有一个即build_phase,于是这里就会将m_common_domain运行图中的build_phase唯一实例句柄放入m_phase_hopper。
在task m_run_phases()中forever循环从邮箱中拿到build_phase的句柄,会马上用fork…join_none启动新线程调用build_phase的execute_phase()执行。由于这次调用execute_phase()的句柄类型的phase_type是UVM_PHASE_NODE,而且是function phase,所以会依次将其m_state进入UVM_PHASE_STARTED、UVM_PHASE_EXECUTING和UVM_PHASE_ENDED状态,并将这些状态作为参数调用traverse()。traverse()我们之前已经研究过,这里传入的第一个参数是top,即uvm_root的唯一实例,则执行这些phase的component是从uvm_root作为入口,对于build_phase是自上而下执行的。在后面的状态中执行一些清理的工作,这里不再赘述。依然是task最后,会检查build_phase的后续phase m_successors[]并将其放入m_phase_hopper邮箱来执行,这样UVM中的各个function phase就这样按固定顺序依次执行。直到执行到最后m_common_domain的m_end_node不再有m_successors,则把变量m_phase_all_done置为1,UVM会调用$finish结束仿真。
在执行到start_of_simulation_phse的时候,该phase有两个后继phase,分别为run_phase和m_uvm_domain,他们会分别被放入m_phase_hopper邮箱并被取走调用fork…join_none分别启动并行的新线程来调用各自的execute_phase(),在m_uvm_domain执行execute_phase()时,并不会真的执行,所以也不会有任何仿真延时,它也会在task最后把后继phase句柄即m_uvm_schedule放入邮箱并被取走开启新线程调用execute_phase(),m_uvm_schedule同样也不会真的被执行,而是继续把后继phase即pre_reset_phase实例句柄放入邮箱,当新的并行线程被启动调用execute_phase()时,这个phase_type为UVM_PHASE_NODE的phase会被执行。所有这一切都是并行发生的,所以我们说run_phase和12个run-time phase是同时被并行启动运行的。
在运行task phase时,execute_phase()中会在调用traverse()执行当前task phase的同时启动数个并行进程,同时监测当前phase的objection和TIMEOUT,一旦达到退出条件,则马上杀掉当前进程进入下一个task phase。因此,在运行task phase时设置合理的objection和timeout以保证phase正常运行非常重要。
3. objection机制
UVM的objection机制是配合task phase来用的,在task phase如run_phase中若整个平台中没有raise任何objection,则UVM运行到这里会马上跳转到下一个phase,不会执行run_phase里的任何内容。
3.1. uvm_objection class
uvm_objection类扩展自uvm_report_object类,其中定义了一个存储内容为uvm_objection类型的静态队列,在new()函数中会把当前调用该函数的component/object句柄存入队列m_objections。此外,在uvm_objection类中还定义了一个uvm_objection_events类型的索引为uvm_object的联合数组m_events[],uvm_objection_events类中定义了数个event变量。联合数组m_source_count[]和m_total_count[]均是以uvm_object类型为索引,记录的是raise objection的sequence或者component节点的objection数量和总的objection数量,联合数组m_drain_time[]以uvm_object类型为索引,记录的是当前sequence或者component节点的objection的drain_time。
//uvm_objection.svh
class uvm_objection_events;int waiters;event raised;event dropped;event all_dropped;
endclassclass uvm_objection extends uvm_report_object;`uvm_register_cb(uvm_objection, uvm_objection_callback)protected int m_source_count[uvm_object];protected int m_total_count [uvm_object];protected time m_drain_time [uvm_object];protected uvm_objection_events m_events [uvm_object];/*protected*/ bit m_top_all_dropped;protected uvm_root m_top;static uvm_objection m_objections[$];...function new(string name="");super.new(name);...m_objections.push_back(this);endfunction...
endclass
3.2. raise_objection
一般我们在某个sequence或者test的run_phase中调用raise_objection(),如phase.raise_objection(),当前phase会调用phase_done的raise_objection()函数,phase_done是定义在uvm_phase中的uvm_objection类型的句柄,在我们调用add()函数添加各个task phase时,就会例化出各个task phase的uvm_objection类型句柄并赋值给phase_done。
//uvm_phase.svh
class uvm_phase extends uvm_object; uvm_objection phase_done;...
endclass
function void uvm_phase::add(uvm_phase phase,uvm_phase with_phase=null,uvm_phase after_phase=null,uvm_phase before_phase=null);if (phase.get_phase_type() == UVM_PHASE_IMP) beginuvm_task_phase tp;...// The phase_done objection is only required// for task-based nodesif ($cast(tp, phase)) beginif (new_node.get_name() == "run") beginnew_node.phase_done = uvm_test_done_objection::get();endelse beginnew_node.phase_done = uvm_objection::type_id::create({phase.get_name(), "_objection"});endendend...
endfunctionfunction void uvm_phase::raise_objection (uvm_object obj, string description="",int count=1);if (phase_done != null)phase_done.raise_objection(obj,description,count);elsem_report_null_objection(obj, description, count, "raise");
endfunction
定义在uvm_objection类中的raise_objection()函数有三个参数,第一个参数uvm_object类型obj,我们可以在一个sequence或者某component节点中使用this指针传入当前的sequence或者component,第二个参数可以输入一些字符串类型的描述,第三个参数是要raise的objection数量,默认是1。若参数obj为null,则obj为uvm_root的唯一实例m_top,然后把obj作为前两个参数调用m_raise()函数。
//uvm_objection.svh
class uvm_objection extends uvm_report_object;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);endfunctionfunction void m_raise (uvm_object obj,uvm_object source_obj,string description="",int count=1);int idx;uvm_objection_context_object ctxt;// Ignore raise if count is 0if (count == 0)return;if (m_total_count.exists(obj))m_total_count[obj] += count;else m_total_count[obj] = count;if (source_obj==obj) beginif (m_source_count.exists(obj))m_source_count[obj] += count;elsem_source_count[obj] = count;endif (m_trace_mode)m_report(obj,source_obj,description,count,"raised");raised(obj, source_obj, description, count);...if (ctxt == null) begin// If there were no drains, just propagate as usualif (!m_prop_mode && obj != m_top)m_raise(m_top,source_obj,description,count);else if (obj != m_top)m_propagate(obj, source_obj, description, count, 1, 0);endelse begin// Otherwise we need to determine what exactly happenedint diff_count;// Determine the diff count, if it's positive, then we're// looking at a 'raise' total, if it's negative, then// we're looking at a 'drop', but not down to 0. If it's// a 0, that means that there is no change in the total.diff_count = count - ctxt.count;if (diff_count != 0) begin// Something changedif (diff_count > 0) begin// we're looking at an increase in the totalif (!m_prop_mode && obj != m_top)m_raise(m_top, source_obj, description, diff_count);else if (obj != m_top)m_propagate(obj, source_obj, description, diff_count, 1, 0);endelse begin// we're looking at a decrease in the total// The count field is always positive...diff_count = -diff_count;if (!m_prop_mode && obj != m_top)m_drop(m_top, source_obj, description, diff_count);else if (obj != m_top)m_propagate(obj, source_obj, description, diff_count, 0, 0);endend// Cleanupctxt.clear();m_context_pool.push_back(ctxt);endendfunction
endclass
在其中定义了一个uvm_objection_context_object类型的变量ctxt,在uvm_objection_context_object类中定义了uvm_object类型的变量obj和source_obj,字符串description以及数量count,其实就是记录下我们每次raise_objection或者drop_objection的参数信息。
//uvm_objection.svh
class uvm_objection_context_object;uvm_object obj;uvm_object source_obj;string description;int count;uvm_objection objection;// Clears the values stored within the object,// preventing memory leaks from reused objectsfunction void clear();obj = null;source_obj = null;description = "";count = 0;objection = null;endfunction : clear
endclass
回到m_raise()函数,若传入count为0,函数直接返回。若是第一次在my_seq中raise objection,则m_total_count[my_seq]=count,若不是,则以该obj为索引的m_total_count数量加上此次要raise的objection数量。由于传入的obj和source_obj是同一个参数m_top,所以m_source_count[my_seq]=count,若不是,则略过,所以m_source_count的索引只会记录在其中raise objection的sequence或者component节点。接下来函数调用raised():
//uvm_objection.svh
class uvm_objection extends uvm_report_object;virtual function void raised (uvm_object obj,uvm_object source_obj,string description,int count);uvm_component comp;if ($cast(comp,obj)) comp.raised(this, source_obj, description, count);`uvm_do_callbacks(uvm_objection,uvm_objection_callback,raised(this,obj,source_obj,description,count))if (m_events.exists(obj))->m_events[obj].raised;endfunction
endclass
若传入的obj参数是component类型,会调用component的callback raised(),我们可以在某component中来重载这个callback。若m_events中有当前obj的索引内容,则触发其中的raised事件。
回到m_raise()中,此时ctxt为null,且m_prop_mode为1(default 1),则调用m_propagate():
//uvm_objection.svh
class uvm_objection extends uvm_report_object;unction void m_propagate (uvm_object obj,uvm_object source_obj,string description,int count,bit raise,int in_top_thread);if (obj != null && obj != m_top) beginobj = m_get_parent(obj);if(raise)m_raise(obj, source_obj, description, count);elsem_drop(obj, source_obj, description, count, in_top_thread);endendfunctionfunction uvm_object m_get_parent(uvm_object obj);uvm_component comp;uvm_sequence_base seq;if ($cast(comp, obj)) beginobj = comp.get_parent();endelse if ($cast(seq, obj)) beginobj = seq.get_sequencer();endelseobj = m_top;if (obj == null)obj = m_top;return obj;endfunction
endclass
若当前传入obj不是m_top,则调用m_get_parent()取得其父节点的句柄。这个obj经过层层参数传递,实际上还是我们在一开始调用raise_objection()时的sequence或者component,若是obj是一个sequence,则m_get_parent()返回该sequence的sequencer,若是一个component节点,则直接返回其父节点。然后在m_propagate()中再次调用m_raise()函数,不过这次obj参数即为其父节点句柄。经过层层递归调用m_raise(),直到uvm_root节点,此时
m_source_count[my_seq] = 1;
m_total_count[env.agt.sqr] = 1;
m_total_count[env.agt] = 1;
m_total_count[env] = 1;
m_total_count[m_top] = 1;
当我们在某个seq或者component中raise objection数为count时,UVM会将count记录在以自身为索引的m_source_count[]中。当m_prop_mode默认为1时其会将count以当前节点的所有上级节点为索引记录在m_total_count[]中,m_prop_mode为0时只会将自身和顶层uvm_root为索引记录在m_total_count[]中,不会记录中间节点。
3.3. drop_objection
同样在某sequence或者component中drop_objection()时,也会在其中调用drop_done的drop_objection()函数:
//uvm_phase.svh
function void uvm_phase::drop_objection (uvm_object obj, string description="",int count=1);if (phase_done != null)phase_done.drop_objection(obj,description,count);elsem_report_null_objection(obj, description, count, "drop");
endfunction//uvm_objection.svh
class uvm_objection extends uvm_report_object;virtual function void drop_objection (uvm_object obj=null,string description="",int count=1);if(obj == null)obj = m_top;m_drop (obj, obj, description, count, 0);endfunction// Function- m_dropfunction void m_drop (uvm_object obj,uvm_object source_obj,string description="",int count=1,int in_top_thread=0);// Ignore drops if the count is 0if (count == 0)return;if (!m_total_count.exists(obj) || (count > m_total_count[obj])) beginif(m_cleared)return;uvm_report_fatal("OBJTN_ZERO", {"Object \"", obj.get_full_name(), "\" attempted to drop objection '",this.get_name(),"' count below zero"});return;endif (obj == source_obj) beginif (!m_source_count.exists(obj) || (count > m_source_count[obj])) beginif(m_cleared)return;uvm_report_fatal("OBJTN_ZERO", {"Object \"", obj.get_full_name(), "\" attempted to drop objection '",this.get_name(),"' count below zero"});return;endm_source_count[obj] -= count;endm_total_count[obj] -= count;if (m_trace_mode)m_report(obj,source_obj,description,count,"dropped");dropped(obj, source_obj, description, count);// if count != 0, no reason to forkif (m_total_count[obj] != 0) beginif (!m_prop_mode && obj != m_top)m_drop(m_top,source_obj,description, count, in_top_thread);else if (obj != m_top) beginthis.m_propagate(obj, source_obj, description, count, 0, in_top_thread);endendelse beginuvm_objection_context_object ctxt;if (m_context_pool.size())ctxt = m_context_pool.pop_front();elsectxt = new;ctxt.obj = obj;ctxt.source_obj = source_obj;ctxt.description = description;ctxt.count = count;ctxt.objection = this;// Need to be thread-safe, let the background// process handle it.// Why don't we look at in_top_thread here? Because// a re-raise will kill the drain at object that it's// currently occuring at, and we need the leaf-level kills// to not cause accidental kills at branch-levels in// the propagation.// Using the background process just allows us to// separate the links of the chain.m_scheduled_list.push_back(ctxt);end // else: !if(m_total_count[obj] != 0)endfunction
endclass
若传入参数obj不是m_top,uvm_objection的drop_objection()会把obj作为前两个参数继续调用m_drop(),若传入的参数count为0,函数直接返回。若m_total_count[]中不存在obj为索引的objection数量记录,或者当前传入的count数量超出了m_total_count[obj],函数直接返回。bit m_cleared用来记录当前objection是否已经全部被drop掉,我们会在每次调用raise_objectiion时候把这个bit置为0,若这个bit为1,则函数也直接返回。当前m_drop()的前两个参数obj和source_obj相等,将m_source_count[obj]减去count,同时将m_total_count[obj]也减去count。接下来调用dropped()函数:
//uvm_objection.svh
class uvm_objection extends uvm_report_object; virtual function void dropped (uvm_object obj,uvm_object source_obj,string description,int count);uvm_component comp;if($cast(comp,obj)) comp.dropped(this, source_obj, description, count);`uvm_do_callbacks(uvm_objection,uvm_objection_callback,dropped(this,obj,source_obj,description,count))if (m_events.exists(obj))->m_events[obj].dropped;endfunction
endclass
若obj是一个component类型,则调用component的callback dropped(),我们可以在component中扩展这个callback。此外会触发m_events[obj]的dropped事件。
回到m_drop()中来,若减去这次drop的objection数量之后m_total_count[obj]不为0且m_prop_mode为1,则调用m_propagate()并把第五个参数传入0,表示这是一次drop操作,m_propagate()在之前我们已经见过,这个函数就是从当前的obj开始遍历所有父节点直到uvm_root,根据第五个参数对这些父节点进行raise或者drop objection操作。
3.4. drain_time
若当前调用m_drop()减去这次drop的objection数量之后m_total_count[obj]为0,说明对obj来说,所有的objection都已经被dropped了,当前phase是否可以直接结束进入下一个phase中呢,答案是否定的,UVM会等待执行当前phase的其他节点中的objection被dropped,那若所有节点的所有objection都已经被dropped了,是否可以结束当前phase呢?答案依然是否定的,若某节点中当前phase设置drain_time,则在objection都被dropped之后,该phase会等待drain_time时间之后才会结束。这个drain_time考虑到了验证平台激励和DUT输出可能会存在延时,比如我们在driver的main_phase中输入向DUT灌输激励之后drop_objection,DUT处理这些激励需要一些延时,如果此时直接结束当前phase,monitor就会漏掉最后的一些激励处理之后的结果,所以phase会在所有的objection被dropped之后再延长drain_time时间才会结束。
//uvm_objection.svh
class uvm_objection extends uvm_report_object; function void set_drain_time (uvm_object obj=null, time drain);if (obj==null)obj = m_top;m_drain_time[obj] = drain;endfunction
endclass
通过调用set_drain_time()函数可以设置当前obj的drain_time,将其存入以obj为索引的联合数组中。若传入参数obj为null,则设置m_top的drain_time。
回到m_drop()中,若当前m_total_count[obj]为0,我们还需要等待当前phase的drain_time时间才能真正结束phase,此时会检查m_context_pool的内容,若有存储直接弹出最前面的一个,若没有,则创建一个uvm_objection_context_object类型的对象ctxt,并把obj/source_obj/description/count和this指针分别赋值给其成员变量,最后将其放入队列m_scheduled_list[ ] 。 其 中 m c o n t e x t p o o l [ ]。其中m_context_pool[ ]。其中mcontextpool[]中存放的是用过的uvm_objection_context_object指针,从这里拿跟new()一个没有太大区别。
//uvm_objection.svh
class uvm_objection extends uvm_report_object;local static uvm_objection_context_object m_context_pool[$];`ifndef UVM_USE_PROCESS_CONTAINER local process m_drain_proc[uvm_object];
`elselocal process_container_c m_drain_proc[uvm_object];
`endiflocal static uvm_objection_context_object m_scheduled_list[$];local uvm_objection_context_object m_scheduled_contexts[uvm_object];local uvm_objection_context_object m_forked_list[$];local uvm_objection_context_object m_forked_contexts[uvm_object];...
endclass
队列m_scheduled_list[$]存放的是当前在等待drain_time的objection信息,联合数组m_scheduled_contexts[]以uvm_object为索引,存放的是关于uvm_object的在等待drain_time的objection信息,队列m_forked_list[$]存放的是当前已经启动fork线程等待drain_time的objection信息,联合数组m_forked_contexts[]以uvm_object为索引,存放的是关于uvm_object的已经启动fork线程在等待drain_time的objection信息。此外联合数组m_drain_proc[]存放的是以uvm_object为索引的等待drain_time的进程句柄。
等待phase的drain_time的进程其实一直作为backgroud进程从仿真一开始调用uvm_root::run_test()就启动了,甚至比调用m_run_phases()还要早,在run_test()中调用静态函数m_init_objections()启动该进程,该函数会用fork…join_none调用m_execute_scheduled_forks()。
//uvm_root.svh
task uvm_root::run_test(string test_name="");uvm_objection::m_init_objections();
endtask//uvm_objection.svh
class uvm_objection extends uvm_report_object;static function void m_init_objections();fork uvm_objection::m_execute_scheduled_forks();join_noneendfunction// background process; when nonstatic task m_execute_scheduled_forks();while(1) beginwait(m_scheduled_list.size() != 0);if(m_scheduled_list.size() != 0) beginuvm_objection_context_object c;uvm_objection o;// Save off the context before the forkc = m_scheduled_list.pop_front();// A re-raise can use this to figure out props (if any)c.objection.m_scheduled_contexts[c.obj] = c;// The fork below pulls out from the forked listc.objection.m_forked_list.push_back(c);// The fork will guard the m_forked_drain call, but// a re-raise can kill m_forked_list contexts in the delta// before the fork executes.fork : guardautomatic uvm_objection objection = c.objection;begin// Check to maike sure re-raise didn't empty the fifoif (objection.m_forked_list.size() > 0) beginuvm_objection_context_object ctxt;ctxt = objection.m_forked_list.pop_front();// Clear it out of scheduledobjection.m_scheduled_contexts.delete(ctxt.obj);// Move it in to forked (so re-raise can figure out props)objection.m_forked_contexts[ctxt.obj] = ctxt;// Save off our process handle, so a re-raise can kill it...
`ifndef UVM_USE_PROCESS_CONTAINER objection.m_drain_proc[ctxt.obj] = process::self();
`elsebeginprocess_container_c c = new(process::self());objection.m_drain_proc[ctxt.obj]=c;end
`endif // Execute the forked drainobjection.m_forked_drain(ctxt.obj, ctxt.source_obj, ctxt.description, ctxt.count, 1);// Cleanup if we survived (no re-raises)objection.m_drain_proc.delete(ctxt.obj);objection.m_forked_contexts.delete(ctxt.obj);// Clear out the context object (prevent memory leaks)ctxt.clear();// Save the context in the pool for later reusem_context_pool.push_back(ctxt);endendjoin_none : guardendendendtask
endclass
静态task m_execute_scheduled_forks()中使用while无限循环,一旦队列m_scheduled_list[$]不为空,则从中取出uvm_objection_context_object类型句柄放入m_scheduled_contexts[obj]和队列m_forked_list[$],这些句柄记录的是当前obj在drop_objection时需要等待drain_time的信息。随即使用fork…join_none并行线程取出m_forked_list[$]的句柄并清除m_forked_list[$]和m_scheduled_contexts[obj]相关内容,同时启动m_drain_proc[obj]调用m_forked_drain()来执行等待任务,等待该task返回以后清除m_forked_drain[obj]和m_forked_contexts[obj]。
//uvm_objection.svh
class uvm_objection extends uvm_report_object;task m_forked_drain (uvm_object obj,uvm_object source_obj,string description="",int count=1,int in_top_thread=0);int diff_count;if (m_drain_time.exists(obj))`uvm_delay(m_drain_time[obj])if (m_trace_mode)m_report(obj,source_obj,description,count,"all_dropped");all_dropped(obj,source_obj,description, count);// wait for all_dropped cbs to completewait fork;/* NOT NEEDED - Any raise would have killed us!if(!m_total_count.exists(obj))diff_count = -count;elsediff_count = m_total_count[obj] - count;*/// we are ready to delete the 0-count entries for the current// object before propagating up the hierarchy. if (m_source_count.exists(obj) && m_source_count[obj] == 0)m_source_count.delete(obj);if (m_total_count.exists(obj) && m_total_count[obj] == 0)m_total_count.delete(obj);if (!m_prop_mode && obj != m_top)m_drop(m_top,source_obj,description, count, 1);else if (obj != m_top)m_propagate(obj, source_obj, description, count, 0, 1);endtaskvirtual task all_dropped (uvm_object obj,uvm_object source_obj,string description,int count);uvm_component comp;if($cast(comp,obj)) comp.all_dropped(this, source_obj, description, count);`uvm_do_callbacks(uvm_objection,uvm_objection_callback,all_dropped(this,obj,source_obj,description,count))if (m_events.exists(obj))->m_events[obj].all_dropped;if (obj == m_top)m_top_all_dropped = 1;endtask
endclass
在task m_forked_drain()中,若当前obj已经设置了drain_time,会首先等待m_drain_time[obj]时间,然后调用all_dropped()函数,若obj是component类型,该函数中会首先调用all_dropped() callback,我们可以在某component中重载这个callback,其次会触发m_events[obj]的all_dropped事件,若当前obj是m_top,则把m_top_all_dropped置为1。回到task m_forked_drain()中,接着把m_source_count[]和m_total_count[]中关于obj的部分清除,若m_prop_mode为1则调用m_propagate()继续drop所有父类节点的objection,直到m_top的所有objection被dropped。
若某obj在等待drain_time期间,又有objection被raised,该如何处理?回到之前的m_raise()中:
//uvm_objection.svh
class uvm_objection extends uvm_report_object;function void m_raise (uvm_object obj,uvm_object source_obj,string description="",int count=1);int idx;uvm_objection_context_object ctxt;// Handle any outstanding drains...// First go through the scheduled listidx = 0;while (idx < m_scheduled_list.size()) beginif ((m_scheduled_list[idx].obj == obj) &&(m_scheduled_list[idx].objection == this)) begin// Caught it before the drain was forkedctxt = m_scheduled_list[idx];m_scheduled_list.delete(idx);break;endidx++;end// If it's not there, go through the forked listif (ctxt == null) beginidx = 0;while (idx < m_forked_list.size()) beginif (m_forked_list[idx].obj == obj) begin// Caught it after the drain was forked,// but before the fork startedctxt = m_forked_list[idx];m_forked_list.delete(idx);m_scheduled_contexts.delete(ctxt.obj);break;endidx++;endend// If it's not there, go through the forked contextsif (ctxt == null) beginif (m_forked_contexts.exists(obj)) begin// Caught it with the forked drain runningctxt = m_forked_contexts[obj];m_forked_contexts.delete(obj);// Kill the drain
`ifndef UVM_USE_PROCESS_CONTAINER m_drain_proc[obj].kill();m_drain_proc.delete(obj);
`elsem_drain_proc[obj].p.kill();m_drain_proc.delete(obj);
`endifendendif (ctxt == null) begin// If there were no drains, just propagate as usualif (!m_prop_mode && obj != m_top)m_raise(m_top,source_obj,description,count);else if (obj != m_top)m_propagate(obj, source_obj, description, count, 1, 0);endelse begin// Otherwise we need to determine what exactly happenedint diff_count;// Determine the diff count, if it's positive, then we're// looking at a 'raise' total, if it's negative, then// we're looking at a 'drop', but not down to 0. If it's// a 0, that means that there is no change in the total.diff_count = count - ctxt.count;if (diff_count != 0) begin// Something changedif (diff_count > 0) begin// we're looking at an increase in the totalif (!m_prop_mode && obj != m_top)m_raise(m_top, source_obj, description, diff_count);else if (obj != m_top)m_propagate(obj, source_obj, description, diff_count, 1, 0);endelse begin// we're looking at a decrease in the total// The count field is always positive...diff_count = -diff_count;if (!m_prop_mode && obj != m_top)m_drop(m_top, source_obj, description, diff_count);else if (obj != m_top)m_propagate(obj, source_obj, description, diff_count, 0, 0);endend// Cleanupctxt.clear();m_context_pool.push_back(ctxt);endendfunction
endclass
首先检查队列m_scheduled_list[$]中是否有当前obj的记录信息,若有(说明此时已经drop所有objection并在等待drain_time)则取出赋值给uvm_objection_context_object类型的ctxt句柄并删除队列中相关记录,若没有接着检查队列m_forked_list[$],若有则同样取出赋值给ctxt句柄并删除队列中相关记录,若没有则检查数组m_forked_contexts[]是否有相关信息,若有(说明已经进入等待fork线程)则同样取出赋值给ctxt句柄并删除队列中相关记录,同时杀掉m_drain_proc[obj]停止等待obj的drain_time进程。当ctxt得到记录信息,比较当前raise_objection的count和ctxt中的count大小,若raise_objection的count较大,则直接将差值count作为参数调用m_propagate()来raise objection,若raise_objection的count较小,则直接将差值count作为参数调用m_propagate()来drop objection,若相等,则略过。
回头来看uvm_phase::execute_phase()中在m_state为UVM_PHASE_EXECUTING时,若当前运行的是一个task phase时,一方面会用fork…join_none启动并行线程,在其中启动一个process来执行traverse(),另一方面也会再启动两个并行线程,一个用来检测当前phase的total_objection,另一个会检测timeout相关设置,以判断是否可以退出fork线程进入phase的下一个执行阶段。值得一提的是,这个timeout是定义在uvm_root中的一个time类型变量phase_timeout,目的是为了防止仿真一直block在某个phase中而设置的退出机制。这个default timeout是9200s,我们可以通过调用set_timeout()来重置这个phase_timeout时间:
//uvm_root.svh
function void uvm_root::set_timeout(time timeout, bit overridable=1);static bit m_uvm_timeout_overridable = 1;if (m_uvm_timeout_overridable == 0) beginuvm_report_info("NOTIMOUTOVR",$sformatf("The global timeout setting of %0d is not overridable to %0d due to a previous setting.",phase_timeout, timeout), UVM_NONE);return;endm_uvm_timeout_overridable = overridable;phase_timeout = timeout;
endfunction
4. phase的高级应用
4.1. jump()
UVM提供了在12个run-time phases间进行phase跳转运行的函数jump(),如我们在main_phase中可以调用jump()跳转到reset_phase: phase.jump(uvm_reset_phase.get());
//uvm_phase.svh
function void uvm_phase::end_prematurely() ;m_premature_end = 1 ;
endfunctionfunction void uvm_phase::jump(uvm_phase phase);set_jump_phase(phase) ;end_prematurely() ;
endfunction
jump()函数会先后调用set_jump_phase()和end_prematurely(),后者很简单就是把变量m_premature_end置为1,这个bit会控制exectute_phase()中当前task phase的运行进入jumping状态。来看set_jump_phase():
//uvm_phase.svh
function void uvm_phase::set_jump_phase(uvm_phase phase) ;uvm_phase d;if ((m_state < UVM_PHASE_STARTED) ||(m_state > UVM_PHASE_ENDED) )begin`uvm_error("JMPPHIDL", { "Attempting to jump from phase \"",get_name(), "\" which is not currently active (current state is ",m_state.name(), "). The jump will not happen until the phase becomes ","active."})end// A jump can be either forward or backwards in the phase graph.// If the specified phase (name) is found in the set of predecessors// then we are jumping backwards. If, on the other hand, the phase is in the set// of successors then we are jumping forwards. If neither, then we// have an error.//// If the phase is non-existant and thus we don't know where to jump// we have a situation where the only thing to do is to uvm_report_fatal// and terminate_phase. By calling this function the intent was to// jump to some other phase. So, continuing in the current phase doesn't// make any sense. And we don't have a valid phase to jump to. So we're done.d = m_find_predecessor(phase,0);if (d == null) begind = m_find_successor(phase,0);if (d == null) beginstring msg;$sformat(msg,{"phase %s is neither a predecessor or successor of ","phase %s or is non-existant, so we cannot jump to it. ","Phase control flow is now undefined so the simulation ","must terminate"}, phase.get_name(), get_name());`uvm_fatal("PH_BADJUMP", msg);endelse beginm_jump_fwd = 1;`uvm_info("PH_JUMPF",$sformatf("jumping forward to phase %s", phase.get_name()),UVM_DEBUG);endendelse beginm_jump_bkwd = 1;`uvm_info("PH_JUMPB",$sformatf("jumping backward to phase %s", phase.get_name()),UVM_DEBUG);endm_jump_phase = d;
endfunction
若当前phase的m_state还没有走到UVM_PHASE_STARTED或者已经处于UVM_PHASE_ENDED状态,则无法进行phase的跳转,函数直接返回。接着调用m_find_predecessor()在当前phase的所有m_predecessors[]中寻找要跳转的目标phase,若找到则把m_jump_bkwd置为1并把该phase句柄赋值给m_jump_phase;若没有找到,则调用m_find_successor()在该phase的所有m_successors[]中寻找目标phase,若找到则把m_jump_fwd置为1并把该phase句柄赋值给m_jump_phase,若没有找到,则报错。
回到uvm_phase::execute_phase()中,当前phase的m_state为UVM_PHASE_ENDED之后,即当前task phase已经执行完毕。若m_premature_end为1,则把m_state设置为UVM_PHASE_JUMPING,否则设置为UVM_PHASE_CLEANUP,然后做一些当前phase进程的清理工作。若m_jump_fwd为1,则phase会向前跳转,则把调用clear_successors()把当前phase到目标phase之间的phase从m_successors[]中删去。接下来,若m_jump_fwd或者m_jump_bkwd为1,则将m_jump_phase放入m_phase_hopper邮箱中等待运行,之后吧m_jump_phase赋值为null,把m_jump_fwd和m_jump_bkwd赋值为0。
4.2. 创建新的domain
4.2.1. set_domain()
UVM有一个default domain叫做m_common_domain,代表8个function phase和run_phase的运行图,另外还包括一个与run_phase并行运行的m_uvm_domain,这个m_uvm_domain代表12个run-time phases的运行图。在m_uvm_domain之外,我们还可以创建新的domain,可以使新domain的12个run-time phases独立运行,而不必与m_uvm_domain中的phase同步。通常我们可以在某component中调用new()函数来创建一个新的domain:
uvm_domain my_domain = new(“my_domain”);
set_domain(my_domain);
set_domain()是定义在uvm_component类中的函数,在其中会调用define_domain()将新domain的句柄传入。若参数hier为default 1,
//uvm_component.svh
function void uvm_component::set_domain(uvm_domain domain, int hier=1);// build and store the custom domainm_domain = domain;define_domain(domain);if (hier)foreach (m_children[c])m_children[c].set_domain(domain);
endfunctionfunction void uvm_component::define_domain(uvm_domain domain);uvm_phase schedule;//schedule = domain.find(uvm_domain::get_uvm_schedule());schedule = domain.find_by_name("uvm_sched");if (schedule == null) beginuvm_domain common;schedule = new("uvm_sched", UVM_PHASE_SCHEDULE);uvm_domain::add_uvm_phases(schedule);domain.add(schedule);common = uvm_domain::get_common_domain();if (common.find(domain,0) == null)common.add(domain,.with_phase(uvm_run_phase::get()));endendfunction
其中会首先调用find_by_name()在新domain中寻找是否有名为"uvm_sched"的phase,很明显我们的新domain还没有添加任何phase,所以为null。接下来会创建一个phase_type为UVM_PHASE_SCHEDULE的phase赋值给句柄schedule,并调用add_uvm_phases()将12个run-time phases加入schedule,再将schedule添加进新建的domain中。最后,将新的my_domain加入UVM的default domain m_common_domain中。其实这个过程跟m_uvm_domain的创建过程一样,加入新的my_domain之后,会形成如下图中的运行图(两个domain的run-time phases并不会同步运行):
4.2.2. sync()
我们可以通过调用sync()来对不同doumain的runtime-phase进行同步,uvm_phase类中有一个队列m_sync[$],其中存储的是需要与当前phase同步的phases。
//uvm_phase.svh
class uvm_phase extends uvm_object;local uvm_phase m_sync[$];...
endclass
function void uvm_phase::sync(uvm_domain target,uvm_phase phase=null,uvm_phase with_phase=null);if (!this.is_domain()) begin`uvm_fatal("PH_BADSYNC","sync() called from a non-domain phase schedule node");endelse if (target == null) begin`uvm_fatal("PH_BADSYNC","sync() called with a null target domain");endelse if (!target.is_domain()) begin`uvm_fatal("PH_BADSYNC","sync() called with a non-domain phase schedule node as target");endelse if (phase == null && with_phase != null) begin`uvm_fatal("PH_BADSYNC","sync() called with null phase and non-null with phase");endelse if (phase == null) begin// whole domain sync - traverse this domain schedule from begin to end node and sync each nodeint visited[uvm_phase];uvm_phase queue[$];queue.push_back(this);visited[this] = 1;while (queue.size()) beginuvm_phase node;node = queue.pop_front();if (node.m_imp != null) beginsync(target, node.m_imp);endforeach (node.m_successors[succ]) beginif (!visited.exists(succ)) beginqueue.push_back(succ);visited[succ] = 1;endendendend else begin// single phase sync// this is a 2-way ('with') sync and we check first in case it is already thereuvm_phase from_node, to_node;int found_to[$], found_from[$];if(with_phase == null) with_phase = phase;from_node = find(phase);to_node = target.find(with_phase);if(from_node == null || to_node == null) return;found_to = from_node.m_sync.find_index(node) with (node == to_node);found_from = to_node.m_sync.find_index(node) with (node == from_node);if (found_to.size() == 0) from_node.m_sync.push_back(to_node);if (found_from.size() == 0) to_node.m_sync.push_back(from_node);end
endfunction
- sync()函数有三个参数,第一个参数传入想要被同步的新domain句柄,第二个参数传入要被同步的phase,第三个参数传入的phase句柄表示要与新domain中的哪个phase进行同步。若我们需要同步m_common_domain中的pre_reset_phase和my_domain中的main_phase,则可以这么调用:
uvm_domain common_domain = uvm_domain::get_common_domain();
common_domain.sync(my_domain,uvm_pre_reset_phase::get(),uvm_main_phase::get());
sync()函数会分别在这两个要被同步的phase的队列m_sync[$]中检索是否已经有了对方的信息,很明显这里没有,那么就把对方的phase句柄分别存入自己的m_sync[$]中。 - 若调用时第三个参数with_phase为null,如
common_domain.sync(my_domain,uvm_pre_reset_phase::get());
则with_phase为pre_reset_phase,两个domain的pre_reset_phase的m_sync[$]中会分别放入一条对方的记录。 - 若调用时只提供第一个参数,如
common_domain.sync(my_domain);
则会将两个domain的所有12个run-time phase进行sync。
回到uvm_phase::execute_phase()中,若当前phase的m_state运行到UVM_PHASE_SYNCING,若m_sync中记录有需要sync的信息,则会一直等待需要sync的这些phase的m_state运行到UVM_PHASE_SYNCING才会继续往下一个状态运行,UVM通过这种方式实现phase间的同步。