Verilog有符号和无符号运算设计分析

主要内容是对有符号数和无符号数在设计时,数据是怎样传递的,符号位是怎样来的,以及相关的几种运算设计应当遵循怎样的想法。
最近对加减乘除运算很困惑,主要是对于有符号数的运算的困扰,如果运算出现负数怎么办。对于数据的赋值应该是怎样的,里面的数据是怎样存储的。
找来找去不如直接看Verilog标准verilog 2001里面写的很清楚。
具体的部分是在4.1.5小节中讲述的。
几种运算符的使用如下:

运算功能
a + ba加b
a - ba减b
a * ba乘以b
a / ba除以b
a % ba除以b的余数
a ** ba的b次方(在设计时,b只能是常量)

说明:
1、 整数除法将截断任何小数部分,也就是向下取整。对于除法运算符(/)或模数运算符(%),如果第二个操作数为零,则整个结果值应为x。模数运算符,例如y % z,当第一个操作数被第二个操作数除时给出余数,因此当z正好除y时为零。模数运算的结果应取第一个操作数的符号。
2、幂运算符(**)的第一个和第二个操作数以及其赋值结果,应当保证,全为有符号数或全为无符号数。如果第一个操作数为零且第二个操作数为非正,或者第一个操作数为负且第二个操作数不是整数值,则幂运算符的结果是未指定的。

对于取模运算符,其各种情况的运算结果如下:

运算取余
3%21
-3%2-1
2%20
-3’d3%21(-3‘d3被视为一个大的正数,除以2时余下1)

1.数据传递

在理解有符号和无符号数的运算之前,我觉得至少要明白我们在设计中,寄存器都是怎样进行赋值的,数据是怎样进行传递的,只有知道了其存储数据和传递数据的方式了,才能正确使用Verilog代码进行设计有符号数和无符号数的运算。
首先看其定义

Data typeInterpretation
unsigned net无符号数
signed net有符号数 (按二进制补码存储)
unsigned reg无符号数
signed reg有符号数 (按二进制补码存储)
integer有符号数 (按二进制补码存储)(可以理解为C语言的int型数据)
time无符号数
real ,realtime有符号数, 浮点类型

有符号数一般定义就是在数据类型前面加上signed,如果不加就是默认成无符号数。

1.1.常量赋值

如果用常量直接对数据进行赋值,如下代码方式:

reg  [3:0] regA;    //无符号
//reg signed [3:0] regA;    //有符号定义
always@(posedge clk)
begin
	regA <= 4'd10;   //正数赋值
	//regA <= 4'd10;  //负数赋值
end

在这里插入图片描述
结果如上图,如果直接赋值的话,正数是直接按照被赋值寄存器的位宽进行赋值的,而负数,无论是定义的有符号还是无符号寄存器,都是赋值其补码(不包括其符号位)(剩余的按符号位补)。如下图:
在这里插入图片描述

这里写一个被赋值寄存器位宽超过赋值的常量位宽的代码进行验证:

reg  [5:0]  a;
initial
begin
	a = 4'd10;     //1
	#4
	a = -4'd10;
end

再写一个被赋值的寄存器位宽小于常量位宽的情况:

reg  [2:0]  a;
initial
begin
	a = 4'd10;
	#4
	a = -4'd10;
end

在这里插入图片描述
仿真显示,跟上面分析的完全一致,所以被赋值的寄存器无论是否有符号定义,其赋值结果都是一样的。
这里需要注意一点,我们在用常量对寄存器进行赋值的时候,代码设计要写成十进制(或者16进制以及8进制)的形式才会按照这种高位补符号位,如果写成二进制,而二进制数据是默认为无符号数的,因此高位是直接补0的(无论赋给有符号数还是无符号数)。因此在设计运算电路的时候,尽量不使用二进制代码

这里在学习的过程中发现了一个常数表示方法:

reg [3:0] regA   //这里设置为4位,最高位作为符号位
always@(posedge clk)
begin
regA <= 3'sd5;
end

sd表示有符号十进制的意思,也就是说把上面5转换成二进制为101,其第一位为符号位,因此存入regA的数据是101再加上其符号位1101。如果表达式写成这样

regA <= -3'sd5;

应当这样进行理解,-5的二进制补码为011则其最高位为0所以最后存入寄存器regA的是0011

1.2.寄存器赋值

上一节已经分析了常量赋值到寄存器,因此下面的理解应该就很轻松了,可以提前思考一下,如果是无符号寄存器传数据,应该是位宽不够时,向高位补0,如果是有符号寄存器传数据,此时应该就是跟常量赋值类似,位宽不够,向高位补符号位。下面进行验证。
注:
上面已经说了被赋值的寄存器的定义是否为signed,其赋值的方案都是一样的,因此下面验证时,被赋值的寄存器均设置为无符号了。

1.2.1.无符号寄存器传数据

reg  [3:0]  a;
reg  [6:0]  b;   //验证高位补什么数据
reg  [1:0]  c;   //验证位宽较低时怎样截数据 
always@(posedge clk)
begin
	b <=  a;
	c <=  a;
end
initial
begin
	clk = 1;
	#4
	a= 4'b1010;
	#4
	a= 4'b0101;
    #20
	$stop;
end

在这里插入图片描述
从仿真的二进制结果来看,将a定义成无符号数的时候,如果被赋值寄存器的位宽较大,则向高位补0,较小,则直接截取对应低位。与前面的想法一样。

1.2.2.有符号寄存器传数据

这里只将上面的a和b定义成有符号数即可。

reg	signed 	[3:0]  a;
reg signed	[6:0]  b;    //当a的输入也是有符号数的时候,b可以不定义成无符号数。
						 //因为默认向高位补符号位

为什么b也定义成有符号数:因为如果不这样,当写代码的时候a的输入用二进制表示的,会导致不能向高位补符号位,而是补0,这里把b也定义成有符号数,就是为了实现当a的位宽不够时,能够向高位补符号位。
在这里插入图片描述
同样看二进制结果,因为a和b被定义成了有符号寄存器,所以最高位为符号位,则在赋值的时候,向高位补符号位,而不是0。

综上:
所有的常数赋值,正数的数据都是直接转换成二进制,然后向高位补符号位,最后存入寄存器中,而负数数据都是转换成二进制补码,然后向高位补符号位,存入寄存器中的。

1.3.总结

对各种传输数据进行一个总结:
在这里插入图片描述
有符号常量就是指用10进制、16进制、8进制的表示,无符号常量就是指2进制表示。
因此我们在进行有符号运算的时候,一定要保证输入输出都定义成有符号数,同时也要注意输入寄存器的输入端的数据是有符号还是无符号,以及他们的位宽是多少。

2.运算

2.1.加法

2.1.1.无符号

代码:

reg [3:0] a,b;
reg [4:0] c;    //结果多写一位,主要是防止溢出。
always@(posedge clk)
begin
	c <=  a + b;
end

在这里插入图片描述
仿真正确。

2.1.2.有符号

代码:

reg signed [3:0] a,b;  //实际上,前面已经说了,无论输入是否定义成有符号数,其存入的数据都是补码。但是加法器的输出必须被定义成有符号数,这样才能正确传递符号位。
reg signed [4:0] c;    //结果多写一位,主要是防止溢出。
always@(posedge clk)
begin
	c <=  a + b;
end

对于输入的数据a和b(位宽width),我们需要保证输入数据的绝对值最大不超过width-1也就是范围在-2(width-1)~2(width-1)
测试:

a = 4'd4;     
b = -4'd3;     
#20
a = -4'd4;
b = 4'd2;
#20

在这里插入图片描述
验证正确。
注:
如果输入或输出均不定义成有符号数

其运算出的结果应当是这样的,综合之后使用的是5位加法器,其最终结果如果转换成有符号,为-15,无符号,则为17 。
在这里插入图片描述
因此在设计的时候,还是全部带上有符号数,这样也方便后续数据传递。

2.2.减法

2.2.1.无符号

代码:

reg [3:0] a,b;  
reg [4:0] c;    //结果多写一位,主要是防止溢出。
always@(posedge clk)
begin
	c <=  a - b;
end

测试
在这里插入图片描述

注:
无符号肯定要保证输入输出都是正数才行,也就是被减数必须大于等于减数。

2.2.2.有符号

代码:

reg signed [3:0] a,b;
reg signed  [4:0] c;    //结果多写一位,主要是防止溢出。
always@(posedge clk)
begin
	c <=  a - b;
end

在这里插入图片描述
这里看一下硬件电路的实现:
代码:

module top(
	input clk,
	input signed [3:0] a,
	input signed [3:0] b,
	output  [4:0] o
);
reg signed [3:0] a0,b0;  //实际上,前面已经说了,无论输入是否定义成有符号数,其存入的数据都是补码。但是加法器的输出必须被定义成有符号数,这样才能正确传递符号位。
reg signed [4:0] c;    //结果多写一位,主要是防止溢出。
always@(posedge clk)
begin
	a0 <= a;
	b0 <= b;
	c <=  a0 - b0;
end
assign o = c;
endmodule

在这里插入图片描述
结果如上图,是通过加法器来实现减法运算的,通过综合出的电路可知道
是一个6位的加法器
被减数和减数经过一位拼接送入加法器A端,即

A[5:0] = {a0[3],a0,1'b1}
B[5:0] = {~{a0[3],a0},1'b1}

然后加法器输出舍弃最低位,输出到下一级寄存器,即

c[4:0] = OUT[5:1]

这里手算一下:
在这里插入图片描述

2.3.除法

2.3.1.无符号

除法比较简单,结果就是两数相除之后取整

reg  [3:0] a,b;
reg   [3:0] c;    //结果不需要扩展,因为除完只会变小,不会变大
always@(posedge clk)
begin
	c <=  a / b;
end

在这里插入图片描述

module top(
	input clk,
	input signed [3:0] a,
	input signed [3:0] b,
	output  [3:0] o
);
reg  [3:0] a0,b0;  
reg  [3:0] c;   
always@(posedge clk)
begin
	a0 <= a;
	b0 <= b;
	c <=  a0 / b0;
end
assign o = c;
endmodule

在这里插入图片描述

2.3.2.有符号

reg  signed [3:0] a,b;
reg  signed [4:0] c;    //结果多写一位,主要是防止溢出。
always@(posedge clk)
begin
	c <=  a / b;
end

在这里插入图片描述
可见其结果的符号与两个操作数有关,也就是符合我们正常的除法运算规律。

module top(
	input clk,
	input signed [3:0] a,
	input signed [3:0] b,
	output signed [3:0] o
);
reg  signed [3:0] a0,b0;  
reg  signed [3:0] c;    
always@(posedge clk)
begin
	a0 <= a;
	b0 <= b;
	c <=  a0 / b0;
end

assign o = c;

endmodule

在这里插入图片描述
查看最后综合出来的电路,有符号和无符号的电路都是一样的,但是其内部不知道是怎样算的,不过功能是正确的。

2.3.3.实现除法的四舍五入

  1. 移位实现
    有时候在硬件算法中,实现最多的就是除以2n 而且大部分是要求四舍五入。
    如果要求四舍五入,假设被除数为a,我们要除以22,结果设为c;
wire signed [3:0] a;
wire signed [3:0] c;
assign c = (a + 2) >>> 2;//加上2的n-1次方再进行移位

在这里插入图片描述

  1. 除法实现

—待更新—

2.4乘法

2.4.1.无符号

reg   [3:0] a,b;
reg  [7:0] c;    
always@(posedge clk)
begin
	c <=  a * b ;
end

在这里插入图片描述

2.4.2.有符号

reg  signed [3:0] a,b;
reg  signed [7:0] c;      //输出的位宽只需要大于等于a和b的位宽之和减一就可以了
always@(posedge clk)
begin
	c <=  a * b ;
end

在这里插入图片描述

易错

在设计的时候尝尝会有常数参与运算,当进行有符号运算时,比如

wire signed [3:0] a;
wire signed [7:0] c;      
assign c = a + 3'd2;

如果输入端有负数,这就会导致结果出现错误,如下图的第三组和第五组。
在这里插入图片描述
如果将表达式写成

assign c = a + 2;

或改成

wire signed [2:0] data;
assign data = 2'd2;
assign c = a + data;

结果:
在这里插入图片描述
这样才是正确的。
所以在设计的时候,需要注意这个点。

  • 7
    点赞
  • 91
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值