news 2026/10/14 9:32:58

流水线 valid 对齐自检:输入气泡和中途复位怎么测

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
流水线 valid 对齐自检:输入气泡和中途复位怎么测

流水线 valid 对齐自检:输入气泡和中途复位怎么测

连续输入时,每拍都有结果,valid 即使提前一拍也可能不容易从几行打印中看出来。把输入改成孤立脉冲和气泡,再在事务还没走完时复位,问题就清楚得多。

本文沿着已有的 3 级乘法器文章继续做验证,但换成一个便于手算的四级算术流水线:8 位无符号输入,计算y=(3x+5) 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.tcl:

open_vcd trace.vcd log_vcd /tb/* run all close_vcd quit

上述执行链与实测一致,源码路径改成了便于复制的相对路径。三个工具实际报告的版本都是Vivado Simulator v2022.1;本次没有验证 Linux 环境。

预期有三条:有效输入在约定边沿输出;输入气泡也按同样的距离传到输出;reset 取消全部未完成事务,之后不泄漏旧 valid。原版实际日志为:

TEST_PASS PIPE cycles=599 assertions=1025 accepted=423 delivered=419 flushed=4 resets=6 random_valid=132 latency=3

这里latency=3统计的是“接受输入的边沿”到“输出更新的边沿”之间的三个完整周期。RTL 有四级寄存器,下面把计数过程展开,避免把日志数字误读成三级结构。

2 四级寄存器,为何日志里写 latency=3

结构很简单:d0接收输入,d1加 1,d2乘 3,out_data加 2。每一级都是 8 位,赋值时截断;最终与(3x+5) mod 256等价,不是饱和运算。

设某笔输入在第 n 个上升沿被接受:

上升沿之后,非阻塞赋值已完成这笔输入所在位置
nd0=x,valid_pipe[0]=1
n+1d1=(x+1) mod 256,valid_pipe[1]=1
n+2d2=3(x+1) mod 256,valid_pipe[2]=1
n+3out_data=(3x+5) mod 256,out_valid=1

因此,接受边沿与输出更新边沿之间隔了三个完整周期,经过四个寄存级。TB 在每个上升沿后 1 ns 读取结果,检查的就是表里的“沿后状态”。

如果下游也是always_ff @(posedge clk),它在 n+3 这个沿采到的仍是更新前的输出,要到 n+4 沿才能采到这笔结果。讨论延迟时,应先说清观察位置,不能把“沿后可见”和“另一个寄存器沿上采样”混用。

图 1:从本次pipeline_trace.csv重绘的逐拍快照,每列取上升沿后 1 ns。灰色数据表示没有有效事务;这不是 Vivado GUI 截图。

实际记录中,cycle 5 接受00,cycle 8 输出05;cycle 10 接受FF,cycle 13 输出02。后一个结果也验证了模 256 的溢出约定:3×255+5=770,低 8 位为02。

3 气泡是空槽,流水线仍然前进

这个接口只在rst=0 && in_valid=1的上升沿接受一笔输入。in_valid=0表示当前没有新事务,但前面已经接受的事务仍要继续前移。

所以 valid 链每个非复位周期都更新:

valid_pipe <= {valid_pipe[2:0], in_valid};

如果把整条链放进if(in_valid)里面,孤立输入后面一旦都是气泡,那笔输入的 valid 就会卡在中途。本次“单笔输入后连续四个空拍”的用例正是为了让这种问题显现。

数据寄存器也会在气泡期间计算。图中某些out_valid=0的周期,out_data仍在变化,这是当前实现允许的行为。下游只能在out_valid=1时把它当作结果;TB 不要求无效输出必须是 0 或保持不变。

但 valid 本身不能跳过检查。如果只在out_valid=1时比较数据,多出来的输出、丢失的输出或提前一拍的有效脉冲,都可能漏测。因此下面的 TB 每一拍都先检查out_valid,再决定是否检查数据。

4 完整 RTL

保存为02_pipeline/rtl.sv。两个MUT_宏是故意保留的错误注入开关;正常编译不定义它们。

`timescale 1ns/1ps // Four register banks; accept at edge n, output at n+3 (3 full periods). // No backpressure: in_valid=0 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_valid=valid_pipe[2]; // DELIBERATE one-edge misalignment. `else assign out_valid=valid_pipe[3]; `endif always_ff @(posedge clk) begin if(rst) begin d0<=0; d1<=0; d2<=0; out_data<=0; `ifdef MUT_RESET_LEAK // DELIBERATE bug: reset with in_valid=1 fails to flush pending valids. if(in_valid) valid_pipe<={valid_pipe[2:0],1'b0}; else valid_pipe<=0; `else valid_pipe<=0; `endif end else begin d0<=in_data; d1<=d0+8'd1; d2<=d1*8'd3; out_data<=d2+8'd2; valid_pipe<={valid_pipe[2:0],in_valid}; end end endmodule

复位优先于输入。复位沿会清空数据和全部 valid,不接收同沿输入,也不兑现本来在该沿到期的结果。这里定义的是“取消在途事务”,没有保存、重放或补发机制。

5 用接受日志建立参考模型

参考模型不再复制一条与 DUT 一模一样的 valid 移位链,而是记账:每接受一笔数据,就直接计算数学结果,并记录它的绝对到期周期cycle+3。到期时应当有输出,其他周期不应冒出有效事务。

复位到来时,尚未到期或尚未交付的队列项全部取消。最终应满足:

accepted = delivered + flushed pending = 0

保存为02_pipeline/tb.sv:

`timescale 1ns/1ps module tb; logic clk=0,rst=1,in_valid=0; logic [7:0] in_data=0; 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 head=0,tail=0,cycle=0,assertions=0,accepted=0,delivered=0,flushed=0; integer resets=0,trace_fd,random_valid=0; logic [31:0] rng=32'h13579bdf; 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); rst=reset_i; in_valid=valid_i; in_data=value_i; cycle++; expected_valid=0; expected_data=0; if(reset_i) begin flushed+=tail-head; head=tail; resets++; end else begin if(valid_i) begin due[tail]=cycle+3; expected[tail]=(3*int'(value_i)+5)%256; tail++; accepted++; end if(head<tail && due[head]==cycle) begin expected_valid=1; expected_data=expected[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 don't-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,8'he7,"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 i=0;i<16;i++) step(0,1,i*17,"continuous values"); drain(); for(int i=0;i<20;i++) step(0,(i%2)==0,8'h80+i,"alternating valid and bubbles"); drain(); // One pending transaction: leak appears AFTER reset, not necessarily on it. step(0,1,8'h42,"pending transaction to cancel"); step(1,1,8'hff,"midstream reset ignores valid input"); drain(); for(int i=0;i<5;i++) step(0,1,8'h20+i,"fill before reset"); step(1,0,0,"reset on a due output cancels all pending"); drain(); step(0,1,8'h7f,"resume immediately after reset drain"); drain(); for(int i=0;i<256;i++) begin // Deterministic xorshift32, independent of simulator random seed. rng=rng^(rng<<13); rng=rng^(rng>>17); rng=rng^(rng<<5); random_valid+=rng[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 i=0;i<256;i++) step(0,1,i,"all 256 arithmetic inputs"); drain(); assertions++; if(head!=tail || accepted!=delivered+flushed) $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 latency=3", 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_valid=1、in_data=FF
  • 42被取消,FF不被接受;cycle 62 的out_valid必须为 0

只检查复位当拍还不够。错误 valid 可能先残留在中间级,复位释放两拍后才走到输出。因此 TB 在复位之后继续送空拍,检查旧事务有没有“回来”。

第二次复位发生在连续输入期间:cycle 65~69 接受20、21、22、23、24,其中前两笔已在 cycle 68、69 输出。cycle 70 复位时,队列还有三笔,原本分别在 cycle 70、71、72 到期;本次约定把这三笔全部取消,包括刚好在复位沿到期的那一笔。

复位结束后,cycle 75 接受7F,cycle 78 输出82,固定延迟没有改变。两次中途复位合计取消四笔,最终423=419+4。日志中的resets=6是六个被检查的复位周期,其中四个在启动阶段,两个在中途,不是六次中途故障。

这里的 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 cycle=7 bubble drains pipeline got=1 expected=0 pending=1

cycle 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 cycle=62 bubble drains pipeline got=1 expected=0 pending=0

这正好对应 cycle 59 接受、cycle 60 本应取消的事务。到 cycle 62,参考队列已空,故障实现却重新输出有效脉冲。

两个变体都成功编译、展开,然后由功能断言抓到指定错误;它们不是原版运行失败。需要注意,本机 xsim 在这两个$fatal之后都返回了退出码 0,所以运行脚本还检查CHECK_FAIL与TEST_PASS。不能仅用进程是否返回非零来判断这种自检仿真的成败,也不能把 watchdog 当作成功抓到设计错误。

8 这组检查可以复用到哪里

这套“接受时记到期周期”的方法适合无背压、无停顿、固定边沿延迟的模块。替换算式时,应同步调整参考计算的位宽、有符号性、溢出约定和到期周期,再保留每拍 valid 检查与复位记账。

如果以后加入全局 CE、ready/valid 背压或弹性缓冲,事务可能停在某一级,按绝对时钟周期cycle+3判定就不再成立,需要按推进条件或握手重新建模。不能只给现有代码加一个if(ready),就继续沿用本次固定延迟结论。

本次没有综合、DSP 资源报告、时序分析或板级测试;10 ns 是仿真激励的时钟周期,不是对器件 100 MHz 时序收敛的证明。仿真已经回答的是:在这 599 个检查周期里,气泡按约定传播,中途 reset 取消了四笔在途事务,两个故意错位的实现也确实被同一份 TB 捕获。

相关阅读

  • 流水线设计 Pipeline:提升时序性能的核心技巧(附3级流水线乘法器实操):前置的流水线与数据对齐内容;本文新增控制时序自检
  • AMD UG900 2022.1:Running SystemVerilog in Standalone or prj Mode:xvlog -sv与独立运行方式
  • AMD UG900 2022.1:Parsing Design Files, xvhdl and xvlog:编译工具与命令说明
  • AMD UG901 2022.1:综合与 HDL 编码参考;功能仿真与实际资源映射应分别验证
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 9:32:47

.NET DataTable帮助类开发实践与性能优化

1. DataTable帮助类概述 在.NET开发中&#xff0c;DataTable是最常用的内存数据表结构之一。作为ADO.NET的核心组件&#xff0c;它提供了灵活的数据存储和处理能力。但在实际项目中&#xff0c;我们经常需要重复编写大量样板代码来处理DataTable的创建、填充、转换等操作。这正…

作者头像 李华
网站建设 2026/10/14 9:32:24

SwiftUI与Agent技能封装:智能体如何自动生成声明式UI代码

1. 从标题拆解这个项目的真实意图 第一次看到“SwiftUI-Agent-Skill”这个组合词&#xff0c;我的直觉是&#xff1a;这大概率是一个把 SwiftUI界面开发能力 和 Agent式任务编排 结合起来的技能封装项目。拆开来看&#xff0c;“SwiftUI”是苹果生态里声明式UI框架&#xf…

作者头像 李华
网站建设 2026/10/14 9:32:11

MATLAB与HFSS联合仿真偶极子天线:自动化扫参与优化实战

简介&#xff1a;面向电子工程领域的天线设计人员与仿真学习者&#xff0c;这份资料提供MATLAB与HFSS联合仿真偶极子天线的可运行脚本方案。压缩包共2个文件&#xff0c;均为.m脚本&#xff0c;整体仅2KB&#xff0c;代码精简聚焦核心流程&#xff0c;结构清晰适合直接参考。主…

作者头像 李华
网站建设 2026/10/14 9:32:05

从Sora到实战:DiT如何统一视频生成与最小实现指南

1. 从Sora说起&#xff1a;为什么视频生成突然“统一”到了DiT如果你这两年一直在关注视频生成这个方向&#xff0c;应该会有一种很强烈的感觉&#xff1a;2024年之前&#xff0c;大家各玩各的&#xff0c;有基于U-Net的、有基于自回归的、有基于GAN的、有基于掩码建模的&#…

作者头像 李华
网站建设 2026/10/14 9:32:03

用Kmeans聚类解析啤酒风味:从数据到风格分类的实战指南

1. 项目概述&#xff1a;用Kmeans给啤酒“分门别类”&#xff0c;不是玄学&#xff0c;是数据在说话 你有没有在超市冰柜前犹豫过——这瓶“麦香浓烈”的德式小麦、那罐“苦度爆表”的美式IPA、还有标着“清爽易饮”的日式拉格&#xff0c;到底该怎么选&#xff1f;靠包装&…

作者头像 李华
网站建设 2026/10/14 9:32:01

HTTPS加密原理与实战优化指南

1. HTTPS加密的本质&#xff1a;从快递柜说起 想象你要给朋友寄一份重要文件。如果直接扔在普通信箱里&#xff0c;快递员、分拣员甚至邻居都能随意翻看内容。HTTPS要解决的正是这个问题——它相当于给文件加了个带密码锁的快递柜&#xff0c;只有你和朋友知道开箱密码。这个&q…

作者头像 李华