FPGA实现数字QAM调制系统
QAM是Quadrature Amplitude Modulation的缩写,中文译名为“正交振幅调制”,其幅度和相位同时变化,属于非恒包络二维调制。本次设计使用环境为Quartus II与Modelsim Altera,项目设计原理图如下:以上就是今天要讲的内容,本文仅仅简单介绍了如何使用FPGA实现数字QAM调制,可见FPGA与通信领域的联系比较紧密。需要工程文件的小伙伴评论区留言~x&plu
前言
QAM是Quadrature Amplitude Modulation的缩写,中文译名为“正交振幅调制”,其幅度和相位同时变化,属于非恒包络二维调制。本次设计使用环境为Quartus II与Modelsim Altera,项目设计原理图如下:
一、项目设计要求
设计任务各模块要求具体如下:
(1)模块时钟生成电路
设计必要的模块时钟生成电路,输出满足电路各模块工作需求的时钟信号。对生成的时钟信号预留仿真输出端口。
(2)m序列发生器
m序列的特征方程为 ,采用线性移位寄存器来产生,输出数字序列信号m的码速率为4kbps。电路在适当的时钟信号控制下工作,上升沿触发。reset信号高有效时电路异步复位,其复位状态为:全1信号。移位寄存器状态信号A_reg需预留仿真输出端口。
(3)串并转换电路
串并转换模块将串行输入的m序列,逐位依次交替送入I路和Q路,I、Q两路信号分别以2位为一组,生成输出信号I、Q,先输入的串行数据位于并行输出数据的高位。这样,每4位串行输入的二进制序列中,第1bit和第3bit组合成并行2位宽I信号输出;第2bit和第4bit组合成并行2位宽Q信号输出。电路在适当的时钟信号控制下工作,上升沿触发。reset信号高有效时电路异步清零。
(4)电平映射电路
分别将I、Q两信号进行电平映射,得到两路3位宽数据流a、b,映射规则如表1所示,其中a/b使用映射电平补码输出。电路在适当的时钟信号控制下工作,上升沿触发。reset信号高有效时电路异步清零。
表1 I/Q输入信号与映射电平a/b关系表
I/Q信号 | 映射电平 | a/b |
00 | +3 | 011 |
01 | +1 | 001 |
11 | -1 | 111 |
10 | -3 | 101 |
(5)载波信号发生电路
载波信号发生器输出同频正交载波信号c_cos和c_sin,分别表示为c_cos=cos2πf0t和c_sin=sin2πf0t,其中f0=10kHz。一个周期内采样200个样值。采样数据存储可以选择使用IP核实现。电路在适当的时钟信号控制下工作,上升沿触发。reset信号高有效时电路异步清零。
(6)ASK幅度调制电路
分别对a、b两路信号进行ASK幅度调制,得到互为正交的调幅信号I_mod和Q_mod,分别表示为:I_mod=acos2πf0t和Q_mod= bsin2πf0t。电路在适当的时钟信号控制下工作,上升沿触发。reset信号高有效时电路异步清零。
(7)加法电路
设计加法器电路实现输出调制信号qam=acos2πf0t-bsin2πf0t。电路在适当的时钟信号控制下工作,上升沿触发。reset信号高有效时电路异步清零。
二、各模块及仿真
1.m序列发生器
由于m序列的特征方程为可知,C3C2C1C0=1011,则使用3级移位寄存器实现,Q[0]与Q[2]异或作为反馈。
代码如下:
module mcode(clk, rst, out);
input clk, rst; //输入端口
output out; //输出端口
reg[2:0] Q; //中间节点
wire C0;
assign C0 = Q[2] ^ Q[0] ; //反馈
assign out = Q[2]; //输出信号
always@(posedge clk or posedge rst)
begin
if(rst )
Q[2:0] <= 3'b111; //异步清零,全1
else
Q[2:0] <= {Q[1:0],C0}; //移位
end
endmodule
仿真结果:
由要求可知,M序列的码元速率为4kbps,则所需时钟为4KHz,周期为25000ns,由图可知仿真结果正确,且M序列为1101001的7位码。
2.串并转换电路
串并转换电路原理比较简单,只需要在每个时钟脉冲到来的时候给I和Q的高低位进行赋值,采用计数的方式完成,此处需注意输出I和Q应当在分完一组后再进行输出,不然每个时钟脉冲到来的时候I和Q的数据会变。
代码如下:
module serial_2_parallel(clk, rst, data_in, I, Q);
input clk;
input rst;
input data_in; //序列输入
output reg[1:0] I;
output reg[1:0] Q;
reg [2:0]cnt;//计数
reg[1:0] data_I;
reg[1:0] data_Q;
always @(posedge clk or posedge rst) //时序问题,第一次计数不需要进行分配。
begin
if(rst)
begin
I <= 2'b00; Q <= 2'b00;
cnt <= 3'b000;
end
else if(cnt==3'b100) //4次才可以分完一组I和Q,因此分完才刷新I和Q的数据。
begin
I <= data_I;
Q <= data_Q;
cnt <= 3'b001;
end
else
cnt <= cnt + 1'b1;
end
always @(*) //串并转换
begin
case(cnt)
3'b001: data_I [1]<=data_in;
3'b010: data_Q [1]<=data_in;
3'b011: data_I [0]<=data_in;
3'b100: data_Q [0]<=data_in;
default: begin
data_I=2'b00;
data_Q=2'b00;
end
endcase
end
endmodule
此处不单独仿真验证,后面将前三个模块联合仿真。
3.电平映射电路
代码如下:
module mapping (clk ,rst ,data_I ,a);
input clk ,rst;
input [1:0] data_I;
output reg [2:0] a;
always@(*)
begin
case(data_I)
2'b00: a<=3'b011;
2'b01: a<=3'b001;
2'b11: a<=3'b111;
2'b10: a<=3'b101;
default: a<=3'b000;
endcase
end
endmodule
仿真结果:
选择前4组M序列作为参考,即
1101001 1101001 1101001 1101001
I:10 01 11 01 00 11 10
Q:10 01 00 11 10 10 01
由上图可知,10为-3、11为-1、01为1、00为3,且串并转换结果正确。
4.载波发生器
载波发生器采用IP核实现,利用matlab对正弦信号进行采样,并生成后缀为mif的文件,将采样数据存入IP中,再利用查表法对IP核中数据进行周期性遍历即可得到正弦载波,余弦波只需改变初始相位即可。后续会出一篇关于DDS信号发生器的文章。
matlab代码:
clc; %清除命令行命令
clear all; %清除工作区变量,释放内存空间
F1=1; %信号频率
Fs=199; %采样频率
P1=0; %信号初始相位
N=200; %采样点数
t=[0:1/Fs:(N-1)/Fs]; %采样时刻
ADC=0; %直流分量
A=2^7; %信号幅度
%生成正弦信号
s=A*sin(2*pi*F1*t + pi*P1/180) + ADC;
plot(s); %绘制图形
%创建 mif 文件
fild = fopen('sin_wave_255x8.mif','wt');
%写入 mif 文件,格式不可更改%
fprintf(fild, '%s\n','WIDTH=8;'); %位宽
fprintf(fild, '%s\n\n','DEPTH=512;'); %深度
fprintf(fild, '%s\n','ADDRESS_RADIX=UNS;'); %地址格式
fprintf(fild, '%s\n\n','DATA_RADIX=UNS;'); %数据格式
fprintf(fild, '%s\t','CONTENT'); %地址
fprintf(fild, '%s\n','BEGIN'); %开始
for i = 1:N
s0(i) = fix(s(i)); %对小数四舍五入以取整
if s0(i) <0 %负数转化为补码
s0(i) = s0(i)+2^8;
end
fprintf(fild, '\t%g\t',i-1); %地址编码
fprintf(fild, '%s\t',':'); %冒号
fprintf(fild, '%d',s0(i)); %数据写入
fprintf(fild, '%s\n',';'); %分号,换行
end
fprintf(fild, '%s\n','END;'); %结束
fclose(fild);
Verilog代码如下:
module wave(
input wire clk,
input wire rst,
output wire [7:0] sin_wave,
output wire [7:0] cos_wave
);
wire [7:0] addr_1;
wire [7:0] addr_2;
reg [31:0] F_cnt1;
reg [31:0] F_cnt2;
parameter F_WODR=667733;//频率控制字=clk*10000/(2^24*200)
parameter P_WODR=50;//相位控制字
always@(posedge clk or posedge rst) //生成正弦波
begin
if(rst)
F_cnt1 <= 32'd0;
else if(F_cnt1[31:24]==8'd199)
F_cnt1 <= 32'd0;
else
F_cnt1 <= F_cnt1 + F_WODR; //频率控制字
end
always @(posedge clk or posedge rst) //生成余弦波
begin
if(rst)
F_cnt2 <= {P_WODR, 24'd0};
else if(F_cnt2[31:24]==8'd199)
F_cnt2 <= 32'd0;
else
F_cnt2 <= F_cnt2 + F_WODR; //频率控制
end
assign addr_1 = F_cnt1[31:24];
assign addr_2 = F_cnt2[31:24];
wave_rom wave_rom_inst( //调用IP核的数据
.address (addr_1),
.clock (clk),
.q (sin_wave)
);
wave_rom wave_rom_inst2(
.address (addr_2),
.clock (clk),
.q (cos_wave)
);
endmodule
仿真结果:
由图测得正余弦波的周期T=100127ns,则f=1/T≈9987Hz,有点误差,这是由于频率控制字和系统频率不是成整数倍的关系。
5.乘法器
乘法器没什么好说的,注意是带符号的两个数相乘。
代码如下:
module ask(
input [7:0] wave,
input [2:0] data_in,
output [10:0] data_out
);
wire sign,sign1;//两个操作数的符号位
wire [2:0] abs_a;
wire [7:0] abs_I;//a和I的绝对值
assign sign=data_in[2];
assign sign1=wave[7];
wire [10:0] out1;
assign abs_a=sign?(~data_in+1):data_in;
assign abs_I=sign1?(~wave+1):wave;
assign out1=abs_a*abs_I;
assign data_out=(sign+sign1)?(~out1+1):out1;
endmodule
6.加法器
带符号数的加法器
代码如下:
module add(
input [10:0] Im,
input [10:0] Qm,
output [12:0] Qam
);
wire [11:0] Im_e;//Im扩展后的补码
wire [11:0] Qm_e;//负Qm扩展后的补码
wire [10:0] Qm_p;//负Qm补码
assign Qm_p = ~(Qm - 1);
assign Qm_e = {Qm_p[10] , Qm_p};
assign Im_e = {Im[10] , Im};
assign Qam = Im_e + Qm_e;
endmodule
三、例化仿真验证功能
顶层代码:
module qam(
input clk,
input rst,
output m,
output [2:0] a,
output [2:0] b,
output [7:0] sin_wave,
output [7:0] cos_wave,
output [10:0] Im,
output [10:0] Qm,
output [11:0] Qam
);
wire div_clk;
divclk U0(clk,rst,div_clk);
wire out;
mcode U1(div_clk, rst, out);
assign m=out;
wire [1:0] I,Q;
serial_2_parallel U2(div_clk, rst, out, I, Q);
wire [2:0] A,B;
mapping U3(clk, rst, I, A);
mapping U4(clk, rst, Q, B);
assign a=A;
assign b=B;
wave U5(clk, rst, sin_wave, cos_wave);
wire [10:0]IM,QM;
ask U6(sin_wave, A, IM);
ask U7(cos_wave, B, QM);
assign Im=IM;
assign Qm=QM;
add U8(IM, QM, Qam);
endmodule
分频代码:
module divclk(clk,rst,div_clk);
input clk,rst;
output reg div_clk;
reg [31:0] counter;
always@(posedge rst or posedge clk)//计数时钟分频模块
begin
if(rst)
begin
counter<=32'h00000000;
div_clk<=0;
end
else
if(counter==32'h00001869)// 4KHz计数到6249翻转counter=(clk/div_clk)/2-1
begin
counter<=32'h00000000;
div_clk <= ~div_clk;
end
else
counter<=counter + 1;
end
endmodule
测试代码:
`timescale 1 ns/ 1 ns
module qam_vlg_tst();
reg clk, rst;
wire m;
wire [2:0] a;
wire [2:0] b;
wire [7:0] sin_wave;
wire [7:0] cos_wave;
wire [10:0] Im;
wire [10:0] Qm;
wire [11:0] Qam;
qam U(clk, rst, m, a, b, sin_wave, cos_wave, Im, Qm, Qam);
initial
begin
clk=0;
rst=1;
#100
rst=0;
end
always #10 clk=~clk;
endmodule
仿真结果:
由结果可知,乘法器与加法器也没有问题。
总结
以上就是今天要讲的内容,本文仅仅简单介绍了如何使用FPGA实现数字QAM调制,可见FPGA与通信领域的联系比较紧密。
工程文件在这里 链接:https://pan.baidu.com/s/1VmOgPDK2t-QB1T-5hFHcbw 提取码:em7y
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)