目录
1. 实现功能
2. matlab 代码
3. quartus 代码
1)用NCO IP核生成本振信号
2)混频后的信号处理
3)代码分析
4. 激励信号仿真
1) 从.txt文件中打开din输入信号:
2) 将nco产生的本振信号及混频后的信号输出到.txt文件中:
3) 文件读取代码分析
5. 有NCO IP核的modelsim仿真操作及结果
1)仿真出现错误现象
2)正确操作
3)仿真结果
1)生成sin输入信号,并生成.coe/.txt文件供FPGA读取;
2)进行上图算法的仿真,生成sin信号,混频、去除直流分量,进行FFT变换,并输出波形;
使用过程会发现两个问题,一是会卡在generation环节,进度不动,二是报error。这个IP核并不是免费的IP核,需要破解。以上两个问题可以参考下面的博客解决:
(5条消息) Quartus II 13.1 调用NCO IP核无法生成终于搞定了_Mbluer的博客-CSDN博客
参考上面链接重新安装两个java包后,无法启动java程序,并且nco IP核没办法创建。显示如下错误: 查百度,应该是java版本更新后的问题. 重复上面的操作,在安装第二个java64时有提醒检测到旧的java版本,忽略掉。再重新尝试,可以成功打开nco IP核设置。
IP核破解可以参考这篇博文:(7条消息) QUARTUS II中IP核的调用方法之ip核破解_freedomff的博客-CSDN博客
NCO的输出波形公式如下:
PIN定义如下:
求均值,滤除直流分量。通过对一个周期波形的8个数据进行求和然后右移3位得到均值,然后用混频后的数据减去均值(直流分量),得到输出结果。
module Mixer(
input clk,rst_n,
input [9:0]din, //输入单频信号
output wire [9:0] s_oc, // 本振信号
output wire [19:0] dout //混频信号
//output ldoc, //本振信号分频后的显示信号
//output ldmix //混频信号分频后的显示信号
);
wire [15:0] phi_inc_i;
wire clken;
wire [9:0]oc_sin;
assign clken=1'b1;
assign phi_inc_i=16'd8192; //设置频率为625khz
oc oc(
.phi_inc_i(phi_inc_i),
.clk(clk), //高电平有效的时钟使能信号
.reset_n(rst_n), //异步复位,低电平有效
.clken(clken), //时钟使能信号,高电平有效
.fsin_o(oc_sin), //输出正弦信号
.out_valid(out_valid));
reg signed [19:0] mult; // 转换成有符号数,因为有正负
wire signed[9:0] s_din;
wire signed[9:0]s_oc_sin;
assign s_oc = oc_sin;
assign s_din=din;
assign s_oc_sin=oc_sin;
always @(posedge clk or negedge rst_n)
if (~rst_n) mult<=20'd0;
else mult<=s_din*s_oc_sin;
//求均值 取8个值求平均是因为一个周期的波形是8个数值
reg signed [19:0]m1,m2,m3,m4,m5,m6,m7;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) 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
end
wire signed [22:0] madd;
wire signed [19:0] mean,mt;
assign madd=mult+m1+m2+m3+m4+m5+m6+m7;
assign mean=madd[22:3];
//滤除直流分量
assign mt=mult-madd;
assign dout=mt;
endmodule
//从外部TX文件(SinIn.txt)读入数据作为激励
integer pattern,file;
reg [9:0]stimulus[1:data_num];
initial begin
//文件仿真在“工程目录\simulation\modelsim”路径下
file=$fopen("SinIn.txt","r");
//$fscanf(file,"%d",stimulus); //不能这么写
pattern=0;
repeat(data_num)
begin
$fscanf(file,"%d",stimulus[pattern]); //需要一个个读,如下方的写一样,不能直接将所有的数据一次性读到stimulus中。
din=stimulus[pattern];
pattern=pattern+1;
#(`clk_period);
end
$fclose(file);
end
//将仿真数据dout写入到外部txt文件中(out.txt)
integer file_out;
initial begin
file_out=$fopen("out.txt","w");
if(!file_out)begin
$display("could not open file!");
$finish;
end
end
wire rst_write;
wire signed[19:0] dout_s;
assign dout_s=dout;
assign rst_write=clk&(rst_n); //产生写入时钟信号,复位状态时不写入数据
always@(posedge rst_write) $fdisplay(file_out,"%d",dout_s);
//将仿真数据s_oc写入到外部txt文件中(oc.txt)
integer file_oc;
initial begin
file_oc=$fopen("oc.txt");
if(!file_oc)begin
$display("could not open file!");
$finish;
end
end
wire signed[9:0] oc_s;
assign oc_s=s_oc;
always@(posedge rst_write) $fdisplay(file_oc,"%d",oc_s);
本次代码中,联调matlab和quartus的txt文件中用的都是有符号的十进制数,因此在matlab中生成的sin和处理的数据均是十进制的,因此直接用十进制输出,而在quartus中,用$readmem函数只能直接读取二进制和十六进制的数,因此用了$fscanf函数读取十进制,同时在实践中尝试发现只能按照指针一个个数据读取保存到数组中,不能像$readmemb一次性读取。
$readmemb("文件名",存储单元,文件中要存入存储单元的起始地址,文件中要存入存储单元的终了地址):对于$readmemb系统任务,每个数字必须使二进制数字,对于$readmemh系统任务,每个数字必须是十六进制数。$readmem只能读取二进制和十六进制。
$fscanf(文件指针,读取格式,数组);返回值为1表示成功,读取格式可以为%b,%d,%h(注意,如果读取格式为二进制,则文件中只能识别0 1.)
同时,$fscanf(file,"%d",stimulus);必须一次写一个数,不能这样直接一次性将文件的数据全部写入stimulus中。
verilog中$readmemb和$readmemh的使用_m0_38037810的博客-CSDN博客
verilog读入.txt的有符号十进制数,把有符号十进制数写入到.txt文件中_芒果爱火锅的博客-CSDN博客
仿真时出现找不大IP核调用的模块文件,显示如下错误:
# ** Error: E:/yanghaizhu/FPGA_PRO/11_mixer/quartus/ip/oc_st.v(135): Module 'asj_dxx' is not defined.
# ** Error: (vopt-7) Failed to open info file "work/_info" in read mode.
# No such file or directory. (errno = ENOENT)
# ** Error: E:/yanghaizhu/FPGA_PRO/11_mixer/quartus/ip/oc_st.v(142): Module 'asj_nco_aprid_dxx' is not defined.
# ** Error: (vopt-7) Failed to open info file "work/_info" in read mode.
主要原因时缺少库,解决方法参考下面博客:
MODELSIM仿真 QUARTUS生成的NCO IP核出现找不到asj等库文件问题的解决方法_little_pants的博客-CSDN博客
首先,quartus下 tool-run trl simulation,打开modelsim-SE;
然后,library-->work--> 点击oc.v,再点击上面的菜单栏compile,选择oc.vo文件编译。然后接着点击simluate-->start simluate-->在design下,选择work里的tb文件,如下图所示,design unit会出现work.xx名称,
然后点击library-->add,将本项目modelsim--simulation下的verilog_libs下的6个库依次添加进去,然后点击OK。
编译没有错误,如下图:
此时需要手动将想要观察的信号添加进波形,然后run-all,结果如下:
问题1:有符号数相乘的结果不对,即得到的dout输出不对。
分析波形如下:mult<=din*s_oc,但是乘积项不是同一时刻的值,有错位。但是乘法运算本身是正确的。 猜测原因是时钟比较大,在时钟锁存数据时就已经完成了assign赋值加非阻塞赋值。
问题2:往.txt中写入的dout数据不对,而写入的oc_s是对的。
这里需要注意,.txt每次运行后都会重新更新,而打开的.txt需要关闭再重新打开,不会再打开的状态下更新。
参考:Verilog学习笔记——有符号数的乘法和加法_DengFengLai123的博客-CSDN博客