基于fpga的qam调制解调器设计
最近在捣鼓软件无线电项目,偶然接触到QAM调制技术。这玩意儿在4G/5G里用得特别多,寻思着用FPGA搞个实时处理的系统应该挺酷。今天就跟大伙儿唠唠怎么在FPGA上实现16-QAM调制解调,重点聊聊核心代码的实现思路。
先说说调制部分的核心------星座图映射。这里我直接用了查表法,把4个比特打包成符号。Verilog里用ROM实现映射表贼方便:
verilog
module symbol_map(
input [3:0] bits,
output reg signed [15:0] I,
output reg signed [15:0] Q
);
always @(*) begin
case(bits)
4'b0000: begin I = 16'h3000; Q = 16'h3000; end // (3,3)
4'b0001: begin I = 16'h3000; Q = 16'h1000; end // (3,1)
//...其他14种组合
4'b1111: begin I = 16'hD000; Q = 16'hD000; end // (-3,-3)
endcase
end
endmodule
这个查表模块把4bit数据映射到I/Q两路的16位有符号数,星座点间距保持2个单位。注意负数是补码表示,这里用十六进制直接定义更方便。
调制信号生成得搞数字上变频,这里用CIC插值滤波器提升采样率。关键代码片段:
verilog
// CIC插值滤波器核心计算
always @(posedge clk) begin
if (en) begin
// 积分器级联
int1 <= int1 + in_data;
int2 <= int2 + int1;
// 梳状滤波器
diff1 <= int2 - prev_int2;
diff2 <= diff1 - prev_diff1;
prev_int2 <= int2;
prev_diff1 <= diff1;
end
end
CIC结构最大的好处是只用加减法,特别适合FPGA流水线实现。不过要注意增益补偿,我后面专门做了移位调整,防止数据溢出。
基于fpga的qam调制解调器设计
解调部分最头疼的是时钟同步,这里分享个简单有效的门限检测法:
verilog
// 符号定时误差检测
reg [15:0] peak_history [0:3];
always @(posedge adc_clk) begin
peak_history <= {peak_history[1:3], I_squared + Q_squared};
if ((peak_history[2] > peak_history[1]) &&
(peak_history[2] > peak_history[3])) begin
symbol_strobe <= 1'b1;
end else begin
symbol_strobe <= 1'b0;
end
end
通过检测信号能量峰值确定最佳采样时刻,实测在20dB信噪比下误码率能控制在1e-4以内。当然更高级的可以用Gardner算法,不过资源消耗会多两三倍。
载波同步用了个改进的Costas环,核心是相位误差检测:
verilog
// Costas环相位误差计算
wire signed [31:0] phase_error = (I_delay * Q_early) - (Q_delay * I_early);
always @(posedge clk) begin
if (symbol_strobe) begin
freq_adj <= freq_adj + (phase_error >>> 6); // 环路滤波器
end
end
这里用移位代替乘法做环路滤波参数调整,实测频偏补偿范围能达到±5%符号率。注意I/Q延迟线要严格对齐,否则会引起稳态误差。
整套系统在Xilinx Artix-7上跑150MHz主频,资源占用大概35%的LUT和20%的DSP。测试时用SignalTap抓的星座图特别干净,码流传输稳定。不过要说教训的话,定点数精度处理太关键了,最开始没做好溢出保护,星座点都成麻花了。
搞FPGA实现最大的乐趣就是能亲手调这些底层参数,看着误码率一点点降下去比打游戏通关还爽。下次打算试试256-QAM,不过估计得换UltraScale系列才扛得住计算量。完整工程已开源在GitHub,需要参考的朋友直接搜"FPGA-QAM"就能找到。
