1. 引言
在OFDM发射机中,IFFT是核心运算模块。通常,前级数据(如QPSK映射、导频插入)工作在一个较低频率的时钟域(例如25MHz),而IFFT运算为了满足实时性要求,往往工作在更高频率(例如200MHz)。直接跨时钟域传递数据会导致亚稳态,使数据错乱。
本文设计了一个名为ifft_sysn的Verilog模块,它作为数据接口适配器,将来自i_clk域的调制符号(I/Q两路)安全地传递到i_clk_8dx域,并按照IFFT运算核要求的时序,精确产生before、en、last等控制信号。该设计已在实际项目中验证,具有良好的可复用性。
代码架构
2. 功能点与创新点
2.1 功能点
- 异步跨时钟域传输:使用双时钟FIFO(
fifo_1)将数据从写时钟i_clk安全转移到读时钟i_clk_8dx。 - 数据打包与解包:将输入的2bit I路和2bit Q路合并为4bit写入FIFO,读出后再拆分,节省存储资源。
- 时序控制信号生成:基于一个内部计数器
r_cnt,在正确的时间窗口输出:o_before:IFFT帧起始前标志(用于同步)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_clk | input | 1 | 写时钟(前级数据时钟) |
i_clk_8dx | input | 1 | 读时钟(IFFT运算时钟,8倍频) |
i_rst | input | 1 | 异步复位,高有效 |
i_en | input | 1 | 写使能,由前级有效数据指示 |
i_Ip | input | 2 | 写入的I路数据(signed) |
i_Qp | input | 2 | 写入的Q路数据(signed) |
o_before | output | 1 | IFFT帧前标志(提前几个周期) |
o_last | output | 1 | 帧最后一个数据标志 |
o_en | output | 1 | 数据有效使能,持续512周期 |
o_Ip | output | 2 | 输出的I路数据 |
o_Qp | output | 2 | 输出的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_cnt
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- 计数器以
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 <= 2000+512) 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 == 2000+512) r_last_1 <= 1; else r_last_1 <= 0; end- 关键时序:假设FIFO读延迟为2拍,数据从
r_write_1有效到w_dout稳定需要两个周期。因此设计:r_write_1提前2个周期使能(cnt >= 2000+125-2)r_write_2在数据稳定时锁存(cnt >= 2000+125)- 而
o_en从cnt=2001开始,正好与稳定数据对齐。
- 这些数值可以根据实际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 = 1'b1; i_clk8dx = 1'b1; i_rst = 1'b1; #1000 i_rst = 1'b0; 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接口的设计思路。如果有任何疑问,欢迎在评论区交流!