FM调制基带信号 Vivado实现

通信题的纯数字方案中FM调制的FPGA实现显得尤为重要,之前一段时间一直在跟着我查大哥整理收发机的一些原理,然后就是时不时问辉说怎么实现FM调制(初期嘛,一直搞不清楚AM、FM哈哈😏😏),现有终于对收发机的原理以及常见结构有点小悟,下面主要就是对FM调制的实现做一个工程开发记录,具体理论这边就不赘述

  • FM调制 - 幅度改变频率

生成 BaseBand signal - DDS ~ ROM

这边调用ROM IP核进行基带信号模拟生成,主要是起到一个存储、读取的介质功能,DDS的生成也是有用到ROM,关于ROM可以看看下面这个文章

https://blog.csdn.net/yifantan/article/details/126748312

相对于之前用的直接产生signal.txt,我觉得会使得生成的基带信号独立于FM调制模块之外,后期做整体发射机时比较容易整合过去

  • MATLAB coe文件生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
%ROM设定
width=12; %设置rom的位宽
depth=1024; %设置rom的深度

x=linspace(0,2*pi,depth); %在一个周期内产生depth个采样点
y=sin(x); %生成正弦函数
%y_cos=round(y_cos*(2^(width-1)-1))+2^(width-1)-1; %将数据转化成整数,生成无符号数
y=round(y*(2^(width-1)-1)); %将数据转化成整数,生成有符号数

fid = fopen('D:\ele_match\FM_modulation\cos.coe','wt');

fprintf(fid,'memory_initialization_radix = 10;\nmemory_initialization_vector = ');
for i = 1 : depth
if mod(i-1,8) == 0
fprintf(fid,'\n');
end
fprintf(fid,'%6d,',y(i));
end

fclose(fid); %关闭文件
  • vivado基带信号生成 - 例如基带信号为5kHz

  • 这边需要将基带信号的频率换算成频率控制字 - 假设系统时钟为100M - Freq = 5k*2^32/100M = 214748

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer: Kevin
//
// Create Date: 2023/03/18 20:23:48
// Design Name:
// Module Name: DDS_Mod
//////////////////////////////////////////////////////////////////////////////////
module DDS_Mod(
input clk,
input rst_n,
output signed [11:0] sin //调制信号
);
//--------------------------------------------------------//
parameter Freq = 32'd214748; //5kHz 注意调整频率时需修改
parameter cnt_width = 8'd32;
//--------------------------------------------------------//

//--------------------------------------------------------//
reg [cnt_width-1:0]cnt_I = 0;
wire [9:0] addr_I;

always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_I <= 0;
end
else begin
cnt_I <= cnt_I + Freq;
end
end

assign addr_I = cnt_I[cnt_width-1:cnt_width-10];
//--------------------------------------------------------//

//--------------------调用一个单口ROM核--------------------//
blk_mem_gen_0 Sin_inst(
.clka (clk),
.addra (addr_I),
.douta (sin)
);

endmodule
  • 如果你的IP核名称有修改,这边调用时记得修改成相应的名称

vivado ROM IP核配置

  • 调用单口ROM IP核
  • 设定相应的位宽和深度
  • 导入COE文件

FM调制 - 细节及开发配置

原理阐述

  • FM调制模块本质上也是一个DDS,不过区别就在于其输出频率是关于基带信号幅度的函数,为了让其输出频率不断变化。我们可以回到DDS的代码中的Freq,这个变量就决定了DDS的输出频率,所以实际上我们只需要在载波频率后面加上一个不断变化的值,也就实现了FM调制

目前假设载波频率为5M,频偏(可设置)为 [-75k 75k]

基带信号大小 对应频偏 对应频偏的频率控制字
0 0 0
2^11(max) 75k 2^32 * 75k / 100M = 3221225
a b c
  • 因为这时候假设ADC采进来的位宽是12位,所以基带信号幅度最大为2^11

$$
c=a*3221225/2^{11}
$$

总结

通过上面的叙述,实际上FM调制的数字方案 基本原理已经清楚,主要就是根据基带信号的幅度大小来控制相应的频偏,本质上就是输出频率可变的DDS

  • 不过这边计算这加上的频偏控制字c,就需要用到乘法操作,所以需要进行乘法器IP核的调用;除法操作可以调用除法器IP核来实现,也可以通过移位的方法,考虑到这边是2的整数次幂,因此直接将乘法器的输出结果右移11位

乘法器IP核配置

  • 最佳流水线阶数配置 - Pipeline Stages
  • 这部分主要是影响整体乘法器的运算速率,阶数越高运算速度越快,不过相应的资源占用也大 - 建议如果没有特殊运算需求,根据vivado自动判定的最佳流水线阶数(Optimum)进行设定


vivado 代码 FM调制模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer: Kevin
//
// Create Date: 2023/03/18 20:26:46
// Design Name:
// Module Name: FM_Mod
//////////////////////////////////////////////////////////////////////////////////
module FM_Mod(
input clk,
input rst_n,
input [11:0] adc_data,
output [11:0] FM_Mod
);

parameter Freq_I = 32'd214_748_365; //载波信号的频率5M,时钟100M
parameter Freq_Word = 32'd3_221_225; //频偏为75k
parameter cnt_width = 8'd32;

//-------------计算频偏控制字--------------//
wire signed [43:0] mult_data;
wire signed [31:0] Freq_Offset;

mult_gen_0 MULT_inst(
.CLK (clk),
.A (adc_data),
.B (Freq_Word),
.P (mult_data)
);

assign Freq_Offset = mult_data[43:12]; //移位 ~ 完成除法操作

//---------------------------------------//
reg [cnt_width-1:0]cnt_I;
wire [9:0] addr_I;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_I <= 0;
end
else begin
cnt_I <= cnt_I + Freq_I + Freq_Offset;//对比DDS,这边加上了频偏控制字
end
end

assign addr_I = cnt_I[cnt_width-1:cnt_width-10];

//----------------ROM核-----------------//
blk_mem_gen_0 Sin_inst(
.clka (clk),
.addra (addr_I),
.douta (FM_Mod)
);

endmodule

顶层文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer: Kevin
//////////////////////////////////////////////////////////////////////////////////
module TOP(
input clk,
input rst_n,

output [11:0] FM_Mod_data
);

//----------------ADC-----------------//
wire [11:0] adc_data;

DDS_Mod DDS_Mod_inst(
.clk (clk),
.rst_n (rst_n),
.sin (adc_data)
);
//------------------------------------//

//---------------FM调制----------------//
FM_Mod FM_Mod_inst(
.clk (clk),
.rst_n (rst_n),
.adc_data (adc_data),
.FM_Mod (FM_Mod_data)
);
endmodule

TestBench

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer: Kevin
//
// Create Date: 2023/03/18 20:31:06
// Design Name:
// Module Name: tb_TOP
//////////////////////////////////////////////////////////////////////////////////

module tb_TOP();

reg sclk;
reg rst_n;

wire [11:0] FM_Mod_data;

//---------系统时钟----------//
initial sclk = 1;
always #5 sclk = !sclk;//100M ~ 1/(2*5*10^-9)
//---------复位---------//
initial begin
rst_n = 0;
#100
rst_n = 1;
end

//-----------------------//
TOP TOP_inst(
.clk (sclk),
.rst_n (rst_n),
.FM_Mod_data (FM_Mod_data)
);
//-----------------------//
endmodule

Simulation

  • 由于我最后FM_Mod模块的输出只有引出FM_Mod,所以为了更好的看出FM调制效果(成功了没!?)需要自主添加下adc_data

  • 首先点击TOP_inst,将adc_data add - waveform

  • 然后你就会发现没有什么软用,所以此时我就开始探索,把所有跟信号显示有关的选项都开了,最后才发现原来只要重跑一遍仿真就行😓😓😓
  • 最后就是调整一下输出波形的Waveform Style Radix - Signed Decimal

  • 不过为了更好地展现一下FM调制的效果,这边把基带信号抬到500k、频偏提到2M

总结

在后续的项目联调时,可以直接将ADC采集的数据接入到这边的FM_Mod的adc_data中,同时需要根据相应的位宽等参数,对乘法器IP核以及ROM的参数进行修改,最后的实际效果还是需要将调制信号从DAC输出康康😁

希望后续联调顺利😏