网上关于QPSK调制原理的介绍比较多,这里我就不再赘述了,直接上内容吧。
本工程基于XC7Z010CLG400-1构建,使用相位调制法进行调制,基本框图如下:
由FPGA将输入信号两两一组转变为并行信号然后进行相位选择 ,然后根据相位对地址累加器中的值进行调节并输入存储正弦波形的ROM中获取波形值,并输出给DAC模块从而实现QPSK调制。
工程中实际框图如图所示
接下来上代码,ROM初始化数据由MATLAB脚本生成:
脚本:
clear,clc;
N = 256 ;%数据深度
y = zeros(N , 1) ;%生成100行*1列的矩阵
y_integer = zeros(N , 1) ;%生成100行*1列的矩阵
y_hex = zeros(N , 1) ;%生成100行*1列的矩阵,十六进制
for i = 1:1:N %循环1~N,累加1
x = i-1 ;
y(i,1) = ceil( 127*sin(x*2*pi/N) ) ;%ceil为四舍五入函数,输出范围为-127~127的正弦波数据
end
subplot(211);
plot(y);%画图预览
title("原始波形");
axis([-10,260,-130,130]);
fid = fopen('cos_256point_new.coe','wt'); %创建一个名为cos_100point.coe的文件
%- COE 文件前置格式
fprintf( fid, 'MEMORY_INITIALIZATION_RADIX = 16;\n'); %coe文件数据为16进制
fprintf( fid, 'MEMORY_INITIALIZATION_VECTOR =\n');
%- 写数据
y_integer=y+127;
y_hex= dec2hex(y_integer);
subplot(212);
plot(y_integer);
title("存入数据");
axis([-10,260,-10,260]);
for i = 1:1:N
if(i == N)
%最后一个点时,标点为分号,其余时候为逗号
fprintf(fid,'%c%c;',y_hex(i,1),y_hex(i,2)); %输出16进制数据
else
fprintf(fid,'%c%c,\n',y_hex(i,1),y_hex(i,2)); %输出16进制数据
end
end
fclose(fid);%关闭文件
该脚本会将将一个周期内的正弦波形采样256次,每次采样结果为8bit的16进制数,采样结果存储于文件cos_256point_new.coe中,VIVADO初始化ROM时选中此文件即可。
QPSK调制模块
module QPSK(
input sys_clk,
input sys_rst_n,
input data_clk,
input data_in,
output reg sw,
output [7:0]data_out,
output data_out_clk
);
reg [1:0]data_buffer=0;
//reg sw=1'd0;
reg [3:0]thera=0;
reg [3:0]now_thera=0;
wire carry_signal_clk;//载波频率
always @(negedge data_clk or negedge sys_rst_n)//串并转换
begin
if(sys_rst_n == 1'b0)
begin
data_buffer <= 2'd0;
sw<=1'd0;
end
else
begin
data_buffer[sw]=data_in;
sw<=~sw;
end
end
reg thera_cnt=0;
always @(posedge data_clk or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0)
thera<=4'd0;
else if(sw==1'd0)
begin
case (data_buffer)
2'b00:
thera<={thera[1:0],2'd0};
2'b01:
thera<={thera[1:0],2'd1};
2'b10:
thera<={thera[1:0],2'd2};
2'b11:
thera<={thera[1:0],2'd3};
endcase
end
end
//assign thera_edg=((thera[3:2]==thera[1:0]) ? 0:1)&(!sw);//在thera改变时产生上升沿信号
assign thera_edg=((thera[3:2]==thera[1:0]) ? 0:1)&sw;
reg [7:0]rd_addr=0;
reg [7:0]addr_pi_4=0;
reg [7:0]addr_3pi_4=0;
reg [7:0]addr_5pi_4=0;
reg [7:0]addr_7pi_4=0;
always @(posedge data_out_clk or negedge sys_rst_n)//地址选择
begin
if(sys_rst_n == 1'b0)
begin
rd_addr<=8'd32;
addr_pi_4 =8'd32;
addr_3pi_4=8'd160;
addr_5pi_4=8'd224;
addr_7pi_4=8'd96;
end
else
begin
//rd_addr<=rd_addr+1'b1;
addr_pi_4<=addr_pi_4+1'b1;
addr_3pi_4<=addr_3pi_4+1'b1;
addr_5pi_4<=addr_5pi_4+1'b1;
addr_7pi_4<=addr_7pi_4+1'b1;
case(thera[1:0])
2'b00://pi/4
rd_addr<=addr_pi_4;
2'b10://3pi/4
rd_addr<=addr_3pi_4;
2'b01://5pi/4
rd_addr<=addr_5pi_4;
2'b11://7pi/4
rd_addr<=addr_7pi_4;
endcase
end
end
data_clock u_data_clock
(
// Clock out ports
.clk_out1(data_out_clk),
// Status and control signals
.reset (~sys_rst_n),
.locked (),
// Clock in ports
.clk_in1 (sys_clk)
);
//ROM存储波形
blk_mem_gen_0 u_rom_256x8b(
.addra (rd_addr),
.clka (data_out_clk),
.douta (data_out)
);
endmodule
代码中根据寄存器thera的值来切换地址,由于相邻两相位(上图两红线)之间地址差值固定为64,因此实际上此处可省略四个并行的寄存器
reg [7:0]addr_pi_4=0;
reg [7:0]addr_3pi_4=0;
reg [7:0]addr_5pi_4=0;
reg [7:0]addr_7pi_4=0;
改为根据当前相位和目标相位作差,差值乘上64再与rd_addr进行相加减以节省资源。
顶层模块
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/07/20 21:23:57
// Design Name:
// Module Name: TOP
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module TOP(
input sys_clk,
input sys_rst_n,
input data_ena_key,
output ena_led,
output [7:0] data_out,
output data_out_clk
);
wire data_in;
wire data_clock;
wire sys_clk1,sys_clk2;
//assign ena_led=~data_ena_key;
QPSK QPSK_modulation(
.sys_clk (sys_clk1),
.sys_rst_n (sys_rst_n),
.data_clk (data_clock),
.data_in (data_in),
.data_out (data_out),
.data_out_clk(data_out_clk)
);
BUFG BUFG_inst(
.O ( sys_clk1 ),
.I ( sys_clk )
);
BUFG BUFG_inst2(
.O ( sys_clk2 ),
.I ( sys_clk )
);
data_generate baseband_signal(
.sys_clk (sys_clk2),
.sys_rst_n (sys_rst_n),
.data_ena (~data_ena_key),
.data_out (data_in),
.data_clk (data_clock)
);
endmodule
信源码发生模块
module data_generate(
input sys_clk,
input sys_rst_n,
input data_ena,
output reg data_out,
output data_clk
);
reg data_clk_buf=1'b0,data_clk_cnt=1'b0;
reg [0:15]data={
1'b1,
1'b0,
1'b1,
1'b1,
1'b0,
1'b1,
1'b1,
1'b1,
1'b0,
1'b1,
1'b1,
1'b1,
1'b1,
1'b0,
1'b0,
1'b0
};
reg [3:0]counter;
always@(posedge data_clk_buf or negedge sys_rst_n)
begin
if(sys_rst_n == 1'b0|data_ena==1'b0)
begin
counter<=4'd0;
data_out<=1'b1;
data_clk_cnt<=1'b0;
end
else
begin
counter<=counter+1'b1;
data_out<=data[counter];
data_clk_cnt<=data_ena;//将数据使能同步至时钟上升沿
end
end
assign data_clk=data_clk_cnt & data_clk_buf;
reg [11:0]clk_counter=0;
always@(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n==1'b0)
begin
clk_counter<=12'd0;
data_clk_buf<=1'd0;
end
else
begin
clk_counter<=clk_counter+1'b1;
if(clk_counter==12'd2500)
begin
clk_counter<=12'd0;
data_clk_buf<=~data_clk_buf;
end
end
end
endmodule
整体工程简单易懂,直接赋值粘贴即可使用,想偷懒的也可以直接下载原始工程:
优化方向:1.省略并行寄存器addr_pi_4,addr_3pi_4,addr_5pi_4,addr_7pi_4改为对rd_addr的加减。
2.缩减ROM空间,只存储正弦波形的前1/4波形。然后将根据条件对rd_addr的值进行处理后输入ROM:<64:rd_addr输入ROM;64≤rd_addr<128:(128-rd_addr)输入ROM;128≤rd_addr<192:(rd_addr-128)输入ROM、ROM输出乘-1;192≤rd_addr<256:(256-rd_addr)输入ROM、ROM输出乘-1
优化后整体框图如下:
、
实测波形: