1.phase机制
uvm 验证环境通过 phase 机制,引入了一套自动化的运行流程,通过该机制我们可以清晰的了解 UVM 仿真阶段的层次化,因为 verilog 中有阻塞和非阻塞赋值,相应的仿真平台中,也要实现 NBA 区域 和 Actice 区域,在不同的 phase 区域中做不同的事情,可以避免竞争关系导致的变量值得不确定性; 用户只需要在需要添加用户代码的区别填充即可,降低了仿真平台的调试成本。
如下图所示,这是 UVM 验证环境中全部 phase,从上至下,依次顺序执行。其中有些 phase 为 task 函数类型,另一些为 function 函数类型。
耗时 phase (图中灰色部分-task)
run_phase 和其十二个子 phase,推荐使用 run_phase,在使用子 phase 时尽量不要和 run_phase 同时使用,混用是可能导致 phase 直接的跳转环境混乱,但并不是混合使用一定会出现问题。其运行关系如下图折叠代码块
run_phase 运行机制
fork
begin
run_phase();
end
begin
pre_reset_phase();
reset_phase();
post_reset_phase();
pre_configure_phase();
configure_phase();
post_configure_phase();
pre_main_phase();
main_phase();
post_main_phase();
pre_shutdown_phase();
shutdown_phase();
post_shutdown_phase();
end
join
稍微解释一下上述代码含义,fork-join 表示两个 begin-end 是同时开始运行的,当 run_phase() 和 pre_reset_phase() 同时存在时,run_phase() 和 pre_reset_phase() 在同一个时刻点开始运行,在 run_phase() 和 pre_reset_phase() 两个 phase 均运行结束以后,主 phase 才会继续执行 extract_phase()。耗时的 phase 的运行是在 uvm_tree 中从下往上运行。
不耗时 phase (图中白色部分-function)
包含 build_phase()、connect_phase()、end_of_elaboartion_phase()、start_of_simulation_phase()、extract_phase()、check_phase()、report_phase()、final_phase();
不耗时的几个 phase 在 uvm_tree 中是从上往下执行的,比如说 build_phase,这个 phase 主要用来实例化各个组件环境,理论上为了 uvm_tree 的顺利构建应该从上往下执行。 并不一定上述所有的 phase 都会用到,考虑个小问题,已经有了 9 主 phase,为什么还要引入 12 子phase 呢,这是为了有时候需要更加细化的进行一些 driver 工作,比如对 DUT 进行 reset 复位,如果我们在 reset_phase 进行了复位相关的操作,我们在运行到后续 phase 时,只需要 jump 到 reset_phase, 后续 phase 会依次重复执行依次,相当于对 DUT 实现了复位操作。
phase 机制-执行顺序
前面只提到 phase 的执行顺序是从上而下执行,以 uvm-tree 的角度叙述则是,从树顶到数叶自上而下执行,如下图所示,uvm_top 是最先开始执行的,后续依次往下执行。
似乎上述的执行过程又存在问题,下来我们来细说一下上述过程,首先执行的是 build_phase() ,该 phase 用于构建 uvm-tree 的结构,前面我们提到 uvm_component 组件类必须在 build_phase 中实例化,也是这个原因,因为 build_phase() 的主要用途就是实例化组件类,构建 tree; 不同层次所有组件的 build_phase() 从空间(uvm-tree)上从上而下执行从树根到树叶的全部组件类 build_phase(),而同层次的组件类则是按照 new/create 是自定的名字以字典序按照前后顺序自动执行 build_phase()。其他不耗时的 function phase 的执行过程则相反,比如先执行 driver 这层记得 connect_phase(), 再执行 agent 这一级的 connect_phase()。
耗时 phase 的执行和上述两种又有一些区别,需要明确以下几个方面
a. 对于同一组件,其 run_phase() 和 pre_reset_phase() 属于两个不同的线程,他们同一个时刻点启动, 但 12 个细分 phase 顺序执行;
b. 对于同一层级的组件,其 run_phase() 和 pre_reset_phase() 属于两个不同的线程,他们同一个时刻点先后启动, 但 12 个细分 phase 顺序执行;
c. 对于不同层级别的 component 在下一级别启动之后,才会启动;
d. run_phase() 和 post_shutdown_phase() 需要都执行结束才会进入 extra_phase() 中。
耗时 phase 的执行过程可以理解为自下而上启动,同时执行。
假设上图中 drv component 其 main_phase() 在 0 时刻开始执行,需要100个时刻才能结束;scb component 其 main_phase() 也从在 0 时刻开始执行, 且需要耗时最长,假设需要200个时刻才能结束(其他组件的 main_phase()也包含耗时,但均未超过 scb 组件的耗时),整个验证平台的 main_phase() 要在第200个时刻才能切换到 post_main_phase(),即 drv 在100时刻执行完毕后,不会马上跳到 post_main_phase(),需要等到所有组件中耗时最长的 mian_phase() 执行结束后,在 200 时刻,进入 post_main_phase(),其启动和执行的过程也是自下而上启动,同时执行。
2.config_db机制
在 uvm_tree 树中,越上层就越接近“用户场景”,通常不同的场景公用一套 driver 和 monitor 函数,通过设置不同的参数,产生不同的场景驱动时序,假设我们的 uvm-tree 如下图所示,上层组件 env 要给“树叶”层的组件 drv 传递参数 pre_num,这个时候就需要用到 config_db 给我们传递信息。
config_db两大函数-set() 和get()
config_db 的主要用途就是进行全局参数的传递共享,主要包含 set() 和 get() 函数,其中 set()用于在 config 全局数据库(config database)中创建一个条目(entries),当然前提是没有数据库中未包含该条目,如果已经存在,则更新它,get() 则用于从数据库中取出最新的 entries
uvm_config_db #(T)::set(…)
uvm_config_db #(T)::get(…)
注意点
a.其中T,需要设置或者取出的 entries 的参数类型
b.get() 必须在 set() 之后进行,通常 set() 一般在 build_phase()中进行,get() 一般在其他 phase 中,或者在以下级别的build_phase中完成。
我们继续上述话题,我们要从 env 中给 drv 传递 pre_num 参数, 中间跨过了中间组件 i_agt。以下是 set()函数的一种写法:
uvm_config_db set 示例
function void my_env::build_phase();
...
int pre_num = 25;
...
uvm_config_db#(int)::set(this,"env.i_agt.drv","pre_num",100)
...
endfuction
上述代码含义为,在组件 my_env 的 build_phase 中包含 pre_num = 25 的局部变量,同时通过 uvm_config_db 对 pre_num 进行设置,下面对立面的具体参数进行解释如下:
int : T 的具体参数类型,该类型必须和第四个参数 100 ,的类型一致,否则会报错;
this : 该参数必须为组件类型的指针, 当设置为 NULL 时,会默认 uvm_root 组件
“env.i_agt_drv”: 欲设置参数的绝对路径,其类型为字符串类型,这也是一个隐患,如果你拼写错了,你不仅无法取到该参数,而且编译器还不报错。
100: 为欲设置的参数值。
第二个参数+第三个参数=目标参数的绝对路径,以我个人的理解是,UVM在 全局 config_db 数据库中,创建一个名字为 pre_num 的config_db 数据库 entries,该条目的索引为“第二个参数+第三个参数组成的字符串”;第二个参数和第三个参数可以很灵活的设置,比如以下骚操作:
uvm_config_db set 示例
function void my_env::build_phase();
...
int pre_num = 25;
...
uvm_config_db#(int)::set(null,"uvm_test_top.env.i_agt.drv","pre_num",100)
...
endfuction
你也可以在 drv 组件中正确的取到 pre_num 参数值。大家似乎绝对还不透彻,可能在以后的使用过程中造成错误,那我再按照我的理解说一条“潜规则”,目标参数的绝对路径对应的字符串的设置“规则”,在 get() 函数执行时,该字符串对应的各个组件均已经被实例化,且实例化名也字符串中对应的一致,你就可以 get() 到设置的参数。有一点忘了提了,uvm_config_db#(int)::set(null,”uvm_test_top.env.i_agt.drv”,”pre_num”,100) 不会影响同名的参数的值,即在此次 set() 函数的执行不会将局部变量 pre_num 由 25 该成 100;
既然已经完成 set 操作了,现在再来说说 get;在 dirver 中你可以通过以下代码来取 pre_num 参数:
uvm_config_db get 示例
function void my_drv::build_phase();
...
int pre_num_A;
...
uvm_config_db#(int)::get(this,"","pre_num",pre_num_A)
...
endfuction
上述代码的含义是,通过 get() 函数将 config_db 数据中,索引为 this ,条目名为 pre_num的值,取出来给 pre_num_A;因为在 my_drv 组件的 build_phase 执行时,字符串 uvm_test_top.env.i_agt.drv 中的各个组件均已经实例化,而且在 my_drv 中 this 就是表示 uvm_test_top.env.i_agt.drv 对应的组件(uvm-tree绝对路径)。
config_db中一大坑-只 set 不 get的情况
一般情况下是 set 和 get 成对出现,但是有个特殊情况是,只 set 不 get 值,也能完成变量信息的传递,下来来简单说一下这个坑。假设 pre_num的 set() 还是如上述情况(情况一),而 drv 如下定义:
config_db 特殊示例
class my_driver extend uvm_driver; int pre_num;
`uvm_component_utils_begin(my_driver) `uvm_field_int(pre_num, UVM_ALL_ON) `uvm_component_utils_end function void my_drv::new(string name = "my_driver", uvm_component parent = null); ... super.new(name,phase); ... endfuction function void my_drv::build_phase(); super.build_phase(phase); ... endfuction
endclass
通过上述代码,就可以省去 get 操作,在上述代码中,首先定义了一个和entries中同名的变量 pre_num ,第二通过 uvm_field_int 对 pre_num 变量进行注册, 第三在 build_phase 中调用了 super.build_phase(),通过这操作者三步,你就可以省略 get()。但是我的建议,咱们还是老老实实的 get 省的给后续看你代码的人造成困惑。
最后再提一点,当有多个 set 对同变量进行设置时,get 会取到那个值,比如我们同时在 i_agt 和 uvm_test_top,中对 pre_num 设置(目标还是给 drv),这个情况 uvm 已经考虑过了,因为 uvm_test_top 离用户进,所以它的权限最高,我们会取到 uvm_test_top 组件中设置到的值,即谁最权威,听谁的;第二在同一个层次对一个变量 进行多次 set, 这个时候就看 进行 get 操作时,那个 set 操作的时间离得最短,即谁时间最近,听谁的。
至于其他的什么内容,如 monitor 中 set 值,给 driver; 我奉劝一句,不要玩的太骚,小心走火。
最新评论