文章目录
背景
由于modelsm只能观察时域波形,无法显示数据的频谱特性,并且对数据进行分析、处理不够方便,特别是在FPGA中设计数字滤波器时,无法直接观察滤波器的频域响应。另外书写激励文件的时候,很难产生用户所需要的具有任意信噪比的输入信号。特别声明,不要转载本博主的所有blog,侵权必究,否则博主将关闭自己的所有blog。
再次认识关于DDS的来源
实际案例
官方资料阅读(NCO IP core)
参数原理
通常你可以使用IP工具接口来实施NCO的架构,包括基于ROM,CORDIC算法、和乘法器。另外IP工具会在你设置参数的时候提供可视化的时域和频域图像。设计者通常可以用NCO在通信领域,产生正交的IQ生成器。
输出的两个信号,用补码的形式给出。
正如上图所示,上图是官方给出的NCO的架构,其实可以看到,是不用于我们所认知的DDS的,所以有很多人通常用DDS来认知NCO,这个是不对的,对于IP核,我们只有从官方手册中学习,才能正确认识到官方想表达的意思。
另外,我们需要注意的是实线部分是必须的,而虚线部分是可选的输入参数。NCO的IP核函数允许你生成一种NCO架构,你可以创建你自定义的NCO,实施的效果你可以在NCO的图形界面中看到。
正如上图公式所示,你可以看到这个波形生成是由上述公式生成的,其中我们要注意的正是这些参数。
从生成的黑盒子模块,我们可以看到,输入接口phi_inc_i正是公式中的fO,也就是NCO架构中左边的第一个必须输入的参数。
而例化中的 [15:0] freq_mod_i ,这个指的正是上图中的Frequency Modulation中,这个是个可选项。
所以输出波形的频率正由[15:0] phi_inc_i;和[15:0] freq_mod_i一起累加构成频率。
上图中的两个公式,虽然官方介绍的是f0(phi_inc)是相位增量,而fFM是频率,其实根据公式,我们可以看出,没啥区别,都是作为频率,既然官方这么叫,那也行吧。另外还有一个精度是角度精度,这个没啥用,和相位精度保持一致即可,这里官方解释的是:角度精度是将坐标转换为笛卡尔的极化坐标转换前的角度精度,那么我们就把它与相位精度保持一致即可。
正如上图所示,我们还可以看到相位调制器参数,这个也是个可选项,它影响我们的初始相位,所以在黑盒子,我们可以看到
input [15:0] phase_mod_i;
正是代表的这个参数。
此外还有一个参数就是抖动,啥叫相位抖动,也就是说会有某个相位这个参数的值是不确定的,会有左右漂移,从而导致幅度的不确定性。
在IP核的这里面可以设置,也就是说我们可以设置相位噪声。
从上面两张图中,我仅仅改变了抖动大小,可以看到抖动越大,信噪比越低,这一点特别是在输出频率比较高的情况下尤其明显。注意,参数只是样图,请不要按照上面的参数设置。
至此,我已经对主要的输入参数进行了彻底的讲解了。
通常的步骤
首先配置参数
然后建立仿真,勾选仿真模型,并设置仿真用的语言,verilog。注意,如果不勾选仿真,那么在后续的仿真中,仿真是无法运行的。
最后是生成IP
如上图,会生成如上图所示的这些文件。从上面,我们可以知道.v文件需要Quartus II综合,它会添加到你的Quartus II工程中。另外qiq文件,也是需要添加入工程中的。另外_bb.v文件,是IP的黑盒子,当使用第三方仿真工具的时候可以使用这个文件,来例化。cos_c.hex和cos_f.hex文件sin_c.hex和sin_f.hex文件,这四个文件是存储初始化数据文件,以十六进制。
- setting parameters
首先,对于算法的具体实现细节,我就不展开讲了,因为很多论文都有写过(抄过)。
好的,至此理论部分,我们已经讲解完毕,至于具体算法实现的细节,比如CORDIC算法的原理,这些我就不讲解了,很多论文已经反反复复的抄过。
工程实例
MATLAB生成波形txt文件
%采用matlab进行电路仿真,用于验证整个FPGA电路的工作过程及输出结果是否满足要求
%同时产生FPGA程序中需要使用到的正弦波采样数据,50M采样率产生625KHZ的随机相位的正弦信号。也就是说一个周期可以采样80个点,已经能够非常好的显示出正弦波形了。
%采用matlab进行电路仿真,用于验证整个FPGA电路的工作过程及输出结果是否满足要求
%同时产生FPGA程序中需要使用到的正弦波采样数据
clc;
clear;
fi=625000; %输入信号的频率
fc=625000; %本振信号的频率
Fs=50000000; %采样频率
L=1024; %数据长度
N=10; %量化位数
% 产生输入信号
t=0:1/Fs:(1/Fs)*(L-1); %产生采样频率的时间序列
theta=rand()*2*pi; %产生一个随机相位角度
si=sin(2*pi*fi*t+theta); %生成具随机起始相位的正弦波输入信号
si=round(si*(2^(N-1)-1)); %10bit量化
%产生本振信号
sc=sin(2*pi*fc*t); %生成本振信号
sc=round(sc*(2^(N-1)-1)); %10bit量化
%仿真混频输出并画图
so=si.*sc; %混频器输出
sof=so-mean(so); %混频器滤出直流分量后输出
fso=abs(fft(so,L)); %求FFT变换的幅度值
%归一化处理
sc=sc/max(abs(sc)); %本振信号的归一化处理
si=si/max(abs(si)); %输入信号的归一化处理
so=so/max(abs(so)); %输出信号的归一化处理
sof=sof/max(abs(sof)); %混频器输出信号滤出直流分量后归一化处理
fso=fso/max(fso); %FFT的幅度值归一化处理
%转换成相对于原点对称的信号
fso=[fso(L/2+1:L),fso(1:L/2)]; %画图
m=[-L/2:1:(L/2-1)]*Fs/L*(10^(-6)); %生成频率坐标轴,单位为MHz
t=t*(10^6) ; %生成时间坐标轴,单位为us
subplot(221);plot(t(1:L),si(1:L));
title('10bit量化后的输入信号si','fontsize',8);
subplot(222);plot(t(1:L),so(1:L));
title('20bit量化后的混频输出信号so','fontsize',8);
subplot(223);plot(t(1:L),sof(1:L));
title('20bit滤出直流分量后的混频输出sof','fontsize',8);
subplot(224);plot(m,fso);
title('混频输出信号的幅频响应','fontsize',8);
%将生成的输入正弦信号的数据,写入外部文本文件(sinin.txt)中
f_s=si/max(abs(si)); %归一化处理
Q_s=round(f_s*(2^(N-1)-1));
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%新建文本文件前,必须建好文件存放的目录文件夹,否则出现提示信息:
%??? Error using ==> fprintf
%Invalid file identifier
fid=fopen('D:\quartus_Project\Liruifeng_tem\DO_Pro\CP2\CP_2_4_matlab_alt_mixNCO\sin.txt','w');
for k=1:length(Q_s)
B_s=dec2bin(Q_s(k)+(Q_s(k)<0)*2^N,N); %Q_s小于0就为1,大于0就为0
%k;
for j=1:N
if B_s(j)=='1'
tb=1;
else
tb=0;
end
fprintf(fid,'%d',tb);
end
fprintf(fid,'\r\n');
end
fprintf(fid,';');
fclose(fid);
关于以上部分代码的解释:
B_s=dec2bin(Q_s(k)+(Q_s(k)<0)*2^N,N); %Q_s小于0就为1,大于0就为0
由于dec2bin只能够将整数转换成二进制,所以必须要判断一下是否有负数,如果有负数,(Q_s(k)<0)的返回值就是1,如果是正数,返回值就是0,也就是说,如果是负数,那么就将原来的值加2^N ,就是其负数的补码的二进制。这一点,我之前已经写过。https://blog.csdn.net/ciscomonkey/article/details/87104636
顶层文件设计
module mix_top (
rst,clk,din,
s_oc,dout) ;
input rst; //复位信号,高电平有效
input clk; //数据采样时钟/FPGA系统时钟,频率为5MHz
input [9:0] din; //输入的625KHz单频信号
output [9:0] s_oc; //本地OC输出的625KHz单频信号
output [19:0] dout; //输出混频滤波后的的1.25MHz单频信号
//实例化NCO IP核
wire reset_n,out_valid,clken;
wire [15:0] phi_inc_i; //本地信号的相位
wire [15:0] phase_mod_i;
wire [15:0] freq_mod_i;
wire [9:0] oc_sin; //本地信号的输出,默认无符号
assign reset_n = rst; //NCO的复位信号低电平有效
assign phi_inc_i = 16'd0; //设置相位为0
assign phase_mod_i=16'd0; //设置相位为0
assign freq_mod_i=16'd819; //大约为624KHZ,当然,有小数的话也只能舍弃。值本来应该是819.2,其实我们可以从IP核里面的提示看出,叫我们相位增量设置为819,这里,我把频率增量设置为819,相位增量设置为0,也一样。
assign clken = 1'b1; //设置时钟允许信号始终有效
assign s_oc = oc_sin; //将本振输出信号送至模块输出
nco nco_inst(
.phi_inc_i(phi_inc_i),
.freq_mod_i(freq_mod_i),
.phase_mod_i(phase_mod_i),
.clk(clk),
.reset_n(reset_n),
.clken(clken),
.fsin_o(oc_sin),
.out_valid(out_valid)
);
//乘法运算实现混频输出
reg signed [19:0] mult=0; //有符号的混频运算结果
wire signed [9:0] s_din; //有符号的输入数字信号
wire signed [9:0] s_oc_sin; //有符号的本地输出625KHz
assign s_din = din; //将乘数转换成有符号数运算
assign s_oc_sin = oc_sin; //将乘数转换成有符号数运算
always @(posedge clk or negedge rst)
if (!rst)
mult <= 20'd0;
else
mult <= s_din * s_oc_sin; //转换为有符号后,进行乘法运算
//求均值
reg signed [19:0] m1,m2,m3,m4,m5,m6,m7;
always @(posedge clk or negedge rst)
if (!rst)
begin
m1 <= 20'd0;
m2 <= 20'd0;
m3 <= 20'd0;
m4 <= 20'd0;
m5 <= 20'd0;
m6 <= 20'd0;
m7 <= 20'd0;
end
else
begin
m1 <= mult;
m2 <= m1;
m3 <= m2;
m4 <= m3;
m5 <= m4;
m6 <= m5; m7 <= m6;
end
wire signed [22:0] madd;
wire signed [19:0] mean,mt;
assign madd = mult+m1+m2+m3+m4+m5+m6+m7; //代表将8个有符号数据加起来,其实这里我要补充一点了,原书上写的是给输入时钟是5M,输出625KHZ,所以刚好是8倍关系,那么也就是一个周期8个点。但是,这里我们的时钟是50MHz,我们本来应该是去平均值80个点,寄存80个,才能求出平均值。这里我就不管那么多啦,大家明白即可。毕竟要写80个点平均值,有点懒得写。
assign mean = madd[22:3]; //代表将8加起来的数据左移3位,相当于除以8
//滤出直流分量(均值)
assign mt = mult -mean; //滤出去直流分量
assign dout = mt;
endmodule
这里为什么要减去直流分量呢,当然是因为混频后626KHZ-625KHZ=0KHZ,那么就会产生直流分量,所以必须要减去直流分量。此外,还要注意的是,你试图计算一下频率增量,如果要在16位,50M时钟的情况下产生NCO,那么根本没法算出一个625KHZ的正弦,算出来,频率增量,我们应该取819.4,这里我就取819吧,这样产生的就是一个624点几KHZ的正弦。也差不多吧,本来也有噪声的,也会导致难免差一点点。这些东西差一点点,也影响不大。
仿真文件:
// Copyright (C) 1991-2013 Altera Corporation
// Your use of Altera Corporation's design tools, logic functions
// and other software and tools, and its AMPP partner logic
// functions, and any output files from any of the foregoing
// (including device programming or simulation files), and any
// associated documentation or information are expressly subject
// to the terms and conditions of the Altera Program License
// Subscription Agreement, Altera MegaCore Function License
// Agreement, or other applicable license agreement, including,
// without limitation, that your use is for the sole purpose of
// programming logic devices manufactured by Altera and sold by
// Altera or its authorized distributors. Please refer to the
// applicable agreement for further details.
// *****************************************************************************
// This file contains a Verilog test bench template that is freely editable to
// suit user's needs .Comments are provided in each section to help the user
// fill out necessary details.
// *****************************************************************************
// Generated on "05/30/2019 16:24:44"
// Verilog Test Bench template for design : mix_top
//
// Simulation tool : ModelSim (Verilog)
//
`timescale 1 ns/ 1 ns
module mix_top_vlg_tst();
reg clk;
reg [9:0] din;
reg rst;
// wires
wire [19:0] dout;
wire [9:0] s_oc;
parameter clk_period=20; //20ns
parameter data_num=800; //仿真数据长度
parameter time_sim=data_num*clk_period; //仿真时间
// assign statements (if any)
mix_top i1 (
// port map - connection between master ports and signals/registers
.clk(clk), //时钟
.din(din), //从文件读取输入的625KHz单频信号
.dout(dout), // 输出混频滤波后的的1.25MHz单频信号
.rst(rst), //复位
.s_oc(s_oc) //本地NCO产生输出的625KHz单频信号
);
initial
begin
clk=0;
rst=0;
din=10'd10;//设置从文本中读取输入的625KHz单频信号的初值
#50 rst=1;
//设置仿真时间
#time_sim
$stop;
end
//产生时钟信号
always #(clk_period/2) clk=~clk;
//从外部TXT文件中读入数据作为测试激励
reg [9:0] stimulus[1:data_num]; //用于存储从文本中读取的数据,全部存放于数组stimulus中
integer address=0;
initial
begin
$readmemb("sin.txt",stimulus);//文件必须放到simulation\modelsim的文件夹中
repeat(data_num)
begin
address=address+1;
din=stimulus[address];
#clk_period;
end
end
//将混频滤波后的的1.25MHz单频信号dout写入外部TXT文件中(out.txt)
integer file_out;
initial
begin
file_out=$fopen("out.txt");
//文件必须放到simulation\modelsim的文件夹中
if(!file_out)
begin
$display("could not open file!");
$finish;
end
end
wire clk_write;
wire signed[19:0] dout_s; //将混频后的数据,转换为有符号数
assign dout_s=dout;
assign clk_write=clk&(rst); //产生写入的时钟信号,复位状态时候不写入数据
always @ (posedge clk_write)
$fdisplay(file_out,"%d",dout_s); //将混频后输出的有符号的数据,写入file_out代表的out.txt文件中
//将NCO产生的数据写入NCO.txt文件中
integer file_nco;
initial
begin
//文件必须放到simulation\modelsim的文件夹中
file_nco = $fopen("nco.txt");
if(!file_nco)
begin
$display("could not open file!");
$finish;
end
end
wire signed [9:0] nco_s;
assign nco_s = s_oc;//将NCO产生的数据转变为有符号数据
always @(posedge clk_write)
$fdisplay(file_nco,"%d",nco_s);
endmodule
IP配置
调用NCO的IP核
然后点击geinirate即可
最后生成成功。
最后,方可得出NCO成功生成并添加其IP。
仿真的过程主要包括了行为仿真和时序仿真,无论何种仿真均需要设计测试激励文件,采用HDL代码文件书写激励,且需要在激励文件中将部分仿真结果数据写入外部的文本中,以方便matlab读取数据做进一步的仿真。
testbench中导入文本数据文件
文件命令语法(官方提示)
modelsim仿真
如上图所示,可以看到混频后输出的波形差不多是1.24MHz的样子,大差不差,存在误差也正常,毕竟我们的NCO本来就是产生的近似625KHZ的信号。
下面,我们来仔细看看从modelsim中,我们还能获取哪些知识。
首先,我们可以看到din,导入的数据是与文件中的数据完全一致的,
此外,我们可以看到,NCO产生的数据,刚开始需要一定的时钟后,然后才能产生稳定的波形。
此外,我们可以看到,写入数据文本与波形中的数据完全一致
我们看到noc_s的波形数据与文本上面的一致。
mult <= s_din * s_oc_sin; //转换为有符号后,进行乘法运算
下面,我们再来看一下转换为有符号后,进行乘法运算,乘法运算所消耗的时钟。
可以看到一个时钟就可以完成乘法运算。
另外采用8级流水线,求平均值,然后减去平均值
可以看到移位操作在本时钟周期以内就可以完成,所以,这就是说移位操作符是非常的强大。
另外,我们再来看看减法操作,减法操作也是在本时钟周期以内就可以完成。
工程福利连接
联系QQ:1183699227 并附带加友目的。