top of page
Writer's pictureAdam Taylor

MicroZed Chronicles: CORDIC algorithm in RTL.

Recently I retired my old trusty TI85 calculator and replaced it with a more modern scientific calculator, which could even run python. Doing this reminded me of the CORDIC algorithm, which was developed by Jack Volder in 1958 and enabled the first scientific calculator the HP35.

 

We have looked at the CORDIC algorithm before however, when previously examined we used an IP block from the Vivado Library.


I did however, want to come back and look at how we could implement a CORDIC algorithm from scratch using VHDL and the fixed point math package which we have previously also examined.

 

Doing so enables us to understand how we can create RTL modules which implement algorithms using the fixed package. While allowing us to see the inner workings of the CORDIC algorithm.

 

As mentioned to develop this I am going to be using the fixed point IEEE packages for this development.

 

The algorithm for the CORDIC algorithm can be seen below and works in two mode, rotation and vectoring.


To ensure flexibility and precision the module has been developed to use generics which enable the number of integer and fractional bits to be adjusted.

 

generic (
        -- Fixed-point format
        INTEGER_BITS   : integer := 2;
        FRACTION_BITS  : integer := 16;
        ITERATIONS     : integer := 16

The design also includes constants for zero and the processing gain which is used in rotating mode to provide a result which is scaled correctly.

-- CORDIC gain compensation factor (approximately 0.607253)

constant K : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS) :=
        to_sfixed(0.607253321089875, INTEGER_BITS-1, -FRACTION_BITS);

constant ZERO : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS) := 
       to_sfixed(0.0, INTEGER_BITS-1, -FRACTION_BITS);

 At its heart is a shift and add algorithm which uses a small look up table storing a values of arctan value for the iteration.

 

This implementation will be built around a 16 iteration CORDIC algorithm, which means the look up table only needs to store 16 entries. These entries will be store arctan 2**-I, these values will be pre calculated and quantised using the fixed point package.

 

When it comes to quantising the values of ArcTan we need to sure we provide sufficient resolution. The smallest value required to be stored is 2**-15 which is a vary small number (0.000030517578116) to store this value, we need at least 16 fractional bits.

 

constant atan_table : angle_array := (
	   to_sfixed(arctan(2.0 ** 0), INTEGER_BITS-1, -FRACTION_BITS),  
        to_sfixed(arctan(2.0 ** (-1)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-2)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-3)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-4)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-5)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-6)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-7)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-8)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-9)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-10)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-11)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-12)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-13)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-14)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-15)), INTEGER_BITS-1, -FRACTION_BITS)      );

 

I want the module to be pipelined that is to be able to process a new input on every clock cycle. To do this I will be declaring a record type which contains the X, Y, Z, mode and a valid signal which propagates through.

-- Type definitions for the pipeline stages
    type stage_data is record
        x     : sfixed(INTEGER_BITS-1  downto -FRACTION_BITS);
        y     : sfixed(INTEGER_BITS-1  downto -FRACTION_BITS);
        z     : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
        valid : std_logic;
        mode  : std_logic;  -- Propagate mode through pipeline
    end record;

This record type can then be used to create an array for the processing pipeline. We can iterate through each of the 16 iterations using a for loop.

 

Within this loop, we need to perform the X and Y shift, using the shift_right function.

shift_x := shift_right(pipe_regs(i).x, i);
shift_y := shift_right(pipe_regs(i).y, i);

We also need to determine the direction of the rotation for both rotating and vectoring modes.

-- Determine rotation direction based on mode
if pipe_regs(i).mode = '0' then
   rotation_direction := '1'  when ( pipe_regs(i).z < 
				to_sfixed(0, INTEGER_BITS-1, -FRACTION_BITS)) else '0';
else
   rotation_direction := '1' when (pipe_regs(i).y >= 
						  to_sfixed(0, 1, -FRACTION_BITS)) else '0';
 end if;

With the direction determined the pipeline can be updated to by performing the additions or subtractions as determined.

-- Compute next values based on rotation direction
if rotation_direction then
     -- Rotate clockwise
     next_x := resize((pipe_regs(i).x + shift_y),
					INTEGER_BITS-1,-FRACTION_BITS) ;
    	 next_y := resize((pipe_regs(i).y - shift_x),
					INTEGER_BITS-1,-FRACTION_BITS);
     next_z := resize((pipe_regs(i).z + atan_table(i)),
					INTEGER_BITS-1,-FRACTION_BITS);
else
   	-- Rotate counter-clockwise
    next_x := resize((pipe_regs(i).x - shift_y),
					INTEGER_BITS-1,-FRACTION_BITS);
	next_y := resize((pipe_regs(i).y + shift_x),
					INTEGER_BITS-1,-FRACTION_BITS);
    next_z := resize((pipe_regs(i).z - atan_table(i)),
					INTEGER_BITS-1,-FRACTION_BITS);
end if;

This solution is not fully pipelined, as such does have a latency to calculate the values of Sin, Cos (rotating) and ArcTan (Vectoring).


The complete design can be seen below

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.FIXED_PKG.ALL;

entity cordic_pipelined is
    generic (
        -- Fixed-point format
        INTEGER_BITS : integer := 2;
        FRACTION_BITS : integer := 16;
        ITERATIONS : integer := 16
    );
    port (
        clk        : in  std_logic;
        rst        : in  std_logic;
        data_valid : in  std_logic;
        -- Mode selection: '0' for rotation, '1' for vectoring
        mode       : in  std_logic;
        -- Inputs depend on mode:
        -- Rotation mode: (angle, 1.0, 0.0) or (angle, x_in, y_in) for arbitrary vector rotation
        -- Vectoring mode: 
	    --(0.0, x_in, y_in) to compute magnitude and angle
        angle_or_zero : in  sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
        x_in       : in  sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
        y_in       : in  sfixed(INTEGER_BITS-1  downto -FRACTION_BITS);
        -- Outputs depend on mode:
        -- Rotation mode
	   --(z_out=~0, cos(angle), sin(angle)) or rotated vector
        -- Vectoring mode: (angle, magnitude, ~0)
        z_out      : out sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
        x_out      : out sfixed(INTEGER_BITS-1  downto -FRACTION_BITS);
        y_out      : out sfixed(INTEGER_BITS-1  downto -FRACTION_BITS);
        valid_out  : out std_logic
    );
end cordic_pipelined;

architecture Behavioral of cordic_pipelined is

    
    -- Type definitions for the pipeline stages
    type stage_data is record
        x     : sfixed(INTEGER_BITS-1  downto -FRACTION_BITS);
        y     : sfixed(INTEGER_BITS-1  downto -FRACTION_BITS);
        z     : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
        valid : std_logic;
        mode  : std_logic;  -- Propagate mode through pipeline
    end record;

    type pipeline_array is array (0 to ITERATIONS) of stage_data
     -- Lookup table for arctangent values

    type angle_array is array (0 to ITERATIONS-1) of 
				   sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);

constant atan_table : angle_array := (
	   to_sfixed(arctan(2.0 ** 0), INTEGER_BITS-1, -FRACTION_BITS),  
        to_sfixed(arctan(2.0 ** (-1)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-2)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-3)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-4)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-5)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-6)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-7)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-8)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-9)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-10)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-11)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-12)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-13)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-14)), INTEGER_BITS-1, -FRACTION_BITS), 
        to_sfixed(arctan(2.0 ** (-15)), INTEGER_BITS-1, -FRACTION_BITS)      );

    -- CORDIC gain compensation factor (approximately 0.607253)
    constant K : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS) := 
        to_sfixed(0.607253321089875, INTEGER_BITS-1, -FRACTION_BITS);
    constant ZERO : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS) := 
       to_sfixed(0.0, INTEGER_BITS-1, -FRACTION_BITS);

    -- Pipeline registers
    signal pipe_regs : pipeline_array;

begin
    -- Pipeline process
    process(clk, rst)
        variable shift_x, shift_y : sfixed
					(INTEGER_BITS-1  downto -FRACTION_BITS);
        variable next_x, next_y : sfixed
					(INTEGER_BITS-1  downto -FRACTION_BITS);
        variable next_z : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
        variable rotation_direction : std_logic;
    begin
        if rst = '1' then
            -- Reset all pipeline stages
            for i in 0 to ITERATIONS loop
                pipe_regs(i).x <= to_sfixed
						(0, INTEGER_BITS-1, -FRACTION_BITS);
                pipe_regs(i).y <= to_sfixed
						(0, INTEGER_BITS-1, -FRACTION_BITS);
                pipe_regs(i).z <= to_sfixed
						(0, INTEGER_BITS-1, -FRACTION_BITS);
                pipe_regs(i).valid <= '0';
                pipe_regs(i).mode <= '0';
            end loop;
        elsif rising_edge(clk) then
            -- Input stage
            if data_valid = '1' then
                -- Initialize based on mode
                if mode = '0' then  -- Rotation mode
                    pipe_regs(0).x <= resize(x_in * K, 
							INTEGER_BITS-1, -FRACTION_BITS);  
                    pipe_regs(0).y <= resize(y_in * K, 
							INTEGER_BITS-1, -FRACTION_BITS);
                    pipe_regs(0).z <= angle_or_zero;
                else  -- Vectoring mode
                    pipe_regs(0).x <= x_in;
                    pipe_regs(0).y <= y_in;
                    pipe_regs(0).z <= angle_or_zero;
                end if;
                pipe_regs(0).valid <= '1';
                pipe_regs(0).mode <= mode;
            else
                pipe_regs(0).valid <= '0';
            end if;
            -- Pipeline stages
            for i in 0 to ITERATIONS-1 loop
                if pipe_regs(i).valid = '1' then
                    -- Calculate shifted values for this iteration
                    shift_x := shift_right(pipe_regs(i).x, i);
                    shift_y := shift_right(pipe_regs(i).y, i);         
				  -- Determine rotation direction based on mode
                    if pipe_regs(i).mode = '0' then
                        rotation_direction := '1'  when 
							( pipe_regs(i).z < to_sfixed
							(0, INTEGER_BITS-1, -FRACTION_BITS))
							 else '0';
                    else
                        rotation_direction := '1' when 
							(pipe_regs(i).y >= to_sfixed
							(0, 1, -FRACTION_BITS)) 
							else '0';
                    end if;
                    -- Compute next values based on rotation direction
                    if rotation_direction then
                        -- Rotate clockwise
                        next_x := resize((pipe_regs(i).x + shift_y)
								,INTEGER_BITS-1,-FRACTION_BITS) ;
                        next_y := resize((pipe_regs(i).y - shift_x)
								,INTEGER_BITS-1,-FRACTION_BITS);
                        next_z := resize((pipe_regs(i).z + atan_table(i))
								,INTEGER_BITS-1,-FRACTION_BITS);
                    else
                        -- Rotate counter-clockwise
                        next_x := resize((pipe_regs(i).x - shift_y),
								INTEGER_BITS-1,-FRACTION_BITS);
                        next_y := resize((pipe_regs(i).y + shift_x),
								INTEGER_BITS-1,-FRACTION_BITS);
                        next_z := resize((pipe_regs(i).z - atan_table(i)),
								INTEGER_BITS-1,-FRACTION_BITS);
                    end if;                  
				  -- Assign to next pipeline stage
                    pipe_regs(i+1).x <= next_x;
                    pipe_regs(i+1).y <= next_y;
                    pipe_regs(i+1).z <= next_z;
                    pipe_regs(i+1).valid <= pipe_regs(i).valid;
                    pipe_regs(i+1).mode <= pipe_regs(i).mode;
                else
                    pipe_regs(i+1).valid <= '0';
                end if;
            end loop;
        end if;
    end process;
   -- Output assignment
    z_out <= pipe_regs(ITERATIONS).z;
    x_out <= pipe_regs(ITERATIONS).x;
    y_out <= pipe_regs(ITERATIONS).y;
    valid_out <= pipe_regs(ITERATIONS).valid;
end Behavioral;

To test this module in both the rotating and vectoring mode I created a simple test bench. The test bench will test the CORDIC in both rotating and vectoring mode reporting an error should the expected result vary by more than the accepted margin of error.

 


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.FIXED_PKG.ALL;
use IEEE.MATH_REAL.ALL;
entity cordic_pipelined_tb is
end cordic_pipelined_tb;
architecture Behavioral of cordic_pipelined_tb is
    -- Constants
    constant CLK_PERIOD : time := 10 ns;
    constant INTEGER_BITS : integer := 3;
    constant FRACTION_BITS : integer := 16;
    constant ITERATIONS : integer := 16;
    constant ERROR_THRESHOLD : real := 0.001; -- Maximum allowed error

    -- Component declaration
    component cordic_pipelined is
        generic (
            INTEGER_BITS : integer := 2;
            FRACTION_BITS : integer := 14;
            ITERATIONS : integer := 16
        );
        port (
            clk          : in  std_logic;
            rst          : in  std_logic;
            data_valid   : in  std_logic;
            mode         : in  std_logic;
            angle_or_zero: in  sfixed
						(INTEGER_BITS-1 downto -FRACTION_BITS);
            x_in         : in  sfixed
						(INTEGER_BITS-1 downto -FRACTION_BITS);
            y_in         : in  sfixed
						(INTEGER_BITS-1 downto -FRACTION_BITS);
            z_out        : out sfixed
						(INTEGER_BITS-1 downto -FRACTION_BITS);
            x_out        : out sfixed
						(INTEGER_BITS-1 downto -FRACTION_BITS);
            y_out        : out sfixed
						(INTEGER_BITS-1 downto -FRACTION_BITS);
            valid_out    : out std_logic        );
    end component;
    -- Signal declarations
    signal clk          : std_logic := '0';
    signal rst          : std_logic := '1';
    signal data_valid   : std_logic := '0';
    signal mode         : std_logic := '0';
    signal angle_or_zero: sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
    signal x_in         : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
    signal y_in         : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
    signal z_out        : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
    signal x_out        : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
    signal y_out        : sfixed(INTEGER_BITS-1 downto -FRACTION_BITS);
    signal valid_out    : std_logic;

    -- Test procedure for checking results

    procedure check_rotation_result (
        expected_cos : real;
        expected_sin : real;
        actual_x     : sfixed;
        actual_y     : sfixed;
        test_angle   : string) is
        variable actual_cos_real : real;
        variable actual_sin_real : real;
        variable cos_error, sin_error : real;
    begin
        actual_cos_real := to_real(actual_x);
        actual_sin_real := to_real(actual_y);
        cos_error := abs(actual_cos_real - expected_cos);
        sin_error := abs(actual_sin_real - expected_sin);
        assert cos_error < ERROR_THRESHOLD and sin_error < ERROR_THRESHOLD
            report "Rotation mode test failed for " & test_angle & LF &
                   "Expected cos = " & real'image(expected_cos) & 
                   ", got = " & real'image(actual_cos_real) & LF &
                   "Expected sin = " & real'image(expected_sin) & 
                   ", got = " & real'image(actual_sin_real)
            severity error;
    end procedure;    

    procedure check_vectoring_result (
        expected_angle : real;
        actual_angle   : sfixed;
        test_point     : string) is
        variable actual_mag_real   : real;
        variable actual_angle_real : real;
        variable mag_error, angle_error : real;
    begin 
		actual_angle_real := to_real(actual_angle);
         angle_error := abs(actual_angle_real - expected_angle);
         assert  angle_error < ERROR_THRESHOLD
            report "Vectoring mode test failed for " & test_point & LF &
                   "Expected angle = " & real'image(expected_angle) & 
                   ", got = " & real'image(actual_angle_real)
            severity error;
    end procedure;

begin
    -- Clock generation
    clk <= not clk after CLK_PERIOD/2;

    -- DUT instantiation
    DUT: cordic_pipelined
        generic map (
            INTEGER_BITS => INTEGER_BITS,
            FRACTION_BITS => FRACTION_BITS,
            ITERATIONS => ITERATIONS        )
        port map (
            clk => clk,
            rst => rst,
            data_valid => data_valid,
            mode => mode,
            angle_or_zero => angle_or_zero,
            x_in => x_in,
            y_in => y_in,
            z_out => z_out,
            x_out => x_out,
            y_out => y_out,
            valid_out => valid_out        );

    -- Stimulus process
    stim_proc: process
        variable pi : real := MATH_PI;
        variable test_angles : real_vector(0 to 4) := 
							(0.0, pi/6.0, pi/4.0, pi/3.0, pi/2.0);
        variable test_points : real_vector(0 to 4)  := 
							(1.0, 0.866, 0.707, 0.5, 0.0001);
    begin
        -- Reset sequence
        rst <= '1';
        wait for CLK_PERIOD*2;
        rst <= '0';
        wait for CLK_PERIOD*2;
        -- Test Rotation Mode
        report "Testing Rotation Mode...";
        mode <= '0';
        for i in test_angles'range loop
            data_valid <= '1';
            angle_or_zero <= to_sfixed(test_angles(i), 
							INTEGER_BITS-1, -FRACTION_BITS);
            x_in <= to_sfixed(1.0, INTEGER_BITS-1, -FRACTION_BITS);
            y_in <= to_sfixed(0.0, INTEGER_BITS-1, -FRACTION_BITS);
            wait for CLK_PERIOD;
            data_valid <= '0';
            -- Wait for result
            wait until valid_out = '1';
            wait for CLK_PERIOD/2; -- Align with clock edge

            -- Check results
            check_rotation_result(
                cos(test_angles(i)),
                sin(test_angles(i)),
                x_out,
                y_out,
                "angle = " & real'image(test_angles(i))
            );

            wait for CLK_PERIOD*2;
        end loop;

        -- Test Vectoring Mode
        report "Testing Vectoring Mode...";
        mode <= '1';
        for i in test_points'range loop
            data_valid <= '1';
            angle_or_zero <= to_sfixed(0.0, 
						INTEGER_BITS-1, -FRACTION_BITS);
            x_in <= to_sfixed(test_points(i), 
						INTEGER_BITS-1, -FRACTION_BITS);
            y_in <= to_sfixed(1.0, INTEGER_BITS-1, -FRACTION_BITS);

            wait for CLK_PERIOD;
            data_valid <= '0';           
		   -- Wait for result
            wait until valid_out = '1';
            wait for CLK_PERIOD/2;

           -- Check results
            check_vectoring_result(
                arctan(1.0/test_points(i)),
                z_out,
                "point = (" & real'image(test_points(i)) & ", 1.0)"
            );
            wait for CLK_PERIOD*2;
        end loop;

        report "CORDIC Testing Completed" severity failure; 
   
    end process;
end Behavioral; 

Running this in simulation shows the results are calculated as expected.



Workshops and Webinars


If you enjoyed the blog why not take a look at the free webinars, workshops and training courses we have created over the years. Highlights include



Boards


Get an Adiuvo development board



Embedded System Book   


Do you want to know more about designing embedded systems from scratch? Check out our book on creating embedded systems. This book will walk you through all the stages of requirements, architecture, component selection, schematics, layout, and FPGA / software design. We designed and manufactured the board at the heart of the book! The schematics and layout are available in Altium here   Learn more about the board (see previous blogs on Bring up, DDR validation, USB, Sensors) and view the schematics here.


0 comments

Recent Posts

See All
bottom of page