ARTICLE DETAIL

建站实战干货

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

ADC动态指标FFT分析避坑指南:从相干采样到ENOB计算

2026/10/6 11:16:56 拓冰建站 浏览量
ADC动态指标FFT分析避坑指南:从相干采样到ENOB计算 做ADC这些年我最深的体会是一颗ADC好不好先别急着看架构、看工艺直接把动态指标摆出来SNR、SINAD、ENOB、SFDR、THD一张表列完基本就能判断它能不能在项目里用。而拿到这些指标最标准的路径就是在Cadence环境里跑瞬态仿真再用Spectrum工具不同版本可能叫Direct Plot FFT或者Calculator里的spectrum函数对ADC的输出做FFT分析。这套流程我刚接触时觉得挺简单但实际做起来才发现坑很多采样点没选对、信号源频率没和采样率对齐、DC分量没有处理甚至瞬态仿真的Maxstep设置都会让ENOB算出来惨不忍睹。这篇文章把我从“算出来的ENOB只有6bit”到“能稳定复现12bit ADC理论性能”的完整过程写下来适合刚用Cadence做混合信号仿真、或者准备流片前需要给ADC做性能评估的同学做参考。1. 为什么FFT是ADC动态指标的“体检报告”1.1 SNR、SINAD、ENOB到底在衡量什么先把几个指标的含义讲透。SNR信噪比指的是信号功率和噪声功率的比值这里的噪声是扣除直流、扣除信号本身、扣除谐波之后的所有频谱分量。SINAD信纳比是把谐波功率也算进噪声里也就是“信号对噪声加失真的比值”所以它更能反映ADC在真实应用下的信号可用程度。ENOB有效位数是用SINAD反推出来的等效分辨率公式是ENOB (SINAD - 1.76) / 6.02。为什么有1.76和6.02这两个常数因为理想N bit ADC用满幅正弦输入时量化噪声信噪比的理论公式就是SNR 6.02N 1.76dB所以反过来给定SINAD就能算出这颗ADC相当于多少位的理想转换器。有些人会用SNR直接算ENOB结果发现和Datasheet对不上。这多半是口径问题Datasheet里的ENOB一般基于SINAD因为谐波失真是真实存在的不能假装它不影响系统。举个例子一颗12bit SAR ADC理想满幅输入下的SINAD理论上能达到74dB左右但实际电路能做到70dB以上已经很不错对应ENOB大约11.3bit。如果FFT结果算出ENOB只有6bit那基本可以断定仿真设置或数据处理有误而不是电路真的差到这个程度。1.2 为什么不能直接看瞬态波形很多新手会问我直接把ADC输出波形画出来看阶梯越齐整不就说明性能越好吗问题是12bit ADC在1MHz输入信号下量化台阶只有满量程的几分之一毫伏在示波器或者Cadence波形窗口里看起来就是一条平滑的曲线肉眼根本无法分辨0.5LSB的随机噪声和0.5LSB的二次谐波。就算你疯狂放大波形也只能看到“噪声变粗了”但搞不清楚噪声的频段、谐波的位置、是否有杂散。FFT的价值在于把时域信号按频率拆开在一个频谱图上同时表现出信号、谐波、噪声底、杂散所有影响动态性能的因素都能定位到具体频率点。这就像体检报告不把每个器官的数据量化出来光看气色是看不出高血压的。不过FFT也不是万能的。建立时间、过冲、指令时序、输入开关切换这类时域行为还是要回到瞬态波形里看。所以正确的策略是动态指标用FFT功能验证用瞬态两者互补缺一不可。2. 仿真前必须先搞懂的相干采样2.1 相干采样公式与点数选择这是整篇避坑指南里最重要的一部分。我第一次做FFT仿真时随手把输入信号设成1MHz采样率100MHzFFT点数4096结果信号峰旁边拖了一个长长的尾巴噪底抬高了快20dBENOB惨不忍睹。原因很简单1MHz和100MHz的比值是1/100但4096个采样点并不能完整覆盖整数个信号周期造成频谱能量泄漏到相邻的频率bin里。正确的做法是满足相干采样条件fin M / N * fs。其中N是FFT采样点数M是信号在采样窗口内包含的完整周期数通常取正整数。N建议取2的幂次方便FFT计算也方便后续谐波分析M必须和N互质这样才能保证采样点均匀分布在一个信号周期的各个相位上不会出现某些相位重复采样、某些相位缺失的情况。我常用的几组参数举个例子。如果fs 100MHzN 4096M取127那么fin 127 * 100MHz / 4096 3.10009765625MHz。M127是质数和4096互质这个频率落在FFT的第127个bin上二次谐波在第254个bin三次谐波在第381个bin不会和信号bin重叠。再比如fs 50MHzN 8192M取1023fin 6.243041992MHz也是一组很典型的组合。这里有个经验M不要太小否则信号频率离DC太近1/f噪声和直流偏移会严重影响SNR的统计M也不建议太接近N/2否则高次谐波容易折叠回带内。一般取N/16到N/4之间比较合适比如N4096时M取257、511、1023这类值都挺好。如果你实在没法满足相干采样那就必须用窗函数比如Hanning窗来抑制频谱泄漏。但我要强调窗函数是补丁不是正路。能用相干采样就用相干采样千万别依赖窗函数掩盖采样条件的错误否则你会花一整天在“为什么这个窗下ENOB比那个窗高这么多”的坑里出不来。2.2 测试Bench搭建要点仿真环境搭建时有四个细节直接影响FFT结果的可靠性。第一信号源幅值。建议把正弦输入峰峰值设为满量程的-1dBFS左右不要让ADC满幅输入否则任何一点点非线性都会因为削波而放大失真。比如满量程2Vpp的ADC信号源输出0.89Vpp对应峰值0.445V这样既能充分利用量程又留了0.9dB的余量。第二信号源频率必须是相干采样计算出的精确值不能四舍五入到好记的小数。有人嫌3.10009765625MHz太难看直接设成3.1MHz结果FFT出来泄漏一塌糊涂。计算器多按几个小数点不会累死但能省下一下午的排查时间。第三采样时钟用理想源上升沿设成采样周期的1/100到1/50比如10ns周期的话上升沿0.5ns到1ns。刚开始跑功能仿真时不要加时钟抖动把抖动留到做完整性能评估时再加否则你很难分辨是电路性能差还是时钟抖动太大导致的ENOB下降。第四输出负载。如果是行为级模型或验证仿真直接在输出节点看数字码电压即可如果是晶体管级电路建议在输出驱动级后面加一个轻负载电容比如1pF到5pF模拟后级数字电路的输入电容否则采样保持输出和比较器输入之间的耦合会影响高频仿真结果。另外瞬态仿真时间要留足初始建立时间。我的习惯是StopTime N / fs settle_time其中settle_time取几百纳秒到几微秒看你的电路环路复杂度。仿真时必须等偏置点、反馈环路、采样保持电容都稳定下来再开始取点。如果你从0时刻开始取FFT窗口前几百纳秒的建立过程会像一段瞬态冲击把整个频谱抬高。最后再说一下瞬态仿真的Maxstep。很多教程建议设成1/(fs64)对于100MHz采样率来说就是0.15625ns这个步长对行为级模型没问题但晶体管级仿真会慢到怀疑人生。我实际用下来Maxstep设成1/(fs20)是性价比比较高的起点也就是0.5ns先跑通流程看频谱形态确认没问题后再缩小步长做精度验证。如果Maxstep设太大仿真器在采样点之间的插值误差会变成额外噪声ENOB偏低而且你很难察觉因为波形看起来还是正常的。3. 完整实操从瞬态仿真到算出手动ENOB3.1 瞬态仿真设置与数据导出假设你已经搭好了ADC测试bench接下来按我的习惯走一遍完整流程。第一步在ADE或ADE XL里配置tran分析。以fs100MHz、N4096、M127为例fin3.10009765625MHz采样周期Ts10nsFFT窗口时长N*Ts40.96μsStopTime设为42.96μs多出来的2μs就是settle_time。将仿真精度选成conservative或者手动设置errpresetconservative并确认iabserr是fF量级而不是pF量级除非你的电路本身噪声很大。第二步跑完瞬态后先别急着FFT。你需要在Y0Viewer或波形窗口里确认settle_time之后ADC输出已经进入稳定状态。如果是SAR ADC观察每次转换完成后的输出码是否按正弦变化规律跳动如果是Pipeline ADC观察每个stage的输出是否还有很大的漂移。如果2μs不够稳定就加长settle_timeFFT窗口起点随之往后推。第三步取点。取点的关键在于起点必须是采样周期的整数倍。比如从2μs开始200个Ts是整数然后连续取4096个采样点。如果你从某个非整数倍的时刻开始取相当于给信号加了一个随机相位跳变FFT结果会出现一条额外的非谐波杂散。第四步把数据导出来。我建议从仿真器里导出CSV文件格式两列时间、输出电压。有的版本直接用Calculator的export功能有的要看Calculator里(plot波形)右键选Export。导出后建议先用MATLAB或Python做后处理这样计算SNR、ENOB的公式可以固化下次换参数只改脚本开头几行就行。3.2 用Cadence Spectrum工具查看频谱在Virtuoso ADE环境里进入Spectrum工具的常见路径是Results → Direct Plot → Frequency → Spectrum/FFT有的版本叫“Frequency/FFT”有的版本直接在Calculator里输入spectrum()函数。界面可能五花八门但核心参数就三个样本点数、窗类型、起始时间/结束时间。样本点数填4096要和时域取点的数量一致。窗类型在相干采样下选矩形窗Rectangular/Boxcar非相干采样下选Hanning。起始时间填2μs结束时间填42.96μs或者直接告诉它“从2μs开始取4096个点”。最后纵轴格式选成dB20这样方便直接读dB值。点Apply或者OK之后你会看到一条像梳子一样的频谱曲线。正常状态下0Hz处有一个很高的直流分量信号峰出现在3.1MHz附近也就是第127个bin幅度应该在信号源-1dBFS附近二次谐波在6.2MHz三次谐波在9.3MHz幅度依次递减噪底则像一条水平线铺在下面。如果看到信号峰根部有“裙子”一样展开的旁瓣或者噪底不是平的而是有个大鼓包先别急着改电路回去检查相干采样条件和起点对齐。另外Calc里如果显示的单位是V/√Hz这样的密度单位不要慌。Spectrum工具为了通用性经常把FFT结果显示成频谱密度。你计算SNR的时候如果用的是密度值一定要把它乘以有效的分辨率带宽再转成功率但多数版本在Spectrum工具的显示选项里可以选择“V”或“Vrms”直接选这种读数才是我们熟悉的谱线幅度。我最初在这个单位换算上栽过跟头后来养成了习惯每次打开先看纵轴标签确认不是密度单位再做数据处理。3.3 从频谱计算SNR/ENOB有了频谱图手动计算SNR和ENOB的过程其实不复杂核心是分清楚哪些bin算信号哪些bin算谐波哪些bin算噪声。假设信号binM127的rms幅度为0.3145V。谐波bin分别在254、381、508等位置分别测得rms幅度0.0002V、0.0001V、0.00005V。这里要补一句谐波不一定都落在二次、三次的整数倍还可能在N-k*M的位置出现折叠因为FFT的频谱是以fs/2为镜像的。所以手动计算时要看频谱图上出现的所有较大杂散分辨是不是谐波的折叠版本。噪声功率的计算方法是把频谱上除了直流、信号、谐波之外的所有bin功率加起来。实际操作中直接手算太难我一般把FFT数据导出然后用Python脚本处理。下面是一个简化的示例代码import numpy as np # 读入从Cadence导出的CSV文件假设两列时间、电压 data np.loadtxt(adc_samples.csv, delimiter,, skiprows1) t data[:, 0] v data[:, 1] # 基础参数 fs 100e6 N 4096 M 127 settle_time 2e-6 # 找到settle_time对应的起始索引并截取N个点 start_idx np.argmin(np.abs(t - settle_time)) v v[start_idx:start_idx N] # 去直流 v v - np.mean(v) # 矩形窗 w np.ones(N) V np.fft.rfft(v * w) mag np.abs(V) / N # 信号bin sig_bin M Asig mag[sig_bin] # 谐波bin考虑折叠 harm_bins [] for k in range(2, 11): b (k * M) % N if b N // 2: b N - b harm_bins.append(b) P_harm sum(mag[b] ** 2 for b in set(harm_bins)) # 噪声功率 总功率 - 信号功率 - 谐波功率扣除直流 total_power sum(mag[1:N // 2] ** 2) P_noise total_power - Asig ** 2 - P_harm snr 20 * np.log10(Asig / np.sqrt(P_noise)) sinad 20 * np.log10(Asig / np.sqrt(P_noise P_harm)) enob (sinad - 1.76) / 6.02 print(fSNR {snr:.2f} dB) print(fSINAD {sinad:.2f} dB) print(fENOB {enob:.2f} bit)这段脚本只做最核心的计算没有做窗函数幅度校正也没有做噪底噪声排除完整工具链我会在文章末尾说。但原理就是这样信号功率看信号bin噪声功率看除了信号、直流、谐波之外所有bin的功率和谐波功率算上前几次主要谐波就够了。ENOB公式别写错SINAD和SNR要区分清楚。严格来说计算SNR时还应该把信号bin附近的一些泄漏bin排除比如M-2、M-1、M1、M2这几个bin如果你用了非矩形窗就会出现信号泄漏相干采样矩形窗则基本不用管。这也是我推荐相干采样的另一个原因后处理简单不用做一堆修正。4. 新手最常见的坑及排查方法4.1 频谱泄漏和窗函数如果说翻车率最高的问题频谱泄漏排第二没人敢排第一。典型现象是信号峰根部变宽旁边出现衰减的“裙边”整个噪底抬高ENOB偏低。出现这种情况先检查三点信号源频率是否和计算结果一致瞬态仿真StopTime是否刚好等于N个采样周期取点起点是否在采样周期的整数倍边界。这三项都没问题再用窗函数比如Hanning窗看频谱是否恢复正常。如果加了Hanning窗之后裙边还在那就是仿真电路本身有问题得回头查电路。另外很多人不知道用窗函数会改变幅度读数。汉宁窗对单频信号的幅度修正系数大约是2也就是说加窗后测到的信号幅度要乘2才能和矩形窗的幅度对齐。如果你只是在Cadence里看个大概趋势不乘修正系数也影响不大但如果你要写报告、做多次对比这个系数必须改对否则SNR计算结果会差好几dB。4.2 仿真不收敛、器件未定义Cadence的Spectrum工具好不好用很大程度上取决于前面的瞬态仿真能不能顺利跑通。新手最容易遇到两个问题一个叫“transient仿真不收敛”一个叫“仿真器件未定义”。先说不收敛。表现为仿真器在某个时间点停止报“Newton iterations failed”或者“timestep too small”。常见原因有三种电源上电瞬间太陡、模型精度不够、初始条件不合理。我的处理顺序是先给电源加一个1μs左右的缓启动斜坡避免上电冲击再把仿真精度从moderate提高到conservative同时把iabserr调紧一档最后检查有没有悬空节点给关键节点设置合理的初始电压。如果还是不行才考虑是不是模型库没加载对。再说器件未定义。这个报错通常出现在换了工艺角或者换了PDK之后报错信息类似“no model for component”。意思是找到了电路器件符号但model library里没有对应的模型定义。解决办法是在ADE的Model Library Setup窗口里把对应工艺角的model文件加载进去常见的是tt、ss、ff这几个角落。有时候还会有“错误找不到某个Pcell”的报错那就是工艺库的Pcell路径没有加到cds.lib里需要检查环境变量和库路径配置。4.3 工具差异与场景差异这里提醒一下Cadence环境下用Spectrum工具做FFT和你在FPGA里用Vivado FFT IP核完全是两回事。在FPGA里你的输入是ADC采样后的数字码流FFT IP核处理的是离散数字信号时钟频率决定了能处理的采样点数点数、缩放方式、输入通道数都是配置参数比Cadence环境多了一层“实时信号处理”的概念。我见过有人把Cadence里的相干采样参数直接套到Vivado FFT IP核里结果IP核报错因为IP核的输入时钟和数据速率不满足配置要求。所以别看到“FFT”三个字母就觉得全天下都一样工具不同使用逻辑不同。还有一个很常见的场景差异是用示波器自带的FFT功能看ADC输出。示波器的垂直分辨率通常只有8bit你连12bit ADC的量化噪声都测不出来更不要说精确计算ENOB了。示波器FFT只适合看个大概频谱形态比如有没有明显的谐波、杂散不能用来做正式的性能评估。我在实验室里见过有人拿低端示波器的FFT功能测一颗16bit ADC测出SNR只有50dB然后怀疑ADC芯片有问题其实就是示波器分辨率不够导致的。专业的事情要用专业的工具做。4.4 不同ADC架构的关注点差异最后说一个容易被忽略的事不同架构的ADC用FFT看动态指标时的关注点不一样。SAR ADC的问题主要集中在采样网络非线性、比较器失调和电容匹配误差上所以FFT里重点看二次、三次谐波是否过大以及噪底是否随着输入幅度增长而异常升高。Pipeline ADC要重点关注SFDR因为各级增益误差和电容失配会产生明显的带内杂散有时候不是谐波而是某个固定频率的spur这个和采样时钟耦合也有关系。Sigma-Delta ADC因为有过采样和噪声整形你要把FFT的观察带宽限定在信号带宽内否则它会把大量量化噪声摘要到高频带内SNR反而很高但带外噪底巨大直接用宽带FFT看会显得性能很差。针对不同的架构FFT设置也要微调。我通常会先跑一个短窗口的FFT比如N1024快速看一眼频谱形态确认没有明显设置错误之后再跑N4096甚至N16384的长窗口得到更精确的ENOB。长窗口仿真时间会成倍增加但数据质量好很多特别是在低频端能看清楚1/f噪声的影响。如果你的电路对噪声要求极高建议至少跑16384点。最后分享一个小技巧。每次做ADC动态指标仿真我都会把相干采样的参数、仿真设置、计算结果记录在一个固定的Excel或者文本模板里写上日期、工艺角、电源电压、温度。这样后续做蒙特卡洛或者全工艺角扫描时只需要改脚本里的fin和仿真条件就能批量输出一张指标对比表。我试过用Ocean脚本跑完一轮TT/SS/FF三个角落的ENOB整个过程不需要手动点一次GUI数据全部自动保存。很多新手觉得用Ocean脚本难但其实你可以在ADE里先把操作录下来改改参数就能用。我个人的体会是动态指标仿真最大的障碍往往不是工具不好用而是设置不严谨。每次ENOB异常先怀疑自己的采样设置再怀疑电路这个顺序能帮你省掉至少一半的调试时间。