最近在准备FPGA秋招面试,看到很多面经都问AXI4-Stream接口的加速器设计。中值滤波在图像预处理里很常用,但排序网络和流水线划分容易踩坑。比如3×3窗口的9个像素怎么并行排序?怎么用奇偶交换网络减少比较器数量?还有行缓冲要存几行数据才能保证吞吐率?求大佬分享一个从窗口生成到中值输出的完整Verilog设计思路,最好能带时序约束的优化技巧。
2026年FPGA工程师面试被问如何用Verilog实现一个AXI4-Stream实时中值滤波加速器,怎么设计流水线?
提问
回答 9

面试问中值滤波加速器很常见,关键先把3×3窗口生成捋清楚:行缓冲至少要存两行数据,加上当前行第三行,配合移位寄存器做滑窗。排序网络别用全比较器,9个数用奇偶交换网络或双调排序,比较器能压到20个左右。流水线重点是把窗口生成、排序、中值输出三级拉开,时序就好收敛了。你目前准备到什么程度了?

提醒一下,面试官问AXI4-Stream接口时,最容易被忽略的是握手逻辑的时序设计——valid/ready的反压处理必须和流水线对齐,否则数据窗口会错位。行缓冲建议用BRAM实现两行缓存,深度设成图像宽度,配合移位寄存器做9像素并行输出。排序的话,3×3窗口用Batcher奇偶归并网络比较省资源,总共19个比较器,比冒泡排序快一倍。流水线划分我习惯分成三级:窗口生成级、排序网络级、中值输出级,每级之间插寄存器打拍,这样时钟频率能上200MHz。另外注意中值滤波对边界像素要补零或复制,面试时如果能主动提到边界处理,会加分不少。

说个实际工程中容易踩的坑:很多人设计3×3窗口时,以为行缓冲存三行就够了,但忽略了图像第一行和最后一行边界像素的处理——要么补零要么复制,这会导致窗口生成逻辑里需要多状态判断,流水线里得插入额外拍数。我建议用两个FIFO加移位寄存器实现行缓冲,FIFO深度等于图像宽度,这样对任意分辨率都通用。排序网络这块,9个数的中值其实有现成的奇偶交换网络结构图,网上能搜到,比你自己推逻辑快得多。时序优化上,组合逻辑最重的是排序网络那级,可以用流水线寄存器把比较器拆成两级,代价是多一个时钟周期的延迟,但fmax能提升30%左右。另外AXI-Stream的tlast信号别忘了处理,帧尾标志要在窗口滑到最后一行时正确拉高,否则下游模块会卡死。个人觉得面试时画个流水线时序图比光说代码更直观,把valid/ready握手和每级寄存器打拍画清楚,面试官一看就知道你理解到位了。你准备用vivado还是quartus做验证?工具不同约束写法有点区别。

其实面试官最想看的是你对握手反压的处理,而不是排序网络有多花哨。我建议你先画一个三级流水线的时序图:第一级是AXI-Stream收数据+行缓冲写入,第二级是窗口生成+排序网络,第三级输出中值。关键点在于valid/ready的连锁反压——当第三级还没完成时,第二级的输出必须被暂存,否则第一级的数据会冲掉前面的窗口。很多人只关注排序器个数,结果在握手逻辑上漏一拍,整个设计就崩了。你可以先用一个简单的3×3仿真,把tvalid/tready的波形拉出来看,能对齐了再加排序网络。问一下,你打算用什么工具做时序分析?

准备这道题,我建议你把重点放在「为什么这么划分流水线」上,而不是背代码。面试官通常先让你画顶层框图,然后追问每一级的延时和握手关系。比如行缓冲为什么存两行再加当前行,而不是直接存三行?因为BRAM读出来要一个周期,你用两个BRAM加一个移位寄存器,可以做到每个时钟输出一行新像素,而读三行BRAM会让读写冲突。排序网络这块,Batcher奇偶归并网络确实比全比较器省资源,但如果你用Xilinx的Vivado,它的DSP48和LUT结构其实更适合用查找表直接做比较树,综合后面积可能更小——这个取舍面试时提出来会显得你有工程经验。另外,边界像素处理一定要讲清楚:我一般复制边界值,这样第一行和最后一行窗口不会缩水,而且实现起来只要在地址生成器里加两个比较器就能控制。最后提醒一下,tlast信号要跟数据对齐,否则下游DMA会以为帧提前结束。你可以先在GitHub找个开源的AXI-Stream中值滤波代码,但别直接抄,自己手写一遍流水线握手逻辑,面试时被问到细节才答得上来。你现在手边有FPGA板子可以跑仿真吗?

从求职角度看,这道题有个隐藏考点是「资源与吞吐率的权衡」。很多教程教你把排序网络做成全流水,每个时钟出一个中值,但实际图像分辨率高时,BRAM和LUT消耗会很大。一个偷懒但实用的做法是:在行缓冲里用两个FIFO加一个移位寄存器,FIFO深度设成图像宽度,这样对1920×1080和640×480都能通用,但记得把FIFO的prog_full阈值调好,防止反压导致丢数。排序网络我见过有人用4级比较器加一个中值索引器,比全排序少用10个比较器,代价是延迟多两拍,但fmax能上到250MHz以上——面试时你可以说这个折中方案,考官会觉得你考虑过面积和时序的置换。另外别忘了,AXI4-Stream的tkeep信号也要处理,不然数据宽度不对齐时会多出无效像素。你准备用多大的数据位宽?8位还是10位?这会影响比较器的位宽和BRAM深度选择。

说实话,你这个问题里最容易被忽略的不是排序网络本身,而是AXI4-Stream握手信号跟行缓冲写使能的对齐。我见过不少人把行缓冲的写使能直接连tvalid,结果下游反压时tvalid拉低,行缓冲少写了一拍,整个窗口就歪了。正确的做法是:行缓冲的写使能必须用tvalid && tready,而且要跟tdata在同一拍。另外,3×3窗口生成时,我建议你先用两个FIFO加一个移位寄存器,FIFO深度设成图像宽度再加2,防止读指针追写指针。排序网络的话,9个数的中值其实可以不用全排序——用4级比较器做奇数偶数列分别排序,再取中间值,比Batcher归并网络少用3个比较器,代价是延迟多两拍,但fmax能稳住。时序约束上,窗口生成那级的组合逻辑路径最长,建议在FIFO读端口和移位寄存器之间插一级寄存器打拍,这样setup slack会好看很多。边界像素你打算复制还是补零?这个选择会影响地址生成器的复杂度,面试时主动提出来能体现你对图像边界的考虑。

这道题的核心矛盾其实是「数据流连续」和「排序网络组合逻辑延迟」之间的tradeoff。很多新手一上来就画一个三级流水线:窗口生成、排序、输出,觉得拍数均匀就完事了。但实际时序分析时会发现,排序那级如果用全比较器,9个数的两两比较会产生至少4级组合逻辑,在Virtex-7上可能只能跑到150MHz。我的建议是:把排序网络拆成两个阶段——先做行内排序(每行3个数用3个比较器排成递增序列),再做列间排序(3行排序结果用6个比较器取中值)。这样每级组合逻辑深度不超过3级,fmax能上250MHz以上。这个思路来自一个叫「三行排序」的论文,面试时讲出来会显得你有调研过。再说行缓冲:很多人纠结存两行还是三行,其实存两行BRAM加当前行的移位寄存器就够了——FIFO深度设成图像宽度,但要注意第一行数据进来时,前宽度个周期窗口是无效的,这时候tvalid必须拉低,否则下游会吞进垃圾像素。这个细节我在面试时被追问过三次,每次都是因为面试官想确认我有没有考虑过帧起始的边界条件。还有一个容易被忽略的点:tlast信号要在窗口滑到最后一行最后一列时拉高,而不是在最后一个像素到来时拉高。因为中值滤波输出比输入延迟了若干拍,tlast必须跟输出数据对齐。我一般用计数器跟踪当前行号和列号,当列号等于图像宽度减1且行号等于图像高度减1时,延迟相应拍数后拉高tlast。你目前是用Vivado还是Quartus?不同工具的时序优化策略差别挺大,比如Vivado的synthesis选项里有个retiming,打开后会自动帮你重排流水线寄存器,但前提是你得在RTL里留出足够的寄存器位置。

说个我踩过的坑吧——刚开始我也按面经说的把排序网络做成全流水,9个数用Batcher归并网络排好再取中值,结果综合完发现LUT消耗比预期多了快一倍,因为Vivado把比较器都map到LUT6里了,每个比较器至少占2个LUT。后来才意识到,对于3×3窗口,其实没必要把9个数完全排好序,只要找到中值就够了。一个省资源的做法是用4个比较器做两轮两两比较,先找出每行的最大值、最小值,再对三个最大值和三个最小值做筛选,最后剩下的三个数里取中间那个。这样比较器数量从19个降到7个,延迟只多了1拍,但面积能省一半以上。当然这个做法有个前提——窗口内的像素分布不能太极端,否则中值可能偏到一边,但实际图像数据里基本不会出问题。行缓冲方面,我建议你存两行BRAM加当前行的移位寄存器,深度设成图像宽度+2,防止读指针追写指针造成数据错位。AXI4-Stream的tvalid和tready一定要用组合逻辑做与门再连到行缓冲写使能,否则下游反压时窗口会歪掉。你目前用的图像分辨率是多少?如果是1080p的话,BRAM深度设成1922就够了,但要注意第一行数据进来时,前1920个周期窗口里只有部分像素有效,边界处理可以用复制边界值的方式,这样实现简单且不会引入额外延迟。
发表回答
登录后可在本页底部提交回答
