信道估计模块
1,一开始
#100; rst_n = 1;首先等待100拍之后,复位信号拉高,系统开始运行。
因为还没输入数据经过FFT模块,FFT模块输出m_axis_data_tuser为0;存储UW的FFT的ROM在黄线时钟上升沿的时刻检测到rst_n还在低电平,下一时钟上升沿时刻检测到rst_n拉高,开始工作,检测到m_axis_data_tuser为0,douta上挂靠第0个数据。
2,之后
s_axis_config_tready:从低电平跳变为高电平。这表示 FFT IP 核已经完成了内部初始化,准备好接收配置数据
s_axis_config_tvalid:一开始复位信号处于低电平的时候就是高电平。
二者在这一时钟里同时为高,配置完成握手;配置指令s_axis_config_tdata=8'b1已经送入FFT模块(表示做FFT)。之后在下一时钟上升沿,将s_axis_config_tvalid拉低,之后送入的配置都是无效的。
s_axis_data_tready(数据通道准备就绪):这是 FFT IP 核告诉你的上游模块:“我现在可以接收时域数据了”。
rsta_busy(复位忙状态):从高电平跳变为低电平。这个信号通常来自BRAM (ROM)模块。当rst_n刚释放时,存储器需要几个时钟周期来处理内部复位,此时rsta_busy为 1,表示 ROM 还不能被读取。当它变低时,说明你的本地导频 ROM(rom_uw_fft)已经可以正常工作了。
3,数据开始输入
输入数据开始送入,data_in_valid拉高,处于发送UW阶段,uw_data_valid拉高。但在此刻时钟上升沿,检测到uw_data_valid还是0,uw_dot_cnt还是0,没有增加;下一时钟上升沿加1。
4,第一个1024点的128点发送完
在黄线的时钟上升沿,检测到uw_data_valid还是1,uw_dot_cnt加1达到127。与此同时,assign s_axis_data_tlast = (uw_dot_cnt == 127) && uw_data_valid;s_axis_data_tlast为1,表示这是128 个点的最后一个点。
在下一时钟上升沿,检测到uw_data_valid还是1.但是uw_dot_cnt == 127,给uw_dot_cnt赋值为0;并且已经发送128点数据,uw_data_valid置0.
5,FFT开始吐出频域数据
FFT 输出启动 (fft_out_valid拉高)
信号跳变:在光标位置,
fft_out_valid从低电平变为高电平。含义:经过了 FFT 内部的流水线延迟,第一组频域数据
fft_out_i和fft_out_q正式出现在总线上。索引同步:注意
m_axis_data_tuser也变为了00。这表示当前输出的是这 128 个子载波中的第 0 号点(即 DC 直流分量,如果是中心对齐则是最左侧频点)。
ROM 地址寻址与延迟对齐
ROM 响应:
m_axis_data_tuser(值为 00)作为地址传给rom_uw_fft。1 拍延迟:在光标后的下一个上升沿,
ram_uw_fft_q/i出现了有效数据(如000764等)。这证实了我们之前的判断:ROM 有 1 个时钟周期的读取延迟。打拍对齐 (
fft_out_i_d1):观察下方的fft_out_i_d1信号。它在光标后的第二个上升沿才更新为 FFT 输出的第一个值(010207)。fft_out_valid_d1有效标识也在此刻才拉高关键点:这说明你的代码中对 FFT 数据做了 1 拍的延迟处理,使得
fft_out_i_d1正好与ram_uw_fft_i在同一个时钟沿对齐。计算就绪:这种对齐是 LS 计算(复数乘法)正确进行的前提。
6,复数乘法器输出
cmpy_out_valid变为 1:在黄色光标处,有效信号拉高。这说明从 FFT 吐出数据并经过 1 拍对齐进入乘法器后,再经过乘法器自身的流水线延迟(约 6 拍),第一组 LS 估计结果正式产生。
全精度计算:你可以看到cmpy_out_q和cmpy_out_i显示的是非常长的十六进制数(例如0000005af7000)。这正是我们之前讨论的49位(符合字节对齐后在总线上显示为 50位)全精度乘法结果。
定点小数点位置:这些数的高位包含了符号信息,而低位包含 27 位小数。
ls_channel_estimate_i/q(截位后的中间变量)
就在cmpy_out下方,你可以看到截位后的数值:
数值对应:红圈内的
14e2和0b5e是从那一长串全精度数据中,按照你的逻辑[29:15]截取出来的 16 位结果。物理意义:这就是在该子载波上计算出的信道频域响应{H}(k)。
csi_out_i/q与csi_data_valid(最终输出)
这是你模块的最外层端口信号,也是 Testbench 写入文件的信号。
同步跳变:可以看到在红圈右侧,
csi_data_valid紧随其后变高,csi_out出现了与截位变量一致的14e2和0b5e。对齐验证:这证明了你最后修改的
always块逻辑是正确的——数据被成功打拍,并与有效信号严格对齐输出。
7,ifft输出
processing_active = is_processing || (ifft_out_tvalid && (count_1024 == 10'd0));
如果寄存器已经在处理中 (is_processing),或者 刚刚收到首个有效数据,它都会立刻为 1。(以防IFFT输出数据会和pad_out有偏差)
IFFT IP核开始输出数据,ifft_out_tvalid拉高,表示输出数据有效;
pad_out_i = (processing_active && count_1024 < 10'd11) ? ifft_out_i : 24'd0;
根据当前状态以及发送过来的数据的数量来判定输出前11个数据还是0.
在下一时刻上升沿,检测到processing_active为1,is_processing才升为1.
注意重点
数学世界中以及MATLAB中,IFFT的公式是带有一个1/N的;但是FPGA(Xilinx IP)里的 Unscaled IFFT:
在硬件里做除法是非常消耗资源的。Xilinx 的工程师在设计 FFT/IFFT IP 核时,为了保证最大化的计算精度和最小的硬件消耗,当配置为Unscaled(不缩放)模式时,它直接把公式里的 1/N扔掉了!
FFT 在底层是由一级一级的加法器和乘法器组成的。每过一级,就是把两个数相加或相减。
在二进制中,两个 n 位的数相加,结果必然需要 n+1位来防止溢出。
128 点的 IFFT 内部需要 log_2(128) = 7级蝶形运算。
经历了 7 次纯粹的加减法累加,数据的整数位不可避免地长胖了 7 圈(增加了 7 bit)。
所以,在硬件的 Unscaled 模式下,IFFT 根本没有任何“缩小”数据的动作,它就像一个贪吃蛇,把 128 个频域点的数据疯狂加在一起,导致总幅度扩大了 128 倍。(相当于左移了7位)
而FPGA 算出来的 FFT 结果,和 MATLAB 算出来的 FFT 结果,在绝对幅度上是天然一模一样的!
8,FFT输出
FFT开始输出第一个数据,csi_out_tvalid拉高
