摘 要: 介绍了Verilog带符号数的不同运算。因为Reg和Wire数据默认情况下是无符号的,而在数据处理的情况下,Verilog既要对带符号数据进行各种运算,也要对无符号数和带符号数进行运算,所以简单使用Verilog提供的运算符是不够的。因此研究不同类型数据运算的通用方法是必要的。
关键词: Verilog; 带符号数; 补码; 算术运算
中图分类号: TN911⁃34 文献标识码: A 文章编号: 1004⁃373X(2015)03⁃0160⁃03
Operation of numbers with symbols by Verilog
HUI Wei⁃jun, SHEN Zhao⁃jun
(Yancheng Institute of Technology, Yancheng 224051, China)
Abstract: Different operations of data with symbols by Verilog are introduced. Reg and Wire data in the case of default is unsigned, but in the case of data processing, a variety of operations of data with symbols are performed by Verilog, and the unsigned and signed with numbers need to be processed. However, it is not enough to use the operation symbols provided by Verilog. It is necessary to research the general method for various types of data operation.
Keywords: Verilog; number with symbol; twos complement; arithmetic operation
0 引 言
Verilog语言是目前流行的一种硬件描述语言。它的最大的优点是简单、规范,语法规则与C语言十分相似。然而,作为一种描述语言,Verilog不具备C语言丰富的数据类型和数据处理能力。Verilog 95中能处理带符号数数据类型是整型,而整型的默认位宽是32位,且位宽不能改变,因而限制了整型数的使用。为了解决这个问题,Verilog 2001版本增加了对带符号数处理的支持,虽然如此,Verilog对数据处理仍然有很多问题。本文以Verilog 2001为版本,重点讨论带符号数的算术运算,并给出Verilog程序和仿真结果,同时,本文也讨论了带符号数的其他运算情况。
1 加、减运算
Verilog 2001处理带符号数加减运算很简单,只要在定义数组是使用关键字signed即可:
module add_signed(a,b,sum);
input signed [7:0] a,b;
output signed [7:0] sum;
assign sum=a+b;
endmodule
仿真结果如图1所示。
图1 加法仿真结果
Verilog处理带符号数和无符号数运算时候,默认是把带符号数转换为无符号数。因此,对于带进位加法或者带借位减法运算,如果不加处理就会发生错误:
module addc(a,b,c,sum);
input signed [7:0] a,b;
input c;
output signed [8:0] sum;
assign sum=a+b+c;
endmodule
仿真如图2所示,可知结果是错误的,原因是a和b高位扩展加零。改进的方法是把带符号数进行符号扩展:
assign sum={a[7],a}+{b[7],b}+c;
图2 带符号数和无符号数加法仿真错误结果
图3所示仿真表明结果是正确的。也可以用系统函数把无符号数转换为带符号数参加运算:
assign sum=a+b+$signed(c);
所得仿真结果将会相同。不同的是,系统函数$signed不能综合,具有$signed的代码只能用于仿真。
图3 带符号数和无符号数加法仿真正确结果
2 乘法运算
两个带符号数乘法运算的处理与加法运算相似,只需在定义端口的时候使用关键字signed即可:
module mult(a,b,prod);
input signed [7:0] a;
input signed [7:0] b;
output signed [15:0] prod;
assign prod = a * b;
endmodule
如图4所示的仿真表面结果是正确的。当然,实现一个带符号数和无符号数的乘法的时候,默认是把带符号数高位全部扩展为零,因而,用乘法运算符实现的乘法结果将会是错误的,如图5所示。
module mult(a,b,prod);
input signed [7:0] a;
input [7:0] b;
output signed [15:0] prod;
assign prod = a*b;
endmodule
图5 带符号数与无符号数乘法仿真错误结果
解决方法是可以用函数$signed把上述代码中的无符号数b转换为带符号数,此时代码就不可综合。实现通用的两个数乘法的算法,基本的思路是用移位加的方式实现。其算法基础是认为补码是一个权重码,位宽为n的向量存放补码,其MSB的权为[-2n-1,]其他位的权为[2i,]i的取值范围为0≤i≤n-2。当两个位宽为n的数相乘时,从右向左,逐位考察乘数索引为i的位的值,若为1,将被乘数左移i位,作为部分和加到结果中。若乘数最高为位1,因为权为[-2n-1,]所以被乘数要先取反加一,然后再左移n-1位。两个位宽为n的数相乘,最终的结果,是由n个移位的部分和相加得到。
module mult8(a,b,prod);
input signed [7:0] a;
input [7:0] b;
output signed [15:0] prod;
wire [15:0] prod_int0;
wire [15:0] prod_int1;
wire [15:0] prod_int2;
wire [15:0] prod_int3;
wire [15:0] prod_int4;
wire [15:0] prod_int5;
wire [15:0] prod_int6;
wire [15:0] prod_int7;
wire [7:0] inv_add1;
assign prod_int0 = b[0] ? {{8{a[7]}},a} : 16′b0;
assign prod_int1 = b[1] ? {{7{a[7]}},a,1′b0} : 16′b0;
assign prod_int2 = b[2] ? {{6{a[7]}},a,2′b0} : 16′b0;
assign prod_int3 = b[3] ? {{5{a[7]}},a,3′b0} : 16′b0;
assign prod_int4 = b[4] ? {{4{a[7]}},a,4′b0} : 16′b0;
assign prod_int5 = b[5] ? {{3{a[7]}},a,5′b0} : 16′b0;
assign prod_int6 = b[6] ? {{2{a[7]}},a,6′b0} : 16′b0;
assign inv_add1 = ~a + 1′b1;
assign prod_int7=b[7]?{inv_add1[7],inv_add1,7′b0} : 16′b0;
assign prod = prod_int0 + prod_int1 + prod_int2+prod_int3 + prod_int4 + prod_int5+prod_int6 + prod_int7;//六个部分和相加得到积
endmodule
以上代码可以对2个无符号数、2个带符号数、1个带符号数和1个无符号数的乘法都适用,仿真结果如图6所示。
图6 带符号数和无符号数相乘的正确结果
3 除法运算
通用的整数除法器也不可以用除法运算符来实现。除法器实现比较复杂,组合逻辑实现的算法较少。以下代码实现的就是一个通用的组合逻辑除法器,其思想是,先把带符号数转换为无符号数,实现两个无符号数的整数除法。
至于两个无符号数的除法器的算法是,循环实现被除数减去除数,直到被除数比除数小。那么,减的次数就是商,保留的被除数就是余数。最后根据被除数和除数的符号位确定商和余数的符号。
Module
dividenew(Dividend,Divisor,Quotient,Reminder);
input [7:0] Dividend,Divisor;
output reg [7:0] Quotient,Reminder;
wire isneg;
wire [7:0] Dividend_abs,Divisor_abs;
integer i;
reg [7:0] tempa;
reg [7:0] tempb;
reg [15:0] temp_a;
reg [15:0] temp_b;
assign isneg = Dividend[7] ^ Divisor[7];
assign Dividend_abs = Dividend[7] ? ~Dividend + 1 : Dividend;
assign Divisor_abs = Divisor[7] ? ~Divisor + 1 : Divisor;
always @(Dividend_abs,Divisor_abs)
begin
tempa <= Dividend_abs;
tempb <= Divisor_abs;
end
always@(tempa,tempb)
begin
temp_a = {8′h00,tempa};
temp_b = {tempb,8′h00};
for(i = 0; i < 8; i = i+1)
begin
temp_a = { temp_a[14:0],1′b0};
if( temp_a[15:8] >= tempb )
temp_a = temp_a - temp_b + 1;
else
temp_a = temp_a;
end
Quotient = isneg ? ~temp_a[7:0] + 1 : temp_a[7:0];
Reminder = Dividend[7] ? ~temp_a[15:8]+1 : temp_a[15:8];
end
endmodule
上述程序适用于任意两个数相除的情况。仿真结果如图7所示,结果表明算法设置是正确的。
4 其他运算
(1) 移位运算
Verilog 2001增加了 >>> 带符号数右移运算符,不同于运算符 >> ,当执行该运算时,低位移出,高位补的是符号位。
(2) 关系运算
当执行带符号数和无符号数运算时候,Verilog默认是把带符号数看成无符号数。因此比较大小时,容易发生错误。
reg [7:0] a = 13;
reg signed [7:0] b = -8;
wire c;
assign c = (a > b);
上述代码中,c的值为0,说明关系运算是错误的。要正确的比较带符号数和无符号数的大小,需要根据符号位的具体情况来处理。
(3) 按位运算
当需要扩展的时候,注意的是用符号扩展,不能用默认的补零扩展。
5 结 语
在Verilog 1995版中,Reg和Wire都只支持无符号数,为了解决对带符号数的问题,Verilog 2001版增加了对带符号数处理的支持。在一般情况下,直接使用运算符实现带符号数的各种运算会出现错误。在加减、关系等运算中需要把带符号数进行符号扩展,然后再参加运算,在乘除运算中,仍然要采取一定的算法,才能实现通用的数据运算。
参考文献
[1] 布莱恩特,奥哈拉伦.深入理解计算机系统(英文版)[M].2版.北京:机械工业出版社,2011.
[2] 王宏政.补码及其发展与应用[J].现代电子技术,2006,29(1):23⁃25.
[3] 夏宇闻.Verilog数字系统设计教程[M].北京:北京航空航天大学出版社,2008.
[4] 鞠芳,马昕,田岚.基于FPGA的数字乘法器性能比较[J].电子器件,2003,26(1):42⁃45.
[5] 李彦孚,宋路.乘法器模块在FPGA中的实现[J].长春大学学报,2012,22(8):93⁃96.
[6] 王鲁豫,陈春深,国磊.基于改进的布斯算法FPGA嵌入式18×18乘法器[J].现代电子技术,2012,35(8):154⁃156.