OFDM项目开发(05):FPGA跨时钟域IFFT数据接口设计——异步FIFO + 精准时序控制

📅 2026/6/25 22:31:38
OFDM项目开发(05):FPGA跨时钟域IFFT数据接口设计——异步FIFO + 精准时序控制
1. 引言在OFDM发射机中IFFT是核心运算模块。通常前级数据如QPSK映射、导频插入工作在一个较低频率的时钟域例如25MHz而IFFT运算为了满足实时性要求往往工作在更高频率例如200MHz。直接跨时钟域传递数据会导致亚稳态使数据错乱。本文设计了一个名为ifft_sysn的Verilog模块它作为数据接口适配器将来自i_clk域的调制符号I/Q两路安全地传递到i_clk_8dx域并按照IFFT运算核要求的时序精确产生before、en、last等控制信号。该设计已在实际项目中验证具有良好的可复用性。代码架构2. 功能点与创新点2.1 功能点异步跨时钟域传输使用双时钟FIFOfifo_1将数据从写时钟i_clk安全转移到读时钟i_clk_8dx。数据打包与解包将输入的2bit I路和2bit Q路合并为4bit写入FIFO读出后再拆分节省存储资源。时序控制信号生成基于一个内部计数器r_cnt在正确的时间窗口输出o_beforeIFFT帧起始前标志用于同步o_en有效数据使能持续512个周期o_last最后一个数据标志FIFO读使能控制根据计数器窗口精确控制FIFO的读使能r_write_1并延迟一拍得到数据锁存信号r_write_2确保数据稳定输出。2.2 创新点窗口延迟补偿针对FIFO读延迟和后续处理延迟设计了r_write_1提前2拍和r_write_2数据有效两级使能保证数据与o_en严格对齐。自适应空满状态忽略未使用FIFO的empty信号来控制读使能而是完全依靠计数器窗口确定性读取避免了因空状态带来的不确定延迟简化了时序。参数化窗口可调虽然本例固定为512点IFFT但计数器比较值采用常量定义方便后续修改为其他点数如1024。边沿检测触发复位通过r_ens_0和r_ens_1检测ri_en的上升沿w_flag在数据块开始时复位计数器确保每帧起始位置准确。3. 模块接口详解端口名方向位宽说明i_clkinput1写时钟前级数据时钟i_clk_8dxinput1读时钟IFFT运算时钟8倍频i_rstinput1异步复位高有效i_eninput1写使能由前级有效数据指示i_Ipinput2写入的I路数据signedi_Qpinput2写入的Q路数据signedo_beforeoutput1IFFT帧前标志提前几个周期o_lastoutput1帧最后一个数据标志o_enoutput1数据有效使能持续512周期o_Ipoutput2输出的I路数据o_Qpoutput2输出的Q路数据4. 核心设计代码分析4.1 异步FIFO例化fifo_1 fifo_1_u0 ( .rst (i_rst ), .wr_clk (i_clk ), .rd_clk (i_clk_8dx ), .din ({i_Ip,i_Qp} ), // 打包为4bit .wr_en (i_en ), .rd_en (r_write_1 ), // 读使能来自计数器窗口 .dout (w_dout ), .full (w_full ), .empty (w_empty ), .rd_data_count(w_rd_data_count), .wr_data_count(w_wr_data_count) );将I/Q两路合并为4bit写入读出后拆分w_dout[3:2]为I[1:0]为Q。读写时钟独立FIFO深度为1024足以缓冲512个数据。4.2 输入寄存与边沿检测// 将输入打一拍消除组合逻辑直接跨域 always (posedge i_clk or posedge i_rst) begin if(i_rst) {ri_en, ri_Ip, ri_Qp} 0; else {ri_en, ri_Ip, ri_Qp} {i_en, i_Ip, i_Qp}; end // 在i_clk_8dx域检测ri_en的上升沿 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) {r_ens_0, r_ens_1} 0; else {r_ens_0, r_ens_1} {ri_en, r_ens_0}; end assign w_flag (r_ens_0 ^ r_ens_1) r_ens_0; // 上升沿检测ri_en是i_en在写时钟域寄存后的信号虽然跨域但只用于检测上升沿产生一个单周期脉冲w_flag。该脉冲用于复位计数器保证每帧数据从0开始计数。4.3 主计数器r_cntalways (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_cnt 0; else if(w_flag) r_cnt 0; // 新帧开始复位计数 else if(r_cnt 4096) r_cnt r_cnt; // 保持最大值 else r_cnt r_cnt 1; end计数器以i_clk_8dx为时钟从0累加到4096可根据需要调整。当检测到输入数据有效上升沿时计数器立即归零确保后续窗口与数据对齐。4.4 时序窗口生成// before信号提前几个周期拉高用于通知IFFT准备 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_before_1 0; else if(r_cnt 1994 r_cnt 1999) r_before_1 1; else r_before_1 0; end // en信号持续512个周期指示有效数据 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_en_1 0; else if(r_cnt 2001 r_cnt 2000512) r_en_1 1; else r_en_1 0; end // last信号最后一个数据点时刻 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_last_1 0; else if(r_cnt 2000512) r_last_1 1; else r_last_1 0; end关键时序假设FIFO读延迟为2拍数据从r_write_1有效到w_dout稳定需要两个周期。因此设计r_write_1提前2个周期使能cnt 2000125-2r_write_2在数据稳定时锁存cnt 2000125而o_en从cnt2001开始正好与稳定数据对齐。这些数值可以根据实际FIFO延迟调整本例已适配Xilinx FIFO Generator的典型延迟。4.5 输出数据锁存always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ro_Ip 0; else if(r_write_2) ro_Ip w_dout[3:2]; else ro_Ip 0; endr_write_2信号在数据稳定周期有效将FIFO输出存入输出寄存器。当r_write_2无效时输出强制为0避免无效数据干扰。5. 完整模块代码module ifft_sysn( input i_clk , input i_clk_8dx , input i_rst , input i_en , input signed [ 1: 0] i_Ip , input signed [ 1: 0] i_Qp , output o_before , output o_last , output o_en , output signed[ 1: 0] o_Ip , output signed[ 1: 0] o_Qp ); reg ri_en ; reg signed [ 1: 0] ri_Ip ; reg signed [ 1: 0] ri_Qp ; reg ro_before ; reg ro_last ; reg ro_en ; reg signed[ 1: 0] ro_Ip ; reg signed[ 1: 0] ro_Qp ; reg r_ens_0 ; reg r_ens_1 ; reg [ 15: 0] r_cnt ; reg r_before_1 ; reg r_last_1 ; reg r_en_1 ; reg r_write_1 ; reg r_write_2 ; wire w_flag ; wire w_full ; wire w_empty ; wire [ 9: 0] w_rd_data_cnt ; wire [ 9: 0] w_wr_data_cnt ; wire [ 3: 0] w_dout ; assign o_before ro_before ; assign o_last ro_last ; assign o_en ro_en ; assign o_Ip ro_Ip ; assign o_Qp ro_Qp ; assign w_flag (r_ens_0 ^ r_ens_1) r_ens_0 ; fifo_1 fifo_1_u0 ( .rst (i_rst ), .wr_clk (i_clk ), .rd_clk (i_clk_8dx ), .din ({i_Ip,i_Qp} ), .wr_en (i_en ), .rd_en (r_write_1 ), .dout (w_dout ), .full (w_full ), .empty (w_empty ), .rd_data_count(w_rd_data_count), .wr_data_count(w_wr_data_count), .wr_rst_busy ( ), .rd_rst_busy ( ) ); // 输入寄存 always (posedge i_clk or posedge i_rst) begin if(i_rst) {ri_en, ri_Ip, ri_Qp} 0; else {ri_en, ri_Ip, ri_Qp} {i_en, i_Ip, i_Qp}; end // 输出寄存打一拍到时钟域 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ro_before 0; else ro_before r_before_1; end always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ro_last 0; else ro_last r_last_1; end always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ro_en 0; else ro_en r_en_1; end always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ro_Ip 0; else if(r_write_2) ro_Ip w_dout[3:2]; else ro_Ip 0; end always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) ro_Qp 0; else if(r_write_2) ro_Qp w_dout[1:0]; else ro_Qp 0; end // 边沿检测 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) {r_ens_0, r_ens_1} 0; else {r_ens_0, r_ens_1} {ri_en, r_ens_0}; end // 主计数器 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_cnt 0; else if(w_flag) r_cnt 0; else if(r_cnt 4096) r_cnt r_cnt; else r_cnt r_cnt 1; end // 时序窗口 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_before_1 0; else if(r_cnt 1994 r_cnt 1999) r_before_1 1; else r_before_1 0; end always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_last_1 0; else if(r_cnt 2000 512) r_last_1 1; else r_last_1 0; end always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_en_1 0; else if(r_cnt 2001 r_cnt 2000 512) r_en_1 1; else r_en_1 0; end // FIFO读使能提前2拍 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_write_1 0; else if(r_cnt 2000 125 - 2 r_cnt 2000 125 262 - 2) r_write_1 1; else r_write_1 0; end // 数据锁存使能延迟2拍 always (posedge i_clk_8dx or posedge i_rst) begin if(i_rst) r_write_2 0; else if(r_cnt 2000 125 r_cnt 2000 125 262) r_write_2 1; else r_write_2 0; end endmodule6. 测试平台Testbench用户提供的tb_shixu.v已经包含了完整的激励生成和模块例化这里给出清晰注释版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) ); // QPSK映射 wire [1:0] o_I; wire [1:0] o_Q; QPSK_Map QPSK_Map_u0( .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_insert_u0( .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_sysn_u0( .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) ); // 时钟及复位 initial begin i_clk 1b1; i_clk8dx 1b1; i_rst 1b1; #1000 i_rst 1b0; end always #40 i_clk ~i_clk; // 25MHz always #5 i_clk8dx ~i_clk8dx; // 200MHz endmodule7. 仿真波形与验证要点数据完整性观察o_Ip/o_Qp是否与输入i_Ip/i_Qp保持一致注意延迟。时序对齐o_en有效期间数据持续输出o_before在数据开始前5个周期拉高o_last在最后一个数据点拉高。跨时钟域安全性检查FIFO的wr_data_count和rd_data_count是否正常无溢出或读空。8. 总结本设计通过一个异步FIFO解决了不同时钟域的数据传递问题并结合计数器精确生成了IFFT模块所需的控制时序。其核心创新在于采用窗口驱动的确定性读取而非依赖FIFO空标志简化了状态机。两级读使能提前和锁存有效补偿了FIFO读延迟保证数据与使能信号严格同步。所有参数如512点、窗口位置均以常量形式给出便于移植到其他点数IFFT。该模块已在实际项目中配合Xilinx FFT IP核使用稳定可靠。读者可在此基础上根据自己FIFO的延迟微调窗口比较值快速适配自己的系统。希望这篇博客能帮助你理解跨时钟域IFFT接口的设计思路。如果有任何疑问欢迎在评论区交流