ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

三阶连续时间Delta-Sigma ADC的Matlab行为级建模与仿真实践

三阶连续时间Delta-Sigma ADC的Matlab行为级建模与仿真实践

简介:这份资源是面向模拟集成电路与信号处理学习者的3阶连续时间Delta-Sigma ADC Matlab仿真模型,适合具备一定ADC理论基础、希望深入理解CTSDM架构与量化噪声整形机制的中高级读者。压缩包共10个文件,约3.14MB,包含3个m脚本、3篇pdf参考论文、1个mdl Simulink模型、1个xlsx计算表、1个txt说明及1张png操作示意图,覆盖时序仿真、频域分析与功率谱评估等核心环节。资源围绕三阶积分器、3位量化与20倍过采样、400MHz采样率展开,可帮助读者复现调制器动态行为、观察噪声整形效果并评估SNDR等性能指标。目前已有960人学习下载,配套论文与说明文档便于对照理论推导与工程实现,适合用于课程设计、科研入门或ADC性能优化参考。

1. 从一次跑不通的仿真说起:这个三阶连续时间 Delta-Sigma ADC 到底能干什么

如果你正在做混合信号电路方向的研究,或者刚接手一个 ADC 建模任务,大概率会遇到这样的场景:论文里写满了传递函数、噪声整形、系数缩放,但真到动手搭一个能跑出频谱的模型时,却不知道从哪下手。这份三阶连续时间 Delta-Sigma ADC 的 Matlab 仿真代码,解决的正是这个问题。它把连续时间调制器的环路滤波器、量化器、反馈 DAC、时钟抖动这些环节用脚本串起来,跑完直接出功率谱密度和信噪比。适合谁?适合正在做 ADC 行为级建模的研究生、需要快速验证架构可行性的模拟 IC 工程师,以及想搞懂 Delta-Sigma 调制器内部信号流向的自学者。Matlab 在这个场景里不是万能的,但它能让你在写 Verilog-A 之前,先把架构层面的坑踩一遍。

2. 架构拆解:三阶连续时间环路到底由哪几块拼起来

2.1 连续时间 Delta-Sigma 调制器的信号链路

一个三阶连续时间 Delta-Sigma ADC 的核心思路,是用过采样加噪声整形,把量化噪声推到高频段,再在数字端滤掉。整个链路可以拆成几个关键环节:输入信号先进入环路滤波器,环路滤波器通常由三个级联的积分器构成,每个积分器处理连续时间信号;积分器的输出进入量化器,量化器一般用多比特 Flash 结构,把模拟量转成数字码;数字码再通过反馈 DAC 送回输入端,形成闭环。这个闭环结构决定了噪声传递函数和信号传递函数,也决定了整个调制器的稳定性。

在 Matlab 里实现这条链路,有两种常见做法。一种是直接用 Simulink 搭模块,另一种是用脚本写差分方程或状态空间方程。这份代码走的是脚本路线,好处是参数调整方便,跑批量仿真时不用反复打开模型文件。代码里用状态空间矩阵描述环路滤波器,每个积分器的状态变量在时间步进中更新,量化器用一个简单的比较函数实现,反馈 DAC 则根据量化输出更新输入端的反馈量。

2.2 环路滤波器的系数映射与状态空间实现

连续时间环路滤波器的系数不是随便填的,它们决定了噪声传递函数的零极点位置。常见做法是先设计一个满足稳定性要求的噪声传递函数,比如 Butterworth 或 Chebyshev 型,再通过脉冲响应不变法或双线性变换,把离散域的系数映射回连续时间域。这份代码里,系数以向量形式给出,每个积分器的增益、反馈路径的权重都单独定义,方便你替换成自己算出来的值。

状态空间实现的好处是把微分方程写成矩阵形式,时间步进时只需要做矩阵乘法。代码里用A、B、C、D四个矩阵描述环路滤波器,状态更新公式是x[n+1] = x[n] + dt*(A*x[n] + B*u[n]),其中dt是仿真时间步长,u[n]是当前输入。这个前向欧拉法虽然简单,但在步长足够小的时候精度够用。如果你要做更精确的仿真,可以换成梯形法或龙格库塔法,代码结构不用大改,只替换状态更新那一行就行。

2.3 量化器与反馈 DAC 的建模细节

量化器在行为级仿真里通常用一个量化函数表示,输入是连续幅值,输出是离散电平。这份代码里,量化器的位数可配置,默认是 4 比特,输出范围从 -8 到 +7,步长是 1。量化误差被建模成加性白噪声,这是行为级仿真的常见简化,虽然不完全准确,但在架构评估阶段足够用。反馈 DAC 则把量化输出乘以一个增益系数,再送回环路滤波器的输入端。这个增益系数很关键,它直接影响闭环增益和稳定性,代码里用一个单独的变量dac_gain表示,方便你扫描不同值看效果。

时钟抖动是连续时间 Delta-Sigma ADC 的一个痛点,这份代码里用随机相位扰动来模拟。具体做法是在每个采样时刻加一个服从高斯分布的小扰动,扰动幅度由jitter_rms参数控制。这个建模方式不算精确,但能让你快速看到抖动对信噪比的影响趋势。如果你要做更严格的抖动分析,可以把扰动加到反馈 DAC 的更新时刻上,而不是采样时刻。

3. 跑起来:从零开始把仿真脚本跑出频谱

3.1 环境准备与文件结构说明

这份代码不需要额外的工具箱,Matlab 基础版本就能跑。我一般会先确认 Matlab 版本在 R2016b 以上,因为代码里用了隐式扩展,老版本可能会报维度不匹配。文件结构很简单,主脚本一个,里面包含参数定义、环路滤波器系数、仿真循环和频谱分析四部分。没有额外的函数文件,所有逻辑都在一个脚本里,方便你直接复制到自己的工程目录下。

跑之前建议先清空工作区,避免旧变量干扰。命令很简单:

clear; close all; clc;

这三行不是可有可无的,尤其是clear,因为代码里有些变量名比较通用,比如fs、N,如果工作区里有同名变量,可能会覆盖掉脚本里的定义,导致仿真结果莫名其妙。血泪经验:有一次我忘了清空,跑出来的频谱跟预期完全对不上,排查了半天才发现是旧变量在捣乱。

3.2 关键参数配置:采样率、过采样比与量化位数

参数配置集中在脚本开头,几个关键参数需要你根据实际需求调整。采样率fs决定了仿真时间步长,通常设为目标信号带宽的几百倍。过采样比OSR决定了噪声整形的效果,三阶调制器一般取 32 或 64。量化位数nbits影响量化噪声底,位数越高,信噪比越好,但仿真时间也会增加。

fs = 1e6; % 采样率,单位 Hz OSR = 64; % 过采样比 nbits = 4; % 量化器位数 fin = 10e3; % 输入信号频率,单位 Hz Ain = 0.5; % 输入信号幅度,归一化值 N = 2^16; % 仿真点数

这几个参数之间有关联。fs和OSR决定了信号带宽,fs/OSR就是奈奎斯特带宽。N决定了频率分辨率,点数越多,频谱越平滑,但跑得也越慢。我一般会先用N = 2^14快速跑一遍看趋势,确认没问题再加到2^16出最终结果。Ain不要设得太大,超过 0.7 容易让调制器进入不稳定区,输出频谱会出现杂散。

3.3 仿真主循环与频谱分析代码逐段讲

主循环是整个脚本的核心,每一轮迭代做四件事:更新环路滤波器状态、计算量化器输入、执行量化、更新反馈。代码结构如下:

% 初始化状态变量 x = zeros(3, 1); % 三个积分器的状态 y = zeros(1, N); % 量化输出序列 u = Ain * sin(2*pi*fin*(0:N-1)/fs); % 输入信号 % 主循环 for n = 1:N % 计算环路滤波器输出 v = C * x + D * u(n); % 量化 q = quantizer(v, nbits); % 反馈 DAC fb = dac_gain * q; % 状态更新 x = x + dt * (A * x + B * (u(n) - fb)); % 保存输出 y(n) = q; end

逐段说明。x是 3×1 的状态向量,对应三个积分器的输出。u是输入正弦信号,频率和幅度由前面的参数决定。循环里先算v,这是量化器的输入,等于状态向量的线性组合加上前馈输入。quantizer是一个自定义函数,输入连续值,输出离散电平,具体实现后面会讲。fb是反馈量,等于量化输出乘以dac_gain。状态更新那一行是前向欧拉法,dt = 1/fs。最后把量化输出存到y里。

跑完循环后,频谱分析用pwelch或fft都行。代码里用的是fft加窗函数,避免频谱泄漏。具体做法是先对y加 Hann 窗,再做 FFT,然后取单边谱,归一化后画图。信噪比的计算用信号功率除以噪声功率,噪声功率是除去信号频点外的总功率。

% 加窗 w = hann(N)'; yw = y .* w; % FFT Y = fft(yw); P = abs(Y).^2 / N; % 单边谱 P_half = P(1:N/2+1); P_half(2:end-1) = 2*P_half(2:end-1); f = (0:N/2) * fs / N; % 画图 figure; plot(f, 10*log10(P_half)); xlabel('频率 (Hz)'); ylabel('功率谱密度 (dB)'); grid on;

这段代码里,hann(N)生成长度为N的 Hann 窗,yw是加窗后的信号。fft之后取平方得到功率,再归一化。单边谱的处理要注意,除了直流和奈奎斯特频点,其他频点要乘以 2,因为双边谱的能量要折算到单边。画图用10*log10转成 dB,这样噪声整形的那条斜率线看起来更直观。三阶调制器的噪声整形斜率是 60 dB/decade,在频谱上应该能看到一条明显的上升线。

4. 避坑与排查:跑仿真时最容易翻车的几个地方

4.1 频谱出现异常杂散

现象:跑出来的频谱在低频段出现不该有的尖峰,或者噪声底明显偏高。原因通常是量化器输入过载,或者反馈增益设得不对。三阶调制器的稳定输入范围比一阶窄很多,Ain超过 0.6 就可能不稳定。解决方法是先把Ain降到 0.3 跑一遍,确认频谱正常后再逐步加大,找到稳定边界。另外检查dac_gain是否和环路滤波器系数匹配,不匹配会导致闭环增益异常。

4.2 仿真速度慢到无法接受

现象:N = 2^16时跑一次要等好几分钟。原因主要是 Matlab 的循环效率低,尤其是状态更新那一行,每次都在做矩阵乘法。解决方法是把循环里的矩阵运算向量化,或者用for循环但提前分配好数组。另一个技巧是把N降到2^14先调参数,最后再跑高分辨率。如果还是慢,可以考虑用parfor并行跑多个参数组合,但要注意状态变量不能并行更新。

4.3 信噪比计算结果和理论值差太多

现象:算出来的 SNR 比理论值低十几 dB。原因可能是频谱分析的窗函数选得不对,或者噪声功率的计算范围没排除直流和信号频点。解决方法是检查pwelch或fft的参数,确保窗长和重叠点数合理。另外,信号频点附近的几个 bin 要排除在噪声功率之外,否则信号泄漏会被算成噪声。我一般会排除信号频点前后各 3 个 bin,这个数字可以根据窗函数的主瓣宽度调整。

4.4 时钟抖动参数改了但结果没变化

现象:调整jitter_rms后,频谱和 SNR 几乎不变。原因是抖动建模的位置不对,或者抖动幅度太小,被量化噪声淹没了。解决方法是把抖动加到反馈 DAC 的更新时刻上,而不是采样时刻,因为连续时间调制器对反馈路径的抖动更敏感。另外,jitter_rms的量级要和采样周期1/fs可比,如果设成1e-12而1/fs是1e-6,那确实看不出变化。

4.5 量化器函数输出电平数不对

现象:量化输出只有几个离散值,和设定的位数不匹配。原因是量化函数的步长计算错了,或者输入范围没对齐。解决方法是检查quantizer函数里的step和offset参数,确保输出电平数等于2^nbits。如果输入是归一化的,输出也要归一化,否则反馈增益会出错。这个坑很隐蔽,因为频谱看起来可能正常,但 SNR 会偏低。

5. 二次开发:把这套代码改成你自己的架构验证工具

5.1 替换环路滤波器系数的正确姿势

这份代码的环路滤波器系数是硬编码的,如果你想验证自己设计的噪声传递函数,需要替换A、B、C、D四个矩阵。常见做法是先用tf2ss把传递函数转成状态空间,再把得到的矩阵填进去。注意连续时间传递函数和离散时间传递函数的区别,tf2ss默认是离散的,如果你有连续时间传递函数,要用tf2ss的连续版本,或者手动推导状态空间。

替换系数后,先跑一个短点数仿真,看输出频谱的噪声整形斜率是否和预期一致。三阶调制器的斜率应该是 60 dB/decade,如果斜率不对,说明系数映射有问题。另外检查闭环极点是否都在左半平面,否则系统不稳定。

5.2 加入非理想因素:运放有限增益与电容失配

行为级仿真的价值在于快速评估非理想因素的影响。你可以在状态更新那一行加入运放有限增益的建模,常见做法是把积分器的理想增益A乘以一个衰减因子1/(1+1/Aol),其中Aol是运放的开环增益。电容失配可以用随机扰动模拟,每个积分器的增益加一个服从高斯分布的误差项。

% 运放有限增益 Aol = 60; % 开环增益,dB gain_err = 1 / (1 + 1/(10^(Aol/20))); A_eff = A * gain_err; % 电容失配 mismatch = 0.001; % 失配标准差 A_eff = A_eff .* (1 + mismatch * randn(size(A_eff)));

这两段代码可以直接插在状态更新之前。Aol设成 60 dB 是常见值,再低就会明显影响 SNR。mismatch设成 0.1% 是典型工艺水平,再大就会在频谱上看到杂散。跑完对比理想情况和非理想情况的 SNR,就能量化这些因素对性能的影响。

5.3 用脚本批量扫描参数并自动出图

二次开发的一个实用技巧是把参数扫描自动化。比如你想看OSR从 16 到 128 变化时 SNR 怎么变,可以写一个外层循环,每次改OSR跑一次仿真,把结果存到数组里,最后画一条曲线。这样比手动改参数跑多次效率高得多。

OSR_list = [16, 32, 64, 128]; SNR_list = zeros(size(OSR_list)); for k = 1:length(OSR_list) OSR = OSR_list(k); % 这里调用仿真函数,返回 SNR SNR_list(k) = run_simulation(OSR, nbits, Ain); end figure; plot(OSR_list, SNR_list, 'o-'); xlabel('过采样比'); ylabel('SNR (dB)'); grid on;

这段代码的关键是把仿真逻辑封装成函数run_simulation,输入参数,输出 SNR。封装的时候注意把状态变量和输出序列都放在函数内部,避免污染工作区。跑完画图,理论上 OSR 每翻一倍,SNR 提升 9 dB 左右,三阶调制器因为噪声整形斜率更陡,提升会更明显。如果曲线不符合预期,回去检查系数映射和量化器建模。

从那以后我每次改环路系数,都会先跑一个短点数仿真确认噪声整形斜率,再跑长点数出最终结果。这个习惯帮我省了很多次重新跑几十分钟的麻烦。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进