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:
- 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. - 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_inis asserted.vld_outpulses 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;
}