verilog数的表示和运算
1.数的表示
1.1 数制转换
十进制整数转换成其他进制数:“除基取余”:十进制整数不断除以转换进制基数,直至商为0。每除一次取一个余数,从低位排向高位。
十进制小数转换成其他进制数:乘基取整,直至ε,高位到低位;“乘基取整”:用转换进制的基数乘以小数部分,直至小数为0或达到转换精度要求的位数。每乘一次取一次整数,从最高位排到最低位。
若要转换的数既有整数又有小数时,整数、小数分别转换。
1.2 二-十进制码(BCD码)
用4位二进制编码表示十进制的0-9十个数码。对于一个多位的十进制数,需要有与十进制位数相同的几组BCD代码来表示。
1.3 ASCII码
美国标准信息交换码,采用7位二进制表示27=128个包括0-9,字母等可打印字符。
数字0-9的ASCII码范围为:011_0000-011_1001:48~57
大写字母A-Z的ASCII码范围为:100_0001-101_1010:65~90
小写字母a-z的ASCII码范围为:110_0001-111_1001:97~122
2.有符号数和无符号数
有符号数指的就是带有符号位的数据,其中最高位就是符号位(如果最高位为0,那么表示是正数,如果最高位为1,那么表示是负数);无符号数就是不带有符号位的数据。
考虑一个4位的整数4’b1011:
如果它是一个无符号数据,那么它表示的值为:$ 1\times 2^{3}+0\times 2^{2}+1\times 2^{1}+1\times 2^{0}=11$
如果它是一个有符号数,那么它表示的值为:$ -1\times 2^{3}+0\times 2^{2}+1\times 2^{1}+1\times 2^{0}=-5$
所以相同的二进制数把它定义为有符号数和无符号数表示的数值大小有可能是不同的。有符号数和无符号数转化为10进制表示的时候唯一的区别就是最高位的权重不同,由上例知,无符号数最高位的权重是 ,而有符号数最高位的权重是$−2^3$ 。
正因为有符号数和无符号数最高位的权重不同,所以他们所表示的数据范围也是不同的。比如,一个4位的无符号整数的数据范围为0~15,分别对应二进制4’b0000~4’b1111,而一个4位的有符号整数的数据范围为-8~7,分别对应二进制4’b1000~4’b0111.
扩展到一般情况,一个位宽为M的无符号整数的数据范围为$0\sim2^{M-1}$,而一个位宽为M的有符号整数的数据范围为$-2^{M-1}-1\sim 2^{M-1}-1$。
N位有符号正数X的补码与该正数的原码相同,如4位有符号数0011的补码还是0011,对应无符号数3.
N位负数X的补码所表示的无符号数对应$2^{N}-|X|$ ,如4位有符号数1011(-3)的补码是1101,对应无符号数$13=2^4−|−3|$.
2.1 有符号数
数据的量化以及截位处理中,一种比较精确的处理方式就是先对截位后的数据进行四舍五入(round),如果在四舍五入的过程中由于进位导致数据溢出,那么我们一般会对信号做饱和(saturation)处理。
饱和处理就是如果计算结果超出了要求的数据格式能存储的数据的最大值,那么就用最大值去表示这个数据,如果计算结果超出了要求的数据格式能存储的数据的最最小值,那么就用最小值去表示这个数据。
规定:如果一个有符号数的总位宽为32位(其中最高位为符号位),小数位宽为16位,那么这个有符号数的数据格式记为32Q16。依次类推,10Q8表示这个数是一个有符号数(最高位为符号位),且总位宽为10位,小数位宽为8位。16Q13表示这个数是一个有符号数(最高位为符号位),且总位宽为16位,小数位宽为13位。总而言之,如果定义一个数据为mQn(m和n均为正整数且m>n)格式,那么我们可以得到三个重要的信息:
1、mQn是一个有符号数,最高位为符号位
2、mQn数据的总位宽为m
3、mQn数据的小数位宽为n
在Verilog-1995中,integer数据类型为有符号类型,而reg和wire类型为无符号类型。而且integer大小固定,即为32位数据。在Verilog-2001中对符号运算进行了如下扩展。Reg和wire变量可以定义为有符号类型:
reg signed [63:0] data;
wire signed [7:0] vector;
input signed [31:0] a;
function signed [128:0] alu;
函数返回类型可以定义为有符号类型。
带有基数的整数也可以定义为有符号数,在基数符号前加入s符号。
16'hC501 //an unsigned 16-bit hex value 16'shC501 //a signed 16-bit hex value
操作数可以在无符号和有符号之间转变。通过系统函数signed和unsigned实现。
reg [63:0] a; //unsigned data type
always @(a) begin
result1 = a / 2; //unsigned arithmetic
result2 = $signed(a) / 2;//signed arithmetic
end
2.2 有符号整数的符号位扩展
如果把一个4位的有符号整数扩展成6位的有符号整数。假设一个4位的有符号整数为4’b0101,显然由于最高位为0,所以它是一个正数,如果要把它扩展成6位,那么只需要在最前面加2个0即可,扩展之后的结果为:6’b000101。
假设一个4位的有符号整数为4’b1011,显然由于最高位为1,所以它是一个负数,如果要把它扩展成6位,,前面不是添2个0,而是添2个1,扩展之后的结果为:6’b111011。为了确保数据扩位以后没有发生错误,这里做一个简单的验证:
$4'b1011=-1*2^{3}+0*2^{2}+1*2^{1}+1*2^{0}=-8+0+2+1=-5$
$6'b111011=-1*2^{5}+1*2^{4}+1*2^{3}+0*2^{2}+1*2^{1}+1*2^{0}==32+16+8+0+2+1=-5$
显然扩位以后数据大小并未发生变化。综上:对一个有符号整数进行扩位的时候为了保证数据大小不发生变化,扩位的时候应该添加的是符号位。
2.3 有符号小数
接下来研究一下有符号小数。前面已经规定了有符号小数的记法。
假设一个有符号小数为4’b1011,它的数据格式为4Q2,也就是说它的小数位为2位。那么看看这个数表示的10进制数是多少
$4′b10.11=−1∗2^{1}+0∗2^{0}+1∗2^{−1}+1∗2^{−2}=−2+0+0.5+0.25=−1.25$
显然,小数的计算方法实际上和整数的计算方法是一样的,只不过我们要根据小数点的位置来确定对应的权重。
下面看看有符号小数的数据范围。拿4Q2格式的数据来说,它的数据范围为$−2∽2−2^{−2}$,对应二进制4’b1000~4’b0111.扩展到一般情况,mQn格式的数据范围为:$−2^{M−N−1}∽2^{M−N−1}+1-2^{-N}$。
最后再来看看有符号小数的数据扩展。假设一个有符号小数为4’b1011,它的数据格式为4Q2,现在要把这个数据用6Q3格式的数据存储。显然需要把整数部分和小数部分分别扩一位,整数部分采用上一节提到的符号位扩展,小数部分则在最后面添一个0,扩展以后的结果为6’b110110,接下来仍然做一个验证:
$4′b10.11=−1∗2^{1}+0∗2^{0}+1∗2^{−1}+1∗2^{−2}=−2+0+0.5+0.25=−1.25$$6'b110.110=-1*2^{2}+1*2^{1}+0*2^{0}+1*2^{-1}+1*2-{2}+0*2^{-3}$
$=-4+2+0+0.5+0.25+0=-1.25$
显然,扩位以后数据大小并未发生变化。
总结:有符号小数进行扩位时整数部分进行符号位扩展,小数部分在末尾添0.
3.有符号数的和运算
3.1 两个有符号整数的和
- 有符号数均为补码表示
- 只有计算表达式右边有无符号数,整个计算式都按照无符号数规则运算
- 只有算式右边全为有符号数,运算才会自动补齐所需的bit数,n+n=n+1.n*n=2n
- verilog2001中用’s来特别声明有符号数,十进制的数都是有符号数
- $usigned()函数在高位补0
- $signed()函数会在高位补与符号位相同的bit
两个n bit数相加,得到n+1 bit结果,比如-2(3’sb110)+3(3’sb011)=1(4’sb0011)
- 先将被加数进行符号位扩展,再相加,结果丢掉进位。
代码如下,
//Code Example 1: Addition - Verilog 1995 module add_signed_1995 ( input [2:0] A, input [2:0] B, output [3:0] Sum ); assign Sum = {A[2],A} + {B[2],B}; endmodule // add_signed_1995
直接采用signed计算如下:
//Code Example 2: Addition - Verilog 2001 module add_signed_2001 ( input signed [2:0] A, input signed [2:0] B, output signed [3:0] Sum ); assign Sum = A + B; endmodule // add_signed_2001
如果是两个3bit有符号数+1bit进位。如果在verilog2001中直接用符号位拓展
sum=A+B+carry_in //整个计算式会转换成无符号计算,signed to unsigned conversion occurs sum=A+B+$signed(carry_in) //就会出现当carry_in=1时候拓展为4'b1111,这时候本来是加1,却变成了减1 sum = A + B + $signed({1'b0,carry_in}) //正确的做法
正确的做法是
// Code Example 3: Add with Carry - Verilog 1995 module add_carry_signed_1995 ( input [2:0] A,dsa input [2:0] B, input carry_in, output [3:0] Sum ); assign Sum = {A[2],A} + {B[2],B} + carry_in; endmodule //add_carry_signed_1995
直接采用signed的计算方法为
// Code Example 5: Add with Carry - Correct module add_carry_signed_final ( input signed [2:0] A, input signed [2:0] B, input carry_in, output signed [3:0] Sum ); assign Sum = A + B + $signed({1'b0,carry_in}); endmodule // add_carry_signed_final
两个有符号小数的和
两个有符号小数相加,为了保证和不溢出,首先应该把两个数据进行扩展使小数点对齐,然后把扩展后的数据继续进行一位的符号位扩展,这样相加的结果才能保证不溢出。
现在把5Q2的数据5’b100.01和4Q3的数据4’b1.011相加。
Step1、由于5Q2的数据小数位只有2位,而4Q3的数据小数点有3位,所以先把5Q2的数据5’b100.01扩位为6Q3的数据6’b100.010,使它和4Q3数据的小数点对齐
Step2、小数点对齐以后,然后把4Q3的数据4’b1.011进行符号位扩展成6Q3的数据6’b111.011
Step3、两个6Q3的数据相加,为了保证和不溢出,和应该用7Q3的数据来存储。所以需要先把两个6Q3的数据进行符号位扩展成7Q3的数据,然后相加,这样才能保证计算结果是完全正确的。
以上就是两个有符号数据相加需要做的一系列转化。下面思考为什么两个6Q3的数据相加必须用7Q3的数据才能准确的存储他们的和。 因为6Q3格式数据的数据范围为$-4\sim 4-2^{-3}$ ;那么两个6Q3格式的数据相加和的范围为$-8\sim 8-2^{-2}$;显然如果和仍然用6Q3来存一定会溢出,而7Q3格式数据的数据范围为$-8\sim 8-2^{-3}$ ,因此用7Q3格式的数据来存2个6Q3格式数据的和一定不会溢出。
结论:在用Verilog做加法运算时,两个加数一定要对齐小数点并做符号位扩展以后相加,和才能保证不溢出。
4.有符号数的积运算
4.1 两个有符号数的积
两个有符号数相乘,为了保证积不溢出,积的总数据位宽为两个有符号数的总位宽之和,积的小数数据位宽为两个有符号数的小数位宽之和。简单来说,两个4Q2数据相乘,要想保证积不溢出,积应该用8Q4格式来存。这是因为4Q2格式数据的范围为:$-2\sim 2-2^{-2}$,那么两个4Q2数据相乘积的范围为:$-4+2^{-1} \sim 4$,而8Q4格式的数据范围为:$-8\sim 8-2^{-4}$,一定能准确的存放两个4Q2格式数据的积。
结论: mQn和aQb数据相乘,积应该用(m+a)Q(n+b)格式的数据进行存储。
例如有符号数[3:0]a * [3:0]b. 其中a=-5,b=7。a用补码表示为1011,b用补码表示是0111,对于这个例子,乘法过程如下:
其中,b的符号位跟a相乘的时候需要注意,如果b的符号位是1,则b的符号位与a相乘的时候实际表示的是-1*a,所以需要将a的结果按位取反加一取反加一。上面的例子b的符号位是0,所以结果是1101_1101,补码是1100011 = -35。如果遇到符号位是1的情况,比如上面的右图a=-5,b=-3,可以看到上面最后一行的结果需要对a进行取反加一取反加一才正确,并且此时取反加一也包括a的符号位。
另外,还需要注意的是所有部分积都要补符号位补到乘法输出值的位数。
其实乘法器就是由加法组成,所以b中的每一位跟a做乘法(异或)之后把部分积累加时,仍然需要遵从加法的原则,扩展符号位直到达到输出位宽,然后再加。
所以有符号乘法跟无符号乘法的区别就在这,无符号乘法不需要考虑符号位扩展问题,而有符号乘法在累加部分积的时候需要做符号位扩展,并且还要考虑符号位参与乘法时的含义不同,也就是说符号位的0表示0,但1却表示-1,所以符号位的1做乘法就不是异或而是对所有位取反再加一了。
在verilog中,一般有符号乘法器的做法是先将补码输入都转成原码,再将符号位单独拿出来进行异或,然后其余部分当作无符号数乘起来,最后再对结果取补码转回原码结果。
4.2 signed有符号乘法
// Code Example 7: Signed Multiply - Verilog 2001
module mult_signed_2001 (
input signed [2:0] a,
input signed [2:0] b,
output signed [5:0] prod
);
assign prod = a*b;
endmodule
4.3 signed和unsigned 乘法
有符号a和无符号b乘法
prod = a*b; //整个运算变成无符号,-3(3'sb101)*2(3'b010)变成5(6'b000101)*2(6'b000010)=10(6'b001010) prod = a*$signed(b); //当乘数的MSB=1的时候会出错,-2(3'sb010)*7(3'b111)变成-2(6'sb000010)*-1(6'sb111111)=2(6'sb110010) prod = a*$signed({1'b0,b}); //正确做法
直接采用signed的计算方法为
// Code Example 11: Signed by Unsigned Multiply
module mult_signed_unsigned_2001 (
input signed [2:0] a,
input [2:0] b,
output signed [5:0] prod
);
assign prod = a*$signed({1'b0,b});
endmodule
signed移位
逻辑移位’>>’, ‘<<’会补零,算术移位’<<<’, ‘>>>’会补符号位
A = 8'sb10100011
A>>3; //8'b00010100
A>>>3; //8'b11110100
关于signed、有符号数、算数左移、算数右移、$signed()、$unsigned()的理解。
1、signed可以和reg和wire联合使用,用于定义有符号数。在代码中使用负的十进制数赋值给有符号数,在电路中是按该数值的补码形式存储的。
2、使用signed定义的类型,做加法或乘法时,对操作数扩位处理时高位补符号位;即负数补1,正数补0;不使用signed的无符号类型,高位默认补0
3、 $signed和$unsigned。首先明确这两个语句是可综合的。$signed(c)是一个function,将无符号数c转化为有符号数返回,不改变c的类型和内容。接上述代码历程:$unsigned同理。
4、算数右移>>>和逻辑右移。
对于无符号数,>>和>>>没有区别,都是按位右移,左侧补零。
有符号数的逻辑右移>>与无符号数一样,将所有位整体右移,左侧补零。
而有符号数的算数右移>>>,左侧扩位符号位,如右移n位,则左侧增加n个符号位,右侧删除n位,即进行除n运算。
5、算数左移。同样根据(有符号数signed和无符号数最根本的区别就是如何扩位,无符号数是添0,有符号数时添加符号位) 这句话进行理解,
在移位前数据a和移位后数据b,具有相同位数情况下,不需要扩位,即整体左移n位,右侧补。此时有符号数的算数左移<<<和有符号数的逻辑左移<<效果一致。
但是在移位前数据a和移位后数据thmp,不具有相同位数情况下,a进行MSB扩位,即为1_1100_0001,然后左移1位thmp=1_1000_0010,乘2运算。
6、总结
其一:被signed定义的数据在电路中是以补码形式存储并计算的。
其二:有符号数signed和无符号数,区别在于如何扩位,无符号数是MSB添0,有符号数MSB添加符号位。
正确的有符号和无符号运算举例
第一种正确的实现方式
localparam signed [8:0] MAX7= (1<<7)-1, MIN7= ~(1<<7)+1; //必须定义为有符号parameter,否则出错 reg [7:0]usigneda,usignedb; //均为8bit无符号数 reg [7:0] gain; //8bit 无符号数
//两个8bit无符号数的差为9bit有符号数,例如255-0=255,0-256=-256
wire signed[8:0] diff_ab = usigneda - usignedb;
//将差限幅到[-256,255]
wire signed[8 :0]diff_abclip = diff_ab < MIN7 ? MIN7 : diff_ab > MAX7 ? MAX7 :diff_ab;
wire [13:0]diffgain = diff_abclip * signed({1'd0,gain});//有符号数和无符号数相乘
但如果我们采用下面的无符号数的差diff_ab不定义为signed,用diff_ab_u,localparam也不定义为signed,用MAX7E和MIN7E表示,
localparam [8:0] MAX7E= (1<<7)-1, MIN7E= ~(1<<7)+1; wire [8 :0]diff_ab_u = usigneda - usignedb; wire [8 :0]diff_ab_u_clip = diff_ab_u < MIN7 ? MIN7 :
diff_ab_u > MAX7 ? MAX7 :diff_ab_u;
将得到错误的结果如下:利用vivado的有符号十进制显示MAX7E、MIN7E、以及diff_ab_u 以及diff_ab_u_clip 时,得到以下结果,显然diff_ab_u_clip 的结果不是我们想要的。
很显然上面的仿真波形中,将MAX7E、MIN7E、以及diff_ab_u 均以无符号数看待,将MIN7E以384看待,我们以vivado的无符号十进制数显示时便得到了以下结果,也不是我们想要的
第二种正确的实现方式
reg [7:0]usigneda,usignedb; //均为8bit无符号数
reg [7:0] gain; //8bit 无符号数
//两个8bit无符号数的差为9bit有符号数,例如255-0=255,0-256=-256
wire [8:0] diff_ab = usigneda - usignedb;
//将差限幅到[-256,255]
wire signed[8 :0]diff_ab = usigneda - usignedb;
wire signed[8 :0]diff_abclip = diff_ab < $signed(-9'd256) ? $signed(-9'd256) :
diff_ab > 9'd255 ? 9'd255 :diff_ab;
//或者下面的方式,此时要求两个参与比较的数的位宽一致,即diff_ab和-9'sd256均为9bit,若diff_ab为10bit,-9'sd256可以写作-10'sd256的形式.
wire signed[8 :0]diff_abclip1 = diff_ab < -9'sd256 ? -9'sd256 :
diff_ab > 9'sd255 ? 9'sd255 :diff_ab;
wire [16:0]diffgain = diff_abclip * $signed({1'd0,gain});
下面得不到正确的结果
wire signed[8 :0]diff_ab = usigneda - usignedb;
wire [7 :0]diff_abclip = $signed(diff_ab) < -8'sd255 ? -8'sd255 : $signed(diff_ab) > 8'sd255 ? 8'sd255 :diff_ab;
//有符号数和无符号数相乘,diff_abclip 未看做有符号数的形式
wire [13:0]diffgain = diff_abclip * {1'd0,gain};
有符号显示
无符号显示
显然是将diff_abclip 和gain均看作是有符号数参加运算了。
下面是利用display函数对有符号和无符号数仿真的结果:
module test/**/ ;
reg signed [7:0]a,b;
wire signed [8:0]sum1;
reg signed [8:0]thmp;
reg [7:0] c,d;
wire [8:0]sum2;
initial
begin
a = -8'd1;
b = 8'd2;
c = 8'b1000_0001;
d = 8'b0000_0010;
#10
$display("signed a =%b=%d",a,a);
$display("signed b =%b=%d",b,b);
$display("a+b =%b=%d",sum1,sum1);
$display("unsigned c =%b=%d",c,c);
$display("unsigned d =%b=%d",d,d);
$display("c+d =%b=%d",sum2,sum2);
$display("$unsigned(a)=%b=%d",$unsigned(a),$unsigned(a));
a=$signed(c);
b=$signed(d);
#10
$display("a+b =%b=%d",sum1,sum1);
#10
a=8'b1000_0010;
b=8'b1000_0010;
$display("a =1000_0010=%d",a);
$display("b =1000_0010=%d",b);
#10
a=a>>2;
b=b>>>1;
$display("a=a>>2 =%b=%d",a,a);
$display("b=b>>>1 =%b=%d",b,b);
#10
thmp=b<<<1;
$display("thmo=b<<<1 =%b=%d",thmp,thmp);
end
assign sum1 = a+b;
assign sum2 = c+d;
//adder_8 u1(sum1,sum2,a,b,c,d);
initial
begin
$vcdpluson;
end
结果如下
signed a =11111111= -1
signed b =00000010= 2
a+b =000000001= 1
unsigned c =10000001=129
unsigned d =00000010= 2
c+d =010000011=131
$unsigned(a)=11111111=255
a+b =110000011=-125
a =1000_0010=-126
b =1000_0010=-126
a=a>>2 =00100000= 32
b=b>>>1 =11000001= -63
thmo=b<<<1 =110000010=-126
4.4 注意事项
当想要进行有符号乘法时,我们想通过定义成signed让综合器选择有符号乘法器,此时我们需要把乘法器的两个输入和一个输出都定义成signed,哪怕是一个定义成signed,另一个用补码但未定义signed也不行。也可以把input port定义成signed。
5. 数据传递
将一个数或者计算结果A(有符号数或者无符号)赋值给另一个数B时,根据位宽不同有以下三种情况:
- 当A和B位宽相同的时候的直接赋值:
- 无论B是有符号或者无符号结果,只是简单的将A各个二进制位上的0或者1完全不变的赋值给B对应的位,并不会传递这个数是整数还是负数,是补码还是原码。如果定义了B为signed ,则将A的各个数据位传递给B,但是在赋值给B后,这个数据按照补码存储,符号位为1为负数,否则为正数,但如果未定义signed,则默认按照无符号数值结果存储。
- 对于长位宽直接赋值给短位宽即A的位宽大于B的位宽的A赋值于B的情况:
- 无论左操作数B、右操作数A是有符号数还是无符号数,都是直接截断高位。而左操作数B二进制所表示的实际十进制数据要看左操作数B是无符号数还是有符号数,如果左操作数是无符号数unsigned,还是将A的各个低数据位传递给B,对于B来说,直接转换成十进制即可,而如果B是有符号数,则将该数据看成2的补码转换成十进制数,对应B的值。
- 对于短位宽赋值给长位宽的情况,需要对高位进行位扩展,具体是扩展1还是扩展0,记住:完全依据右操作数A,具体如下:
- 1)右操作数是无符号数,则无论左操作数是什么类型,高位都扩展成0;
- 2)右操作数是有符号数,则要看右操作数的符号位,按照右操作数的符号位扩展,符号位是1就扩展1,是0就扩展0;
- 3)上述位扩展后的结果,赋值给左操作数,按照左操作数是无符号数还是有符号数解释成对应的十进制数值,如果是无符号数,则直接转换成十进制数值,如果是有符号数,则看成2的补码转换成十进制数;
- 4)从上面4种情况看出,有符号数赋值成无符号数会出现数据错误的情况,因此要避免这种赋值,而其他情况都是可以保证数据正确的。