注:本文为 “CORDIC 算法” 相关文章合辑。
未整理去重。
如有内容异常,请看原文。
Cordic 算法的原理介绍
乐富道 2014-01-28 23:05
Cordic 算法知道正弦和余弦值,求反正切,即角度。
采用用不断的旋转求出对应的正弦余弦值,是一种近似求解发。
旋转的角度很讲求,每次旋转的角度必须使得 正切值近似等于 1 2 N \frac{1}{2^N} 2N1。旋转的目的是让 Y Y Y 轴趋近与 0 0 0。把每次旋转的角度累加,即得到旋转的角度和即为正切值。
比如 Y Y Y 轴旋转 4 5 ∘ 45^{\circ} 45∘,则值减小 1 2 \frac{1}{2} 21;
再旋转 26.5650 5 ∘ 26.56505^{\circ} 26.56505∘,再减少 1 4 \frac{1}{4} 41;
再旋转角度 14.0362 4 ∘ 14.03624^{\circ} 14.03624∘,再减少 1 8 \frac{1}{8} 81; 依次减少 1 16 \frac{1}{16} 161, 1 32 ⋯ ⋯ \frac{1}{32}\cdots\cdots 321⋯⋯,最后 Y Y Y 轴的值无限小,趋近于 0 0 0 。
比如 X = 1 X = 1 X=1, Y = 1 Y = 1 Y=1,的角度,角度是 4 5 ∘ 45^{\circ} 45∘。经过一次旋转,要使得 Y = 0 Y = 0 Y=0,这个角度必须是 4 5 ∘ 45^{\circ} 45∘。
如上图
如图中,直角坐标系中点( X 0 X_0 X0, Y 0 Y_0 Y0)逆时钟旋转角度 θ \theta θ,变换成坐标( X 1 X_1 X1, Y 1 Y_1 Y1),那么用 X 0 X_0 X0, Y 0 Y_0 Y0,以及 θ \theta θ 的三角函数,如果表示 X 1 X_1 X1, Y 1 Y_1 Y1 呢?
请想象,如果坐标也旋转角度 θ \theta θ,那么 X 1 X_1 X1, Y 1 Y_1 Y1 的坐标依然是( X 0 X_0 X0, Y 0 Y_0 Y0)。接着往下看:
看完以上这副图,就该明白这个等式了:
x 1 = x 0 cos θ − y 0 sin θ x_1 = x_0\cos\theta - y_0\sin\theta x1=x0cosθ−y0sinθ
y 1 = x 0 sin θ + y 0 cos θ y_1 = x_0\sin\theta + y_0\cos\theta y1=x0sinθ+y0cosθ
再把这个式子化成正切函数。
Cordic 算法的思想是通过迭代的方法,不断的旋转特定的角度(这个特定的角度就是使得 Y Y Y 为上次的 1 2 \frac{1}{2} 21),使得累计旋转的角度的和无限接近某一设定的角度,
每次旋转的角度的 θ = arctan ( 1 2 n ) \theta = \arctan(\frac{1}{2^n}) θ=arctan(2n1);
具体迭代如下表: Z 0 = 3 0 ∘ Z_0 = 30^{\circ} Z0=30∘, Y 0 = 0 Y_0 = 0 Y0=0, X 0 = 0.6073 X_0 = 0.6073 X0=0.6073
输入 3 0 ∘ 30^{\circ} 30∘,经过 9 9 9 次迭代后, Z 0 = 0 Z_0 = 0 Z0=0, Y 0 = 0.5006 Y_0 = 0.5006 Y0=0.5006, X 0 = 0.8657 X_0 = 0.8657 X0=0.8657
x ( i + 1 ) ′ = ( x i ′ − y i ′ ( σ i ) 2 − i ) x'_{(i + 1)} = (x'_i - y'_i(\sigma_i)2^{-i}) x(i+1)′=(xi′−yi′(σi)2−i)
y ( i + 1 ) ′ = ( x i ′ ( σ i ) 2 − i + y i ′ ) y'_{(i + 1)} = (x'_i(\sigma_i)2^{-i} + y'_i) y(i+1)′=(xi′(σi)2−i+yi′)
(当 i = 0 i = 0 i=0)
x 1 ′ = 0.607 − 0 ⋅ ( + 1 ) ⋅ 1 = 0.607 x'_1 = 0.607 - 0 \cdot (+1 ) \cdot 1 = 0.607 x1′=0.607−0⋅(+1)⋅1=0.607
y 1 ′ = 0.607 ⋅ ( + 1 ) ⋅ 1 + 0 = 0.607 y'_1 = 0.607 \cdot (+1 ) \cdot 1 + 0 = 0.607 y1′=0.607⋅(+1)⋅1+0=0.607
通过 Cordic 算法后,得到
y 9 = 0.5006 ( = sin ( 3 0 ∘ ) ) y_9 = 0.5006 (=\sin(30^{\circ})) y9=0.5006(=sin(30∘))
x 9 = 0.8657 ( = cos ( 3 0 ∘ ) ) x_9 = 0.8657 (=\cos(30^{\circ})) x9=0.8657(=cos(30∘))
所以也可以用 cordic 算法求出正切值的。
或者求反正切值:
计算公式:
posted @ 2014-01-28 23:05 乐富道
基于 FPGA 的 CORDIC 算法实现 —— Verilog 版
善良的一休君于 2017-08-21 20:07:34 发布
目前,学习与开发 FPGA 的程序员们大多使用的是 Verilog HDL 语言(以下简称为 Verilog),关于 Verilog 的诸多优点一休哥就不多介绍了,在此,我们将重点放在 Verilog 的运算操作上。
我们都知道,在 Verilog 中,运算一般分为逻辑运算(与或非等)与算术运算(加减乘除等)。而在一开始学习 Verilog 时,老司机一定会提醒我们,“切记,千万别用‘/’除、‘%’取模(有的也叫取余)和‘**’幂。” 这话说的不无道理,因为这三个运算是不可综合的。但,需清楚理解的是,不可综合的具体意思为不能综合为简单的模块,当我们在程序中调用了这些运算时,‘/’除和‘%’取模在 Quartus 软件中是可以综合的,因此可以正常调用运行,但是会消耗一些逻辑资源,而且会产生延时,即这两个运算的处理时间会很长,可能会大于时序控制时钟的单周期时间。此时呢,我们会建议你调用 IP 核来实现运算操作,虽然这样也会消耗许多逻辑资源,但产生的延时相对较小满足了你基本的需求。
问题好像迎刃而解了,可是仔细一想,除了这些运算,我们还剩下什么?对呀,三角函数,反三角函数,对数函数,指数函数呢,这些函数我们在高中就学习了的呀,难道在 FPGA 中就没有用武之地吗?有人会说,查找表呗,首先将某个运算的所有可能的输入与输出对一一罗列出来,然后放进 Rom 中,然后根据输入查表得到输出。这个方法虽然有效的避免了延时问题,却是一个十分消耗资源的方法,不适合资源紧张的设计。那么,就真的没有办法了吗?
答案就是咱们今天的标题了,CORDIC,而且 CORDIC 是一个比较全能的算法,通过这一原理,我们可以实现三角函数,反三角函数,对数函数,指数函数等多种运算。接下来,一休哥就带领大家来学习 CORDIC 的原理吧。(题外话:请相信一休哥,本文不会让你感到太多痛苦~)
本文将分三个小部分来展开介绍:
1、CORDIC 的基本原理介绍
2、CORDIC 的具体操作流程介绍
3、CORDIC 的旋转模式 ——Verilog 仿真
本文涉及到的全部资料链接:
链接:http://pan.baidu.com/s/1gfrJzMj
密码:x92u
一、CORDIC 的基本原理介绍
CORDIC 算法是一个 “化繁为简” 的算法,将许多复杂的运算转化为一种 “仅需要移位和加法” 的迭代操作。CORDIC 算法有旋转和向量两个模式,分别可以在圆坐标系、线性坐标系和双曲线坐标系使用,从而可以演算出 8 种运算,而结合这 8 种运算也可以衍生出其他许多运算。下表展示了 8 种运算在 CORDIC 算法中实现的条件。
这里写图片描述
首先,我们先从旋转模式下的圆坐标系讲起,这也是 CORDIC 方法最初的用途。
1、CORDIC 的几何原理介绍
假设在 x y xy xy 坐标系中有一个点 P 1 ( x 1 , y 1 ) P1(x_1,y_1) P1(x1,y1),将 P 1 P1 P1 点绕原点旋转 θ \theta θ 角后得到点 P 2 ( x 2 , y 2 ) P2(x_2,y_2) P2(x2,y2)。
这里写图片描述
于是可以得到 P 1 P1 P1 和 P 2 P2 P2 的关系:
x 2 = x 1 cos θ − y 1 sin θ = cos θ ( x 1 − y 1 tan θ ) y 2 = y 1 cos θ + x 1 sin θ = cos θ ( y 1 + x 1 tan θ ) \begin{align*} x_2 &= x_1\cos\theta - y_1\sin\theta = \cos\theta(x_1 - y_1\tan\theta)\\ y_2 &= y_1\cos\theta + x_1\sin\theta = \cos\theta(y_1 + x_1\tan\theta) \end{align*} x2y2=x1cosθ−y1sinθ=cosθ(x1−y1tanθ)=y1cosθ+x1sinθ=cosθ(y1+x1tanθ)
以上就是CORDIC的几何原理部分,而我们该如何深入理解这个几何原理的真正含义呢?
从原理中,我们可以知道,当已知一个点 P 1 P1 P1 的坐标,并已知该点 P 1 P1 P1 旋转的角度 θ \theta θ,则可以根据上述公式求得目标点 P 2 P2 P2 的坐标。然后,麻烦来了,我们需要用FPGA去执行上述运算操作,而FPGA的Verilog语言根本不支持三角函数运算。因此,我们需要对上述式子进行简化操作,将复杂的运算操作转换为一种单一的“迭代位移”算法。那么,接下来我们介绍优化算法部分。
2、CORDIC的优化算法介绍
我们先介绍算法的优化原理:将旋转角 θ \theta θ 细化成若干分固定大小的角度 θ i \theta_i θi,并且规定 θ i \theta_i θi 满足 tan θ i = 2 − i \tan\theta_i = 2^{-i} tanθi=2−i,因此 ∑ θ i \sum\theta_i ∑θi 的值在 [ − 99. 7 ∘ , 99. 7 ∘ ] [-99.7^{\circ},99.7^{\circ}] [−99.7∘,99.7∘] 范围内,如果旋转角 θ \theta θ 超出此范围,则运用简单的三角运算操作即可(加减 π \pi π)。
然后我们需要修改几何原理部分的假设,假设在 x y xy xy 坐标系中有一个点 P 0 ( x 0 , y 0 ) P0(x_0,y_0) P0(x0,y0),将 P 0 P0 P0 点绕原点旋转 θ \theta θ 角后得到点 P n ( x n , y n ) Pn(x_n,y_n) Pn(xn,yn)。
于是可以得到 P 0 P0 P0 和 P n Pn Pn 的关系:
x n = x 0 cos θ − y 0 sin θ = cos θ ( x 0 − y 0 tan θ ) y n = y 0 cos θ + x 0 sin θ = cos θ ( y 0 + x 0 tan θ ) \begin{align*} x_n &= x_0\cos\theta - y_0\sin\theta = \cos\theta(x_0 - y_0\tan\theta)\\ y_n &= y_0\cos\theta + x_0\sin\theta = \cos\theta(y_0 + x_0\tan\theta) \end{align*} xnyn=x0cosθ−y0sinθ=cosθ(x0−y0tanθ)=y0cosθ+x0sinθ=cosθ(y0+x0tanθ)
然后,我们将旋转角 θ \theta θ 细化成 θ i \theta_i θi,由于每次的旋转角度 θ i \theta_i θi 是固定不变的(因为满足 tan θ i = 2 − i \tan\theta_i = 2^{-i} tanθi=2−i),如果一直朝着一个方向旋转则 ∑ θ i \sum\theta_i ∑θi 一定会超过 θ \theta θ(如果 θ \theta θ 在 [ − 99. 7 ∘ , 99. 7 ∘ ] [-99.7^{\circ},99.7^{\circ}] [−99.7∘,99.7∘] 范围内)。因此我们需要对 θ i \theta_i θi 设定一个方向值 d i d_i di。如果旋转角已经大于 θ \theta θ,则 d i d_i di 为 − 1 -1 −1,表示下次旋转为顺时针,即向 θ \theta θ 靠近;如果旋转角已经小于 θ \theta θ,则 d i d_i di 为 1 1 1,表示下次旋转为逆时针,即也向 θ \theta θ 靠近。然后我们可以得到每次旋转的角度值 d i θ i d_i\theta_i diθi,设角度剩余值为 z i + 1 z_{i + 1} zi+1,则有 z i + 1 = z i − d i θ i z_{i + 1} = z_i - d_i\theta_i zi+1=zi−diθi,其中 z 0 z_0 z0 为 θ \theta θ。如此随着 i i i 的增大,角度剩余值 z i + 1 z_{i + 1} zi+1 将会趋近于 0 0 0,此时运算结束。(注:可以发现, d i d_i di 与 z i z_i zi 的符号位相同)
第一次旋转 θ 0 \theta_0 θ0, d 0 d_0 d0 为旋转方向:
x 1 = cos θ 0 ( x 0 − d 0 y 0 tan θ 0 ) y 1 = cos θ 0 ( y 0 + d 0 x 0 tan θ 0 ) \begin{align*} x_1 &= \cos\theta_0(x_0 - d_0y_0\tan\theta_0)\\ y_1 &= \cos\theta_0(y_0 + d_0x_0\tan\theta_0) \end{align*} x1y1=cosθ0(x0−d0y0tanθ0)=cosθ0(y0+d0x0tanθ0)
第二次旋转 θ 1 \theta_1 θ1, d 1 d_1 d1 为旋转方向:
x 2 = cos θ 1 ( x 1 − d 1 y 1 tan θ 1 ) = cos θ 1 cos θ 0 ( x 0 − d 0 y 0 tan θ 0 − d 1 y 0 tan θ 1 − d 1 d 0 x 0 tan θ 1 tan θ 0 ) y 2 = cos θ 1 ( y 1 + d 1 x 1 tan θ 1 ) = cos θ 1 cos θ 0 ( y 0 + d 0 x 0 tan θ 0 + d 1 x 0 tan θ 1 − d 1 d 0 y 0 tan θ 1 tan θ 0 ) \begin{align*} x_2 &= \cos\theta_1(x_1 - d_1y_1\tan\theta_1)\\ &= \cos\theta_1\cos\theta_0(x_0 - d_0y_0\tan\theta_0 - d_1y_0\tan\theta_1 - d_1d_0x_0\tan\theta_1\tan\theta_0)\\ y_2 &= \cos\theta_1(y_1 + d_1x_1\tan\theta_1)\\ &= \cos\theta_1\cos\theta_0(y_0 + d_0x_0\tan\theta_0 + d_1x_0\tan\theta_1 - d_1d_0y_0\tan\theta_1\tan\theta_0) \end{align*} x2y2=cosθ1(x1−d1y1tanθ1)=cosθ1cosθ0(x0−d0y0tanθ0−d1y0tanθ1−d1d0x0tanθ1tanθ0)=cosθ1(y1+d1x1tanθ1)=cosθ1cosθ0(y0+d0x0tanθ0+d1x0tanθ1−d1d0y0tanθ1tanθ0)
大家可能已经发现了,在我们旋转的过程中,式子里一直会有 tan θ i \tan\theta_i tanθi 和 cos θ i \cos\theta_i cosθi,而每次都可以提取出 cos θ i \cos\theta_i cosθi。虽然我们的FPGA无法计算 tan θ i \tan\theta_i tanθi,但我们知道 tan θ i = 2 − i \tan\theta_i = 2^{-i} tanθi=2−i,因此可以执行和 tan θ i \tan\theta_i tanθi 效果相同的移位操作 2 − i 2^{-i} 2−i 来取代 tan θ i \tan\theta_i tanθi。而对于 cos θ i \cos\theta_i cosθi,我们可以事先全部提取出来,然后等待迭代结束之后(角度剩余值 z i + 1 z_{i + 1} zi+1 趋近于 0 0 0,一般当系统设置最大迭代次数为 16 16 16 时 z i + 1 z_{i + 1} zi+1 已经很小了),然后计算出 ∏ i = 0 n − 1 cos θ i \prod_{i = 0}^{n - 1}\cos\theta_i ∏i=0n−1cosθi 的值即可。
总结一下:
迭代公式有三:
x i + 1 = x i − d i y i 2 − i (提取了 cos θ i , 2 − i 等效替换了 tan θ i 之后) y i + 1 = y i + d i x i 2 − i (提取了 cos θ i , 2 − i 等效替换了 tan θ i 之后) z i + 1 = z i − d i θ i \begin{align*} x_{i + 1} &= x_i - d_iy_i2^{-i} \quad \text{(提取了}\cos\theta_i, 2^{-i}\text{等效替换了}\tan\theta_i\text{之后)}\\ y_{i + 1} &= y_i + d_ix_i2^{-i} \quad \text{(提取了}\cos\theta_i, 2^{-i}\text{等效替换了}\tan\theta_i\text{之后)}\\ z_{i + 1} &= z_i - d_i\theta_i \end{align*} xi+1yi+1zi+1=xi−diyi2−i(提取了cosθi,2−i等效替换了tanθi之后)=yi+dixi2−i(提取了cosθi,2−i等效替换了tanθi之后)=zi−diθi
其中 i i i 从 0 0 0 开始迭代,假设当 i = n − 1 i = n - 1 i=n−1 时, z n z_n zn 趋近于 0 0 0,迭代结束。然后对结果乘上 ∏ i = 0 n − 1 cos θ i \prod_{i = 0}^{n - 1}\cos\theta_i ∏i=0n−1cosθi,于是得到点 P n ( x n ∏ i = 0 n − 1 cos θ i , y n ∏ i = 0 n − 1 cos θ i ) Pn(x_n\prod_{i = 0}^{n - 1}\cos\theta_i,y_n\prod_{i = 0}^{n - 1}\cos\theta_i) Pn(xn∏i=0n−1cosθi,yn∏i=0n−1cosθi),此时的点 P n Pn Pn 就近似等于之前假设中的点 P n ( x n , y n ) Pn(x_n,y_n) Pn(xn,yn) 了,所以此时的点 P n Pn Pn 同样满足之前假设得到的公式:
x n ∏ i = 0 n − 1 cos θ i = x 0 cos θ − y 0 sin θ y n ∏ i = 0 n − 1 cos θ i = y 0 cos θ + x 0 sin θ \begin{align*} x_n\prod_{i = 0}^{n - 1}\cos\theta_i &= x_0\cos\theta - y_0\sin\theta\\ y_n\prod_{i = 0}^{n - 1}\cos\theta_i &= y_0\cos\theta + x_0\sin\theta \end{align*} xni=0∏n−1cosθiyni=0∏n−1cosθi=x0cosθ−y0sinθ=y0cosθ+x0sinθ
由于 i i i 从 0 0 0 至 n − 1 n - 1 n−1,所以上式可以转化成下式:
x n = 1 ∏ i = 0 n − 1 cos θ i ( x 0 cos θ − y 0 sin θ ) (其中 i 从 0 至 n − 1 ) y n = 1 ∏ i = 0 n − 1 cos θ i ( y 0 cos θ + x 0 sin θ ) (其中 i 从 0 至 n − 1 ) \begin{align*} x_n &= \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i}(x_0\cos\theta - y_0\sin\theta) \quad \text{(其中}i\text{从}0\text{至}n - 1\text{)}\\ y_n &= \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i}(y_0\cos\theta + x_0\sin\theta) \quad \text{(其中}i\text{从}0\text{至}n - 1\text{)} \end{align*} xnyn=∏i=0n−1cosθi1(x0cosθ−y0sinθ)(其中i从0至n−1)=∏i=0n−1cosθi1(y0cosθ+x0sinθ)(其中i从0至n−1)
注意:上式中的 x n x_n xn, y n y_n yn 是经过迭代后的结果,而不是之前假设中的点 P n ( x n , y n ) Pn(x_n,y_n) Pn(xn,yn)。了解这点是十分重要的。
根据高中学的三角函数关系,可以知道 cos θ i = 1 ( 1 + tan 2 θ i ) 1 2 = 1 ( 1 + 2 − 2 i ) 1 2 \cos\theta_i = \frac{1}{(1 + \tan^{2}\theta_i)^{\frac{1}{2}}} = \frac{1}{(1 + 2^{-2i})^{\frac{1}{2}}} cosθi=(1+tan2θi)211=(1+2−2i)211,而 1 ( 1 + 2 − 2 i ) 1 2 \frac{1}{(1 + 2^{-2i})^{\frac{1}{2}}} (1+2−2i)211 的极值为 1 1 1,因此我们可以得出一个结论:当 i i i 的次数很大时, ∏ i = 0 n − 1 cos θ i \prod_{i = 0}^{n - 1}\cos\theta_i ∏i=0n−1cosθi 的值趋于一个常数。
关于如何计算 ∏ cos θ i \prod\cos\theta_i ∏cosθi 的代码如下所示:
close all;
clear;
clc;
% 初始化
die = 16;%迭代次数
jiao = zeros(die,1);%每次旋转的角度
cos_value = zeros(die,1);%每次旋转的角度的余弦值
K = zeros(die,1);%余弦值的N元乘积
K_1 = zeros(die,1);%余弦值的N元乘积的倒数
for i = 1 : diea = 2^(-(i-1));jiao(i) = atan(a);cos_value(i) = cos(jiao(i));if( i == 1)K(i) = cos_value(i);K_1(i) = 1/K(i);elseK(i) = K(i-1)*cos_value(i);K_1(i) = 1/K(i);end
end
jiao = vpa(rad2deg(jiao)*256,10)
cos_value = vpa(cos_value,10)
K = vpa(K,10)
K_1 = vpa(K_1,10)
这里写图片描述
从上表也可以看出,当迭代次数为 16 16 16, i = 15 i = 15 i=15 时, cos θ i \cos\theta_i cosθi 的值已经非常趋近于 1 1 1 了, ∏ i = 0 n − 1 cos θ i \prod_{i = 0}^{n - 1}\cos\theta_i ∏i=0n−1cosθi 的值则约等于 0.607253 0.607253 0.607253, 1 ∏ i = 0 n − 1 cos θ i \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i} ∏i=0n−1cosθi1 为 1.64676 1.64676 1.64676。所以当迭代次数等于 16 16 16 时,通过迭代得到的点 P n Pn Pn 坐标已经非常接近之前假设中的点 P n Pn Pn。所以,当迭代次数等于 16 16 16 时,这个式子是成立的。
x n = 1 ∏ i = 0 n − 1 cos θ i ( x 0 cos θ − y 0 sin θ ) ( 其中 i 从 0 至 n − 1 ) y n = 1 ∏ i = 0 n − 1 cos θ i ( y 0 cos θ + x 0 sin θ ) ( 其中 i 从 0 至 n − 1 ) \begin{align*} x_n &= \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i}(x_0\cos\theta - y_0\sin\theta) \quad (\text{其中 }i \text{ 从 }0 \text{ 至 }n - 1)\\ y_n &= \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i}(y_0\cos\theta + x_0\sin\theta) \quad (\text{其中 }i \text{ 从 }0 \text{ 至 }n - 1) \end{align*} xnyn=∏i=0n−1cosθi1(x0cosθ−y0sinθ)(其中 i 从 0 至 n−1)=∏i=0n−1cosθi1(y0cosθ+x0sinθ)(其中 i 从 0 至 n−1)
此时,已知条件有三个 x 0 x_0 x0、 y 0 y_0 y0 和 θ \theta θ。通过 16 16 16 次迭代,我们可以得到 x n x_n xn 和 y n y_n yn。而式中的 ∏ i = 0 n − 1 cos θ i \prod_{i = 0}^{n - 1}\cos\theta_i ∏i=0n−1cosθi 是个随 i i i 变化的值,我们可以预先将其值存入系统中。
然后,我们人为设置 x 0 = ∏ i = 0 n − 1 cos θ i x_0 = \prod_{i = 0}^{n - 1}\cos\theta_i x0=∏i=0n−1cosθi, y 0 = 0 y_0 = 0 y0=0,则根据等式, x n = cos θ x_n = \cos\theta xn=cosθ, y n = sin θ y_n = \sin\theta yn=sinθ。其中 1 ∏ i = 0 n − 1 cos θ i \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i} ∏i=0n−1cosθi1 的值我们也同样预先存入系统中。如此,我们就实现了正弦和余弦操作了。
二、CORDIC 的具体操作流程介绍
1、CORDIC 的旋转模式
由于算法较复杂,一休哥再总结一些具体的操作流程。
1、 设置迭代次数为 16 16 16,则 x 0 = 0.607253 x_0 = 0.607253 x0=0.607253, y 0 = 0 y_0 = 0 y0=0,并输入待计算的角度 θ \theta θ, θ \theta θ 在 [ − 99. 7 ∘ , 99. 7 ∘ ] [-99.7^{\circ}, 99.7^{\circ}] [−99.7∘,99.7∘] 范围内。
2、 根据三个迭代公式进行迭代, i i i 从 0 0 0 至 15 15 15:
x i + 1 = x i − d i y i 2 − i y i + 1 = y i + d i x i 2 − i z i + 1 = z i − d i θ i \begin{align*} x_{i + 1} &= x_i - d_iy_i2^{-i}\\ y_{i + 1} &= y_i + d_ix_i2^{-i}\\ z_{i + 1} &= z_i - d_i\theta_i \end{align*} xi+1yi+1zi+1=xi−diyi2−i=yi+dixi2−i=zi−diθi
注: z 0 = θ z_0 = \theta z0=θ, d i d_i di 与 z i z_i zi 同符号。
3、 经过 16 16 16 次迭代计算后,得到的 x 16 x_{16} x16 和 y 16 y_{16} y16 分别为 cos θ \cos\theta cosθ 和 sin θ \sin\theta sinθ。
至此,关于 CORDIC 的三角函数 cos θ \cos\theta cosθ 和 sin θ \sin\theta sinθ 的计算原理讲解结束。
关于 CORDIC 算法计算三角函数 cos θ \cos\theta cosθ 和 sin θ \sin\theta sinθ 的 MATLAB 代码 如下所示:
close all;
clear;
clc;
% 初始化
die = 16;%迭代次数
x = zeros(die+1,1);
y = zeros(die+1,1);
z = zeros(die+1,1);
x(1) = 0.607253;%初始设置
z(1) = pi/4;%待求角度θ
%迭代操作
for i = 1:dieif z(i) >= 0d = 1;elsed = -1;endx(i+1) = x(i) - d*y(i)*(2^(-(i-1)));y(i+1) = y(i) + d*x(i)*(2^(-(i-1)));z(i+1) = z(i) - d*atan(2^(-(i-1)));
end
cosa = vpa(x(17),10)
sina = vpa(y(17),10)
c = vpa(z(17),10)
2、CORDIC的向量模式
讲完了旋转模式后,我们接着讲讲向量模式下的圆坐标系。
在这里,我们需从头来过了,假设在 x y xy xy 坐标系中有一个点 P 0 ( x 0 , y 0 ) P0(x_0,y_0) P0(x0,y0) ,将 P 0 P0 P0 点绕原点旋转 θ \theta θ 角后得到点 P n ( x n , 0 ) Pn(x_n,0) Pn(xn,0) , θ \theta θ 在 [ − 99. 7 ∘ , 99. 7 ∘ ] [-99.7^{\circ},99.7^{\circ}] [−99.7∘,99.7∘] 范围内。
于是可以得到 P 0 P0 P0 和 P n Pn Pn 的关系:
x n = x 0 cos θ − y 0 sin θ = cos θ ( x 0 − y 0 tan θ ) y n = y 0 cos θ + x 0 sin θ = cos θ ( y 0 + x 0 tan θ ) = 0 \begin{align*} x_n &= x_0\cos\theta - y_0\sin\theta = \cos\theta(x_0 - y_0\tan\theta)\\ y_n &= y_0\cos\theta + x_0\sin\theta = \cos\theta(y_0 + x_0\tan\theta) = 0 \end{align*} xnyn=x0cosθ−y0sinθ=cosθ(x0−y0tanθ)=y0cosθ+x0sinθ=cosθ(y0+x0tanθ)=0
如何得到 P n ( x n , y n ) Pn(x_n,y_n) Pn(xn,yn) 一直是我们的目标。而此时,我们还是列出那几个等式:
根据三个迭代公式进行迭代, i i i 从 0 0 0 至 15 15 15 :
x i + 1 = x i − d i y i 2 − i y i + 1 = y i + d i x i 2 − i z i + 1 = z i − d i θ i \begin{align*} x_{i + 1} &= x_i - d_iy_i2^{-i}\\ y_{i + 1} &= y_i + d_ix_i2^{-i}\\ z_{i + 1} &= z_i - d_i\theta_i \end{align*} xi+1yi+1zi+1=xi−diyi2−i=yi+dixi2−i=zi−diθi
不过此时我们尝试改变初始条件:
设置迭代次数为 16 16 16 ,则 x 0 = x x_0 = x x0=x , y 0 = y y_0 = y y0=y , z 0 = 0 z_0 = 0 z0=0 , d i d_i di 与 y i y_i yi 的符号相反。表示,经过 n n n 次旋转,使 P n Pn Pn 靠近 x x x 轴。
因此,当迭代结束之后, P n Pn Pn 将近似接近 x x x 轴,此时 y n = 0 y_n = 0 yn=0 ,可知旋转了 θ \theta θ ,即 z n = θ = arctan ( y / x ) z_n = \theta = \arctan(y/x) zn=θ=arctan(y/x) 。
而
x n = 1 ∏ i = 0 n − 1 cos θ i ( x 0 cos θ − y 0 sin θ ) ( 其中 i 从 0 至 n − 1 ) y n = 1 ∏ i = 0 n − 1 cos θ i ( y 0 cos θ + x 0 sin θ ) ( 其中 i 从 0 至 n − 1 ) \begin{align*} x_n &= \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i}(x_0\cos\theta - y_0\sin\theta) \quad (\text{其中 }i \text{ 从 }0 \text{ 至 }n - 1)\\ y_n &= \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i}(y_0\cos\theta + x_0\sin\theta) \quad (\text{其中 }i \text{ 从 }0 \text{ 至 }n - 1) \end{align*} xnyn=∏i=0n−1cosθi1(x0cosθ−y0sinθ)(其中 i 从 0 至 n−1)=∏i=0n−1cosθi1(y0cosθ+x0sinθ)(其中 i 从 0 至 n−1)
因此,可得 y cos θ + x sin θ = 0 y\cos\theta + x\sin\theta = 0 ycosθ+xsinθ=0 ,
x n = 1 ∏ i = 0 n − 1 cos θ i ( x cos θ − y sin θ ) = 1 ∏ i = 0 n − 1 cos θ i { [ ( x cos θ − y sin θ ) 2 ] 1 2 } = 1 ∏ i = 0 n − 1 cos θ i { [ x 2 cos 2 θ + y 2 sin 2 θ − 2 x y sin θ cos θ ] 1 2 } = 1 ∏ i = 0 n − 1 cos θ i { [ x 2 cos 2 θ + y 2 sin 2 θ + y 2 cos 2 θ + x 2 sin 2 θ ] 1 2 } = 1 ∏ i = 0 n − 1 cos θ i { [ x 2 + y 2 ] 1 2 } \begin{align*} x_n &= \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i}(x\cos\theta - y\sin\theta)\\ &= \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i}\{ [ (x\cos\theta - y\sin\theta)^2]^{\frac{1}{2}}\}\\ &= \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i}\{ [ x^2\cos^2\theta + y^2\sin^2\theta - 2xy\sin\theta\cos\theta]^{\frac{1}{2}}\}\\ &= \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i}\{ [ x^2\cos^2\theta + y^2\sin^2\theta + y^2\cos^2\theta + x^2\sin^2\theta]^{\frac{1}{2}}\}\\ &= \frac{1}{\prod_{i = 0}^{n - 1}\cos\theta_i}\{ [ x^2 + y^2]^{\frac{1}{2}}\} \end{align*} xn=∏i=0n−1cosθi1(xcosθ−ysinθ)=∏i=0n−1cosθi1{[(xcosθ−ysinθ)2]21}=∏i=0n−1cosθi1{[x2cos2θ+y2sin2θ−2xysinθcosθ]21}=∏i=0n−1cosθi1{[x2cos2θ+y2sin2θ+y2cos2θ+x2sin2θ]21}=∏i=0n−1cosθi1{[x2+y2]21}
由上可以知道,我们通过迭代,就算出了反正切函数 z n = θ = arctan ( y / x ) z_n = \theta = \arctan(y/x) zn=θ=arctan(y/x) ,以及向量 O P 0 → ( x , y ) \overrightarrow{OP_0}(x,y) OP0(x,y) 的长度 d = x n ⋅ ∏ i = 0 n − 1 cos θ i d = x_n \cdot \prod_{i = 0}^{n - 1}\cos\theta_i d=xn⋅∏i=0n−1cosθi 。
关于反正切函数,一休哥要多啰嗦几句了,由于 θ \theta θ 在 [ − 99. 7 ∘ , 99. 7 ∘ ] [-99.7^{\circ},99.7^{\circ}] [−99.7∘,99.7∘] 范围内,所以我们输入向量 O P 0 → ( x , y ) \overrightarrow{OP_0}(x,y) OP0(x,y) 时,需要保证其在第一、四象限。
关于 CORDIC 算法计算反三角函数 arctan θ \arctan\theta arctanθ 的 MATLAB 代码如下所示:
close all;
clear;
clc;
% 初始化
die = 16;%迭代次数
x = zeros(die+1,1);
y = zeros(die+1,1);
z = zeros(die+1,1);
x(1) = 100;%初始设置
y(1) = 200;%初始设置
k = 0.607253;%初始设置
%迭代操作
for i = 1:dieif y(i) >= 0d = -1;elsed = 1;endx(i+1) = x(i) - d*y(i)*(2^(-(i-1)));y(i+1) = y(i) + d*x(i)*(2^(-(i-1)));z(i+1) = z(i) - d*atan(2^(-(i-1)));
end
d = vpa(x(17)*k,10)
a = vpa(y(17),10)
c = vpa(rad2deg(z(17)),10)
三、CORDIC 的旋转模式——Verilog 仿真
一休哥在编写CORDIC算法时,采用了 16 级流水线,仿真效果十分明显。以下是顶层文件的代码。
为了避免浮点运算,为了满足精度要求,一休哥对每个变量都放大了 2 16 2^{16} 216 倍,并且引入了有符号型 reg \text{reg} reg 和算术右移。
关于 Verilog 代码的编写,一休哥已经不想多说了,因为代码是完全符合我之前所讲的 CORDIC 的原理与 MATLAB 仿真代码。相信大家在看完本文的前两个部分之后,对 Verilog 的理解应该不是难事儿。
module Cordic_Test
(CLK_50M,RST_N,Phase,Sin,Cos,Error
);input CLK_50M;
input RST_N;
input [31:0] Phase;
output [31:0] Sin;
output [31:0] Cos;
output [31:0] Error;`define rot0 32'd2949120 //45度*2^16
`define rot1 32'd1740992 //26.5651度*2^16
`define rot2 32'd919872 //14.0362度*2^16
`define rot3 32'd466944 //7.1250度*2^16
`define rot4 32'd234368 //3.5763度*2^16
`define rot5 32'd117312 //1.7899度*2^16
`define rot6 32'd58688 //0.8952度*2^16
`define rot7 32'd29312 //0.4476度*2^16
`define rot8 32'd14656 //0.2238度*2^16
`define rot9 32'd7360 //0.1119度*2^16
`define rot10 32'd3648 //0.0560度*2^16
`define rot11 32'd1856 //0.0280度*2^16
`define rot12 32'd896 //0.0140度*2^16
`define rot13 32'd448 //0.0070度*2^16
`define rot14 32'd256 //0.0035度*2^16
`define rot15 32'd128 //0.0018度*2^16parameter Pipeline = 16;
parameter K = 32'h09b74; //K=0.607253*2^16,32'h09b74,reg signed [31:0] Sin;
reg signed [31:0] Cos;
reg signed [31:0] Error;
reg signed [31:0] x0=0,y0=0,z0=0;
reg signed [31:0] x1=0,y1=0,z1=0;
reg signed [31:0] x2=0,y2=0,z2=0;
reg signed [31:0] x3=0,y3=0,z3=0;
reg signed [31:0] x4=0,y4=0,z4=0;
reg signed [31:0] x5=0,y5=0,z5=0;
reg signed [31:0] x6=0,y6=0,z6=0;
reg signed [31:0] x7=0,y7=0,z7=0;
reg signed [31:0] x8=0,y8=0,z8=0;
reg signed [31:0] x9=0,y9=0,z9=0;
reg signed [31:0] x10=0,y10=0,z10=0;
reg signed [31:0] x11=0,y11=0,z11=0;
reg signed [31:0] x12=0,y12=0,z12=0;
reg signed [31:0] x13=0,y13=0,z13=0;
reg signed [31:0] x14=0,y14=0,z14=0;
reg signed [31:0] x15=0,y15=0,z15=0;
reg signed [31:0] x16=0,y16=0,z16=0;
reg [ 1:0] Quadrant [Pipeline:0];always @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx0 <= 1'b0; y0 <= 1'b0;z0 <= 1'b0;endelsebeginx0 <= K;y0 <= 32'd0;z0 <= Phase[15:0] << 16;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx1 <= 1'b0; y1 <= 1'b0;z1 <= 1'b0;endelse if(z0[31])beginx1 <= x0 + y0;y1 <= y0 - x0;z1 <= z0 + `rot0;endelsebeginx1 <= x0 - y0;y1 <= y0 + x0;z1 <= z0 - `rot0;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx2 <= 1'b0; y2 <= 1'b0;z2 <= 1'b0;endelse if(z1[31])beginx2 <= x1 + (y1 >>> 1);y2 <= y1 - (x1 >>> 1);z2 <= z1 + `rot1;endelsebeginx2 <= x1 - (y1 >>> 1);y2 <= y1 + (x1 >>> 1);z2 <= z1 - `rot1;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx3 <= 1'b0; y3 <= 1'b0;z3 <= 1'b0;endelse if(z2[31])beginx3 <= x2 + (y2 >>> 2);y3 <= y2 - (x2 >>> 2);z3 <= z2 + `rot2;endelsebeginx3 <= x2 - (y2 >>> 2);y3 <= y2 + (x2 >>> 2);z3 <= z2 - `rot2;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx4 <= 1'b0; y4 <= 1'b0;z4 <= 1'b0;endelse if(z3[31])beginx4 <= x3 + (y3 >>> 3);y4 <= y3 - (x3 >>> 3);z4 <= z3 + `rot3;endelsebeginx4 <= x3 - (y3 >>> 3);y4 <= y3 + (x3 >>> 3);z4 <= z3 - `rot3;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx5 <= 1'b0; y5 <= 1'b0;z5 <= 1'b0;endelse if(z4[31])beginx5 <= x4 + (y4 >>> 4);y5 <= y4 - (x4 >>> 4);z5 <= z4 + `rot4;endelsebeginx5 <= x4 - (y4 >>> 4);y5 <= y4 + (x4 >>> 4);z5 <= z4 - `rot4;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx6 <= 1'b0; y6 <= 1'b0;z6 <= 1'b0;endelse if(z5[31])beginx6 <= x5 + (y5 >>> 5);y6 <= y5 - (x5 >>> 5);z6 <= z5 + `rot5;endelsebeginx6 <= x5 - (y5 >>> 5);y6 <= y5 + (x5 >>> 5);z6 <= z5 - `rot5;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx7 <= 1'b0; y7 <= 1'b0;z7 <= 1'b0;endelse if(z6[31])beginx7 <= x6 + (y6 >>> 6);y7 <= y6 - (x6 >>> 6);z7 <= z6 + `rot6;endelsebeginx7 <= x6 - (y6 >>> 6);y7 <= y6 + (x6 >>> 6);z7 <= z6 - `rot6;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx8 <= 1'b0; y8 <= 1'b0;z8 <= 1'b0;endelse if(z7[31])beginx8 <= x7 + (y7 >>> 7);y8 <= y7 - (x7 >>> 7);z8 <= z7 + `rot7;endelsebeginx8 <= x7 - (y7 >>> 7);y8 <= y7 + (x7 >>> 7);z8 <= z7 - `rot7;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx9 <= 1'b0; y9 <= 1'b0;z9 <= 1'b0;endelse if(z8[31])beginx9 <= x8 + (y8 >>> 8);y9 <= y8 - (x8 >>> 8);z9 <= z8 + `rot8;endelsebeginx9 <= x8 - (y8 >>> 8);y9 <= y8 + (x8 >>> 8);z9 <= z8 - `rot8;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx10 <= 1'b0; y10 <= 1'b0;z10 <= 1'b0;endelse if(z9[31])beginx10 <= x9 + (y9 >>> 9);y10 <= y9 - (x9 >>> 9);z10 <= z9 + `rot9;endelsebeginx10 <= x9 - (y9 >>> 9);y10 <= y9 + (x9 >>> 9);z10 <= z9 - `rot9;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx11 <= 1'b0; y11 <= 1'b0;z11 <= 1'b0;endelse if(z10[31])beginx11 <= x10 + (y10 >>> 10);y11 <= y10 - (x10 >>> 10);z11 <= z10 + `rot10;endelsebeginx11 <= x10 - (y10 >>> 10);y11 <= y10 + (x10 >>> 10);z11 <= z10 - `rot10;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx12 <= 1'b0; y12 <= 1'b0;z12 <= 1'b0;endelse if(z11[31])beginx12 <= x11 + (y11 >>> 11);y12 <= y11 - (x11 >>> 11);z12 <= z11 + `rot11;endelsebeginx12 <= x11 - (y11 >>> 11);y12 <= y11 + (x11 >>> 11);z12 <= z11 - `rot11;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx13 <= 1'b0; y13 <= 1'b0;z13 <= 1'b0;endelse if(z12[31])beginx13 <= x12 + (y12 >>> 12);y13 <= y12 - (x12 >>> 12);z13 <= z12 + `rot12;endelsebeginx13 <= x12 - (y12 >>> 12);y13 <= y12 + (x12 >>> 12);z13 <= z12 - `rot12;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx14 <= 1'b0; y14 <= 1'b0;z14 <= 1'b0;endelse if(z13[31])beginx14 <= x13 + (y13 >>> 13);y14 <= y13 - (x13 >>> 13);z14 <= z13 + `rot13;endelsebeginx14 <= x13 - (y13 >>> 13);y14 <= y13 + (x13 >>> 13);z14 <= z13 - `rot13;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx15 <= 1'b0; y15 <= 1'b0;z15 <= 1'b0;endelse if(z14[31])beginx15 <= x14 + (y14 >>> 14);y15 <= y14 - (x14 >>> 14);z15 <= z14 + `rot14;endelsebeginx15 <= x14 - (y14 >>> 14);y15 <= y14 + (x14 >>> 14);z15 <= z14 - `rot14;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginx16 <= 1'b0; y16 <= 1'b0;z16 <= 1'b0;endelse if(z15[31])beginx16 <= x15 + (y15 >>> 15);y16 <= y15 - (x15 >>> 15);z16 <= z15 + `rot15;endelsebeginx16 <= x15 - (y15 >>> 15);y16 <= y15 + (x15 >>> 15);z16 <= z15 - `rot15;end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginQuadrant[0] <= 1'b0;Quadrant[1] <= 1'b0;Quadrant[2] <= 1'b0;Quadrant[3] <= 1'b0;Quadrant[4] <= 1'b0;Quadrant[5] <= 1'b0;Quadrant[6] <= 1'b0;Quadrant[7] <= 1'b0;Quadrant[8] <= 1'b0;Quadrant[9] <= 1'b0;Quadrant[10] <= 1'b0;Quadrant[11] <= 1'b0;Quadrant[12] <= 1'b0;Quadrant[13] <= 1'b0;Quadrant[14] <= 1'b0;Quadrant[15] <= 1'b0;Quadrant[16] <= 1'b0;endelsebeginQuadrant[0] <= Phase[17:16];Quadrant[1] <= Quadrant[0];Quadrant[2] <= Quadrant[1];Quadrant[3] <= Quadrant[2];Quadrant[4] <= Quadrant[3];Quadrant[5] <= Quadrant[4];Quadrant[6] <= Quadrant[5];Quadrant[7] <= Quadrant[6];Quadrant[8] <= Quadrant[7];Quadrant[9] <= Quadrant[8];Quadrant[10] <= Quadrant[9];Quadrant[11] <= Quadrant[10];Quadrant[12] <= Quadrant[11];Quadrant[13] <= Quadrant[12];Quadrant[14] <= Quadrant[13];Quadrant[15] <= Quadrant[14];Quadrant[16] <= Quadrant[15];end
endalways @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)beginCos <= 1'b0;Sin <= 1'b0;Error <= 1'b0;endelsebeginError <= z16;case(Quadrant[16])2'b00: //if the Phase is in first Quadrant,the Sin(X)=Sin(A),Cos(X)=Cos(A)beginCos <= x16;Sin <= y16;end2'b01: //if the Phase is in second Quadrant,the Sin(X)=Sin(A+90)=CosA,Cos(X)=Cos(A+90)=-SinAbeginCos <= ~(y16) + 1'b1;//-SinSin <= x16;//Cosend2'b10: //if the Phase is in third Quadrant,the Sin(X)=Sin(A+180)=-SinA,Cos(X)=Cos(A+180)=-CosAbeginCos <= ~(x16) + 1'b1;//-CosSin <= ~(y16) + 1'b1;//-Sinend2'b11: //if the Phase is in forth Quadrant,the Sin(X)=Sin(A+270)=-CosA,Cos(X)=Cos(A+270)=SinAbeginCos <= y16;//SinSin <= ~(x16) + 1'b1;//-Cosendendcaseend
endendmodule
以下是testbench文件代码
`timescale 1 ps/ 1 psmodule Cordic_Test_tb;// Inputs
reg CLK_50M;
reg RST_N;
reg [15:0] cnt;
reg [15:0] cnt_n;
reg [31:0] Phase;
reg [31:0] Phase_n;
wire [31:0] Sin;
wire [31:0] Cos;
wire [31:0] Error;// Instantiate the Unit Under Test (UUT)
Cordic_Test uut
(.CLK_50M (CLK_50M ),.RST_N (RST_N ),.Phase (Phase ),.Sin (Sin ),.Cos (Cos ),.Error (Error )
);initial
begin#0 CLK_50M = 1'b0;#10000 RST_N = 1'b0;#10000 RST_N = 1'b1;#10000000 $stop;
end
always #10000
beginCLK_50M = ~CLK_50M;
end
always @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)cnt <= 1'b0;elsecnt <= cnt_n;
endalways @ (*)
beginif(cnt == 16'd359)cnt_n = 1'b0;elsecnt_n = cnt + 1'b1;
end
//生成相位0-359度,Phase[17:16]为相位的象限,Phase[15:10]为相位的值
always @ (posedge CLK_50M or negedge RST_N)
beginif(!RST_N)Phase <= 1'b0;elsePhase <= Phase_n;
endalways @ (*)
beginif(cnt <= 16'd90)Phase_n = cnt;else if(cnt > 16'd90 && cnt <= 16'd180)Phase_n = {2'd01,cnt - 16'd90};else if(cnt > 16'd180 && cnt <= 16'd270)Phase_n = {2'd10,cnt - 16'd180};else if(cnt > 16'd270)Phase_n = {2'd11,cnt - 16'd270};
endendmodule
最后来一张效果图,可以发现,我们的 16 级流水线已经正常的运行起来了,由于我们仿真输入的相位值为 0-359 度循环,因此 sin 和 cos 也循环了~~~
CORDIC 算法详解及 FPGA 实现
ngany 于 2021-05-30 18:52:04 发布
CORDIC 算法详解
1 平面坐标系旋转
CORDIC 算法的思想是通过迭代的方法,使得累计旋转过的角度的和无限接近目标角度。它是一种数值计算逼近的方法,运算只有移位和加减。
通过圆坐标系可以了解 CORDIC 算法的基本思想,如图 1 所示,初始向量 ( x 1 , y 1 ) \left (x_{1},y_{1} \right) (x1,y1)旋转 θ \theta θ角度之后得到向量 ( x 2 , y 2 ) \left ( x_{2},y_{2} \right) (x2,y2),两者之间满足(公式 1)关系。
图 1 CORDIC 算法原理示意图
$$\left{ x2=x1cosθn−y1sinθny2=y1cosθn−x1sinθn\begin {matrix} x_{2} = x_{1} \cos\theta_{n} - y_{1} \sin\theta_{n} \ y_{2} = y_{1} \cos\theta_{n} - x_{1}\sin\theta_{n} \ \end {matrix} \right.$$
通过提取因数 cos θ \cos\theta cosθ,方程式可以改写成下面的形式:
x 2 = x 1 cos θ − y 1 sin θ = cos θ ( x 1 − y 1 tan θ ) y 2 = y 1 cos θ + x 1 sin θ = cos θ ( y 1 + x 1 tan θ ) \begin{align*} x_{2} &= x_{1} \cos\theta - y_{1} \sin\theta = \cos\theta(x_{1} - y_{1}\tan\theta) \\ y_{2} &= y_{1} \cos\theta + x_{1} \sin\theta = \cos\theta(y_{1} + x_{1}\tan\theta) \end{align*} x2y2=x1cosθ−y1sinθ=cosθ(x1−y1tanθ)=y1cosθ+x1sinθ=cosθ(y1+x1tanθ)
2 伪旋转
如果去掉 cos θ \cos\theta cosθ,我们可以的到伪旋转方程式(公式 3),即旋转的角度是正确的,但是 x x x, y y y 的值增加了 cos − 1 θ \cos^{- 1}\theta cos−1θ 倍,由于 cos − 1 θ > 1 \cos^{- 1}\theta > 1 cos−1θ>1,所以模值变大。这里并不能通过数学方法去除 cos θ \cos\theta cosθ项,但是可以化简坐标平面旋转的计算。
x ^ 2 = x 1 − y 1 tan θ y ^ 2 = y 1 + x 1 tan θ \begin{align*} \widehat{x}_{2} &= x_{1} - y_{1}\tan\theta \\ \widehat{y}_{2} &= y_{1} + x_{1}\tan\theta \end{align*} x 2y 2=x1−y1tanθ=y1+x1tanθ
图 2 去除 cos θ \cos\theta cosθ后伪坐标系
3 CORDIC 方法
这里为了便于硬件的计算,采用迭代的思想,旋转角度 θ \theta θ 可以通过若干步实现,每一步旋转一个角度 θ i \theta^{i} θi 。并且约定每一次旋转的角度的正切值为 2 的倍数,即 tan θ i = 2 − i {\tan\theta^{i} = 2}^{- i} tanθi=2−i 。下面表格是用于 CORDIC 算法中每个迭代 i i i 的旋转角度。这样,乘以正切值就可以变成移位操作。
表 1 迭代角度 θ i \theta^{i} θi正切值
i i i | θ i \theta^{i} θi(degree) | tan θ i = 2 − i {\tan\theta^{i} = 2}^{- i} tanθi=2−i |
---|---|---|
0 | 45.0 | 1 |
1 | 26.555051177 | 0.5 |
2 | 14.036243467 | 0.25 |
3 | 7.125016348 | 0.125 |
4 | 3.576334374 | 0.0625 |
4 角度累加器
引入控制算子 d i d_{i} di,用于确定旋转的方向。其中第 i i i 步顺时针旋转时 d i = − 1 d_{i} = - 1 di=−1,逆时针旋转 d i = 1 d_{i} = 1 di=1。前面的伪旋转坐标方程现在可以表示为:
x i + 1 = x i − d i 2 − i y i y i + 1 = y i + d i 2 − i x i \begin{align*} x_{i + 1} &= x_{i} - d_{i}2^{- i}y_{i} \\ y_{i + 1} &= y_{i} + d_{i}2^{- i}x_{i} \end{align*} xi+1yi+1=xi−di2−iyi=yi+di2−ixi
这里,我们引入第三个方程,被称为角度累加器,用来在每次迭代过程中追踪累加的旋转角度,表示第 n n n 次旋转后剩下未旋转的角度,定义为
z i + 1 = z i − d i θ i z_{i + 1} = z_{i} - d_{i} \theta^{i} zi+1=zi−diθi,其中 d i = ± 1 d_{i} = \pm 1 di=±1
5 移位 - 加法算法
因此,原始的算法现在已经被简化为使用向量的伪旋转方程式(公式 6)来表示迭代(移位 - 加法)算法。
-
2 次移位;
( 2 − i 2^{- i} 2−i用移位实现,每右移 n 位就把原来数值乘以 2 的 - i 次方了) -
1 次查找表;(每一次迭代都会有一个固定角度 θ i \theta^{i} θi的累加,这个角度是 2 − i 2^{- i} 2−i对应的角度值,使用查表实现。 θ i = arc tan 2 − i \theta^{i} = {\text {arc}\tan} 2^{- i} θi=arctan2−i)
-
3 次加法;(x,y,z 的累加,共三次)
x i + 1 = x i − d i 2 − i y i y i + 1 = y i + d i 2 − i x i z i + 1 = z i − d i θ i \begin{align*} x_{i + 1} &= x_{i} - d_{i}2^{- i}y_{i} \\ y_{i + 1} &= y_{i} + d_{i}2^{- i}x_{i} \\ z_{i + 1} &= z_{i} - d_{i}\theta_{i} \end{align*} xi+1yi+1zi+1=xi−di2−iyi=yi+di2−ixi=zi−diθi
6 伸缩因子
当简化算法以伪旋转时, cos θ \cos\theta cosθ项被忽略,这样 ( x n , y n ) \left ( x_{n},y_{n} \right) (xn,yn) 就被伸缩了 K n K_{n} Kn 倍。如果迭代次数已知,可以预先计算伸缩因子 K n K_{n} Kn。同样 1 / K n 1/K_{n} 1/Kn也可被预先计算以得到 ( x n , y n ) \left ( x_{n},y_{n} \right) (xn,yn) 的真值。
K n = ∏ n 1 cos θ i = ∏ n ( 1 + 2 − 2 i ) K_{n} = \prod_{n}^{}\frac {1}{\cos\theta^{i}} = \prod_{n}^{}\left ( \sqrt {1 + 2^{- 2i}} \right) Kn=∏ncosθi1=∏n(1+2−2i)
这里 K n − > 1.64676 K_{n} - > 1.64676 Kn−>1.64676,当 n − > ∞ n - > \infty n−>∞
1 / K n − > 0.607253 1/K_{n} - > 0.607253 1/Kn−>0.607253,当 n − > ∞ n - > \infty n−>∞
7 旋转模式
CORDIC 方法有两种模式,旋转模式和向量模式。工作模式决定了控制算子 d i d_{i} di 的条件。在旋转模式中,旋转剩余角度初始变量 z 0 = θ z_{0} = \theta z0=θ,经过 n 次旋转后,使 z 0 = 0 z_{0} = 0 z0=0。整个旋转过程可以表示为一系列旋转角度不断偏摆,从而逼近所需旋转角度的迭代过程。n 次迭代后可以得到:
x n = K n ( x 0 cos z 0 − y 0 sin z 0 ) y n = K n ( y 0 cos z 0 + x 0 sin z 0 ) z n = 0 \begin{align*} x_{n} &= K_{n}(x_{0} \cos z_{0} - y_{0} \sin z_{0}) \\ y_{n} &= K_{n}(y_{0} \cos z_{0} + x_{0} \sin z_{0}) \\ z_{n} &= 0 \end{align*} xnynzn=Kn(x0cosz0−y0sinz0)=Kn(y0cosz0+x0sinz0)=0
通过设置 x 0 = 1 / K n = 0.6073 x_{0} = 1/K_{n} = 0.6073 x0=1/Kn=0.6073 和 y 0 = 0 y_{0} = 0 y0=0 可以计算 cos z 0 \cos z_{0} cosz0和 sin z 0 \sin z_{0} sinz0。分析可知 CORDIC 算法在圆周系统旋转模式下可以用来计算一个输入角的正弦值、余弦值。
x n = K n ( x 0 cos z 0 − y 0 sin z 0 ) = cos θ y n = K n ( y 0 cos z 0 + x 0 sin z 0 ) = sin θ \begin{align*} x_{n} &= K_{n}(x_{0} \cos z_{0} - y_{0} \sin z_{0}) = \cos\theta \\ y_{n} &= K_{n}(y_{0} \cos z_{0} + x_{0} \sin z_{0}) = \sin\theta \end{align*} xnyn=Kn(x0cosz0−y0sinz0)=cosθ=Kn(y0cosz0+x0sinz0)=sinθ
图 3 旋转模式角度逼近
8 象限判断
对于任意角度 θ \theta θ,都可以通过旋转一系列小角度来 i i i 完成。第一次迭代旋转 4 5 ∘ 45^{\circ} 45∘,第二次迭代旋转 26. 6 ∘ 26.6^{\circ} 26.6∘,第三次迭代旋转 1 4 ∘ 14^{\circ} 14∘。很显然,每次旋转的方向都影响到最终的累计角度。在 − 99. 7 ∘ < θ < 99. 7 ∘ - 99.7^{\circ}< \theta < 99.7^{\circ} −99.7∘<θ<99.7∘ 的范围内任意角度都可以旋转。对于该角度范围之外的角度,可以通过三角函数等式转化成该范围内的角度。因此设计时通常把角度 θ \theta θ 转化到第一象限角,根据 θ \theta θ 所在的圆坐标系象限获得 x n x_{n} xn 和 y n y_{n} yn。
9 溢出处理
通过表 2 可以看出,把初始 θ \theta θ 角度设置为 90°,由于 x 0 x_{0} x0 赋的初值存在误差(一般是偏大),最终获得的 x n x_{n} xn 和 y n y_{n} yn 是有可能大于 1 的。所以设计时需要做溢出保护。或者把 x 0 x_{0} x0 初值减小,这可能会牺牲精度。
表 2 θ \theta θ 接近 90° 数据溢出
i | d | theta | z | y | x | SIN | COS |
---|---|---|---|---|---|---|---|
0 | 1 | 45 | 89.9970 | 0 | 0.6073 | 0 | 19900 |
1 | 1 | 26.6 | 44.9970 | 0.6073 | 0.6073 | 19900 | 19900 |
2 | 1 | 14 | 18.3970 | 0.9110 | 0.3037 | 29850 | 9950 |
3 | 1 | 7.1 | 4.3970 | 0.9869 | 0.0759 | 32338 | 2488 |
4 | -1 | 3.6 | -2.7030 | 0.9964 | -0.0474 | 32648 | -1555 |
5 | 1 | 1.8 | 0.8970 | 0.9993 | 0.0148 | 32746 | 486 |
6 | -1 | 0.9 | -0.9030 | 0.9998 | -0.0164 | 32761 | -537 |
7 | -1 | 0.4 | -0.0030 | 1.0000 | -0.0008 | 32769 | -26 |
8 | 1 | 0.2 | 0.3970 | 1.0000 | 0.0070 | 32769 | 230 |
9 | 1 | 0.1 | 0.1970 | 1.0001 | 0.0031 | 32770 | 102 |
10 | 1 | 0.056 | 0.0970 | 1.0001 | 0.0012 | 32770 | 38 |
11 | 1 | 0.027 | 0.0410 | 1.0001 | 0.0002 | 32771 | 6 |
10 Verilog 代码实现
CORDIC 算法代码
module MyCORDIC (input clk,input rst_n,input [15:0] theta,output reg [15:0] sin_theta,output reg [15:0] cos_theta
);parameter Kn = 'd19898; // 0.607253*2^15
parameter iKn = 'd53961; // 1.64676*2^15 parameter arctan_0 = 8192 ; //arctan (1/2)
parameter arctan_1 = 4836 ; //arctan (1/2^1)
parameter arctan_2 = 2555 ; //arctan (1/2^2)
parameter arctan_3 = 1297 ; //arctan (1/2^3)
parameter arctan_4 = 651 ; //arctan (1/2^4)
parameter arctan_5 = 326 ; //arctan (1/2^5)
parameter arctan_6 = 163 ; //arctan (1/2^6)
parameter arctan_7 = 81 ; //arctan (1/2^7)
parameter arctan_8 = 41 ; //arctan (1/2^8)
parameter arctan_9 = 20 ; //arctan (1/2^9)
parameter arctan_10 = 10 ; //arctan (1/2^10)
parameter arctan_11 = 5 ; //arctan (1/2^11)reg signed [15:0] x [11:0];
reg signed [15:0] y [11:0];
reg signed [15:0] z [11:0];wire [15:0] x_tmp;
wire [15:0] y_tmp;reg signed [15:0] theta_1;
wire [2:0] Quadrant;//theta 角所在的象限// 象限判断
assign Quadrant = theta [15:14] + 1;always@(*)
beginbegintheta_1 <= {2'b00,theta [13:0]};if (Quadrant==1)begintheta_1 <= theta;endelse if (Quadrant==2)begintheta_1 <= 32768 - theta;endelse if (Quadrant==3)begintheta_1 <= theta - 32768;endelse if (Quadrant==4)begintheta_1 <= 65536 - theta;endend
endalways@(posedge clk or negedge rst_n)
beginif (!rst_n)beginx [0] <= 16'd0;y [0] <= 16'd0;z [0] <= 16'd0;endelsebeginx [0] <= Kn;y [0] <= 16'd0;z [0] <= theta_1;end
endalways@(posedge clk or negedge rst_n) //i=0
beginif (!rst_n)beginx [1] <= 16'd0;y [1] <= 16'd0;z [1] <= 16'd0;endelsebeginif (z [0][15]) // 剩余角度为负数,顺时针旋转,d=-1beginx [1] <= x [0] + y [0];y [1] <= y [0] - x [0];z [1] <= z [0] + arctan_0;end // 剩余角度为正数,顺时针旋转,d=+1elsebeginx [1] <= x [0] - y [0];y [1] <= y [0] + x [0];z [1] <= z [0] - arctan_0;endend
end// >>> 符号表示算术右移,不改变符号位
always@(posedge clk or negedge rst_n) //i=1
beginif (!rst_n)beginx [2] <= 16'd0;y [2] <= 16'd0;z [2] <= 16'd0;endelsebeginif (z [1][15]) // 剩余角度为负数,顺时针旋转,d=-1beginx [2] <= x [1] + (y [1] >>> 1);y [2] <= y [1] - (x [1] >>> 1);z [2] <= z [1] + arctan_1;end // 剩余角度为正数,逆时针旋转,d=+1elsebeginx [2] <= x [1] - (y [1] >>> 1);y [2] <= y [1] + (x [1] >>> 1);z [2] <= z [1] - arctan_1;endend
endalways@(posedge clk or negedge rst_n) //i=2
beginif (!rst_n)beginx [3] <= 16'd0;y [3] <= 16'd0;z [3] <= 16'd0;endelsebeginif (z [2][15]) // 剩余角度为负数,顺时针旋转,d=-1beginx [3] <= x [2] + (y [2] >>> 2);y [3] <= y [2] - (x [2] >>> 2);z [3] <= z [2] + arctan_2;end // 剩余角度为正数,逆时针旋转,d=+1elsebeginx [3] <= x [2] - (y [2] >>> 2);y [3] <= y [2] + (x [2] >>> 2);z [3] <= z [2] - arctan_2;endend
endalways@(posedge clk or negedge rst_n) //i=3
beginif (!rst_n)beginx [4] <= 16'd0;y [4] <= 16'd0;z [4] <= 16'd0;endelsebeginif (z [3][15]) // 剩余角度为负数,顺时针旋转,d=-1beginx [4] <= x [3] + (y [3] >>> 3);y [4] <= y [3] - (x [3] >>> 3);z [4] <= z [3] + arctan_3;end // 剩余角度为正数,逆时针旋转,d=+1elsebeginx [4] <= x [3] - (y [3] >>> 3);y [4] <= y [3] + (x [3] >>> 3);z [4] <= z [3] - arctan_3;endend
endalways@(posedge clk or negedge rst_n) //i=4
beginif (!rst_n)beginx [5] <= 16'd0;y [5] <= 16'd0;z [5] <= 16'd0;endelsebeginif (z [4][15]) // 剩余角度为负数,顺时针旋转,d=-1beginx [5] <= x [4] + (y [4] >>> 4);y [5] <= y [4] - (x [4] >>> 4);z [5] <= z [4] + arctan_4;end // 剩余角度为正数,逆时针旋转,d=+1elsebeginx [5] <= x [4] - (y [4] >>> 4);y [5] <= y [4] + (x [4] >>> 4);z [5] <= z [4] - arctan_4;endend
endalways@(posedge clk or negedge rst_n) //i=5
beginif (!rst_n)beginx [6] <= 16'd0;y [6] <= 16'd0;z [6] <= 16'd0;endelsebeginif (z [5][15]) // 剩余角度为负数,顺时针旋转,d=-1beginx [6] <= x [5] + (y [5] >>> 5);y [6] <= y [5] - (x [5] >>> 5);z [6] <= z [5] + arctan_5;end // 剩余角度为正数,逆时针旋转,d=+1elsebeginx [6] <= x [5] - (y [5] >>> 5);y [6] <= y [5] + (x [5] >>> 5);z [6] <= z [5] - arctan_5;endend
endalways@(posedge clk or negedge rst_n) //i=6
beginif (!rst_n)beginx [7] <= 16'd0;y [7] <= 16'd0;z [7] <= 16'd0;endelsebeginif (z [6][15]) // 剩余角度为负数,顺时针旋转,d=-1beginx [7] <= x [6] + (y [6] >>> 6);y [7] <= y [6] - (x [6] >>> 6);z [7] <= z [6] + arctan_6;end // 剩余角度为正数,逆时针旋转,d=+1elsebeginx [7] <= x [6] - (y [6] >>> 6);y [7] <= y [6] + (x [6] >>> 6);z [7] <= z [6] - arctan_6;endend
endalways@(posedge clk or negedge rst_n) //i=7
beginif (!rst_n)beginx [8] <= 16'd0;y [8] <= 16'd0;z [8] <= 16'd0;endelsebeginif (z [7][15]) // 剩余角度为负数,顺时针旋转,d=-1beginx [8] <= x [7] + (y [7] >>> 7);y [8] <= y [7] - (x [7] >>> 7);z [8] <= z [7] + arctan_7;end // 剩余角度为正数,逆时针旋转,d=+1elsebeginx [8] <= x [7] - (y [7] >>> 7);y [8] <= y [7] + (x [7] >>> 7);z [8] <= z [7] - arctan_7;endend
endalways@(posedge clk or negedge rst_n) //i=8
beginif (!rst_n)beginx [9] <= 16'd0;y [9] <= 16'd0;z [9] <= 16'd0;endelsebeginif (z [8][15]) // 剩余角度为负数,顺时针旋转,d=-1beginx [9] <= x [8] + (y [8] >>> 8);y [9] <= y [8] - (x [8] >>> 8);z [9] <= z [8] + arctan_8;end // 剩余角度为正数,逆时针旋转,d=+1elsebeginx [9] <= x [8] - (y [8] >>> 8);y [9] <= y [8] + (x [8] >>> 8);z [9] <= z [8] - arctan_8;endend
endalways@(posedge clk or negedge rst_n) //i=9
beginif (!rst_n)beginx [10] <= 16'd0;y [10] <= 16'd0;z [10] <= 16'd0;endelsebeginif (z [9][15]) // 剩余角度为负数,顺时针旋转,d=-1beginx [10] <= x [9] + (y [9] >>> 9);y [10] <= y [9] - (x [9] >>> 9);z [10] <= z [9] + arctan_9;end // 剩余角度为正数,逆时针旋转,d=+1elsebeginx [10] <= x [9] - (y [9] >>> 9);y [10] <= y [9] + (x [9] >>> 9);z [10] <= z [9] - arctan_9;endend
endalways@(posedge clk or negedge rst_n) //i=10
beginif (!rst_n)beginx [11] <= 16'd0;y [11] <= 16'd0;z [11] <= 16'd0;endelsebeginif (z [10][15]) // 剩余角度为负数,顺时针旋转,d=-1beginx [11] <= x [10] + (y [10] >>> 10);y [11] <= y [10] - (x [10] >>> 10);z [11] <= z [10] + arctan_10;end // 剩余角度为正数,逆时针旋转,d=+1elsebeginx [11] <= x [10] - (y [10] >>> 10);y [11] <= y [10] + (x [10] >>> 10);z [11] <= z [10] - arctan_10;endend
end// 溢出判断
assign x_tmp = x [11][15]==1 ? 16'h7FFF : x [11];
assign y_tmp = y [11][15]==1 ? 16'h7FFF : y [11];
//assign x_tmp = x [11];
//assign y_tmp = y [11];always@(posedge clk or negedge rst_n) //i=11
beginif (!rst_n)beginsin_theta <= 16'd0;cos_theta <= 16'd0;endelsebeginif (Quadrant == 3'd1)beginsin_theta <= y_tmp;cos_theta <= x_tmp;endelse if (Quadrant == 3'd2)beginsin_theta <= y_tmp;cos_theta <= -x_tmp;endelse if (Quadrant == 3'd3)beginsin_theta <= -y_tmp;cos_theta <= -x_tmp;endelse if (Quadrant == 3'd4)beginsin_theta <= -y_tmp;cos_theta <= x_tmp;endelsebeginsin_theta <= sin_theta;cos_theta <= cos_theta;endend
end
endmodule
testbench 文件
`timescale 1 ns/ 1 nsmodule MyCORDIC_tb (
);integer i;reg clk,rst_n;
reg [15:0] theta,theta_n;
wire [15:0] sin_theta,cos_theta;reg [15:0] cnt,cnt_n;MyCORDIC u0 (.clk (clk ),.rst_n (rst_n ),.theta (theta ),.sin_theta (sin_theta),.cos_theta (cos_theta)
);initial
begin#0 clk = 1'b0;#10 rst_n = 1'b0;#10 rst_n = 1'b1;#10000000$stop;
end initial
begin#0 theta = 16'd20;for (i=0;i<10000;i=i+1)begin#400;theta <= theta + 16'd200;end
end always #10
beginclk = ~clk;
endendmodule
ModelSim 仿真
参考文献
- XILINX FPGAs for DSP CORDIC 算法 [OL].
- 张剑锋。基于改进 CORDIC 算法的 DDFS 和 FFT 研究与实现 [D].
国防科学技术大学,2011. - Cordic 算法的原理介绍 [OL].
https://www.cnblogs.com/touchblue/p/3535968.html - cordic 算法详解 [OL].
https://blog.csdn.net/u010712012/article/details/77755567
via:
-
Cordic 算法的原理介绍 - 乐富道 - 博客园 乐富道 2014-01-28 23:05
https://www.cnblogs.com/touchblue/p/3535968.html -
基于 FPGA 的 CORDIC 算法实现 ——Verilog 版_cordic 算法详解 一休哥 - CSDN 博客 善良的一休君于 2017-08-21 20:07:34 发布.
https://blog.csdn.net/qq_39210023/article/details/77456031 -
CORDIC 算法原理详解及其 Verilog 实现_verilog cordic 算法 - CSDN 博客 爱吃蛋挞的Dolly
于 2021-01-11 16:54:10 发布
https://blog.csdn.net/gemengxia/article/details/112475073 -
CORDIC 算法详解及 FPGA 实现 - CSDN 博客 ngany 于 2021-05-30 18:52:04 发布
https://blog.csdn.net/ngany/article/details/117401494