cicadc

ANALOG
NOISE
PRE-FILTER
NYQUIST ADC
FILTER ×1
DIGITAL
ADC MODE
SIGNAL
1.00 Hz
0.80 FS
1.0×
0.00
QUANTISER
3 b
0.25 s
FILTER (ΣΔ)
1
SincM decimator (M = order+1)
Nyquist: no filter
BLOCKER
1.50 Hz
0.40 FS
PRE-FILTER
0.50 Nyq = 1.00 Hz
Analog signal
Sample instants
Raw ADC output
Filtered digital
Quant. error
FFT signal bin
Blocker / FFT blocker bin
Post-filter / FFT analog input

VIDEO INTRODUCTION

INTRODUCTION TO ADCs

An Analog-to-Digital Converter (ADC) is the bridge between the continuous physical world and the discrete digital world. A microphone, a temperature sensor, a radio receiver — all produce analog values that vary smoothly over time. A processor or digital logic can only work with numbers. The ADC samples that analog signal at regular intervals and assigns each sample a binary code, turning sound, light of all frequencies, and temperature into bits.

Two properties define what an ADC can represent:

Quantization noise is the rounding error introduced every time a continuous value is mapped to the nearest discrete level. For a full-scale sine wave in an ideal N-bit ADC the signal-to-noise ratio is approximately 6.02·N + 1.76 dB. The animated visualiser above lets you watch this tradeoff live: drag the Bits slider and watch the staircase steps (and the noise strip) change.

NYQUIST ADC vs SIGMA–DELTA

There are two fundamentally different strategies for digitising a signal. The visualiser implements both so you can compare them side by side.

Nyquist-rate ADC

A Nyquist ADC samples the input at one sample per interval. Every sample uses all N bits of resolution. This is what a classic successive-approximation (SAR) or pipeline ADC does. Achieving high resolution requires precise analog components (matched capacitors, accurate references), which become harder to build as process nodes shrink.

In the visualiser: select Nyquist, set Filter taps K = 1. Each yellow dot becomes one step on the digital staircase. The noise strip shows the rounding error — bounded by ±½ LSB.

Sigma–Delta (ΣΔ) ADC

A sigma–delta ADC takes the opposite approach: sample much faster than strictly necessary (oversampling), but use a very coarse quantizer — even just 1 bit. A feedback loop called the modulator shapes the quantization noise so that it is pushed out of the signal band, into high frequencies where a digital decimation filter can remove it. The result can achieve very high effective resolution using only simple, well-matched digital circuits. See Oversampling and Sigma-Delta ADCs for more information.

The tradeoff: ΣΔ ADCs trade speed for resolution. They are ideal for audio (24-bit at 192 kHz), sensor readout, and instrumentation — but not for wideband RF receivers (base-stations) that need simultaneous high speed and high resolution.

In the visualiser: select ΣΔ 1st or ΣΔ 2nd, then increase Filter taps K. Watch the pale-green coarse stream bounce rail-to-rail while the white filtered output smoothly tracks the analog sine. The noise strip collapses as K grows.

1st-order vs 2nd-order modulator

The order of the sigma–delta modulator sets how aggressively noise is shaped:

OrderNoise shapingIn-band noise reduction per octave of OSRTypical use
1st(1 − z⁻¹)9 dBSimple, always stable
2nd(1 − z⁻¹)²15 dBExcellent SNR, needs careful design
3rd (LF)(1 − z⁻¹)³ + spread zeros21 dBLeapfrog ladder, tuned coefficients

The decimation filter is a sincM cascade (M = modulator order + 1), matched to the noise shaping to maximise stop-band rejection. A higher oversampling ratio K also helps: each doubling of K reduces in-band noise by an extra 3 dB beyond the shaping benefit.

Leapfrog and control-bounded modes

The LF 3rd button selects a 3rd-order single-loop leapfrog modulator: a chain of three integrators with a resonator cross-coupling that moves a pair of noise-transfer-function zeros off DC into the signal band, flattening the in-band noise floor. It is reconstructed by the usual sinc4 decimation filter.

The LF CB button selects the control-bounded leapfrog: instead of one quantizer in a loop, every integrator carries its own local 1-bit control — which is what guarantees the loop stays bounded — and the input is recovered by a digital estimation filter whose taps come from forward/backward Riccati equations. There is no sinc decimator; the only latency is a half-sample group delay, because each 1-bit control decision acts over the following control period. It needs heavy oversampling, so selecting it automatically lowers the signal frequency. The digital panel draws the three control bit-streams (s₁, s₂, s₃) as thin staircases near the rails — they are the converter's actual digital output — with the white staircase being the estimate computed from all of them.

STATE-SPACE FORMULATION

The maths below follows the unified continuous-time (CT) ADC analysis of Feyling, Malmberg, Wulff & Ytterdal, “A Unified Analysis of Continuous-Time A/D Converters” (TechRxiv, 2024). In that framework every conventional CT ADC is a loop filter \(G(s)\) feeding a back-end quantizer \(Q\) — modelled as a sampler at rate \(f_s\) with additive quantization noise \(q[n]\) — whose output is fed back through one or more DACs. With \(\tilde{H}(z)\) the transfer function from the DAC input to the quantizer output, the noise- and signal-transfer functions are

\[ \mathrm{NTF}(e^{j\omega T}) = \frac{1}{1 - \tilde{H}(e^{j\omega T})}, \qquad \mathrm{STF}(j\omega) = \frac{G(j\omega)}{1 - \tilde{H}(e^{j\omega T})}. \]

The \(\mathrm{STF}\) supplies the inherent anti-alias filtering of a CT ADC: for an integrator \(G(s) = 1/s\) it is a sinc with nulls at multiples of \(f_s\). The Leapfrog architecture of the paper builds \(G(s)\) from a chain of \(N\) integrators (forward gain \(\beta\), backward gain \(\alpha\)), each stabilised by a local single-bit digital feedback loop of gain \(\kappa\) — a control-bounded state-space system \(\dot{\mathbf{x}}(t) = \mathbf{A}\,\mathbf{x}(t) + \mathbf{B}\,u(t) + \boldsymbol{\Gamma}\,\mathbf{s}(t)\).

This tool implements the discrete-time, low-order relatives of that picture. Writing the state vector \(\mathbf{x}[n]\), input \(u[n]\) and fed-back quantized output \(y[n-1]\), each modulator advances as

\[ \mathbf{x}[n] = \mathbf{A}\,\mathbf{x}[n-1] + \mathbf{B}\,u[n] + \boldsymbol{\Gamma}\,y[n-1], \qquad y[n] = Q\!\left(\mathbf{c}^{\!\top}\mathbf{x}[n]\right), \]

where \(Q\) is a mid-rise quantizer with \(2^{\text{bits}}\) levels spread over \([-v_\text{ref}, v_\text{ref}]\) (a 1-bit quantizer returns only \(\pm v_\text{ref}\)).

Nyquist ADC

No loop filter and no analog state: \(\tilde{H}(z) = 0\), so \(\mathrm{NTF} = 1\) (the quantization noise is white and unshaped) and the converter is memoryless — \(\mathbf{A}\), \(\mathbf{B}\), \(\boldsymbol{\Gamma}\) vanish:

\[ y[n] = Q\big(u[n]\big). \]

1st-order ΣΔ

A single delaying integrator accumulates the error between the input and the fed-back output. The state \(x\) is scalar, with \(\mathbf{A}=[\,1\,]\), \(\mathbf{B}=[\,1\,]\), \(\boldsymbol{\Gamma}=[-1]\), \(\mathbf{c}=[\,1\,]\):

\[ x[n] = x[n-1] + u[n] - y[n-1], \qquad y[n] = Q\big(x[n]\big). \]

The loop transfer function \(\tilde{H}(z) = z^{-1}\) gives, via the relation above, a first-order shaping \(\mathrm{NTF}(z) = 1 - z^{-1}\).

2nd-order ΣΔ (CIFB)

The standard cascade-of-integrators feedback (CIFB) structure with two integrators and feedback coefficients \(a_1 = 1,\ a_2 = 2\) (and \(b_1 = 1,\ g_1 = 1\)). With the state \(\mathbf{x} = [\,x_1,\ x_2\,]^{\top}\), substituting the first integrator into the second gives the closed-form update:

\[ \begin{bmatrix} x_1[n] \\ x_2[n] \end{bmatrix} = \begin{bmatrix} 1 & 0 \\ 1 & 1 \end{bmatrix} \begin{bmatrix} x_1[n-1] \\ x_2[n-1] \end{bmatrix} + \begin{bmatrix} 1 \\ 1 \end{bmatrix} u[n] + \begin{bmatrix} -1 \\ -3 \end{bmatrix} y[n-1], \qquad y[n] = Q\!\left(\mathbf{c}^{\!\top}\mathbf{x}[n]\right),\ \ \mathbf{c}^{\!\top} = [\,0\ \ 1\,]. \]

The unit sub-diagonal of \(\mathbf{A}\) is the chain feed-forward (\(x_1\) into \(x_2\)) — the discrete two-stage analogue of the Leapfrog integrator chain; the feedback column \(\boldsymbol{\Gamma} = [-1,\ -3]^{\top}\) collects \(-a_1\) at the first integrator and \(-(a_1 + a_2)\) at the second. This realises the second-order shaping \(\mathrm{NTF}(z) = (1 - z^{-1})^2\), which pushes far more quantization noise out of band than the first-order loop.

3rd-order leapfrog (single loop)

The LF 3rd mode chains three delaying integrators with the quantized output fed back to every stage (gains \(a_1 = 0.2,\ a_2 = 0.4,\ a_3 = 1.2\)), plus a resonator cross-coupling \(g = 0.004\) that feeds \(x_3\) back into \(x_2\):

\[ \begin{aligned} x_1[n] &= x_1[n-1] + b_1 u[n] - a_1\,y[n-1] \\ x_2[n] &= x_2[n-1] + x_1[n] - a_2\,y[n-1] - g\,x_3[n-1] \\ x_3[n] &= x_3[n-1] + x_2[n] - a_3\,y[n-1], \qquad y[n] = Q\big(x_3[n]\big). \end{aligned} \]

Substituting each stage into the next gives the closed-form state-space matrices

\[ \mathbf{A} = \begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & -g \\ 1 & 1 & 1-g \end{bmatrix}, \quad \mathbf{B} = b_1\!\begin{bmatrix} 1 \\ 1 \\ 1 \end{bmatrix}, \quad \boldsymbol{\Gamma} = \begin{bmatrix} -a_1 \\ -(a_1{+}a_2) \\ -(a_1{+}a_2{+}a_3) \end{bmatrix} = \begin{bmatrix} -0.2 \\ -0.6 \\ -1.8 \end{bmatrix}, \quad \mathbf{c}^{\!\top} = [\,0\ \ 0\ \ 1\,]. \]

The resonator moves a pair of NTF zeros off DC to \(\omega \approx \pm\sqrt{g} \approx \pm 0.06\) rad/sample, spreading the in-band notch the way an Nth-order leapfrog ladder does. The input scaling \(b_1 = a_1/2 = 0.1\) keeps the loop stable to a full-scale input; the resulting signal-transfer gain of \(\tfrac{1}{2}\) is measured by the lock-in \(|\mathrm{STF}|\) and divided out by the reconstruction filter.

Control-bounded leapfrog

The LF CB mode abandons the single quantizer entirely. The leapfrog ladder (forward gain \(\beta = 0.4\), backward coupling \(\alpha = 0.004\)) runs with a local 1-bit control on every integrator:

\[ \mathbf{x}[n{+}1] = \mathbf{A}_d\,\mathbf{x}[n] + \mathbf{B}_d\,u[n] + \boldsymbol{\Gamma}\,\mathbf{s}[n], \qquad s_\ell[n] = \mathrm{sign}\big(x_\ell[n]\big), \] \[ \mathbf{A}_d = \begin{bmatrix} 1 & -\alpha & 0 \\ \beta & 1 & -\alpha \\ 0 & \beta & 1 \end{bmatrix}, \qquad \mathbf{B}_d = \begin{bmatrix} \beta \\ 0 \\ 0 \end{bmatrix}, \qquad \boldsymbol{\Gamma} = -\kappa\,\mathbf{I},\ \ \kappa = 0.8 . \]

Because every integrator is individually steered back toward zero, the states are bounded by construction — no overload, whatever the input. The price is that no single bit stream is the output: the input must be estimated from all three control sequences. The estimate is a non-causal FIR over a window of \(\pm K\) control samples,

\[ \hat{u}[n] = \sum_{j=0}^{K-1} \mathbf{h}_f[j]^{\!\top}\,\mathbf{s}[n{-}1{-}j] + \sum_{k=0}^{K-1} \mathbf{h}_b[k]^{\!\top}\,\mathbf{s}[n{+}k], \]

whose taps come from the steady-state Wiener (smoothing) solution: two algebraic Riccati equations

\[ \mathbf{A}\mathbf{V}_f + \mathbf{V}_f\mathbf{A}^{\!\top} - \tfrac{1}{\eta^2}\mathbf{V}_f\mathbf{V}_f + \mathbf{B}\mathbf{B}^{\!\top} = 0, \qquad -\mathbf{A}\mathbf{V}_b - \mathbf{V}_b\mathbf{A}^{\!\top} - \tfrac{1}{\eta^2}\mathbf{V}_b\mathbf{V}_b + \mathbf{B}\mathbf{B}^{\!\top} = 0, \]

give the forward/backward closed-loop matrices \(\mathbf{A}_f = e^{(\mathbf{A} - \mathbf{V}_f/\eta^2)T}\), \(\mathbf{A}_b = e^{-(\mathbf{A} + \mathbf{V}_b/\eta^2)T}\) and \(\mathbf{W} = (\mathbf{V}_f + \mathbf{V}_b)^{-1}\mathbf{B}\), from which \(\mathbf{h}_f[j] = -\mathbf{B}_f^{\!\top}(\mathbf{A}_f^{\!\top})^{j}\,\mathbf{W}\) and \(\mathbf{h}_b[k] = -\mathbf{B}_b^{\!\top}(\mathbf{A}_b^{\!\top})^{k}\,\mathbf{W}\) (this page solves the Riccati equations live, by Kleinman–Newton iteration). The bandwidth parameter \(\eta^2 = |G(e^{j\omega_B})|^2\) sets the estimator band edge; the resulting signal transfer

\[ \mathrm{STF}(\omega) = \frac{|G(\omega)|^2}{|G(\omega)|^2 + \eta^2} \]

is real — a zero-phase smoother. The only latency left is half a control period, \(T/2\), because each decision \(s_\ell[n]\) acts over the following hold interval \([nT, (n{+}1)T)\); the visualiser compensates exactly this delay.

HOW THE CODE WORKS

cicadc began as a Python/Manim desktop app for teaching ADC concepts in lectures. The core simulation lives in src/cicadc/; the browser visualiser in docs/index.html is a pure HTML5 + JavaScript reimplementation that runs entirely in the browser with no server.

Signal chain

The simulation mirrors the real analog signal chain shown at the top of the page:

Rendering

Everything is drawn each frame onto a single <canvas> element scaled by devicePixelRatio for sharp rendering on high-DPI screens. Four panels share the canvas:

The layout is fully responsive: on screens narrower than 700 px the four panels stack vertically and all fonts and line widths scale with a pxf factor derived from the panel width.

HISTORY

The project started on 5 June 2026 with a Python/Manim desktop application designed to animate the ADC conversion process for classroom use — inspired by an idea from Domen Visnar. Within a day, a 1st-order sigma–delta modulator and slider controls were added, followed by 2nd-order ΣΔ with sincN decimation, a quantization noise strip, and an FFT display (v0.3.0).

On 6 June 2026 the app gained cross-platform binary builds (macOS, Linux, Windows via PyInstaller), a drag-to-Applications macOS DMG, and MP4 recording — making it easy to distribute and use in lecture recordings.

Also on 6–7 June 2026 a native Swift iOS app was added so the visualiser could run on an iPad or iPhone without any desktop software. The iOS version reproduces the full signal chain — ΣΔ modulators, decimation filters, FFT — in Swift, and introduced the pixel-art car sprites that now animate along the signal curve on all platforms.

On 8 June 2026 the browser version (docs/index.html) was created — a single self-contained HTML file that runs in any modern browser with no installation. It brought responsive mobile layout, high-DPI canvas rendering, and a series of accuracy improvements: the STF lock-in gain correction, proper group-delay compensation, and a post-filter noise strip that correctly shows the residual after reconstruction. The browser version is what you see at the top of this page.