task形参类型 inout 和 ref 的区别
原文链接 https://stackoverflow.com/q/31017629/13685812
通常来说,task或者function的形参声明为input时,input类型的形参仅仅是得到了一份拷贝;
当task或者function的形参声明为output时,output类型的形参会在return的时候将此形参的拷贝复制给接收对象。
而inout类型的形参,则会在被调用时得到一份拷贝,并在return的时候将形参的拷贝复制给接收对象。
==》 以上都是涉及到把形参拿过来拷贝。
对于ref类型的形参,则传入的是引用,而不是一份拷贝。因此,ref类型的形参,具有更严格的数据类型要求。由于传入的是引用,当task或者function对这个参数进行修改时,外部是可见的。
特别注意:
对于一个需要消耗时间的task来讲,inout和ref的形参,具有很大的区别。在task运行期间,ref类型的参数,外部一直是可见,并且外部对这个引用所指向的内存空间的值的修改,task也是可见的。而对于inout类型的形参,task中仅仅是得到的一份拷贝,因此,在task运行期间,尽管这个参数对应的外部值发生变化,task中也是不可见的。当然,task中对这个值进行修改时,外部也是不可见的。当task结束时,才会将这个参数的拷贝传递给外部,这时,外部才能得到这个值。
module ref_inout;
logic [3:0] a,b;
task automatic mytask(inout logic [3:0] arg1, ref logic [3:0] arg2);
#0 $display("%m %t arg1 %h arg2 %h", $time, arg1, arg2);
#5 $display("%m %t arg1 %h arg2 %h", $time, arg1, arg2);
#0 arg1 = 1; arg2 = 2; // task 中途改了,inout参数是看不到的。但是ref参数却可以实时看到。
#5 $display("%m %t arg1 %h arg2 %h", $time, arg1, arg2);
endtask
initial begin
#0 mytask(a,b);
end
initial begin
a = 'z; b = 'z;
#2
a = 0; b = 0;
#5 $display("%m %t arg1 %h arg2 %h", $time, a, b);
#5 $display("%m %t arg1 %h arg2 %h", $time, a, b);
end
endmodule
/* 打印
//======================================================
inout ref
# ref_inout.mytask 0 arg1 z arg2 z
# ref_inout.mytask 5 arg1 z arg2 0
# ref_inout 7 arg1 0 arg2 2 --> 7ns 外部打印是看不出inout参数变化的,但是却可以实时看到ref传进去的参数变化。
# ref_inout.mytask 10 arg1 1 arg2 2 --> inout 只有当task结束了,才可以看到其变化。
# ref_inout 12 arg1 1 arg2 2
//======================================================
*/
上例可以清楚看到inout 和 ref 的区别:
1,在0ns,启动两个initial块;初始化a b为z;
2,在0ns,调用my_task;line4打印 arg1 =z arg2 =z;
3,在1ns,对a b分别赋值为0;
4,在5ns,打印arg1 =z arg2 =0;(b的值此时为0,准确的说,在2ns时,arg2已经可见b的值0)
5,在6ns,打印a = 0,b=1;(arg2为1,对外部可见,故B为1。准确的说,在6ns时,使b已得到值1)
6,在10ns,打印arg1 =1 arg2 =1;(task退出,arg1的拷贝传递给了a)
7,在12ns,打印a = 1,b=1;(arg1为1,传递给a)
==》 可以看到,即使task的参数 中途改了,inout参数只能看到进去之前和出去之后的值。但是ref参数却可以实时看到。当task出现耗时的时候,差异特别明显。
//=================================
还有一个需要注意:
需要特别注意的一点是,如果形参是一个class类型的,那么这时候传进来的是一个句柄。也就是说,task中对这个class进行修改时,其实是在修改这个句柄所指向的对象,那么外部也是看见的,这可能并不是所期望的,很容易与ref混淆,(我明明没有申明ref类型,为什么我改的task内部,却还是会影响到外部?),应当特别小心。同时需要说明,如果想要在task或者function中,对类句柄进行修改(如new一块新的内存空间),那么,应该把这个形参声明为ref。
class reg_trans;
xxxx;
endclass
task reg_write(input reg_trans t); //这里传进来的是input,但是下面t.data = xxx;竟然可以传出去!!! ==> 因为传进来的形参是句柄!!!注意,是句柄,你直接去修改对象里的值了。
@(posedge intf.clk iff intf.rstn);
case(t.cmd)
`READ: begin
intf.drv_ck.cmd_addr <= t.addr;
intf.drv_ck.cmd <= t.cmd;
repeat(2) @(negedge intf.clk); //第一个下降沿在当前拍,第2个下降沿在下一拍数据中间
t.data = intf.cmd_data_s2m; //这个是直接改t句柄指向的对象中的数值。
end
`IDLE: begin
this.reg_idle();
end
default: $error("command %b is illegal", t.cmd);
endcase
$display("%0t reg driver [%s] sent addr %2x, cmd %2b, data %8x", $time, name, t.addr, t.cmd, t.data);
endtask
//=================================
最后,如果需要传递一个特别大的参数给task或者function,使用ref类型是一个很好的选择,可以节省内存资源。
为什么?
因为只有ref是直接引用,其他的都是拷贝后使用。