Designing a DDS and Phase Calculator Using CORDIC in Verilog

Direct Digital Synthesizer (DDS)

A DDS generates programmable complex tones. The output frequency (f_{out}) is a functon of the system clock (f_{clk}), accumulator width (N), and frequency control word (K):

[f_{out} = \frac{f_{clk}}{2^N} \times K]

The phase accumulator computes (\theta(n) = (K \times n) \bmod 2^N). The complex output is:

[y(n) = e^{j 2\pi \theta(n) / 2^N} = \cos\left(\frac{2\pi (K n \bmod 2^N)}{2^N}\right) + j \sin\left(\frac{2\pi (K n \bmod 2^N)}{2^N}\right)]

Both real and imaginary parts lie in ((-1, 1)).

Implementation

  • Phase accumulator width (N = 20).
  • Cosine and sine computed using CORDIC in rotation mode.
  • Fully pipelined architecture: one output per clock cycle.
  • Top module: dds
Port Direction Width Description
clk I 1 System clock
rst_n I 1 Asynchronous active-low reset
freq_k I 20 Frequency control word, unsigned, range [1, 2^20-1]
y_re O 16 Real part, signed fixed-point: 1 integer, 15 fractional bits, range (-1,1)
y_im O 16 Imaginary part, same format as y_re

Design Decomposition

The design splits into two blocks:

  1. Phase generator (phase_gen): accumulates (K) each clock. CORDIC convergence is limited to ([-\pi/2, \pi/2]), so the phase is folded into the first quadrant (0–90°). The original quadrant is recorded.
  2. CORDIC core (cordic): 18-stage pipelined rotator. Outputs are adjusted based on the stored quadrant flag.

Code

Parameters.v

`ifndef _PARAMETER_H_
`define _PARAMETER_H_

`define PI 3.1415926

// CORDIC angle constants (24-bit)
`define ANGLE_1  24'sh0c90fd
`define ANGLE_2  24'sh076b19
`define ANGLE_3  24'sh03eb6e
`define ANGLE_4  24'sh01fd5b
`define ANGLE_5  24'sh00ffaa
`define ANGLE_6  24'sh007ff5
`define ANGLE_7  24'sh003ffe
`define ANGLE_8  24'sh001fff
`define ANGLE_9  24'sh000fff
`define ANGLE_10 24'sh0007ff
`define ANGLE_11 24'sh0003ff
`define ANGLE_12 24'sh0001ff
`define ANGLE_13 24'sh0000ff
`define ANGLE_14 24'sh00007f
`define ANGLE_15 24'sh00003f
`define ANGLE_16 24'sh00001f
`define ANGLE_17 24'sh00000f
`define ANGLE_18 24'sh000007

`endif

phase_gen.v

`include "parameters.v"

module phase_gen (
    input clk,
    input rst_n,
    input [19:0] freq_k,
    output [23:0] theta,
    output reg [1:0] quadrant
);

    reg [19:0] acc;
    reg [19:0] acc_folded;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            acc <= 0;
        else
            acc <= acc + freq_k;
    end

    // Fold into first quadrant and record original quadrant
    always @(*) begin
        if (acc < 20'h40000) begin
            acc_folded = acc;
            quadrant = 2'b00;
        end else if (acc < 20'h80000) begin
            acc_folded = acc - 20'h40000;
            quadrant = 2'b01;
        end else if (acc < 20'hc0000) begin
            acc_folded = acc - 20'h80000;
            quadrant = 2'b10;
        end else begin
            acc_folded = acc - 20'hc0000;
            quadrant = 2'b11;
        end
    end

    // Scale to radians: theta = (acc_folded / 2^20) * 2*pi
    // For synthesis, approximate 2*pi ≈ 6.28318 as (4 + 2 + 0.25 + 0.125)
    // Here we use a direct multiplication for simulation clarity.
    assign theta = `PI * 2 * acc_folded;

endmodule

cordic.v (simplified pipeline structure using generate for brevity)

`include "parameters.v"

module cordic (
    input clk,
    input rst_n,
    input signed [23:0] angle,
    input [1:0] quadrant,
    output reg signed [15:0] cos_out,
    output reg signed [15:0] sin_out
);

    localparam STAGES = 18;
    // X and Y pipeline registers
    reg signed [15:0] x [STAGES:0];
    reg signed [15:0] y [STAGES:0];
    reg signed [23:0] z [STAGES:0];
    reg [1:0] q_pipe [STAGES:0];

    // Initialization: set initial vector (K, 0)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            x[0] <= 0;
            y[0] <= 0;
            z[0] <= 0;
        end else begin
            x[0] <= 16'sh4DBA;  // CORDIC gain compensation ≈ 0.6073
            y[0] <= 0;
            z[0] <= angle;
        end
    end

    // CORDIC rotation stages
    genvar i;
    generate
        for (i = 0; i < STAGES; i = i + 1) begin : stage
            always @(posedge clk or negedge rst_n) begin
                if (!rst_n) begin
                    x[i+1] <= 0;
                    y[i+1] <= 0;
                    z[i+1] <= 0;
                end else if (z[i] > 0) begin
                    x[i+1] <= x[i] - (y[i] >>> i);
                    y[i+1] <= y[i] + (x[i] >>> i);
                    z[i+1] <= z[i] - angle_const[i];
                end else begin
                    x[i+1] <= x[i] + (y[i] >>> i);
                    y[i+1] <= y[i] - (x[i] >>> i);
                    z[i+1] <= z[i] + angle_const[i];
                end
            end
        end
    endgenerate

    // Pipeline quadrant flag
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            for (integer j = 0; j <= STAGES; j = j + 1)
                q_pipe[j] <= 0;
        end else begin
            q_pipe[0] <= quadrant;
            for (integer j = 1; j <= STAGES; j = j + 1)
                q_pipe[j] <= q_pipe[j-1];
        end
    end

    // Output reconstruction based on original quadrant
    always @(*) begin
        case (q_pipe[STAGES])
            2'b00: begin cos_out = x[STAGES]; sin_out = y[STAGES]; end
            2'b01: begin cos_out = -y[STAGES]; sin_out = x[STAGES]; end
            2'b10: begin cos_out = -x[STAGES]; sin_out = -y[STAGES]; end
            2'b11: begin cos_out = y[STAGES]; sin_out = -x[STAGES]; end
            default: begin cos_out = x[STAGES]; sin_out = y[STAGES]; end
        endcase
    end

    // Angle constants for each stage (values from Parameters.v)
    wire signed [23:0] angle_const [0:STAGES-1];
    assign angle_const[0]  = `ANGLE_1;
    assign angle_const[1]  = `ANGLE_2;
    assign angle_const[2]  = `ANGLE_3;
    assign angle_const[3]  = `ANGLE_4;
    assign angle_const[4]  = `ANGLE_5;
    assign angle_const[5]  = `ANGLE_6;
    assign angle_const[6]  = `ANGLE_7;
    assign angle_const[7]  = `ANGLE_8;
    assign angle_const[8]  = `ANGLE_9;
    assign angle_const[9]  = `ANGLE_10;
    assign angle_const[10] = `ANGLE_11;
    assign angle_const[11] = `ANGLE_12;
    assign angle_const[12] = `ANGLE_13;
    assign angle_const[13] = `ANGLE_14;
    assign angle_const[14] = `ANGLE_15;
    assign angle_const[15] = `ANGLE_16;
    assign angle_const[16] = `ANGLE_17;
    assign angle_const[17] = `ANGLE_18;

endmodule

dds.v (top module)

module dds (
    input clk,
    input rst_n,
    input [19:0] freq_k,
    output [15:0] y_re,
    output [15:0] y_im
);

    wire [23:0] phase;
    wire [1:0] quad;

    phase_gen u_ph (
        .clk(clk),
        .rst_n(rst_n),
        .freq_k(freq_k),
        .theta(phase),
        .quadrant(quad)
    );

    cordic u_cord (
        .clk(clk),
        .rst_n(rst_n),
        .angle(phase),
        .quadrant(quad),
        .cos_out(y_re),
        .sin_out(y_im)
    );

endmodule

Simulation

  • The pipeline fills after 18 clocks; thereafter one output per cycle.
  • Output format: 16-bit signed fixed-point (1 integer, 15 fractional).
    • For positive sign: decimal = value / 2^15.
    • For negative sign: decimal = (value - 2^16) / 2^15.
  • Example with freq_k = 83886 (assuming 100 MHz clock → 8 MHz tone) yields correct sinusoidal outputs and spectral peak at 8 MHz.

Complex Phase Angle Calculation

Design a sequential circuit that computes the normalized phase angle (P \in [-1, 1)) of a complex number (Z = X + jY).

Examples:

  • (Z = 10 + 10j \Rightarrow P = 0.25)
  • (Z = -3 + 4j \Rightarrow P = 0.704833)
  • (Z = 3 - 4j \Rightarrow P = -0.295167)
  • (Z = -10 + 0j \Rightarrow P = -1)

Implementation

  • Uses CORDIC in vectoring mode.
  • Serial (iterative) architecture: one output after 15 clock cycles.
  • Input is first mapped to the first quadrant to guarantee convergence.
  • Internal precision is increased by 10 fractional bits to reduce quantization error.
  • Top module: calc_phase
Port Direction Width Description
clk I 1 System clock
rst_n I 1 Asynchronous active-low reset
vld_in I 1 Input valid indication
x I 16 Real part, signed two's complement
y I 16 Imaginary part, signed two's complement
vld_out O 1 Output valid indication
p O 16 Phase, fixed-point: 1 integer, 15 fractional, range [-1, 1)

Code

Parameters.v (for phase module)

`ifndef _PARAMETER_PHASE_H_
`define _PARAMETER_PHASE_H_

`define INV_PI 0.3183099

`define ANGLE_1  16'sh6487
`define ANGLE_2  16'sh3b58
`define ANGLE_3  16'sh1f5b
`define ANGLE_4  16'sh0fea
`define ANGLE_5  16'sh07fd
`define ANGLE_6  16'sh03ff
`define ANGLE_7  16'sh01ff
`define ANGLE_8  16'sh00ff
`define ANGLE_9  16'sh007f
`define ANGLE_10 16'sh003f
`define ANGLE_11 16'sh001f
`define ANGLE_12 16'sh000f
`define ANGLE_13 16'sh0007
`define ANGLE_14 16'sh0003
`define ANGLE_15 16'sh0001

`endif

input_process.v

module input_process (
    input signed [15:0] x,
    input signed [15:0] y,
    input vld_in,
    output signed [15:0] x_norm,
    output signed [15:0] y_norm,
    output reg [2:0] quad
);

    always @(*) begin
        if (vld_in) begin
            if (x > 0 && y > 0) begin
                x_norm = x;
                y_norm = y;
                quad = 3'b000;
            end else if (x < 0 && y > 0) begin
                x_norm = -x;
                y_norm = y;
                quad = 3'b001;
            end else if (x < 0 && y < 0) begin
                x_norm = -x;
                y_norm = -y;
                quad = 3'b010;
            end else if (x > 0 && y < 0) begin
                x_norm = x;
                y_norm = -y;
                quad = 3'b011;
            end else if (x > 0 && y == 0) begin
                x_norm = x;
                y_norm = 0;
                quad = 3'b100;
            end else if (x == 0 && y > 0) begin
                x_norm = 0;
                y_norm = y;
                quad = 3'b101;
            end else if (x < 0 && y == 0) begin
                x_norm = -x;
                y_norm = 0;
                quad = 3'b110;
            end else begin // x == 0 && y < 0
                x_norm = 0;
                y_norm = -y;
                quad = 3'b111;
            end
        end else begin
            x_norm = 0;
            y_norm = 0;
            quad = 3'b000;
        end
    end

endmodule

cordic_vectoring.v

`include "parameters.v"

module cordic_vectoring (
    input clk,
    input rst_n,
    input signed [15:0] x,
    input signed [15:0] y,
    input [2:0] quad,
    output reg vld_out,
    output reg signed [15:0] phase_out
);

    reg [3:0] cnt;
    reg signed [25:0] xi, yi, xt;
    reg signed [15:0] angle_acc;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt <= 0;
            vld_out <= 0;
            phase_out <= 0;
        end else begin
            cnt <= cnt + 1;
            case (cnt)
                4'b0000: begin
                    xi <= {x, 10'b0};   // extend precision
                    yi <= {y, 10'b0};
                    angle_acc <= 0;
                    vld_out <= 0;
                end
                4'b0001: begin ... end  // stage with `ANGLE_1
                4'b0010: begin ... end  // stage with `ANGLE_2
                // ... (similar for stages 3 to 15)
                4'b1111: begin
                    // last iteration
                    if (yi > 0) begin
                        xt = xi;
                        xi = xi + (yi >>> 14);
                        yi = yi - (xt >>> 14);
                        angle_acc = angle_acc + `ANGLE_15;
                    end else begin
                        xt = xi;
                        xi = xi - (yi >>> 14);
                        yi = yi + (xt >>> 14);
                        angle_acc = angle_acc - `ANGLE_15;
                    end
                    vld_out <= 1;
                    // Reconstruct phase based on quadrant
                    case (quad)
                        3'b000: phase_out <= angle_acc * `INV_PI;
                        3'b001: phase_out <= 16'sh7FFF - angle_acc * `INV_PI;
                        3'b010: phase_out <= 16'sh8000 + angle_acc * `INV_PI;
                        3'b011: phase_out <= -(angle_acc * `INV_PI);
                        3'b100: phase_out <= 0;
                        3'b101: phase_out <= 16'sh4000;
                        3'b110: phase_out <= 16'sh8000;
                        3'b111: phase_out <= 16'shC000;
                        default: phase_out <= 0;
                    endcase
                end
                default: vld_out <= 0;
            endcase
        end
    end

endmodule

calc_phase.v

module calc_phase (
    input clk,
    input rst_n,
    input vld_in,
    input [15:0] x,
    input [15:0] y,
    output vld_out,
    output [15:0] p
);

    wire [15:0] xn, yn;
    wire [2:0] quad;

    input_process u_inp (
        .x(x),
        .y(y),
        .vld_in(vld_in),
        .x_norm(xn),
        .y_norm(yn),
        .quad(quad)
    );

    cordic_vectoring u_cord (
        .clk(clk),
        .rst_n(rst_n),
        .x(xn),
        .y(yn),
        .quad(quad),
        .vld_out(vld_out),
        .phase_out(p)
    );

endmodule

Simulation Results

  • Output latency: 15 clock cycles after vld_in is asserted. vld_out pulses high when valid.
  • Test cases:
    • (10, 10) → 0x2000 → 0.25
    • (-3, 4) → 0x5A38 → 0.704834
    • (3, -4) → 0xDA39 → -0.295135
    • (-10, 0) → 0x8000 → -0.5

Appendix: C/C++ Reference Models

Cordic.cpp (for DDS verifictaion)

#include <iostream>
#include <cmath>
using namespace std;

const double Kproduct = 0.607252935008881; // product of cos(atan(2^-i))
const double atan_table[18] = {
    0.785398163397448, 0.463647609000806, 0.244978663126864, 0.124354994546761,
    0.062418809995957, 0.031239833430268, 0.015623728620477, 0.007812341060101,
    0.003906230131967, 0.001953122516479, 0.000976562189559, 0.000488281211195,
    0.000244140620149, 0.000122070311894, 0.000061035156174, 0.000030517578116,
    0.000015258789061, 0.000007629394531
};

double my_cos(double theta) {
    double x = Kproduct, y = 0.0, z = 0.0;
    for (int i = 0; i < 18; i++) {
        double xt = x;
        if (theta > z) {
            x = x - y * pow(2, -i);
            y = y + xt * pow(2, -i);
            z += atan_table[i];
        } else {
            x = x + y * pow(2, -i);
            y = y - xt * pow(2, -i);
            z -= atan_table[i];
        }
    }
    return x;
}

double my_sin(double theta) {
    double x = Kproduct, y = 0.0, z = 0.0;
    for (int i = 0; i < 18; i++) {
        double xt = x;
        if (theta > z) {
            x = x - y * pow(2, -i);
            y = y + xt * pow(2, -i);
            z += atan_table[i];
        } else {
            x = x + y * pow(2, -i);
            y = y - xt * pow(2, -i);
            z -= atan_table[i];
        }
    }
    return y;
}

Cordic_atan.cpp (for phase calculator verification)

#include <iostream>
#include <cmath>
using namespace std;

const double atan_table[15] = {
    0.785398163397448, 0.463647609000806, 0.244978663126864, 0.124354994546761,
    0.062418809995957, 0.031239833430268, 0.015623728620477, 0.007812341060101,
    0.003906230131967, 0.001953122516479, 0.000976562189559, 0.000488281211195,
    0.000244140620149, 0.000122070311894, 0.000061035156174
};

double my_atan2(double y, double x) {
    double z = 0.0;
    for (int i = 0; i < 15; i++) {
        double xt = x;
        if (y > 0) {
            x = x + y * pow(2, -i);
            y = y - xt * pow(2, -i);
            z += atan_table[i];
        } else {
            x = x - y * pow(2, -i);
            y = y + xt * pow(2, -i);
            z -= atan_table[i];
        }
    }
    return z;
}

Tags: Verilog DDS CORDIC FPGA Digital Design

Posted on Wed, 13 May 2026 17:19:43 +0000 by php_guest