多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

FPGA信号被优化?PDS在线调试三招保住关键信号

FPGA信号被优化?PDS在线调试三招保住关键信号 1. 现象确认信号到底去哪儿了1.1 问题表现与现场描述用紫光同创 Pango Design SuitePDS做了大半年项目Debug 时最常被问到的就是这句话“我这个信号明明写在了代码里为什么在线逻辑分析仪里找不到”不信你打开工程试一下在 RTL 代码里定义了一个中间信号做了一堆组合逻辑或状态跳转想在调试时把它拉出来看波形。结果在 PDS 的调试核添加信号列表里翻遍整个层次结构都没看到这个名字或者在综合后网表里搜到了但实际拉出来后波形永远是一根直线要么全 0 要么全 1完全没法反映真实逻辑变化。这不是你操作有问题而是综合工具“帮”你优化掉了。FPGA 综合本质上是把 RTL 描述翻译成查找表LUT、触发器FF、RAM 等底层资源的过程这个过程中工具会做大量逻辑等价性变换。优化本身不是坏事——面积变小、时序更好、功耗更低都是它带来的好处。但如果你的中间信号对输出端口没有任何实质影响或者它的层级关系太简单工具就会毫不留情地把它“内联”进后续逻辑甚至直接删除。等你回头想看它的时候它已经不存在了。处理这类问题之前先确认三件事第一你用的是不是足够新的 PDS 版本第二综合报告里有没有相关警告第三你在调试核里拉的是 RTL 信号名还是综合后的网表信号名。很多人在第一点上就栽了跟头——全程在看 RTL 里的名字但 PDS 在线逻辑分析仪能看到的信号大多数情况下来自综合后网表或布局布线后网表名字已经被工具自动加工过比如加了 _reg、_z 之类的后缀甚至整段被拆掉重命名。1.2 为什么综合器要“优化”你的代码把“信号被优化”当作综合器的恶意那就误会它了。综合器其实很像一个严格的文字编辑你交了一篇冗长的手稿它帮你删掉废话、合并重叠段落、调整句式最终目的是让文章在有限的纸张上打印出来既清楚又省纸。硬件设计里这张“纸”就是 FPGA 芯片内部的 LUT、FF、DSP 和 Block RAM每一种资源都有限工具必须在逻辑正确的前提下拼命压缩。具体到优化手段“信号被优化”通常来自下面几种情况常量传播Constant Propagation某个信号在所有路径上都被赋了固定值比如if (en) a 1b1; else a 1b1;综合器算完发现 a 永远是 1那它会直接把用到 a 的位置替换成常量 1a 这个信号也就不需要存在了。冗余逻辑消除Redundancy Removal一个信号计算出来的结果对最终输出没有任何贡献等于白算工具自然会删掉。内联Inlining你写了一个中间信号它只被下一级组合逻辑用到一次且位宽不大工具会把它的表达式直接展开到后面省掉一级物理连线。寄存器复制与重定时Retiming / Register Duplication布局布线阶段为了满足时序工具可能会移动寄存器位置、复制寄存器原来那个信号名对应的物理寄存器就不存在了。资源共享Resource Sharing几个运算逻辑结构几乎一样工具共用一个硬件单元中间信号被合并。大部分时候这些优化是积极的。但到了调试阶段你需要的不是省资源而是能“看见”信号。理解了综合器为什么要删你的信号你才能真正理解后面所有“保信号”手段的原理——不是跟工具对抗而是明确告诉工具这个信号是调试观测点禁止按常规流程处理。2. 第一板斧用综合属性把信号“钉住”2.1 两种语言的属性写法与含义在 PDS 工程里最直接的保信号方式是给信号加综合属性Attribute。不同 HDL 语言写法不同我从 Verilog 和 VHDL 两种风格分别说明。Verilog 里常见的是在信号声明前用(* ... *)语法(* keep true *) reg [7:0] mid_signal; (* syn_preserve true *) wire [15:0] data_bus; (* preserve true *) reg [3:0] state_reg;这三个属性看起来差不多实际侧重点有区别keep告诉综合器在优化时保留这条信号对应的逻辑网线不要把它内联到扇出端。这是最常用的“保留网线”属性也能保留中间节点方便调试核探测。syn_preserve更偏向于阻止工具把信号对应的寄存器进行复制、合并或重定时保证你看到的寄存器层级结构基本不变。名字里的 syn 提示它是 Synthesizer 层面的指令对某些寄存器复制问题效果更明显。preserve很多工具链里它是keep的另一种表达两个属性经常能互换使用。保险起见在 PDS 里我会用keep作为首选syn_preserve作为对寄存器的补充加固。VHDL 里写法更啰嗦一些但原理相同signal mid_signal : std_logic_vector(7 downto 0); attribute keep : boolean; attribute keep of mid_signal : signal is true;注意 VHDL 里需要先在 architecture 声明区定义 attribute再给具体信号打标记。如果你用的是 PDS 自带的编辑器输入 attribute 时一般会有自动补全提示直接把 keep 补上就行。2.2 属性到底保的是什么为什么不建议加错位置很多人以为加了keep之后这个信号在综合网表里就一定原封不动、以原名存在。实际没那么简单。keep属性通常保证的是综合器不会把这个信号对应的“连线”优化到完全不存在的程度并且会在综合后的网表里尽量保留这个节点方便后续查看或连接调试逻辑。但如果你加在了错误的信号类型上或者这条路径本身被更上层的优化比如整段逻辑被常量折叠连带处理掉属性也不一定能救回来。我之前遇到过一个典型例子一个组合逻辑assign valid_out (cnt 4d5) start;我给valid_out加了keep但cnt和start一个来自固定寄存器一个来自另一个被保留的信号综合后valid_out虽然在但连进去的路径被简化成了cnt 4d5某个位与 start 的“与”逻辑名字还在逻辑却干净得不像原代码。这种时候如果波形里看到 valid_out 某些时刻和 RTL 仿真不一致不要太惊讶综合器已经帮你做了无数等价变换你看到的是变换后的结果。所以加属性要注意三个点尽量加在信号的“声明处”不要加在赋值语句、端口列表或例化位置避免属性没有被综合器解析。对向量信号可以直接在声明处对整个向量加 keep不需要逐位处理。加了属性后建议立即重新综合再用网表查看器搜索信号名确认它确实还在。不验证就等于没加。另外keep不加在时钟、复位这类高扇出信号上。你如果给时钟树中间节点加 keep它可能阻碍时钟网络的正常结构导致后续布局布线时序变化非常明显甚至引入额外延迟这是得不偿失的。2.3 在 PDS 综合设置里做二次加固除了代码里加属性PDS 综合设置里一般也会有一些选项能影响信号保留的力度。不同版本菜单位置略有不同但思路一致。打开工程后在综合设置Synthesis Settings里通常能看到和 Optimization 相关的选项比如综合优化等级Optimization Effort、寄存器重定时Register Retiming开关、资源共享Resource Sharing开关等。如果你正处在调试阶段不想跟综合器的优化玩捉迷藏我建议临时把优化等级从性能优先Performance调成面积优先或均衡模式同时关掉 Register Retiming。这样处理会让很多原本会被搬移的寄存器留在原处信号保留率大幅提高。缺点是面积和时序可能会变差所以调试完毕记得改回来。这里给一个操作顺序建议先在 RTL 里给需要观测的信号加好keep或syn_preserve。到 PDS 综合设置里暂时调低优化等级、关闭寄存器重定时。重新综合在综合后网表里搜索信号名确认存在。进入布局布线前先接调试核再跑 PnR。我踩过最痛的坑是先跑完布局布线再想加调试信号结果布局布线后网表和综合后网表又不完全一样信号名又变了一轮最后只能回退到综合阶段重新做。所以最好在综合后立刻验证信号不要拖到最后。3. 第二板斧在 PDS 调试核里正确摘取信号3.1 添加调试观测信号的常规流程属性加好了信号保住了接下来要解决“怎么把它拿进调试核”的问题。PDS 的在线调试功能我习惯把它理解成一个片上逻辑分析仪你选好一堆要观测的信号设置触发条件然后下板跑等触发条件满足时FPGA 内部会把这段采样数据存下来上传到 PC 端显示波形。常规添加步骤大概是在 PDS 里新建或打开一个调试核Debug Core / Logic Analyzer IP设置好采样深度、触发通道。在信号选择界面从当前设计层次里拉选需要观测的内部信号。这里看到的多半是综合后网表层次信号名不一定和 RTL 完全一致但加了 keep 后名字通常会很好认。确认信号位宽按需加入触发条件。位宽太宽会吃掉大量存储资源建议只保留必要的位。例化调试核到顶层设计重新综合、布局布线生成 bit 流。下载到板卡运行调试工具设置触发和采集模式跑一次触发看波形。这个流程在 PDS 里几乎是所有在线调试的通用路径。但很多项目卡就卡在第一步的“信号选择”上——打开列表找不到想要的信号或者看到一堆后缀乱七八糟的名字不敢认。所以前面加keep属性才那么重要它是让信号在列表里“有名有姓”出现的前置条件。3.2 信号找不到时的替代抓取方案属性已经加了综合报告里也确认信号存在了但调试核信号列表里还是找不到或者找到了但按下去提示“不可观测”这怎么办这就得换思路了。第一种办法给目标信号额外打一拍再观测。这种方法对已经存在的信号很管用。假设想观测comb_valid可以先在 RTL 里加一个寄存器(* keep true *) reg comb_valid_dbg; always (posedge clk) begin comb_valid_dbg comb_valid; end然后直接观测comb_valid_dbg这个寄存器。寄存器是物理存在的器件综合器很难把它优化掉调试核也更容易识别。代价是你在时序上往后移了一个时钟周期分析波形时心里数着就行。第二种办法给信号加一个“存在性消费者”。所谓存在性消费者就是不影响主逻辑、但会让信号“看起来被使用”的一段无害逻辑。比如把组合信号和一个固定值做按位或结果赋给一个输出端口的高位assign debug_probe {8b0, comb_valid};注意这个debug_probe需要是顶层端口而且不能悬空不连否则综合器照样给你优化掉。你可以把它连到一个空的测试点引脚或者连到板上的 LED。这种方法最直接但占用引脚适合调试板卡测试点充足的情况。第三种办法把内部信号拉到顶层虚拟端口。在顶层声明一个 wire直接 assign 过去但不绑定物理引脚靠keep保命。这个方法在 Xilinx 和 Altera 流程里都常用PDS 里同样可行。需要注意虚拟端口在综合时如果不被使用有时会被工具自动去掉所以要配合 keep 属性一起用。第四种办法降低优化等级重跑综合。前面已经提过不再展开。但要注意这属于“全局性”手段会影响整个工程不只是你想看的那个信号。项目临近交付时尽量别随便降优化等级免得时序出问题。3.3 采样深度、触发条件设置的小建议信号丢不丢是一回事抓不抓得到是另一回事。就算信号已经在调试核里了如果触发方式没设好采样存储也容易出问题。采样深度Sample Depth决定了你能记录多长时间的波形。深度越深能看的窗口越长但占用的 Block RAM 也越多。PDS 里一般给的是 2^N 形式比如 1024、2048、4096。我习惯先用 1024 跑通触发再根据波形窗口需求扩大。尽量不要在调试初期就选最大深度否则一个调试核吃光片上 RAM后面想再加核就没资源了。触发位置Trigger Position也值得花点心思。一般调试核会提供“触发前采样点”和“触发后采样点”的占比设置比如 25% 表示触发点位于采样的前 1/4 位置能看到约 75% 的触发后数据。如果你要抓的是触发后的稳态过程把触发后占比调高如果要看触发前的异常状态把触发前占比调高。触发条件不要设太复杂。一个 16 位信号你设了 AND、OR、不等于等多级组合PDS 里配置时间长下板后触发概率低有时候你根本不确定是不是条件写得有问题。我通常是先抓最简单的、只触发一个信号的上升沿或特定值确认信号确实在工作再逐步加多条件。这是排查信号有没有被优化的最快路径。4. 常见问题与排查技巧实录4.1 问题速查表根据我这段时间在 PDS 里调试的实践把常见问题做成一个速查表方便你对照排查。现象可能原因处理办法调试核信号列表里根本搜不到目标信号综合时信号被内联或删除加 keep 属性后重新综合检查综合警告信号能找到但添加后提示不可观测信号是纯组合逻辑没经过寄存器打拍打一拍生成调试寄存器或用虚拟端口引出波形全是常量 0 或常量 1信号被常量传播优化测试条件没触发对确认原始逻辑是否可变重新设置触发条件布局布线后信号名消失波形乱掉寄存器重定时或复制导致信号名改变综合设置里关闭 Register Retiming重新加属性采样深度不够波形截断深度设置太小或触发位置不合理加大深度调整触发前后占比信号在但波形边沿比 RTL 仿真晚了几拍综合/布局后引入了寄存器链路径这是正常时序差异按周期数换算回去核对这张表基本覆盖了我在调试时遇到的 80% 情况。剩下的 20%多半出在调试核本身配置错误或者多个时钟域问题那就需要专门去看 CDC 了。4.2 如何快速验证信号到底有没有被优化很多人处理“信号被优化”是摸着石头过河加个属性试一次不行再加一个。我更推荐先花五分钟确认问题根源再动手。第一步看综合报告。PDS 综合完成后会输出日志里面经常有关于信号移除、常量折叠、寄存器合并的 Warning 或 Info。搜索“removed”“optimized”“constant”这些关键词大概率能看到目标信号名。看到哪个名字被优化心里就有底了。第二步搜综合后网表。PDS 综合后生成的网表文件比如 .edf、.edif或者工具内部格式直接用文本编辑器打开搜索你信号的名字。如果搜到了说明它还在如果只有_reg之类的重命名版本说明被改名了如果完全搜不到那就是被删了。搜之前建议先把 RTL 里的信号名改成短一点、有辨识度的名字避免重名搜索干扰。第三步看原理图/网表视图。PDS 里通常有查看综合后结构或 Technology Map Viewer 的功能你可以在图形界面里找到你的信号顺着连线看它接了什么。这个方法比搜文本直观但对大型设计可能卡顿建议只在局部层次里查看。很多工程师直接跳过这三步凭感觉加属性结果要么加了没效果要么加了浪费一晚上。调试调试先定位再动手永远比盲目试要快。4.3 我实际工程里的一次完整处理记录举个实际例子。之前做一个数据链路项目FPGA 里有一个 32 位累加器信号acc_value我需要在某个异常状态下抓它的波形看是不是累加逻辑跑飞了。第一次添加调试核时信号列表里完全找不到acc_value综合报告里搜出了类似这样的 Warning“acc_value[31:0] was optimized due to constant propagation”。我大概就知道问题在哪儿了。检查 RTL发现这个累加器的输出在某种配置模式下被一个常量使能信号强制清零综合器就算出来当使能为 0 时 acc_value 必定为 0当使能为 1 时 acc_value 又没有被后续逻辑有效使用所以干脆把这个累加器整体优化掉了。项目里的原因比这个复杂一点但本质差不多。处理办法是两步走第一在acc_value声明处加上(* syn_preserve true *)第二在综合设置里关闭寄存器重定时优化等级从 Performance 调成 Balanced。重新综合后网表里能搜到acc_value这个信号调试核也能正常添加。最后触发抓到的波形和 RTL 仿真完全对得上问题顺利定位。那次之后我养成了一个习惯在写 RTL 的第一时间就把顶层调试用的寄存器信号和 keep 属性提前规划好而不是等出了问题再回头补。信号加 keep 不影响正常逻辑顶多多占一点点资源但调试时省下的时间是以小时计的。4.4 调试完成后的收尾工作信号调试完成波形确认无误别急着高兴还有收尾工作要做。这个步骤很少人提醒但踩过坑的人都知道有多重要。第一把临时加的调试寄存器、虚拟端口、keep 属性清理掉或者集中到一个ifdef DEBUG的宏里统一控制。如果直接保留在正式版本里轻则多占资源重则影响时序收敛到时候回归测试出问题你都不知道是哪来的。第二把综合设置里的优化等级改回默认或性能优先。我之前就犯过糊涂交付前忘了把优化等级调回去结果时序报告差了 2ns排查了大半天才发现是优化等级被调低了。第三把调试过程中确认过的关键信号整理成一个“信号观测清单”记录信号名、所在层次、添加方式、触发条件。下次再调试同一个工程直接照单复用不用重新摸索一遍。第四如果调试核里还留着临时的大量采样配置记得在最终 bit 流生成前把调试核移除或改成最小配置。否则下载到现场板卡后如果误触发了调试逻辑可能影响实际运行。写在最后的小技巧应该说紫光同创 PDS 这些年迭代下来在线调试功能已经比早年好用了不少但“信号被优化”这种问题本质上不是工具单方面的问题——它是综合器优化策略和调试需求之间天然存在的矛盾。你理解了综合器的思路再掌握 keep、syn_preserve、打拍观测这几招基本就能应付九成以上场景。最后分享一个我很常用的小技巧如果你要观测的信号分布在好几个不同的逻辑层次里与其一个个加 keep、一个个去调试核里拉取不如在顶层提前定义一组“调试总线”把需要观察的关键信号都赋到这个总线上再给总线整体加 keep。这样一次综合、一次配置调试核就能把所有关心的信号都抓回来。这个习惯帮我省掉了大量来回综合和配置的时间每次调试时都感觉特别顺手。调试这件事最忌讳的就是“不会先硬调”。多花几分钟验证信号在哪、为什么丢、怎么保后面所有工作都会顺畅很多。
返回列表