草庐IT

FPGA实现数字QAM调制系统

拿铁男孩-713 2023-08-26 原文

目录

前言

一、项目设计要求

二、各模块及仿真

1.m序列发生器

2.串并转换电路

3.电平映射电路

4.载波发生器

5.乘法器

6.加法器

三、例化仿真验证功能

总结


前言

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信号高有效时电路异步清零。

6ASK幅度调制电路

分别对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与通信领域的联系比较紧密。需要工程文件的小伙伴评论区留言~

有关FPGA实现数字QAM调制系统的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  3. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  4. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  5. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  6. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  7. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  8. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  9. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  10. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

随机推荐