
高云GoWin这几年在国产FPGA里的存在感越来越强价格合适供货也比某些海外大厂稳得多中小批量项目切过来的团队不在少数。但换平台这事最折腾人的不是写逻辑而是工程能综合、能布线一上板就各种“不听话”。我在高云GoWin上跑完几个项目之后最大的感悟是时序约束和管脚分配这两块属于典型的高频踩坑区而且坑点跟Vivado、Quartus的习惯差得挺多不能直接用老经验硬套。这篇就把我实际调试过程中整理出来的约束写法、管脚规划思路、以及各种现场翻车记录一次性说清楚。这篇内容主要面向两类人一类是从Xilinx/Intel平台切到高云的开发者另一类是在高云上能把功能仿真跑通、但综合实现后时序不过或IO乱飞的入门选手。看完之后你会知道约束文件到底该怎么写、管脚分配有哪些隐藏规则、以及遇到时序违例时去哪里查、怎么改。1. 工程结构与环境准备先把工具的脾气摸清1.1 高云软件与文件组成高云的开发环境叫GowinSynthesis也就是常说的云源软件。目前主流的1.9.x版本功能已经比较完整支持从综合、布局布线到比特流生成的全流程。工程文件后缀是.gprj顶层设计文件是.v或.vhd此外还有两类非常重要的约束文件.cst和.sdc。很多从Vivado切过来的同事第一次打开高云工程都会愣一下因为Vivado把物理管脚约束和时序约束都丢在一个XDC文件里而高云是分开的。.cst负责管脚分配、IO电平标准、上下拉配置.sdc负责时钟、延迟、伪路径等时序约束。这个区分看起来简单但项目里至少有一半的“为什么我改了管脚没生效”问题都是因为把约束写错了文件。另外云源软件的项目配置里器件型号、封装、速度等级这三个选项一定要核对清楚。高云的芯片命名规则比较直观比如GW1NR-4C和GW2A-18后缀里的数字代表逻辑资源规模封装和速度等级直接影响引脚定义和时序余量选错一个后面对应的约束可能全部要推翻。1.2 从综合到比特流的流程习惯高云的标准流程是综合Synthesis、布局布线Place Route、时序分析Timing Analysis、生成比特流Bitstream。我个人的习惯是每次修改完代码或约束后一定完整跑一遍PR不要只做综合就急着下载。因为综合通过只代表语法和资源没问题管脚冲突、Bank电压不匹配、时序违例这些硬伤全都是在PR阶段才暴露的。还有一个很容易被忽略的点高云的时序分析是在PR之后自动执行的但它不会因为你没写约束就报错而是默认所有路径都不约束然后在报告中给你一堆“No constraint”的路径。这种情况下看到的时序结果其实是“假通过”上板能不能跑全看运气。所以每次PR完我建议先打开时序报告看一眼约束覆盖率再决定要不要下载。2. 时序约束实战让工具按你的真实需求来2.1 SDC语法与高云的实现差异高云的时序约束遵循标准SDC语法核心命令包括create_clock、create_generated_clock、set_input_delay、set_output_delay、set_false_path、set_multicycle_path、set_max_delay等。相比Vivado需要配合复杂的Tcl脚本高云的SDC解析更接近纯文本标准格式写起来反而清爽一些。不过高云对SDC的支持并非100%完整。我踩过的一个实实在在的坑是set_clock_uncertainty在部分版本里对某些时钟网络不生效导致时序报告里看到的结果跟实际分析不一致。所以如果你要用时钟不确定性建议在工程里加一条注释并确认版本支持否则老老实实用默认值就好。基础约束模板一般是这样的create_clock -name clk_50m -period 20.0 [get_ports {clk}] set_input_delay -clock clk_50m -max 5.0 [get_ports {rxd}] set_input_delay -clock clk_50m -min 2.0 [get_ports {rxd}] set_output_delay -clock clk_50m -max 5.0 [get_ports {txd}] set_output_delay -clock clk_50m -min 0.0 [get_ports {txd}]改成你自己的时钟和信号名就能用。关键是理解每一条的物理含义下面展开说。2.2 时钟约束一切时序分析的起点时序分析是围绕时钟展开的。没有时钟约束工具就不知道数据路径的起点和终点在哪里自然也就无从判断是否满足建立时间和保持时间。主时钟约束用create_clock。比如板子上有一颗50MHz的有源晶振接到FPGA的全局时钟引脚约束就是上面写的那句create_clock -name clk_50m -period 20.0 [get_ports {clk}]period单位是纳秒20ns对应50MHz。名字可以自己取但建议跟信号名保持一致后面写input/output delay时要用。高云对PLL输出时钟的处理比较友好综合后工具能自动识别PLL的CLKOUT并生成内部时钟约束不需要手动写create_generated_clock。但有一个前提你的PLL输入时钟必须先有明确的create_clock约束否则工具不知道PLL参考时钟从哪里来自动生成也就无从谈起。我遇到过一个比较隐蔽的问题工程里有两级PLL级联第二级PLL的输入是第一个PLL的输出。高云工具对这种情况的自动约束偶尔会漏导致第二级PLL输出时钟没有约束时序报告里出现大量未约束路径。解决办法是手动补上生成时钟约束create_generated_clock -name pll1_clk -source [get_pins {pll1/CLKOUT}] -divide_by 1 [get_pins {pll1_out}]具体pin名需要根据你的PLL例化名称去“时钟向导”报告里查但思路就是这样手动指定源时钟和输出时钟之间的关系。时钟网络还有一个容易忽略的资源问题高云芯片的全局时钟网络Global Clock Network数量有限。如果设计中用时钟管理器生成了很多个不同频率的时钟而且每个都进了全局时钟网络PR阶段就会出现时钟资源不足的报错。我的一般做法是频率高、扇出大的时钟才用全局网络低速率模块的时钟可以直接走普通布线资源通过约束或综合属性控制。2.3 输入输出延迟约束别让工具猜很多入门用户写完create_clock就以为约束结束了其实这只是第一步。对于外部输入信号工具不知道它相对于时钟是什么时候有效的需要你用set_input_delay告诉它对于输出到外部的信号也需要set_output_delay说明外部器件对数据到达时间的要求。input delay的物理含义是外部信号相对时钟沿提前或滞后多久到达FPGA引脚。如果上游是单片机或另一个FPGA这个延迟大致等于上游芯片的Tco时钟到输出延迟加上PCB走线延迟。如果上游是异步信号比如UART的RX数据跟你的系统时钟其实没有固定相位关系这时候就有两种处理路径。一种做法是把这个信号约束到采样时钟上set_input_delay -clock clk_50m -max 5.0 [get_ports {rxd}] set_input_delay -clock clk_50m -min 2.0 [get_ports {rxd}]另一种做法是直接set_false_path。对于UART这种本身就靠过采样恢复数据的接口我倾向于直接把rxd设成伪路径让工具不要在这条路径上做严格时序分析。原因很直接UART数据位的中心位置由内部波特率计数器决定跟rxd到达引脚的绝对时间关系不大。你在约束上花再多心思不如在代码里把采样点算准。输出延迟约束正好反过来。它表示FPGA输出数据相对于时钟沿到达外部器件的窗口。比如驱动一个SPI从机外部器件要求数据在时钟上升沿前至少Tsu时间稳定这个Tsu加上PCB走线延迟就是你要填的output delay数值。注意setup方向用maxhold方向用min。我在项目中给UART、SPI、I2C这类接口写约束时都会单独开一个sdc段落信号多的时候用get_ports加括号列表一次性约束比一条条写省事也不容易漏set_input_delay -clock clk_50m -max 5.0 [get_ports {rxd cts rts}] set_input_delay -clock clk_50m -min 2.0 [get_ports {rxd cts rts}] set_output_delay -clock clk_50m -max 5.0 [get_ports {txd dtr}] set_output_delay -clock clk_50m -min 0.0 [get_ports {txd dtr}]2.4 伪路径与多周期路径该松的地方就松时序约束不是越严越好而是越准越好。如果一条路径本来就不需要在一个周期内完成收敛你偏要工具给它做严格约束结果就是布局布线耗费大量资源去优化一条没意义的路径真正关键的路径反而没得到足够的优化空间。set_false_path适用的典型场景有三类一是跨时钟域的控制信号比如异步FIFO的空满标志二是异步复位信号它本身不参与数据路径的时序计算三是UART这种异步采样接口的数据输入。写法很简单set_false_path -from [get_ports {rxd}]或者指定时钟域之间的路径set_false_path -from [get_clocks {clk_50m}] -to [get_clocks {clk_27m}]但要提醒一句伪路径是给工具“豁免”用的不是给你掩盖问题用的。如果一条同步路径本来有时序违例你直接set_false_path糊上去短期内可能能跑高温环境下分分钟出现随机错误。我见过一个项目SPI读取外部ADC的数据老是偶发跳变排查到最后就是有人对SPI时钟域的路径加了false path掩盖了真正的采样时序问题。所以加伪路径之前一定先确认这条路径确实不需要时序收敛。set_multicycle_path用在数据路径可以跨多个时钟周期稳定传输的场景。典型例子是低速率总线接口比如I2C的SCL频率只有几百kHz而系统时钟可能是50MHz数据在SCL一个周期内早就稳定了没必要按单周期路径约束。标准写法是set_multicycle_path -setup 2 -from [get_clocks {clk_sys}] -to [get_clocks {clk_scl}] set_multicycle_path -hold 1 -from [get_clocks {clk_sys}] -to [get_clocks {clk_scl}]这里setup设为2表示允许数据在两个周期内到达hold设为1是因为setup周期的改变会连带影响hold分析需要同步调整。如果你只写setup不写hold工具可能会在hold分析时出现“overshoot”反而报出虚假的hold违例。2.5 时序报告怎么读以UART_RX为例高云PR完成后会生成时序报告一般包含时钟概览、约束覆盖率和详细的路径分析。关键信息是每个路径的Data Path Delay、Clock Path Delay、Arrival Time、Required Time和Slack。Slack为正说明满足为负说明违例。我以一个UART_RX模块为例来说明怎么用约束和代码配合解决setup违例。这个模块的主体逻辑是16倍过采样reg [3:0] rx_sync; reg [3:0] clk_cnt; reg [3:0] bit_cnt; reg [7:0] rx_data; reg rx_done; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin rx_sync 4b0; clk_cnt 4b0; bit_cnt 4b0; rx_done 1b0; end else begin rx_sync {rx_sync[2:0], rxd}; if (rx_sync[2] ~rx_sync[3]) begin clk_cnt 4b0; bit_cnt 4b0; end else if (bit_cnt 4d10) begin clk_cnt clk_cnt 1b1; if (clk_cnt 4d7) begin rx_data rx_sync[3]; bit_cnt bit_cnt 1b1; clk_cnt 4b0; end end end endrx_sync就是把外部rxd打三拍用于消除亚稳态和检测起始位下降沿。使用rx_sync[2]和rx_sync[3]来判断下降沿是保证时序安全的关键比直接对rxd打一拍判断要可靠得多。如果这个模块出现了setup违例先别急着改约束。第一步看时序报告里违例路径的起点和终点如果终点是rx_data寄存器路径起点是rx_sync说明从pin脚到内部寄存器的输入延迟约束可能过紧可以适当放宽set_input_delay的max值。如果路径完全在内部像clk_cnt到bit_cnt的进位链那就是组合逻辑级数太多代码上需要拆逻辑或加流水。记住一个原则约束反映的是物理现实代码决定的是路径复杂度。遇到时序违例先优化代码再调整约束。反过来做往往越调越乱。3. 管脚分配实战布线之前就想清楚的事3.1 物理管脚约束文件怎么写高云的.cst文件格式非常直观。先分配位置再配置电气属性IO_LOC clk 52; IO_PORT clk IO_TYPELVCMOS33; IO_LOC rxd 105; IO_PORT rxd IO_TYPELVCMOS33 PULL_MODEUP; IO_LOC txd 106; IO_PORT txd IO_TYPELVCMOS33;注意几点信号名必须用双引号IO_TYPE是电气标准PULL_MODE是上下拉可选项有UP、DOWN和NONE。管脚编号对应芯片封装的实际ball编号具体可以在芯片的pinout文件里查到也可以在软件里打开“FloorPlanner”图形界面点选。一个常见低级错误是IO_PORT忘写或IO_TYPE不匹配。如果某个Bank的VCCIO电压是3.3V你却给这个Bank上的信号配了LVCMOS25软件会直接报IO标准与Bank电压冲突。这种错误在项目后期才被发现是最难受的因为要改的往往不是一个信号而是一整片区域。3.2 电气标准选择与Bank电压分组的门道IO_TYPE的选择直接由外部器件的电平决定3.3V器件就用LVCMOS332.5V用LVCMOS251.8V用LVCMOS18高速差分信号用LVDS或HSTL。这个没什么悬念真正的坑在于高云芯片的Bank电压不是所有Bank都一样的部分Bank可以配置成多种电压部分Bank固定。查手册的IO Bank章节一定不要跳过。举个例子我之前一块板子上FPGA左边接3.3V的ADC右边接1.8V的DDR3中间还有一组2.5V的LVDS。如果选封装时没注意Bank划分很容易出现某个Bank同时要接3.3V和1.8V器件的尴尬局面。高云软件对这类问题的报错信息还算明确但如果你在布局布线之前先看一眼芯片顶视图的Bank分布整个布板阶段都能省下不少沟通成本。IO_TYPE里还有一个容易忽略的选项是SLEW_RATE。如果项目对信号边沿速率有要求比如外部器件对上升时间敏感可以在IO_PORT里加上SLEW_RATEFAST或SLOW。默认值一般够用但高速输出接口建议显式配置不要依赖默认。3.3 特殊管脚时钟脚、复位脚、配置脚不要乱动全局时钟引脚GCLK是专门为时钟信号设计的输入引脚内部直连全局时钟网络延迟小、抖动低。板级设计时应该把晶振或外部时钟源接到这些引脚上不要图省事随便接一个普通IO口绕进去。虽然普通IO经过逻辑资源也可能产生时钟但那样就等于放弃了FPGA芯片最宝贵的时钟分发资源后续时序收敛难度直线上升。复位信号的处理也类似。高云芯片有全局复位网络如果复位信号从专用全局复位引脚进入工具能自动把复位分发到整个芯片的逻辑单元。如果从普通IO引入复位信号就会像普通数据一样经过组合逻辑和布线资源到达各个触发器不同触发器的复位到达时间可能差好几纳秒这在高速设计里会造成复位释放不同步。配置脚是另一个高危雷区。高云芯片支持JTAG下载部分配置引脚在用户设计里如果被占用成普通IO极可能导致下载不稳定甚至无法识别器件。我建议把配置相关的引脚全部让出来除非你明确知道自己在做什么。3.4 多die FPGA的拉古纳约束高云的新一代大容量FPGA采用多die架构比如GW5AT系列die与die之间通过拉古纳Laguna接口互联。拉古纳接口相当于是封装内部的高速数据通道负责在不同die的逻辑资源之间搬运数据但它的时序特性和die内部布线有本质区别。我先说结论跨die路径的时序收敛难度远高于die内部路径尤其是hold time因为拉古纳接口本身的固定延迟较大数字上很容易出现负余量。在项目规划阶段就应该尽量减少跨die信号的数量和频率。具体操作上高云软件支持FloorPlanner图形化布局可以把关键模块约束在指定的die区域。比如把整个图像采集链路放在die0把显示控制放在die1两者之间只留少量异步FIFO接口信号。配合约束指令限制模块位置可以有效控制跨die路径数量IO_LOC eth_rst_n 5; IO_PORT eth_rst_n IO_TYPELVCMOS33 PULL_MODEUP;另外高云提供PARTITION约束。我习惯对确实需要跨die的关键数据通路手动添加set_max_delay和set_min_delay让工具在布线时对这类路径投入更多优化力度。这里要提醒的是对拉古纳接口上的路径强行设置过于紧凑的延迟反而会让布局布线工具陷入反复重试的死循环编译时间成倍增加。合理的做法是参考高云官方给出的拉古纳接口建议延迟范围在这个范围内做约束。3.5 管脚规划的实战原则从原理图开始管脚分配管脚分配这件事最忌讳的是画完原理图再回头分配FPGA管脚。正确流程是先读芯片pinout文件确定Bank分布和特殊引脚再根据板级信号方向、电平标准、时序要求把信号分配到合适的引脚最后把分配结果落实到原理图。分配时我会重点看几条原则高速时钟信号必须走GCLK引脚差分时钟则要找到成对的差分GCLK。高速并行总线尽量集中在同一个Bank减少跨Bank的参考电压差异。相同电平标准的信号尽量放同一Bank避免VCCIO冲突。功耗敏感的信号不要全部堆在散热条件差的角落Bank。差分信号对必须使用芯片上标注的差分引脚对不能随便找两个普通IO凑数。还有一个很多人容易忽略的点高云部分管脚内部集成了可编程上下拉外部可以省掉一些电阻。比如UART的RX信号板级没有上拉到VCC你可以在IO_PORT里配PULL_MODEUP实测效果等同于外部10k上拉。这个特性在改板或调试时的价值很大可以临时替代外部上拉电阻验证设计假设。4. 复位与跨时钟域从源头减少亚稳态隐患4.1 复位信号不可忽视的一根线复位信号是FPGA设计里被忽视最多、出问题最隐蔽的“单点故障”。一个复位信号要到达芯片里的每一个触发器而且要求释放时所有触发器在同一个时钟沿开始正常工作稍有偏差就会出现部分模块复位了、部分模块还在运行的状态整个系统的初始状态就不可控。经典的处理方法是异步复位同步释放。代码逻辑是这样的外部异步复位先经过两个触发器同步产生一个干净的复位信号再送给内部逻辑使用reg rst_n_sync1; reg rst_n_sync2; always (posedge clk or negedge rst_n_raw) begin if (!rst_n_raw) begin rst_n_sync1 1b0; rst_n_sync2 1b0; end else begin rst_n_sync1 1b1; rst_n_sync2 rst_n_sync1; end end assign rst_n rst_n_sync2;复位触发是异步的外部复位信号一拉低两个同步触发器立刻复位不管当时时钟在什么位置。但复位释放时rst_n_sync2只有在时钟沿到来后才会跟着rst_n_sync1变高从而保证所有使用rst_n的模块在同一个时钟沿释放复位。在高云平台上我特别建议大家把这种同步复位代码写好之后在综合属性里关闭“自动全局复位提取”或显式控制复位信号的传递路径。高云软件会把复位信号自动推断到全局复位网络但如果你在代码里混合了同步复位和异步复位风格工具可能会做出错误推断导致全局复位网络被绕过。4.2 跨时钟域CDC处理不要用打拍扛一切跨时钟域的根本问题是亚稳态。当数据信号在采样触发器的建立保持窗口内发生变化触发器的输出会进入亚稳态表现为一段时间内输出既不是确定的0也不是确定的1之后才随机稳定下来。更麻烦的是亚稳态可能通过组合逻辑向后传播影响一串寄存器。单bit控制信号用两级同步器打两拍这是最基础的手段。但多bit总线绝对不能靠打拍子搬运因为总线里每个bit到达目标时钟域的时刻可能差出好几个时钟周期组合出的值就是完全错误的数据。多bit跨时钟域的正确做法是用异步FIFO或者用握手协议。高云提供了异步FIFO IP核在IP核生成器里可以配置位宽和深度读写侧时钟独立。它在内部已经做了格雷码地址同步和空满标志生成比自己写的异步FIFO要可靠得多。我之前在一个以太网接口设计里就是直接用高云的异步FIFO IP对接不同时钟域的数据流省了很多头疼事。4.3 UART RX的同步与采样子模块设计UART协议本身没有时钟线数据发送方和接收方各自使用自己的时钟通过约定的波特率来同步。这意味着rxd信号跟你的系统时钟没有任何相位关系属于典型的跨时钟域异步输入。UART接收模块的数学原理是过采样以16倍波特率时钟对rxd采样在检测到起始位下降沿后等7个采样周期找到数据位中心再从中心开始每个16周期采样一个数据位连续采8个数据位最后判断停止位。采样点选择是UART接收质量的核心。如果采样点偏到数据位边缘噪声容限就很小稍微有点毛刺就可能采错。16倍过采样下数据位宽度是16个时钟周期理想采样点在第8个周期。我的代码里用clk_cnt计数到7时采样实际就是等了8个周期等效于采在数据位中心。这里还有一个细节起始位检测必须看到rxd从1到0的跳变并保持半个位宽以上才是有效起始位否则可能是毛刺。上述代码里rx_sync[2] ~rx_sync[3]的检测方法配合clk_cnt在7附近再次判断rx_sync[3]的电平就能过滤掉大部分窄毛刺干扰。5. 现场救援常见编译错误与时序违规排查手册5.1 拿到负Slack后的一整套定位思路出现setup或hold违例时先不要慌按下面的顺序一步步来。第一步确认违例发生在哪个时钟域、哪条路径。打开时序报告找到负Slack最大的路径记录它的起点和终点寄存器名称、所在模块路径。这一步能帮你判断是代码问题还是约束问题。第二步判断路径类型。如果是输入引脚到内部寄存器的路径检查set_input_delay的设置是否合理过紧就放宽。如果是内部寄存器到内部寄存器的路径说明组合逻辑太深要么在代码里增加流水线要么用set_multicycle_path允许跨周期传输但前提是功能允许。第三步检查时钟约束是否正确。有时候违例的根源是create_clock写错了周期比如实际是25MHz却写了20ns导致工具按50MHz去分析当然全是违例。第四步考虑FloorPlan干预。如果代码和约束都优化过了仍然违例可以在FloorPlanner里手动把相邻模块放在物理上靠近的区域缩短布线延迟。这一步的优化空间通常有5%到15%是最后的手段之一。第五步实在不行再考虑更换速度等级更快的芯片或者在综合设置里开启更高的优化级别。注意这一步会导致编译时间明显变长不是首选方案。5.2 高云高频报错与解决清单我在高云项目里整理了一份高频报错的对照表遇到类似问题能少走很多弯路。报错/警告信息常见原因解决办法Cant place all the IO管脚分配冲突或Bank占用检查.cst是否有信号分配到了同一引脚或该引脚已被配置脚占用No clock constraint found缺少时钟约束为所有时钟输入端口添加create_clock确认文件和工程层级对应Timing constraint not met存在时序违例打开时序报告定位违例路径按上节方法逐步排查sdc file syntax errorSDC语法错误检查命令关键词、引号、括号是否完整建议用标准SDC模板逐条核对IO Type is not supported电气标准不支持查阅对应芯片的IO标准列表改成LVCMOS/LVDS等受支持标准Bank voltage conflictBank电压冲突调整IO_TYPE或修改Bank的VCCIO供电电压Clock resource exhausted全局时钟资源不足将低频时钟改为普通布线或改用低扇出时钟网络这个表格只是起点实际项目中每个报错背后还有更细的变种。我的习惯是每次遇到不认识的新报错先把完整错误信息复制下来存到本地笔记里配上截图和自己的理解时间久了就是一本很实用的排错手册。5.3 仿真与实物不一致的常见根源功能仿真跑得好好的下载到板子上就不动这种情况的原因通常集中在四个方向。时钟问题排在第一位。仿真里的时钟是理想方波板上实际时钟可能有抖动、压摆率不足、或者根本没有起振。先用示波器确认FPGA时钟引脚上确实有预期频率的信号。复位问题排第二。仿真里你手动给了复位信号但板上的复位引脚如果上拉电阻没焊接或者复位电路时序不对芯片可能一直处于复位状态。检查复位引脚的静态电平。第三个是管脚分配问题。仿真不关心管脚但板上管脚如果跟.cst不一致信号就根本没接到正确的物理引脚上。用逻辑分析仪抓FPGA输出脚对比设计预期。第四个是IO电平标准不匹配。仿真用的是逻辑电平板上可能接了5V器件而FPGA是3.3V或者LVCMOS33配了LVCMOS25的输出驱动结果就是信号边沿过缓或高电平不够。我在一个项目中遇到过FPGA驱动达林顿管再输出给MCU高低电平的场景达林顿管的饱和压降把原本的3.3V高电平拉到了2.1VMCU死活识别不了换用开漏输出加上拉才解决问题。这类问题在仿真阶段完全看不出来必须靠实测电平确认。6. 把项目跑稳定后我总结的几条实在经验时序约束和管脚分配这两件事做得好的工程从综合到量产一路顺畅做得粗糙的工程到了冬夏温差大的环境中就开始神出鬼没地报错。我个人的习惯是在每个项目立项时就把约束文件当成正式设计文档来管理每次修改都留下注释说明原因和日期。这个习惯帮我省过很多次返工。最后分享一个很实用的小技巧高云云源软件支持在综合设置里生成约束模板对于新建工程可以直接套用模板再改成自己的时钟和管脚比从零写sdc/cst要快得多也能避免一些基础语法错误。等到工程能综合过了再到时序报告里逐条核对约束覆盖率和违例路径。这套流程走下来不敢说必定一次通过但至少不会死得不明不白。