FPGA实现dds(ISE实现)
0赞上次说了dds的原理,这次我们用FPGA来实现dds。
因为dds在da之前都是数字器件,所以我们可以用FPGA来实现dds的前两个部分。
首先要先规定一下:rom的地址输入是12位,输出时8位。
对于第一部分:
这里看出,功能是将寄存器输出的值与频率控制字M的值相加,然后在时钟的上升沿,将相加后的数据通过寄存器输出。所以这里的verilog代码也很简单。
module frequency_tiaozhi( input clk, //输入时钟 input rst_n, //复位信号,用来给寄存器初始复位 input [11:0] k, //频率控制字k output reg [11:0] result //相加后的寄存器输出结果 ); wire [11:0] sum; //相加后的值 assign sum = k + result; always@( posedge clk or negedge rst_n) begin if( !rst_n ) result <= 0; else result <= sum; end endmodule
第二部分:
可以看出,就是将第一部分的输出值加上一个相位控制字。所以代码也很简单:
module phase_tiaozhi( input clk, //输入时钟 input rst_n, //输入复位信号,给寄存器复位 input [11:0] phase, //相位控制字 input [11:0] result, //第一部分的输出结果的输入 output [11:0] address //输出给rom的地址 ); reg [11:0] phase_reg; always@( posedge clk or negedge rst_n ) begin if( !rst_n ) phase_reg <= 0; else phase_reg <= phase; end assign address = result + phase_reg; endmodule
第三部分:
这里就只实现查找表,因为da是模拟器件,FPGA实现不了。
查找表其实是一个rom。所以这里用ISE自带的rom的ip核。既然用到了rom,那么就要对rom里面的值要写入我们需要的值。这里就用ise的rom的ip核的固定初始化文件coe文件。
Coe文件格式是:
第一行是: MEMORY_INITIALIZATION_RADIX=10; 后面的10表示数据是以什么进制表示,这里是10进制,所以是10.如果是16就是16进制表示。
第二行是:MEMORY_INITIALIZATION_VECTOR= 这个是固定的。
接下来第三行就是数据,数据以逗号相隔,最后一个数据以分号结束。
这里我们是要产生正弦波,方波,三角波,所以需要三个rom。这里方便统一,直接将方波的数据也放进rom里面。
那就要对正弦波,方波,三角波分别生成rom初始化文件coe。用matlab来生成。
这里要注意,由于da只能转换正数值,所以要将sin的负值部分要处理一下,使之数据范围在0到1之间。
生成正弦波matlab代码:
t=0:2*pi/2^12:2*pi y=0.5*sin(t)+0.5; r=ceil(y*(2^8-1)); %将小数转换为整数,ceil是向上取整。 fid = fopen('sin.coe','w'); %写到sin.coe文件,用来初始化sin_rom fprintf(fid,'MEMORY_INITIALIZATION_RADIX=10;\n'); fprintf(fid,'MEMORY_INITIALIZATION_VECTOR=\n'); for i = 1:1:2^12 fprintf(fid,'%d',r(i)); if i==2^12 fprintf(fid,';'); else fprintf(fid,','); end if i%15==0 fprintf(fid,'\n'); end end fclose(fid);
生成方波matlab代码:
t=1:1:2^12; y=(t<=2047); r=ceil(y*(2^8-1)); fid = fopen('square.coe','w'); %写到square.coe,用来初始化rom_square fprintf(fid,'MEMORY_INITIALIZATION_RADIX=10;\n'); fprintf(fid,'MEMORY_INITIALIZATION_VECTOR=\n'); for i = 1:1:2^12 fprintf(fid,'%d',r(i)); if i==2^12 fprintf(fid,';'); else fprintf(fid,','); end if i%15==0 fprintf(fid,'\n'); end end fclose(fid);
最后是生成三角波matlab
t=1:1:2^12; y=[0.5:0.5/1024:1-0.5/1024, 1-0.5/1024:-0.5/1024:0, 0.5/1024:0.5/1024:0.5]; r=ceil(y*(2^8-1)); fid = fopen('triangular.coe','w'); %写到triangular.coe,初始化三角波rom fprintf(fid,'MEMORY_INITIALIZATION_RADIX=10;\n'); fprintf(fid,'MEMORY_INITIALIZATION_VECTOR=\n'); for i = 1:1:2^12 fprintf(fid,'%d',r(i)); if i==2^12 fprintf(fid,';'); else fprintf(fid,','); end if i%15==0 fprintf(fid,'\n'); end end fclose(fid);
生成coe文件后,接下来,就要调用ise的rom ip,进行rom的创建。这里,就不说明怎么创建rom了。可自行百度。
这下,我们的模块就都做好了。但是发现,我们没有编写波形选择的模块。输出的波形有三种,所以要选择输出是哪一种。
代码如下:
module wave_select( input [1:0] wave, //波形选择 input [7:0] sin_data, input [7:0] square_data, input [7:0] triangular_data, output reg [7:0] dds_data ); parameter sin = 2'b00; parameter square = 2'b01; parameter triangular = 2'b10; always@(*) begin case(wave) sin: dds_data = sin_data; square: dds_data = square_data; triangular: dds_data = triangular_data; default: dds_data = sin_data; endcase end endmodule
接下来,就是将刚刚设计好的模块连接在一起即可。顶层top代码如下:
module dds_top( input clk, //clock input rst_n, //reset input [1:0] wave, //wave select 00 sin 01 square 10 triaangular input [11:0] k, //adjust frequency input [11:0] phase, //adjust phase output [7:0] dds_data //dds output data ); wire [11:0] result; //frequency add result wire [11:0] address; //phase add result wire [7:0] sin_data; //sin data output wire [7:0] square_data; //square data output wire [7:0] triangular_data; //triangular data output frequency_tiaozhi u1 ( .clk(clk), .rst_n(rst_n), .k(k), .result(result[11:0]) ); phase_tiaozhi u2 ( .clk(clk), .rst_n(rst_n), .phase(phase[11:0]), .result(result[11:0]), .address(address[11:0]) ); wave_select u3 ( .wave(wave), .sin_data(sin_data[7:0]), .square_data(square_data[7:0]), .triangular_data(triangular_data[7:0]), .dds_data(dds_data[7:0]) ); loop_up_table_sin u4 ( .clk(clk), .address(address[11:0]), .sin_data(sin_data[7:0]) ); look_up_table_square u5 ( .clk(clk), .address(address[11:0]), .square_data(square_data[7:0]) ); look_up_table_triangular u6( .clk(clk), .address(address[11:0]), .triangular_data(triangular_data[7:0]) ); endmodule
文件结构如上所示:
dds_top为顶层文件。U4到u6为正弦波,方波,三角波的rom例化模块。
接下来就是测试了:
module dds_top_tb; // Inputs reg clk; reg rst_n; reg [1:0] wave; reg [11:0] k; reg [11:0] phase; // Outputs wire [7:0] dds_data; // Instantiate the Unit Under Test (UUT) dds_top uut ( .clk(clk), .rst_n(rst_n), .wave(wave), .k(k), .phase(phase), .dds_data(dds_data) ); always #1 clk = ~clk; integer i; initial begin // Initialize Inputs clk = 0; rst_n = 0; wave = 0; i = 0; k = {$random}%256; //随机产生频率控制字 phase = 0; // Wait 100 ns for global reset to finish #100 rst_n = 1; repeat(300) begin @(uut.address >3800) //。可以是引用模块内部信号 i = i+1; if(i==40) begin k = {$random}%256; end if(i==80) begin k = {$random}%256; end if(i==120) begin k = {$random}%256; wave = 1; end if(i==160) begin k = {$random}%256; end if(i==200) begin k = {$random}%256; wave = 2; end if(i==250) begin k = {$random}%256; end end end endmodule
用modelsim仿真。采用模拟显示波形。
波形选择正弦波情况下,可以看到不同的k的值,输出的正弦波的频率是不一样的。K越大,输出频率越大。
方波也是和正弦波一样的效果。
三角波也是一样的情况。
大家可以将k的值改大一些,比如超过2000,大家观察下波形,看看会发生什么奇怪的现象。
其实当k的值变得比较大的时候,会发现输出的波形已经失真了,所以k的值不能取很大。所以在程序中,k的位数其实是不需要12位的,同理相位phase也不需要12位的。
将输出接一个告高速的da,就可以得到真正的模拟波形了。