今年FPGA校招笔试题越来越卷了,看到一道题让用Verilog实现AXI4-Stream接口的实时中值滤波,处理1080P60的视频流。3×3窗口的排序网络如果用冒泡排序,延迟太大,时序肯定过不了。我查了一下,可以用并行比较器网络,比如Batcher奇偶归并网络,但具体怎么映射到流水线级数,每级需要多少个比较器,资源估算和时序约束怎么平衡?有没有大佬分享过类似的实现方案,或者给个参考代码框架?
2026年FPGA校招笔试题:手撕Verilog实现AXI4-Stream实时中值滤波,3x3窗口的排序网络怎么用流水线设计才能满足1080P60时序?
10 个回答

其实这道题的关键不是排序网络本身,而是怎么把3×3窗口的9个数用流水线拍平。1080P60的像素时钟大概148.5MHz,一个时钟周期要出一个结果,冒泡排序肯定不行。Batcher奇偶归并网络确实是个方向,但更常见的做法是用两级流水线:第一级做四个两输入比较器,把9个数分成四组,每组取最小和次小;第二级再合并出中值。这样资源消耗大概是12个比较器加一些寄存器,比全并行排序省一半。要注意的是,AXI4-Stream接口的valid-ready握手逻辑会引入背压,你流水线里得加一个深度至少3的FIFO来缓存行数据,否则窗口滑动会断流。另外,排序网络里比较器输出的位宽要小心,别为了省寄存器把数据截断了,中值滤波对精度要求不高,但边界像素的处理容易出bug,建议窗口滑动到边界时直接复制边界的值。你目前是打算用纯Verilog写还是用HLS?如果面试官追问时序约束,可以提一下把比较器放慢半个时钟周期,用双沿采样来缓解组合逻辑延迟。

我去年面过类似题,当时给的解法是三级流水线。先明确一点:1080P60要求每个时钟周期出一个像素,所以你的流水线必须全速运行,不能有任何气泡。3×3窗口的排序网络,最直观的流水线设计是这样的:第一级,把9个数做两两比较,产生9个min和9个max,但这样会生成18个结果,后续逻辑爆炸。实际工程里会用奇偶归并网络,把9个数先排成3×3矩阵,对每一行做排序,得到三组有序数列;第二级,对每一列取中值,得到三个候选;第三级,对三个候选再取中值。这样总共需要3个三级比较链,每个三级比较链消耗3个比较器,总计9个比较器,再加上寄存器打拍,资源大概在200个LUT左右。但要注意,这个方案要求你先把行缓存做好,因为3×3窗口需要三行数据同时有效。AXI4-Stream接口通常用两个行缓冲FIFO来延迟两行,第三个FIFO存当前行。面试官可能会问你怎么处理行首对齐,我的做法是给每个FIFO加一个行同步计数器。至于时序,148.5MHz下组合逻辑深度控制在8级以内比较安全,你可以在每级比较器之间插一级寄存器,这样三级流水线总共6个时钟周期的延迟,对于视频流来说完全能接受。对了,你用的开发板是哪个系列的?如果是7系列,LUT6资源够用,但如果是更老的Spartan-6,可能要考虑用DSP slice来加速比较操作。

其实你问的排序网络只是中值滤波的中间一环,真正决定1080P60能不能跑通的是行缓存和握手逻辑的配合。3×3窗口需要三行数据同时有效,一般用两个行缓冲FIFO来延迟两行,第三个FIFO存当前行。但AXI4-Stream的ready-valid背压会让FIFO读指针乱跳,如果你直接用FIFO读出的数据做排序,窗口滑动时可能读到旧数据。我见过有人在这里踩坑:排序网络本身只用了3级流水线,时序确实过了,但图像边缘出现条纹,最后发现是行缓存深度没考虑back-to-back传输,导致窗口数据不同步。建议你先用仿真验证好行缓存的读写时序,再写排序网络。另外排序网络用Batcher奇偶归并确实比冒泡靠谱,但注意第一级比较器输出要打一拍再进第二级,不然组合逻辑路径太长。你目前用的开发板是哪个型号?不同器件的LUT结构会影响比较器映射效率。

这道题我去年帮实验室学弟改过代码,核心矛盾是资源与延迟的取舍。先说结论:1080P60的像素时钟148.5MHz,用3级流水线做Batcher奇偶归并网络,总共需要9个比较器,每级打一拍,加上行缓冲的FIFO延迟,整体latency大概6个时钟周期,时序完全能收。但这里有个容易被忽略的点——Batcher网络在9个输入时并不是最优解。你查的论文里常用的是Odd-Even Merge,但实际工程里更常见的做法是先用三个两输入比较器把9个数分成三组,每组内部排序得到三个有序列表,然后对三个列表的中值再排序。这本质上是n=3的Median-of-Medians算法,比较器数量可以压到7个,比Batcher少两个,而且流水线级数不变。代价是中间结果需要多打一拍来对齐,不过对于148.5MHz来说,多一个寄存器周期完全能接受。面试官如果追问为什么选这个结构,你可以说因为LUT6在Xilinx器件里可以映射两个比较器,7个比较器刚好用4个LUT,比9个更紧凑。另外注意AXI4-Stream的tready信号要连到行缓冲FIFO的读使能,否则窗口滑动时如果tvalid被拉低,你的流水线会读入无效数据。建议写一个简单的testbench,用随机像素序列验证中值结果是否正确,很多人在边界处理上直接复制边缘值,但这样会引入直流偏置,正确的做法是窗口滑到边界时只取有效像素做排序,比如左上角只有4个像素参与运算。如果你时间紧,可以先用HLS写一个中值滤波的IP核,对比一下纯Verilog实现的资源差异,但面试时一定要能说清流水线每一级的比较器拓扑。你现在是准备暑期实习还是秋招?不同时间节点对代码完整度的要求不一样。

我个人感觉你其实可以把注意力从排序网络本身稍微移开一点,先想清楚一个问题:1080P60的像素时钟是148.5MHz,这意味着你每个时钟周期必须稳定输出一个中值结果,流水线里不能有任何气泡。很多校招题里大家一上来就纠结Batcher网络要几级、比较器数量怎么最小化,但实际工程里更常见的坑是行缓存和AXI4-Stream握手信号没配合好,导致窗口数据不同步,排序网络算得再快也白搭。我给你一个从项目经验里总结出来的设计思路:先搭好两个行缓冲FIFO,用BRAM实现,深度至少是1920(一行像素数),然后用valid信号作为FIFO的读使能,确保三行数据在同一个时钟沿对齐。排序网络这边,我推荐用三级流水线做Median-of-Medians算法,而不是Batcher。具体做法是:第一级,把9个数分成三组,每组三个数,用三个两输入比较器对每组内部做冒泡排序(因为每组只有三个数,组合逻辑路径很短),得到三组有序列表;第二级,取每组的中值,也就是每组三个数里中间那个,得到三个候选数;第三级,对这三个候选数再取一次中值。这样一共只需要7个比较器,比Batcher的9个少,而且每一级的组合逻辑深度都控制在两个比较器以内,148.5MHz完全能收。资源估算下来大概150-200个LUT加几个寄存器,比全并行网络省不少。代价是第二级需要把第一级的输出打一拍对齐,但多一个寄存器周期对延迟没影响,因为流水线是全速运行的。最后提醒一下,笔试面试里考官不一定要求你写出完整代码,但你要能讲清楚为什么选这个结构、资源怎么算、时序怎么约束。你可以追问自己一句:你的开发板是7系列还是UltraScale?不同器件的LUT查找表结构会影响比较器的实际实现面积。

Batcher奇偶归并网络确实经典,但笔试里我觉得更讨巧的做法是直接用三级流水线做行排序再列排序。第一级把3×3窗口的每一行三个数分别排序,三个比较器就够了;第二级把三行的中值列出来,再对这三个中值做一次排序;第三级输出中值。这样总共六个比较器,流水线级数少,组合逻辑深度浅,148.5MHz没问题。注意行缓存要用BRAM实现,深度取1920,防止AXI4-Stream背压导致数据错位。面试官一般更关心你知不知道行缓存怎么跟排序网络握手,而不是排序网络本身有多花哨。你目前写代码时是习惯用generate循环还是手写每个比较器?

其实你只要记住一个原则:对于3×3窗口,排序网络的比较器数量控制在6到8个之间,流水线级数不超过3级,资源就不会炸。Batcher网络不是唯一解,Medians-of-Medians更省面积。先跑仿真看行缓存时序,别一上来就写排序。你用的仿真工具是Vivado还是Modelsim?

其实你画个时序图就清楚了:148.5MHz下组合逻辑延迟要控制在6.7ns以内,冒泡排序肯定不行。我建议你直接去看Xilinx应用笔记XAPP1208里的中值滤波代码,它用的就是三级流水加行缓存,架构很干净,比你自己从头推Batcher网络省时间。顺便问一句,你准备用纯逻辑还是带DSP48?

我个人觉得这道题真正想考察的不是排序网络本身,而是你对AXI4-Stream握手和行缓存的理解。1080P60意味着每个时钟周期要稳定输出一个像素,流水线不能有任何气泡。如果你一上来就纠结Batcher网络要几个比较器,反而容易忽略一个常见坑:行缓存FIFO的读使能没跟AXI4-Stream的valid信号对齐,导致窗口数据错位。我之前给学弟改代码时遇到过,他用了很漂亮的奇偶归并网络,但仿真波形里第三行数据总是比前两行晚一个周期,最后发现是BRAM读延迟没打拍补偿。我的建议是先把行缓存部分搭好:两个深度1920的FIFO,用移位寄存器的方式读出三行数据,然后用valid信号作为排序网络的启动标志。排序网络用三级流水线就够了——第一级对每行的三个数做冒泡排序(三个比较器),第二级取出三行的中值列,第三级对这三个中值再排序。总共六个比较器,资源很少,而且三级流水线在148.5MHz下时序很容易收敛。你如果担心组合逻辑路径长,可以在每级比较器输出端都加寄存器打一拍。另外注意边界处理:窗口滑动到图像边缘时,要么复制边界像素,要么输出0,这个要跟面试官确认清楚。你目前打算用generate循环来实例化比较器,还是手动写每个比较器的连线?

给个不一样的思路吧:不用Batcher,改用'行排序+列中值'的三级流水线。第一级用三个两输入比较器把每一行的三个数排好序,第二级把三行的中位数拿出来,第三级对这三个中位数再排序。这样总共六个比较器,比Batcher少两个,而且组合逻辑深度浅,148.5MHz完全能跑。资源上大概一百多个LUT加两个BRAM做行缓存。但你要注意AXI4-Stream的tready信号可能拉低导致背压,所以行缓存前面最好加一个深度为4的异步FIFO做缓冲,防止窗口数据断流。先写testbench仿一下边界情况吧,比如全黑或全白图像,看输出是不是正确的中值。
发表回答
登录后可在本页底部提交回答