符号なし除算(回復法)

[] [] []


除算をFPGAに実装するとき、自分でRTLを書くのは難しそうなのでIP生成ツールを使う人が多いと思うが、最近はVitisHLSのような高位合成ツールがあるので、アルゴリズムを知っていると用途に合わせて除算器を作れる。そこで除算のアルゴリズムを合成可能なCコードで書いてみよう。ツールとしてVitisHLSの固定小数点型テンプレートap_int.hを使うが、SystemCsystemc.hでも同様である。(→補足固定小数点型テンプレートの使い方)

まず、符号なし整数の除算を考える。なお固定小数点でも、最下位ビットを合わせれば整数の除算と同じである

a /d = q + e

分子a:符号なし、整数部IAビット

分母d:符号なし、整数部IDビット

q: 符号なし、整数部IQビット、小数部FQビット

    (1) a,dが任意の値をとりうる場合はa/d≦2IA/1だからIQ=IAだけ必要

    (2) a/d≦2IQ-2-FQとわかっている場合はビット幅IQ<IAで十分

     例) N個の値xiの平均値はxiの値の範囲内に必ず収まる

誤差e(1) 切捨の場合 0≦e2-FQ

     (2) 丸めの場合 -2-FQ-1≦e2-FQ-1

手順は、分子から分母を引いてみて、非負だったらq[i]=1にして次は引いた値を使い、負だったらq[i]=0にして次は引く前の値に戻せばよい。この方法は引き戻し法回復法Restoringとよばれる。

簡単な例としてIA=3,ID=2,IQ=IA=3,FQ=0の場合で手計算すると次のようになる。

【回復法】 符号なし/符号なし

説明

例)a[2:0]=101=(5)  d[1:0]=11=(3)

         _______   

      11)  101.0   r={00,a[2]}

       -) 11       d

         ---

         110       t=r-d <0

q[2]=0    010      r={r[1:0],a[1]}

        -) 11      d

          ---

          111      t=r-d <0

q[1]=0     101     r={r[1:0],a[0]}

         -) 11     d

           ---

           010.0   t=r-d >=0

q[0]=1      10.0   r={t[1:0],0}

          -) 1.1   d

           --------

            00.1   t=r-d >=0

q[-1]=1

切捨の場合 q[2:0]=001

丸めの場合 q[2:0]=q[2:0]+q[-1]=010

 

 

引き算はq[IA-1](aMSB)から始める

分母を引いて

 負だったらq[i]=0にして次は引く前の値に戻す

 非負だったらq[i]=1にして次は引いた値を使う

 t=r-d

 if(t<0) q[i]=0 r={r[1:0],a[i-1]}

 else    q[i]=1 r={t[1:0],a[i-1]}

 符号を判定するためt,rのビット幅は[ID:0]だけ必要

 

 

 

 

切捨(0<=e<1)の場合はq[0]まで求めて終わり

 

 

丸め(-0.5<=e<0.5)の場合はq[-1]まで求めて

 q[2:0]=q[2:0]+q[-1]とする

◇◇

このアルゴリズムをVitisHLSで合成可能なCコードで書くと次のようになる。ここで商qを小数点以下まで求めるためにa*2FQ/d=qaを左シフトしている。

#include <ap_int.h>

/********************************************************

 * division: a/d = q + e

 *   param range: IQ<=IA

 *   input range: 0<d,0<=a<=d*(2**(IQ)-2**(-FQ))

 *   error range: 0<=e<2**(-FQ) (truncation)

 *******************************************************/

template <int IA,int ID,int IQ,int FQ>

ap_ufixed<IQ+FQ,IQ> div_uint_trn  // (o) quotient

  (ap_uint<IA> a        // (i) dividend

  ,ap_uint<ID> d        // (i) divisor

  ) {

  ap_uint<IA+FQ> a_tmp=(ap_uint<IA+FQ>)a(IA-1,0)<<FQ;

  ap_uint<ID+1> r;

  ap_uint<ID+1> t;

  ap_ufixed<IQ+FQ,IQ> q;

  for(int i=IQ+FQ-1; i>=0; i--) {

    if(i==IQ+FQ-1) {

      r = a_tmp(IA+FQ-1,i);

    } else {

      r = (q[i+1]? t(ID-1,0):r(ID-1,0),a_tmp[i]);

    }

    t = r - d(ID-1,0);

    q[i] = !t[ID];

  }

  return q;

}

#include <ap_int.h>

/*********************************************************

 * division: a/d = q + e

 *   param range: IQ<=IA

 *   input range: 0<d,0<=a<=d*(2**(IQ)-2**(-FQ))

 *   error range: -2**(-FQ-1)<=e<2**(-FQ-1) (rounding)

 ********************************************************/

template <int IA,int ID,int IQ,int FQ>

ap_ufixed<IQ+FQ,IQ> div_uint_rnd // (o) quotient

  (ap_uint<IA> a        // (i) dividend

  ,ap_uint<ID> d        // (i) divisor

  ) {

  ap_uint<IA+FQ+1> a_tmp=(ap_uint<IA+FQ+1>)a(IA-1,0)<<(FQ+1);

  ap_uint<ID+1> r;

  ap_uint<ID+1> t;

  ap_ufixed<IQ+FQ,IQ> q;

  for(int i=IQ+FQ; i>=0; i--) {

    if(i==IQ+FQ) {

      r = a_tmp(IA+FQ,i);

    } else {

      r = (q[i]? t(ID-1,0):r(ID-1,0),a_tmp[i]);

    }

    t = r - d(ID-1,0);

    if(i==0) {

      q(IQ+FQ-1,0) = q(IQ+FQ-1,0) + !t[ID];

    } else {

      q[i-1] = !t[ID];

    }

  }

  return q;

}

このコードは次のテストベンチで検証してある。

int main(int argc, char *argv[]) {

  const int IA=3;

  const int ID=3;

#ifdef FULL_IQ

  const int IQ=3,FQ=1;  // IQ=IA

#else

  const int IQ=1,FQ=1;  // IQ<IA

#endif

  ap_uint<IA> a;

  ap_uint<ID> d;

  ap_ufixed<IQ+FQ,IQ> q;

  double a_val,d_val,q_val,e_val;

  printf("-- IA=%-d ID=%-d IQ=%-d FQ=%-d\n",IA,ID,IQ,FQ);

  /* a,dを全範囲振って商と誤差を表示する */

  for(int j=1; j<pow(2,ID); j++) {

    for(int i=0; i<pow(2,IA); i++) {

      a = i;

      d = j;

      a_val = a.to_double();

      d_val = d.to_double();

      printf("-- a=%f d=%f",a_val,d_val);

      /* IQ<IA+1のとき大きいaはスキップする */

      if(a_val<=d_val*(pow(2,IQ)-pow(2,-FQ))) {

        /* 切捨の場合 */

        q = div_uint_trn<IA,ID,IQ,FQ>(a,d);

        q_val = q.to_double();

        e_val = a_val/d_val - q_val;

        printf(" q_trn=%f e=%+f",q_val,e_val);

        if(!(0<=e_val && e_val<pow(2,-FQ))) printf(" [FAIL]");

        /* 丸めの場合 */

        q = div_uint_rnd<IA,ID,IQ,FQ>(a,d);

        q_val = q.to_double();

        e_val = a_val/d_val - q_val;

        printf(" q_rnd=%f e=%+f",q_val,e_val);

        if(!(-pow(2,-FQ-1)<=e_val && e_val<pow(2,-FQ-1))) printf(" [FAIL]");

      }

      printf("\n");

    }

  }

  return 0;

}


[] [] []