FPGA|CPLD|ASICwilliam hill官网
直播中

张静

7年用户 1479经验值
私信 关注
[经验]

FPGA如何制作DDS频率合成器(一)

【1】DDS简介

DAC
假设你的FPGA开发板有一个快速的DAC(数模转换器)模拟输出。以下是一个100MHz下运转的10位DAC与FPGA相连。
1.png

在100MHz下,FPGA每隔10ns就会给DAC发送一个新的10位数据。


一个简单的DDS
DDS通常用来生成周期信号,这里我们先来生成一个方波看看。
  1. module SimpleDDS(DAC_clk, DAC_data);
  2. input DAC_clk;
  3. output [9:0] DAC_data;

  4. // 创建一个16位2进制计数器
  5. reg [15:0] cnt;
  6. always @(posedge DAC_clk) cnt <= cnt + 16'h1;

  7. // 用它来生成DAC信号
  8. wire cnt_tap = cnt[7];
  9. assign DAC_data = {10{cnt_tap}};
  10. endmodule
以上代码我们用计数器的第八位来生成输出。在100MHz下,第八位变化的频率为100MHz/2^8=390kHz。所以DAC输出为390kHz的方波。
2.png
如果想要生成锯齿波的话,需要将最后两句代码改为
  1. assign DAC_data = cnt[9:0];
也可以生成三角波
  1. assign DAC_data = cnt[10] ? ~cnt[9:0] : cnt[9:0];
此处我们创建了一个简单的DDS,但实际的DDS应该可以:
1.生成任何形状的信号
2.生成任何频率的信号

【2】任意信号
要想生成任意信号,DDS必须依靠两招。

LUT
第一招是使用LUT(查找表)。LUT是一张保存有模拟信号形状的表。
3.png
在FPGA中,LUT一般由BLOCK RAM实现。如上图所示,我们用了一个512x10位的LUT,这样的通常需要用到一个到两个物理FPGA的blockram。

正弦波
最常见的波形当属正弦波了。它的特殊之处就在于它是对称的,能被LUT充分利用。

在正弦波中,第一个对称就是sin(a)=sin(π-a)。假设我们实例化一个blockram“my_DDS_LUT
  1. wire [9:0] LUT_output;

  2. blockram512x10bit_2clklatency my_DDS_LUT(.rdclock(clk), .rdaddress(cnt[8:0]), .q(LUT_output));
然后我们可以反向实现第一个对称
  1. 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用的是primitive)。所以接下来我们利用正弦波的两条对称来重写代码。
  1. module sine_lookup(input clk, input [10:0] addr, output reg [16:0] value);

  2. wire [15:0] sine_1sym;
  3. blockram512x16bit_2clklatency my_quarter_sine_LUT(
  4.     .rdclock(clk),
  5.     .rdaddress(addr[9] ? ~addr[8:0] : addr[8:0]),
  6.     .q(sine_1sym)
  7. );

  8. reg addr10_delay1; always @(posedge clk) addr10_delay1 <= addr[10];
  9. reg addr10_delay2; always @(posedge clk) addr10_delay2 <= addr10_delay1;

  10. wire [15:0] sine_2sym = addr10_delay2 ? {1'b0,-sine_1sym} : {1'b1,sine_1sym};

  11. always @(posedge clk) value <= sine_2sym;
  12. endmodule
要注意sine_lookup模块总共有3个时钟延迟(两个来自blockram,一个来自输出)。

我们还可以将blockram的位数从10位改到16位。这样新的代码为
  1. reg [10:0] cnt;
  2. always @(posedge clk) cnt <= cnt + 11'h1;

  3. wire [16:0] sine_lookup_output;
  4. sine_lookup my_sine(.clk(clk), .addr(cnt), .value(sine_lookup_output));

  5. wire [9:0] DAC_data = sine_lookup_output[16:7];  

更多回帖

发帖
×
20
完善资料,
赚取积分