第一式:开启多个线程
SV——线程及线程间的通信(一) · 大专栏 (dazhuanlan.com)https://www.dazhuanlan.com/idadai/topics/1523517
initial begin
语句1;
#10;
fork
语句2;
语句3;
join_none
语句4;
语句5;
end
// #0 语句1
// #10 语句2,语句3,语句4并发执行
// #10 语句4执行完之后才执行语句5。4执行完之后,即使2,3没执行完,也会接着执行5,因为fork块内语句与之后的语句是并行的,不会阻塞之后的语句
// 来源:https://www.dazhuanlan.com/idadai/topics/1523517
如果循环启动线程,需要用 automatic 关键字,来自动创建变量,这样为每个线程单独分配内存。
initial begin
for(int i=0;i<3;i++)
fork
automatic int k=i;//注意k的声明
$display(k); // 注意这里用的式k
join_none
end
wait fork 来等待线程都执行完。
需求:在某一个循环里面,根据条件来决定是否开启fork join_none;但是可能需要把对应的条件打印出来。(注意automatic 的使用)
foreach (mab_is_1[i]) begin
if(mab_is_1[i] == 1 ) begin
int search_range_window=0;
fork
automatic int interrupt_qid = i;
do begin
if ( ???) begin
`uvm_info(get_type_name(), $sformatf("Congratulation: interrupt compare is success!(Qid=%0d)",interrupt_qid), UVM_LOW);
break;
end
@(posedge v_xxx_intf.clk);
search_range_window++;
while(search_range_window <= 10) ;
join_none
end
end
线程间通信
测试平台中所有的线程需要传递数据,可能多个线程同时要访问同一个数据,测试平台的代码是使得同一时间只有一个线程能访问。
@e1是边沿敏感的阻塞语句;wait(e1.triggered()) 是电平敏感的。
event done[N];// N是发生器数目 int cnt; initial begin foreach (gen[i])begin gen[i]=new(done[i]); gen[i].run(); end foreach(gen[i]) fork automatic int k=i; begin //begin块 wait(done[k].triggered()); cnt++; //触发一个,计数加一。 end join_none wait(cnt==N); //等待计数到N。说明所有的fork执行完毕,所有的事件都触发 end
第二式:callback 机制(三步骤)
回调的概念:(参考公众号:芯片学堂)
回调函数概念:在上面的类比中,“制作身份证”是一个首先被执行的主函数(main function),填写信息表中取送方式一栏就是向主函数注册回调函数(register a callback function),而“邮寄”和“本人来取”其实就是被注册的回调函数(callback function),主函数在结束之前会调用回调函数,这个调用的地方叫做回调函数的hook。回调函数的接口通常是预先定义好的,比如CC总不能给主函数注册一个“由民警配送到我家”的回调函数吧。
SV的callback步骤
1、在需要回调的地方预留入口;(类似pre_callback、post_callback)
2、定义回调的类及其内部的回调函数;(virtual class;virtual task;)
3、例化和添加回调类的实例;(回调类 xx=new(); 添加到1的地方)
// 第一步:添加回调函数和hook
typedef enum {OKAY, EXOKAY, SLVERR, DECERR} resp_type;
class slave_driver;
resp_type resp;
// 这个就是回调函数,一般声明称virtual,方便override
// 在使用的时候是直接继承该类并重载定义回调函数
virtual task update_resp;
endtask
task send_response;
std::randomize(resp) with {resp == OKAY;};
update_resp();
// 这行代码就是回调函数的hook
endtask
endclass
// 第二步:使用回调数
class err_inject extends slave_driver;
// 在子类中重新定义回调函数的实现
virtual task update resp;
$display("Injecting SLVERR");
resp = SLVERR;
endtask
endclass
// 第三步:使用回调数
program error_test;
slave env env;
err_inject err_driver;
initial begin
// 例化环境组件
env =new();
err driver =new();
// 用err_driver覆盖slave_driver
env.slv_driver = err_driver();
// 仿真开始
env.run();
end
endprogram
UVM的callback
参考白皮书P288的9.1.4小节
callback的开发者:
1、定义一个A类:class A extend uvm_callback;回调函数要添加virtual(以便使用者重载)
如:virtual task pre_tran(xxx123 m_driver, ref transaction tr)
2、声明yigeA_pool类:typedef uvm_callback #(xxx123,A) A_pool;
第一个参数:xxx123:这个A_pool将会被xxx123这个类使用;
第二个参数:A:这是一个A类型的pool;
3、在要预留callback函数|任务接口的类中调用uvm_register_cb宏:
class xxx123 extend uvm_driver #(transaction);
// 在主函数中注册回调函数
`uvm_register_cb(xxx123,A)
4、在调用callback函数或任务接口的函数或任务中,使用宏uvm_do_callback来调用回调函数;
task xxx123::main_phase(uvm_phase phase)
//.........
`uvm_do_callback(xxx123,A,pre_tran(parameters))
//........
endtask
使用者:需要实现具体的callback函数
1、从A中派生一个类ex_A,并重载回调函数pre_tran;
2、在测试用例这一层的connect_phase中(要求就是在上面步骤4中使用回调函数之前的phase即可):把ex_A类实例化并加入到A_pool中。(A_pool::add(xx.a,my_cb))参考白皮书P290 代码清单9-13
下面是:芯片学堂给出的一个实例
// 第一步:添加和引用回调函数,回调基类定义和hook分开
class driver_callback extends uvm_callback;
uvm object utils(driver callback)
function new(string name = "driver_callback");
super.new(name);
endfunction
// 定义回调函数
virtual task pre drive;
endtask
virtual task post_drive;
endtask
endclass
// 回调函数在使用之前,需要先被注册到回调hook的object当中
//注册的时候可以使用UVM提供的宏来完成,并且在object中加hook
class driver extends uvm_component;
`uvm_component_utils(driver)
// 这里就是对回调函数的注册,将回调基类和驱动类型关联起来
`uvm_register_cb(driver, driver_callback)
function new(string name, uvm_component parent);
super.new(name,parent);
endfunction
task run_phase(uvm phase phase);
//这里就是对两个回调函数的hook,实际上是从回调函数资源池里面找函数执行
// 函数索引是驱动类型+回调类型+回调函数名
`uvm_do_callbacks(driver, driver_callback, pre_drive());
drive_pkt();
`uvm_do_callbacks(driver, driver_callback, post_drive());
endtask
task drive pkt();
`uvm_info("DRIVER", "Inside drive_pkt method", UVM_LOW);
endtask
endclass
// 第二步:实现回调函数
// 在上面SystemVerilog的例子中,实现回调函数是要继承整个driver
// 在UVM这个例子中,回调函数的声明和hook是分开的,因此在实现回调函数的时候
// 只要继承callback类就可以了
class user callback extends driver callback:
`uvm_object utils(user callback)
function new(string name = "user_callback")
super.new(name);
endfunction
task pre_drive;
uvm_info("USER_CALLBACK", "Inside pre_drive method", UVM_LOW);
endtask
task post drive;
uvm_info( "USER_CALLBACK", "Inside post_drive method", UVM_LOW)
endtask
endclass
// 第三步:使用回调函数
// 在UVM这个例子中,因为回调函数跟hook是分开的,在使用的时候也需要单独例化
//在SystemVerilog实现的例子中,则是只要例化driver就可以,这是个区别
class user callback_test extends basic_test;
// 声明用户实现后的回调类
user_callback callback_1;
uvm_component_utils(user_callback_test)
function new(string name = "user_callback_test", uvm_component parent);
super.new(name,parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// 回调类实例化
callback 1 = user callback::type id::create("callback 1", this);
// 给env的driver的回调函数资源池里添加回调对象
uvm callback #(driver, driver callback)::add(env.drv, callback_1);
endfunction
endclass
第三式:给函数传递参数和函数返回
第1个点:函数返回,task是不能声明类型的,只有function允许声明类型,如下图所示。
第2个点就是:
- function或task里面参数的方向如果没有显示声明的话,就是input;
- 如果第一个参数指明方向是output,而后面的参数没有指明方向的话,也会是output,沿用前面的方向属性;
- const ref 这个特性也会被后面的参数沿用,如果后面的参数没有指明方向属性的话,正确方式如下图;
第3点:函数返回可以用return提前结束函数的执行;参数output类型可以直接用;(外面声明一下,传给函数处理,再引用这个参数时,是已经处理后的值了)
第四式:随机化
1、调用randomize可以只随机部分变量;
2、多个随机约束冲突后,工具不会进行随机化,此时给出的值一般都是0;
解决方案1:关闭部分约束或调整约束;(xx.constraint_mode(0):参考绿皮书P157,例子6.38)
3、在约束里面使用foreach
4、随机化二维数组
上面截图来自iEEE手册