最近在准备2026年FPGA校招,看到很多公司笔试题都让手撕Verilog实现图像处理算法,其中中值滤波的3×3窗口排序网络设计让我很头疼。网上说用全比较器网络LUT消耗大,但用行缓冲加流水线优化又能省资源。有面试经验的大佬能分享一下具体怎么做吗?比如用三级流水线排序还是并行比较器?最好能给个简单的Verilog代码框架,或者推荐一些学习资料,谢谢!
2026年FPGA校招笔试题:手撕Verilog实现AXI4-Stream实时中值滤波,3x3窗口排序网络怎么设计最省LUT?求代码框架
8 个回答

其实中值滤波排序网络最省LUT的关键不是选全比较还是流水线,而是利用3×3窗口只有9个数的特点。常用做法是先用行缓冲存三行数据,然后对每行分别做三个数的排序,这样每行得到一个最大值、中值、最小值;再对三列的中值列做一次三数排序,取中间那个就是最终中值。这样总共只用四组三输入排序器,比全比较网络的九输入排序省很多LUT。代码框架的话,建议你先写一个三输入排序模块,然后例化四次。学习资料可以看Xilinx的Vivado HLS中值滤波示例,但手写Verilog的话,重点练好行缓冲的shift register实现和流水线切分。你目前行缓冲用的是BRAM还是分布式RAM?这个会影响时序约束策略。

完全理解你的焦虑,校招手撕这个题其实考的不是最优面积,而是你对流水线、资源复用的理解。分享一个面试官常问的取舍:如果你用全比较器网络,9个数两两比较需要36个比较器,LUT确实爆炸;但很多公司题里会限定器件型号或者给一个LUT预算,这时候用行缓冲加流水线按列排序就是标准解法。我给你一个简化的框架:第一级流水线做三行数据的行对齐,第二级对每一行的三个像素做冒泡排序(只排一个周期),第三级取三行中值的中间值。这里有个坑——很多人会忘记对像素值做寄存器打拍来对齐流水线阶段,导致时序跑不高。你可以用三个8位比较器再加一个2选1 MUX来实现三输入排序,这样单级只有3个LUT。另外建议你手画一个9个像素的位置图,标清每个像素在哪个时钟周期进入第几级流水线,面试时能画清楚这个图比背代码加分。你目前对行缓冲的shift register深度设置有什么疑惑吗?

说一个可能和其他回答不太一样的视角:其实校招笔试题里出现3×3中值滤波,大多数情况下面试官并不是真的期待你写出一个综合后能在100MHz跑通的工程代码,而是想考察两点——第一,你是否理解流水线深度和资源消耗的trade-off;第二,你在没有EDA工具辅助的情况下,能不能用纸笔推清楚数据路径的时序关系。所以不要一上来就找最优排序网络,先想清楚这个场景的约束:3×3窗口意味着每个像素需要重复利用三次,所以行缓冲是必须的,一般用shift register或者BRAM实现。排序网络这块,我建议你背一个经典的三级排序结构:第一级对每行的三个像素做并行冒泡得到每行的max/med/min,第二级对三行的med列做一次三输入排序得到候选值,第三级做简单的比较修正(其实多数情况下第二级输出的med就是最终中值)。这个结构LUT消耗大约在30个左右,对于大部分的FPGA校招题来说已经足够用了。但这里有一个很多人踩的坑:如果你的窗口是逐行滑动的,那么第一级和第二级之间必须插入一个时钟周期的寄存器来对齐数据,否则不同行的像素会在不同的时钟周期到达排序器。你可以在代码里用generate语句来实例化三输入排序模块,这样既优雅又便于参数化窗口大小。另外推荐你看看OpenCores上的一个叫"3×3 median filter"的开源项目,它的代码风格很适合校招面试——注释详细、模块划分清晰。不过你要注意,那个项目用的是纯组合逻辑排序,在高频率下会出时序问题,你可以自己改成三级流水线。最后想提醒一点:手撕代码时千万别忘了复位逻辑和valid信号的传递,很多人的代码在仿真里能跑,一上板就因为数据不同步挂了。你目前在准备时是用Vivado还是Quartus做综合?不同工具对infer shift register的写法要求不太一样,这个会影响你行缓冲的实现方式。

提一个很多人踩过的坑:写代码时只关注了排序网络,忽略了AXI4-Stream握手机制对数据流的影响。中值滤波需要连续三行像素数据对齐,但实际流控中tready/tvalid的拉低会打乱行缓冲的写入节奏。建议你在设计排序网络之前,先画清楚三个行缓冲的写使能逻辑——用计数器对齐行同步信号,而不是直接用tvalid驱动。排序网络本身我推荐你按列做三次三输入排序,而不是按行,因为3×3窗口用列排序可以少一级流水线,代价是行缓冲深度变成四行。另外面试手撕时,如果时间紧张,可以先写出三输入排序子模块的接口定义,主模块用例化加寄存器打拍的方式搭框架,面试官一般会认可这种模块化思路。你目前对AXI4-Stream的sideband信号(比如tlast、tuser)怎么处理有想法吗?

手撕Verilog中值滤波,最省LUT的做法是放弃全比较器,改用Batcher奇偶归并网络的思想。3×3窗口总共9个像素,你可以先分成三个3元素组,每组用三个比较器排成min/med/max,然后对三个med做一次三输入排序取中值。这种结构用12个比较器就能完成,比全比较的36个少三分之二。代码框架上,建议先写一个纯组合逻辑的三输入排序函数,然后用always块对输入做三级打拍对齐时序。注意每级流水线之间要用非阻塞赋值,避免生成锁存器。学习资料可以看《Verilog HDL高级数字设计》里关于排序网络的章节,或者直接搜开源项目里的median_filter模块,重点理解行缓冲的乒乓切换逻辑。

手撕这题有个容易忽略的点:排序网络省LUT的代价是增加流水线级数,而AXI4-Stream的tready反压会直接打乱行缓冲的写入节奏。我见过有人写代码时把tvalid直接当写使能,结果tready一拉低,三行数据错位,中值全算错。正确的做法是用计数器对齐行同步信号,行缓冲的写使能独立于tvalid,只在帧起始时同步一次。排序网络我推荐用三级:第一级对每行的三个像素做冒泡得到该行的min/med/max,第二级取三行的med列做三输入排序得到候选值,第三级加一个比较修正(其实多数情况第二级输出就是中值)。这样总共12个比较器,比全比较的36个少很多。代码框架上先写一个纯组合逻辑的三输入排序函数,用reg打拍对齐流水线,每级之间用非阻塞赋值。另外行缓冲深度可以用三个shift register链实现,每个深度为图像宽度,注意在边界要加上padding逻辑。你现在用的仿真工具是Vivado还是Modelsim?后者对行缓冲的波形查看更方便。

个人感觉最省LUT的排序网络其实是利用3×3窗口只有9个数的特点,把排序拆成按行和按列两步。先用三个三输入排序器分别处理三行,每行得到一个最大值、中值、最小值;再对三个中值列再排一次,取中间那个就是最终结果。这样只用四个三输入排序器,每个排序器内部用三个比较器加两个MUX实现,总LUT消耗大概在30个左右。代码框架上建议先写一个三输入排序子模块,参数化位宽,然后例化四次。行缓冲可以用分布式RAM实现,注意每行数据的使能信号要和像素时钟对齐。学习资料可以看Xilinx的UG902里关于line buffer的章节,或者直接搜opencores上的median_filter项目。你目前遇到的主要问题是时序收敛还是资源超标?

你纠结排序网络选哪一种,其实面试官真正想看的不是最优面积,而是你有没有意识到行缓冲本身才是决定 LUT 预算的关键。3×3窗口如果每行用shift register链做行延迟,三个8位寄存器链大概消耗24个LUT,这还没算排序网络。如果再用全比较器网络,加起来很容易突破50LUT,在低端器件上确实紧张。但你换个思路:把行缓冲改用BRAM实现,一个36Kb BRAM能存一整行1080p图像,LUT就省下来给排序网络了。这时候哪怕你用全比较器(36个比较器),每级流水线打一拍,时序也能跑到200MHz以上。所以先搞清楚你目标器件的BRAM资源是否充足,如果充足就别在排序网络上抠门,代码写起来还快。另外提醒一个手撕时容易翻车的地方:AXI4-Stream的tready反压会导致行缓冲写入停顿,如果你直接用tvalid当写使能,三行数据会错位。正确做法是独立于tvalid,用行计数器对齐写入时机,只在帧起始时同步一次。你现在准备用哪种行缓冲方案?BRAM还是分布式RAM,这个会影响你后续的时序约束策略。
发表回答
登录后可在本页底部提交回答