Verdvana

空持千百偈 不如吃茶去

Verilog HDL中数的表示与运算

22 Jun 2019 » Verilog HDL, FPGA

1 数的表示

1.1 原码、反码和补码

        谁都知道。

1.2 浮点数的表示

        计算机中的浮点数标准IEEE 754表示浮点数n的方法:

符号位  指数位  尾数位  
s  e  m  
n=(-1)^s  * m * 2^e

        s:n>0时,s=0;n<0时,s=1。

        e:可正可负。

        m:有效数字位,尾数位。

        单精度浮点数用32位存储数据:符号位1位,指数位8位,尾数位23位。

        双精度浮点数用64位存储数据:符号位1位,指数位11位,尾数位52位。

        其中,浮点数的小数点左侧必须为1,因此在保留尾数时省略小数点左侧的这个1,从而腾出一个二进制位来保存更多的尾数。

        例如对于单精度数而言,二进制的1001.101(十进制为9.625)可以表示为1.001101*2^3,因此实际保存在尾数域中的值为00110100000000000000000,此时去掉小数点左侧的1,并用0在右侧补齐,这就是归一化。但当n非常小的时候,则无须归一化,否则e会超出表示范围。由于浮点数的特殊性,IEEE 754标准采用源码方式表示尾数。

        在指数域,由于单精度数为8位,可以表示的范围为0~255,共256个指数值。但是指数e可以为正数可以为负数。为了处理负数的情况,实际的指数值按要求需要加上一个偏差值作为保存在指数域中的值,单精度的偏差值为127,双精度的偏差值为1023。

  • 将浮点数-8.5转为二进制:
    • 整数部分为8,则二进制为1000;小数部分为0.5,二进制为1.由于浮点数尾数为23位,实际包含的数值为24位,所以8.5在填补0补充为24位后表示为:
        1000.1000 0000 0000 0000 0000
      
    • 把小数点移到第一个1的后面,需要左移三位,加上偏移量127:127+3=130,转换为二进制是10000010,所以阶码e为10000010。
    • 根据第一步得到23位的尾数位M为:
        000 1000 0000 0000 0000 0000
      
    • 由于-8.5是负数,所以符号位s为1。
    • 将符号位s、阶码e、和尾数m进行连接:
        1 1000 0010 000 1000 0000 0000 0000 0000
      
  • 将二进制40490E56转为浮点数:
    • 将40490E56转为二进制:
        0100 0000 0100 1001 0000 1110 0101 0110
      
    • 得到的符号位s为0;阶码e为100 0000 0,真实阶数为128-127=1;尾数m为100 1001 0000 1110 0101 0110。
    • 该数实际小数为1.1001 0010 0001 1100 1010 110,即十进制的1.5707499980926513671875。
    • 根据公式:n=(-1)^s * m * 2^e计算数值:
        n = 1.5707499980926513671875 * 2^1 =3.141499996185302734375
      

#### 1.3 定点数的表示

        对于l位的定点数n,有两部分显式表示:符号位s和数据位m,整体采用补码表示,该值记为x,表示的范围为[-2^(l-1),2^(l-1)-1];而定标的指数位e是隐含的,因此当两个定点数指数位相同时,可以直接运算。

符号位  数据位  
s  m(l-1)  
n = x * 2^e

        例如,对于l=16的定点数,如果指数为e=15,即含有15位小数位,记为Q15或S0.15,则n=x*2^-15,表示范围为-1≤n≤0.999 9695。

        Q、S表示法如表:

Q表示法  S表示法  数值表示范围    表示精度
Q15S0.15-1≤n≤0.999 969 51/2^15
Q14S1.14-2≤n≤1.999 939 01/2^14
Q13S2.13-4≤n≤3.999 877 91/2^13
Q12S3.12-8≤n≤7.999 755 91/2^12
Q11S4.11-16≤n≤15.999 511 71/2^11
Q10S5.10-32≤n≤31.999 023 41/2^10
Q9S6.9-64≤n≤63.998 046 91/2^9
Q8S7.8-128≤n≤127.996 093 81/2^8
Q7S8.7-256≤n≤255.992 187 51/2^7
Q6S9.6-512≤n≤511.980 437 51/2^6
Q5S10.5-1024≤n≤1023.968 751/2^5
Q4S11.4-2048≤n≤2047.937 51/2^4
Q3S12.3-4096≤n≤4095.8751/2^3
Q2S13.2-8192≤n≤8191.751/2^2
Q1S14.1-16384≤n≤16383.51/2^1
Q0S15.0-32768≤n≤327671/2^0

        但在芯片设计中,加减乘除运算不会关心Q是多少,而是直接按照整数补码运算。

        不同的Q所表示的数不仅范围不同,而且精度也不同。在浮点表示中单精度的Q值范围为[-127,+127],但实际上可以表达更大的范围,因为定点数的定标值隐藏在编程实现中,范围可以接近无穷大。

        浮点数与定点数的转化关系

浮点数n转换为定点数nq    定点数nq转换为浮点数n    
nq=int(n*2^Q)n=float(nq*2^-Q)

        例如,浮点数n=0.5,定标Q=15,则定点数nq=int(0.5*32768)=16384。

        定标Q=15的定点数为16384,其浮点数为16384*2^-15=16384/32768=0.5。

        浮点数转化为定点数时,为了降低截尾误差,通常在最后加上0.5。

        对定点数而言,当选定字长l后,数值范围与精度互相矛盾。同样,字长l越长,能表示的范围或精度也会相应提升,但消耗的硬件资源就会更多。因此,字长、精度、表示范围和资源消耗需要整体权衡。例如FFT定点化,对于WLAN系统,只需要12位精度就可以,但对于LTE系统,至少要14位精度才能保证基本性能。


2 定点数的运算规则

        对于加减运算以及要求输入数据具备相同字长的运算,定点数必须调整到相同字长。对于乘除运算、指数、对数等运算以及不要求对等字长的运算,则无须调整定点数字长。

2.1 定点数的运算举例

        以下均假设x、y、z为L位的定点数,temp则为大于L的定点数,用于储存中间结果的定点变量。他们的定标精度分别为Px、Py和Pz。

  • 加法运算z=x+y

        如果x和y的定标精度相同,即Px=Py,则直接做加法即可,但由于x+y可能溢出,所以z的字长应当扩大1位,以获得全部精度;如果维持z的字长保持不变,则需要进行溢出判断处理,即:

    assign temp = {x[L-1],x}+ {y[L-1],y};
    assign z    = (temp[L-1]!=temp[L])?{ temp[L],{ { L-1 } { ~temp[L] } } }:temp[L-1:0];

        如果两者的定位精度不同,假定Px>Py,则用以下算法可实现定点加法:

    assign temp = x>>(Px-Py)+y;
    assign z    = (Px>Py)?temp<<(Px-Py):temp>>(Py-Pz);   //没有考虑溢出和饱和截位的情况

        上述加法预算是在已知定标长度的前提下,进行加法运算。现在换另外一种常见的场景:已知a是x位宽的有符号数据,b是y位宽的有符号数据,结果是z位宽的有符号数据c,a/b/c/定标相同,这段代码的设计实现为:

    localparam  x=10;
    localparam  y=12;
    localparam  z=16;

    wire [x-1:0] a;
    wire [y-1:0] b;
    wire [z-1:0] c;

    assign c[z-1:0] = { { { z-x } {a[x-1] } },a[x-1:0] } + { { { z-y} { b[y-1] } },b[y-1:0] };

        可以发现a与b均通过扩展符号位,达到相加结果z所要求的宽度然后相加。

  • 乘法运算z=x+y         对于x=X1×2^E1,y=Y1×2^E2,两者的乘法运算相对加法要简单很多,因为:
      z=x×y=(X1×Y1)×2^(E1+E2)
    

            所以定点乘法与普通有符号整数乘法的结果基本一致,只是需要记录新的定标,因为量化精度变为E1+E2。

  • 除法运算z=x+y         对于x=X1×2^E1,y=Y1×2^E2,两者的除法运算与乘法计算类似,因为:
      z=x÷y=(X1÷Y1)×2^(E2-E1)
    

            而算法则可以归结为普通有符号除法,再加上两者的定标相减。而基于有符号补码的除法函数实现如下所示,该算法采用辗转相减的办法实现了除法和求余运算。

      assign {qout,mod} = div( param1,param2 );       //用法示例
    
      function [a_width+b_width-1 : 0] div;
      input [a_width-1 : 0] a;
      input [b_width-1 : 0] b;
    
      reg [b_width : 0]   sum;
      reg [a_width-1 : 0] dividend;
      reg [b_width : 0]   rem_adjust;
      reg [b_width : 0]   temp_b;
      reg [a_width-1 : 0] rem;
    
      integer i;
    
      begin
          sum = {b_width{1'b0}};
          dividend = a;
          sum[0] = a[a_width -1];
          dividend = dividend << 1'b1;
          temp_b = ~b + 1'b1;
          sum = sum = temp_b;
          sividennd[0] = ~sum[b_width];
    
          for(i=0 ; 1<a_width-1'b1 ; i=i+1'b1)
              begin
                  if(sum[b_width])begin
                      temp_b = b;
                  end
    
                  else begin
                      temp_b = ~b +1'b1;
                  end
    
              sum = sum << 1'b1;
              sum[0] = dividend[d_width-1];
              dividend = dividend << 1'b1;
              sum = sum + temp_b;
              dividend[0] = ~sum[b_width];
          end
    
          rem_adjust = sum[b_width] ? sum + b : sum;
          rem = rem_adjust[b_width-1 : 0];
          div = {dividend,rem};
      end
      endfunction
    

2.2 定点数的移位规则

        逻辑移位的规则是,逻辑左移时,高位移出,低位填0;逻辑右移时,低位移出,高位填0.

        算术移位的规则是,移位时带符号位扩展,即算术右移时,移动后空缺的位置填充符号位;而算数左移等同逻辑左移,移位后空缺位置填0。

        在不考虑溢出和提高截断精度的情况下,针对用原码、反码和补码的形式 表示的定点数,在保证移位后符号位不变的前提下,左移和右移按照下表规则进行:

数值类型    码制    填补代码    
正数原码、补码、反码0
 原码0
负数补码左移填0,右移填1
 反码1
  • 具体规则

    • 机器数为正时,不论左移或右移,填补代码为0;
    • 由于负数的原码数值部分与真值相同,所以在移位时只要使符号位不变,其空位均填0;
    • 由于负数的反码中除符号位之外的各位与负数的原码相反,所以移位后所添加的数值应与原码相反,即全部填0;
    • 分析任意负数的补码可发现,当由低位向高位找到第一个“1”时,此“1”左边的各位均与对应的反码相同,而此“1”右边的各位(包括此“1”在内)均与对应的原码相同,即填0;右移时空位出现在高位,则填补的数值应与反码相同,即填1。

        例如,,寄存器内容为01110011,逻辑左移1位为11100110,算数左移1位后的结果为01100110(最高位的1丢弃)。又如寄存器内容为10111010,逻辑右移为01011101,若将其视为补码,算术右移为11011101。

        因此Verilog中的移位符号只针对带符号位的补码和无符号位的原码有效,对有符号的原码或反码移位,数据则会出错。

  • 移位举例
    • 正数移位举例:

              数据A=27,采用8位方式存储时(包含1位符号位),则A=27=11011,因此,[A]原=[A]补=[A]反=00011010。则移位结果如表所示:

      移位操作    机器数        对应真值    
      移位前0 0011011+27
      左移一位0 0110110+54
      左移两位0 1101100+108
      右移一位0 0001101+13
      右移两位0 0000110+6

              因此对于正数,三种机器数移位后符号位不变,左移时等于乘法操作,但如果移位次数过多,将会出现溢出情况;右移是等效于除法操作,整体精度降低。

    • 负数移位举例

              数据 A=-27=-11011,则移位结果如表所示:

      移位操作    机器数(原码)    对应真值    
      移位前1 0011011-27
      左移一位1 0110110-54
      左移两位1 1101100-108
      右移一位1 0001101-13
      右移两位1 0000110-6
      移位操作    机器数(补码)    对应真值    
      移位前1 1100101-27
      左移一位1 1001010-54
      左移两位1 0010100-108
      右移一位1 1110010-14
      右移两位1 1111001-7
      移位操作    机器数(反码)    对应真值    
      移位前1 1100100-27
      左移一位1 1001001-54
      左移两位1 0010011-108
      右移一位1 1110010-13
      右移两位1 1111001-6

              对于负数,如果移位次数过多,同样也会出现溢出情况。右移时等效于除法操作,整体精度也会降低。

  • 截位与饱和处理

            通过以上两个例子可以发现,左移会发生饱和,右移会直接截断,损失精度。

            因此对于右移操作,为了提高精度,可以将截取位按照四舍五入的方式计入最后一位;对于左移操作,必然会发生溢出情况,此时就需要判别全部的符号位是否完全一致,如果不一直就表明发生溢出。


        告辞。