verilog中的位宽一般都是确定的,大部分是根据表达式左边的变量位宽对表达式右边的数据进行截断(右边变量位数多)或者补零(右边变量位数少),但是verilog有一个隐藏的位宽问题,之前一直没有注意过,直到最近在读《verilog编程艺术》的时候注意到这个问题,刚好最近遇到了这个问题导致的错误。
这就是自决定表达式,不要小看这个自决定,这个词的意思就是表达式的位宽可能看编译器的心情,而有可能和设计人员的想法背离。首先来看这样一个问题。
`timescale 1ns/1ns
module test(
);
reg [1023 :0] out;
reg [31 :0] out_div_mult;
reg [31 :0] out_div_shif;
reg [31 :0] out_div1;
reg [31 :0] out_div2;
reg [31 :0] out_div3;
reg [31 :0] out_div4;
reg [31 :0] out_div5;
reg [31 :0] out_div6;
reg [6 :0] id;
wire [11 :0] id_32tiems1;
wire [11 :0] id_32tiems2;
initial
begin
out = 1;
id = 1;
#20
out_div1 <= out[( id )<<5+: 32] ;
out_div2 <= out[( id[4:0] )<<5+: 32] ;
out_div3 <= out[{2'b0,(id[4:0])}<<5+: 32] ;
out_div4 <= out[( id+ 8'b0 )<<5+: 32] ;
out_div5 <= out[((id[4:0])*32)+: 32] ;
out_div6 <= out[((id[4:0])*5'd31+id[4:0])+: 32] ;
out_div_mult <= out[id_32tiems1+: 32] ;
out_div_shif <= out[id_32tiems2+: 32] ;
end
assign id_32tiems1 = (id[4:0])<<5 ;
assign id_32tiems2 = (id[4:0])*32 ;
/*iverilog */
initial
begin
$dumpfile("wave.vcd"); //generate wave file named "wave.vcd""
$dumpvars(0); //dump the wave of all signals
#40 $finish;
end
/*iverilog */
endmodule
第一眼看起来,所有的8个结果应该是相同的,但仿真波形如下
可以看到有两个结果与其他结果不同,这是因为:
id的位数定义为7位,out_div_1用了7位,所以7位结果左移5位没有归零,而是成为7'b010_0000;但是out_div_2的代码里只用了低5位, 五位数左移五位会成为5'b0_0000;
这个问题解释了,那么另一个问题又出现了,为什么以下两行结果也不同呢?理论上左移5位和x32的结果是一样的。
out_div2 <= out[( id[4:0] )<<5+: 32] ;
out_div5 <= out[((id[4:0])*32)+: 32] ;
这就涉及到自决定表达式位宽规则了
从上图可以看到,移位运算的中间结果位宽取决于被移位的数,在上面两行代码的第一行中,中间结果位宽是5(最大值是31),移位结果为零。
但是,乘法的位宽取决于乘数和被乘数两者位宽的最大值,刚好verilog对未指定位宽的数默认为32位,因此在上面两行代码的第一行中,中间结果位宽是32(最大值是2^32),乘法结果是32。
out_div2 <= out[( id[4:0] )<<5+: 32] ;
out_div6 <= out[((id[4:0])*5'd31+id[4:0])+: 32] ;
这两个计算结果就是完全一样的, 因为乘法指定了位数5,所以中间结果都是5位的,中间算出来都是0,out_div的结果都是低32位,都是1。
所以可以看出来,不注意位宽规则,最后可能算出全然不同的结果,这一点大家可以查看《verilog编程艺术》进一步学习。
下附自动化仿真代码(保存改为.bat双击运行):
echo "start compile"
iverilog -g2012 -o wave.vvp test.v
echo "compile completed"
vvp wave.vvp
echo "open wave file with gtkwave"
gtkwave wave.vcd
pause