【1】DDS简介
DAC
假设你的FPGA开发板有一个快速的DAC(数模转换器)模拟输出。以下是一个100MHz下运转的10位DAC与FPGA相连。
在100MHz下,FPGA每隔10ns就会给DAC发送一个新的10位数据。
一个简单的DDS
DDS通常用来生成周期信号,这里我们先来生成一个方波看看。
- module SimpleDDS(DAC_clk, DAC_data);
- input DAC_clk;
- output [9:0] DAC_data;
- // 创建一个16位2进制计数器
- reg [15:0] cnt;
- always @(posedge DAC_clk) cnt <= cnt + 16'h1;
- // 用它来生成DAC信号
- wire cnt_tap = cnt[7];
- assign DAC_data = {10{cnt_tap}};
- endmodule
以上代码我们用计数器的第八位来生成输出。在100MHz下,第八位变化的频率为100MHz/2^8=390kHz。所以DAC输出为390kHz的方波。
如果想要生成锯齿波的话,需要将最后两句代码改为
- assign DAC_data = cnt[9:0];
也可以生成三角波
- assign DAC_data = cnt[10] ? ~cnt[9:0] : cnt[9:0];
此处我们创建了一个简单的DDS,但实际的DDS应该可以:
1.生成任何形状的信号
2.生成任何频率的信号
【2】任意信号
要想生成任意信号,DDS必须依靠两招。
LUT
第一招是使用LUT(查找表)。LUT是一张保存有模拟信号形状的表。
在FPGA中,LUT一般由BLOCK RAM实现。如上图所示,我们用了一个512x10位的LUT,这样的通常需要用到一个到两个物理FPGA的blockram。
正弦波
最常见的波形当属正弦波了。它的特殊之处就在于它是对称的,能被LUT充分利用。
在正弦波中,第一个对称就是sin(a)=sin(π-a)。假设我们实例化一个blockram“my_DDS_LUT”
- wire [9:0] LUT_output;
- blockram512x10bit_2clklatency my_DDS_LUT(.rdclock(clk), .rdaddress(cnt[8:0]), .q(LUT_output));
然后我们可以反向实现第一个对称
- blockram512x10bit_2clklatency my_DDS_LUT(.rdclock(clk), .rdaddress(cnt[9] ? ~cnt[8:0] : cnt[8:0]), .q(LUT_output));
如今我们已经存储了一般的波形,但输出信号每个周期的内容都被用了两遍,要是算上第二个对称的话,那么LUT的大小就是2048x10位。
我们用blockram “blockram512x10bit_2clklatency”以两个时钟延迟来发送数据(因为单个时钟延迟的blockram要更慢),接下来如何实现就取决于FPGA芯片的厂商了。(Altera用的是LPM,而Xilinx用的是primi
tive)。所以接下来我们利用正弦波的两条对称来重写代码。
- module sine_lookup(input clk, input [10:0] addr, output reg [16:0] value);
- wire [15:0] sine_1sym;
- blockram512x16bit_2clklatency my_quarter_sine_LUT(
- .rdclock(clk),
- .rdaddress(addr[9] ? ~addr[8:0] : addr[8:0]),
- .q(sine_1sym)
- );
- reg addr10_delay1; always @(posedge clk) addr10_delay1 <= addr[10];
- reg addr10_delay2; always @(posedge clk) addr10_delay2 <= addr10_delay1;
- wire [15:0] sine_2sym = addr10_delay2 ? {1'b0,-sine_1sym} : {1'b1,sine_1sym};
- always @(posedge clk) value <= sine_2sym;
- endmodule
要注意sine_lookup模块总共有3个时钟延迟(两个来自blockram,一个来自输出)。
我们还可以将blockram的位数从10位改到16位。这样新的代码为
- reg [10:0] cnt;
- always @(posedge clk) cnt <= cnt + 11'h1;
- wire [16:0] sine_lookup_output;
- sine_lookup my_sine(.clk(clk), .addr(cnt), .value(sine_lookup_output));
- wire [9:0] DAC_data = sine_lookup_output[16:7];