CORDIC算法原理及硬件實現(FPGA)


一、CORDIC算法

  CORDIC(Coordinate Rotation DIgital Computer)是一種通過迭代實現快速平面旋轉的算法,通過變形擴展,它可以對多種超越函數求值,例如三角/反三角函數、雙曲函數等。

  對超越函數求值,常見方法為用多項式近似,例如利用泰勒展開來逼近目標函數,只要階數取得足夠大,就可以無限逼近目標函數。當我們把這個方法運用到某些特殊機器時,很快會發現問題:泰勒展開包括大量復雜浮點運算,對於沒有硬件浮點運算單元的機器,只能通過軟件浮點實現。

  CORDIC的出現解決了這個問題。該算法利用迭代逼近的方法,僅僅通過加/減和移位操作即可計算,極大的方便了機器實現。

 

本文的:

  一、解釋CORDIC旋轉的原理。

  二、旋轉模式出發,解釋三角函數(sin, cos, tan)的計算;

  三、旋轉模式出發,解釋反三角函數(arcsin、arccos)的計算;

  四、從向量模式出發,解釋向量模的計算;

  五、介紹定點運算的實現及軟件模型的建立

  六、通過Verilog HDL設計硬件。

 

本文代碼倉庫詳見:https://github.com/cassuto/CORDIC-all-in-one-verilog 

 

二、基本思想

  正如該算法的名字所說,CORDIC最初是為一種用來進行坐標軸旋轉的專用計算機開發的(其原型硬件於1959年成功應用於實時導航)。追根溯源,CORDIC的核心就是坐標軸旋轉。其后又由不同的旋轉方式導出了各類函數的計算方法。

  CORDIC包括旋轉模式向量模式,下面逐一解釋。

 

1、旋轉模式

1.1 偽旋轉

  假設現要將一個直角坐標系中的點P(x0, y0)繞原點逆時針旋轉z0角度,則變換后的點P1(x1, y1)坐標如下:

  我們發現這個轉換式中涉及三角函數,但現在假設機器還不能求任意三角函數值,那么能否改進?

  繼續觀察旋轉變換式,可以等價得到:

  上式仍然涉及兩個三角函數,但是cos被放到了一邊,於是重點研究tan(z0)。將角度z0微分為n個小角度,每一步旋轉θn,通過若干步的旋轉最終逼近目標如果像下面這樣特殊選取θn,使tan θn恰好只與2的冪有關,則原本復雜的乘tan θn運算就可以通過二進制右移n位實現(xn,yn都是整數,相關問題在后面討論),這對二進制計算機非常自然。

$$ tan \theta_n = \frac{1}{2^n} $$

  結合上面的變換公式,令cos θn=Pn,得到如下迭代格式:

  分析計算量。θn = atan(1/2n)可查表得出。同理系數Pn也可以通過查表求出,但它們仍是浮點數。

  到這里,算法原先的4次浮點運算,被成功減少到了2次,我們離CORDIC偽旋轉算法已經很近了。

  但問題還沒有結束,如果迭代n次的話,仍需要進行2n次浮點運算。引起2n次浮點運算的原因是每次都需要與系數Pn相乘,這樣做真的有必要嗎?

  構造輔助三角形,可以得出:

  因此Pn的取值只與n有關。完全可以在所有迭代都完成后,再在最終結果上乘P=ΠPn

  對於根據迭代較少的情況,可以將Pn的不同取值固化到表格中,通過查表求出。

  但這仍需查表。進一步分析發現,對於迭代次數較多的情況,可以將Pn看作常數近似處理,因為隨着n的增加,Pn逐漸穩定下來:

  即迭代多次時,可取P≈ lim(n)Pn,這成功去除了Pn查找表。不過引入了截斷誤差。誤差分析至關重要,本文有待完善。

  通過數值方法,可以形象地看出Pn隨n增大的變化趨勢。

  

 

  同理可得順時針方向的旋轉算法。

對兩旋轉方向的公式進行歸納:定義符號函數S(n)控制旋轉方向,逆時針旋轉時S(n)取1;順時針旋轉時S(n)取-1。可以寫出更一般的偽旋轉公式:

(式1.1-1)

  至此我們得到了一個完整的旋轉算法,一些文獻中也稱這個過程為“偽旋轉”。

 

1.2 迭代逼近

  偽旋轉算法雖然避免了復雜的計算,但結果並不一定精確,原因在於每次旋轉的角度θn是嚴格受限的。例如目標角度Z0取30°,第一次旋轉θ0=atan(1)=45°,顯然已經遠遠超過了目標角,因此偽旋轉算法不一定收斂於精確值。

  為了確保結果收斂於精確值,需要引入迭代逼近。思路很直觀:上例中“超過了目標角”,就以更小的角度向相反方向旋轉;若未“超過”,則繼續同向旋轉,如此反復,經過無窮次迭代后,點(Xn,Yn)將最終收斂於目標點。

為了實現迭代逼近,首先引入角度累加器Zn,以確定相對於目標角度旋轉了多少。Z0取目標角度,規定每步迭代滿足:

(式1.2-2)

  則Zn正是目標角度與累計旋轉角度的差值,稱為相位誤差。如果已經轉過的角度大於目標角,則Zn<0,反之Zn>=0。

回到逼近的原理:如果轉過的角度大於目標角,則向相反方向旋轉,反之則繼續同向旋轉。發現,這正是Zn+1和S(n)的關系。

  假設目標角Z0>0表示逆時針旋轉,則S(n)與Zn+1可總結如下:

(式1.2-3)

  聯立式1.1-1、1.2-2、1.2-3,便可得到完整的旋轉算法。可以證明,當n∞時,Zn→0,(Xn, Yn)將收斂於目標點。

  稱這種CORDIC模式為旋轉模式

上述算法可用偽代碼描述如下:

 1 For n=0 to i-1
 2     If (Z(n) >= 0) Then
 3         X(n + 1) := X(n) – (Yn >> n)
 4         Y(n + 1) := Y(n) + (Xn >> n)
 5         
 6         Z(n + 1) := Z(n) - atan(1/2^n)
 7     Else
 8         X(n + 1) := X(n) + (Yn >> n)
 9         Y(n + 1) := Y(n) – (Xn >> n)
10         
11         Z(n + 1) := Z(n) + atan(1/2^n)
12     End if
13 End for
14 X(i) *=
0.607253 // limP
15 Y(i) *= 0.607253 // limP

 

1.3 通過旋轉模式計算三角函數

  借助單位圓工具,在直角坐標系中,將點(1, 0)以原點為中心逆時針旋轉α角,旋轉后點的坐標(x',y')正是(cosα, sinα)。

下圖顯示了CORDIC算法計算三角函數的迭代過程。

 

 

 

 

2、向量模式

  向量模式是為了計算向量模和反三角函數而引出的。

1.1 向量模的計算

  直角坐標系中,設目標向量V(x0, y0),向量模的定義如下:

  向量模的計算:將點P0(x0, y0)作為初始坐標。如果將點P0旋轉某個角度,使縱坐標y'=0,則此時橫坐標x'即為向量V(x0, y0)的模。

  與CORDIC旋轉模式類似,關鍵在於逼近的目標不同。取S(n):

 

 

  則旋轉過程中,Zn→0,向量將向Yn=0的方向逼近,其過程如下(注意圖中向量模長比例並不精確):

算法用偽代碼描述如下:

 1 limP := 0.607253
 2 For n=0 to i-1
 3     If (Y(n) >= 0) Then
 4         X(n + 1) := X(n) + (Yn >> n)
 5         Y(n + 1) := Y(n) - (Xn >> n)
 6     Else
 7         X(n + 1) := X(n) - (Yn >> n)
 8         Y(n + 1) := Y(n) + (Xn >> n)
 9     End if
10 End for
11 X(i) *= limP

 

1.2 反正弦函數的計算

  已知正弦函數值S,在直角坐標系中,若當點(1, 0)逆時針旋轉某個角度A時縱坐標恰等於S,則A=asinS 即為反正弦函數的值。可見這只是正弦函數計算的逆過程。

  仍為向量模式,只是逼近的目標發生了變化。取Zn = Y(n) - S,Zn為縱坐標相對於S的誤差。

  則旋轉過程中,Zn向量將不斷向豎直分量Yn=S的方向逼近。

 

算法用偽代碼描述如下:

 1 A := 0
2 S /= limP
3 For n=0 to i-1 4 If (Y(n) >= S) Then 5 X(n + 1) := X(n) + (Yn >> n) 6 Y(n + 1) := Y(n) - (Xn >> n) 7 8 A := A + atan(1/2^n) 9 Else 10 X(n + 1) := X(n) - (Yn >> n) 11 Y(n + 1) := Y(n) + (Xn >> n) 12 13 A := A - atan(1/2^n) 14 End if 15 End for

 

同理可得反余弦函數的計算。

 

總結,CORDIC旋轉模式可完成sinA、cosA、asinA、acosA的計算。

 

三、定點算法的軟件模型建立

定點數

  定點數可通過整數來表示。

  1、量化角度:在直角范圍內,將b位無符號整數的取值區間均分為coeff1 = 2^b / 90 個單位。

則對應任意給定的浮點角度Z°,其對應的整數可表示為:floor(Z * coeff1);

  2、轉換坐標(x, y):將原始坐標放大coeff2倍並取整即可得出定點坐標。coeff2應根據實際問題適當選取,盡量減小舍入誤差。

坐標用整數可表示為:(floor(X * coeff2)、floor(Y * coeff2))。

  3、對於算法的整數輸出,只需將整數除以相應coeff即可得到對應的浮點數。

  浮點數到定點數的轉換過程將引入舍入誤差。

 

軟件模型

       軟件的抽象程度高於硬件,可以更加緊湊地對算法進行描述,同時能快速地進行調試。

  通過軟件實現CORDIC算法:

    旋轉模式的C語言模型:

      https://github.com/cassuto/CORDIC-all-in-one-verilog/blob/master/CORDIC-rotate-fixed-point.c

    向量模式的C語言模型:

      https://github.com/cassuto/CORDIC-all-in-one-verilog/blob/master/CORDIC-anti-rotate-fixed-point.c

   完整軟件實現詳見:https://github.com/cassuto/CORDIC-all-in-one-verilog 

 

四、FPGA實現

  CORDIC在硬件中的典型的應用包括DDS(直接數字頻率合成)等等。

  本小節主要介紹硬件實現的相關細節,包括象限轉換,流水化處理。

 

象限轉換

  根據三角函數的對稱性,只需實現一個象限,即可得出其余所有象限的情況。

  由於相位取值范圍被象限四等分,可取相位輸入的高2位決定象限。得出象限后,即可根據三角函數值在4個象限的符號關系得到正確的輸出。

 

流水化處理

  流水化提高了時鍾頻率,“以面積換取速度”。

  從流水線為空開始,下游模塊需等待PIPE_DEPTH+2個時鍾周期才能從流水線得出結果;

  而當流水線填滿后,對於相位輸入,流水線都可以在單時鍾周期內更新輸出。

 

Verilog設計

輸入:相位phase_i。

輸出:三角函數值sin_o,cos_o,相位誤差err_o

  1 module cordic_dds # (
  2    parameter DW = 16,               /* Data width */
  3    parameter PIPE_DEPTH = 14,       /* Pipeline depth */
  4    parameter limP = 16'h4dba        /* P = 0.607253 * 2^15 */
  5 )
  6 (/*AUTOARG*/
  7    // Outputs
  8    sin_o, cos_o, err_o,
  9    // Inputs
 10    clk, phase_i
 11 );
 12 
 13    input             clk;
 14    input [DW-1:0]    phase_i;       /* Phase */
 15    output [DW:0]     sin_o, cos_o;  /* Function value output */
 16    output [DW:0]     err_o;         /* Phase Error output */
 17 
 18    reg [DW:0] cos_r=0, sin_o_r=0;
 19    reg [DW:0] x[PIPE_DEPTH:0];
 20    reg [DW:0] y[PIPE_DEPTH:0];
 21    reg [DW:0] z[PIPE_DEPTH:0];
 22 
 23    reg [DW:0] atan_rom[PIPE_DEPTH:0];
 24    
 25    reg [1:0] quadrant [PIPE_DEPTH:0];
 26 
 27    integer i;
 28    initial begin
 29       for(i=0; i<=PIPE_DEPTH; i=i+1) begin
 30          x[i] = 0; y[i] = 0; z[i] = 0;
 31          quadrant[i] = 2'b0;
 32       end
 33    end
 34    
 35    initial begin
 36       atan_rom[0] <= 8189;
 37       atan_rom[1] <= 4834;
 38       atan_rom[2] <= 2554;
 39       atan_rom[3] <= 1296;
 40       atan_rom[4] <= 650;
 41       atan_rom[5] <= 325;
 42       atan_rom[6] <= 162;
 43       atan_rom[7] <= 81;
 44       atan_rom[8] <= 40;
 45       atan_rom[9] <= 20;
 46       atan_rom[10] <= 10;
 47       atan_rom[11] <= 5;
 48       atan_rom[12] <= 2;
 49       atan_rom[13] <= 1;
 50    end
 51    
 52    
 53    // ================= //
 54    // Pipeline stages   //
 55    // ================= //
 56    always @ (posedge clk) begin // stage 0
 57       x[0] <= {1'b0, limP};
 58       y[0] <= 0;
 59       z[0] <= {3'b0, phase_i[DW-1-2:0]}; // control the phase_i to the range[0-Pi/2]
 60    end
 61 
 62    always @ (posedge clk) begin // stage 1
 63       x[1] <= x[0] - y[0];
 64       y[1] <= x[0] + y[0];
 65       z[1] <= z[0] - atan_rom[0]; // reversal 45deg
 66    end
 67 
 68    generate
 69       genvar k;
 70       for(k=1; k<PIPE_DEPTH; k=k+1) begin
 71          always @ (posedge clk) begin
 72             if (z[k][DW]) begin /* the diff is negative on clockwise */
 73                x[k+1] <= x[k] + {{k{y[k][DW]}},y[k][DW:k]}; /* >> k */
 74                y[k+1] <= y[k] - {{k{x[k][DW]}},x[k][DW:k]}; /* >> k */
 75                z[k+1] <= z[k] + atan_rom[k];
 76             end else begin
 77                x[k+1] <= x[k] - {{k{y[k][DW]}},y[k][DW:k]};
 78                y[k+1] <= y[k] + {{k{x[k][DW]}},x[k][DW:k]};
 79                z[k+1] <= z[k] - atan_rom[k];
 80             end
 81          end
 82       end
 83    endgenerate
 84 
 85    // ================= //
 86    // Count quadrant    //
 87    // ================= //
 88    always @ (posedge clk) begin
 89       quadrant[0] <= phase_i[DW-1:DW-2];
 90    end
 91    generate
 92       genvar j;
 93       for(j=0; j<PIPE_DEPTH; j=j+1) begin
 94          always @ (posedge clk) begin
 95             quadrant[j+1] <= quadrant[j];
 96          end
 97       end
 98    endgenerate
 99 
100    // ================= //
101    // Adjust quadrant   //
102    // ================= //
103    always @ (posedge clk)
104       case(quadrant[PIPE_DEPTH])
105          2'b00: begin
106             cos_r <= x[PIPE_DEPTH]; /* cos */
107             sin_o_r <= y[PIPE_DEPTH]; /* sin */
108          end
109          2'b01: begin
110             cos_r <= ~(y[PIPE_DEPTH]) + 1'b1; /* -sin */
111             sin_o_r <= x[PIPE_DEPTH]; /* cos */
112          end
113          2'b10: begin
114             cos_r <= ~(x[PIPE_DEPTH]) + 1'b1; /* -cos */
115             sin_o_r <= ~(y[PIPE_DEPTH]) + 1'b1; /* -sin */
116          end
117          2'b11: begin
118             cos_r <= y[PIPE_DEPTH]; /* sin */
119             sin_o_r <= ~(x[PIPE_DEPTH]) + 1'b1; /* -cos */
120          end
121       endcase
122 
123    assign cos_o = cos_r;
124    assign sin_o = sin_o_r;
125    assign err_o = z[PIPE_DEPTH];
126 
127 endmodule

 

 

  通過仿真得出波形如下:

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM