正弦信号的产生流程
正弦信号离散化
1.首先使用matlab产生一个正弦信号,并将连续的正弦信号离散化
2.将离散化的正弦波信号一个整周期存储到RAM中
2.1 将matlab中的正弦波数据导出进行定点化,RAM的规格设置为256*8.数据规格为1位符号位,7位小数位。
负数放入到RAM中都是以补码的形式存放的
负数的补码计算简便方法:对于1个八位数的数据,怎么求他负数的补码呢?
假设附属为-127;则它对应的十进制的补码形式位256-127=129;则129就是-127的补码形式。
2.2 创建一个RAM,存放离散化的数据
创建一个单口的就可以了,因为我们只需要输出数据。
我们需要先创建一个mif文件,对这个ram进行初始化,建立好RAM之后,先随便修改几个值,然后打开这个文件,看看这个文件里面的格式是什么样的,然后再利用matlab去写这样的一个文件。创建的时候要使用fope与fprintf函数,如果不知道这两个函数的使用方法,则在matlab的命令窗口输入 help fope与help fprintf就可以跳出这两个函数的帮助文档。
matlab的程序
clc;
clear;
N=2^8; %采样频率,1秒钟采多少个点
s_p = 0:255; %采样点数
t=s_p/N;
sin_data=sin(2*pi*t);
% plot(t,sin_data);
%定点化(这个定点化小数位数是7位,符号位是1位;当然这个具体的根据自己的需要去设置,和采样频率没啥关系)
fix_sin_data=fix(sin_data*127);%这个的出来的是带正负号的定点数还要想办法把负数的副号去掉
for i=1:N
if fix_sin_data(i)<0
fix_sin_data(i) = N+fix_sin_data(i);
else
fix_sin_data(i) = fix_sin_data(i);
end
end
fid=fopen('sp_ram_256_8.mif','w+');
%这些都是根据生成的初始化.mif的文件格式写出来的
fprintf(fid,'WIDTH=8;\n');
fprintf(fid,'DEPTH=256;\n');
fprintf(fid,'ADDRESS_RADIX=UNS;\n');
fprintf(fid,'DATA_RADIX=UNS;\n');
fprintf(fid,'CONTENT BEGIN\n');
for i=1:N
fprintf(fid,'%d: %d; \n',i-1,fix_sin_data(i));
end
fprintf(fid,'end; \n');
fclose(fid);
使用matlab生成的.mif文件来替换之前在quartus中生成的.mif文件,在使用这个文件对ram或rom的IP核进行初始化
然后建立顶层文件对IP核进行初始化。
Verilog程序
.v文件
module ex_dds(
input wire sclk,
input wire rst_n,
output wire [7:0] o_wave
);
reg [7:0] addr;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
addr <= 0;
else
addr <= addr+1'b1; //这个地址的地增量和我们的频率是成正比的,也叫做频率控制字
sp_ram sp_ram_inst (
.address ( addr ),
.clock ( sclk ),
.data ( 8'd0 ), //输入数据
.wren ( 1'b0 ), //写使能让他为0
.q ( o_wave )
);
endmodule
tb文件
`timescale 1ns/1ns
module tb_ex_dds;
reg sclk;
reg rst_n;
wire [7:0] o_wave;
initial begin
sclk=0;
rst_n = 0;
#100
rst_n = 1;
end
always #10 sclk=~sclk;
ex_dds ex_dds_inst(
.sclk(sclk),
.rst_n(rst_n),
.o_wave(o_wave)
);
endmodule
在进行仿真时要注意的问题是对生成IP核的.v文件中的初始化.mif文件的位置进行修改。
因为在进行仿真时会默认根目录为sim文件夹下,如果不进行从修改的话可能会造成找不到文件,还有一种方法就是将mif文件拿到他原来默认的文件夹下
上面的这个程序生成的正弦信号的频率就是50M/256,如果地址每次累加的数据改变了,则正弦信号的频率也就跟着改变了
DDS的原理介绍
DDS的基本结构
M就相当于来一个时钟信号我的地址加M个数字,所以M是控制目标频率的一个数据。当M=2的时候就相当于1个时钟沿加了2个地址进去,那么时间周期就缩短了原来的一半
这个 n 指的就是相位累加器的位宽,在这次的实验中是8,当然可以根据需要去进行设置
频率分辨率和什么有关? 和我们相位累加器的位宽有关系。比如在这个系统里面,频率分辨率为50M/256,可以看到整个系统的频率分辨率的数值很高,效果很差。所以在正常使用的时候通过提高相位累加器的位宽来提高频率分辨率。
整个RAM的深度为256;那么整个RAM的深度和什么有关系呢? 存储正弦波的RAM深度或者叫正弦波一个周期量化的点数,量化的点数越多生成的正弦波的相位噪声越低
下面就把频率控制字加进去
设这次相位累加器的位宽为32位
接下来输出一个信号频率为1MHz的正弦信号
信号的频率 f0=M * Fc /2^32
从而可以反向求得M=1M*2^32/50M ~= 85,899,346
相位的设置
可以通过设置初始相位来实现
这个是通过控制相位控制字来控制信号的振荡频率
未加相位初始值
.v文件
module ex_dds(
input wire sclk,
input wire rst_n,
output wire [7:0] o_wave
);
parameter FRQ_W = 32'd85899346;
parameter FRQ_ADD = 32'd85899346/2;
parameter INI_phase = 32'd2147483648;//设置这样一个初始相位,可以起到使相位发生移动的情况,这个是2^31,所以对应相位应该反转
reg [31:0] phase_sum;
wire [7:0] addr;
reg [31:0] frq_word;
reg [6:0] div_cnt; //一般情况下都不拿计数器的值去作为某一个组合逻辑的判断条件,这比较容易出现竞争和冒险
reg div_flag; //这个就相当于只用一根线来作为判断条件,比较稳定,不易出错
/* always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
addr <= 0;
else
addr <= addr+1'b1; */
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
div_cnt<='d0;
else if(div_cnt == 7'd99)
div_cnt <= 'd0;
else
div_cnt <= div_cnt + 1'b1;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
div_flag <= 1'b0;
else if(div_cnt == 7'd99)
div_flag <=1'b1;
else
div_flag<=1'b0;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
frq_word <= FRQ_W;
else if(div_flag == 1'b1)
frq_word <= frq_word + FRQ_ADD;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
// phase_sum <= 'd0;
phase_sum <= INI_phase; //通过改变初始相位来进行位移
else
phase_sum <= phase_sum + frq_word;
assign addr = phase_sum[31:24]; //成比例的将数据从RAM中拿出去
sp_ram sp_ram_inst (
.address ( addr ),
.clock ( sclk ),
.data ( 8'd0 ), //输入数据
.wren ( 1'b0 ), //写使能让他为0
.q ( o_wave )
);
endmodule