尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

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

OFDM项目开发(05):FPGA跨时钟域IFFT数据接口设计——异步FIFO + 精准时序控制
📅 发布时间:2026/6/25 22:32:32

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_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_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; 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_count和rd_data_count是否正常,无溢出或读空。

8. 总结

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

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

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


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

相关新闻

  • STM32-S369-存取柜+光敏+灯光+消毒+取件码+二维码+语音播报+存件+手机号录入+后台数据+4舵机+OLED屏+按键+(无线方式选择)-2(设计源文件+万字报告+讲解)(支持资料、图片参考_
  • NCMDump是什么?网易云NCM格式转换工具详解及使用教程(附替代方案)
  • 2019年全球10km分辨率人类发展指数栅格数据集

最新新闻

  • Java工程师轻松转型AI大模型:收藏这份4个月实战路线图,高薪岗位等你来拿!
  • A2A协议:让AI代理像人类一样协作的通信契约
  • LCD12864 ,LCD屏显开发—幽冥大陆(一百46)-东方仙盟
  • 企业级TLS部署实战:PEM文件管理的7个关键安全细节
  • 用上这八款提效办公软件的同事,办公效率翻一番,都升职当上主管了
  • 深海水油气开采为何搭载ADCP?偶信科技设备如何抵御深海强内波?

日新闻

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号