Use Cordic to Implement DDS with Verilog Code

Make it to the Right and Larger Audience

Blog

Use Cordic to Implement DDS with Verilog Code

Cordic based DDS is based on the previous post in this series that uses Cordic to calculate sine and cosine of an arbitrary angle. The difference is in previous post, we have one cordic core and it is reused 16 times to do the 16 iterations with the penalty of slow throughput. To implement DDS, we need an output per clock cycle. So instead of time-sharing one core, we implement 16 cores and use pipelining to generate sine output per clock cycle.

Here is the m code to prepare the parameters:

% Conventional CORDIC DDS

% clean up
close all;
clear all;
clc;

% declare parameter for phase accumulator
fclk = 100e6;       % 100MHz
phase_depth = 32;   % 32 bit accumulator depth
fout = 1e6;         % set output f (the actrual f is slightly different)

% declare equivalent table depth
input_width = 15;   % this is the index after Q unit

% define scaling
scale_vec = 2^15;   % s16,15
scale_ang = 2^(input_width);

% attention to the f <--> w(=2*pi*f) conversion
% w = 2*pi*f, CORDIC needs w as angle input
% truncated phase accumulator outputs f (not w)
% we normalize w to 1, i.e. divide by 2*pi
% 2^input_width equals 2*pi

% declare parameter for number of samples
num_of_sample = 2^10;% set it as you please

% phase increment calculation
phase_inc = fout*2^phase_depth/fclk;
phase_inc = round(phase_inc);
fprintf('Desired output frequcecy = %d Hz\n',fout);
fprintf('Clock rate = %d Hz\n',fclk);
fprintf('Phase bit width = %d bit\n',phase_depth);
fprintf('Phase increment value is  = %d \n',phase_inc);

% phase accumulator
M = 2^phase_depth;
acc = 0;
l = zeros(1,num_of_sample);
for i = 1:num_of_sample
    acc = acc + phase_inc;
    l(i) = mod(acc,M);
end

% phase quantization truncation used (this is the Q unit)
l = floor(l./2^(phase_depth-input_width));

%----------------------CORDIC---------------------%

% declare parameter for CORDIC
iteration = 16;

% create arctan table
atan_table = zeros(1,iteration);
for i=1:1:iteration
  atan_table(i) = atan(1/2^(i-1));
end;
% atan table fixed-point, normalized
atan_table = round(atan_table*scale_ang/(2*pi));
atan_table_hex = dec2hex(atan_table)

% gain factor calculation
K = 1.0;
for i=1:1:iteration
	K = K*cos(atan(1/2^(i-1)));
end;
K = round(K*scale_vec) % used for pre-rotation to cancel rotaion gain

cos_out = zeros(1,num_of_sample);
sin_out = zeros(1,num_of_sample);
for i = 1:1:num_of_sample
    
    % input quardrant fitting
    % CORDIC does have a convergence range
    z_in = l(i) - scale_ang/2; % 0-2*pi --> -pi-pi
    if (z_in>=0)
        quad_sel = 0;
        z_in = z_in - scale_ang/4;       % -pi-pi --> -pi/2-pi/2
    else
        quad_sel = 1;
        z_in = z_in + scale_ang/4;       % -pi-pi --> -pi/2-pi/2
    end

    % CORDIC main
    for k=1:1:iteration
        if (k==1)
            a_in = K;           % pre-rotation to cancel rotaion gain
            b_in = 0;
            [a_out,b_out,z_out] = cordic_cell(a_in,b_in,z_in,atan_table(k),k-1,0);
        else
            a_in = a_out;
            b_in = b_out;
            z_in = z_out;
            [a_out,b_out,z_out] = cordic_cell(a_in,b_in,z_in,atan_table(k),k-1,0);
        end
    end;
    
    % output quardrant fitting
    % one more word, in RTL implementation, be aware of the corner case
    % eg. -2^15, if negated, is 2^15, overflow.
	switch quad_sel
        case 0
            cos_out(i) = b_out;
            sin_out(i) =-a_out;
        case 1
            cos_out(i) =-b_out;
            sin_out(i) = a_out;
	end
    
end

%------------------------------------------------%

%------------------------REF---------------------%

% if you intend to bulid a DDS using LUT, this is it.

% build lookup table
N = 2^input_width;
t = (0:N-1)*2*pi/N;

% sine/cosine calculation
cos_tab = cos(t);
sin_tab = sin(t);

% output scaling
lut_max = scale_vec - 1;
lut_min = - lut_max;
cos_tab = round(cos_tab.*scale_vec);
cos_tab(cos_tab>lut_max) = lut_max;
cos_tab(cos_tab<lut_min) = lut_min;
sin_tab = round(sin_tab.*scale_vec);
sin_tab(sin_tab>lut_max) = lut_max;
cos_tab(cos_tab<lut_min) = lut_min;

% sine/cosine output
p = l + 1; % integer to index
cos_lut = cos_tab(p);
sin_lut = sin_tab(p);
%------------------------------------------------%

% plot result
x = linspace(0,floor(1e9/fout),floor(2^phase_depth/phase_inc));
figure;
subplot(2,1,1); 
plot(x,cos_lut(1:floor(2^phase_depth/phase_inc)),'r.-');
hold on;
plot(x,cos_out(1:floor(2^phase_depth/phase_inc)),'b.--');
xlabel('ns');
ylabel('cos(\Theta)');
title('cos results');
subplot(2,1,2);
plot(x,sin_lut(1:floor(2^phase_depth/phase_inc)),'r.-');
hold on;
plot(x,sin_out(1:floor(2^phase_depth/phase_inc)),'b.--');
xlabel('ns');
ylabel('sin(\Theta)');
title('sin results');
hold off;

% calculate SNR
SNR = 20*log10(norm(cos_out)/norm(cos_lut-cos_out));
fprintf('SNR of cos output = %4.2f dB\n',SNR);

 

Here is the result when running above m-code:

Desired output frequcecy = 1000000 Hz
Clock rate = 100000000 Hz
Phase bit width = 32 bit
Phase increment value is  = 42949673 

atan_table_hex =

1000
0972
04FE
0289
0146
00A3
0051
0029
0014
000A
0005
0003
0001
0001
0000
0000


K =

       19898

SNR of cos output = 74.58 dB

cordic_dds_m

Here is simulation output and we set x/y output to use analog format. So they have sin/cos wave shape as expected.

cordic_dds

Here is the zip file for full verilog code, tb, and m code:

.cordic_dds.zip

 
Senior Engineer
Author brief is empty
Tags:

1 Comment
  1. earnestwu 4 years ago
    0
    -0

    Thanks! The code all works on my side. Good reference to get me started using Cordic.

    5

Contact Us

Thanks for helping us better serve the community. You can make a suggestion, report a bug, a misconduct, or any other issue. We'll get back to you using your private message ASAP.

Sending

©2019  ValPont.com

Forgot your details?