The CORDIC algorithm is a clever method for accurately computing trigonometric functions using only additions, bitshifts and a small lookup table.

The Algorithm

It’s well known that rotating the vector \((1, 0)\) anticlockwise about the origin by an angle \(\theta\) gives the vector \((\cos \theta, \sin \theta)\). We will use this as the basis of our algorithm:

function cordic(θ) { [c, s] ← rotate([1, 0], θ) return c, s }

Let’s split up this big rotation into \(N\) smaller rotations, with the angle of rotation in step \(i\) given by \(\alpha_i\). At the moment, we don’t care what the values of \(\alpha_i\) are, we just want their sum to be equal to \(\theta\).

α ← [ ... ] function cordic(θ) { c ← 1 s ← 0 for i in 0 .. N-1 { [c, s] ← rotate([c, s], α[i]) } return c, s }

How do we rotate a vector? The page for rotation on Wikipedia tells us that it is equivalent to left-multiplying by a particular matrix:

α ← [ ... ] function cordic(θ) { c ← 1 s ← 0 for i in 0 .. N-1 { rotation_matrix ← [[cos α[i], -sin α[i]], [sin α[i], cos α[i]]] [c, s] ← rotation_matrix × [c, s] } return c, s }

Let’s expand out this matrix multiplication:

α ← [ ... ] function cordic(θ) { c ← 1 s ← 0 for i in 0 .. N-1 { c_new ← cos α[i] × c - sin α[i] × s s_new ← sin α[i] × c + cos α[i] × s c ← c_new s ← s_new } return c, s }

We will use the fact that \(\sin \alpha_i = \cos \alpha_i \tan \alpha_i\) to factorise by \(\cos \alpha_i\):

α ← [ ... ] function cordic(θ) { c ← 1 s ← 0 for i in 0 .. N-1 { c_new ← cos α[i] × (c - tan α[i] × s) s_new ← cos α[i] × (s + tan α[i] × c) c ← c_new s ← s_new } return c, s }

Now, we will consider the values of \(\alpha_i\). Let’s assume that each rotation angle \(\alpha_i\) has a fixed magnitude \(\beta_i\) with respect to \(\theta\), but can be either positive or negative depending on the value of \(\theta\). In this way, the rotating vector can be directed to converge on a particular result vector.

We will accomplish this using a new variable \(\phi\), initially at 0. Each step, we compare \(\phi\) to \(\theta\). If \(\phi\) is less than \(\theta\), we need to rotate by a positive angle (i.e. \(\alpha_i = \beta_i\)). If \(\phi\) is greater than \(\theta\), we need to rotate by a negative angle (\(\alpha_i = -\beta_i\)).

β ← [ ... ] function cordic(θ) { c ← 1 s ← 0 φ ← 0 for i in 0 .. N-1 { if φ < θ { direction ← 1 } else { direction ← -1 } α ← direction × β[i] c_new ← cos α × (c - tan α × s) s_new ← cos α × (s + tan α × c) c ← c_new s ← s_new φ ← φ + α } return c, s }

Let’s eliminate the variable \(\alpha\) and use \(\beta_i\) directly:

β ← [ ... ] function cordic(θ) { c ← 1 s ← 0 φ ← 0 for i in 0 .. N-1 { if φ < θ { direction ← 1 } else { direction ← -1 } c_new ← cos (direction × β[i]) × (c - tan (direction × β[i]) × s) s_new ← cos (direction × β[i]) × (s + tan (direction × β[i]) × c) c ← c_new s ← s_new φ ← φ + (direction × β[i]) } return c, s }

\(\cos (-x) = \cos x\) and \(\tan (-x) = -\tan x\), so this program is equivalent to:

β ← [ ... ] function cordic(θ) { c ← 1 s ← 0 φ ← 0 for i in 0 .. N-1 { if φ < θ { direction ← 1 } else { direction ← -1 } c_new ← cos β[i] × (c - direction × tan β[i] × s) s_new ← cos β[i] × (s + direction × tan β[i] × c) c ← c_new s ← s_new φ ← φ + (direction × β[i]) } return c, s }

Now what about the values of \(\beta_i\)? If we assign them to be such that \(\tan \beta_i = 2^{-i}\), then we have:

β ← [ atan 2^0, atan 2^1, ... atan 2^(N-1) ] function cordic(θ) { c ← 1 s ← 0 φ ← 0 for i in 0 .. N-1 { if φ < θ { direction ← 1 } else { direction ← -1 } c_new ← cos β[i] × (c - direction × 2^(-i) × s) s_new ← cos β[i] × (s + direction × 2^(-i) × c) c ← c_new s ← s_new φ ← φ + (direction × β[i]) } return c, s }

Multiplication by \(2^{-i}\) is a shift-right by \(i\) places:

β ← [ atan 2^0, atan 2^1, ... atan 2^(N-1) ] function cordic(θ) { c ← 1 s ← 0 φ ← 0 for i in 0 .. N-1 { if φ < θ { direction ← 1 } else { direction ← -1 } c_new ← cos β[i] × (c - direction × (s >> i)) s_new ← cos β[i] × (s + direction × (c >> i)) c ← c_new s ← s_new φ ← φ + (direction × β[i]) } return c, s }

Let’s move the multiplications by \(\cos \beta_i\) into a separate loop:

β ← [ atan 2^0, atan 2^1, ... atan 2^(N-1) ] function cordic(θ) { c ← 1 s ← 0 φ ← 0 for i in 0 .. N-1 { if φ < θ { direction ← 1 } else { direction ← -1 } c_new ← c - direction × (s >> i) s_new ← s + direction × (c >> i) c ← c_new s ← s_new φ ← φ + (direction × β[i]) } for i in 0 .. N-1 { c ← c × cos β[i] s ← s × cos β[i] } return c, s }

In fact, all these multiplications can be replaced with a single value, which we will call \(K\). \(K\) is equal to the product of \(\cos \beta_i\) for all \(i\) between \(0\) and \(N-1\) inclusive. To put it mathematically,

\[K = \prod_{i=0}^{N-1} \cos \beta_i\]

It can be shown through the use of trigonometric identities that:

\[K = \prod_{i=0}^{N-1} \frac{1}{\sqrt{1 + 2^{-2i}}}\]

\(K\) tends to approximately \(0.607252935\) as \(N\) increases.

Therefore, we have:

β ← [ atan 2^0, atan 2^1, ... atan 2^(N-1) ] K ← 1 for i in 0 .. N-1 { K ← K × cos β[i] } function cordic(θ) { c ← K s ← 0 φ ← 0 for i in 0 .. N-1 { if φ < θ { direction ← 1 } else { direction ← -1 } c_new ← c - direction × (s >> i) s_new ← s + direction × (c >> i) c ← c_new s ← s_new φ ← φ + (direction × β[i]) } return c, s }

Finally, we will change \(\phi\) so that it now holds \(\theta\) minus “whatever it was holding before this change”. This replaces the comparison against \(\theta\) with a comparison against \(0\), which is simpler to compute:

β ← [ atan 2^0, atan 2^1, ... atan 2^(N-1) ] K ← 1 for i in 0 .. N-1 { K ← K × cos β[i] } function cordic(θ) { c ← K s ← 0 φ ← θ for i in 0 .. N-1 { if φ > 0 { direction ← 1 } else { direction ← -1 } c_new ← c - direction × (s >> i) s_new ← s + direction × (c >> i) c ← c_new s ← s_new φ ← φ - (direction × β[i]) } return c, s }

What we now have is an algorithm that is possible to implement in hardware, but is still equivalent to the original algorithm.

The implementation

We will assume that all numbers are stored as 32-bit fixed-point numbers, with the radix point between the second-most-significant and third-most-significant bits. This allows for numbers ranging between -2 and 2, which includes the interval \(-\frac{\pi}{2}\) to \(\frac{\pi}{2}\) that all other inputs can be reduced into.

First off, we declare the constants that we will use:

Then, we introduce the module and its ports.

We need five registers: cos , sin and angle from the above algorithm, a counter register count , and a state register state . The first three are 32 bits wide, since they are storing fixed-point numbers as described above. count is 5 bits since 32 iterations will be used, and state is only 1 bit since we only need two states.

The inputs to this registers will be determined in a sequential logic block, so five more registers cos_next , sin_next , angle_next , count_next and state_next are defined, although these will be automatically reduced to logic functions during synthesis.

The two states that we will use are 0 , an idle state, and 1 , a state indicating computation is occurring.

The logic block is defined as follows:

The cos_shr and sin_shr signals refer to the values of cos and sin shifted right by count places:

The direction_negative signal is high if direction in the pseudocode algorithm is negative (if \(φ < 0\)), and low if it is positive (if \(φ ≥ 0\)). It is therefore defined to simply be equal to the sign bit of angle :

Lastly, we will define the lookup table used for the beta values:

The complete source code can be downloaded here: