weiqi7777

FPGA实现dds(ISE实现)

0
阅读(7668)

      上次说了dds的原理,这次我们用FPGA来实现dds。

      因为dds在da之前都是数字器件,所以我们可以用FPGA来实现dds的前两个部分。

      首先要先规定一下:rom的地址输入是12位,输出时8位。

      对于第一部分:

                                           clip_image002

      这里看出,功能是将寄存器输出的值与频率控制字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

        第二部分:

                                     clip_image004

           可以看出,就是将第一部分的输出值加上一个相位控制字。所以代码也很简单:

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

              第三部分:

                                 clip_image006

           这里就只实现查找表,因为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

clip_image008

        文件结构如上所示:

       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仿真。采用模拟显示波形。

clip_image010

波形选择正弦波情况下,可以看到不同的k的值,输出的正弦波的频率是不一样的。K越大,输出频率越大。

clip_image012

方波也是和正弦波一样的效果。

clip_image014

      三角波也是一样的情况。

      大家可以将k的值改大一些,比如超过2000,大家观察下波形,看看会发生什么奇怪的现象。

      其实当k的值变得比较大的时候,会发现输出的波形已经失真了,所以k的值不能取很大。所以在程序中,k的位数其实是不需要12位的,同理相位phase也不需要12位的。

      将输出接一个告高速的da,就可以得到真正的模拟波形了。