
流水线 valid 对齐自检输入气泡和中途复位怎么测连续输入时每拍都有结果valid 即使提前一拍也可能不容易从几行打印中看出来。把输入改成孤立脉冲和气泡再在事务还没走完时复位问题就清楚得多。本文沿着已有的 3 级乘法器文章继续做验证但换成一个便于手算的四级算术流水线8 位无符号输入计算y(3x5) mod 256。重点是逐拍对应关系、自检参考模型和 reset 取消在途事务的边界不比较 Fmax也不声称使用了 DSP 或完成上板。1 运行环境与应该看到的结果本次实际运行于 Windows、Vivado Simulator 2022.1使用 SystemVerilog。时钟周期 10 ns复位同步高有效模块没有 ready、背压或全局 clock enable每个非复位上升沿都会推进。把第 4、5 节的代码保存为02_pipeline/rtl.sv与02_pipeline/tb.sv新建与02_pipeline同级的空目录run_pipeline。工具加入 PATH 后在这个目录中执行xvlog -sv ../02_pipeline/rtl.sv ../02_pipeline/tb.sv xelab work.tb -s sim -debug typical xsim sim -tclbatch simulate.tcl同目录的simulate.tclopen_vcd trace.vcd log_vcd /tb/* run all close_vcd quit上述执行链与实测一致源码路径改成了便于复制的相对路径。三个工具实际报告的版本都是Vivado Simulator v2022.1本次没有验证 Linux 环境。预期有三条有效输入在约定边沿输出输入气泡也按同样的距离传到输出reset 取消全部未完成事务之后不泄漏旧 valid。原版实际日志为TEST_PASS PIPE cycles599 assertions1025 accepted423 delivered419 flushed4 resets6 random_valid132 latency3这里latency3统计的是“接受输入的边沿”到“输出更新的边沿”之间的三个完整周期。RTL 有四级寄存器下面把计数过程展开避免把日志数字误读成三级结构。2 四级寄存器为何日志里写 latency3结构很简单d0接收输入d1加 1d2乘 3out_data加 2。每一级都是 8 位赋值时截断最终与(3x5) mod 256等价不是饱和运算。设某笔输入在第 n 个上升沿被接受上升沿之后非阻塞赋值已完成这笔输入所在位置nd0xvalid_pipe[0]1n1d1(x1) mod 256valid_pipe[1]1n2d23(x1) mod 256valid_pipe[2]1n3out_data(3x5) mod 256out_valid1因此接受边沿与输出更新边沿之间隔了三个完整周期经过四个寄存级。TB 在每个上升沿后 1 ns 读取结果检查的就是表里的“沿后状态”。如果下游也是always_ff (posedge clk)它在 n3 这个沿采到的仍是更新前的输出要到 n4 沿才能采到这笔结果。讨论延迟时应先说清观察位置不能把“沿后可见”和“另一个寄存器沿上采样”混用。图 1从本次pipeline_trace.csv重绘的逐拍快照每列取上升沿后 1 ns。灰色数据表示没有有效事务这不是 Vivado GUI 截图。实际记录中cycle 5 接受00cycle 8 输出05cycle 10 接受FFcycle 13 输出02。后一个结果也验证了模 256 的溢出约定3×2555770低 8 位为02。3 气泡是空槽流水线仍然前进这个接口只在rst0 in_valid1的上升沿接受一笔输入。in_valid0表示当前没有新事务但前面已经接受的事务仍要继续前移。所以 valid 链每个非复位周期都更新valid_pipe {valid_pipe[2:0], in_valid};如果把整条链放进if(in_valid)里面孤立输入后面一旦都是气泡那笔输入的 valid 就会卡在中途。本次“单笔输入后连续四个空拍”的用例正是为了让这种问题显现。数据寄存器也会在气泡期间计算。图中某些out_valid0的周期out_data仍在变化这是当前实现允许的行为。下游只能在out_valid1时把它当作结果TB 不要求无效输出必须是 0 或保持不变。但 valid 本身不能跳过检查。如果只在out_valid1时比较数据多出来的输出、丢失的输出或提前一拍的有效脉冲都可能漏测。因此下面的 TB 每一拍都先检查out_valid再决定是否检查数据。4 完整 RTL保存为02_pipeline/rtl.sv。两个MUT_宏是故意保留的错误注入开关正常编译不定义它们。timescale 1ns/1ps // Four register banks; accept at edge n, output at n3 (3 full periods). // No backpressure: in_valid0 inserts a bubble, not a clock stall. module fixed_pipeline(input logic clk,rst,in_valid, input logic [7:0] in_data,output logic out_valid, output logic [7:0] out_data); logic [7:0] d0,d1,d2; logic [3:0] valid_pipe; ifdef MUT_VALID_EARLY assign out_validvalid_pipe[2]; // DELIBERATE one-edge misalignment. else assign out_validvalid_pipe[3]; endif always_ff (posedge clk) begin if(rst) begin d00; d10; d20; out_data0; ifdef MUT_RESET_LEAK // DELIBERATE bug: reset with in_valid1 fails to flush pending valids. if(in_valid) valid_pipe{valid_pipe[2:0],1b0}; else valid_pipe0; else valid_pipe0; endif end else begin d0in_data; d1d08d1; d2d1*8d3; out_datad28d2; valid_pipe{valid_pipe[2:0],in_valid}; end end endmodule复位优先于输入。复位沿会清空数据和全部 valid不接收同沿输入也不兑现本来在该沿到期的结果。这里定义的是“取消在途事务”没有保存、重放或补发机制。5 用接受日志建立参考模型参考模型不再复制一条与 DUT 一模一样的 valid 移位链而是记账每接受一笔数据就直接计算数学结果并记录它的绝对到期周期cycle3。到期时应当有输出其他周期不应冒出有效事务。复位到来时尚未到期或尚未交付的队列项全部取消。最终应满足accepted delivered flushed pending 0保存为02_pipeline/tb.svtimescale 1ns/1ps module tb; logic clk0,rst1,in_valid0; logic [7:0] in_data0; wire out_valid; wire [7:0] out_data; // Independent acceptance log: absolute deadlines, no DUT-like shift model. integer due[0:1023]; logic [7:0] expected[0:1023]; integer head0,tail0,cycle0,assertions0,accepted0,delivered0,flushed0; integer resets0,trace_fd,random_valid0; logic [31:0] rng32h13579bdf; always #5 clk~clk; fixed_pipeline dut(.*); task automatic step(input bit reset_i,valid_i,input logic [7:0] value_i,input string label_i); bit expected_valid; logic [7:0] expected_data; (negedge clk); rstreset_i; in_validvalid_i; in_datavalue_i; cycle; expected_valid0; expected_data0; if(reset_i) begin flushedtail-head; headtail; resets; end else begin if(valid_i) begin due[tail]cycle3; expected[tail](3*int(value_i)5)%256; tail; accepted; end if(headtail due[head]cycle) begin expected_valid1; expected_dataexpected[head]; head; delivered; end end (posedge clk); #1; assertions; if(out_valid!expected_valid) $fatal(1,CHECK_FAIL PIPE valid cycle%0d %s got%b expected%b pending%0d, cycle,label_i,out_valid,expected_valid,tail-head); if(expected_valid || reset_i) begin assertions; if(out_data!expected_data) $fatal(1,CHECK_FAIL PIPE data cycle%0d %s got%02h expected%02h, cycle,label_i,out_data,expected_data); end // Data is intentionally dont-care when invalid and not in reset. $fdisplay(trace_fd,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%s, cycle,rst,in_valid,in_data,out_valid,out_data,expected_valid,expected_data,tail-head,label_i); endtask task automatic drain; repeat(4) step(0,0,8he7,bubble drains pipeline); endtask initial begin trace_fd$fopen(pipeline_trace.csv,w); if(!trace_fd) $fatal(1,CHECK_FAIL PIPE trace open); $fdisplay(trace_fd,cycle,rst,in_valid,in_data,out_valid,out_data,expected_valid,expected_data,pending,scenario); repeat(4) step(1,0,0,startup synchronous reset); step(0,1,0,isolated zero); drain(); step(0,1,255,isolated overflow FF); drain(); for(int i0;i16;i) step(0,1,i*17,continuous values); drain(); for(int i0;i20;i) step(0,(i%2)0,8h80i,alternating valid and bubbles); drain(); // One pending transaction: leak appears AFTER reset, not necessarily on it. step(0,1,8h42,pending transaction to cancel); step(1,1,8hff,midstream reset ignores valid input); drain(); for(int i0;i5;i) step(0,1,8h20i,fill before reset); step(1,0,0,reset on a due output cancels all pending); drain(); step(0,1,8h7f,resume immediately after reset drain); drain(); for(int i0;i256;i) begin // Deterministic xorshift32, independent of simulator random seed. rngrng^(rng13); rngrng^(rng17); rngrng^(rng5); random_validrng[0]; step(0,rng[0],rng[15:8],fixed-seed random valid); end drain(); // All arithmetic inputs at full throughput, covers modular overflow. for(int i0;i256;i) step(0,1,i,all 256 arithmetic inputs); drain(); assertions; if(head!tail || accepted!deliveredflushed) $fatal(1,CHECK_FAIL PIPE accounting accepted%0d delivered%0d flushed%0d pending%0d, accepted,delivered,flushed,tail-head); $fclose(trace_fd); $display(TEST_PASS PIPE cycles%0d assertions%0d accepted%0d delivered%0d flushed%0d resets%0d random_valid%0d latency3, cycle,assertions,accepted,delivered,flushed,resets,random_valid); $finish; end initial begin #20000; $fatal(1,WATCHDOG PIPE); end endmodule这里的检查是过程式比较与$fatal没有依赖 SVA 覆盖数据库。!会把意外 X/Z 也判为不一致#1用于在当前上升沿的非阻塞赋值更新后观察 DUT不能搬进可综合逻辑当作延迟实现。测试安排包含孤立输入、连续输入、交替 valid、连续四个空拍、固定种子的 256 周期 xorshift 输入以及全部 256 个算术输入值。固定种子随机段实际有 132 个有效输入所以再次运行能对照相同的计数和轨迹。原版 1025 次检查由 599 次 valid 检查、419 次有效数据检查、6 次复位数据检查和 1 次最终记账检查组成。全部 256 个输入值都测过不代表所有可能的控制时序都穷举过这是有限的定向与确定性伪随机测试。6 两次中途复位分别取消了什么图 2本次 CSV 的两段记录。红列为复位沿蓝格为接受输入绿格为有效输出pending是 TB 完成当前周期处理后的待交付数量。第一次复位专门覆盖“rst 与 in_valid 同时为 1”cycle 59 接受42按原计划应在 cycle 62 更新输出cycle 60 复位同时输入端给出in_valid1、in_dataFF42被取消FF不被接受cycle 62 的out_valid必须为 0只检查复位当拍还不够。错误 valid 可能先残留在中间级复位释放两拍后才走到输出。因此 TB 在复位之后继续送空拍检查旧事务有没有“回来”。第二次复位发生在连续输入期间cycle 6569 接受20、21、22、23、24其中前两笔已在 cycle 68、69 输出。cycle 70 复位时队列还有三笔原本分别在 cycle 70、71、72 到期本次约定把这三笔全部取消包括刚好在复位沿到期的那一笔。复位结束后cycle 75 接受7Fcycle 78 输出82固定延迟没有改变。两次中途复位合计取消四笔最终4234194。日志中的resets6是六个被检查的复位周期其中四个在启动阶段两个在中途不是六次中途故障。这里的 delivered 统计的是 TB 在沿后观察到的有效输出。它没有模拟下游寄存器的握手或采样反馈也没有ready不能把这个计数扩大成带背压系统中的交付证明。7 两个错误注入验证自检是否敏感两个故障变体都使用同一份 TB不修改期望。各自在新的空目录内编译、展开和运行避免宏定义或旧快照混用。第一种故意把out_valid接到valid_pipe[2]比正确输出提前一级xvlog -sv -d MUT_VALID_EARLY ../02_pipeline/rtl.sv ../02_pipeline/tb.sv xelab work.tb -s sim -debug typical xsim sim -tclbatch simulate.tcl实际捕获结果Fatal: CHECK_FAIL PIPE valid cycle7 bubble drains pipeline got1 expected0 pending1cycle 5 的输入应在 cycle 8 才有效cycle 7 提前冒出的 valid 被抓住。这个用例直接检查时间关系不是仅比较算术结果。第二种故意在rst in_valid时继续移位 valid而没有完整清空xvlog -sv -d MUT_RESET_LEAK ../02_pipeline/rtl.sv ../02_pipeline/tb.sv xelab work.tb -s sim -debug typical xsim sim -tclbatch simulate.tcl实际捕获结果Fatal: CHECK_FAIL PIPE valid cycle62 bubble drains pipeline got1 expected0 pending0这正好对应 cycle 59 接受、cycle 60 本应取消的事务。到 cycle 62参考队列已空故障实现却重新输出有效脉冲。两个变体都成功编译、展开然后由功能断言抓到指定错误它们不是原版运行失败。需要注意本机 xsim 在这两个$fatal之后都返回了退出码 0所以运行脚本还检查CHECK_FAIL与TEST_PASS。不能仅用进程是否返回非零来判断这种自检仿真的成败也不能把 watchdog 当作成功抓到设计错误。8 这组检查可以复用到哪里这套“接受时记到期周期”的方法适合无背压、无停顿、固定边沿延迟的模块。替换算式时应同步调整参考计算的位宽、有符号性、溢出约定和到期周期再保留每拍 valid 检查与复位记账。如果以后加入全局 CE、ready/valid 背压或弹性缓冲事务可能停在某一级按绝对时钟周期cycle3判定就不再成立需要按推进条件或握手重新建模。不能只给现有代码加一个if(ready)就继续沿用本次固定延迟结论。本次没有综合、DSP 资源报告、时序分析或板级测试10 ns 是仿真激励的时钟周期不是对器件 100 MHz 时序收敛的证明。仿真已经回答的是在这 599 个检查周期里气泡按约定传播中途 reset 取消了四笔在途事务两个故意错位的实现也确实被同一份 TB 捕获。相关阅读流水线设计 Pipeline提升时序性能的核心技巧附3级流水线乘法器实操前置的流水线与数据对齐内容本文新增控制时序自检AMD UG900 2022.1Running SystemVerilog in Standalone or prj Modexvlog -sv与独立运行方式AMD UG900 2022.1Parsing Design Files, xvhdl and xvlog编译工具与命令说明AMD UG901 2022.1综合与 HDL 编码参考功能仿真与实际资源映射应分别验证