Verilog基础知识(有符号数运算规则,加减运算,乘法运算中的符号位拓展问题)

本文详细介绍了Verilog中进行有符号数运算的规则,包括加法、乘法及移位操作。解释了如何正确处理有符号数与无符号数的混合运算,避免类型转换错误,以及提供了代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://blog.youkuaiyun.com/maxwell2ic/article/details/80620991

 

rule of thumb

The format of the signed type is two’s complement. 有符号数均为补码表示
If any operand in an expression is unsigned the operation is considered to be unsigned. 只有计算表达式右边有无符号数,整个计算式都按照无符号数规则运算
只有算式右边全为有符号数,运算才会自动补齐所需的bit数,n+n=n+1.n*n=2n
The value -4 represented as a 3bit signed hex value would be specified as -3’sh4. A decimal number is always signed. verilog2001中用’s来特别声明有符号数,十进制的数都是有符号数
Type casting using $unsigned will make the operation unsigned. The operand will be sign extended with 0’s if necessary. $usigned()函数在高位补0
A=$signed(B) will extend using sign bit. $unsigned()函数会在高位补与符号位相同的bit
basic signed addition

两个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
1
2
3
4
5
6
7
8
//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
1
2
3
4
5
6
7
8
signed + unsigned

如果是两个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}) //正确的做法
1
2
3
正确的做法是

// 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
1
2
3
4
5
6
7
8
9
// 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
1
2
3
4
5
6
7
8
9
basic signed multiplication

两个n bit数相乘,得到2n bit结果。如果-3(3’sb101) * 2 (3’sb010) 得到正确结果 -6 (6’sb111010)。但如果乘数是负数,则最高位的乘积需要以减法参与运算,而不是加法。 


// Code Example 6: Signed Multiply - Verilog 1995
module mult_signed_1995 (
input [2:0] a,
input [2:0] b,
output [5:0] prod
);
wire [5:0] prod_intermediate0;
wire [5:0] prod_intermediate1;
wire [5:0] prod_intermediate2;
wire [2:0] inv_add1;
assign prod_intermediate0 = b[0] ? {{3{a[2]}}, a} : 6'b0;
assign prod_intermediate1 = b[1] ? {{2{a[2]}}, a, 1'b0} : 6'b0;
// Do the invert and add1 of a.
assign inv_add1 = ~a + 1'b1;
assign prod_intermediate2 = b[2] ? {{1{inv_add1[2]}},
inv_add1, 2'b0} : 6'b0;
assign prod = prod_intermediate0 + prod_intermediate1 +
prod_intermediate2;
endmodule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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
1
2
3
4
5
6
7
8
signed * unsigned

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}); //正确做法
1
2
3
4
// 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
1
2
3
4
5
6
7
8
signed shift

逻辑移位’>>’, ‘<<’会补零,算术移位’<<<’, ‘>>>’会补符号位

A = 8'sb10100011
A>>3; //8'b00010100 
A>>>3;  //8'b11110100
--------------------- 
作者:maxwell2ic 
来源:优快云 
原文:https://blog.youkuaiyun.com/maxwell2ic/article/details/80620991 
版权声明:本文为博主原创文章,转载请附上博文链接!

<think>我们处理的是Verilog有符号数运算的溢出问题。根据引用[3],我们可以通过检查输入和输出的符号位来判断是否溢出。对于有符号数,溢出发生在: 1. 两个正数相加,结果变成了负数(正溢出) 2. 两个负数相加,结果变成了正数(负溢出) 引用[3]中的代码给出了一个8有符号数加法的溢出判断方法: assign overflow = (a[7] && b[7] && ~s[7]) || (~a[7] && ~b[7] && s[7]); 但是,这个逻辑只适用于加法。对于减法,我们需要调整。另外,对于任意,我们可以推广这个逻辑。 在Verilog中,有符号数通常使用二进制补码表示。假设我们有两个n有符号数a和b,进行加法运算,结果s也是一个n数。那么溢出标志可以这样计算: overflow = (a[n-1] & b[n-1] & ~s[n-1]) | (~a[n-1] & ~b[n-1] & s[n-1]); 然而,如果我们的运算包括减法,那么我们需要考虑减法的溢出情况。对于减法(a-b),可以转化为a+(-b),而-b的补码是取反加1。但是,直接判断减法的溢出也有类似的规则: 1. 正数减负数(相当于正数加正数)得到负数,则正溢出 2. 负数减正数(相当于负数加负数)得到正数,则负溢出 实际上,对于减法,我们可以这样判断: overflow = (a[n-1] & ~b[n-1] & ~s[n-1]) | (~a[n-1] & b[n-1] & s[n-1]); 但是,更通用的做法是将减法转换为加法,然后统一用加法的溢出判断。不过,在硬件中,我们通常直接根据操作和操作数的符号来设计。 另一种方法是利用进标志。在二进制补码运算中,溢出可以通过最高的进和次高的进异或得到。具体来说,对于n加法,设进输入为0,则: 溢出 = 进输出(n-1) ^ 进输出(n) 但是,由于我们通常没有逐的进,所以更常用的是符号位比较的方法。 下面,我将分别给出加法、减法和可配置加减法的溢出判断方法。 ### 1. 有符号加法溢出处理 假设我们有一个加法器,输入为a和b(有符号数),输出为s(和),以及overflow(溢出标志)。 **溢出判断逻辑**: ```verilog module signed_adder ( input signed [7:0] a, input signed [7:0] b, output signed [7:0] sum, output overflow ); assign sum = a + b; // 溢出判断:当两个正数相加得负数,或两个负数相加得正数 assign overflow = (a[7] & b[7] & ~sum[7]) | (~a[7] & ~b[7] & sum[7]); endmodule ``` ### 2. 有符号减法溢出处理 对于减法,即a-b: ```verilog module signed_subtractor ( input signed [7:0] a, input signed [7:0] b, output signed [7:0] diff, output overflow ); assign diff = a - b; // 溢出判断:当正数减负数得负数(正溢出),或负数减正数得正数(负溢出) // 注意:正数减负数:a为正,b为负,则相当于两个正数相加(因为a - (-|b|) = a+|b|) // 负数减正数:a为负,b为正,则相当于两个负数相加(因为-|a| - |b| = -(|a|+|b|)) // 所以,溢出判断为: // (a为正,b为负,结果却为负) -> 正溢出 // (a为负,b为正,结果却为正) -> 负溢出 assign overflow = (~a[7] & b[7] & diff[7]) | (a[7] & ~b[7] & ~diff[7]); endmodule ``` ### 3. 通用方法:利用进信息(更适用于任意运算) 另一种更通用的方法是利用进标志。对于n加法,设: s = a + b; 进C_out = 加法器的进输出(即第n) 而实际参与运算的进是第n-1向第n的进,记为C_in_n。 在二进制补码中,溢出标志V = C_in_{n-1} ^ C_out 但是,在Verilog中,我们可以通过扩展一来获取进信息,然后判断溢出。 例如,对于n有符号加法,我们可以将操作数扩展一,然后取结果的最低n作为和,而扩展的进情况可以用于判断溢出。 具体如下: ```verilog module signed_adder_carry ( input signed [n-1:0] a, input signed [n-1:0] b, output signed [n-1:0] sum, output overflow ); parameter n = 8; wire signed [n:0] extended_sum; // 扩展一 assign extended_sum = {a[n-1], a} + {b[n-1], b}; // 符号位扩展一 assign sum = extended_sum[n-1:0]; // 溢出判断:如果扩展后的两个符号位(即extended_sum[n]和extended_sum[n-1])不同,则溢出 assign overflow = (extended_sum[n] != extended_sum[n-1]); endmodule ``` 这种方法的好处是适用于任意,且逻辑简单。对于减法,同样可以这样处理,只需将减法转化为加上负数的补码。 ### 4. 加减法组合的溢出处理 如果我们有一个模块同时支持加法和减法,我们可以用一个控制信号add_sub(0表示减法,1表示加法)来控制,并处理溢出。 ```verilog module signed_add_sub ( input signed [7:0] a, input signed [7:0] b, input add_sub, // 1为加法,0为减法 output signed [7:0] result, output overflow ); wire signed [7:0] b_operand; assign b_operand = add_sub ? b : -b; // 减法时取负 // 扩展一计算 wire signed [8:0] extended_sum; assign extended_sum = {a[7], a} + {b_operand[7], b_operand}; assign result = extended_sum[7:0]; assign overflow = (extended_sum[8] != extended_sum[7]); endmodule ``` ### 5. 处理溢出的策略 当检测到溢出时,我们可以: - 将结果饱和处理(saturation):对于正溢出,将结果设置为最大正数;对于负溢出,将结果设置为最小负数。 - 或者,直接保留溢出结果(不推荐,除非后续处理能接受) 饱和加法的例子(8有符号数): ```verilog module saturating_signed_adder ( input signed [7:0] a, input signed [7:0] b, output signed [7:0] sum, output overflow ); wire signed [8:0] extended_sum; assign extended_sum = {a[7], a} + {b[7], b}; wire ovf; assign ovf = (extended_sum[8] != extended_sum[7]); // 溢出标志 // 饱和处理:如果溢出且正数,则取127;如果溢出且负数,则取-128;否则取正常结果 assign sum = ovf ? (extended_sum[8] ? 8'sh80 : 8'sh7f) : extended_sum[7:0]; assign overflow = ovf; endmodule ``` ### 总结 1. **溢出检测**:通过比较操作数和结果的符号位,或者通过扩展后比较符号位是否一致。 2. **溢出处理**:可以选择饱和处理或者报错。 3. **通用性**:扩展的方法更通用,且易于实现饱和运算。 > 注意:在Verilog中,有符号数运算时,如果赋值给一个不足的变量,会发生截断,这可能导致无法判断溢出。因此,建议在运算时使用足够的,然后再进行饱和或截断处理[^2]。 §§相关问题§§ 1. 如何在Verilog中实现有符号数的饱和加法? 2. 有符号数乘法如何避免溢出? 3. 在SystemVerilog中处理有符号数溢出有哪些更简洁的方法?</think>在Verilog中处理有符号数运算的溢出问题,需要结合二进制补码表示和符号位分析。以下是系统的解决方案: ### 1. 溢出检测原理 有符号数运算溢出发生在: - **正溢出**:两正数相加结果变负数($a>0, b>0, s<0$) - **负溢出**:两负数相加结果变正数($a<0, b<0, s>0$) 数学表示为: $$overflow = (a_{msb} \land b_{msb} \land \neg s_{msb}) \lor (\neg a_{msb} \land \neg b_{msb} \land s_{msb})$$ 其中$msb$表示最高符号位)[^3] ### 2. 基础实现(8加法器示例) ```verilog module signed_adder ( input signed [7:0] a, // 8有符号输入 input signed [7:0] b, // 8有符号输入 output signed [7:0] s, // 8有符号输出 output overflow // 溢出标志 ); assign s = a + b; // 有符号加法 // 溢出检测逻辑 assign overflow = (a[7] & b[7] & ~s[7]) | (~a[7] & ~b[7] & s[7]); endmodule ``` ### 3. 通用实现(参数化设计) ```verilog module signed_adder #(parameter WIDTH=8) ( input signed [WIDTH-1:0] a, input signed [WIDTH-1:0] b, output signed [WIDTH-1:0] s, output overflow ); localparam MSB = WIDTH-1; // 符号位索引 assign s = a + b; assign overflow = (a[MSB] & b[MSB] & ~s[MSB]) | // 负溢出 (~a[MSB] & ~b[MSB] & s[MSB]); // 正溢出 endmodule ``` ### 4. 溢出处理策略 #### (1) 饱和处理(推荐) ```verilog module saturating_adder #(parameter WIDTH=8) ( input signed [WIDTH-1:0] a, input signed [WIDTH-1:0] b, output reg signed [WIDTH-1:0] result ); localparam MAX_POS = (1 << (WIDTH-1)) - 1; // 最大正值 localparam MAX_NEG = -1 << (WIDTH-1); // 最大负值 wire signed [WIDTH:0] extended_sum = a + b; // 扩展 always @(*) begin casex ({extended_sum[WIDTH], extended_sum[WIDTH-1]}) 2'b01: result = MAX_POS; // 正溢出->饱和到最大值 2'b10: result = MAX_NEG; // 负溢出->饱和到最小值 default: result = extended_sum[WIDTH-1:0]; // 正常结果 endcase end endmodule ``` **饱和处理原理**: - 正溢出:$result = 2^{n-1}-1$(如8的127) - 负溢出:$result = -2^{n-1}$(如8的-128) - 正常范围:直接输出结果[^2] #### (2) 带标志的溢出报告 ```verilog output reg overflow, output reg underflow; always @(*) begin overflow = 0; underflow = 0; casex ({a[MSB], b[MSB], s[MSB]}) 3'b000_1: overflow = 1; // 正溢出 3'b111_0: underflow = 1; // 负溢出 endcase end ``` ### 5. 测试方法(验证溢出处理) ```verilog module tb(); reg signed [7:0] a, b; wire signed [7:0] s; wire overflow; signed_adder uut (.*); initial begin // 测试正溢出 (127+1=-128) a = 127; b = 1; #10; $display("%d + %d = %d, overflow=%b", a, b, s, overflow); // 测试负溢出 (-128-1=127) a = -128; b = -1; #10; $display("%d + %d = %d, overflow=%b", a, b, s, overflow); // 边界测试 a = 100; b = 50; #10; // 正常范围 a = -100; b = -50; #10; // 正常范围 end endmodule ``` ### 6. 关键注意事项 1. **扩展**:运算前扩展1-2可预防临时溢出 ```verilog wire signed [8:0] temp = {a[7], a} + {b[7], b}; // 符号扩展 ``` 2. **混合符号运算**:无符号与有符号混合时,Verilog自动按无符号处理,需显式转换: ```verilog assign result = $signed(unsig_a) + signed_b; ``` 3. **乘法溢出**:乘法结果应为操作数之和: ```verilog wire signed [15:0] mult = a * b; // 8*8=16 ``` > 实际设计中推荐使用饱和处理,可避免不可控的环绕行为(wrap-around)[^2]。对于复杂系统,建议使用SystemVerilog的`assert`进行溢出断言检测。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值