HLS / Chisel 实现CORDIC算法高性能复数除法

CORDIC(坐标旋转数字算法)是一种计算三角、双曲和其他数学函数的数字算法,每次运算均产生一次结果输出。这使我们能够根据应用需求调整算法精度;增加运算迭代次数可以得到更精确的结果。CORDIC 是只使用加法、减法、移位和查找表实现的简单算法,这种算法在FPGA中实现效率高,在硬件算法实现中经常用到。

论文[1]介绍了一种通过CORDIC算法中CR、CV、LV三种模式拆分高性能计算复数除法的方法,本文将使用Chisel 实现该方法,这里默认在以下几篇文章的CORDIC原理基础之上完成。

HLS / Chisel 实现CORDIC算法计算三角函数(圆周系统之旋转模式)
HLS / Chisel 实现CORDIC算法计算反三角函数(圆坐标系向量模式)
HLS / Chisel 实现CORDIC算法计算除法(线性坐标系向量模式LV)

原理

CORDIC背景

CORDIC算法有旋转和向量两个模式,分别可以在圆坐标系、线性坐标系和双曲线坐标系使用,从而可以演算出8种运算,而结合这8种运算也可以衍生出其他许多运算。下表展示了8种运算在CORDIC算法中计算的核心公式推导:

在这里插入图片描述

接下来我们将利用如上的CR、CV、LV三种模式实现复数除法

复数除法高性能计算

首先有如下的复数除法公式,我们一般会做如下的拆分:

Z = a + i b p + i q = a p + b q p 2 + q 2 + i b p − a q p 2 + q 2 \mathrm{Z}=\frac{a+i b}{p+i q}=\frac{a p+b q}{p^{2}+q^{2}}+i \frac{b p-a q}{p^{2}+q^{2}} Z=p+iqa+ib=p2+q2ap+bq+ip2+q2bpaq

  1. CV模式转化为极坐标形式

首先输入如下的数值

X 0 = p , Y 0 = q , Z 0 = 0 \mathrm{X}_{0}=p, \quad Y_{0}=q, \quad Z_{0}=0 X0=p,Y0=q,Z0=0

在此反三角函数计算模块可以得到如下的结果:

X = K p 2 + q 2 = r , Y = 0 , Z = tan ⁡ ( q / p ) − 1 = θ X=K \sqrt{p^{2}+q^{2}} = r, \quad \mathrm{Y}=0, \quad Z=\tan (q / p)^{-1}=\theta X=Kp2+q2 =r,Y=0,Z=tan(q/p)1=θ

其中K在CV模式的推导文章中有介绍,是一个固定的常数。接下来的步骤我们需要极坐标系的结果 ( r , θ ) (r,\theta) (r,θ)

  1. LV模式计算实数除法

将1中的结果 X = r X=r X=r作为输入此模块,输入初始化为如下:

X 0 = K p 2 + q 2 , Y 0 = a , Z 0 = 0 \mathrm{X}_{0}=K \sqrt{p^{2}+q^{2}}, \quad Y_{0}=a, \quad Z_{0}=0 X0=Kp2+q2 ,Y0=a,Z0=0

最后通过实数除法得到如下的结果:

X = K p 2 + q 2 , Y = 0 , Z = a K p 2 + q 2 X=K \sqrt{p^{2}+q^{2}}, \quad \mathrm{Y}=0, \quad Z=\frac{a}{K \sqrt{p^{2}+q^{2}}} X=Kp2+q2 ,Y=0,Z=Kp2+q2 a

同理再输入

X 0 = K p 2 + q 2 , Y 0 = b , Z 0 = 0 \mathrm{X}_{0}=K \sqrt{p^{2}+q^{2}}, \quad Y_{0}=b, \quad Z_{0}=0 X0=Kp2+q2 ,Y0=b,Z0=0

获得如下结果:

X = K p 2 + q 2 , Y = 0 , Z = b K p 2 + q 2 X=K \sqrt{p^{2}+q^{2}}, \quad \mathrm{Y}=0, \quad Z=\frac{b}{K \sqrt{p^{2}+q^{2}}} X=Kp2+q2 ,Y=0,Z=Kp2+q2 b

  1. CR模式计算旋转推导出复数除法

将1的结果 Z = tan ⁡ ( q / p ) − 1 = θ Z=\tan (q / p)^{-1}=\theta Z=tan(q/p)1=θ,2中的结果 Z = a K p 2 + q 2 , Z = b K p 2 + q 2 Z=\frac{a}{K \sqrt{p^{2}+q^{2}}},Z=\frac{b}{K \sqrt{p^{2}+q^{2}}} Z=Kp2+q2 aZ=Kp2+q2 b输入CR模块,输入如下:

X 0 = a K p 2 + q 2 , Y 0 = b K p 2 + q 2 , Z 0 = − θ \mathrm{X}_{0}=\frac{a}{K \sqrt{p^{2}+q^{2}}}, \quad Y_{0}=\frac{b}{K \sqrt{p^{2}+q^{2}}}, \quad Z_{0}=-\theta X0=Kp2+q2 a,Y0=Kp2+q2 b,Z0=θ

已知CR模式的推导公式如下:

K ( x cos ⁡ z − y sin ⁡ z ) K ( y cos ⁡ z + x sin ⁡ z ) \begin{aligned} &K(x \cos z-y \sin z) \\ &K(y \cos z+x \sin z) \end{aligned} K(xcoszysinz)K(ycosz+xsinz)

已知

tan ⁡ ( q / p ) − 1 = θ \tan (q / p)^{-1}=\theta tan(q/p)1=θ

那么可以得到:

sin ⁡ θ = q q 2 + p 2 cos ⁡ θ = p q 2 + p 2 \begin{aligned} &\sin\theta = \frac q {\sqrt{q^2+p^2}} \\ &\cos\theta = \frac p {\sqrt{q^2+p^2}} \end{aligned} sinθ=q2+p2 qcosθ=q2+p2 p

将x,y,z带入CR模式的推导模式则可以得到:

X = a p + b q p 2 + q 2 Y = b p − a q p 2 + q 2 X=\frac{a p+b q}{p^{2}+q^{2}} \quad Y=\frac{b p-a q}{p^{2}+q^{2}} X=p2+q2ap+bqY=p2+q2bpaq

即复数的实部和虚部计算完成。

在这里插入图片描述

输入输出范围限定

整个CORDIC计算复数除法的输入输出如上图。由于CR、CV、LV模块有输入输出的范围限制,则对于整个复数除法计算大模块的输入输出也有范围限制,首先我们单独讨论子模块:

  • CR模块执行旋转功能,将输入的点(x,y)∈R旋转 θ ∈ [ − 90 ° , 90 ° ] \theta∈[-90°,90°] θ[90°90°]角度得到点(X,Y)
  • CV模块执行极坐标转化(arctan计算),将输入的点(x>0,y∈R)即一四象限的点转化为极坐标(r∈R, θ ∈ [ − 90 ° , 90 ° ] \theta∈[-90°,90°] θ[90°90°]
  • LV模块执行除法运算,在我们设计的模块中,我们使用二进制移位处理使得可以实现实数除法,所以 输入(x,y)∈R,输出z∈R

基于如上分析,我们分别对复数除法模块的a、b、q、p进行取值范围分析:

  1. 对于a、b

a、b参与的运算从LV模块开始,然后LV模块输出影响CR模块的输入。由于LV和CR的输入都是实数域R,所以输入a、b∈R

  1. 对于p、q

p、q参与了CV、CR、LV三个模块的运算,有以下分析。

a. 对于q<0,q>0

此时在首先CV模块的输入,(q,p)是在第二象限,由于CV模块的输入范围限制,我们可以将 ( q , p ) (q,p) (q,p)作原点对称到 ( q ‘ , p ‘ ) = ( − p , − q ) (q`,p`) = (-p,-q) (q,p)=(p,q),则计算出来的结果 θ \theta θ为(q`,p`) 对应的结果,但极坐标的 K p 2 + q 2 = r K \sqrt{p^{2}+q^{2}} = r Kp2+q2 =r是没有任何影响的

在这里插入图片描述

然后在LV模块中计算由于r是不变化的,所以LV模块没有任何影响。

最后在CR模块中将 − θ -\theta θ带入,得到的应该是

X = a p ‘ + b q ‘ p ‘ 2 + q ‘ 2 Y = b p ‘ − a q ‘ p ‘ 2 + q ‘ 2 X=\frac{a p`+b q`}{p`^{2}+q`^{2}} \quad Y=\frac{b p`-a q`}{p`^{2}+q`^{2}} X=p2+q2ap+bqY=p2+q2bpaq

带入 ( q ‘ , p ‘ ) = ( − p , − q ) (q`,p`) = (-p,-q) (q,p)=(p,q)即可以将负号加上就可以得到复数除法结果

X = − a p − b q p 2 + q 2 Y = − b p + a q p 2 + q 2 X=\frac{-a p-b q}{p^{2}+q^{2}} \quad Y=\frac{-b p+a q}{p^{2}+q^{2}} X=p2+q2apbqY=p2+q2bp+aq

Z = a + i b p + i q = a p + b q p 2 + q 2 + i b p − a q p 2 + q 2 = − a p ‘ + b q ‘ p ‘ 2 + q ‘ 2 − i b p ‘ − a q ‘ p ‘ 2 + q ‘ 2 \mathrm{Z}=\frac{a+i b}{p+i q}=\frac{a p+b q}{p^{2}+q^{2}}+i \frac{b p-a q}{p^{2}+q^{2}} = -\frac{a p`+b q`}{p`^{2}+q`^{2}}-i \frac{b p`-a q`}{p`^{2}+q`^{2}} Z=p+iqa+ib=p2+q2ap+bq+ip2+q2bpaq=p2+q2ap+bqip2+q2bpaq

b.对于q<0,q<0

此时在首先CV模块的输入,(q,p)是在第三象限,同理如上小节方法也可以转换到第一象限,最后在第三步CR模块得到的处理也一样,即如下:

X = − a p − b q p 2 + q 2 Y = − b p + a q p 2 + q 2 X=\frac{-a p-b q}{p^{2}+q^{2}} \quad Y=\frac{-b p+a q}{p^{2}+q^{2}} X=p2+q2apbqY=p2+q2bp+aq

c.对于q>0,q>0 或 q>0,q<0

此时首先CV模块的输入是在允许的一四象限处理范围内,可以直接计算得到正确的结果

X = a p + b q p 2 + q 2 Y = b p − a q p 2 + q 2 X=\frac{a p+b q}{p^{2}+q^{2}} \quad Y=\frac{b p-a q}{p^{2}+q^{2}} X=p2+q2ap+bqY=p2+q2bpaq

HLS实现

  • 定义复数
  • 对输入的数做处理转移到输入合适的区间
#define COS_SIN_TYPE float
#define NUM_ITERATIONS 50

typedef struct { COS_SIN_TYPE re, im; } Complex;

void cordic_complex_divide(Complex dividend, Complex divisor, Complex &z)
{
        COS_SIN_TYPE a,b,p,q;
        a = dividend.re;
        b = dividend.im;
        p = divisor.re;
        q = divisor.im;
        bool flag = 1; // 默认不改变最后输出实部虚部的符号
        if((p < 0 and q > 0) or (p < 0 and q < 0)){
                // 将2,3象限的点旋转到1,4象限,方便CV模块操作
                p = -p;
                q = -q;
                flag = 0;
        }
        
        COS_SIN_TYPE theta, r, arctan; // arctan为舍弃的
        cordic_cv(p, q, r, theta, arctan);
        
        COS_SIN_TYPE temp_divide_a, temp_divide_b;
        cordic_lv(r, a, temp_divide_a);
        cordic_lv(r, b, temp_divide_b);
        
        cordic_cr(x = temp_divide_a, y = temp_divide_b, theta = -theta, z.re, z.im);
        if(flag) {
                z.re = -z.re;
                z.im = -z.im;
        }
}

Chisel实现

定义数据类型

import chisel3._
import chisel3.experimental._


trait HasDataConfig {
  /* 定点数的位宽定义 */ 
  val DataWidth = 32
  val BinaryPoint = 20
}


class Complex extends Bundle with HasDataConfig{
  /* 复数的 实部,虚部*/
  val re: FixedPoint = FixedPoint(DataWidth.W, BinaryPoint.BP)
  val im: FixedPoint = FixedPoint(DataWidth.W, BinaryPoint.BP)
}


class ComplexOperationIO extends Bundle with HasDataConfig{
  val op1: Complex = Input(new Complex())
  val op2: Complex = Input(new Complex())
  val res: Complex = Output(new Complex())
}

源码

import chisel3._
import chisel3.experimental._

class cordic_complex_divide extends Module with HasDataConfig with Cordic_Const_Data {
  /*
   * @io.op1 : 输入的被除数 定点数表示的复数Complex 输入范围R
   * @io.op2 : 输入的除数 定点数表示的复数Complex 输入范围R
   * @io.res : 输出的复数除法结果 定点数表示的复数Complex 范围R
   * details: 利用cordic实现复数除法,周期为3*迭代次数NUM_ITERATIONS
  **/
  val io = IO(new ComplexOperationIO)

  val a = WireInit(io.op1.re)
  val b = WireInit(io.op1.im)
  val p = Wire(FixedPoint(DataWidth.W,BinaryPoint.BP))
  val q = Wire(FixedPoint(DataWidth.W,BinaryPoint.BP))

  /* (p,q)所在象限必须调整到一四象限才能在CV阶段输入,最后通过flag再回调即可,flag 需要存储NUM_ITERATIONS个流水层级到最后输出时使用 */
  val flag_reg: Vec[Bool] = RegInit(VecInit(Seq.fill(NUM_ITERATIONS*3)(1.B)))
  when(io.op2.re < FixedPoint.fromDouble(0,DataWidth.W,BinaryPoint.BP)){
    p := -io.op2.re
    q := -io.op2.im
    flag_reg(0) := 0.B
  }.otherwise{
    p := io.op2.re
    q := io.op2.im
    flag_reg(0) := 1.B
  }
  for(i <- 1 until NUM_ITERATIONS*3){
    flag_reg(i) := flag_reg(i-1)
  }

  /* a,b为LV模块的输入,但是需要存储保存NUM_ITERATIONS个流水层级到LV模块使用 */
  val a_reg: Vec[FixedPoint] = RegInit(VecInit(Seq.fill(NUM_ITERATIONS)(FixedPoint.fromDouble(0.0, DataWidth.W, BinaryPoint.BP))))
  val b_reg: Vec[FixedPoint] = RegInit(VecInit(Seq.fill(NUM_ITERATIONS)(FixedPoint.fromDouble(0.0, DataWidth.W, BinaryPoint.BP))))
  for(i <- 0 until NUM_ITERATIONS){
    if(i == 0){
      a_reg(0) := a
      b_reg(0) := b
    }else{
      a_reg(i) := a_reg(i-1)
      b_reg(i) := b_reg(i-1)
    }
  }

  /* CV模块得到极坐标坐标系结果 */
  val (theta, r, arctan) = cordic_cv(p,q)

  /* theta为CV模块的输出,但是需要存储保存NUM_ITERATIONS个流水层级到CR模块使用 */
  val theta_reg: Vec[FixedPoint] = RegInit(VecInit(Seq.fill(NUM_ITERATIONS)(FixedPoint.fromDouble(0.0, DataWidth.W, BinaryPoint.BP))))
  for(i <- 0 until NUM_ITERATIONS){
    if(i == 0){
      theta_reg(0) := theta
    }else{
      theta_reg(i) := theta_reg(i-1)
    }
  }

  /* LV模块并行计算得到两个除法结果 */
  val z_a = cordic_divide(r, a_reg(NUM_ITERATIONS-1))
  val z_b = cordic_divide(r, b_reg(NUM_ITERATIONS-1))

  /* CR旋转点计算复数除法 */
  val (re, im) = cordic_cr(z_a, z_b, -theta_reg(NUM_ITERATIONS-1))

  when(flag_reg(NUM_ITERATIONS*3-1)){
    io.res.re := re
    io.res.im := im
  }.otherwise{
    io.res.re := -re
    io.res.im := -im
  }
}

object cordic_complex_divide_APP extends App {
  println(getVerilogString(new cordic_complex_divide))
  (new chisel3.stage.ChiselStage).emitVerilog(new cordic_complex_divide)
}

定义伴生对象工厂方法

最后我们定义两个个伴生对象工厂方法来方便直接调用函数,无需多加连线

/*
* 定义这个类的伴生对象,并定义一个工厂方法来简化模块的例化和连线。
**/
object cordic_complex_divide {
    def apply(divided: Complex, divisor: Complex): Complex = {
    /*
     * @divided : 输入的被除数 定点数表示的复数Complex 输入范围R
     * @divisor : 输入的除数 定点数表示的复数Complex 输入范围R
     * @res : 输出的复数除法结果 定点数表示的复数Complex 范围R
     * details: 利用cordic实现复数除法,周期为3*迭代次数NUM_ITERATIONS
    **/
      val unit = Module(new cordic_complex_divide)
      unit.io.op1 := divided
      unit.io.op2 := divisor
      unit.io.res
  }
}

参考论文

[1] Yang B , Dong W , Liu L . Complex division and square-root using CORDIC. IEEE, 2012.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlwaysDayOne

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值