面试官让我手撕一个基于AXI4-Stream的实时JPEG-LS无损压缩,要求1080P60帧。我写了预测和残差编码的流水线,但他说时序不满足,流水线深度不够。有没有大佬分享下具体怎么设计流水线?比如预测误差怎么在单周期内计算,残差编码怎么用查找表加速,还有边界像素怎么处理?求详细步骤和Verilog代码思路。
2026年FPGA校招笔试题:手撕Verilog实现AXI4-Stream实时JPEG-LS无损压缩,预测和残差编码流水线怎么设计才能满足时序?
9 个回答

面试官说时序不满足,大概率是你在预测误差那个加法器链路上没做retiming。JPEG-LS的预测器里几个加减法可以拆成两级流水,但要注意边界像素的bypass mux会引入额外路径。建议先把MED预测那三个候选值算出来再选,别在同一个周期里又算又选。

看到1080P60帧就知道单周期必须搞定一个像素,时钟大概150MHz左右。你提的查找表加速残差编码是个好方向,但要注意LUT的读延迟和输出寄存器的位置。我去年在类似项目里踩过坑:把Golomb-Rice编码的k值查找和mapped error计算分成两个流水级,但中间那个桶形移位器面积太大导致布线拥塞。后来改成先把error映射成绝对值+符号,再用组合逻辑算Golomb参数,反而更干净。边界像素处理建议单独做一个状态机,在行首和行尾插入流水线stall,但这样会损失吞吐量,所以更常见的做法是把边界条件展开成特例,让主流水线每周期都能接收新像素,边界时用mux插入修正值。你当前的设计里预测误差那级到底用了多少级寄存器?如果少于两级,可以试试把MED里的min和max计算各分一级,选择结果再放一级,这样三级下来时序应该能过。另外AXI4-Stream的ready/valid握手也要小心,如果下游反压,你的流水线必须能暂停,否则预测上下文会错。

建议先把整个流水线画成时间图,标清每个像素从输入到输出经过多少周期,以及每个周期的组合逻辑深度。JPEG-LS的MED预测器本身运算量不大,但残差编码里的Golomb-Rice查表如果直接用case语句映射,综合后可能产生很大的多路选择器。我自己的做法是把k值计算拆成两步:先算error的绝对值范围,再用一个简单的优先编码器得到近似k值,这样面积比查LUT小,时序也容易收敛。边界像素的处理可以单独用一组寄存器存当前行的第一个和上一个像素值,在行首和行尾各插入一个周期的流水控制信号。对了,你用的工具是Vivado还是Quartus?不同工具的retiming策略差异挺大的,Vivado里加个( keep )或者手动插寄存器比依赖综合器自动retiming更稳妥。

面试官说时序不满足,大概率是你把预测误差计算和MED选择器放在同一级了。1080P60下时钟大概150MHz,单周期里算三个候选值再加比较选最小,组合逻辑深度至少七八级,不爆时序才怪。我的做法是把MED拆成两拍:第一拍算A、B、C三个值和它们的差值,第二拍做min/max选择并输出预测值。边界像素别单独做状态机,那样会打乱流水节奏,直接在每个行首和行尾用mux切换成固定预测值,这样每周期都能进新像素。你用的开发板是哪个型号?不同器件的查找表延迟差挺多的,如果是7系列的话建议额外给残差编码的k值查找表输出加一级寄存器。

你问流水线深度不够,其实更常见的问题是没处理好残差编码里的桶形移位器。Golomb-Rice编码里那个右移k位的操作,如果用组合逻辑直接写个移位器,面积大而且路径长。我看过好些校招生的代码,都喜欢把error映射和Golomb参数计算塞在一个周期,然后下一周期直接移位输出,结果时序一塌糊涂。更好的办法是把k值计算单独拎出来,用优先编码器代替查表,这样面积小很多;然后移位操作拆成两拍:第一拍根据k值选出一个mask,第二拍用mask做与运算和拼接,这样比纯移位器好综合。另外要注意AXI-Stream的ready/valid握手信号,如果插入等待周期,你的流水线必须能stall,建议在每一级都加上valid寄存器链和握手逻辑,不然边界处理时容易丢数据。你目前预测误差那级用的什么位宽?如果超过12位,可以考虑把符号位单独处理,这样加减法路径能短不少。

这题我当年校招也遇到过,说下我的完整思路。首先1080P60意味着每帧1920×1080,每秒60帧,算下来像素时钟大约124.4MHz,加上消隐期大概150MHz左右。你的流水线必须做到每个时钟都能处理一个像素,否则带宽不够。
关键瓶颈在MED预测器。MED公式里要先算A、B、C三个候选值,然后根据B和C的比较结果选一个输出。新手最容易犯的错误是把三个值的计算和选择逻辑放在同一个always块里,综合后组合逻辑深度会超过10级。正确做法是把流水线拆成三级:第一级算A、B、C和它们之间的差值(比如B-C和C-B),第二级做比较并输出预测值,第三级算预测误差。这样每级只有三四级逻辑,150MHz下完全能跑。
边界像素的处理有个取巧方法:不需要单独的状态机,而是把行首和行尾的像素当成特殊情况,用条件判断直接给预测值赋0。比如第一列的像素,C和B都不存在,就把预测值设成0;第一行的像素,A和C不存在,也设成0。这样做的好处是不会打断流水线的节奏,每个周期都能接收新像素。但要注意,这种mux选择会引入一些额外路径,所以最好把边界判断逻辑放在MED计算的前一级,用组合逻辑提前算好各候选值的有效标志。
残差编码部分,Golomb-Rice编码的关键是找到合适的k值。我建议别用查找表,因为1080P的像素范围是0-255,预测误差范围在-255到255之间,绝对值范围0-255,可以用一个简单的优先编码器来找最高位位置,然后根据误差范围确定k值。这样面积大概只有几十个LUT,比用BRAM做查找表快得多。编码输出可以用拼接操作,注意在AXI-Stream接口上要处理好字节对齐,因为Golomb码是变长的,每拍输出的位数可能不一样,需要用一个位宽累加器来管理输出缓冲。
最后提醒一点:面试官让你手撕代码,他其实更看重你对时序和流水结构的理解,而不是代码本身有多完美。你可以先画出流水线的时序图,标清每级的寄存器位置和组合逻辑深度,然后再写代码。如果时间允许,建议你用SystemVerilog的interface来封装AXI-Stream信号,这样代码更整洁。你试过用Vivado的report_timing看过关键路径具体在哪一级吗?如果没有,建议先跑一下综合,看看是MED计算路径长还是残差编码的路径长,对症下药。

看到1080P60和AXI-Stream组合,就知道面试官想看你有没有实际做过视频类IP。时序不满足,我猜你大概率是把预测误差计算和Golomb-Rice参数查表塞到同一级了。JPEG-LS的流水线瓶颈不在MED本身,而在残差编码里的桶形移位——如果直接用case语句写一个64路的k值查找表,综合出来的多路选择器逻辑深度轻松超过8级。我自己在zynq上做过类似设计,建议把Golomb-Rice拆成两段:第一段只做error映射和绝对值范围比较,用优先编码器代替查找表输出k的近似值;第二段根据k值做移位和拼接。这样每级逻辑大概4-5级,150MHz完全能跑。边界像素的处理有个偷懒但有效的方法:不用额外状态机,而是在行首和行尾各插入一个周期的流水线stall信号,同时把预测值强制设为上一行同列像素(即C值),这样不会丢数据,代价是每行损失两周期带宽。你算过自己设计里最差路径是哪一段吗?如果是MED里的减法器链,可以试试把A、B、C三个候选值的计算分别放到三个always块里,用三个独立的流水级,最后一级做选择输出,比在一个块里算完再选好综合很多。

你提到的这个题,其实核心矛盾在于:既要实时处理1080P60(像素时钟约150MHz),又要无损压缩,这就逼着你把流水线做到每个时钟都能进一个新像素。很多人的误区是把JPEG-LS当成一个黑盒,照着算法描述直接写Verilog,结果MED里的比较器链和Golomb-Rice的桶形移位器堆在一起,时序直接炸。我的建议是抛开算法文档,先画一张完整的流水线时间图:标清楚每个像素从进入AXI-Stream到输出压缩码流,经过多少个时钟周期,每个周期里组合逻辑有几层。你会发现,真正吃路径的不是MED(它只是几个加减法和比较),而是残差编码里那个根据k值对error做右移的桶形移位器——如果k值是从一个64项的查找表里读出来的,那这个移位器的选择端就是一个64选1的多路器,综合后走线延迟巨大。解决办法是换一种思路:把error映射成绝对值,然后用一个简单的优先编码器(比如看error的二进制最高位在哪儿)来近似k值,这样查找表就被替换成一个面积小得多的组合逻辑。实测下来,优先编码器的延迟只有查找表的一半左右。另外,边界像素的处理有个很常见的工程技巧——不要单独做状态机去判断行首行尾,而是把整帧当成一个连续流,在每行的第一个像素到来时,用一个计数器产生一个flag信号,然后用这个flag去mux掉MED里的预测值,直接输出固定的预测值(比如上一行同列像素)。这样流水线不会断流,每周期都能收像素。你当前设计里,AXI-Stream的tready信号是怎么生成的?如果是等整个压缩模块处理完才拉高,那肯定丢数据;正确做法是让每一级流水都带一个valid寄存器链,tready只取决于第一级是否准备好接收新像素。做到这一点,面试官基本就不会再挑时序问题了。你用的FPGA型号是什么?如果是Artix-7这种慢速器件,可能还需要在残差编码和输出之间再加一级寄存器做打拍。

其实你问的这个问题,很多校招生都栽在同一个地方——太在意算法本身的数学正确性,反而忽略了硬件实现里最核心的时序预算分配。面试官说流水线深度不够,我猜你大概率是把残差编码里的Golomb-Rice参数查表和桶形移位器塞到了同一级。1080P60下像素时钟大概150MHz,一级组合逻辑的深度最好控制在4-5级以内,而一个64路输入的桶形移位器,如果用组合逻辑直接写,综合出来的路径延迟很容易超过6ns,时序自然不满足。我的建议是换个思路:不要把k值当成一个精确查表的结果,而是用优先编码器近似地得到k。具体做法是先把error映射成绝对值,然后判断这个绝对值落在哪个2的幂次区间里,比如0-1给k=0,2-3给k=1,4-7给k=2,这样用几个比较器就能得到k,根本不需要查表。这个近似做法在大多数自然图像上压缩率损失不到1%,但时序好收敛得多。边界像素的处理你也不用单独写一个状态机,那样会打断流水线的连续性。更干净的做法是在行首和行尾各加一个周期的stall信号,同时用一个mux把预测值强制换成上一行同列像素(即C值),这样主流水线每周期都能进新像素,吞吐量不受影响。另外提醒一点:AXI-Stream的ready/valid握手信号一定要在每一级流水线里都配上valid寄存器链,否则遇到backpressure时数据会乱。你目前设计的预测误差那级位宽是多少?如果超过12位,可以试试把符号位单独提出来,这样移位器的数据路径能再窄一些。
发表回答
登录后可在本页底部提交回答