无符号乘法器

无符号乘法器
与无符号加法类似,无符号乘法器也要求两边的乘数是无符号的,一旦有一方为有符号数,则整个结果为有符号数,否则综合会出现不可预知的结果。与无符号加法不同的是,无符号的乘法,乘积结果位宽为两个乘数位宽相加,而非乘数最大位宽+1,其实从原理上是比较容易理解的,因为二进制乘法,就是几组二进制加法移位的结果,例如:
               1101              4位
   *            110              3位
---------------------------------
               0000
   +        1101
   +      1101
----------------------------------
          1001110             7位

乘法进行Verilog 编写,以前综合工具不是很优化,不能解析*,一般采用例会标准单元的方式,完成乘法运算:
传统古老方式Verilog 无符号乘法写法:

        localparam   A_WIDTH;
        localparam   B_WIDTH;
        localparam   PRDCT_WIDTH = A_WIDTH + B_WIDTH;
        
        reg [A_WIDTH-1:0]                 a;  // Default declaration type is unsigned
        reg [B_WIDTH-1:0]                 b;  // Default declaration type is unsigned
     
        wire [PRDCT_WIDTH-1:0]         prdct;
      
        DW02_MULT #(
                           .A_WIDTH       (A_WIDTH         ),
                           .B_WIDTH       (B_WIDTH         )
                          )
         U_DW_MULT
                          (
                           .TC                 (1'b0                 ), // 0 for unsigned, 1 for signed                       
                           .A                   (a                     ),
                           .B                   (b                     ),
                           .PRODUCT       (prdct               )
                          );

随着工具不断优化,包括Synplify也被synopsys收购后,FPGA综合工具也支持*乘法识别,只需要代码中申明乘法参数的符号属性既可。

推荐乘法运算Verilog 代码:
        localparam   A_WIDTH   =  8;
        localparam   B_WIDTH   =  16;
        localparam   PRDCT_WIDTH = A_WIDTH + B_WIDTH;
        
        reg unsigned [A_WIDTH-1:0]                a;  // Default declaration type is unsigned
        reg unsigned [B_WIDTH-1:0]                b;  // Default declaration type is unsigned
     
        reg unsigned [PRDCT_WIDTH-1:0]         prdct;

        always@(*) begin
              prdct = a * b;
        end
乘法不用显示把a和b位宽扩位到A_WIDTH+W_WIDTH,只要prdct 定义位宽为A_WIDTH+B_WIDTH,工具就不会报错。

以上讲解的是乘法器两边都是变量信号的无符号乘法运算,对于一个变量,一个常量的无符号运算,需要注意一下几点:
1. 常数的位宽要定义清楚;
2. 常数的符号类型要显示定义为无符号;
3. 对于常数无论是是否2的整数次幂,均按照* 写,不需要自己优化移位,因为综合的优化效果,不会比手动移位差。
示例:
        localparam                                            A_WIDTH   =  8;
        localparam                                            B_WIDTH   =  8;
        localparam   unsigned [B_WIDTH -1 : 0]  B              =  32;
        localparam                                             PRDCT_WIDTH = A_WIDTH + B_WIDTH;
        
        reg unsigned [A_WIDTH-1:0]                   a;  // Default declaration type is unsigned

     
        reg unsigned [PRDCT_WIDTH-1:0]         prdct;

        always@(*) begin
              prdct = a * B;
        end

强烈不推荐:
        always@(*) begin
              prdct = a << 5;
        end
原因: 1. 代码可扩展性上讲,后续常数B的值变化不是2的5次方,或者说不是2的整数次幂,这个地方就需要修改为*
         2.  代码可读性上讲,推荐的方式容易看懂,就是两个数相乘,不推荐的方式,还需要推敲一下,这行代码功能
         3.  代码可控性上讲,a往作移位,低位补0,还是补1,还是补a的最低位,工具都可以有不同理解,所以不同工具可能理解会不一样
         4.  两边位宽还不匹配,语法检查工具也会报Warning


顺便讲解一下这个章节代码规范一些细节:
1.  信号定义和申明,一行对应一个信号,不要多个信号定义在一行,否则修改其中一个信号,可能会影响其他信号,另外一行太长,也影响阅读,不建议定义方式:
              localparam   A_WIDTH = 8,B_WIDTH = 16;

2.  对于模块例化,建议按照名字进行例化,不要按照位置进行例化,否则被例化模块端口有修改,例化的上层文件就要重新修改,即不建议这样的例化代码风格:
          DW_MULT #(
                           A_WIDTH         ,
                           B_WIDTH         
                          )
         U_DW_MULT
                          (
                          1'b0                 ,
                           a                     ,
                           b                     ,
                           prdct               
                          );
     甚至很多教科书上的这种写法,可维护性更差,就更不推荐了哈:
       DW_MULT #( A_WIDTH ,  B_WIDTH ) U_DW_MULT(1'b0,a,b,prdct);
     原因很简答,如果a和b位置搞反了,a和b的位宽又不一样,就可能会报错,能够报错都算是不坏结果,就怕语法检查不跑错,最后仿真出错,定位问题会浪费较长时间。

3.  注意一下语法,例化赋值,或者用assign赋值的信号,我们定义为wire,在 always 块中的变量,无论是组合逻辑还是时序逻辑,都需要定义为reg。比如上面例子当中的prdct,第一个写法是通过例化模块得到值,所以定义为wire,第二个写法是在always块中得到,所以定义为reg。这个就是语法规定,没有什么理由,大家记住就行,否则工具就会报语法错误。

4. 在always 块中,组合逻辑采用非阻塞赋值 =, 时序逻辑采用阻塞赋值 <= ,具体原因,这里先不表述,前面章节主要讲组合逻辑,等讲到时序逻辑章节,会详细阐述原因,大家先有这个一个印象即可。

5.常数乘法给大家引申的一个写代码原则,尽量按照功能或者代码行为去写,只要是可综合风格即可,切忌自己觉得自己很聪明,对电路进行电路级的优化。这样会影响代码的可读性,扩展性以及可控性,同时现在综合工具优化功能很强,大家不必担心得不到最好的PPA(Power,Performance,Area),而且大家进行代码风格选择时,考虑的也不光是PPA这几个维度,也要从可以实现性,复杂度,可阅读星,开发周期多方面去考量。
posted @ 2023-03-04 17:19  大块头  阅读(206)  评论(0编辑  收藏  举报