*build阶段出现UVM_ERROR停止仿真
之前的代码中,如果使用config_db::get无法得到virtual interface,就会直接调用uvm_fatal结束仿真。由于 virtual interface对于一个driver来说是必须的,所以这种uvm_fatal直接退出的使用方式是非常常见的。但如果这里使用uvm_error,也会退出:
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
`uvm_error("my_driver", "UVM_ERROR test")
endfunction
# UVM_ERROR my_driver.sv(16) @ 0: uvm_test_top.env.i_agt.drv [my_driver] UVM_ERROR test
# UVM_FATAL @ 0: reporter [BUILDERR] stopping due to build errors
这里给出的uvm_fatal是UVM内部自定义的。在end_of_elaboration_phase及其前的phase中,如果出现了一个或多个UVM_ERROR,那么UVM就认为出现了致命错误,会调用uvm_fatal结束仿真。
UVM的这个特性在小型设计中体现不出优势,但在大型设计中非常有用。大型设计中真正仿真前的编译、优 化可能会花费一个多小时的时间。完成编译、优化后开始仿真,几秒钟后出现一个uvm_fatal就停止仿真。当修复这个问题后再次运行,发现又有一个uvm_fatal出现。如此反复可能会耗费大量时间。但如果将这些uvm_fatal替换为uvm_error,将所有类似的问题一次性暴露出来,一次性修复,这会极大缩减时间,提高效率。
*phase的跳转
前面所有表述中各个phase都是顺序执行的,前一个phase执行完才执行后一个。并没有介绍过当后一个phase执行后还可以再执行一次前面的phase。而“跳转”这个词则完全打破了这种观念:phase之间可以互相跳来跳去。
phase的跳转是比较高级的功能,举一个最简单的例子,实现main_phase到reset_phase的跳转:假如在验证平台中监测到reset_n信号为低电平, 则马上从main_phase跳转到reset_phase。 driver的代码如下:
task my_driver::reset_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("driver", "reset phase", UVM_LOW)
vif.data <= 8'b0;
vif.data <= 1'b0;
while(!vif.rst_n)
@(posedge vif.clk);
phase.drop_objection(this);
endtask
task my_driver::main_phase(uvm_phase phase);
`uvm_info("driver", "main phase", UVM_LOW)
fork
while(1) begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
seq_item_port.item_done();
end
begin
@(negedge vif.rst_n);
phase.jump(uvm_reset_phase::get());
end
join
endtask
reset_phase主要做一些清理工作并等待复位完成。main_phase中一旦监测到reset_n为低电平,则马上跳转到reset_phase。在top_tb中,控制复位信号代码如下:
initial begin
rst_n = 1'b0;
#1000;
rst_n = 1'b1;
#3000;
rst_n = 1'b0;
#3000;
rst_n = 1'b1;
end
在my_case中控制objection代码如下:
task my_case0::reset_phase(uvm_phase phase);
`uvm_info("case0", "reset_phase", UVM_LOW)
endtask
task my_case0::main_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("case0", "main_phase", UVM_LOW)
#10000;
phase.drop_objection(this);
endtask
运行上述的例子,则显示:
# UVM_INFO my_case0.sv(15) @ 0: uvm_test_top [case0] reset_phase
# UVM_INFO my_driver.sv(25) @ 0: uvm_test_top.env.i_agt.drv [driver] reset phase
# UVM_INFO my_case0.sv(20) @ 1100: uvm_test_top [case0] main_phase
# UVM_INFO my_driver.sv(34) @ 1100: uvm_test_top.env.i_agt.drv [driver] main phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1314) @ 4000: repo-rter[PH_JUMP] phase main
# UVM_WARNING @ 4000: main_objection [OBJTN_CLEAR] Object 'uvm_top' cleared
ob jection counts for main_objection
# UVM_INFO my_case0.sv(15) @ 4000: uvm_test_top [case0] reset_phase
# UVM_INFO my_driver.sv(25) @ 4000: uvm_test_top.env.i_agt.drv [driver] reset phase
# UVM_INFO my_case0.sv(20) @ 7100: uvm_test_top [case0] main_phase
# UVM_INFO my_driver.sv(34) @ 7100: uvm_test_top.env.i_agt.drv [driver] main phase
整个验证平台都从main_phase跳转到了reset_phase。运行结果中出现了一个UVM_WARNING。这是因为在my_driver中调用jump时,并没有把my_case0中提起的objection进行撤销。加入跳转后整个验证平台phase的运行图实现变为如图所示的形式: 灰色区域的phase在整个运行图中出现了两次。跳转中最难的地方在于跳转前后的清理和准备工作。如上面的运行结果中的警告信息就是因为没有及时对objection进行清理。对于scoreboard来说这个问题可能尤其严重。跳转前scoreboard的expect_queue中的数据应该清空,同时要容忍跳转后DUT可能输出一些异常数据。
在my_driver中使用了jump函数,它的原型是:function void uvm_phase::jump(uvm_phase phase);
jump函数的参数必须是一个uvm_phase类型的变量。在UVM中,这样的变量共有如下几个:
uvm_build_phase::get();
uvm_connect_phase::get();
uvm_end_of_elaboration_phase::get();
uvm_start_of_simulation_phase::get();
uvm_run_phase::get();
uvm_pre_reset_phase::get();
uvm_reset_phase::get();
uvm_post_reset_phase::get();
uvm_pre_configure_phase::get();
uvm_configure_phase::get();
uvm_post_configure_phase::get();
uvm_pre_main_phase::get();
uvm_main_phase::get();
uvm_post_main_phase::get();
uvm_pre_shutdown_phase::get();
uvm_shutdown_phase::get();
uvm_post_shutdown_phase::get();
uvm_extract_phase::get();
uvm_check_phase::get();
uvm_report_phase::get();
uvm_final_phase::get();
并非所有的phase都可以作为jump的参数。如果将上面代码jump的参数由uvm_reset_phase::get()替换为uvm_build_phase::get(),那么运行验证平台后会给出如下结果:UVM_FATAL /home/landy/uvm/uvm-1.1d/src/base/uvm_root.svh(922) @ 4000: reporte r [RUNPHSTIME] The run ph
所以往前跳转到从build到start_of_simulation的function phase是不可行的。如果把参数替换为uvm_run_phase:: get()也是不可行的:UVM会提示run_phase不是main_phase的先驱phase或后继phase。因为run_phase是与12个动态运行的phase并行运行的,不存在任何先驱或后继的关系。
uvm_pre_reset_phase::get()后的所有phase都可以作为jump的参数。从main_phase跳转到reset_phase是向前跳转,这种向前跳转只能是main_phase前的动态运行phase中的一个。也可以向后跳转:如从main_phase跳转到shutdown_phase。在向后跳转中除了动态运行的phase外,还可以是函数phase,如可以从main_phase跳转到final_phase。
phase机制的必要性
Verilog中有非阻塞赋值和阻塞赋值,相对应的在仿真器中要实现分为NBA区域和Active区域,这样在不同区域做不同事情,可以避免因竞争关系导致的变量值不确定的情况。同样,验证平台是很复杂的,要搭建一个验证平台是一件相当繁杂的事情,要正确地掌握并理顺这些步骤是一个相当艰难的过程。
举一个最简单的例子,一个env下面会实例化agent、scoreboard、reference model等,agent下面又会有sequencer、driver、monitor。并且这些组件之间还有连接关系,如agent中monitor的输出要送给scoreboard或reference model,这种通信的前提是要先将reference model和scoreboard连接在一起。那么可以:
scoreboard = new;
reference_model = new;
reference_model.connect(scoreboard);
agent = new;
agent.driver = new;
agent.monitor = new;
agent.monitor.connect(scoreboard);
最后一句话一定要放在最后写,因为连接的前提是所有组件已经实例化。reference_model.connect(scoreboard)的要求则没有那么高,只需要在上述代码中reference_model=new之后任何一个地方编写即可。可以看出代码的书写顺序会影响代码的实现。若要将代码顺序的影响降低到最低,可以按照如下方式编写:
scoreboard = new;
reference_model = new;
agent = new;
agent.driver = new;
agent.monitor = new;
reference_model.connect(scoreboard);
agent.monitor.connect(scoreboard);
只要将连接语句放在最后两行写就没有关系了。UVM采用了这种方法,将前面实例化的部分都放在build_phase来做,而连接关系放在connect_phase来做,这就是phase最初始的来源。
在不同时间做不同事情是UVM中phase的设计哲学。但仅仅划分成phase是不够的,phase的自动执行功能才极大方便了用户。上面代码当new语句执行完成后,后面的connect语句肯定就会自动执行。现引入phase的概念,将前面new的部分包裹进build_phase里面,把后面的connect语句包裹进connect_phase里面,很自然的,当build_phase执行结束就应该自动执行connect_phase。
phase的引入在很大程度上解决了因代码顺序杂乱可能会引发的问题。遵循UVM的代码顺序划分原则(如build_phase做实例化工作,connect_phase做连接工作等),可以在很大程度上减少验证平台开发者的工作量, 使其从一部分杂乱的工作中解脱出来。
phase的调试
UVM的phase机制如此复杂,如果碰到问题后每次都使用uvm_info在每个phase打印不同的信息显然是不能满足要求的。UVM提供命令行参数UVM_PHASE_TRACE对phase机制进行调试,其使用方式为:
<sim command> +UVM_PHASE_TRACE
这个命令的输出非常直观,下面列出了部分输出信息:
超时退出
验证平台运行时,有时测试用例会出现挂起(hang up)的情况。在这种状态下仿真时间一直向前走,driver或者monitor并没有发出或收到transaction,也没有UVM_ERROR出现。一个测试用例的运行时间是可以预计的,如果超出了这个时间,那么通常就是出错了。在UVM中通过uvm_root的set_timeout函数可以设置超时时间:
function void base_test::build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create("env", this);
uvm_top.set_timeout(500ns, 0);
endfunction
set_timeout函数有两个参数:要设置的时间和此设置是否可以被其后的其他set_timeout语句覆盖。代码将超时的时间定为500ns,500ns时测试用例还没有运行完毕,则会给出一条uvm_fatal的提示信息并退出仿真。默认的超时退出时间是9200s,是通过宏UVM_DEFAULT_TIMEOUT指定:`define UVM_DEFAULT_TIMEOUT 9200s
除了可以在代码中设置超时退出时间外,还可以在命令行中设置:其中timeout是要设置的时间,overridable表示能否被覆盖,其值可以是YES或NO。如将超时退出时间设置为300ns且可被覆盖,代码如下:
<sim command> +UVM_TIMEOUT=<timeout>,<overridable>
<sim command> +UVM_TIMEOUT="300ns, YES"
|