Advertisement

Verilog学习笔记——有符号数的乘法和加法

阅读量:

有符号数的计算 在 Verilog 设计中是一个关键问题(同样容易被忽视),特别是在基于 Verilog 语言实现 FIR 滤波器 的设计过程中,不可避免地会遇到带有符号位的加法与乘法运算。为了确保系统性能,在之前的开发工作中我把所有信号都定义为了带符号形式(但后来发现部分信号仍需保持无符号状态),这导致了之前设计中的一个问题(经过进一步调试发现原程序存在逻辑缺陷),下面将尝试通过修改 Verilog 实现来验证这一猜想;

1. 编写程序测试无符号数和有符号数的乘法

请编写如下的程序:在其中两数相乘涉及四种组合方式:一个是无符号与无符号相乘;另一个是无符号与有符号相乘;还有一种是有符号与无符号相乘;最后一种是有符号与有符号相乘。运算结果同样分为两种类型:一种是无符号类型;另一种是有 symbol 型别。总共有 8 种不同的情况需要考虑。

复制代码
    module signed_test(
    input           [7:0]   data_in_unsigned_1,
    input           [7:0]   data_in_unsigned_2,
    
    input   signed  [7:0]   data_in_signed_1,
    input   signed  [7:0]   data_in_signed_2,
    
    output          [15:0]  data_out_000,
    output          [15:0]  data_out_001,
    output          [15:0]  data_out_010,
    output          [15:0]  data_out_011,
    
    output  signed  [15:0]  data_out_100,
    output  signed  [15:0]  data_out_101,
    output  signed  [15:0]  data_out_110,
    output  signed  [15:0]  data_out_111
    );
    
    //无符号 = 无符号 * 无符号 
    assign data_out_000 = data_in_unsigned_1 * data_in_unsigned_2;
    //无符号 = 无符号 * 有符号 
    assign data_out_001 = data_in_unsigned_1 * data_in_signed_2;
    //无符号 = 有符号 * 无符号 
    assign data_out_010 = data_in_signed_1   * data_in_unsigned_2;
    //无符号 = 有符号 * 有符号 
    assign data_out_011 = data_in_signed_1   * data_in_signed_2; 
    
    //有符号 = 无符号 * 无符号 
    assign data_out_100 = data_in_unsigned_1 * data_in_unsigned_2;
    //有符号 = 无符号 * 有符号 
    assign data_out_101 = data_in_unsigned_1 * data_in_signed_2;
    //有符号 = 有符号 * 无符号 
    assign data_out_110 = data_in_signed_1   * data_in_unsigned_2;
    //有符号 = 有符号 * 有符号 
    assign data_out_111 = data_in_signed_1   * data_in_signed_2; 
    
    endmodule

生成的 RTL 图如下:

在这里插入图片描述

可以看出,在乘法运算中输出结果不受符号位的影响,在计算机中采用有符号与无符号表示本质上是同一个数值这取决于我们如何定义它。例如考虑一个由十六位二进制数字组成的乘积值如十六进制表示为\texttt{c8_0_0_3}当我们将其视为无符号数值时最高有效位不再作为符号位而是代表2^{15}(即2的十亿五千万次方)这样的高位权重值因此该数值对应的十进制值为2^{15} + 2^{14} + 2^{1} + 2^{0} = \texttt{49,155}

如果把 16 位的二进制 16’b1100_0000_0000_0011 当成是一个有符号数来看,那么最高位是符号位,且剩下的数据时原来的数据二进制表示后取反再加1(补码表示),要计算它对应的十进制数
(1)先去掉符号位,保留剩下的 15-bit 的 100_0000_0000_0011;
(2)把 100_0000_0000_0011 取反,得到 011_1111_1111_1100;
(3)把 011_1111_1111_1100 的最低位 + 1,得到 011_1111_1111_1101;
(4)011_1111_1111_1101 按照无符号数换算成十进制是 16381;
(5)把最高位符号位加上,0代表正数,1代表负数,所以最后换算是 -16831;

Windows 计算器默认最高位是符号位;

测试数据如下:

在这里插入图片描述
复制代码
    initial begin 
        data_in_unsigned_1 = 8'hff; //255
        data_in_unsigned_2 = 8'hf0; //240
    
        data_in_signed_1 = 8'hff;   //-1
        data_in_signed_2 = 8'hf0;   //-16
    
    #200;
        data_in_unsigned_1 = 8'hff; //255
        data_in_unsigned_2 = 8'h0f; //15
        data_in_signed_1 = 8'hff;   //-1
        data_in_signed_2 = 8'h0f;   //15
    
    #200;
        data_in_unsigned_1 = 8'd127; //127
        data_in_unsigned_2 = 8'd15;  //15
        
        data_in_signed_1 = -8'sd127;  //-127,十进制有符号数赋值,必须要用 sd 表示
        data_in_signed_2 = -8'sd15;   //-15
    
    #200;     
        data_in_unsigned_1 = 8'd128; //128
        data_in_unsigned_2 = 8'd15;  //15       
        data_in_signed_1 = -8'sd128;  //-128
        data_in_signed_2 = -8'sd15;   //-15
    
    #200;
        data_in_unsigned_1 = 8'd127; //127
        data_in_unsigned_2 = 8'd15;  //15           
        data_in_signed_1 = -8'sd127;  //-127
        data_in_signed_2 = 8'sd15;    //15
        
    #200;     
        data_in_unsigned_1 = 8'd128; //128
        data_in_unsigned_2 = 8'd15;  //15               
        data_in_signed_1 = -8'sd128;  //-128
        data_in_signed_2 = 8'sd15;   //15
    
    #200;
        data_in_unsigned_1 = 8'd127; //127
        data_in_unsigned_2 = 8'd15;  //15          
        data_in_signed_1 = 8'sd127;  //127
        data_in_signed_2 = -8'sd15;   //-15
            
    #200;          
        data_in_unsigned_1 = 8'd127; //127
        data_in_unsigned_2 = 8'd15;  //15
        data_in_signed_1 = 8'sd127;  //127
        data_in_signed_2 = 8'sd15;   //15        
    
    #200;
    $stop;
    end

2. 仿真分析

计算的结果仿真如下:

在这里插入图片描述

对上图分析:
(1)在 0 ~ 400 ns,仿真中使用十六进制赋值相同的十六进制数据给乘数,让乘数分别以无符号数和有符号数进行读取,可以看到对 8’hff(对应二进制 8’b1111_1111)以无符号数读取时是按照 原码 读取,对应十进制 255,以有符号数读取时是按照补码读取,按照上文所说的去掉符号位后取反、加1再计算十进制得 -1;
(2)直接赋值十进制数据,乘数在以无符号数读取时时按照原码读取,127就对应 8 位二进制数 8’b0111_1111,十进制 128 就对应 8 位二进制 8’b1000_0000;而以有符号数读取的时候是会直接转换为补码形式,如 -127,先去掉符号位是 127,对应 7 位二进制数 7’b111_1111,取反为 7’b000_0000,加 1 为 7’b000_0001,将符号位补回到最高位为 8’b1000_0001;对于 -128 的表示比较特殊,8-bit的二进制数最高位是符号位,表示正负,剩下的 7-bit 能够表示的数的范围是 0 ~ 127,前面加上 ± 就能表示 -127 ~ 127,其中有 2 个数很特殊就是 8’b0000_0000 和 8’b1000_0000,按照上面会出现 +0 和 -0,为了区分出这两个数,前人定义 8’b0000_0000 表示 0,而 8’b1000_0000 表示 -128,这样不仅能区分开两个数,还多表示了一个数 -128(整个计算机体系通用,其他位数时类似表示一个负数);

在这里插入图片描述

(3)事实上,在查看下图的数据后可以发现,在data_out_000 和 data_out_111 时的数据全部正确;这也合乎情理:
无符号乘以无符号结果为无符号(仅包含零和正数);
有符号乘以有符号结果为有符号(零可正可负);
至于其他计算为什么会出现错误,则原因在于:
如果一个操作数是无符号的,则所有参与运算的操作量都会被强行转换为该无符号类型;
这就解释了在时间范围 0 ~ 400 ns 内 data_out_001 和 data_out_010 的计算结果与 data_out_000 完全一致——这是因为它们都是将赋值的8位十六进制数值作为无符号进行运算(这里没有涉及十进制到二进制的原码或补码转换问题的原因在于给定的是十六进制数值);
**当后续设计输入输出参数时若采用有符号形式,则必须明确定义输入/输出端以及中间变量均为 signed 类型;

在这里插入图片描述

3. 有符号数乘法的另一种计算

在之前的描述中提到,在计算过程中涉及的所有相关量都被明确地定义为带有符号的数值类型是一种常用的方法。此外,在常规情况下可能会定义的一些无符号数值。然而,在实际应用中输入的数据通常是带有符号的数值。例如,在本例中输入与输出部分并没有明确指定为带有sign位的signed类型。此时若外部传入的数据实际上是有symbol类型的带值(例如FIR滤波器接收到了既有正值又有负值的数据),那么在进行乘法和加法运算时就需要先扩展这些带sign位的有效数值。

module signed_test_2(
input [7:0] data_in_1,
input [7:0] data_in_2,
output [15:0] data_out_1,
output [15:0] data_out_2
);
对于乘法,需要扩展符号位 到 和积的位数相等,比如乘数a为 N-bit,乘数 b 为M-bit,两个相乘得到 N+M 位数据,此时需要对 a 扩展 M-bit 到 N+M 位,对 b 扩展 N-bit 到 N+M 位;
下面,使用 位拼接符 { } 来做演示,位拼接符可以按照二进制的位来进行高低位的拼接 ,假设 data_in_1= 8’b1000_0011,对于 {{8{data_in_1[7]}},data_in_1} 可以这样理解:
(1)先看 8{data_in_1[7]},表示取出 8-bit 数据 data_in_1 的最高位 data_in_1[7],重复 8 次,相当于
{ data_in_1[7], data_in_1[7], data_in_1[7], data_in_1[7], data_in_1[7], data_in_1[7], data_in_1[7], data_in_1[7] },
即高位扩展 8-bit 的 1;
(2){{8{data_in_1[7]}},data_in_1} 相当于在 data_in_1 的前面补上 8 个 data_in_1[7],即 结果为 16-bit 的 16’b1111_1111_1000_0011;

复制代码
    //不做符号位扩展,直接相乘
    assign data_out_1 = data_in_1 * data_in_2;
    //做符号位扩展,再相乘
    assign data_out_2 = {{8{data_in_1[7]}},data_in_1} * {{8{data_in_2[7]}},data_in_2};

在仿真测试中,在线状数据的具体数值采用了多种不同的表示方式以供对比分析。其中一处使用十六进制表示形式来描述数据特征参数;另一处则采用带符号的十进制赋值方法进行数值计算;第三处则用于与第二处的数据对比分析,并以此来观察最终赋值结果是否一致(参考相关技术博客中的处理方式)。

在这里插入图片描述

仿真结果显示,在观察到第2处和第3处的位置时发现其数值是一致的;通过将所有数值设置为带有符号的十进制表示(右键单机数据 Radix选项卡 -> Signed Decimal)。

在这里插入图片描述

可以看出,在数据处理过程中如果不进行位数扩展操作(即未对数值进行扩展),则会导致计算出错。而通过在数值运算中加入相应的处理步骤(如带入正确的运算规则),则能够确保计算结果的准确性

在这里插入图片描述

在处理带有符号的数值相加时,同样地,或者将所有运算统一定义为带符号数值的操作,或者进行 symbol 延展。针对 add 运算而言,在这种情况下,每个被操作的对象只需添加一位额外的 symbol 位。

除此之外,在IP核中还可以通过替代乘法运算符的方式来实现乘法操作具体来说在IP核中可以选择调用乘法器IP来替代乘法运算符*同时也可以选择调用加法器IP来替代加法运算符+最后在设置输入输出参数时应确保它们都是有符号数即可完成相应的计算任务

基于MATLAB的无线通信系统结合FPGA技术实现的一系列详细解析与应用案例汇总

全部评论 (0)

还没有任何评论哟~