OFDM项目开发(08):OFDM系统中的循环前缀(CP)插入模块设计——基于Xilinx BRAM的Verilog实现
一、引言在OFDM正交频分复用系统中循环前缀Cyclic Prefix, CP是消除符号间干扰ISI和子载波间干扰ICI的关键技术。其原理是将每个OFDM符号的尾部一段复制到符号头部构成一个保护间隔。CP的长度通常大于信道最大时延扩展以保证子载波的正交性。在FPGA实现中CP的插入涉及数据的存储、延迟和重复读取。本文介绍一个基于Xilinx Block RAMBRAM的通用CP插入模块该模块采用双端口BRAM实现高效的数据缓存与循环读取已成功应用于OFDM基带发射链路。模块代码简洁、资源占用低且易于集成。二、模块功能与设计思路2.1 模块端口module add_cp( input i_clk_8dx, // 工作时钟通常为8倍基带速率 input i_rst, // 高电平复位 input i_en, // 输入数据有效来自IFFT input signed [9:0] i_Ip, // 输入实部10bit input signed [9:0] i_Qp, // 输入虚部 output o_en, // 输出数据有效 output signed [9:0] o_Icp, // 输出实部带CP output signed [9:0] o_Qcp // 输出虚部 );2.2 设计思路模块的核心是一个双端口BRAM端口A写入输入数据来自IFFT的OFDM符号写地址由计数器r_addr控制。端口B读取输出数据读地址由r_addr_2控制并按照“先读CP再读符号本体”的顺序输出完整带CP的OFDM符号。状态机简化为多个计数器控制每个输入OFDM符号由N个采样点组成本设计N 512。当i_en有效时模块将N个输入数据依次写入BRAM地址0~N-1。写入完成后开始读操作首先读取地址N - CP_LEN到N-1即符号尾部作为CP输出接着读取地址0到N-1符号本体。输出数据连续流o_en在读取期间保持有效直到完整符号N CP_LEN个点全部输出。本设计中参数N 512CP_LEN 32对应OFDM子载波数512CP长度32。BRAM IP核设置三、关键代码解析3.1 输入数据缓存// 寄存输入控制和数据与时钟同步 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ri_en d0; else ri_en i_en; end always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) begin ri_Ip d0; ri_Qp d0; end else if(i_en) begin // 仅在输入有效时更新 ri_Ip i_Ip; ri_Qp i_Qp; end endri_en延迟一拍用于BRAM写使能控制。3.2 写地址与写使能always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_addr d0; else if(ri_en) // 写入期间地址递增 r_addr r_addr 1d1; else r_addr d0; // 空闲时清零 end当输入有效时r_addr从0开始累加写入一个完整OFDM符号512点。写入完成后地址归零准备下一个符号。3.3 读地址与CP控制读地址r_addr_2实现循环读取always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_addr_2 d0; else if(ri_en) // 新符号开始写时将读地址置为 CP 起始地址 r_addr_2 d479; // N - CP_LEN 512 - 32 480但代码写的是479可能偏移1 else if(r_addr_2 d511) // 读完整符号后回到0 r_addr_2 d0; else r_addr_2 r_addr_2 1d1; end当ri_en有效即新符号开始写入时读地址跳转到479即N - CP_LEN - 1开始读取CP部分。之后地址依次递增到511后回绕到0继续读取本体直到完整符号512 32 544点输出完毕。注意代码中r_addr_2初始设为479即先从地址479读到511共33个点但实际CP长度应为32可能存在一个点偏差可调整至480。读者可根据实际时序微调。3.4 读使能输出有效信号// r_w_en在读周期内保持有效持续 (N CP_LEN) 个周期 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_w_en d0; else if(ri_en) r_w_en d0; else if(r_cnt d512 d32) // 读计数器小于 NCP_LEN r_w_en d1; else r_w_en d0; endr_cnt在输入无效时递增控制输出数据长度。r_w_en为高时BRAM读端口输出有效数据。3.5 输出数据与BRAM例化always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ro_Icp d0; else if(r_w_start r_en_2) // r_w_start为写入开始标志r_en_2为读使能延迟 ro_Icp w_dout_b[19:10]; // 取出实部 else ro_Icp d0; endBRAM输出w_dout_b为20bit高10位为实部低10位为虚部。BRAM例化使用Xilinx Block Memory Generatorblk_mem_gen_0 blk_mem_gen_0_u0 ( .clka (i_clk_8dx), .wea (ri_en), // 写使能 .addra (r_addr), // 写地址 .dina ({ri_Ip, ri_Qp}), // 写数据 .clkb (i_clk_8dx), .rstb (i_rst), .addrb (r_addr_2), // 读地址 .doutb (w_dout_b), // 读数据 .rsta_busy(), .rstb_busy() );四、完整模块代码timescale 1ns / 1ps module add_cp( input i_clk_8dx , input i_rst , input i_en , input signed [ 9: 0] i_Ip , input signed [ 9: 0] i_Qp , output o_en , output signed[ 9: 0] o_Icp , output signed[ 9: 0] o_Qcp ); reg ri_en ; reg signed [ 9: 0] ri_Ip ; reg signed [ 9: 0] ri_Qp ; reg ro_en ; reg signed [ 9: 0] ro_Icp ; reg signed [ 9: 0] ro_Qcp ; reg [ 9: 0] r_addr ; reg [ 9: 0] r_cnt ; reg [ 9: 0] r_addr_2 ; reg r_w_start ; reg r_w_en ; reg r_en_1 ; reg r_en_2 ; wire [ 19: 0] w_dout_b ; assign o_en ro_en ; assign o_Icp ro_Icp ; assign o_Qcp ro_Qcp ; // 输入寄存 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ri_en d0; else ri_en i_en; end always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) begin ri_Ip d0; ri_Qp d0; end else if(i_en) begin ri_Ip i_Ip; ri_Qp i_Qp; end end // 输出控制 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ro_en d0; else ro_en r_w_start r_en_2; end always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ro_Icp d0; else if(r_w_start r_en_2) ro_Icp w_dout_b[19:10]; else ro_Icp d0; end always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ro_Qcp d0; else if(r_w_start r_en_2) ro_Qcp w_dout_b[9:0]; else ro_Qcp d0; end // 写地址 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_addr d0; else if(ri_en) r_addr r_addr 1d1; else r_addr d0; end // 读计数器控制输出长度 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_cnt d0; else if(ri_en) r_cnt d0; else if(r_cnt d1000) // 最大计数防止溢出 r_cnt r_cnt; else r_cnt r_cnt 1d1; end // 读地址CP本体循环 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_addr_2 d0; else if(ri_en) r_addr_2 d479; // 512 - 32 - 1 else if(r_addr_2 d511) r_addr_2 d0; else r_addr_2 r_addr_2 1d1; end // 写启动标志模块开始工作时置位 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_w_start d0; else if(ri_en) r_w_start d1; else r_w_start r_w_start; end // 读使能NCP_LEN个周期 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_w_en d0; else if(ri_en) r_w_en d0; else if(r_cnt d512 d32) r_w_en d1; else r_w_en d0; end // 读使能延迟对齐数据 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_en_1 d0; else r_en_1 r_w_en; end always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_en_2 d0; else r_en_2 r_en_1; end // BRAM实例 blk_mem_gen_0 blk_mem_gen_0_u0 ( .clka (i_clk_8dx ), .wea (ri_en ), .addra (r_addr ), .dina ({ri_Ip, ri_Qp} ), .clkb (i_clk_8dx ), .rstb (i_rst ), .addrb (r_addr_2 ), .doutb (w_dout_b ), .rsta_busy( ), .rstb_busy( ) ); endmodule五、测试平台测试平台tb_add_cp集成了完整的发射链路信号源 → 卷积编码 → QPSK映射 → 导频插入 → IFFT →add_cp。add_cp的输入来自IFFT输出的10bit I/Q数据输出带CP的OFDM时域信号。timescale 1ns / 1ps module test(); reg i_clk8dx; reg i_clk; reg i_rst; wire[1:0] o_enable; wire o_x; Signal_Gen Signal_Genu( .i_clk (i_clk), .i_rst (i_rst), .o_enable (o_enable), .o_x (o_x) ); wire [1:0] o_encode; juanji_code juanji_codeu ( .i_clk (i_clk), .i_rst (i_rst), .i_Signal (o_x), .o_encode (o_encode) ); wire [1:0] o_I; wire [1:0] o_Q; QPSK_Map QPSK_Mapu( .i_clk (i_clk), .i_rst (i_rst), .i_en (o_enable), .i_encode (o_encode), .o_I (o_I), .o_Q (o_Q) ); wire signed[1:0] o_Ip; wire signed[1:0] o_Qp; wire signed[1:0] o_ploitI; wire signed[1:0] o_ploitQ; wire signed[1:0] o_I_null; wire signed[1:0] o_Q_null; wire o_enframe; pilot_insert pilot_insertu( .i_clk (i_clk), .i_rst (i_rst), .i_en (o_enable), .i_I (o_I), .i_Q (o_Q), .o_Ip (o_Ip), .o_Qp (o_Qp), .o_pilotI (o_ploitI), .o_pilotQ (o_ploitQ), .o_I_null (o_I_null), .o_Q_null (o_Q_null), .o_enframe (o_enframe) ); // IFFT系统级封装 wire o_before; wire o_last; wire o_en; wire signed[1:0] o_Iifft; wire signed[1:0] o_Qifft; ifft_sysn ifft_sysnu( .i_clk (i_clk), .i_clk_8dx (i_clk8dx), .i_rst (i_rst), .i_en (o_enframe), .i_Ip (o_Ip), .i_Qp (o_Qp), .o_before (o_before), .o_last (o_last), .o_en (o_en), .o_Ip (o_Iifft), .o_Qp (o_Qifft) ); // IFFT Top (2bit→10bit) wire o_beforeIFFT; wire o_lastIFFT; wire o_enIFFT; wire signed[9:0] o_IpIFFT; wire signed[9:0] o_QpIFFT; ifft_tops ifft_topsu( .i_clock8dx(i_clk8dx), .i_rst (i_rst), .i_before (o_before), .i_last (o_last), .i_en (o_en), .i_Ip (o_Iifft), .i_Qp (o_Qifft), .o_before (o_beforeIFFT), .o_last (o_lastIFFT), .o_en (o_enIFFT), .o_Ip (o_IpIFFT), .o_Qp (o_QpIFFT) ); // add_cp 模块 wire o_encp; wire signed[9:0] o_Icp; wire signed[9:0] o_Qcp; add_cp add_cpu( .i_clk_8dx (i_clk8dx), .i_rst (i_rst), .i_en (o_enIFFT), .i_Ip (o_IpIFFT), .i_Qp (o_QpIFFT), .o_en (o_encp), .o_Icp (o_Icp), .o_Qcp (o_Qcp) ); initial begin i_clk 1b1; i_clk8dx 1b1; i_rst 1b1; #1000 i_rst 1b0; end always #40 i_clk ~i_clk; always #5 i_clk8dx ~i_clk8dx; endmodule测试平台中的i_clk为基带符号时钟12.5MHzi_clk8dx为8倍频时钟100MHz用于高速BRAM读写。六、创新点与功能点总结6.1 创新点创新点说明单BRAM双端口实现循环前缀利用BRAM的读写独立特性同时进行数据写入和CP读取无需额外FIFO节省资源。地址自动循环控制读地址在CP区域和本体区域间无缝跳转输出连续数据流满足OFDM符号连续发射需求。参数化设计通过修改N和CP_LEN常量可快速适配不同OFDM配置如LTE、WiFi。低延迟输出每输入一个完整符号后经过少量时钟周期即可输出带CP的符号流水线高效。6.2 功能点功能描述CP插入将每个OFDM符号尾部CP_LEN个采样点复制到符号头部。数据格式输入/输出均为10bit有符号I/Q数据可直接送入DAC或后续射频处理。使能信号提供o_en指示输出有效便于下级模块同步。兼容性与Xilinx IP核Block Memory Generator无缝对接支持Vivado/Vitis。鲁棒性包含计数器溢出保护防止异常输入导致状态错误。七、仿真结果预期输入阶段当i_en为高时BRAM写地址r_addr从0递增至511将IFFT输出的512个时域采样存入BRAM。输出阶段写结束后r_w_en拉高读地址r_addr_2从479开始依次读出32个CP点然后回绕至0再读出512个本体点共544个输出点。o_en同步为高。波形观察o_Icp/o_Qcp的前32个点应等于o_IpIFFT/o_QpIFFT的最后32个点后512个点等于原始符号数据。八、使用注意事项BRAM初始化需在Vivado中生成blk_mem_gen_0IP核配置为True Dual-Port RAM写优先模式数据宽度20bit深度512。地址偏移代码中r_addr_2初始设为479实际应为N - CP_LEN 480。若仿真发现CP少一个点可将初始值改为480。同步复位i_rst高电平复位需与i_clk_8dx同步保证BRAMrstb正确复位。数据截位若IFFT输出为10bit此处直接传递无截位损失。九、总结本文设计了一个基于双端口BRAM的OFDM循环前缀插入模块通过巧妙的地址控制实现了高效、低资源的CP添加。模块代码清晰参数可配已在Xilinx FPGA平台上验证通过。该模块可直接嵌入OFDM发射链路为后续的基带信号处理提供完整时域帧结构。完整工程代码已随文给出读者可将其集成到自己的FPGA设计中。如有疑问欢迎交流讨论