ADCToolbox Usage Guide
Router, not a full manual. Keep the basic tier resident; open
references/*.md only when you need more.
1. When to use (and not to use)
Use for:
- Writing, fixing, or reviewing Python that calls ADCToolbox APIs
- Picking the right spectrum / calibration helper
- Getting from a raw
dout / aout buffer to SNDR / SFDR / ENOB
- Generating synthetic ADC stimulus for a testbench
- Forward-modeling an ADC architecture in Python — for SAR, use
adctoolbox.models.sar_convert / sar_reconstruct / sar_ideal_weights
/ sar_apply_cap_mismatch (binary or sub-radix-2, with optional unit-cap
mismatch + sampling noise + comparator noise; vectorized). Convention: vin is
interpreted relative to quant_range=(v_min, v_max), default (0, 1).
SAR weights are still explicit and normalized by sum(bit_weights) + 1 LSB
(for example [8, 4, 2, 1] / 16, or redundant [8, 4, 4, 2, 1] / 20).
For differential SAR, pass VIP - VIN with a differential quant_range
such as (-VDD, VDD). Keep analog CDAC weights and digital reconstruction
weights explicit; they match unless modeling mismatch or calibration.
Do NOT use for:
- Analog topology / transistor design →
analog-design, analog-explore
- Spectre simulation, pre/post-layout audit →
analog-verify, analog-audit
- Editing ADCToolbox's own source →
adctoolbox-contributor-guide
2. Critical conventions (read first — these are the common bug sources)
Names
bits is the per-sample binary decision matrix shape (N_samples, N_bits),
values in {0, 1} — used by every digital-calibration / bit-level helper.
aout is the analog output (1D float array) — used by spectrum and
error-analysis helpers.
- ADC integer codes are not
bits; convert separately if your data is
packed as integers.
Frequency units
fs, Fin, plotting frequencies: Hz.
fit_sine_4param(...)["frequency"]: normalized Fin/Fs (range 0–0.5),
not Hz.
calibrate_weight_sine, calibrate_weight_sine_lite, analyze_enob_sweep,
generate_dout_dashboard: freq parameter is normalized Fin/Fs.
generate_aout_dashboard: freq is in Hz (it normalizes internally).
analyze_spectrum does NOT take Fin — it auto-detects the fundamental
from the FFT.
Return shapes
Most analysis functions return dict. Notable exceptions and dict-key gotchas:
| Function |
Return |
analyze_spectrum, analyze_spectrum_polar, analyze_spectrum_virtuoso |
dict — keys: enob, sndr_dbc, sfdr_dbc, snr_dbc, thd_dbc, sig_pwr_dbfs, noise_floor_dbfs, nsd_dbfs_hz, harmonics_dbc |
quick_sndr |
dict — minimal: only sndr_dbc, enob. No SFDR/THD/HD/NSD breakdown. |
compute_spectrum |
dict — top-level keys metrics (same as above) and plot_data (freq, power_spectrum_db_plot, complex_spectrum, fundamental_bin, …) |
fit_sine_4param |
dict — frequency (normalized), amplitude, phase, dc_offset, rmse, fitted_signal, residuals |
find_coherent_frequency |
tuple (fin_actual_hz, best_bin) |
calibrate_weight_sine |
dict — weight, offset, calibrated_signal, ideal, error, refined_frequency |
calibrate_weight_sine_lite |
ndarray (weights only) |
analyze_bit_activity |
ndarray (% of 1's per bit, length = N_bits) |
analyze_overflow |
tuple of 4 ndarrays (range_min, range_max, ovf_pct_zero, ovf_pct_one) |
analyze_enob_sweep |
tuple (enob_sweep, n_bits_vec) |
analyze_weight_radix |
dict — radix, wgtsca, effres (weight-list resolution estimate) |
fit_static_nonlin |
tuple (k2, k3, fitted_sine, fitted_transfer) |
convert_cap_to_weight |
tuple (weights, c_total) |
analyze_weight_radix(weights)["effres"] is computed from significant
absolute weights as log2(sum(abs_w_sig) / min(abs_w_sig) + 1). It estimates
the theoretical span of the supplied SAR/DAC weight list; it is not a
missing-code, DNL/INL, or SAR-reachability check.
calibrate_weight_sine(...) returns solver-unit-sine scaled waveform fields by
default (scale_convention == "solver_unit_sine"). Use
scale_calibration_output(...) before interpreting calibrated
sig_pwr_dbfs, noise_floor_dbfs, or nsd_dbfs_hz against a known ADC/code
full-scale.
When docs conflict, trust the current __init__.py exports + the
tests/integration/test_user_guide_skill_examples.py smoke tests.
3. Basic workflow — spectrum
from adctoolbox import (
analyze_spectrum, analyze_spectrum_polar,
find_coherent_frequency, fit_sine_4param,
)
from adctoolbox.fundamentals import validate_aout_data
validate_aout_data(aout)
metrics = analyze_spectrum(aout, fs=fs, create_plot=False)
print(metrics["sndr_dbc"], metrics["sfdr_dbc"], metrics["enob"])
For subplot layouts, pass the target Matplotlib axes directly. Do this before
falling back to custom FFT plotting:
fig, axes = plt.subplots(3, 1)
for ax, trace in zip(axes, traces):
metrics = analyze_spectrum(trace, fs=fs, create_plot=True, ax=ax)
Side-bin defaults differ by entry point. analyze_spectrum and
compute_spectrum use waveform-based auto detection when side_bin=None.
quick_sndr keeps side_bin=None as the coherent-main-lobe fast path; pass
side_bin="auto" when you want its SNDR/ENOB-only path to match the analyzer's
auto side-bin selection for a dominant non-coherent single tone. Auto detection
adds an ideal-tone FFT pass, so keep explicit/coherent side bins in hot
optimization loops when the capture setup allows it.
To set up a coherent capture upstream (where you control the stimulus
frequency), snap Fin to an FFT bin first:
fin_hz, k_bin = find_coherent_frequency(fs, fin_target_hz, n_fft=len(aout))
# now drive the test with fin_hz
Pick the variant by output:
analyze_spectrum — magnitude spectrum + SNDR/SFDR/ENOB/THD metrics dict
(default — annotated white-bg plot, Hann window)
analyze_spectrum_virtuoso — same metrics, but Cadence Virtuoso /
ADE-Explorer dark-theme stem plot. Defaults to rectangular window
(one stem = one bin, no main-lobe smearing).
analyze_spectrum_polar — phase-aware (I/Q or mixer contexts); same keys
quick_sndr — lean SNDR + ENOB only. Use in optimization loops,
parameter sweeps, spec gates. No plot, no SFDR/THD/HD/NSD breakdown.
Returns just {sndr_dbc, enob}. Its default side_bin=None is the
coherent fast path; side_bin="auto" is available when non-coherent
single-tone accuracy matters more than speed.
compute_spectrum (from adctoolbox.spectrum) — both metrics and plot-ready
data (access via result["plot_data"]["freq"] etc.)
find_coherent_frequency — pre-step at signal generation time, not analysis
fit_sine_4param — pre-step for nonlinearity work; remember its
"frequency" key is normalized Fin/Fs
For the lean path:
from adctoolbox import quick_sndr
m = quick_sndr(aout, fs=fs)
print(m["sndr_dbc"], m["enob"])
# For a dominant non-coherent single tone, opt into analyzer-style side-bin
# detection. This is slower than the coherent default.
m_auto = quick_sndr(aout, fs=fs, side_bin="auto")
# Override the window when the upstream stimulus is coherent and
# you want a clean rectangular FFT instead of Hann:
m = quick_sndr(aout, fs=fs, win_type='rectangular')
4. Basic workflow — digital calibration
from adctoolbox import calibrate_weight_sine, scale_calibration_output
from adctoolbox.calibration import calibrate_weight_sine_lite
from adctoolbox.fundamentals import validate_dout_data
validate_dout_data(bits) # bits: (N_samples, N_bits) in {0, 1}
freq_norm = fin_hz / fs # normalized — not Hz
result = calibrate_weight_sine(bits, freq=freq_norm)
weights = result["weight"]
calibrated = result["calibrated_signal"]
# Map solver-unit-sine calibration output back to an ADC/code convention
# before interpreting dBFS, noise floor, or NSD.
# Use the same nominal ADC/code weights that define the desired full scale.
result_adc = scale_calibration_output(result, target_weights=nominal_weights)
calibrated_adc = result_adc["calibrated_signal"]
weights_fast = calibrate_weight_sine_lite(bits, freq_norm) # ndarray, no dict
calibrate_weight_sine returns a dict with weight, offset,
calibrated_signal, ideal, error, refined_frequency. The _lite variant
returns just the weights ndarray and is positional (no freq= kw).
calibrated_signal is in solver-unit-sine scale unless explicitly rescaled.
Ratio metrics are scale-invariant; sig_pwr_dbfs, noise_floor_dbfs, and
nsd_dbfs_hz are not.
max_scale_range=None is self-referenced, so calibrated dBFS can look tidy
while still having no physical ADC full-scale meaning. Supply an explicit
range when interpreting calibrated spectra against an ADC/code full-scale.
If freq is omitted, calibrate_weight_sine estimates the tone frequency and
then fine-searches it against the calibration residual. If the coherent
training frequency is already known, pass freq=k/N to keep that exact
frequency fixed; use force_search=True only when you deliberately want to
refine a provided frequency.
5. Import rules (compressed)
| Kind |
Use |
Anything re-exported by adctoolbox.__init__ |
from adctoolbox import X |
Submodule-only public tool (siggen, toolset, aout, calibration, fundamentals, spectrum) |
from adctoolbox.<submodule> import X |
If a flat import fails, check the submodule's __init__.py before
concluding the tool is gone. Common submodule-only names:
ADC_Signal_Generator (siggen), compute_spectrum (spectrum),
calibrate_weight_sine_lite (calibration), validate_aout_data /
validate_dout_data / convert_cap_to_weight (fundamentals),
analyze_phase_plane / analyze_error_phase_plane (aout),
generate_aout_dashboard / generate_dout_dashboard (toolset).
6. Going further
- Dashboards, phase-plane, bit-level, error decomposition, static
nonlinearity, ramp INL/DNL, cap-to-weight →
references/advanced-debug.md
- Function signatures / return keys →
references/api-quickref.md
- Ready-to-adapt example files →
references/example-map.md
Highly Recommended Baseline: For the simplest end-to-end analysis
- plot template, adapt
02_spectrum/exp_s03_analyze_spectrum_savefig.py
(see references/example-map.md for the path). The packaged CLI
adctoolbox-get-examples [dest] dumps the full example tree.
Every code block in this file (and in references/advanced-debug.md) is
exercised by python/tests/integration/test_user_guide_skill_examples.py
— if a future edit breaks one, that test fails.
1---2name: adctoolbox-user-guide3description: Router skill for using ADCToolbox from Python. Trigger when a task involves: computing or plotting spectra (SNDR, SFDR, ENOB, THD) from ADC output, fitting a sine to measured aout, calibrating SAR weights (weight_sine / weight_sine_lite), generating synthetic ADC stimulus/output, or validating aout/dout buffer shapes. For deeper debug (dashboards, phase-plane, bit-level, error decomposition, static nonlinearity, ramp INL/DNL, cap-to-weight), open references/advanced-debug.md. NOT for analog topology selection, transistor sizing, Spectre simulation, or layout/parasitic review — those belong to the analog-agents skills (analog-design, analog-verify, analog-audit). NOT for editing ADCToolbox source code — use adctoolbox-contributor-guide instead.4---56# ADCToolbox Usage Guide78Router, not a full manual. Keep the basic tier resident; open9`references/*.md` only when you need more.1011## 1. When to use (and not to use)1213Use for:14- Writing, fixing, or reviewing Python that calls ADCToolbox APIs15- Picking the right spectrum / calibration helper16- Getting from a raw `dout` / `aout` buffer to SNDR / SFDR / ENOB17- Generating synthetic ADC stimulus for a testbench18- **Forward-modeling an ADC architecture in Python** — for SAR, use19 `adctoolbox.models.sar_convert` / `sar_reconstruct` / `sar_ideal_weights`20 / `sar_apply_cap_mismatch` (binary or sub-radix-2, with optional unit-cap21 mismatch + sampling noise + comparator noise; vectorized). Convention: `vin` is22 interpreted relative to `quant_range=(v_min, v_max)`, default `(0, 1)`.23 SAR weights are still explicit and normalized by `sum(bit_weights) + 1 LSB`24 (for example `[8, 4, 2, 1] / 16`, or redundant `[8, 4, 4, 2, 1] / 20`).25 For differential SAR, pass `VIP - VIN` with a differential `quant_range`26 such as `(-VDD, VDD)`. Keep analog CDAC weights and digital reconstruction27 weights explicit; they match unless modeling mismatch or calibration.2829Do NOT use for:30- Analog topology / transistor design → `analog-design`, `analog-explore`31- Spectre simulation, pre/post-layout audit → `analog-verify`, `analog-audit`32- Editing ADCToolbox's own source → `adctoolbox-contributor-guide`3334## 2. Critical conventions (read first — these are the common bug sources)3536### Names3738- **`bits`** is the per-sample binary decision matrix shape `(N_samples, N_bits)`,39 values in `{0, 1}` — used by every digital-calibration / bit-level helper.40- **`aout`** is the analog output (1D `float` array) — used by spectrum and41 error-analysis helpers.42- ADC integer codes are *not* `bits`; convert separately if your data is43 packed as integers.4445### Frequency units4647- `fs`, `Fin`, plotting frequencies: **Hz**.48- `fit_sine_4param(...)["frequency"]`: **normalized** `Fin/Fs` (range 0–0.5),49 **not** Hz.50- `calibrate_weight_sine`, `calibrate_weight_sine_lite`, `analyze_enob_sweep`,51 `generate_dout_dashboard`: `freq` parameter is **normalized** `Fin/Fs`.52- `generate_aout_dashboard`: `freq` is in **Hz** (it normalizes internally).53- `analyze_spectrum` does NOT take `Fin` — it auto-detects the fundamental54 from the FFT.5556### Return shapes5758Most analysis functions return `dict`. Notable exceptions and dict-key gotchas:5960| Function | Return |61|---|---|62| `analyze_spectrum`, `analyze_spectrum_polar`, `analyze_spectrum_virtuoso` | `dict` — keys: `enob`, `sndr_dbc`, `sfdr_dbc`, `snr_dbc`, `thd_dbc`, `sig_pwr_dbfs`, `noise_floor_dbfs`, `nsd_dbfs_hz`, `harmonics_dbc` |63| `quick_sndr` | `dict` — minimal: only `sndr_dbc`, `enob`. No SFDR/THD/HD/NSD breakdown. |64| `compute_spectrum` | `dict` — top-level keys `metrics` (same as above) and `plot_data` (`freq`, `power_spectrum_db_plot`, `complex_spectrum`, `fundamental_bin`, …) |65| `fit_sine_4param` | `dict` — `frequency` (normalized), `amplitude`, `phase`, `dc_offset`, `rmse`, `fitted_signal`, `residuals` |66| `find_coherent_frequency` | `tuple (fin_actual_hz, best_bin)` |67| `calibrate_weight_sine` | `dict` — `weight`, `offset`, `calibrated_signal`, `ideal`, `error`, `refined_frequency` |68| `calibrate_weight_sine_lite` | `ndarray` (weights only) |69| `analyze_bit_activity` | `ndarray` (% of 1's per bit, length = N_bits) |70| `analyze_overflow` | `tuple` of 4 ndarrays `(range_min, range_max, ovf_pct_zero, ovf_pct_one)` |71| `analyze_enob_sweep` | `tuple (enob_sweep, n_bits_vec)` |72| `analyze_weight_radix` | `dict` — `radix`, `wgtsca`, `effres` (weight-list resolution estimate) |73| `fit_static_nonlin` | `tuple (k2, k3, fitted_sine, fitted_transfer)` |74| `convert_cap_to_weight` | `tuple (weights, c_total)` |7576`analyze_weight_radix(weights)["effres"]` is computed from significant77absolute weights as `log2(sum(abs_w_sig) / min(abs_w_sig) + 1)`. It estimates78the theoretical span of the supplied SAR/DAC weight list; it is not a79missing-code, DNL/INL, or SAR-reachability check.8081`calibrate_weight_sine(...)` returns solver-unit-sine scaled waveform fields by82default (`scale_convention == "solver_unit_sine"`). Use83`scale_calibration_output(...)` before interpreting calibrated84`sig_pwr_dbfs`, `noise_floor_dbfs`, or `nsd_dbfs_hz` against a known ADC/code85full-scale.8687When docs conflict, trust the current `__init__.py` exports + the88`tests/integration/test_user_guide_skill_examples.py` smoke tests.8990## 3. Basic workflow — spectrum9192```python93from adctoolbox import (94 analyze_spectrum, analyze_spectrum_polar,95 find_coherent_frequency, fit_sine_4param,96)97from adctoolbox.fundamentals import validate_aout_data9899validate_aout_data(aout)100metrics = analyze_spectrum(aout, fs=fs, create_plot=False)101print(metrics["sndr_dbc"], metrics["sfdr_dbc"], metrics["enob"])102```103104For subplot layouts, pass the target Matplotlib axes directly. Do this before105falling back to custom FFT plotting:106107```python108fig, axes = plt.subplots(3, 1)109for ax, trace in zip(axes, traces):110 metrics = analyze_spectrum(trace, fs=fs, create_plot=True, ax=ax)111```112113Side-bin defaults differ by entry point. `analyze_spectrum` and114`compute_spectrum` use waveform-based auto detection when `side_bin=None`.115`quick_sndr` keeps `side_bin=None` as the coherent-main-lobe fast path; pass116`side_bin="auto"` when you want its SNDR/ENOB-only path to match the analyzer's117auto side-bin selection for a dominant non-coherent single tone. Auto detection118adds an ideal-tone FFT pass, so keep explicit/coherent side bins in hot119optimization loops when the capture setup allows it.120121To set up a coherent capture *upstream* (where you control the stimulus122frequency), snap `Fin` to an FFT bin first:123124```python125fin_hz, k_bin = find_coherent_frequency(fs, fin_target_hz, n_fft=len(aout))126# now drive the test with fin_hz127```128129Pick the variant by output:130- `analyze_spectrum` — magnitude spectrum + SNDR/SFDR/ENOB/THD metrics dict131 (default — annotated white-bg plot, Hann window)132- `analyze_spectrum_virtuoso` — same metrics, but Cadence Virtuoso /133 ADE-Explorer dark-theme stem plot. Defaults to rectangular window134 (one stem = one bin, no main-lobe smearing).135- `analyze_spectrum_polar` — phase-aware (I/Q or mixer contexts); same keys136- `quick_sndr` — **lean** SNDR + ENOB only. Use in optimization loops,137 parameter sweeps, spec gates. No plot, no SFDR/THD/HD/NSD breakdown.138 Returns just `{sndr_dbc, enob}`. Its default `side_bin=None` is the139 coherent fast path; `side_bin="auto"` is available when non-coherent140 single-tone accuracy matters more than speed.141- `compute_spectrum` (from `adctoolbox.spectrum`) — both metrics and plot-ready142 data (access via `result["plot_data"]["freq"]` etc.)143- `find_coherent_frequency` — pre-step at *signal generation* time, not analysis144- `fit_sine_4param` — pre-step for nonlinearity work; remember its145 `"frequency"` key is normalized `Fin/Fs`146147For the lean path:148149```python150from adctoolbox import quick_sndr151m = quick_sndr(aout, fs=fs)152print(m["sndr_dbc"], m["enob"])153# For a dominant non-coherent single tone, opt into analyzer-style side-bin154# detection. This is slower than the coherent default.155m_auto = quick_sndr(aout, fs=fs, side_bin="auto")156# Override the window when the upstream stimulus is coherent and157# you want a clean rectangular FFT instead of Hann:158m = quick_sndr(aout, fs=fs, win_type='rectangular')159```160161## 4. Basic workflow — digital calibration162163```python164from adctoolbox import calibrate_weight_sine, scale_calibration_output165from adctoolbox.calibration import calibrate_weight_sine_lite166from adctoolbox.fundamentals import validate_dout_data167168validate_dout_data(bits) # bits: (N_samples, N_bits) in {0, 1}169170freq_norm = fin_hz / fs # normalized — not Hz171result = calibrate_weight_sine(bits, freq=freq_norm)172weights = result["weight"]173calibrated = result["calibrated_signal"]174175# Map solver-unit-sine calibration output back to an ADC/code convention176# before interpreting dBFS, noise floor, or NSD.177# Use the same nominal ADC/code weights that define the desired full scale.178result_adc = scale_calibration_output(result, target_weights=nominal_weights)179calibrated_adc = result_adc["calibrated_signal"]180181weights_fast = calibrate_weight_sine_lite(bits, freq_norm) # ndarray, no dict182```183184`calibrate_weight_sine` returns a dict with `weight`, `offset`,185`calibrated_signal`, `ideal`, `error`, `refined_frequency`. The `_lite` variant186returns just the weights ndarray and is positional (no `freq=` kw).187`calibrated_signal` is in solver-unit-sine scale unless explicitly rescaled.188Ratio metrics are scale-invariant; `sig_pwr_dbfs`, `noise_floor_dbfs`, and189`nsd_dbfs_hz` are not.190`max_scale_range=None` is self-referenced, so calibrated dBFS can look tidy191while still having no physical ADC full-scale meaning. Supply an explicit192range when interpreting calibrated spectra against an ADC/code full-scale.193If `freq` is omitted, `calibrate_weight_sine` estimates the tone frequency and194then fine-searches it against the calibration residual. If the coherent195training frequency is already known, pass `freq=k/N` to keep that exact196frequency fixed; use `force_search=True` only when you deliberately want to197refine a provided frequency.198199## 5. Import rules (compressed)200201| Kind | Use |202|---|---|203| Anything re-exported by `adctoolbox.__init__` | `from adctoolbox import X` |204| Submodule-only public tool (`siggen`, `toolset`, `aout`, `calibration`, `fundamentals`, `spectrum`) | `from adctoolbox.<submodule> import X` |205206If a flat import fails, check the submodule's `__init__.py` before207concluding the tool is gone. Common submodule-only names:208`ADC_Signal_Generator` (siggen), `compute_spectrum` (spectrum),209`calibrate_weight_sine_lite` (calibration), `validate_aout_data` /210`validate_dout_data` / `convert_cap_to_weight` (fundamentals),211`analyze_phase_plane` / `analyze_error_phase_plane` (aout),212`generate_aout_dashboard` / `generate_dout_dashboard` (toolset).213214## 6. Going further215216- Dashboards, phase-plane, bit-level, error decomposition, static217 nonlinearity, ramp INL/DNL, cap-to-weight → **`references/advanced-debug.md`**218- Function signatures / return keys → `references/api-quickref.md`219- Ready-to-adapt example files → `references/example-map.md`220221**Highly Recommended Baseline:** For the simplest end-to-end analysis222+ plot template, adapt `02_spectrum/exp_s03_analyze_spectrum_savefig.py`223(see `references/example-map.md` for the path). The packaged CLI224`adctoolbox-get-examples [dest]` dumps the full example tree.225226Every code block in this file (and in `references/advanced-debug.md`) is227exercised by `python/tests/integration/test_user_guide_skill_examples.py`228— if a future edit breaks one, that test fails.