DDS频率控制字设计详解

前言:
DDS的频率精度受限于\(fs/2^N\),其中,\(2^N\)为频率控制字寄存器宽度。书上说,N可以比查找表的地址宽度大,到时候只需要截取高位送到查找表即可。说的很粗糙,怎么量化分析这个过程?为什么只需要截取高位呢?本文将带你分析一下这个问题。

首先,假定我们需要产生模拟域频率为\(f_c\)的正弦信号,时钟频率为\(f_s\),采样周期为\(T_s=1/fs\)

\[\begin{equation} x(t)\vert _{t=nT_s}=cos(2\pi f_c t)\vert _{t=nT_s}=cos(2\pi f_c/f_s \cdot n) \label{signal_expression} \end{equation} \]

如果查找表存了整个周期的正弦值,那么地址范围从\([0:1:2^N-1]\)对应了相位的\([0:1/2^N:1-2^{-N}]\),在式\(\eqref{signal_expression}\)中,相位为

\[phase = f_c/f_s \cdot n \]

那么查找表的地址就应该是

\[\begin{equation} addr = floor(phase \cdot 2^N) = floor(f_c/f_s \cdot n \cdot 2^N), \space n=0,1,2,...,+\infty \label{addr} \end{equation} \]

当时间无限延展,地址也会无限扩展,显然,由于信号是周期的,我们可以对地址进行绕回,也就是

\[\begin{equation} addr = mod(addr,2^N) \label{addr_wrap} \end{equation} \]

如此生成的正弦波,将会是一个更为精确的数字。

算法中有一个关键的问题:
1.n是无限延展的,如何用定点数表示?
2.如何进行mod操作?
先来解答第一个问题,分析式\(\eqref{addr}\),可以看出,每次addr的增量是固定的,即fc/fs2^N,那么我们可以把乘法替代为累加运算,也就是改写为addr = addr + fc/fs2^N的形式,发生溢出时正常溢出即可,因为无符号数溢出不会影响低位的正确性。
第二个问题,如何进行mod操作?这个也简单,直接取整数部分的低位即可。

\[\begin{array}{l} \hline \boldsymbol{algorithm} \\ \hline \boldsymbol{Input:}fc \\ \boldsymbol{Output:}addr \\ \hline 1.addr\_temp=0; \\ 2.根据式\eqref{addr},计算常量值 const0 = fc \cdot 1/f_s \\ 3.计算地址增量 addr\_delta = const0*2^N \\ 4.在每个时钟周期: \\ addr\_temp = addr\_delta + addr\_temp; \\ addr = mod(addr\_temp,2^N) \\ \hline \end{array} \]

给出一个设计的例子。
\(fs=12MHz,\ fc = 18Hz\),查找表深度为\(2^{16}\)
首先,对 1/fs 进行定点化。


>> fi(1/12e6,0,12)

ans = 

   8.3324e-08

          DataTypeMode: Fixed-point: binary point scaling
            Signedness: Unsigned
            WordLength: 12
        FractionLength: 35
>> bin(ans)

ans =

    '101100101111'

对输入频率进行量化,假定指定输入频率定点方案为u(16,0),即输入范围为0Hz~2^17-1Hz
对其进行全精度运算,const0=u(12,35)*u(16,0)=u(28,35)
计算地址增量,对const0乘2^16,相当于小数点右移16bit,结果为addr_delta = u(28,19),二进制表示不变
对addr_temp做加法,addr_temp需要确保有16位整数值,以容纳所有可能的wrap,故运算结果应为u(35,19)
addr为取了整数位的addr_temp,即量化方式位wrap,量化结果为u(16,0)。这一步实际上就是引言中对频率控制字寄存器取高位的步骤。
给出伪Verilog代码:

module dds_inftyPrecise
(
input clk,rstn
input [15:0] fc,
output [15:0] addr
)
wire [11:0] 1_fs = 12'b101100101111;
wire [27:0] const0 = fc*1_fs;
wire [34:0] addr_delta = {7'b0,const0};
reg [34:0] addr_temp;
always @(posedge clk or negedge rstn)
if(rstn==0)
	addr_temp<=0;
else
	addr_temp<=addr_temp+addr_delta;

assign addr = addr_temp[34-:16];
endmodule
posted @ 2025-01-07 16:35  蕉太羊  阅读(463)  评论(2)    收藏  举报