基于FPGA的AM调制与解调(Verilog语言)

一、概述
说是概述,但是你还是得必须容我先瞎扯一番的。又是课程的作业,要通过FPGA实现AM信号的产生与解调。我们最开始手上是有硬件的板卡的,型号是叫Nexys Video。(当然现在被老师收走了,所以下面的程序只能讲解到仿真的层次)要求是通过VIO控制载波频率、调制信号频率、调制深度可调,然后通过ILA观察AM信号和解调后的信号。我记得载波信号的频率要求是1M~10M,调制信号的频率要求是1K~10K,调制深度从0到1、步进0.1。当然他规定了一定的精度。(VIO与ILA只能通过硬件板卡实现,下面的讲解中演示不了)。这个程序虽然说不算难吧,但是我确实忙活了一个星期才搞明白,每天都得超过12点睡觉。最让我崩溃的是:考试验收的那一天,我竟然忘记把昨天晚上最后改好的程序考到U盘上来,又是各种原因不能回去拿电脑。我的那个心凉的。。。从头一点点开始写啊,然后还有各种各样的波折,害的我都以为这门课是要来年重修的节奏。还好,最后一刻顺利完成。好,瞎扯到此为止,下面进入正题。
二、平台
软件: Vivado 2016.4
硬件:Nexys Video(这个不重要)
三、要求
为了更好的说明下面一些参数设定的意义,把我们课程的部分要求贴上来

完成AM信号调制和解调功能,具体要求如下:
(1)载波信号频率范围:1M-10MHz,分辨率0.01MHz;
(2)调制信号为单频正弦波信号,频率范围:1kHz-10kHz,分辨率0.01kHz;
(3)调制深度0-1.0,步进0.1,精度优于5%;
(4)调制信号和解调信号位宽为8位,AM信号16位,其他信号位宽自定义。
四、原理
虽然这部分简单,但却是最最重要的,把这部分看懂,所有的程序也就明白了。
1.AM信号:(A+ma*cos(w0t))*cos(wct)
一步步来嘛,首先肯定要产生两个频率不同的余弦波cos(w0t),cos(wct)。立马想到调用系统自带的DDS IP核来实现嘛,这是最简单的方法。我在网上还看到一个自己通过导coe文件来模拟DDS然后来产生余弦波的,这里就不说了。
产生两个余弦波后,再来两个乘法器(现在没有彻底想明白“*”这个符号和乘法器的区别,这里就不说了,我还是乖乖的调系统的乘法器吧)、一个加法器(后面程序直接用的“+”号,没有用加法器ip 核),运算一把不就搞定了吗。
2.AM信号生成中的注意点(这个有点绕)
首先看一下调制深度的问题。调制深度通常为已调波的最大振幅与最小振幅之差对载波最大振幅与最小振幅之和的比。就是生成AM波包络的最大值与最小值之差除以最大值与最小值之和。包络其实就是(A+ma*cos(w0t))。它的最大值是A+ma,最小值是A-ma。最后可以算出调制深度就是ma/A。A为1时,调制深度就是ma,其实只要A的值和后面的余弦波的最大值是相同的,调制深度就会为ma,为0~1之间。
但是有一个重要的问题不要忘了,就是在硬件描述语言中表示小数并不像C语言那么简单直接(其实所谓的小数只是我们对每个字节中的0和1的解释方式不同而已,在硬件描述语言中,我们会很自然的会把0和1两种状态直接转换为十进制,比如8'b0000_0011,我们会很自然的把它看做3,那么这样的话,硬件描述语言中是没有小数的)。我们上面生成的余弦信号cos(w0t)并不是在0~1范围内。假如我们让DDS的输出位宽为8位,那么这个余弦信号的幅度大小-128~127。我们就当做是-127~127,那么这里先假设A为127。再来再算一下调制深度。这时包络最大值为127+ma*127,最小值为127-ma*127。最后调制深度还会是ma,这个ma的范围还是0~1。ma是从0~1之间变的话,还是有不能直接表示小数这个问题。有两种解决方法:
1.让A=1270,也就是包络最大值为1270+ma*127,最小值1270+ma*127。算出调制深度最后应该为ma/10的。这时便可以设定ma为1~10来改变调制深度了。
2,.下面的程序是用的是以下这种方法。比如说(127*256)>>8(要知道右移一位相等于除2,右移8位的话等于除以256),就相当于127*1。而(127*128)>>8就相当于127*0.5=63。也就是这样产生我们的小数ma的,通过设定一个8位的变量depth_con乘以127,然后将得到的结果右移8位,那么我们就可以通过depth_con来控制调制深度depth了。他们的关系也就是depth=depth_con/256。当然这些推理是在保持A=127的情况下进行的,也就是AM信号包络为127+(depth_con*COS)>>8。(COS是DDS产生的8位信号)
前面的127+(depth_con*COS)>>8得到后,再经过乘法器乘以8位的载波就行了,这个乘法器的输出的就是我们要的AM信号了。这里还有一点就是127+(depth_con*COS)>>8的结果的范围是0~256。这时可以将乘法器的一个输入改为8无符号数,正好可以满足0~256的范围。乘以8位的载波信号后,得到的AM波正好是我们要求的那样,输出16位的AM波。(主要就是因为这才选的这种方法)
3.AM信号的解调
首先来谈一下解调方法:
相干解调,就是在AM波的基础上再乘以载波一次,然后经过低通滤波,隔直便可以得到我们要的解调信号。在频域分析也就是乘以载波后有了会产生一个w0的频率分量和几个高频分量,将这几个高频分量滤除便可以得到原始的调制信号。这个原理简单。
然而我用的还是下面的这种方法不知道算不算包络解调(我觉得不算吧),就是将AM波进行全波整流(就是取绝对值)或者半波整流(就是把负半轴的信号不要),然后低通滤波便可以得到我们要的解调信号了。这个原理最开始我是百思不得其解,因为最开始不知听谁说这种方法算包络解调,所以我一直在想为什么AM信号直接低通滤波后提取不了包络,而整流后低通滤波就可以提取包络了呢?但是其实再回头看,原理也挺简单。跟本不算提取包络吧。下面是AM信号进行全波整流后的频谱图(MATLAB)。调制信号频率为1KHz,载波频率为100KHz。
放大前

基于FPGA的AM调制与解调(Verilog语言)
文章图片


放大后
基于FPGA的AM调制与解调(Verilog语言)
文章图片


可以看出全波整流后的AM波在调制信号频率处有了新的频率分量,所以再经过低通滤波就可以得到最开始的调制信号。
理论推导的话,这几天也大概搞明白了。首先(1+cosw0t)coswct全波整流后得到(1+cosw0t)|coswct|(肯定要保证(1+cosw0t)大于0的)
其实你只要得到|coswct|的傅里叶级数就可以明白了。|coswct|的傅里叶级数具体我记不清了,但是我记得展开后有一个常数项2/pi,然后加上后面的一大坨(上网上搜一下就知道了)。主要就是有了这个常数项2/pi,它和前面的(1+cosw0t)相乘后就会得到w0的频率分量,也就是我们调制信号的频率。然后滤波一下,你懂得。

至于半波整流一样分析吧。
再来说一下滤波器在vivado里的实现
直接调用系统自带的fir IP核,点开后第一个界面。选COE文件,然后导入。
基于FPGA的AM调制与解调(Verilog语言)
文章图片

这个COE文件可以通过MATLAB生成(我也只知道这一种方式)。在MATLAB窗口输入命令:》fdatool
基于FPGA的AM调制与解调(Verilog语言)
文章图片


有些东西并不一定按照我这么选,比如FIR滤波器的种类(Window,Hamming),选其余的应该没什么大问题。主要是要保证截止频率,我设的是20K,因为要求调制信号的频率是1~10K,我稍微设高了一点。采样频率的话最少比载波频率大2倍(采样定理)我设的100M
还要进行量化后才能导出COE文件。如下
基于FPGA的AM调制与解调(Verilog语言)
文章图片

选择Fixed-point后可能要等几秒钟。稍微等一会。量化完后就可以导出了,就是点Targets里的选项,就不给图了
fir IP核第二,第三个设置窗口基本上不用改什么。下面的Input Sampling Frequency最好不要太大。我设成100时,程序运行好久都运行不完,太慢。

基于FPGA的AM调制与解调(Verilog语言)
文章图片


fir IP核设置完后,基本就没什么说的了。直接给程序吧,对照着前面的原理看,看懂应该不是问题。

五、程序
代码的话,先直接全部贴上来吧,还是比较简单的,不像我最开始从网上看到的一个,各种各样的模块叠加在一起。一些注意点讲解在后面。

`timescale 1ns / 1ps // // Company: Haerbin Engineering University // Engineer: huangshang // // Create Date: 2018/05/16 14:02:46 // Design Name: // Module Name: AM_generate2 // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // //module AM_generate2( input sysclk ); wire [15:0] AM_out; wire [15:0] carrier_con=2620 ; wire [23:0] modulate_con=1679; //用vio输入时,将下面的vio代码激活,并且要这几个变量不能赋初值(carrier_con,modulate_con,depth) wire [3:0] depth= 9; //vio_0 vio_instance_name ( //.clk(sysclk),// input wire clk //.probe_in0(AM_out),// input wire [22 : 0] AM signal //.probe_out0(depth),// output wire [3 : 0] shen to control the depth of modulation //.probe_out1(carrier_con),// output wire [15 : 0] carrier_con to control the frequence of carrier signal //.probe_out2(modulate_con)// output wire [15 : 0] modulate_con to control the frequence of modulate signal //); wire [7:0] carrier_out; dds_compiler_0 carrier_instance_name (//DDS输出载波信号 .aclk(sysclk),// input wire aclk .s_axis_config_tvalid(1),// input wire s_axis_config_tvalid .s_axis_config_tdata(carrier_con),// input wire [15 : 0] s_axis_config_tdata .m_axis_data_tvalid(),// 这个括号里的变量只要不要有和其余的重名的就行,全删掉只留括号也行。 .m_axis_data_tdata(carrier_out)// output wire [7 : 0] m_axis_data_tdata ); wire [7:0] modulate_out; modulating_signalDDS modulate_instance_name (//dds输出调制信号 .aclk(sysclk),// input wire aclk .s_axis_config_tvalid(1),// input wire s_axis_config_tvalid .s_axis_config_tdata(modulate_con),// input wire [23 : 0] s_axis_config_tdata .m_axis_data_tvalid(),// output wire m_axis_data_tvalid .m_axis_data_tdata(modulate_out)// output wire [7 : 0] m_axis_data_tdata ); reg[7:0] A= 127; //AM波直流分量 reg signed[8:0] depth_con; always@(posedge sysclk)//设置调制深度 begin case (depth) 0:depth_con <= 0 ; //调制深度为0,直流分量对应值 1:depth_con <= 13; //调制深度为0.1,直8流分量对应值 2:depth_con <= 28; //调制深度为0.2,直流分量对应值 3:depth_con <= 45; //调制深度为0.3,直流分量对应值 4:depth_con <= 64; //调制深度为0.4,直流分量对应值 5:depth_con <= 85; //调制深度为0.5,直流分量对应值 6:depth_con <= 110; //调制深度为0.6,直流分量对应值 7:depth_con <= 138; //调制深度为0.7,直流分量对应值 8:depth_con <= 171; //调制深度为0.8,直流分量对应值 9:depth_con <= 209; //调制深度为0.9.,直流分量对应值 10:depth_con <= 255; //调制深度为1,直流分量对应值 endcase end (* use_dsp48 = "yes" *)//这个没太大关系wire signed[16:0] modulate_mul8ma; wire signed[8:0] modulate_mulma; mult_genx your_instance_name (//调用乘法IP核,就是进行前面说的得到小数的那一部分操作 .CLK(sysclk),// input wire CLK .A(modulate_out),// input wire [7 : 0] A .B(depth_con),// input wire [8 : 0] B .P(modulate_mul8ma)// output wire [16 : 0] P ); assign modulate_mulma = modulate_mul8ma>>8; reg [7:0] modulate_withdc; always@(posedge sysclk) begin modulate_withdc <=modulate_mulma + 127; endmult_gen_0 mult_instance_name (//与载波相乘 .CLK(sysclk),// input wire CLK .A(modulate_withdc),// input wire [7 : 0] A .B(carrier_out),// input wire [7 : 0] B .P(AM_out)// output wire [15 : 0] P ); reg [15:0] AM_abs; always @(posedge sysclk ) beginif(AM_out[15] == 1)begin//全波整流 AM_abs <= -{AM_out}; //如果符号位是1,对数据取反 end else if(AM_out[15] == 0)begin AM_abs <= AM_out; //如果符号位是0,数据不变 end elsebegin AM_abs <= AM_abs; end endwire [39:0] demolate_signal; fir_my yfir_instance_name (//低通滤波 .aclk(sysclk),// input wire aclk .s_axis_data_tvalid(1),// input wire s_axis_data_tvalid .s_axis_data_tready(s_axis_dalta_tready),// output wire s_axis_data_tready .s_axis_data_tdata(AM_abs),// input wire [15 : 0] s_axis_data_tdata .m_axis_data_tvalid(),// output wire m_axis_data_tvalid .m_axis_data_tdata(demolate_signal)// output wire [39 : 0] m_axis_data_tdata ); wire [7:0] demolate_final; assign demolate_final[7:0] = demolate_signal[34:27]; //截位 //ila_0 ila_instance_name ( //.clk(sysclk), // input wire clk//.probe0(AM_out), // input wire [15:0]probe0 //.probe1(demolate_final) // input wire [7:0]probe1 //); endmodule

还有仿真模块的顶层文件,就几行。
`timescale 1ns / 1ps // // Company: Haerbin Engineering University // Engineer: huangshang // // Create Date: 2018/05/16 15:41:00 // Design Name: // Module Name: textbench // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // //module textbench(); reg sysclk; AM_generate2 instance_AM( .sysclk(sysclk) ); initial begin sysclk <= 0; end always #5 sysclk = ~sysclk; endmodule

六、注意事项
从最容易出现的错误开始说吧
1、fir iP核

在从U盘里往电脑里拷程序时一定会出现错误的,首先要把fir IP核的COE文件再导一遍。如果还是报错的话要把以前的coe文件删掉。就是下面红色的文件

基于FPGA的AM调制与解调(Verilog语言)
文章图片

2.DDS的设置
前面没怎么说DDS的事,比较简单嘛。就是调系统的ip核,改一下频率控制字的位宽和输出位宽,其余的基本上不用改。计算输出信号的频率:100M/2^24是频率分辨率,再用它乘以频率控制字就是输出信号的频率了。程序前面的modulate_con,,carrier_con就是这样算的,分别是10K,1M。
需要注意的是,当DDS的频率控制字的位宽太小时,输出的AM信号可能会失真的,这时只要把位宽改大点就好。我最开始调制信号dds的位宽设定16位,出来的就是失真AM信号。改完dds位宽后,不要忘了改前面定义的wire modulate_con的位宽。他们是对应的。

3.截位

由于最后要求输出的是8位的解调信号,而通过fir IP 核滤波后的输出信号远远大于8位,所以这里就有一个截位的问题。截位其实是通过看仿真图来觉定截哪几位的。
基于FPGA的AM调制与解调(Verilog语言)
文章图片

可以看到39到35位都是0,所以就从第34位开始往下截取8位嘛。要注意的是,我们这样截的话,连符号位也截去了,但符号位一直是0。所以在观察最后解调出来的信号的波形时,要设为无符号数来观察。



最后放一张完整的图

基于FPGA的AM调制与解调(Verilog语言)
文章图片

最后,这个程序同学也在硬件板子上试过的没有问题的。把那个VIO,和前面的几个变量(carrier_con,modulate_con,depth)的初始化去掉。综合生成bit流,导入板子。完工~
又想了一下,在网上查了一下包络解调的定义:
包络解调又称包络检波,适用于普通调幅信号的解调,指产生的输出信号与已调信号包络线成正比的幅度解调。
那这样说的话,这种方法算是包络解调吧。(原谅我的知识有限)

另外,本人水平一般,如果各位发现错误的地方,希望能热心指正,不胜感激。




【基于FPGA的AM调制与解调(Verilog语言)】

    推荐阅读