FPGA 三点中值滤波:不补零、不丢背压样本的流式实现

单个离群尖峰常使滑动平均产生拖尾。三点中值取三个数排序后的中间值,可抑制某些孤立尖峰,但不是万能去噪器,也不能保证保留窄脉冲、拐点或快速阶跃。MathWorks median 文档 给出中值定义;本文没有调用 MATLAB,也没有声称 MATLAB 实测。

定义窗口,比比较器数量更重要

输入是无符号 W bit(支持 1~32)数。只统计握手成功的样本,定义输出为 median(x[n-2],x[n-1],x[n])。它是因果、尾随三点窗口,不是以当前样本为中心、需要未来点的离线滤波。

复位后前两个输入仅填充历史,不输出;第 3 个及之后每个成功接收的输入产生一个输出。因此一个连续片段的 N 个输入产生 max(N-2,0) 个输出。不做前后补零,不自动 flush 出最后两点。输入空闲几个周期不推进窗口;背压时没有成功握手的数据也不得写入历史。

输出寄存器深度为 1:s_ready = !rst && (!m_valid || m_ready)。当旧结果被接收的同时可接收新输入并写入新结果;当下游停顿时,输出和两份历史都保持。第 3 个样本接收边沿之后结果有效,最早下一上升沿被下游接收。在持续有效且下游持续就绪时,填满后可每周期接收/输出一次,但这不是时序收敛的实测结论。

用区间夹取求中值

先令 lo=min(older,newer)、hi=max(older,newer):当前输入小于 lo 则中值是 lo,大于 hi 则中值是 hi,否则就是当前输入。无需求和,避免 a+b+c-min-max 中容易忽略的加法溢出问题。寄存器与比较使用无符号声明;直接接入补码 ADC 会出错,需先明确码型,再决定修改为有符号比较或做码型变换。

完整实现

rtl.sv:

systemverilog 复制代码
module median3 #(parameter integer W=8)(
 input wire clk,rst,input wire s_valid,output wire s_ready,
 input wire [W-1:0] s_data,output reg m_valid,input wire m_ready,
 output reg [W-1:0] m_data);
 reg[W-1:0] older,newer,lo,hi,med;reg[1:0] count;
 initial if(W<1||W>32)$fatal(1,"W must be 1..32");
 assign s_ready=!rst&&(!m_valid||m_ready);
 always @*begin
  lo=(older<newer)?older:newer;hi=(older<newer)?newer:older;
  med=(s_data<lo)?lo:((s_data>hi)?hi:s_data);
 end
 always @(posedge clk)begin
  if(rst)begin older<=0;newer<=0;count<=0;m_data<=0;m_valid<=0;end
  else begin
   if(m_valid&&m_ready)m_valid<=0;
   if(s_valid&&s_ready)begin
    older<=newer;newer<=s_data;
    if(count<2)count<=count+1;
    if(count==2)begin m_data<=med;m_valid<=1;end
   end
  end
 end
endmodule

独立排序 oracle 与握手 scoreboard

测试台通过独立冒泡排序求期望中值,不复制 RTL 的 lo/hi 夹取表达式。只有 s_valid && s_ready 才推进参考历史。只有输出握手才弹出期望队列;输出停顿时每拍核对 valid/data 稳定。

用 0~7 的全部 512 个有序三元组逐个注入连续流,覆盖每个指定三元组及相邻三元组之间的跨边界窗口,并不逐三元组复位。后续加入最大值孤立尖峰、确定性多值序列、输入间隙、连续 valid 和周期背压。最后在输出停顿时复位,并检查重启后前两点不输出。W=3 配置覆盖 3 bit 三元组全集;W=8/16 不能称为全输入空间穷举。

tb.sv:

systemverilog 复制代码
`timescale 1ns/1ps
module tb;
 parameter W=8;
 reg clk=0;always #5 clk=~clk;
 reg rst=1,sv=0,mr=0;reg [W-1:0] sd=0;
 wire sr,mv;wire [W-1:0] md;
 median3 #(.W(W)) dut(clk,rst,sv,sr,sd,mv,mr,md);
 reg [W-1:0] expected[0:20000],h0,h1,held;
 integer wr=0,rd=0,count=0,j,a,b,c,phase=0;reg stalled=0,drive_ready=0;
 function automatic [W-1:0] oracle(input reg[W-1:0] x,y,z);
  reg [W-1:0] v[0:2],tmp;integer i,k;
  begin v[0]=x;v[1]=y;v[2]=z;
   for(i=0;i<2;i=i+1)for(k=0;k<2-i;k=k+1)if(v[k]>v[k+1])begin tmp=v[k];v[k]=v[k+1];v[k+1]=tmp;end
   oracle=v[1];
  end
 endfunction
 always @(posedge clk) begin
  if(rst)begin wr=0;rd=0;count=0;h0=0;h1=0;stalled=0;end
  else begin
   if(stalled&&(!mv||md!==held))$fatal(1,"stall stability");
   // Consume old output before appending the newly accepted window.
   if(mv&&mr)begin if(rd>=wr||md!==expected[rd])$fatal(1,"median mismatch %0d",rd);rd=rd+1;end
   if(sv&&sr)begin
    if(count>=2)begin expected[wr]=oracle(h0,h1,sd);wr=wr+1;end
    h0=h1;h1=sd;if(count<2)count=count+1;
   end
   stalled=mv&&!mr;held=md;
  end
 end
 task automatic reset_dut;
  begin @(negedge clk);rst=1;sv=0;mr=0;repeat(2)@(negedge clk);rst=0;end
 endtask
 task automatic send(input reg[W-1:0] n);
  begin @(negedge clk);sv=1;sd=n;do @(posedge clk);while(!sr);@(negedge clk);sv=0;end
 endtask
 always @(negedge clk)if(drive_ready)begin phase=phase+1;mr=(phase%11)>=4;end
 initial begin
  reset_dut();drive_ready=1;
  // All triples of 0..7: boundaries, repetitions and all orderings.
  for(a=0;a<8;a=a+1)for(b=0;b<8;b=b+1)for(c=0;c<8;c=c+1)begin send(a);send(b);send(c);end
  send(0);send({W{1'b1}});send(0);
  // Continuous valid across accepted transfers; hold each value during stalls.
  for(j=0;j<1000;j=j+1)begin
   @(negedge clk);sv=1;sd=j;
   do @(posedge clk);while(!sr);
  end
  @(negedge clk);sv=0;
  for(j=0;j<3000;j=j+1)begin send((j*73)^(j>>2));if(j%7==0)repeat(3)@(negedge clk);end
  wait(rd==wr);@(negedge clk);drive_ready=0;mr=0;
  send(1);wait(mv);repeat(3)@(negedge clk);reset_dut();drive_ready=1;
  send(5);send(2);repeat(3)@(negedge clk);if(mv)$fatal(1,"startup output");
  send(4);wait(rd==wr);repeat(5)@(negedge clk);
  $display("PASS median3 W=%0d",W);$finish;
 end
 initial begin #2000000;$fatal(1,"timeout");end
endmodule

接入采样链的四个检查点

  1. 确定上游样本是否允许被阻塞。没有 ready 的真实 ADC 不能直接丢到此接口,应按采样率与最坏背压配置缓冲,并明确溢出策略。
  2. 上游必须在 valid 高且 ready 低期间保持样本;不要依赖滤波器"顺手"捕获变化中的输入。
  3. 随样本携带时间戳时明确输出对应最新点 n 或窗口中心 n-1;本 RTL 未传输时间戳,不能凭时钟周期推断经过空闲后的真实采样时间。
  4. 帧边界若不允许跨帧窗口,需复位/清状态或设计明确的帧接口。当前代码没有帧信号,不能把 valid 拉低当作清历史。

当前验证边界与复现环境

本稿是待实测的工程候选稿,不能按已验证文章发布 。2026-10-27 云工作区仅完成文件准备和工具可用性检查;未发现 Icarus Verilog、Verilator 或 Vivado xsim。运行批量脚本得到 BLOCKED: iverilog not installed; no RTL executed。因此没有 RTL 编译成功、波形、仿真 PASS、综合资源、时序收敛或上板结果;也没有用 Python 结果代替 RTL 仿真。

代码使用 SystemVerilog,时钟为测试台中的 10 ns 周期,同步高有效复位。实际工程应替换为自己的时钟约束和复位同步方案。下列运行命令是待执行步骤,不代表工具已经运行。前置条件为已合法安装并可执行的 Icarus Verilog(支持 -g2012)及 vvp,本次不安装或购买软件。

在附件目录执行:

bash 复制代码
bash run.sh

脚本为每次运行创建独立 runs/run-* 目录,保存工具版本、编译输出和运行输出,避免覆盖旧证据,并要求进程成功且日志含 PASS。还需要人工检查日志没有 fatal/error、核对执行文件与源码哈希、进行独立复核。源码内参数范围检查供仿真提前暴露配置错误,不替代目标综合器的可综合性检查。

若已有 Vivado,可使用官方三阶段流程(在对应子目录执行,默认参数):

text 复制代码
xvlog -sv rtl.sv tb.sv
xelab work.tb -s tb_snapshot
xsim tb_snapshot -R

以上流程依据 AMD UG900:xelab、xvhdl、xvlog 和 xsim 命令选项。实际版本与日志待补,不据此宣称厂商工具已执行。

下一步验证和相关实践

待补证据:W=3/8/16 RTL 编译与实际回归、连续输入吞吐计数、独立技术复核。故障注入计划包括"每个时钟都更新历史""停顿时改写输出""复位后第 2 点提前有效"。这些目前仅是计划,未执行不能标记通过。

相关已发布内容:ADC 码流窗口统计与码型转换、ready/valid 握手与背压。你的业务中单点高脉冲是干扰还是有效事件?如果它是有效事件,是否应只标记异常而不直接替换输出?

相关推荐
hexiaoyan8279 小时前
互联卡设计方案:504-PCIE X16 金手指 FPGA Aurora协议互联卡
fpga开发·测试载板·互联卡·fpga aurora协议·夹板连接器·板卡互联
星夜离尘569 小时前
FPGA 二进制数如何变成十进制显示:可背压的逐位 BCD 转换器
fpga开发
匠行科技10 小时前
基于VPX架构的4路10GSPS ADC信号采集处理板
fpga开发
幻智星科技1 天前
蒙哥马利模乘FPGA优化实战
fpga开发·密码学
星夜离尘561 天前
FPGA 多通道 DAC 目标码影子寄存器:成组提交、同拍写入与快照边界
fpga开发
t_niu_niu2 天前
基于多帧累加平均的图像传感器暗电流抑制方法与 FPGA 实现
fpga开发·ram·fpn·固定模式噪声·image sensor
幻智星科技2 天前
FPGA纯硬件UDP桥接:微秒级响应新范式
c++·fpga开发·硬件架构
小蘇的FPGA2 天前
基于FPGA实现PCIe H2C过程中TAG的一种管理方法
fpga开发
威嵌神州2 天前
一台加固平板,能替掉几台现场设备?
嵌入式硬件·fpga开发·电脑·平板