开篇

起因是一个背板的 I2C BUG,在测试主板到背板上的 I2C 时,出现在 ACK 位置下冲现象,与 TJ 同事沟通后才发现其实是Glitch(毛刺),不愧是老法师啊~

进而引申出此篇低速信号常见BUG的分析,花了几个小时到处搜索资料,结合自己一些分析理解,记录一下学习的过程

CLK_Glitch

先解释下一些名词:

Glitch(毛刺):在原本预期的连续或稳定的信号中出现的短暂而异常的电压或电流波动,表现为瞬态尖峰或窄脉冲。

Overshoot(过冲;正脉冲信号):过,就是超过,越过了预定电平门限[1]

Undershoot(下冲;负脉冲信号):下,就是不及,没有到达预定的电平门限。

Ringing(振铃):反复的过冲与下冲,就形成了振铃

信号的毛刺

毛刺产生的原因——通常是由于电路设计、制造缺陷、噪声耦合、信号切换速度过快或其他复杂的电气交互作用所引起。

信号毛刺的产生原理[2]

毛刺是一种形象的说法,它的产生归因于组合逻辑的竞争与冒险。

竞争[3]

竞争:在组合逻辑中,信号经由不同的路径达到某一会合点的时间有先有后,这种现象称为竞争。

有两种情况会产生竞争:门电路两个输入信号同时向相反的逻辑电平跳变或同一信号经不同路径到达终点的时间有先有后的现象。

  • 例1:信号 A、B 不可能突变,需要经历一段极短的过渡时间。而门电路的传输时间也各不相同,故当 A、B 同时改变状态时可能在输出端产生虚假信号。

A+B=Y

传输时间不同导致毛刺

  • 例2:同一信号经不同路径到达终点的时间有先后(或者两个不同信号变化不同步),结果在 t1-t2 时间内,电路输出端产生了 Y=1 的尖峰脉冲,不符合静态下,Y 恒为0的逻辑关系:

不同路径

结果,在 t1-t2 时间内,电路输出端产生了 Y=1 的尖峰脉冲,不符合静态下,Y 恒为0的逻辑关系:

信号变化不同步导致毛刺

冒险[3:1]

冒险:由于竞争而引起电路输出发生瞬间错误现象称为冒险。表现为输出端出现了设计预期之外的窄脉冲,常称其为毛刺。(可见,冒险是竞争产生的结果)

冒险信号的脉冲宽度很小,常常只有数纳秒或数十纳秒,其频带带宽可达数百兆赫兹或更宽。在板级调试时,如果示波器的上限频率较低,会将幅度较大的毛刺显示为幅度较小的毛刺,甚至不易被察觉。

冒险按产生形式的不同可以分为静态冒险动态冒险两大类。

  • 静态冒险:输入有变化,而输出不应变化时产生的单个窄脉冲

  • 动态冒险:输入有变化,输出也应变化时产生的单个窄脉冲

动态冒险是由静态冒险引起的,因此存在动态冒险的电路也存在静态冒险。

静态冒险根据产生条件的不同,分为功能冒险逻辑冒险两大类。

  • 功能冒险:当有两个或两个以上输入信号同时产生变化时,在输出端产生毛刺

  • 逻辑冒险:如果只有一个变量产生变化时,在输出端产生毛刺

冒险往往会影响到逻辑电路的稳定性,因此要注意冒险的消除。

竞争与冒险的关系:有竞争不一定会产生冒险,但有冒险就一定有竞争

毛刺造成的危害[2:1]

当毛刺信号成为系统的启动信号,控制信号,握手信号,触发器的清零信号,预置信号,时钟信号,或锁存器的输入信号时就会产生逻辑错误;同时组合逻辑产生的毛刺也会增加系统功耗

毛刺的消除

组合逻辑的毛刺通常总是存在,难于甚至无法消除,但是毛刺只有在异步设计[4]中(连接到时钟、异步复位、锁存器的使能端)才存在问题,在同步设计[4:1]中,由于寄存器在时钟沿才会动作,只要能满足时延要求,不出现在时钟的上升沿并且满足数据的建立和保持时间(由于毛刺很短,多为几纳秒,基本上都不可能满足数据的建立和保持时间),就能确保采样到稳定正确的结果,就不会对系统造成危害,毛刺虽然无法消除,但其造成的问题却可以消除。很幸运,我这里是 I2C 采用了同步设计。

同步设计:同步设计是指电路中的所有信号都在一个共同的时钟信号的控制下进行操作。这意味着所有的状态变化都发生在时钟的上升沿或下降沿。同步设计的优点在于它简化了时序分析,因为所有信号的变化都被约束在一个可预测的时间点上。

异步设计:异步设计涉及处理不受统一时钟控制的信号,这可能是因为信号源自外部设备,或者是在FPGA内部的不同时钟域中。异步设计更加复杂,因为它涉及到解决不同信号之间的时间不确定性和亚稳态问题。

其余方法请参考文末参考资料。

再说回我这边遇到的ACK位置的 CLK 出现毛刺现象,此时处于数据交换的时候,Master刚刚发送完数据,Slave进行应答,此时 ACK 为高,那应该为 NACK (ACK是低电平有效)。且测量时发现该 NACK存在不单调的情况,因为此时测量在Slave端,NACK 是Slave发送的,这里的不单调属于信号的反射,故我们进行了Master端的测量,发现NACK是正常的,符合预期,这是题外话。

处在这个主从交换控制权的位置,并且我得IPMB线是一转三的,我推测可能出现了静态冒险情况,信号造成了一定的干扰,耦合到了时钟上,导致了这一现象的发生,又被我们误判成了下过冲。更换了屏蔽线缆之后结果略有改善,一转三的线缆存在太多干扰了,一转一的就不会发生这种情况。

过冲与下冲

过冲是指信号跳变的第一个峰值或谷值,它是在电源电平之上或参考地电平之下的额外电压效应;下冲是指信号跳变的下一个谷值或峰值。

影响与危害[5]

过冲与下冲都是不利的因素,过大的过冲电压经常长期性地冲击会造成器件的损坏,如图所示。严重的下冲会超过接收器件的门限而导致电路的逻辑错误。

过冲下冲

如果信号的下冲过大,可能会产生高低电平的误判,导致系统致命的逻辑错误,本应该为1的地方变成了0,0变成了1

逻辑判断

与信号下冲不同,过冲并不会导致逻辑判断错误,但却可能给芯片带来潜在的累积性伤害,从而缩短其工作寿命,严重者可能会损坏芯片。如下图为AST2600 DataSheet关于过冲部分的说明

Spec

如果过冲信号在 1.3*VCC~4V 范围之内呢?4V 是所能允许的最大输入电压,不能越过这个门限。设想如果一个输入电平保持在 3.9V,又该如何评价这个信号质量(过冲)能否接受?对于芯片来讲是否安全呢?需要引入过冲/下冲的另一个评估参数: 过冲/下冲持续时间[6]。一般来讲,过冲越高,持续时间越长,器件寿命会越短。

虽然没有到达4V,但当持续时间 ≥ 5ns时,我们认为这样的过冲也是需要进行Debug的,以免较长时间的过冲使得芯片的可靠性降低。

信号反射[7]

产生过冲和下冲的本质原因是:传输路径上的信号的反射导致

反射是引起SI的一个最基本因素,信号在传输线传播过程中,一旦它所感受到的传输线瞬时阻抗发生变化,那么就必将有发射发生。

反射及反射系数

设绿色阻抗为 Z1,蓝色阻抗为 Z2,信号经过两个阻抗不同的区域,在交界处A处,电压和电流不能产生突变(若电压不连续,将产生无穷大的电场;若电流不连续,将产生无穷大的磁场)。

反射

若 Z1 ≠ Z2,则关系式 V1 =I1 × Z1 ; V2 =I2 × Z2 ,无法同时满足电压和电流连续的条件V1 = V2,I1 = I2 ,故只能从电磁波反射的角度进行分析,如下所示。
  信号由区域1往区域2传输的过程中,入射(incident)信号、反射信号(reflect)、传输信号(transfer)分别如下图表示:

分界面两侧的电压相等,有 Vinc + Vref = Vtra ;

分界面两侧的电流相等,有Iinc - Iref = Itra ;

再有 Iinc × Z1 = Vinc ;Iref × Z1 = Vref ;Itra × Z1 = Vtra ;

由以上5个等式可以推导得出:

建立模型

模型

展开时间轴,计算实时反射波形

下面举个栗子

设传输线阻抗Rz=30Ω,源端串接的匹配电阻Rs=10Ω,则传输线左端A点反射系数为 (10 - 30)/(10 + 30) = -0.5,右端B点反射系数为 (+∞ - 30)/(+∞ + 30) = 1。

设初始状态都为低电平0.0V,T0时刻源端跳变为3.3V,发送逻辑高电平信号,末端B点的电压变化如下。

模型计算

T1时刻,由于电阻分压,传输线左端A点电压为3.3*30/(10+40)=2.475V,抽象理解为T1时刻有一个+2.475V的信号在传输线上向B点传播;

T2时刻,该信号在B点产生全反射(反射系数为1),T2时刻B点电压为原始信号、入射信号、反射信号的叠加,即0+2.475+2.475 = 4.95V;

T3时刻,末端的一次反射信号到达A点,由于阻抗不匹配,反射电压为2.475 * (-0.5)=-1.2375V,此时A点电压也为原始信号、入射信号、反射信号的叠加;

T4时刻,源端的一次反射信号到达B点,同理计算末端B点电压为4.95-1.2375-1.2375 = 2.475V;

T5时刻,末端的二次反射信号到达A点…

T6时刻,源端的二次反射信号达到B点,如上图所示计算B点电压为 3.7125V

在理想情况(无损传输)下,信号会在传输线A、B两端无休止的反射振荡,反射电压的幅值越来越趋近于0,在实际中信号在传输过程中有衰减,最终趋于稳态。


上面的看起来如果比较难理解的话,可以看看这个例子,简单点可以将主板上铜线看作水管,电信号看作水,从水厂到用户家里经过很多水管,这些水管肯定有粗有细,水流从细(阻抗大)的水管流向粗(阻抗小)的,流速肯定比较快(信号频率变快了),但是流出来的量并不多,占不满水管(电压相低);水流从粗的水管突然流向细的,快将水管占满了(电压升高),流速就没有之前从细水管的里面出来那么快了(频率降低),而且势必会被反弹回来一部分,这就是信号的反射。水锤效应、山谷的回音也是信号反射的另一种体现。

阻抗匹配[7:1]

信号反射的本质原因是:传输链路的阻抗不匹配

根据这个来进行Debug,只需要调整线路的串阻进行阻抗匹配即可,一般比较成熟的设计中都会在信号输出端加一个匹配电阻,在Layout的时候这颗电阻尽量的靠近源端器件的输出管脚,算是一个经验设计方法。实际上, 其实这个小电阻的作用就是为了解决信号反射问题。而且随着电阻的加大,振铃会消失,但你会发现信号上升沿不再那么陡峭了,串联电阻是为了减小反射波,避免反射波叠加引起过冲。这个解决方法叫阻抗匹配,一定要注意阻抗匹配,阻抗在信号完整性问题中占据着极其重要的地位。

匹配串阻

假设我传输线特性阻抗为50ohm,负载阻抗为75ohm,用Python进行模拟匹配电阻和反射系数之间的关系

阻抗匹配对反射系数的影响

随着我匹配电阻不断的加大,反射系数也在不断的变小

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

# 设置matplotlib的字体,防止中文乱码
plt.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体为黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题

# 定义传输线参数
Z0 = 50.0 # 传输线的特性阻抗
ZL = 75.0 # 负载阻抗

# 阻抗匹配电阻值,可以根据需要修改
R_match = 20.0 # 假设的匹配电阻值

# 计算阻抗变换后的负载阻抗
Z_transformed = Z0 * (R_match / (Z0 + R_match))

# 计算反射系数
Gamma = (Z_transformed - Z0) / (Z_transformed + Z0)

# 打印反射系数
print(f"反射系数(阻抗匹配后): {Gamma:.4f}")

# 绘制反射系数随匹配电阻变化的图
R_values = np.linspace(0, 100, 500) # 假设的匹配电阻范围
Gamma_values = [((R/(Z0 + R)) - 1) / ((R/(Z0 + R)) + 1) for R in R_values]

plt.figure(figsize=(10, 6))
plt.plot(R_values, np.abs(Gamma_values), label='反射系数幅度')
plt.axvline(x=R_match, color='r', linestyle='--', label=f'当前匹配电阻值: {R_match}')
plt.xlabel('匹配电阻值 (欧姆)')
plt.ylabel('反射系数幅度')
plt.title('阻抗匹配对反射系数的影响')
plt.legend()
plt.grid(True)
plt.show()

模拟实际波形来看看,以下是Python实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import numpy as np
import matplotlib.pyplot as plt

# 系统初始条件
DataNum = 100 # 100个数据模拟波形
StartNum = 30 # 30个起始数据,方便对比
SourceRes = 10.0 # 源端电阻,根据需要修改
LineRes = 30.0 # 传输线电阻
StartVoltage = 0.0 # 初始电平
TailVoltage = 3.3 # 跳变后电平

# 计算反射系数
ReflectTail = 1.0 # 末端反射系数
ReflectSource = (SourceRes - LineRes) / (SourceRes + LineRes) # 源端反射系数
VoltageReflectSource = (TailVoltage - StartVoltage) * LineRes / (SourceRes + LineRes) # 传输线起始端电压

# 初始化数据数组
OutputData = np.zeros(DataNum)

# 添加初始数据
for i in range(StartNum):
OutputData[i] = StartVoltage

# 计算保存数据
for i in range(StartNum, DataNum):
OutputData[i] = OutputData[i - 1] + (VoltageReflectSource + VoltageReflectSource * ReflectTail)
VoltageReflectSource *= ReflectSource * ReflectTail

# 绘制波形图
plt.figure(figsize=(10, 6)) # 设置图形的大小
plt.plot(OutputData, label='Voltage Waveform') # 绘制波形图
plt.title('Voltage Reflection Waveform') # 添加标题
plt.xlabel('Time') # 添加x轴标签
plt.ylabel('Voltage') # 添加y轴标签
plt.legend() # 显示图例
plt.grid(True) # 显示网格
plt.show() # 显示图形

通过改变源端电阻阻值,得到以下模拟数据,分别为10ohm、20ohm、30ohm、40ohm、50ohm、60ohm的波形结果

源端电阻10Ω

源端电阻20Ω

源端电阻30Ω

源端电阻40Ω

源端电阻50Ω

源端电阻60Ω

  • 发生从0到1跳变时,当源端电阻小于传输线电阻时,信号变化比较快(上升时间较短),但是会伴随着过冲的产生,影响信号的完整性;

  • 当源端电阻大于传输线电阻时,信号上升相对比较平缓,能有效解决过冲问题,但是增大了上升时间,限制了信号的传输速度;

  • 只有当源端电阻和传输线电阻相等时(即阻抗匹配状态),信号质量最接近理想状态。

当多个反射信号和原信号叠加则会导致反复的过冲与下冲,反复的过冲和下冲则会引起振铃。这增加了信号稳定所需要的时间,从而也影响了系统稳定的时序。

振铃

振铃现象是一种高频波动,主要表现为电路或系统中的振荡和失真。它的产生原因可以是信号幅度过大、相位差不当、负载阻抗变化等多种因素。

具体来说,在反馈电路中,当输入信号与反馈信号相位差发生改变时,如果其幅度超过一定阈值,则可能会引起振铃现象。这种情况下,输出信号会反复振荡,并逐渐失真,最终导致整个电路或系统的失效。

振铃

实际上,要解决这个问题,我们可以选择合适的开关管、采用滤波器等,还有在布线的时候尽量的减少环路的面积,这样也能够减少寄生电感/杂散电感,然而在项目中还是会以高速信号为主进行布线,所以最有效的还是进行阻抗的匹配,当阻抗匹配后,过冲问题会得到解决,同样的振铃也会一起跟着消失。

总结

言而总之,总而言之,信号的Glitch也好,过冲、下冲、振铃也好,看了那么多解释和例子之后对信号完整性的理解又多了一些,好像归根结底就是阻抗不匹配(小声逼逼),有很多东西都可以延展再延展,一开始写就停不下来,但是强迫自己停下来吧,毕竟手头还有活要干呢(叹气= =),信号完整性真的涉及到了太多东西,就本篇来看,需要掌握数电、模电、基础的电路知识,会问ChatGPT点编程,知道怎么测试…想要做一些扩展的话,更得熟练掌握数电、模电、电路。

当然还有很多东西没有提到的,比如说串扰啊、抖动啊等等,每一点又能分几小点,每个小点又能叭叭一堆。

参考资料


  1. 这个门限在CMOS电平标准里指 VDD, VSS ↩︎

  2. 毛刺glitch的产生与消除 ↩︎ ↩︎

  3. 竞争与冒险——随笔 ↩︎ ↩︎

  4. FPGA设计精要:时钟同步与异步技术深度解析及最佳实践 ↩︎ ↩︎

  5. 信号完整性与高速PCB设计(1):过冲、欠冲、振铃 ↩︎

  6. 过冲(overshoot)、下冲(Undershoot)的量化标准与评估实例 ↩︎

  7. 信号完整性之“过冲“(振铃)深度分析 ↩︎ ↩︎