FPGA 上的 OFDM 同步:从 S&C 到残差 CFO 的工程化实现
1. 背景与目标
在软件仿真(MATLAB)中,我们可以用"全数据可见"的方式做同步:
-
通过 S&C 训练块做粗定时 与粗 CFO;
-
以二维代价面 J(τ,ϵ)J(\tau,\epsilon)J(τ,ϵ) 做精同步;
-
用 NCO 校正 CFO,并在CP 边界附近做微调;
-
通过均衡 + 去数据相位 得到每符号 CPE ,拟合其斜率评估残差 CFO。
在 FPGA 上,必须把这些步骤改造成 流式、定点、有限资源 的架构:
-
滑窗/环形缓冲实现相关累加;
-
局部搜索 + 控制器 FSM 代替"全二维并行";
-
CORDIC/LUT 计算相位与旋转;
-
BRAM 对齐 + AXI-Stream 握手串接后级 FFT/均衡链路。
本文给出一套可综合的模块化实现 与验证闭环 ,适配 N=256, Ncp=32
等常见参数,易扩展到更大 FFT。
2. 顶层数据流与接口
2.1 处理链路(时序)
I/Q流 → S&C延迟自相关(粗τ0, εc) → 精同步半符号局部搜索(τ̂, ε̂)
→ NCO CFO校正 → CP边界扫描(起点微调)
→ (可选) FFT → 均衡 → 逐符号CPE → 线性拟合(残差CFO)
2.2 AXI-Stream 风格接口
-
输入:
s_axis_*
(I/Q 定点、valid/ready) -
输出:
m_axis_*
(CFO 校正后的 I/Q 流,交给 FFT 核或后续处理) -
关键估计量:
tau0, eps_coarse, tau_hat, eps_hat
(寄存器导出,PS 可读)
3. S&C 粗同步(延迟自相关)
3.1 数学回顾
训练块由两段相同的长度 L=N/2L = N/2L=N/2 子块构成,定义:
P(i)=∑k=0L−1r[i+k]r∗[i+k+L], γ(i)=∣P(i)∣∑k=0L−1∣r[i+k]∣2⋅∑k=0L−1∣r[i+L+k]∣2. \begin{aligned} P(i) &= \sum_{k=0}^{L-1} r[i+k] r^*[i+k+L],\ \gamma(i) &= \frac{|P(i)|}{\sqrt{\sum_{k=0}^{L-1}|r[i+k]|^2 \cdot \sum_{k=0}^{L-1}|r[i+L+k]|^2}}. \end{aligned} P(i)=k=0∑L−1r[i+k]r∗[i+k+L], γ(i)=∑k=0L−1∣r[i+k]∣2⋅∑k=0L−1∣r[i+L+k]∣2 ∣P(i)∣.
触发点 τ0\tau_0τ0 在 γ(i)\gamma(i)γ(i) 超阈值后附近取峰值位置;
粗 CFO 取自 P(τ0)P(\tau_0)P(τ0) 的相位:
ϵcoarse≈∠P(τ0)2π⋅NL \epsilon_{\text{coarse}} \approx \frac{\angle P(\tau_0)}{2\pi}\cdot\frac{N}{L} ϵcoarse≈2π∠P(τ0)⋅LN
3.2 RTL 设计要点
-
环形延迟线 (深度 LLL)获取 (s,sdelayed)(s,s_{\text{delayed}})(s,sdelayed)。
-
逐拍乘加 累加 P(i)P(i)P(i)、能量项(用 FIFO 退出项做真滑窗,或 IIR 近似)。
-
阈值/滞回:建议"双阈值 + 最小间隔"抑制虚警。
-
相位求取 :
CORDIC-atan2(P_im, P_re)
输出相位 → 粗 CFO 定点换算。
3.3 可综合骨架(节选)
verilog
// 延迟自相关 (S&C) 粗同步,输出 tau0 与 eps_coarse
delay_corr_sc #(
.IQW(16), .ACCW(40), .LWIN(N/2), .PHW(24)
) u_sc (/* ... AXI-Stream + outputs ... */);
工程建议:滑窗能量的开方可省略,用比较/比例近似代替;相位必须用 CORDIC 或 LUT/差值,确保过采样/量化误差在预算内。
4. 精同步:半符号相关 + 局部二维搜索
4.1 思路替换"全二维并行"
MATLAB 里的 J(τ,ϵ)J(\tau,\epsilon)J(τ,ϵ) 全面网格搜索在 FPGA 上成本过高。我们改为局部搜索:
-
在 τ0\tau_0τ0 周围 [τ0−Δ, τ0+Δ][\tau_0-\Delta,\ \tau_0+\Delta][τ0−Δ, τ0+Δ] 枚举 τ\tauτ。
-
对每个 τ\tauτ,先累加
P0(τ)=∑k=0N/2−1r[τ+k]⋅r∗[τ+N/2+k], P_0(\tau) = \sum_{k=0}^{N/2-1} r[\tau+k]\cdot r^*[\tau+N/2+k], P0(τ)=k=0∑N/2−1r[τ+k]⋅r∗[τ+N/2+k],再扫描离散 ϵ\epsilonϵ 网格,用
J(τ,ϵ)=ℜ { P0(τ) ejπϵ} J(\tau,\epsilon)=\Re\!\left\{\,P_0(\tau)\,e^{j\pi\epsilon}\right\} J(τ,ϵ)=ℜ{P0(τ)ejπϵ}取最大点。归一化 ∣s1∣,∣s2∣|s_1|,|s_2|∣s1∣,∣s2∣ 可选做(硬件中常略去或统一门限)。
4.2 控制器 FSM
-
LOAD_TAU
:从 BRAM 读出窗口数据; -
ACCUM
:NH 次乘加得 P0,E1,E2P_0, E_1, E_2P0,E1,E2; -
SWEEP_EPS
:查表 ejπϵe^{j\pi\epsilon}ejπϵ 与 P0P_0P0 做一次复乘取实部; -
UPDATE_BEST
:比较并记录 (τ∗,ϵ∗)(\tau^*,\epsilon^*)(τ∗,ϵ∗); -
NEXT_TAU
/DONE
。
4.3 实现片段(节选)
verilog
fine_sync_halfcorr_ctrl #(
.N(256), .IQW(16), .ACCW(40), .PHW(24), .TAU_SPAN(10)
) u_fine (/* tau0/epsc in, tau_hat/eps_hat out */);
工程建议:把 ϵ\epsilonϵ 网格中心放在
eps_coarse
附近,步长按 SNR/残差设为 0.005∼0.020.005\sim0.020.005∼0.02 子载波;NEPS≈33~65
足够多数场景。
5. NCO CFO 校正与相位累加可视化
5.1 相位步进
phase_step=−2π⋅ϵhatN \text{phase\step} = -2\pi \cdot \frac{\epsilon{\text{hat}}}{N} phase_step=−2π⋅Nϵhat
采用 PHW 位 相位累加器(2π↦2PHW2\pi \mapsto 2^{\text{PHW}}2π↦2PHW),步进由 eps_hat
定点换算得到。
5.2 旋转实现
-
CORDIC
或sin/cos LUT
取 cosθ, sinθ\cos\theta,\ \sin\thetacosθ, sinθ; -
复乘:(I+jQ)⋅(cos+jsin)(I+jQ)\cdot(\cos+j\sin)(I+jQ)⋅(cos+jsin);
-
位宽回切与饱和。
verilog
nco_rotator #(.IQW(16), .PHW(24)) u_nco (/* s_axis → m_axis */);
6. CP 边界细化(只向右微调)
6.1 指标
对偏移 o=0,...,omaxo=0,\ldots,o_{\max}o=0,...,omax:
val(o)=∣ ℜ {⟨CP(o), tail(o)⟩∥CP(o)∥ ∥tail(o)∥}∣ \mathrm{val}(o) = \left|\,\Re\!\left\{\frac{\langle \mathrm{CP}(o),\,\mathrm{tail}(o)\rangle}{\|\mathrm{CP}(o)\|\,\|\mathrm{tail}(o)\|}\right\}\right| val(o)= ℜ{∥CP(o)∥∥tail(o)∥⟨CP(o),tail(o)⟩}
取最大 ⇒\Rightarrow⇒ start_refined = start_coarse + o^\*
。实践中只允许正向小步,避免回跳到训练块。
6.2 实现提示
-
与精同步类似,用 FSM 顺序累加;
-
归一化可用能量近似或统一门限;
-
OFF_MAX = min(Ncp/2, 16)
一般足够。
7. 残差 CFO 观测(CPE 斜率)
7.1 流程
-
CFO 校正后 → FFT (IP 核) → 均衡 (HkH_kHk 估计或导频)
-
用已知发端数据去相位,得到每符号公共相位 ϕs\phi_sϕs
-
对 ϕs\phi_sϕs 做 unwrap 后最小二乘直线拟合:斜率 a^\hat{a}a^(rad/符号)
-
换算残差 CFO(单位:子载波/符号):
rescfo=a^2π(1+NcpN). res_{cfo} = \frac{\hat{a}}{2\pi\left(1+\dfrac{N_{cp}}{N}\right)}. rescfo=2π(1+NNcp)a^.
7.2 FPGA/PS 分工
-
FFT/均衡 → 建议 IP/PS;
-
逐符号累计 ∑ϕ, ∑kϕ, ∑k, ∑k2\sum \phi,\ \sum k\phi,\ \sum k,\ \sum k^2∑ϕ, ∑kϕ, ∑k, ∑k2 → 可在 PL 做;
-
最终除法/换算 → PS 完成,写回寄存器或监控面板。
8. 定点化与资源规划
位宽/资源建议:
模块 | 主要运算 | 建议位宽 | 备注 |
---|---|---|---|
I/Q 数据 | 加/乘 | IQW=14~16 (Q1.(IQW-1)) |
基带固定点 |
P、能量累加 | 乘加 | ACCW ≥ IQW + log2(win) + margin ,如 40 位 |
防溢出 |
相位/NCO | 加法/查表 | PHW=24 (2π→2242\pi \to 2^{24}2π→224) |
相位累加 |
CORDIC | 旋转/atan2 | 迭代 12~16 级 | 角误差 < 10−310^{-3}10−3 rad |
ε 网格 LUT | sin/cos | 16 位输出 | 配合缩放/舍入 |
门限与滞回 :建议使用 2 级阈值 + 最小保持长度,抑制短促噪声峰值。
时钟与吞吐:各乘法/累加上管线;核心链路 100~200 MHz 对 Zynq/7 系常见配置足够。
9. 关键 RTL 片段(可直接嵌入)
9.1 NCO 旋转(节选)
verilog
wire [PHW-1:0] phase_step_q = /* round(-eps_hat/N * 2^PHW) */;
reg [PHW-1:0] phase_acc;
always @(posedge clk) if (rstn && valid && ready && enable)
phase_acc <= phase_acc + phase_step_q;
// cos_q/sin_q → LUT or CORDIC;I'Q' = 复数乘法后截位
9.2 精同步控制器(伪代码)
verilog
for (tau = tau0-TAU_SPAN; tau <= tau0+TAU_SPAN; tau++) begin
P0 = Σ s1 * conj(s2); // NH 次
for (eps in eps_grid) begin
J = Re{ P0 * e^{jπeps} }; // LUT给cos/sin
if (J > Jbest) begin
Jbest = J;
tau_hat = tau;
eps_hat = eps;
end
end
end
10. 缓存与对齐:把"估计"落地到"流"
-
在 S&C 之后引入一个环形缓冲区(BRAM);
-
精同步得到
tau_hat
后,把读取指针对齐到tau_hat
,再把后续样点送入 NCO; -
CP 扫描使用同一缓冲窗口,避免 DDR 往返。
小技巧:对齐时可以在 stream 中插入"时间戳/样点计数",方便调试与波形对比。
11. 仿真与上板验证
11.1 仿真(Modelsim/VCS/Verilator)
-
激励 :用 MATLAB 生成 I/Q
.bin
(含 S&C 训练 + 数据 + CFO + 噪声),在 testbench 中喂入; -
观测 :dump
tau0, eps_coarse, tau_hat, eps_hat
与 NCO 输出波形; -
指标:
-
定时误差 ∣τhat−τtrue∣\lvert \tau_{\text{hat}}-\tau_{\text{true}}\rvert∣τhat−τtrue∣;
-
CFO 误差 ∣ϵhat−ϵtrue∣\lvert \epsilon_{\text{hat}}-\epsilon_{\text{true}}\rvert∣ϵhat−ϵtrue∣;
-
CP 扫描偏移量对齐到 [0,,Ncp/2][0,,N_{cp}/2][0,,Ncp/2] 内的局部最大;
-
残差 CFO 的斜率接近 0(或在预算内)。
-
11.2 上板(Zynq + AD9361 为例)
-
PS 负责:参数下发、LUT/寄存器配置、状态读取、残差 CFO 拟合;
-
PL 负责:S&C、精同步、NCO、CP 扫描、FFT(可用 IP)等;
-
采集链路:DMA 抓取 NCO 前/后的数据做 A/B 对比;
-
UI 面板:实时显示 γ(d)\gamma(d)γ(d) 峰值、J(τ,ϵ)J(\tau,\epsilon)J(τ,ϵ) 最优点、CPE 趋势线。
12. 常见坑位与调参建议
-
阈值过敏 :噪声峰值触发 → 加双阈值与最小长度;
movmean
在硬件用 IIR 或真滑窗替代。 -
定点溢出:累加器不够 → 提前估算动态范围,宁宽勿窄;乘法后适当右移保留 SNR。
-
CORDIC 迭代不足 :角度量化误差导致 ϵ\epsilonϵ 偏差 → 迭代 14~16 级 + 相位缩放校正。
-
网格过稀 :精同步 ϵ\epsilonϵ 网格步长过大 → 先粗后细(两轮搜索或自适应步长)。
-
对齐错误 :
tau_hat
未正确对齐到输出流 → 用样点计数/戳确认边界。 -
CP 扫描回跳:允许负向微调可能跳回训练块 → 仅向右扫描(正向小幅微调)。
-
残差 CFO 偏差:未做 unwrap 或符号选择不纯 → 做相位展开 & 仅用导频/已知数据。
13. 资源与时序(估算模板)
以
N=256, IQW=16, PHW=24, TAU_SPAN=10, NEPS=41
为例(不同器件略有差异):
-
乘法器:S&C(2~4 个复乘/拍,管线摊销)、精同步(1 个复乘 + 1 复旋转/拍);
-
BRAM:环形缓冲 ≥
N + (N+Ncp) * 若干符号
; -
LUT/FF:控制器 FSM + CORDIC/LUT;
-
Fmax:> 150 MHz(7 系/US+ 常见约束);AXIS 满速 1 样点/拍。
实际请以综合/实现报表与时序分析为准。
14. 与 MATLAB 结果对照的清单
-
在同一输入
.bin
上,比较tau_hat/eps_hat
与 MATLAB 的 τ/ϵ\tau/\epsilonτ/ϵ; -
绘制 NCO phase step 与 phase accum(前 500 样点)对齐;
-
CP 扫描曲线峰值位置一致;
-
CPE 曲线斜率与脚本拟合一致,残差 CFO 换算公式一致:
rescfo=a^2π(1+NcpN). res_{cfo}=\dfrac{\hat{a}}{2\pi\left(1+\dfrac{N_{cp}}{N}\right)}. rescfo=2π(1+NNcp)a^.