news 2026/10/18 3:39:24

FPGA 复位回归实验:双时钟停钟、释放中重入与消费端首拍自检

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA 复位回归实验:双时钟停钟、释放中重入与消费端首拍自检

复位链在单时钟、一次上电的波形里看起来正常,接入多个时钟域后却还要回答三个具体问题:一个时钟停了,另一个时钟域能否按自己的节拍退出复位;释放到一半又收到复位,是否重新等待完整链长;复位输出刚拉低的那个沿,业务计数器到底能不能开始工作。

此前的《FPGA 复位设计:同步复位 vs 异步复位,异步复位同步释放最佳实践》讨论了复位类型与两级模板。本篇围绕上面三个边界做一组独立的 RTL 自检实验:两个周期不同的目的时钟,各自实例化复位释放链,再用边沿计数参考模型、消费端模型和变异反例检查行为。

1 依赖、命令和验证边界

正文给出完整reset_release.v与tb_reset_release.sv。依赖为 Vivado Simulator 的xvlog、xelab、xsim;在已配置 Vivado 环境的终端执行:

xvlog --sv reset_release.v tb_reset_release.sv xelab tb_reset_release -s reset_snapshot -debug typical xsim reset_snapshot -runall

--sv必须保留:设计主体使用 Verilog 风格,仿真保护使用$fatal,测试平台使用 SystemVerilog。这里没有实例化器件原语或 XPM 宏,因此这份行为测试不需要额外编译 XPM 库。三阶段流程见 UG900 2022.1 批处理仿真。

配套run_fpga_tests.py使用 Python 3.8 及以上和标准库,按用例分别保存源码、三阶段日志、源文件 SHA-256 和summary.json。工具在 PATH 中时运行python run_fpga_tests.py --out results,否则用--vivado-bin指定安装目录;输出目录必须是新目录。

2026 年 10 月 16 日,使用 Windows 11(构建 26200)、Python 3.14.5、Vivado Simulator 2022.1 64 位,XSim SW Build 3526262 完成实测。实际工具位于D:/tools/Xilinx/Vivado/2022.1/bin。本篇主回归的 1 个正例、5 个变异反例、3 个参数反例全部符合各自预期,两个时钟域各覆盖 2、3、4 级释放链。

TRACE stopped_clock a_rst=000 b_rst=111 CASE_PASS reset domain=0 stages=2,3,4 edges=84 assertions=7 CASE_PASS reset domain=1 stages=2,3,4 edges=52 assertions=7 TEST_PASS reset_release six configurations, two domains, stopped clock

这里assertions=7是外部复位 arst 的置位次数,不是断言检查数量;edges=84/52是各域观察到的时钟上升沿数。A 域在 B 停钟时已经撤销复位,B 域仍保持三个输出为 1,与 TRACE 一致。独立补充 TB 又枚举 STAGES=2…8,通过 1099 次输出检查和 9 轮重入检查,完整代码见后文。

本次最终双模块套件重跑 17/17 项符合预期;全部功能反例与参数反例都先成功编译、展开,再命中预定 Fatal。没有进行综合、布局布线、恢复/移除时序分析、MTBF 计算或硬件测试,不能把通过记录当成真实亚稳态或物理脉宽已经验证。

2 先写清这次实验的契约

arst和rst都是高有效。arst=1时,整条链异步置 1,不必等待目的时钟;arst=0后,每个目的时钟上升沿向链中移入一个 0。理想 RTL 中,第 STAGES 个上升沿之后,输出rst才变成 0。

STAGES 在本例限制为 2 到 8,默认取 3。这个范围是本文接口的约定,不是对所有器件、频率和可靠性目标推荐同一个级数。正文主测试覆盖 2、3、4 级,补充测试枚举 2 到 8 级,非法参数测试覆盖 0、1、9;这些用例也不等于穷举所有时钟、输入和物理工作条件。

同一个外部复位可以送进多个链的arst,但每个输出只服务自己的目的时钟域。某个域的时钟不再产生上升沿时,该域不能继续完成同步释放。另一个域仍按自己的时钟运行,两者没有“同一物理时刻一起退出”的保证。

这里也校正基础篇中容易产生误解的选型表述:AMD UG949 在需要复位时推荐优先考虑同步复位;不能一概认为同步复位必然多耗 LUT。确需异步复位时,应同步其撤销并检查恢复、移除时序。同步链降低亚稳态向后传播的风险,不能承诺绝对没有亚稳态。本实验只观察该结构的数字行为。UG949 2023.1:Synchronous Reset vs. Asynchronous Reset

3 三个值得单独检查的边界

3.1 链长为 3,不等于业务第三拍就工作

令pipe[2]为输出,复位置位后pipe=111。外部复位在两个沿之间撤销,接下来的状态如下:

目的时钟沿沿后 pipe沿后 rst示例业务计数器沿后值
E111010
E210010
E300000
E400001

业务计数器在 E3 读取到的是沿前rst=1,因此该沿仍执行复位分支。输出在 E3 的 NBA 更新后撤销,业务在 E4 才首次更新。不能用“波形上 rst 在 E3 变为 0”推断同一个沿里所有下游都已运行。

3.2 释放中重新置位,要重新装满整条链

若已经移了两拍,pipe=100,此时arst再次变为 1,链必须重新变成111。下一次撤销后重新数 E1、E2、E3。保留之前的释放进度可能让第二次复位过早撤销。

测试平台安排了短复位脉冲和释放中途的再次置位。这只是在数字仿真里检查“新的复位事件是否重新开始链的释放”;短脉冲在实际器件上能否被可靠识别,要查看器件数据手册中的异步置位/复位脉宽要求,不能由几纳秒的 RTL 激励直接得出结论。官方异步复位同步宏同样要求按目标器件检查最小输入脉宽。UG953 2022.1:XPM_CDC_ASYNC_RST

3.3 一个域停钟,另一个域继续释放

实验中 A 时钟周期为 10 ns,B 时钟周期为 14 ns。暂停 B 后再次产生外部复位,A 的释放链继续移零,B 则一直保持输出复位。恢复 B 后,它要等待自己的 STAGES 个有效上升沿。

如果系统必须等所有域准备好才允许交易,应另外设计就绪握手和跨域传递。把 A 域的rst直接拿去释放 B 域,或者假定两个域都会同时退出,都不满足本实验的独立释放契约。

4 完整 RTL

保存为reset_release.v。移位使用pipe << 1,避免在非法 STAGES 下先产生负下标切片,使参数保护能够以清楚的错误信息终止仿真。

`timescale 1ns/1ps // Active-high asynchronous assertion, destination-clock synchronous release. // One instance is required per destination clock domain. module reset_release #( parameter integer STAGES = 3 ) ( input wire clk, input wire arst, output wire rst ); // Keep invalid configurations syntactically legal so the guard can report. localparam integer S = (STAGES < 1) ? 1 : STAGES; (* ASYNC_REG = "TRUE", SHREG_EXTRACT = "NO" *) reg [S-1:0] pipe; // synthesis translate_off initial begin if ((STAGES < 2) || (STAGES > 8)) $fatal(1,"PARAM_GUARD: reset_release requires 2 <= STAGES <= 8, STAGES=%0d",STAGES); end // synthesis translate_on always @(posedge clk or posedge arst) begin if (arst) pipe <= {S{1'b1}}; else pipe <= pipe << 1; // shift zero toward the output stage end assign rst = pipe[S-1]; endmodule

ASYNC_REG是面向实现工具的同步链属性;AMD 文档说明它会影响优化与放置,帮助保留并靠近放置同步寄存器。把属性写进 RTL 不等于本文已经证明综合工具识别了该链,更不等于可靠性自动达标。UG912 2022.1:ASYNC_REG

这里同时给出SHREG_EXTRACT="NO",表达保持寄存器链的实现意图。目标器件工程中应检查综合后的结构、属性是否保留以及相关报告。本文没有给出通用的set_false_path模板:把复位相关路径全部屏蔽,可能把需要审查的恢复、移除或域内释放路径一起藏起来。

若实际工程使用 AMD 平台提供的XPM_CDC_ASYNC_RST,应按对应版本和目标架构的参数说明集成。本文的小模块便于解释、变异和测试,并未宣称与厂商宏在初始化选项、实现属性或全部器件上的行为完全等价。

5 完整自检 TB:不把移位代码再抄一遍当参考答案

保存为tb_reset_release.sv。每个reset_case使用“外部复位撤销后经过了多少目的时钟沿”作为参考状态,达到 STAGES 才期望撤销输出。同时放一个真正消费rst的业务计数器,检查第一个可工作沿。

检查分为三层:

  • 每个目的上升沿和外部置位之后,比对rst和业务计数器
  • 每次rst下降时,检查它发生在该域的上升沿时刻
  • 停钟窗口结束前,明确检查 A 已退出、B 仍保持复位

异步激励避开两个时钟的上升沿,检查延迟#0.2用于等待 NBA 更新完成。这样测试的是明确的接口行为,而不是用同一仿真时刻的调度竞争制造“偶发现象”。

`timescale 1ns/1ps module reset_case #(parameter integer STAGES=3, DOMAIN=0) ( input wire clk, input wire arst ); wire rst; integer released_edges=0, edges=0, assertions=0; integer work_count=0, expected_work=0; reg expected_rst=1; reg initialized=0; reg previous_expected_rst=1; realtime last_rise=-1; reset_release #(.STAGES(STAGES)) dut(.*); // Example consumer: remains reset on the edge that releases rst. always @(posedge clk or posedge rst) begin if (rst) work_count <= 0; else work_count <= work_count+1; end // Reference model counts destination edges instead of copying the shift RTL. always @(posedge clk or posedge arst) begin if (arst) begin released_edges=0; expected_rst=1; expected_work=0; initialized=1; end else if (initialized) begin previous_expected_rst=expected_rst; released_edges=released_edges+1; expected_rst=(released_edges < STAGES); if (previous_expected_rst) expected_work=0; else expected_work=expected_work+1; end #0.2; if (initialized && rst !== expected_rst) $fatal(1,"RESET_ASSERT domain=%0d stages=%0d t=%0t edges_since_release=%0d actual=%b expected=%b",DOMAIN,STAGES,$time,released_edges,rst,expected_rst); if (initialized && work_count !== expected_work) $fatal(1,"RESET_CONSUMER_ASSERT domain=%0d stages=%0d t=%0t actual=%0d expected=%0d",DOMAIN,STAGES,$time,work_count,expected_work); end always @(posedge clk) begin last_rise=$realtime; edges=edges+1; end always @(posedge arst) assertions=assertions+1; always @(negedge rst) begin if (initialized && ($realtime != last_rise)) $fatal(1,"RESET_EDGE_ASSERT domain=%0d stages=%0d t=%0t",DOMAIN,STAGES,$time); end endmodule module tb_reset_release; reg clk_a=0, clk_b=0, enable_b=1, arst=0; always #5 clk_a=~clk_a; // Testbench-only stopped clock. This is not synthesizable clock-gating RTL. always begin #7; if (enable_b) clk_b=~clk_b; else clk_b=0; end reset_case #(.STAGES(2),.DOMAIN(0)) a2(clk_a,arst); reset_case #(.STAGES(3),.DOMAIN(0)) a3(clk_a,arst); reset_case #(.STAGES(4),.DOMAIN(0)) a4(clk_a,arst); reset_case #(.STAGES(2),.DOMAIN(1)) b2(clk_b,arst); reset_case #(.STAGES(3),.DOMAIN(1)) b3(clk_b,arst); reset_case #(.STAGES(4),.DOMAIN(1)) b4(clk_b,arst); initial begin // All asynchronous transitions avoid coinciding with positive edges. #2.3 arst=1; #21 arst=0; #90 arst=1; #4 arst=0; // assertion shorter than either period, visible to RTL #11 arst=1; // reassert before all chains have completed release #6 arst=0; #100; @(negedge clk_b); enable_b=0; #2.3 arst=1; #4 arst=0; #100; if ({b2.rst,b3.rst,b4.rst} !== 3'b111) $fatal(1,"RESET_STOP_ASSERT stopped domain released reset"); if ({a2.rst,a3.rst,a4.rst} !== 3'b000) $fatal(1,"RESET_INDEPENDENCE_ASSERT running domain stayed in reset"); $display("TRACE stopped_clock a_rst=%b%b%b b_rst=%b%b%b",a2.rst,a3.rst,a4.rst,b2.rst,b3.rst,b4.rst); enable_b=1; #150; if ({a2.rst,a3.rst,a4.rst,b2.rst,b3.rst,b4.rst} !== 6'b000000) $fatal(1,"RESET_RESTART_ASSERT"); // Repeated warm resets at several phases relative to the two clocks. #2 arst=1; #9 arst=0; #86 arst=1; #3 arst=0; #93 arst=1; #8 arst=0; #140; $display("CASE_PASS reset domain=0 stages=2,3,4 edges=%0d assertions=%0d",a2.edges,a2.assertions); $display("CASE_PASS reset domain=1 stages=2,3,4 edges=%0d assertions=%0d",b2.edges,b2.assertions); $display("TEST_PASS reset_release six configurations, two domains, stopped clock"); $finish; end initial begin #3000; $fatal(1,"RESET_TIMEOUT_ASSERT"); end endmodule

TB 中暂停 B 时钟的enable_b只是激励生成方法,不是给硬件设计推荐的门控时钟写法。时钟仍在高电平时随意切断也不是这个实验要考察的行为,因此测试先等待下降沿再暂停。

last_rise记录仿真时间,negedge rst检查释放事件与目的时钟沿重合;这只能检查 RTL 事件调度。真实器件的 clock-to-Q、复位布线延迟、时钟偏斜不会以这个理想事件模型完整呈现。

6 五个单缺陷变体和三种非法参数

每个反例都从原版 RTL 新复制一份,只修改一处,再运行同一 TB:

变体修改希望捕获的错误
reset_sync_assert从敏感表删除posedge arst置位也要等时钟,停钟域不能及时复位
reset_early输出从pipe[S-1]改接pipe[S-2]释放少等待一拍
reset_wrong_directionpipe << 1改为pipe >> 1最高位在第一拍就移入 0
reset_bypass输出直接接arst外部撤销直接穿透,不再按目的沿释放
reset_no_release移位改为pipe <= pipe外部撤销后永不释放

实际首个失败点如下:

变体XSim 实际停止时间失败状态
reset_sync_assert2.5 nsB域4级,首次异步置位后仍为X,期望1
reset_early25.2 nsA域2级,仅经过1个释放沿就输出0
reset_wrong_direction25.2 nsA域4级,第一释放沿后输出0
reset_bypass23.3 nsB域4级,输出跟随外部撤销,在非目的上升沿释放
reset_no_release35.2 nsA域2级,已满2个释放沿仍输出1

表内时间取自日志的Time:行,而不是错误消息里由$time打印的取整字段。例如第一项消息中t=3000,紧随其后的精确停止时间为Time: 2500 ps。本 TB 的检查还包含#0.2,因此25 ns上升沿对应的失败检查时刻为25.2 ns。

第一项在首次异步置位时就被发现,没有运行到停钟段;这仍是对被破坏契约的有效检出。反例事实按首个断言记录,不能为了叙述方便改写成更靠后的失败波形。

参数反例分别实例化 STAGES=0、1、9,实际均在 0 ps 命中PARAM_GUARD。这三个值仍能形成合法的声明宽度,避免把“语法错误”误算成“参数保护正常”。

单独复现参数保护,可使用下面顶层,依次替换 STAGES 的值,并把xelab的顶层改成tb_guard:

`timescale 1ns/1ps module tb_guard; wire rst; reset_release #(.STAGES(1)) dut( .clk(1'b0), .arst(1'b0), .rst(rst) ); initial begin #20; $display("GUARD_MISSING"); $finish; end endmodule

参数保护被综合忽略,因此仍要在工程入口检查参数是否合法。负例只有在编译、展开成功,并命中预定的功能断言或参数保护,且没有TEST_PASS时才算符合预期;工具启动失败或语法错误不算检出功能缺陷。本次 XSim 对故意失败的仿真也返回0,实际判断同时要求对应Fatal与没有TEST_PASS,不能只读退出码。

7 补充复核:枚举合法级数与各个重入阶段

另一个 TB 不使用自由运行时钟,主动发出每个目的沿,枚举 STAGES=2…8。先在完全无时钟时检查置位和保持,再在0…8个释放沿之后重新置位,检查必须重新开始等待。保存为tb_reset_review.sv:

xvlog --sv reset_release.v tb_reset_review.sv xelab tb_reset_review -s reset_review_snapshot -debug typical xsim reset_review_snapshot -runall
`timescale 1ns/1ps module tb_reset_review; logic clk=0,arst=0; wire [8:2] rst; integer checks=0,release_edges=0,rounds=0; for(genvar s=2;s<=8;s++)begin:g reset_release #(.STAGES(s)) dut(.clk(clk),.arst(arst),.rst(rst[s])); end task automatic check(input integer elapsed_i); for(int s=2;s<=8;s++)begin checks++; if(rst[s] !== (elapsed_i<s)) $fatal(1,"REVIEW_RESET_ASSERT stages=%0d elapsed=%0d got=%b",s,elapsed_i,rst[s]); end endtask task automatic tick(input integer elapsed_i); clk=1;#1;check(elapsed_i);clk=0;#1; endtask initial begin // Assertion without a running clock; deassertion alone cannot release. #1;arst=1;#1;check(0); tick(0);tick(0); arst=0;#20;check(0); for(int k=1;k<=9;k++)tick(k); // Reassert at every partial-release boundary and require a fresh chain. for(int phase_i=0;phase_i<=8;phase_i++)begin arst=1;#1;check(0);arst=0;#1; for(int k=1;k<=phase_i;k++)tick(k); #0.1;arst=1;#0.1;check(0);arst=0;#10;check(0); for(int k=1;k<=9;k++)tick(k); rounds++; end $display("TEST_PASS independent reset boundary review stages=2..8 checks=%0d reassert_rounds=%0d",checks,rounds); $finish; end initial begin #5000;$fatal(1,"REVIEW_TIMEOUT_RESET");end endmodule

实际输出:

TEST_PASS independent reset boundary review stages=2..8 checks=1099 reassert_rounds=9

其中0.1 ns脉冲只用于数字事件测试,不能作为目标器件允许同样物理脉宽的证据。本文讨论的STAGES范围也只是自定义模块的参数契约,不表示更长同步链在硬件上普遍非法。

8 波形检查:把两个域放在同一个窗口

在已经成功展开的目录执行xsim reset_snapshot -gui。将顶层arst、clk_a、clk_b、enable_b,以及a3/dut/pipe、a3/rst、a3/work_count、b3/dut/pipe、b3/rst、b3/work_count加到波形中。

  1. 先看第一次外部复位的撤销:两个域各自从第一有效沿开始数,不要求它们在同一时刻拉低rst。
  2. 放大 STAGES=3 的释放沿:pipe 由 100 变 000 时,work_count 仍为 0,下一目的沿才为 1。
  3. 查看释放中再次拉高arst:两条链应重新装入全 1,业务计数归零。
  4. 定位enable_b=0的区间:B 时钟停止,B 的复位维持;A 仍能移零并开始工作。
  5. 恢复 B 后只数 B 的上升沿,确认它完成自己的链长后才撤销复位。

不要以“波形里没有 X”判断已经验证了亚稳态。这里没有模拟触发器的模拟态持续时间,也没有建立器件相关的恢复、移除违例模型。即使所有断言通过,也只能说明这些数字激励下的行为符合契约;硬件中的释放延迟还可能受同步过程影响,不能把理想 RTL 的精确拍数当作模拟可靠性保证。

9 把回归结果接到真实工程还缺什么

实际接入时,应确认外部复位来源和最小脉宽、目的时钟何时稳定、每个复位输出服务哪些寄存器、同步链属性是否被识别,以及跨域数据在启动期间有没有协议约束。再检查实现后的复位相关时序和器件适用的可靠性报告。

多时钟系统尤其要把“本域已退出复位”和“系统可以开始收发数据”分开定义。如果 B 域时钟一直没有恢复,你的 A 域应该继续工作并等待 B 的就绪握手,还是整个系统都保持禁止交易?这个选择应该进入顶层协议和回归检查,而不能只由两条复位链的波形代替。

延伸阅读:《FPGA 复位设计:同步复位 vs 异步复位,异步复位同步释放最佳实践》。你的系统是否已把各域就绪握手加入启动协议?

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 3:38:32

百考通AI实测:论文降重与AIGC优化的完整流程与避坑指南

前段时间&#xff0c;一位准备投核心期刊的朋友跟我抱怨&#xff1a;初稿查重率并不算高&#xff0c;但审稿人反馈语言风格平淡、结构松散&#xff0c;有些段落读起来像机器拼出来的。另一边&#xff0c;刚上研究生的A同学也来咨询&#xff1a;学校鼓励用AI辅助写作&#xff0c…

作者头像 李华
网站建设 2026/10/18 3:37:41

C++基础三件事:初始化、输入输出与const详解

C每天会收到无数句"凭什么"。凭什么局部变量不初始化就可能是垃圾值&#xff1f;凭什么输入一个字母程序就死循环&#xff1f;凭什么一个const变量碰都不能碰&#xff1f;这些问题的答案&#xff0c;其实都藏在C的"性格"里。我一直觉得&#xff0c;学C基础…

作者头像 李华
网站建设 2026/10/18 3:37:33

考试云全流程解析:在线考试系统组卷、防作弊与成绩分析实战

如果你组织过考试&#xff0c;大概率体会过那种被流程支配的恐惧&#xff1a;出题、排版、打印、封卷、排队领卷、监考、收卷、阅卷、登分、复核……一套流程下来&#xff0c;真正用来“考察能力”的时间反而没多少。我这些年参与过学校期末考、企业招聘笔试和技术认证考试的组…

作者头像 李华
网站建设 2026/10/18 3:37:30

单例模式五种实现详解:从线程安全到防反射,附Android避坑指南

简介&#xff1a;这是一份面向Android开发者的设计模式学习资料&#xff0c;以单例模式为专题展开实例详解。资源从单例模式的概念、定义与适用场景入手&#xff0c;重点剖析饿汉模式、懒汉模式、DCL双重检查锁定等常见实现方式&#xff0c;并结合Java内存模型说明线程安全、同…

作者头像 李华
网站建设 2026/10/18 3:36:52

网络理论面试题集:从协议原理到内核源码的思维诊断图

1. 这不是题库&#xff0c;而是一张网络理论的“能力诊断图”“网络理论面试题集”这六个字&#xff0c;乍看平平无奇&#xff0c;像极了简历筛选阶段被随手点开又迅速关闭的PDF。但在我带过十几届某高校通信工程方向实习生、参与过不下二十场某实验室系统架构岗技术面谈后&…

作者头像 李华