ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

FPGA调试从玄学到科学:避坑指南与实战方法论

2026/8/5 1:21:51 拓冰建站 浏览量
FPGA调试从玄学到科学:避坑指南与实战方法论 1. 从“玄学”到“科学”FPGA调试的必经之路如果你在电子工程、通信或者嵌入式系统领域摸爬滚打了一段时间大概率会听到过关于FPGA的“传说”代码仿真一切正常一上板就各种稀奇古怪的问题时序报告明明通过了实际运行却会偶发数据错误甚至有时候仅仅是重新编译一遍之前能跑的功能就“跑飞”了。很多人戏称FPGA调试是“玄学”靠的是经验和运气。但作为一名和FPGA打了十几年交道的工程师我想说所谓的“玄学”背后往往是那些容易被忽略的、系统性的“科学”问题没有解决。今天我就把我这些年踩过的坑、总结的经验系统地梳理成一个“问题汇总”这不仅仅是一个问题列表更是一套从设计到调试的完整方法论。无论你是刚刚接触FPGA的新手还是正在被某个棘手问题困扰的老手希望这份“避坑指南”能帮你把“玄学”变成可分析、可解决的“科学”。FPGA现场可编程门阵列的魅力在于其无与伦比的灵活性和并行处理能力但这也正是其复杂性的来源。与软件调试可以单步执行、随意打印日志不同FPGA的调试更像是在黑盒中观察一个高速运转的精密机械任何一处微小的失衡都可能导致整个系统行为异常。我们遇到的问题大体可以归结为几个核心层面设计规范与代码风格、时序收敛与时钟管理、复位与初始化策略、仿真与实测的鸿沟、以及工具链的使用技巧。接下来我们就沿着一个典型FPGA项目的开发流程逐一拆解这些“坑”及其应对之道。2. 设计源头代码风格与规范埋下的“雷”很多问题在编写代码的那一刻就已经注定了。糟糕的代码风格和设计规范会给后续的时序收敛、调试和维护带来无穷无尽的麻烦。这里说的不仅仅是语法正确与否更是硬件描述语言HDL所特有的“硬件思维”。2.1 不可综合与可综合代码的混淆这是新手最容易踩的坑。Verilog或VHDL语言中有一部分语法是专门为仿真测试Testbench服务的不能被综合工具转换成实际的电路结构。典型问题在需要综合的模块中使用了initial语句对寄存器赋初值、fork/join、wait基于时间的#delay或者使用了force/release等语句。这些代码在仿真时可能工作得很好但综合工具会直接忽略或报错导致实际硬件行为与仿真结果完全不符。解决方案与设计原则严格区分设计文件与测试文件建立清晰的工程目录结构例如/src存放所有可综合的设计模块/tb存放所有的测试平台文件。从物理和逻辑上隔离两者。寄存器的初始化不要用initial。正确的做法是使用复位信号。在代码中明确地使用复位逻辑来初始化寄存器。// 正确做法通过复位逻辑初始化 always (posedge clk or posedge rst) begin if (rst) begin data_reg d0; // 复位时清零 end else begin data_reg data_next; end end避免使用仿真专用结构在/src下的所有文件中自觉避免使用纯仿真语句。如果需要对某些参数进行配置应使用参数parameter或宏定义这些是可以在综合时传递的。实操心得我习惯在代码开头就用注释明确标注模块的用途和是否可综合。例如// SYNTHESIZABLE MODULE - Avoid simulation-only constructs!。同时充分利用Lint工具如SpyGlass、Questa Lint在早期进行代码规则检查它们能高效地揪出这类不可综合的代码。2.2 组合逻辑环路与潜在冒险组合逻辑环路是指一个组合逻辑的输出不经过任何寄存器直接或间接地反馈到自身的输入。这会产生不稳定的振荡是绝对的“设计禁区”。典型问题在描述组合逻辑时由于条件语句if-else,case覆盖不全或者赋值语句的书写疏漏导致在某些输入条件下输出信号无法被确定驱动从而隐含地保持了上一个值形成了隐性的锁存器Latch或反馈环路。// 一个产生Latch的坏例子 always (*) begin if (en) begin q data; end // 当en为0时q没有赋值工具会综合出一个锁存器来保持q的值 end解决方案与设计原则always块中if和case语句必须完整对于组合逻辑的always (*)块确保所有可能的输入分支都有明确的输出赋值。通常的做法是在if-else链的最后加一个else在case语句的最后加一个default。// 正确做法避免Latch always (*) begin if (en) begin q data; end else begin q 1‘b0; // 明确指定en为0时的值 end end警惕组合逻辑反馈检查代码中是否存在assign a b a;这样的语句。使用工具进行综合后检查看报告中有无“combinational loop”警告。实操心得养成“防御性编码”的习惯。对于每个组合always块在写完之后立刻自检是否所有信号都在所有路径下被赋值了对于大型状态机我强烈推荐使用“三段式”写法将下一状态逻辑组合、状态寄存器时序更新、输出逻辑可以是组合或时序严格分开这是避免各种奇怪问题最稳健的风格。2.3 跨时钟域信号处理的严重缺失这是导致系统不稳定、出现偶发错误的头号杀手。当信号从一个时钟域传递到另一个时钟域时如果直接连接由于时钟相位和频率关系不确定接收时钟域可能会采样到信号变化过程中的亚稳态导致后续电路功能完全错乱。典型问题将模块A时钟clk_a中的一个标志信号flag直接连接到模块B时钟clk_b中使用。在clk_b的上升沿采样flag时flag可能刚好在变化采样值可能是0、1或者一个非0非1的亚稳态值这个亚稳态会在后续电路中像病毒一样传播。解决方案与设计原则单比特信号使用两级同步器。这是最基本且必须的。// 在clk_b时钟域中对来自clk_a的脉冲信号pulse_a进行同步 reg [1:0] sync_reg; always (posedge clk_b or posedge rst) begin if (rst) begin sync_reg 2b00; end else begin sync_reg {sync_reg[0], pulse_a}; // 打两拍 end end assign pulse_b_synced sync_reg[1]; // 同步后的信号注意这只能同步脉冲或电平信号且对发送频率有要求脉冲宽度必须大于接收时钟周期。对于连续变化的信号需要更复杂的机制。多比特数据总线使用异步FIFO或握手协议。绝对不能对多个比特分别打两拍因为每个比特的延迟可能不同会导致采样到的数据整体错位。异步FIFO是解决此问题的标准方案它使用双端口RAM和格雷码计数器来安全地传递数据。使用工具辅助检查综合和布局布线工具如Vivado、Quartus通常都有跨时钟域检查CDC报告功能。务必仔细查看并解决所有CDC违规。实操心得在设计架构初期就要明确划分时钟域并规划好跨时钟域通信的接口。我通常会为每个跨时钟域接口单独封装一个小的同步模块如sync_pulse,async_fifo并在项目中被复用。记住一个黄金法则只要信号跨越了时钟域就必须进行同步处理无一例外。3. 时序之殇建立时间与保持时间的博弈时序收敛是FPGA设计的核心挑战。你的逻辑功能再正确如果时序不满足在高速时钟下运行必然出错。这背后是数字电路最基础的建立时间Setup Time和保持时间Hold Time约束。3.1 如何正确理解时序报告很多人看到时序报告里一大堆红色违例就头疼或者看到绿色通过就以为万事大吉。其实你需要学会“读懂”报告。典型问题忽略最差路径工具报告的时序裕量Slack是“最差路径”的裕量。一条路径违例意味着系统在该时钟频率下不可靠。不能因为平均裕量很大就掉以轻心。看不懂路径分析报告中的路径起点Launch Flip-Flop和终点Capture Flip-Flop以及中间的组合逻辑延迟、时钟偏斜Clock Skew、时钟不确定性Clock Uncertainty共同决定了最终裕量。解决方案与设计原则从关键路径入手时序报告通常会按裕量从差到好排序。找到最差的几条路径分析其逻辑层级是否过多。一条路径的组合逻辑延迟过大是导致建立时间违例的主因。检查时钟约束是否完整正确这是前提。你必须为所有时钟包括生成的衍生时钟创建正确的约束.xdc或.sdc文件定义其频率、占空比和相互关系。如果约束不对时序分析就失去了基准。分析路径细节以Vivado为例点击某条违例路径可以查看图形化视图。关注逻辑层级Logic Levels是否超过10级甚至更多考虑插入流水线寄存器Pipeline Register来切割长路径。高扇出网络High Fanout Net一个信号驱动了成百上千个负载会导致布线延迟急剧增加。考虑使用寄存器复制Register Duplication或BUFG全局时钟缓冲器但通常只用于时钟来优化。布线延迟Route Delay是否异常高这可能是因为布局布线结果不理想或者物理位置约束不合理。实操心得我习惯在第一次实现设计后不急于下载测试而是花大量时间阅读时序报告。对于关键模块我会预先进行“流水线化”设计这是用寄存器面积换取时序裕度最有效的方法。例如一个复杂的32位加法器链可以在中间插入一级寄存器将计算分成两个周期完成。3.2 时钟管理不仅仅是频率时钟是FPGA的脉搏时钟网络的质量直接决定系统的稳定上限。典型问题时钟抖动与噪声使用普通的IO引脚输入时钟或者内部逻辑产生的门控时钟容易引入抖动恶化时序裕量。时钟偏斜时钟到达不同寄存器的时间存在差异。虽然工具会尽力优化但大型设计中仍不可避免。衍生时钟约束错误使用PLL/MMCM生成的时钟其约束关系如相位、同源必须正确定义否则时序分析会出错。解决方案与设计原则优先使用专用时钟引脚和全局时钟网络FPGA有专用的时钟输入引脚如MRCC, SRCC和低歪斜的全局时钟树BUFG。外部时钟必须从专用引脚进入并通过BUFG驱动。谨慎使用门控时钟在FPGA中应尽量避免用组合逻辑去使能时钟如assign gated_clk clk en;。这会导致时钟质量下降增加静态时序分析的复杂性。正确的做法是使用时钟使能Clock Enable信号。// 推荐使用时钟使能 always (posedge clk) begin if (rst) begin data_reg d0; end else if (clk_en) begin // clk_en是同步使能信号 data_reg data_next; end end精确约束生成时钟对于PLL/MMCM的输出使用create_generated_clock命令进行约束并明确定义其与源时钟的关系。关注时钟交互对于异步时钟域要设置set_clock_groups -asynchronous对于有固定相位关系的同步时钟要设置set_clock_groups -exclusive或定义时钟延迟。实操心得在设计初期就规划好时钟架构图。我通常会画一个简单的框图标明所有时钟的来源晶振、Serdes恢复、内部产生、频率、以及它们之间的域关系。这份文档会和约束文件一起维护。对于高速设计200MHz我会特别关注电源完整性因为电源噪声会直接转化为时钟抖动必要时需要对时钟电源进行额外的滤波处理。4. 复位策略全局与局部的权衡复位电路保证了系统从一个已知的确定状态开始运行。但一个不恰当的复位设计本身就会成为问题的来源。4.1 异步复位与同步释放这是FPGA设计中最经典、最推荐的复位设计模式。它结合了异步复位的及时性和同步释放的安全性。典型问题直接使用异步复位信号always (posedge clk or posedge rst)并在复位撤销时让rst信号相对于clk异步变化。这可能导致复位撤离时间Recovery Time和移除时间Removal Time违例相当于寄存器的另一个时钟端引发亚稳态。不同寄存器脱离复位状态的时间点有微小差异可能导致系统启动逻辑混乱。解决方案与设计原则采用“异步复位同步释放”电路。即复位信号到来时立即生效异步但撤销时必须与时钟边沿同步。// 异步复位、同步释放电路模块 module reset_sync ( input wire clk, input wire rst_async, // 异步输入的复位 output wire rst_sync // 同步释放的复位 ); reg [2:0] reset_sync_reg; always (posedge clk or posedge rst_async) begin if (rst_async) begin reset_sync_reg 3b111; end else begin reset_sync_reg {reset_sync_reg[1:0], 1b0}; end end assign rst_sync reset_sync_reg[2]; // 经过同步后的复位信号 endmodule将这个模块产生的rst_sync分发到各个功能模块作为复位信号。实操心得我通常在项目的顶层实例化一个复位同步模块生成一个全局的同步复位信号。对于某些需要独立复位的模块如某个接口IP可能会为其生成局部的同步复位。务必确保复位信号本身有良好的扇出能力和布线可以将其作为高扇出网络进行约束优化。4.2 复位与初始化的混淆很多初学者期望上电后寄存器能有一个默认值于是想用initial或者不规范的复位逻辑这会导致仿真和实测不一致。解决方案与设计原则FPGA上电后的状态大多数FPGA的触发器在上电配置完成后其初始状态是不确定的虽然某些型号或设置可以定义初始值但不可依赖。因此必须通过外部输入的复位信号执行一个明确的复位序列将系统带入确定状态。复位序列的设计复位不应是简单的“一拉一放”。复杂的系统可能需要一个有序的复位序列先复位全局控制器和时钟模块等待时钟稳定如PLL锁定再释放其他模块的复位。这个序列通常由一个“复位管理”状态机来控制。实操心得我习惯设计一个power_on_reset模块它检测外部稳定的电源和参考时钟在稳定后产生一个足够长的脉冲例如持续1ms的低电平作为整个系统的“异步复位源”。这个脉冲再送入前述的“同步释放”电路产生最终的全局复位。这样可以确保系统在恶劣的上电环境下也能可靠启动。5. 仿真与现实的鸿沟为什么我的仿真通过了板子却不行这是最令人沮丧的情况。仿真环境是理想的而实际硬件充满了非理想特性。5.1 Testbench的覆盖度不足你的Testbench可能只测试了“Happy Path”理想路径而忽略了边界情况、极端条件和异步事件。典型问题输入激励过于简单没有模拟真实的信号抖动、毛刺和异步关系。没有验证复位和初始化过程。对于高速接口没有模拟数据的背压Backpressure机制。没有进行足够的随机化测试。解决方案与设计原则采用基于断言的验证ABV在Testbench中插入断言Assertion实时检查设计是否违反了某些协议规则或内部不变性条件。这能在仿真中主动发现问题。使用随机化约束测试编写带约束的随机测试向量让仿真器自动生成大量测试场景覆盖更多的状态空间。模拟真实环境为DDR、以太网等接口的Testbench加入模型模拟实际的内存响应或网络延迟。为输入信号加入合理的高斯抖动。进行门级仿真在布局布线后将工具生成的包含实际延迟信息的网表.vo或.vho文件反标回仿真器进行门级仿真。这是最接近真实硬件的仿真能发现时序违例导致的功能错误但速度极慢通常只用于最关键的路径。实操心得我坚持“测试驱动开发”的思路。在写RTL代码之前先构思好Testbench的框架和关键测试用例。一个复杂的模块其Testbench代码量常常是RTL代码的3-5倍。我会专门编写一些“错误注入”测试例如随机拉低复位、模拟数据错误等来验证系统的鲁棒性。5.2 板级与信号完整性问题这是仿真完全无法触及的领域。典型问题电源噪声开关电源的纹波、负载瞬变导致电源轨波动影响FPGA内核和IO的稳定性。信号反射与串扰高速信号在PCB走线上因阻抗不匹配产生反射或与相邻信号线耦合产生串扰导致接收端波形畸变产生误码。同步开关输出噪声大量IO引脚同时翻转如数据总线会导致瞬间的大电流引起地弹和电源塌陷。未使用的IO引脚未处理浮空的IO引脚可能随机振荡增加功耗和噪声甚至引发闩锁效应。解决方案与设计原则电源设计为FPGA的各个电源轨VCCINT, VCCAUX, VCCO等提供充足、干净的电源。使用低ESR的滤波电容并遵循厂商的电源设计指南。PCB设计对于关键高速信号如时钟、差分对、DDR数据线严格控阻抗、做等长处理、提供完整的参考平面。增加适当的端接电阻串联或并联来抑制反射。IO约束与设置在约束文件中正确设置IO标准如LVCMOS, LVDS、驱动强度、摆率。对于非关键信号可以适当降低驱动强度和摆率以减少噪声。处理未用引脚在约束文件或代码中将所有未使用的IO引脚设置为弱上拉或下拉或者直接设置为三态输出。# 在XDC约束文件中示例 set_property PULLUP true [get_ports {unused_pin*}] set_property IOSTANDARD LVCMOS33 [get_ports {unused_pin*}]使用片上逻辑分析仪当问题出现在板级时仿真和静态分析都无能为力。此时必须借助像Vivado的ILA集成逻辑分析仪或Quartus的SignalTap这样的工具。它们将FPGA的一部分逻辑资源配置成逻辑分析仪可以实时抓取内部信号的波形是连接仿真与现实的“桥梁”。实操心得遇到无法解释的偶发错误我的第一反应是怀疑电源和信号完整性。我会用示波器测量关键电源轨的纹波通常要求50mV用高速探头测量时钟和数据信号的波形质量看眼图。有一次一个千兆以太网接口偶发丢包最终发现是PHY芯片的模拟电源滤波不足增加一个磁珠和电容后就解决了。硬件问题必须用硬件手段来排查。6. 工具链的“坑”与使用技巧EDA工具很强大但并非全自动。错误的使用方式会把你引入歧途。6.1 综合与实现策略的误选综合工具如Vivado Synthesis将RTL转换为门级网表实现工具如Vivado Implementation进行布局布线。不同的策略会产生不同的结果。典型问题始终使用默认的“Run”流程对于复杂设计可能无法时序收敛。不理解“Out-of-Context (OOC)”和“Global”综合模式的区别导致模块接口变化时整个设计重新综合耗时漫长。盲目追求运行速度使用“Quick”模式但该模式优化力度低可能隐藏了真正的时序问题。解决方案与设计原则增量编译对于大型设计当只修改了某个子模块时使用增量编译可以极大地节省时间。这需要合理划分设计层次并为模块设置DONT_TOUCH或等效属性防止工具跨层次优化。多种策略尝试Vivado和Quartus都提供了多种综合与实现策略如“Performance_Explore”, “Area_Optimized_high”等。当默认策略失败时不要灰心尝试其他策略。可以写一个Tcl脚本批量跑几种策略然后选择结果最好的一个。合理使用OOC综合对于稳定的、接口定义清晰的底层模块或IP核可以采用OOC模式单独综合。这样在顶层集成时工具只需使用其网表不再重新综合节省时间并保证性能一致性。分析失败原因如果实现失败时序不收敛不要只看总结报告。要深入分析“时序收敛助手”或“设计收敛分析”报告工具通常会给出具体建议如“优化高扇出网络”、“降低目标频率”等。实操心得我通常会为项目建立一个“策略库”。针对不同的设计阶段初期探索、中期优化、后期收敛我会保存不同的策略配置。例如初期用“Quick”模式快速迭代功能中期用“Performance_Explore”来压榨性能后期用“Congestion_SpreadLogic_high”来优化布线拥堵。同时我会熟练使用Tcl脚本自动化整个流程包括生成报告、解析关键指标如WNS, WHS, 功耗这比点击GUI高效得多。6.2 约束文件的管理与调试约束文件是指挥工具进行优化的“宪法”。约束错误或不完整结果必然错误。典型问题约束文件中有语法错误或冲突导致部分约束未被应用。时钟约束不完整漏掉了某个衍生时钟或虚拟时钟。物理位置约束如管脚分配、模块布局不合理导致布线拥堵和时序恶化。使用了过紧或不切实际的约束如要求200MHz的系统时钟在资源占用95%的设计上达到250MHz。解决方案与设计原则约束检查在运行实现前使用工具的“Validate Constraints”功能检查约束文件。仔细阅读报告确保所有时钟都被识别没有冲突。分而治之不要把所有约束写在一个文件里。按功能拆分clocks.xdc时钟约束、ios.xdc管脚约束、timing.xdc例外约束、physical.xdc物理约束。这样便于管理和调试。理解约束优先级知道set_max_delay/set_min_delay与时钟周期约束的关系知道set_false_path和set_clock_groups的区别。错误的例外约束会掩盖真实问题。迭代优化物理约束不要一开始就加很多物理约束。先让工具自由布局布线看时序报告和拥塞图。如果发现某个区域拥塞严重Congestion 0.8再考虑用PBLOCK或CELL约束将相关模块锁定到该区域或者将互连密集的模块布局得近一些。实操心得我习惯在项目开始时先创建一个最简化的“约束原型”工程只包含时钟输入和几个输出IO。在这个工程里验证我的基础时钟和管脚约束是否正确能否达到预期的频率。确认无误后再将约束文件复制到主工程中。对于复杂的时序例外我会在代码中用(* dont_touch “true” *)或(* async_reg “true” *)等属性进行标注这些属性会引导工具进行正确的优化和检查有时比约束文件更直接有效。7. 调试实战当问题发生时你的排查链路是什么当FPGA设计在板卡上出现异常时一个系统化的排查思路至关重要可以避免像无头苍蝇一样乱试。7.1 建立分层分级的排查思路从宏观到微观从外到内。第一层电源与时钟基础测量所有电源轨用万用表和示波器检查电压是否在允许容差内通常±5%纹波噪声是否过大。检查时钟用示波器测量输入时钟和关键内部时钟如果引出了测试点的频率、幅度、抖动是否正常。确认PLL锁定指示灯如果有是否常亮。检查复位测量全局复位信号确认其上电和按键操作时的波形符合预期同步释放的边沿是否干净。第二层通信接口与外围检查配置完成信号如INIT_B,DONE引脚确保FPGA已成功加载比特流。检查关键通信接口如UART、SPI、I2C。先用最简单的回环测试Loopback验证物理层是否正常。例如将UART的TX和RX短接发送数据看是否能正确接收。检查外部器件确认Flash、DDR、ADC等外围器件的供电、复位和参考时钟是否正常。第三层内部逻辑与信号使用ILA/SignalTap这是最强大的手段。在怀疑出问题的关键路径上插入探针抓取实际运行时的信号波形。重点关注控制信号如使能、有效、复位是否按预期跳变。数据流在关键节点如FIFO的读写接口、状态机跳转条件是否正确。是否存在毛刺或亚稳态现象信号在时钟边沿附近变化。对比仿真与实测波形将ILA抓取的波形与仿真波形在相同激励下进行对比差异点往往就是问题所在。进行边界扫描测试如果怀疑是PCB焊接或连接问题可以使用JTAG边界扫描功能测试FPGA引脚与外围电路的连接性。第四层代码与工具深度检查复查CDC报告确认所有跨时钟域路径都已正确处理。复查时序报告即使报告显示通过也要看最差路径的裕量是否充足建议至少留0.5ns以上的裕量。在高温或低压条件下裕量会变小。代码走查与同事一起重新审视问题模块的代码特别是状态机、计数器、比较器等容易出错的逻辑。实操心得我电脑里永远保存着一个“调试清单”文档。每次调试新问题都按照这个清单一步步走并在每个步骤后面打勾和记录测量结果。这个清单帮助我形成了肌肉记忆也避免了遗漏。例如有一次一个系统随机死机按照清单排查到第二步时发现某个核心电源的纹波在特定负载下超标更换了更大容量的电容后问题消失。没有清单我可能会花几天时间去怀疑软件逻辑。7.2 ILA使用的技巧与陷阱片上逻辑分析仪是救星但使用不当也会带来新问题。典型问题采样深度与时钟的权衡采样深度决定了能捕获多长时间的波形采样时钟决定了时间分辨率。深度越大、时钟越快消耗的Block RAM资源就越多甚至可能导致布局布线困难。触发条件设置过于简单只设置一个简单的边沿触发在复杂问题中可能永远抓不到想要的数据段。探针信号改变布局布线插入ILA核并连接探针后工具会为了布线到这些探针而改变原有的布局可能导致时序变化使得抓到的波形状态与不插ILA时实际运行的状态不一致这就是“探针效应”。解决方案与设计原则精心设计触发条件利用ILA的触发状态机功能。设置多级触发条件例如“当FIFO满信号拉高后再检测到写使能有效则触发”这样可以精准定位到溢出发生的瞬间。使用标记Mark功能在代码中插入虚拟的标记信号如一个计数器或特定模式当ILA捕获到该标记时你就知道代码执行到了哪个特定位置。管理资源与时钟对于高速信号100MHz使用ILA自带的时钟域交叉功能用较低的时钟如系统时钟分频来采样以节省存储深度。合理分配多个ILA核而不是把所有信号塞进一个核。评估“探针效应”对于最关键的时序路径比较插入ILA前后的时序报告。如果裕量变化很大则需要考虑其他调试方法比如使用VIO虚拟IO动态控制内部信号或者将关键信号引出到IO上用示波器观察。实操心得我通常会在设计代码中预留一些“调试信号”端口例如状态机的状态码、错误计数器、FIFO的空满状态等。在顶层模块中这些信号可以连接到ILA也可以连接到物理IO测试点。在开发阶段我会使能ILA在产品固化阶段则可以在综合时用宏定义条件编译掉这些调试逻辑以节省资源。记住调试本身也是设计的一部分需要提前规划。