FPGA之DDS信号发生器(个人学习参考)
- 如图,一个量化的32点的正弦波,也就是说一个ROM里存了32个这样的数据,每次读出一个数据要1ms,分别读出1,2,3...30,31,32,共32个点,读取完整的正弦波需要1ms * 32 = 32ms的时间
该正弦波参数为
- 周期T = 1ms * 32 = 32ms,
- 频率为 f = 1/T = 1/(1ms * (32/1))
- 在读出一个数据时间不变(1ms)的情况下,想要让读出的正弦波频率增加一倍,那就要间隔读取,分别读出2,4,6,8,10...28,30,32,此时只需要读16个点
那么读出完整正弦波的参数为
- 周期T = 1ms * 16 = 16ms
- 频率f = 1/T = 1/(1ms * 16) = 1/(1ms * (32/2))
- 想要读出的正弦波频率减少一倍,那就要插值读取,分别读出0.5,1,1.5,2,2.5,3...30.5,31,31.5,32,此时要读64个点
读出正弦波的参数为
- 周期T = 1ms * 64 = 64ms
- 频率f = 1/T = 1/(1ms * 64) = 1/(1ms * (32/0.5))
这里,1ms即为Tclk,fclk = 1/Tclk = 1/1ms;32 = 2^5即为N=5,而32除以的数(1,2,0.5)即为频率控制字fword,那么fo = (fclk * fword)/(2^N)
通常,FPGA并不擅长浮点运算,第三种情况,上式的(32/0.5)是很难实现的,因此在正弦波周期一样的情况下,将精度N调高一位,N=6,(2^5 * 2)/(0.5 * 2),此时fword就不用为0.5,而是1
为什么地址是由相位控制字加频率控制字高12位得到的?
1、本次实验使用的rom是宽度为14,深度为2^12 = 4096的数据,所以相位控制字根据rom的深度选择了12位宽
2、为什么ROM宽度是14,深度不取2^14?FPGA资源不够,没有这么多的寄存器存取这么多的数据
3、地址 = 相位 + 频率更迭,而相位宽度为12位,频率的宽度比相位多,所以频率控制字取高几位是由相位控制字的宽度决定的
4、取频率控制字高12位是如何完成频率变换的?
举例:
2^1 = 2'b10
2^2 = 3'b100
2^3 = 4'b1000
......
2^19 = 20'b1000_0000_0000_0000_0000
2^20 = 21'b1_0000_0000_0000_0000_0000
2^21 = 22'b10_0000_0000_0000_0000_0000
f = 1/T,N = 32
频率控制字为:2^20
fword_acc[31:0] + 2^20 相当于 (fword_acc[31:20] + 1)此时就是按照地址+1的速度寻址,假如Fclk = 50MHz(系统时钟),Tclk = 20ns,输出波形的周期就为:To = 20ns * 4096
频率控制字为:2^19
fword_acc[31:0] + (2^19 + 2^19) 相当于 (fword_acc[31:20] + 1),也就是要加两次频率控制字,才能实现一次地址+1,Tclk = 20ns,输出完整波形就要输出2次*4096个数据,输出的波形周期为:To = 20ns * (2 * 4096)
频率控制字为:2^21
fword_acc[31:0] + (2^21) 相当于 (fword_add[31:20] + 2'b10),加一次频率控制字,实现一次地址+2,Tclk = 20ns,因为是跳过一位地址取的数据,所以数据量减半,输出完整波形只需要输出4096/2个数据,输出的波形周期为:To = 20ns * (4096/2)
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 2022/01/10 15:31:44 // Design Name: // Module Name: DDS // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module DDS( clk , ce , we, data, reset, sine, cose ); //参数定义 parameter DATA_W = 32; parameter DATA_W1 = 16; //输入信号定义 input clk ; input reset ; input [DATA_W-1:0] data; input we; input ce; //输出信号定义 output[DATA_W1-1:0] sine ; output[DATA_W1-1:0] cose ; //中间信号定义 reg [DATA_W-1:0] ADD_A ; reg [DATA_W-1:0] ADD_B ; reg [DATA_W1-1:0] cose_DR ; reg [DATA_W1-1:0] sine_DR ; wire [DATA_W-1:0] data; wire [DATA_W1-1:0] cose_D; wire [DATA_W1-1:0] sine_D; wire [9:0] ROM_A; assign cose=cose_DR; assign sine=sine_DR; assign ROM_A=ADD_B[31:22]; //这里比较关键,需要明白为什么是高位开始想加 //时序逻辑写法 always@(posedge clk or negedge reset)begin if(reset)begin ADD_A <=0; end else if(we) begin ADD_A <=data; end end always@(posedge clk or negedge reset)begin if(reset)begin ADD_B <=0; end else if(ce) begin ADD_B <=ADD_B + ADD_A; end end always@(posedge clk or negedge reset)begin if(reset)begin cose_DR <=0; end else if(ce) begin cose_DR <=cose_D; end end always@(posedge clk or negedge reset)begin if(reset)begin sine_DR <=0; end else if(ce) begin sine_DR <=sine_D; end end cos u1 ( .clka(clk), // input wire clka .addra(ROM_A), // input wire [9 : 0] addra .douta(cose_D) // output wire [15 : 0] douta ); sine u2 ( .clka(clk), // input wire clka .addra(ROM_A), // input wire [9 : 0] addra .douta(sine_D) // output wire [15 : 0] douta ); endmodule
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 2022/01/10 15:57:17 // Design Name: // Module Name: dds_tb // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module dds_tb(); //时钟和复位 reg clk ; reg rst_n; reg we; reg ce; //uut的输入信号 reg[31:0] data ; //uut的输出信号 wire[15:0] cos; wire[15:0] sin; //时钟周期,单位为ns,可在此修改时钟周期。 parameter CYCLE = 20; //复位时间,此时表示复位3个时钟周期的时间。 parameter RST_TIME = 3 ; //待测试的模块例化 DDS uut( .clk (clk ), .reset (rst_n ), .we (we ), .ce (ce ), .data (data ), .sine (sin ), .cose (cos ) ); //生成本地时钟50M initial begin clk = 0; forever #(CYCLE/2) clk=~clk; end //产生复位信号 initial begin rst_n = 0; #2; rst_n = 1; #(CYCLE*RST_TIME); rst_n = 0; end //输入信号din0赋值方式 initial begin #1; //赋初值 we = 0; #(5*CYCLE); //开始赋值 we = 1; end //输入信号din1赋值方式 initial begin #1; //赋初值 ce = 0; #(10*CYCLE); //开始赋值 ce=1; end initial begin #1; //赋初值 data = 4194304; end endmodule