ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

门级仿真I2C SDA毛刺误触发START的根因分析与修复方案

门级仿真I2C SDA毛刺误触发START的根因分析与修复方案 这个问题我印象太深了。上个月做一颗MCU子系统的门级仿真Gate-Level SimulationGLSI2C从机模块在跑随机用例的时候前仿真RTL行为级仿真一切正常一换成综合后的门级网表SDA线上的数据几乎隔三岔五就冒出一个START误触发信号直接导致状态机跳错、FIFO里混入垃圾数据。最后查了一圈问题根源锁定在SDA上的Delta毛刺也就是零延迟仿真步进内出现的极窄脉冲。这类问题在数字IC验证里不算罕见但因为现象隐蔽、复现不定很多人会误判成网表Bug或是综合约束问题。这篇就把我完整的排查思路、根因分析和最终采用的根治方案整理出来希望能给你省下几天调试时间。1. 问题全景门级仿真、SDA毛刺与START误触发的三角关系1.1 门级仿真和RTL仿真到底差在哪先说一个很多人容易忽略的点门级仿真之所以比前仿真更“真实”是因为它拿到了综合、布局布线之后的真实网表里面的每一个与非门、传输门、反相器都带上了单元库里的延迟信息标准延迟格式SDF反标。RTL仿真里你看到的SDA从1变0是“瞬间完成”的门级仿真里这个跳变会被拆成好几段先是驱动端输出三态门关闭再是内部逻辑拉低再经过输出缓冲、Pad模型最终出现在SDA引脚上。这个中间过程会引入两类问题一是延迟差异导致的竞争race二是Delta Cycle——也就是仿真器在同一个仿真时间戳内按事件队列依次处理信号更新不同路径上的门因为输入到达时刻不同输出会经历一个极窄的中间态这个中间态在波形上可能只持续一个Delta周期肉眼在普通波形窗口里几乎看不到但逻辑上确实存在。1.2 I2C的START条件为什么会踩中这个坑I2C协议里START的判定条件是SCL为高电平期间SDA出现一个高到低的下降沿。从机侧的起始检测逻辑通常长这样采样SCL确认它处于高电平窗口采样SDA检测是否有下降沿两个条件同时成立就判定START清空位计数器、复位状态机。问题就出在这个“采样SDA下降沿”上。门级仿真中SDA往往由一个开漏输出级配合外部上拉电阻模型驱动。在驱动端进行三态切换、或内部组合逻辑重新收敛的那个Delta周期里SDA线上的值可能短暂地出现“不是正常1也不是正常0”的竞争状态最终表现为一个极窄的负脉冲。如果此时SCL恰好为高START检测逻辑就会认为收到了起始信号。我在自己的工程里复现的场景是I2C从机在读操作完、主控切换为接收模式的那一刻总线处于空闲高电平从机内部的SDA输出使能信号OE恰好经过几级门延迟后撤销综合网表里OE的撤销路径和SDA数据路径延迟不一致导致在Delta周期内SDA先被拉低了一个瞬间随后才回到高。就是这一个瞬间触发了START。1.3 典型现象清单遇到这些表现就该怀疑Delta毛刺这类问题有一些比较典型的“症状”如果你在门级仿真里碰到下面任意几条基本可以沿着这个方向排查同一套测试向量RTL仿真通过GLS失败失败用例集中在总线状态切换、读写方向切换、设备地址匹配前后波形上看START标志位在SDA上没有明显完整低电平脉冲的情况下就置位了把波形放大到Delta级别工具里看事件队列或驱动器列表能看到SDA出现了极窄的毛刺频率越高、SDF反标越完整出现概率越大用零延迟网表无SDF又完全没问题。有一条很有意思有时候你在波形图上把SDA信号放大看到的毛刺宽度是0ps也就是没有实际时间宽度仅仅发生在同一个仿真时间点的多个Delta步进里。这种情况最坑因为你很难用肉眼从波形上捕捉到。1.4 为什么“用同步器打两拍”不能直接套用很多从数字逻辑设计转过来的人第一反应是给SDA加个两级同步器不就完了这思想其实没错但I2C这种总线协议有它的特殊性盲目套同步器反而会引出新问题。I2C是开漏结构SDA和SCL都是“线与”逻辑。正常情况下总线空闲时被上拉电阻拉高任意设备拉低则总线为低。这意味着SDA不只是一个普通的单端输入信号它同时承载了多设备仲裁、时钟同步SCL拉低握手机制等信息。你要是简单地在引脚输入处加一个两级触发器同步会引入至少一个时钟周期的延迟而I2C标准里对建立/保持时间都是有严格要求的尤其在高速率模式Fast Mode Plus1MHz下一个同步延迟可能就把时序预算吃光了。其次从机的START检测通常工作在系统时钟域而SDA是一个异步输入。设计里的常规做法就是用一个高频采样时钟去采样总线配合连续多拍判断来抑制亚稳态和毛刺。门级仿真中出现的这个Delta毛刺宽度远小于采样时钟周期理论上采样器是采不到的。但为什么还会误触发问题往往出在采样器自身也是门级网表实现出来的它的内部采样逻辑里也存在Delta竞争。换句话说毛刺不只在SDA输入上还可能在采样逻辑内部被“制造”出来。这一点后面细说。2. 根因剖析Delta毛刺是怎么被“炮制”出来的2.1 单元延迟与输入引脚竞争门级仿真中每个标准单元的延迟由SDF文件决定。SDF里通常包含IOPATH延迟从输入引脚到输出引脚COND条件延迟条件路径对应的延迟时序检查setup/hold/recovery/removal等。一个基本场景就能说明毛刺的来源假设有一个二输入与非门A、B两个输入本来都是1输出为0。某一时刻A先变成0经过3ns的输入缓冲延迟B再过2ns也变成0。按照与非门的逻辑AB从“11”变“10”时输出应该从0变1A的跳变先到所以输出在A到达后经历单元延迟变为1紧接着B也到了AB变为“00”输出又从1变回0。如果这个单元延迟大于B相对于A的到达延迟差输出就会产生一个宽度为“B到达时刻减去A到达时刻”的毛刺脉冲。这种输入竞争在I2C的SDA路径上简直太常见了。SDA从机内部的数据输出来自发送移位寄存器输出使能来自控制状态机数据线和使能线各自经过不同的组合逻辑路径路径级数不同延迟自然不同。当状态机切换的瞬间先到达的那条路径会把输出拉到一个临时值另一条路径随后才到最终值虽然是对的但中间已经产生了一个不期望的脉冲。2.2 三态门/开漏结构下的驱动强度竞争I2C的SDA在门级网表里通常是这样建模的从机的SDA引脚连接一个三态输出缓冲器PAD模型里的输出部分PAD模型外部有一个上拉电阻用Verilog的pullup或tran模型表示驱动能力上PAD输出“0”时的下拉强度远大于上拉的弱驱动强度当PAD输出为“Z”高阻时总线由上拉电阻维持在“1”。问题出在“PAD从驱动0切换到高阻”的瞬间。理想情况下N管关闭输出变高阻上拉把SDA慢慢拉高。但实际门级PAD模型里控制N管栅极的信号也要经过几个缓冲级。栅极驱动信号的撤销路径和PAD内部其他寄生路径存在延迟差可能在某个Delta步进内N管还在半导通状态、驱动强度又不足以把总线完全钳位在0于是总线电压在事件队列的中间节点上出现了逻辑意义上的“未知”或“弱0”状态。我实际在仿真器里打开SDA的驱动列表看到过同一个仿真时刻上上拉电阻说SDA是1PAD输出级说SDA是0两个驱动器互相“打架”。这里的true是仿真器按强度解析出来的最终值依赖于Verilog解析规则。如果强度解析结果发生了跳动就可能产生毛刺。2.3 START检测逻辑的具体设计缺陷来重点看从机里的START检测逻辑。常见实现有两种第一种是边沿检测加电平判断always (posedge clk or negedge rst_n) begin if (~rst_n) begin sda_r 1b1; sda_rr 1b1; end else begin sda_r sda_in; // 第一拍采样 sda_rr sda_r; // 第二拍采样 end end wire start_pos sda_rr ~sda_r; // 检测到下降沿 wire start_cond scl_in start_pos; // SCL为高且SDA下降第二种是状态机判断// 空闲状态下SCL为高时SDA拉低 - 进入START态表面上看sda_r、sda_rr两拍采样会把脉宽小于一个时钟周期的毛刺滤掉。但问题是门级仿真中sda_in在进入采样触发器之前往往还经过了一些组合逻辑比如同步器、电平转换、测试MUX。一旦sda_in在某个Delta步进出现毛刺而这个毛刺恰好落在时钟采样沿的建立保持窗口附近采样触发器就可能采到一个非预期的值。更隐蔽的一种情况是sda_in经过的组合逻辑本身产生了毛刺而这个毛刺的宽度虽然小于时钟周期但刚好大于触发器的采样窗口导致sda_r提前拉低sda_rr还没来得及跟随start_pos就在下一拍被错误地拉高了。你从波形上看到的其实是毛刺被放大成了一个完整的采样后下降沿。2.4 为什么前仿真和门级仿真表现不同理解前仿真为何“干净”很关键。RTL仿真中SDA是一个由测试bench或者行为级模型驱动的理想信号你没有真实的PAD延迟、没有输出使能的逐级撤销过程SDA在逻辑上永远是稳稳的0或1。而且RTL仿真里assign语句和always块的事件调度相对简单Delta竞争少得多。门级仿真多了三层东西单元延迟、SDF反标、真实的电源/地/ESD模型。这三层组合在一起就把一个在RTL里“看不见”的亚微观时间窗口暴露出来了。这也是为什么GLS是芯片流片前验证里不可替代的一环——它不是给设计找麻烦而是帮你在硅片之前找出真正的时序风险。3. 排查过程实录从乱猜瞎试到精确定位3.1 第一轮排查确认不是约束问题拿到失败用例后我的第一反应是检查SDC约束里I2C相关路径的set_false_path、set_multicycle_path是否配置错误。这是经验之谈——综合时序约束如果漏了异步信号的false path工具可能为了满足约束插入了奇怪的缓冲链导致仿真行为异常。但查完后发现约束没有问题异步信号路径是设了false path的内部同步路径也满足了setup/hold。而且复现失败完全不需要特定时序余量纯粹靠随机用例就能撞出来。所以排除了约束导致的系统性错误。3.2 第二轮排查缩小故障窗口接下来做法是定位“第一次误触发发生在哪个时刻”。方法是用一个专门加了探针probe的仿真版本在START检测逻辑输出处拉一个信号出来配合断言实时监测property check_start_valid; (posedge clk) disable iff (~rst_n) (start_pos 1b1) |- ($past(sda_in) 1b1 sda_in 1b0); endproperty断言跑不过的时候仿真器会自动打印出时间戳。我把时间戳附近的波形全部导出来用Verdi里的Delta调试模式逐级查看。发现start_pos拉高前的那一拍sda_r确实从1变0了sda_rr还是1所以start_pos1是符合逻辑的。问题变成sda_r为什么会提前变0于是我把目光聚焦到dout_enSDA输出使能和sda_outSDA数据两条路径的竞争关系上。3.3 第三轮排查用波形驱动列表双重确认这一步的关键操作是选中SDA信号打开仿真工具的驱动列表Driver List。它会显示当前时刻所有驱动SDA节点的逻辑单元以及它们各自的驱动值。我截取了误触发发生时刻的快照驱动器数值强度备注上拉电阻1pull弱上拉PAD输出N管0strong下拉驱动内部数据寄存器Q端0strong经组合逻辑到达PAD注意看内部数据寄存器Q端在那一刻已经翻转为1了但PAD输出N管还维持着0。这是因为输出使能信号的撤销路径经过了多级缓冲数据信号已经先翻过去了。在某个Delta周期内PAD仍然认为自己在输出0于是总线被强下拉。再过一个DeltaOE撤销传到PADN管关闭总线才被上拉回1。这个“总线短暂为0”的过程恰好被下一拍的采样时钟采到sda_r变成了0。就这么简单一个只在Delta步进中存在、肉眼在波形图上几乎看不见的“幽灵毛刺”触发了整个START检测逻辑。3.4 顺带排查确认不是测试bench的锅中间我还怀疑过是不是测试bench里SDA的激励模型写得不严谨。后来单独写了一个最小复现用例只让主控发送一个START地址读信号然后等从机响应完全不碰数据操作发现复现概率为0。而一旦跑到“主控接收数据结束、主控发送NAK、然后产生STOP”这个操作流复现概率直线上升。这说明问题就是从机内部在多拍状态切换时产生的竞争不是外部激励的问题。到这里根因基本锁死SDA输出使能的撤销与数据翻转之间存在门级延迟差产生Delta级毛刺被START检测逻辑误判。4. 根治方案从设计、仿真到验证的全部闭环4.1 设计侧给SDA输入加真正的毛刺滤波器前面说了直接加两级同步器不行但加一个基于高频采样时钟的毛刺滤波器是可行且I2C协议允许的。I2C标准里规定只有满足最小脉冲宽度的信号才被认为是有效信号比如标准模式100kHz下SDA低电平脉冲的最小宽度为4.7us。对于毛刺滤波器只要滤波窗口小于协议规定的最小脉冲宽度就不会误滤掉合法信号。我的做法是在SDA输入到START检测逻辑之前插入一个三拍连续采样滤波单元module glitch_filter_3tap ( input wire clk, input wire rst_n, input wire sig_in, output reg sig_out ); reg [2:0] shift; always (posedge clk or negedge rst_n) begin if (~rst_n) begin shift 3b111; sig_out 1b1; end else begin shift {shift[1:0], sig_in}; if (shift) sig_out 1b1; else if (~|shift) sig_out 1b0; // 中间态保持原值天然滤除小于两拍的毛刺 end end endmodule这个滤波器要求输入信号至少连续三个采样周期保持同一电平输出才会翻转。对SDA而言这意味着毛刺宽度必须超过两个采样周期才能通过。采样时钟如果用系统时钟比如50MHz周期20ns那么能通过的毛刺宽度至少40ns而门级仿真里的Delta毛刺往往只有几ps到几ns完全被滤掉。有人会问三拍滤波器会引入多少延迟从SDA输入到START检测的路径延迟变成了3个采样周期。这个代价在标准I2C协议里完全可以接受因为START建立时间tSU:STA在标准模式下最小为4.7usFast Mode为0.6usFast Mode Plus为0.26us。3个20ns周期就是60ns远小于上述时序预算。如果你用的是更快的系统时钟比如100MHz那只有30ns更稳。4.2 设计侧从根源消除OE与数据的竞争窗口毛刺滤波是“治标”能保证不再误触发但本质上SDA上仍然存在Delta毛刺。如果总线上还有别的设备或者未来将来集成到SoC里接上更复杂的PAD模型这个毛刺可能在其他模块引发新的问题。所以我在从机内部又做了一处改动把SDA输出数据在OE撤销前提前一个周期稳定。具体的做法是调整发送状态机的时序在通信过程中先让数据线上的输出与输出使能同拍产生但在状态切换前的最后一拍提前把数据寄存器内容固定为1再撤销OE。这样在OE从1变0的Delta窗口里SDA数据已经不是由内部数据寄存器驱动而是由外部上拉驱动数据路径和OE路径的竞争窗口被封闭。这个改动需要小心处理的是不能影响I2C原本的时序要求。在ACK/NACK位之后、总线释放之前SDA本来就允许是高阻态提前一拍的改动不会破坏协议。4.3 仿真侧在SDF反标时增加专门检查门级仿真最大的问题是毛刺只存在于Delta步进里普通波形看不全。为了以后能快速捕获这类问题我在验证环境里加了两个措施一是断言覆盖对任何START/STOP检测信号都要求其对应的SDA电平保持时间不小于N个采样周期。这样即使毛刺被滤波器滤掉了断言也能在毛刺产生的源头SDA输入信号上报出。property sda_no_short_glitch; (posedge clk) disable iff (~rst_n) // 若SDA在连续两个时钟上升沿之间翻转并回来则视为非法毛刺 $rose(sda_in) | $stable(sda_in[$past(sda_in,2)]); endproperty二是波形深度调试在仿真命令里给关键信号开启-delta级别的记录Verdi/VCS里可以在信号属性里选择“Delta Cycle”显示。开到Delta模式后你能直观看到SDA在同一个时间戳上经历了多少次事件。这是定位门级毛刺最直接的手段。4.4 验证侧构造定向“高压”用例既然随机用例能撞出问题那就干脆写一组定向用例专门在“总线方向切换”“读转写”“写转读”“NAK后释放总线”这些协议临界点制造压力。在每个临界点把SDA的驱动强度、OE切换位置随机化配合SDF里的最小/最大延迟做min-max仿真看能不能把毛刺激出来。这组用例加进去之后有两点收获一是在修完根因后可以回归确认毛刺消失二是为后续其它模块的门级仿真提供了类似场景的参考。RTL仿真阶段这种用例看起来像“没事找事”但到了GLS阶段它们比一般随机用例有价值得多。4.5 方案对比为什么不能用延迟线或RC滤波期间我也评估过另外两种方案这里说说为什么不推荐第一种是给SDA加模拟延迟单元比如库里的DELAY单元让毛刺尽量延到采样窗口之后。问题在于DELAY单元的延迟量是固定的而毛刺宽度随PVT、SDF反标变化很难精确匹配。而且加DELAY会占用版图面积增加静态时序分析负担。第二种是让START检测逻辑直接对SCL和SDA做组合逻辑的默认值/前导检查比如坚持先看SCL是否为1再看SDA下降。这个能挡住SCL为低时的毛刺但我们这根毛刺恰好发生在SCL为高的空闲窗口里所以治不了本。不过它的思想可以结合滤波器一起用多一层防护总归是好的。4.6 修复前后对比数据为了给自己和团队一个交代我拿同一组回归用例分别跑了修复前和修复后的仿真统计结果如下指标修复前修复后随机用例数20002000FAIL次数370START误触发次数520平均仿真时长相对1.0x1.03x峰值内存相对1.0x1.02x修复后几乎没有额外的仿真代价逻辑面积增加也在可接受范围内。另外我把采样时钟从50MHz换成100MHz重新跑了一遍结论一致——毛刺在滤波器面前完全无效。5. 常见问题与避坑指南5.1 为什么毛刺在波形上“看不见”很多人定位这种问题时卡在第一步波形图里SDA明明是平滑的高电平为什么断言会报START答案是Delta毛刺不占据仿真时间轴只占据Delta步进轴。普通波形显示按时间绘制你把时间轴放大到皮秒级也看不到它因为你看到的是每个时间点的事件最终值。要看到毛刺必须在VCS/Verdi里开启Delta Cycle显示或者打印SDA的驱动器事件列表。有一种更快的替代诊断法在SDA输入到采样寄存器之间临时插入一个“毛刺计数”逻辑用组合逻辑检测SDA上是否有“电平在极短时间内跳变两次”的事件仿真时用断言统计。这样不用肉眼去翻Delta波形直接靠自动检查定位。5.2 SDA毛刺会不会影响真实芯片说实话这种Delta毛刺在真实芯片上往往不存在。因为真实芯片有物理电容、连续的上拉强度不会出现仿真器里那种抽象强度竞争。那为什么还要管它两个原因一是门级仿真是用来验证功能时序的如果仿真环境里有过多这类虚假故障会淹没真实风险二是“无风不起浪”——虽然毛刺本身可能是建模抽象产生的但它背后暴露出的OE撤销与数据翻转延迟差在真实芯片上如果极端条件下比如弱上拉、大负载电容被放大仍然有变成真实毛刺的可能。把它修掉是消除一个潜在风险不是过度设计。5.3 遇到类似问题优先查哪几条路径我整理了一个排查优先级表供参考优先级排查点常用手段P0SDA/SCL输入到采样寄存器的组合逻辑路径门级波形Delta模式P0输出使能OE与输出数据的时序关系驱动列表、SDF路径报告P1PAD模型的三态切换行为单元库仿真模型文档P1采样时钟的建立/保持余量静态时序分析报告P2测试bench的SDA激励模型最小复现用例5.4 滤波器参数到底怎么定才不误滤这是很多新手踩的坑。滤波器的容限太长会把I2C合法的短脉冲特别是Fast Mode下给滤掉导致通信失败太短又滤不掉门级毛刺。我的经验是采样时钟频率至少要高于I2C SCL频率的4倍以上推荐8-10倍滤波器拍数N取3比较均衡输出需要连续N拍同值才翻转等效滤波宽度 (N-1) * 采样周期这个值必须小于协议规定的最小脉冲宽度如果系统时钟裕量足够采样时钟建议用系统时钟而不是专门分频时钟少一个时钟域就少一类问题。按50MHz采样时钟、3拍滤波计算等效滤波宽度40ns。I2C标准模式下SDA低电平最小宽度是4.7usFast Mode是1.3usFast Mode Plus是0.5us。即便在最高速模式40ns也远小于0.5us所以完全不会误滤合法信号。5.5 仿真工具差异会不会导致结果不同会。我同时用过VCS和Xcelium跑同一个网表VCS下稳定复现的毛刺在Xcelium里可能被事件调度顺序掩盖掉。不同工具对Verilog事件队列的实现有差异对强度解析和Delta调度的处理不完全一致。如果你在A工具下修完换B工具跑出来又是另一个问题不要慌这说明网表里仍有潜在竞争只是B工具恰好把它“隐藏”了。建议在回归时至少跑两个主流仿真器把Delta竞争逼到台面上。5.6 能不能在综合阶段就直接避免综合时可以通过某些策略降低这类风险比如对OE路径设置max_delay约束、给数据路径和使能路径增加相关约束但门级竞争从根本上很难靠综合约束完全消除。因为毛刺产生的根本原因是信号路径的延迟失配而综合工具优化时并不知道你的仿真环境会对哪个边界条件敏感。所以设计侧验证侧双管齐下才是可靠的方案。6. 收尾的一点心得这次排查花了我大概三天时间头两天都在“猜”到底是哪里的问题——怀疑过时钟门控、怀疑过复位释放、甚至怀疑过综合库选型。最后靠的其实是最笨但最有效的方法把触发时刻的波形一层层往下钻从最终输出追到信号源头再用驱动列表证实了信号竞争的存在。如果你也遇到类似的门级仿真毛刺问题我建议你直接从SDA/SCL的驱动列表入手别在更高抽象层级浪费太多时间。门级仿真的麻烦就在这里——它的“假”故障往往映射着一个“真”的风险而排查这类问题没有捷径只有顺着信号路径一路挖到底。毛刺滤波器是目前最稳妥的兜底手段但真正的根治还是要回到输出使能和数据路径的设计时序上去。
返回列表