任务、函数以及void函数
任务和函数之间有很明显的区别,其中最重要的一点是,任务可以消耗时间而函数不能。verilog中的函数必须有返回值。
子程序参数
高级参数类型
verilog中对参数处理方式很简单,子程序开头把input和inout的值复制给本地变量,在子程序退出时则复制output和inout的值。
在SV中,参数传递方式可以指定为引用而不是复制。ref
1.input与output
本质:当你把一个变量传给input参数时,仿真器会在内存中复制(Copy)一份一模一样的数据给子程序。子程序在里面怎么折腾这个“副本”,外部的那个原变量都毫发无损。只有当子程序执行完返回时,如果是output,才会把最终结果再Copy回去。
痛点:如果你传递的是一个超大的数组(比如 1024 位的包数据),每次调用函数都要在内存里 Copy 一遍,仿真器性能会瞬间暴跌。
2.ref(引用传递(指针))
本质: ref不会复制任何数据。它传递的是变量在内存中的地址。
效果:子程序直接对外部的那个原始变量进行操作。没有任何内存复制开销,速度极快。而且子程序内部对该变量的任何修改,外部会实时看到,不需要等到子程序结束。
小问题
1 保护数据不被篡改
const ref
ref传递的是地址,子程序内部如果一不小心把这个传入的数据改了,外部的数据也就被污染了。这在验证中非常危险。 所以如果不仅想提高仿真性能,又不想子程序修改这个数组,必须加上const修饰符。
2task和function都可以用ref吗?它们有什么区别?
task: 可以消耗时间(#10 , @(posedge clk)等时序控制),可以使用ref
function:不能消耗时间(在0仿真时刻内返回) 也可以使用ref
注意:如果在function中使用了ref ,那么这个被引用的变量不能在其他地方被并发的进程(fork join)修改,否则就违反了function不能消耗时间的原则。
3 automatic 动态生命周期
为什么在 SV中使用ref传递参数时,子程序通常必须声明为automatic?
默认情况下,verilog的子程序是静态的,即内存空间是固定的。在SV验证环境中,为了支持递归调用或并发调用(多个transaction同时发送),子程序必须是动态的。 ref参数要求其指向的变量生命周期也是动态分配的,否则多处并发调用会指向同一个静态内存地址,导致数据混乱。
练习:test_sub3.sv
上面的代码会报错, 因为 const ref 只读不能修改。
注释掉 const ref 的函数和initial test3.编译。
顺便说一下
形参和实参的概念
函数print_by_value(input big_data_t data);这里data只是一个临时的占位符。它负责在函数内部规定好逻辑。 实参(真正的数据),当在initial块里声明了my_packet = xxxx;,并调用print_by_value(my_packet);
此时实参的值会被复制到data中。
运行结果:
test4.sv
练习 1 打印出的是 0xaaaa_aaaa,练习 2打印出的是 0x5555_5555
易错点
用名称绑定
// 推荐写法:清晰显式,绝不会因为逗号数量数错而出错 do_config(.new_cfg(my_config_reg), .status(result));如果把output挪到最前面
task automatic do_config(output status_e status, seed = 10, ref bit [31:0] new_cfg);此时seed没有显示指定方向,它会继承前一个参数status的output方向。可能因为无法把默认值赋给输出端口而触发编译警告。
不要依赖隐式方向继承