当前位置: 首页 > news >正文

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

1. 引言

在OFDM发射机中,IFFT是核心运算模块。通常,前级数据(如QPSK映射、导频插入)工作在一个较低频率的时钟域(例如25MHz),而IFFT运算为了满足实时性要求,往往工作在更高频率(例如200MHz)。直接跨时钟域传递数据会导致亚稳态,使数据错乱

本文设计了一个名为ifft_sysn的Verilog模块,它作为数据接口适配器,将来自i_clk域的调制符号(I/Q两路)安全地传递到i_clk_8dx域,并按照IFFT运算核要求的时序,精确产生beforeenlast等控制信号。该设计已在实际项目中验证,具有良好的可复用性。

代码架构

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_0r_ens_1检测ri_en的上升沿(w_flag),在数据块开始时复位计数器,确保每帧起始位置准确。

3. 模块接口详解

端口名方向位宽说明
i_clkinput1写时钟(前级数据时钟)
i_clk_8dxinput1读时钟(IFFT运算时钟,8倍频)
i_rstinput1异步复位,高有效
i_eninput1写使能,由前级有效数据指示
i_Ipinput2写入的I路数据(signed)
i_Qpinput2写入的Q路数据(signed)
o_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_eni_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_encnt=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; end
  • r_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 endmodule

6. 测试平台(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 endmodule

7. 仿真波形与验证要点

  • 数据完整性:观察o_Ip/o_Qp是否与输入i_Ip/i_Qp保持一致(注意延迟)。
  • 时序对齐o_en有效期间,数据持续输出;o_before在数据开始前5个周期拉高;o_last在最后一个数据点拉高。
  • 跨时钟域安全性:检查FIFO的wr_data_countrd_data_count是否正常,无溢出或读空。

8. 总结

本设计通过一个异步FIFO解决了不同时钟域的数据传递问题,并结合计数器精确生成了IFFT模块所需的控制时序。其核心创新在于:

  • 采用窗口驱动的确定性读取,而非依赖FIFO空标志,简化了状态机。
  • 两级读使能(提前和锁存)有效补偿了FIFO读延迟,保证数据与使能信号严格同步。
  • 所有参数(如512点、窗口位置)均以常量形式给出,便于移植到其他点数IFFT。

该模块已在实际项目中配合Xilinx FFT IP核使用,稳定可靠。读者可在此基础上根据自己FIFO的延迟微调窗口比较值,快速适配自己的系统。


希望这篇博客能帮助你理解跨时钟域IFFT接口的设计思路。如果有任何疑问,欢迎在评论区交流!

http://www.jsqmd.com/news/1077538/

相关文章:

  • STM32-S369-存取柜+光敏+灯光+消毒+取件码+二维码+语音播报+存件+手机号录入+后台数据+4舵机+OLED屏+按键+(无线方式选择)-2(设计源文件+万字报告+讲解)(支持资料、图片参考_
  • 循环学习率CLR实战指南:提升收敛速度与泛化能力
  • Adobe-GenP 3.0:开源逆向工程的艺术与实用指南
  • OpenCLIP与Diffusion Bee:AI模型工程化落地实战指南
  • 5大理由:为什么企业需要billd-desk私有化部署的远程控制解决方案
  • LoRA微调实战:在笔记本上高效微调大模型的完整指南
  • Bunny DNS 免费!多维度优化助力构建更快更安全应用
  • 2026年重庆山三云企售后跟进的技术解析与工作要点说明
  • 现代gpu编程系统教程(一) ------- 概述
  • NCMDump是什么?网易云NCM格式转换工具详解及使用教程(附替代方案)
  • 3.7V升压5V2A芯片哪个好?PW6276同步升压低功耗方案
  • 基于 OB2513x开关芯片的PSR DCM模式反激电源的FB波形
  • 欧拉-丸山法在McKean-Vlasov SDE不变测度收敛性中的分析与MATLAB实践
  • Django毕业设计-基于 Django + 协同过滤算法的电影推荐系统设计与实现 基于 Django + 协同过滤算法的个性化电影推荐平台(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • SAMTEC/申泰 asp系列 134488 01 中文资料 板对板连接器
  • 2019年全球10km分辨率人类发展指数栅格数据集
  • 星载深度学习实战:空间遥感与自主导航的轻量AI部署
  • LSTM时间序列实战:工业级预测的12个关键工程细节
  • 电影评分为什么是离散分布?认知、平台与技术的三重约束
  • 从 PHP 到 AI + Golang,程序员自救转型手记(六):泛型基服务、控制器、仓储实现,自动发现和注册业务路由
  • 游泳池表面涂装,从别墅泳池到大型主题乐园,选对材料是关键
  • 线性回归实战:从数据到利润的商业建模指南
  • 【数据库系统原理】第22篇:索引的神经:哈希索引、位图索引与全文索引的原理及应用场景
  • 星露谷物语模组创作革命:从游戏玩家到数字园艺师的蜕变之旅
  • AI安全实战手记:从启发式扫描到神经符号防火墙
  • EXE软件加密实战:从原理到应用,保护你的代码与授权
  • 硬件安全引擎描述符机制:嵌入式网络加密加速的核心原理与实践
  • 一个项目对接N个团队,沟通到崩溃?公墓设计急需一站式的“省心方案”
  • 机器学习效率指标实战:Latency、内存与功耗三维评估
  • Okbiye AI PPT 深度实测:毕业论文答辩演示文稿,告别通宵返工