SV学习(4)——数据作用域和类型转换 $cast()
SV学习(4)
1. 数据作用域
在一个静态任务、函数或者块内声明的变量默认情况下具有静态的生命周期并具有本地的作用范围。
Verilog允许将函数和任务声明成自动的(automatic),这使得任务或函数内的所有存储空间都是自动的。
SV允许一个静态的任务或函数内的特定数据被显式地声明成自动地(automatic)。声明成自动的变量具有调用块内的生命周期,并且在每次进入调用或者块内的时候进行初始化。
SV也允许变量被显式地声明成静态的。在一个自动任务、函数或者块内声明的静态数据具有静态的声明周期,并且对于块来说具有本地的作用范围。
// static_auto_example
module test_static_aito ( );
int st0; // static
initial begin
int st1; // static
automatic int auto1; // automatic
end
task automatic t1 ( );
int auto2; // automatic
static int auto3; // automatic
endtask
endmodule
2. 数据类型转换
Verilog是一种弱类型语言,允许一个数据类型的值赋值给另一个数据类型的变量或者网线。赋值时,按照Verilog标准中定义的规则进行数据类型的变换;使用系统函数 &signed 和 $unsigned 实现有符号数和无符号数之间的转换。
SV增加了强制数据类型转换的能力。强制类型转换不同于在赋值时自动转变;使用强制类型转换,不用赋值,在一个表达式内,一盒数值就可以转换成一个新的类型。
2.1. 静态类型转换
SV加入了一个强制类型转换操作符( ‘ )来改变一个表达式的数据类型:
<type> '(<expression>);
需要进行强制类型转换的表达式必须包含在圆括号内,或者必须包含在串联或复制花括号内,
int '(2.0 * 3.0);
shortint '{8'hFA, 8'hCE};
如果将一个正的十进制数作为数据类型,那么这意味着需要改变数据的位宽,
16 '(2)
数据的符号也可以改变,
bit [ 7: 0] x;
signed '(x);
如果强制类型转化中的表达式需要改变位宽或符号,那么该表达式必须具有整型值。当改变位宽的时候,符号不会发生变化。当改变符号的时候,位宽不会发生变化。
注意:静态转换操作不对转换值进行检查
2.2. 动态类型转换
上面说的静态类型转换是一种编译时进行的转换,转换操作总会运行,而不会检查结果的有效性。SV还提供了一个新的系统函数 $cast ,这是动态的,在仿真运行时进行数据类型的检查, $cast 可以作为任务或函数来调用。 $cast 语法如下,
function int $cast (singular dest_var, singular source_exp);
或
task $cast (singular dest_var, singular source_exp);
dest_var时目标变量,source_exp是源变量的表达式。
将 $cast 作为任务还是函数调用,确定了无效赋值是如何处理的。当作为任务调用时, $cast 试图将源表达式赋值给目的变量;如果赋值时无效的,会出现运行时间错误,并且目的变量保持不变。当作为函数调用时, $cast 试图将源表达式赋值给目的变量,如果赋值成功则返回1;如果赋值失败, $cast不会进行赋值操作而是返回0。 当作为函数调用时,不会出现运行时错误,并且目标变量保持不变。
需要注意:$cast执行的时仿真运行时检查。除了检查目的变量和源表达式是否为单一类型外,编译器不会进行类型检查。
typedef enum (red, green, blue, yellow, white, black) Colors;
Colors co1;
$cast (co1, 2+3);
这个例子将表达式(5, black)赋值给枚举类型。如果不使用 $cast 或者后面提到的静态编译时强制类型转换,这种类型的赋值是无效的。用下面的例子就可以显示如何使用 $cast 检查一个赋值操作是否成功,
if (!$cast(col, 2+8)) // 10:无效的强制类型转换
$display ("Error in cast");
作为前一种选择,前一个例子还可以使用静态强制类型转换来进行类型转换,例如,
col = Colors'(2+3);
然而,这是一种编译时的强制类型转换,也就是说,在静态强制类型转换操作时总是运行的,即使表达式不在枚举值合法的范围内,静态强制转换也不会提供错误检查或警告。
这两种类型的强制类型转换为用户提供了完全的控制能力。如果用户知道将某个表达式赋值给一个枚举变量是安全的,那么就可以使用静态强制类型转换。如果用户需要检查表达式是否位于枚举值范围内,那么用户没有必要手写一个冗长的 case 语句,通过使用 $cast() 函数,编译器自动提供了这种功能。 通过这两种类型的强制类型转换,用户能够控制时间和安全性之间的平衡。
总结:
关于 $cast() 系统函数,它的作用了解了,在进行数据类型转换的时候它比使用( ’ )操作符的静态类型转换更加安全,它不管转换合不合理都执行,即使数据都越界了,它还是要赋值,但是 $cast() 作为内置函数,转换失败或者成功会有0 / 1返回值,如果失败就不进行赋值操作,通过返回值就能知道这样的转换是不是不好的。
【使用modelsim进行编译,对 $cast() 系统函数的使用会有报错:near " $cast": syntax error, unexpected SYSTEM_IDENTIFIER,不太理解为啥呢么】
问题解决了,因为只记得写示例,忘了把代码放进initial块了,顺便把两种转换的例子写上
// cast_example
module test_cast ();
typedef enum {red, green, blue, yellow, white, black} Colors;
Colors col;
initial begin
// within the enum
$cast (col, 2+1);
$display (col);
col = Colors'(2+3);
$display (col);
// beyond the enum
$cast (col, 7);
$display (col);
col = Colors'(8);
$display (col);
if (!$cast(col, 2+8)) // 10:无效的强制类型转换
$display ("Error in cast");
end
int a = 2;
real b;
initial begin
b = real'(a); // ERR
$display (a);
$display (b);
end
endmodule
对比打印结果可以看到,’ 不管赋的值在不在枚举范围内,都会强制赋值,$cast当违规赋值时,会对应报错,并且取消当前赋值任务
module test ( );
typedef enum bit[1:0] {RED=0,BLUE,GREEN} COLOR_E; // 枚举值范围{0, 1, 2}
COLOR_E color,c2;
int c;
initial begin
color = BLUE; // 赋值一个已知的合法的值
c = color; // 将枚举变量赋值给int,此时为1
c = c + 1; // int型变量加1
if(!$cast(color, c)) // 将整型显示转换回枚举类型,如果越界会报错
$display("cast failed for c=%0d",c); // c的值此时为2
$display("Color is %0d / %s", color, color.name());
c = c + 1; // c的值为3,对于枚举类型数据越界了
c2 = COLOR_E'(c); // 不做类型检查,下句c2.name()由于越界而打印不出
$display("c2 is %0d / $s",c2,c2.name()); // 打印:c2 is 3/
end
endmodule
2.3. 流操作符
如果源变量和目标变量的比特位分布完全相同,例如整数和枚举类型,那么它们之间可以直接互相赋值。如果比特位分布不同,例如字节数组和字数组,则需要使用流操作符对比特分布重新安排。
流操作符 << 和 >> 用在赋值表达式的右边,后面带表达式、结构或数组。流操作符是于把其后的数据打包成一个比特流。操作符 >> 把数据从左至右变成流,操作符 << 把数据从右至左变成流。也可以指定一个片段宽度,把源数据按照这个宽度分段以后再变成流。
不能将比特流结果直接赋值给非合并数组,而应该再赋值表达式的左边使用流操作符把比特流拆分到非合并数组中。
// flow_operator
module flow_operator ;
int h;
bit [ 7: 0] b;
bit [ 7: 0] g[4]; // unpacked类型,需要用%p打印
bit [ 7: 0] j[4] = '{8'ha, 8'hb, 8'hc, 8'hd};
bit [ 7: 0] q, r, s, t;
initial begin
h = {>>{j}}; // 0a0b0c0d 把数组打包成整型
$display ("h = %h", h);
h = {<<{j}}; // b030d050 位倒序
$display ("h = %h", h);
h = {<<byte{j}}; // 0d0c0b0a 字节倒序
$display ("h = %h", h);
g = {<<byte{j}}; // 0d, 0c, 0b, 0a 拆分成数组
$display ("g = %h %h %h %h", g[0], g[1], g[2], g[3]);
b = {<<{8'b0011_0101}}; // 1010_1100 位倒序
$display ("b = %b", b);
b = {<<4{8'b0011_0101}}; // 0101_0011 半字节倒序
$display ("b = %b", b);
{>>{q, r, s, t}} = j; // 把j分散到四个字节变量里
$display ("q = %h, r = %h, s = %h, t = %h", q, r, s, t);
h = {>>{t, s, r, q}}; // 把字节集中到h里
$display ("h = %h", h);
end
endmodule
如果需要打包或拆分数组,可以用流操作符来完成具有不同尺寸元素的数组间的转换。例如,将字节数组转换成字数组。对于定宽数组、动态数组和队列都可以这样。
下例是队列之间的转换,这种转换同样也适用于动态数组,数组元素会根据需要自动分配。
// flow_queue
module flow_queue ;
bit [15: 0] wq[$] = {16'h1234, 16'h5678};
bit [ 7: 0] bq[$];
initial begin
// 把字数组转换成字节数组
bq = {>>{wq}}; // 12 34 56 78
foreach (bq[i]) $display ("bq[%2d] = %h", i,bq[i]);
// 把字节数组转换成字数组
bq = {8'h98, 8'h76, 8'h54, 8'h32};
wq = {>>{bq}}; // 9876 5432
foreach (wq[i]) $display ("%h", wq[i]);
end
endmodule
使用流操作符可以实现在结构和数组之间的转换。
// flow_struct2array
module flow_struct2array ;
typedef struct {
int a;
byte b;
shortint c;
int d;
} my_struct_s;
my_struct_s st = '{ 32'haaaa_aaaa,
8'hbb,
16'hcccc,
32'hdddd_dddd};
byte b[ ];
initial begin
// 结构体转换成字节数组
b = {>>{st}}; //
foreach (b[i]) $display ("b[%2d] = %h", i,b[i]);
// 字节数组转换成结构体
b = '{8'h11, 8'h22, 8'h33, 8'h44,
8'h55,
8'h66, 8'h77,
8'h88, 8'h99, 8'haa, 8'hbb};
st = {>>{b}};
$display ("st.a = %h, st.b= %h, st.c = %h, st.d = %h", st.a, st.b, st.c, st.d);
end
endmodule