我在准备2026年FPGA校招,看到很多面经都在问手撕Verilog实现实时视频处理。最近刷到一道题,要求用AXI4-Stream实现JPEG-LS无损压缩,预测和残差编码的流水线设计要满足1080P60不丢帧。我查了JPEG-LS的算法,预测部分用了MED(中边缘检测),残差编码是Golomb-Rice。但流水线怎么划分才能平衡延迟和吞吐率?行缓冲深度怎么算?边界像素怎么处理?有没有大佬能分享下具体推导和Verilog代码框架?
2026年FPGA校招笔试题:手撕Verilog实现AXI4-Stream实时JPEG-LS无损压缩,预测和残差编码流水线怎么设计才能满足1080P60?
12 个回答

其实这道题的核心矛盾是:MED预测需要当前像素左边的数据,而Golomb-Rice编码又需要等残差算出来才能开始,所以流水线划分的关键是把「预测」和「编码」解耦。我自己的做法是三级流水:第一级读行缓冲和左像素,算出MED预测值;第二级做残差计算和符号处理;第三级查表选Golomb参数并输出码字。行缓冲深度取图像宽度+2是为了存左边界和上一行的第一个像素,边界像素单独写一个状态机判断,避免溢出。AXI4-Stream的背压主要卡在第三级,如果编码器输出来不及被下游消费,tvalid拉低时要保证前两级的数据能暂存在寄存器里——所以每级之间要插valid-ready握手寄存器,别图省事用单拍使能。另外Golomb参数的选择完全可以用一个组合逻辑查表,把残差分布和k值映射成BRAM或者LUT,不用每次都重新算,省资源。你准备面试的话,建议先画清楚时序图,标出每拍的latency和吞吐率,再动手写代码。你目前是用Vivado还是Quartus?不同工具对BRAM的推断策略有点差别。

说点实际的吧。1080P60意味着每帧约124万像素,60帧就是每秒7440万像素,时钟跑148.5MHz左右。你的流水线设计如果做不到每时钟拍输出一个码字,必然丢帧。所以流水线的首要目标是保证每拍吞吐率等于1,而不是压低延迟。MED预测本身是组合逻辑,但你要把预测和残差计算放在同一级的话,关键路径会很长,尤其当行缓冲用BRAM时,读地址到数据输出的延迟加上预测逻辑容易超时序。我建议把行缓冲读地址和读数据分到不同级:第一级算地址并读BRAM,第二级用读出的左、上、左上像素做MED并算出残差,第三级做Golomb编码。这样每一级的组合逻辑深度都控制在10-15个LUT以内,148.5MHz不难。编码部分用查找表选参数时要注意,Golomb的k值一般是0到16,你可以把残差绝对值的高几位直接映射成k值,不用真建16个表,用几个MUX加少量LUT就能搞定。边界像素的处理其实很简单:第一列没有左像素,你就把左像素设成0,同时把左上像素也设成0,这样MED退化成简单的上像素预测;第一行没有上像素,就只用左像素。写代码时用两个计数器分别记录当前行号和列号,组合逻辑判断边界条件。AXI4-Stream的握手一定要按标准写法:valid和ready同时为高才算一拍传输,ready不能组合依赖valid,否则会死锁。我当年校招面试时被追问过握手信号打拍导致的延迟累积问题,你可以提前想好如果下游连续反压多个周期,你流水线内部的寄存器够不够存中间数据。一般做法是在每级之间加一个深度为1的FIFO,用valid-ready控制,这样背压最多回退一级,不会打乱整条流水。最后提醒一下,面试官很可能让你在白板上画时序图,建议你练熟每级寄存器的波形,包括行缓冲读使能、预测值有效信号、编码输出valid的时序关系。你目前是把MED和Golomb的细节都自己写,还是准备调IP核?如果是手写,建议先写个单行的小模块验证正确性,再扩展到全帧。

说实话,1080P60的JPEG-LS流水线,最容易被忽视的不是MED预测本身,而是行缓冲的读写冲突。你算一下,148.5MHz下每时钟要读三个像素(左、上、左上),同时还要写当前像素进缓冲,如果BRAM用单端口,一个周期根本搞不定。常见做法是把行缓冲切成两个Bank,或者用Simple Dual Port RAM,读地址和写地址分开,写使能留到下一拍。另外,边界处理不是简单加个if-else就完事——左边界时左上像素要复用上一行第一个像素,上边界时左上像素复用当前像素的左边,这个状态机写不好容易漏掉第一列和第一行。个人建议先画一个像素坐标时序图,标清楚每个时钟沿哪些像素可用,再动手写RTL。至于Golomb参数查找表,可以省BRAM:把残差绝对值右移两位当索引,映射到k值,用分布式LUT实现,大概几十个LUT就能搞定,不用专门开一块BRAM。最后提醒一下,AXI4-Stream的tready如果拉低,你的流水线第三级必须能冻住前两级的数据,否则残差计算会丢拍。这里可以用一个简单的valid-ready握手寄存器做背压,别用全局使能。你目前行缓冲的深度是打算用图像宽度+2还是+3?这个选型会直接影响BRAM的位宽利用率。

见过很多人死磕MED的时序优化,其实1080P60下148.5MHz对于7系列FPGA来说根本不算高频,关键是把Golomb编码的查找表用组合逻辑做,别用时钟节拍去算参数,不然流水线会多出一级延迟。行缓冲深度用图像宽度+2就够了,边界像素单独写个case语句处理第一行和第一列,别搞复杂状态机。

这道题如果只是应付笔试,把三级流水画出来、行缓冲深度写清楚就能拿大半分了;但如果想拿高分甚至面试时现场写代码,你得理解一个隐藏考点——MED预测的数据依赖本质上是二维的,而流水线只能处理一维时序。具体来说,当前像素需要上一行同一列的像素(正上方),而上一行那个像素的残差还没算完,因为它在上一行流水线里。这意味着你必须在行缓冲里存原始像素值,而不是存残差或码流。很多新手会误以为行缓冲存编码后的结果能省BRAM,实际上那样会导致读出来的数据是乱序的,因为Golomb编码长度可变。所以行缓冲深度必须是图像宽度+2,每个位置存8bit原始像素,这决定了BRAM的位宽至少是8bit,深度至少是1920+2=1922,一般用18Kb BRAM配置成2048×8刚好够。边界处理时,第一行和第一列的MED预测需要特殊赋值:第一行把上方像素设为0,第一列把左边像素设为0,左上角像素复用左像素。这个逻辑可以用一个简单的计数器加比较器实现,不用写成独立状态机,否则代码可读性差。再往下想一步,Golomb-Rice的参数k值选择其实可以跟残差绝对值的高位直接挂钩——比如残差绝对值大于等于2^k的整数部分时k加1,这个比较器用LUT实现比BRAM查找表更快,因为BRAM有读延迟。面试官如果追问为什么不用BRAM,你可以回答:对于1080P60,每像素只有约13.5ns可用,BRAM的读地址到数据输出延迟通常在1-2ns,加上组合比较器可能超过一个时钟周期,而LUT查找是纯组合逻辑,延迟更可控。最后,AXI4-Stream的tlast信号要记得在每行结束时拉高,否则下游的DMA或DDR控制器可能把两行数据当成一包处理,导致解码时图像错位。你目前用的FPGA型号是什么?如果是Artix-7或者更低端的,BRAM的读写时序还要再仔细算一下。

这道题我在准备校招时也卡过一阵,后来去实习跟了个视频IP项目才想通。你提到的三级流水——预测、残差、编码——方向是对的,但很多人忽略了一个细节:MED预测的那个「左像素」其实不是上一级算完传下来的,而是从当前行缓冲的写端口旁路回来的。具体说,当你写当前像素进行缓冲时,这个值刚好就是下一个时钟周期右边像素需要的左像素。所以行缓冲的读、写、旁路这三路数据要同时处理,否则流水线要么多等一拍,要么读出来的数据是旧的。我当时写法是:第一级读行缓冲拿到上像素和左上像素,同时从旁路寄存器拿左像素,然后组合逻辑算出MED预测值;第二级用预测值和当前原始像素算残差,并做符号映射;第三级做Golomb-Rice编码的查表和码流打包。行缓冲深度取图像宽度+2,这个+2是因为第一列和最后一列边界需要特殊处理——第一列时左像素和左上像素都要用上一行对应位置的像素来替代,最后一列时写地址要回绕。边界处理我建议单独写个always块,根据col==0和row==0两个标志位做mux,别在预测逻辑里套大量if-else,否则综合出来的LUT层级很深。另外AXI4-Stream的背压确实容易踩坑,我建议每级之间都插一个带valid-ready的握手寄存器,第三级编码器处理慢的时候,前两级的数据能暂存在寄存器里,不会被冲掉。你目前是在准备笔试阶段还是已经拿到面试通知了?如果还在笔试阶段,建议先画清楚时序图,把每个时钟沿的有效数据标出来,再动手写代码。

行缓冲深度用图像宽度+2,这个2其实是为了存第一列和最后一列的边界像素。很多人只想到第一行和第一列的特殊赋值,但忘了最后一列写地址回绕时,上一行最后一个像素要作为下一行的左上像素被读出。边界处理我建议用单独的mux,别在MED预测公式里嵌if-else,不然综合出来路径太长。至于Golomb-Rice参数,用残差绝对值的高两位直接映射k值,实测压缩率损失很小但能省一个BRAM。你代码框架打算用状态机还是纯流水线?

我当年搞这题时犯过一个蠢错:行缓冲深度算对了,但BRAM读使能没和写使能错开,导致同一周期读出来的数据是上一轮的旧值。解决方法是用双端口RAM,写地址比读地址提前一拍,或者把行缓冲切成两个单端口Bank乒乓操作。另一个容易忽略的是Golomb编码的码表——很多人真去建一个16KB的ROM存参数,其实残差绝对值右移两位后直接作为索引,用LUT组合逻辑映射到k值,资源省很多还不用读延迟。我觉得你先别纠结完整代码,先把MED预测那一级的数据依赖图画清楚:当前像素需要左、上、左上三个值,其中左值直接从当前行缓冲的写端口旁路拿,上值和左上值从行缓冲读端口拿,这三个值必须在同一个时钟沿对齐。你准备用几个状态机来控制边界处理?

其实这道题有个陷阱藏在AXI4-Stream接口里。很多人只盯着流水线内部吞吐率,忽略了tvalid/tready的背压传播。假如第三级编码器输出码字时,下游拉低tready,你的数据得暂存在哪?常见做法是在每级流水线之间插valid-ready握手寄存器,但这会引入额外延迟。我见过一个取巧方案:让MED预测和残差计算这两级做成无背压的纯流水(只要输入有效就每拍处理),只在最后一级编码器出口处用FIFO缓冲码字,FIFO空时拉低tvalid、快满时通过反压信号让前级暂停写行缓冲。这样做的好处是行缓冲的写地址不会因为背压而跳变,边界逻辑简单很多。另外,关于Golomb参数选择,我建议你实测一下自然图像的残差分布:大部分像素残差绝对值集中在0到15之间,所以你只需要把参数k映射到0到4即可,高位全归并到k=4。这样查找表用几十个LUT就能实现,连BRAM都省了。你手头有仿真用的1080P测试图吗?没有的话可以去网上找标准测试序列,用MATLAB先跑一遍算法验证边界条件,再写RTL会少走很多弯路。

行缓冲深度用图像宽度+2,这个+2很多人只想到第一列边界,其实最后一列的写地址回绕时,上一行最后一个像素要作为下一行的左上像素被读出,少了这个就会在每行末端出现数据错位。我建议你先把第一行和第一列的MED预测值单独写个组合逻辑mux,别在公式里嵌if-else,不然综合出来路径太长容易超148.5MHz。另外Golomb参数用残差绝对值的高两位直接映射k值,实测压缩率损失很小但能省一个BRAM。你代码框架打算用状态机还是纯流水线?
发表回答
登录后可在本页底部提交回答