Created
May 24, 2026 15:37
-
-
Save raytroop/dba921298532e162f088f496a3f25e02 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Overlay: OSR=1 (FOH inactive), OSR=16 (FOH active), nd*sinc^2 ideal FOH at | |
| OSR=16, and target nd. noise_gen.va verification. | |
| """ | |
| import numpy as np | |
| import matplotlib | |
| # matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| nd, bw, seed = 4e-9, 1e9, 271 | |
| N_samples = 1 << 18 | |
| Ts = 0.5 / bw | |
| sigma = nd * np.sqrt(bw) | |
| def measure(OSR, nper=8192): | |
| rng = np.random.default_rng(seed) # same seed -> identical samples | |
| nv = rng.normal(0.0, sigma, N_samples) | |
| if OSR == 1: | |
| v, dt = nv.copy(), Ts # FOH never sampled -> bare seq | |
| else: | |
| dt = Ts / OSR | |
| st = np.arange(N_samples) * Ts | |
| tf = np.arange((N_samples - 1) * OSR + 1) * dt | |
| v = np.interp(tf, st, nv) # continuous FOH ramp | |
| fs = 1.0 / dt | |
| win = np.hanning(nper); wp = (win**2).sum() | |
| nseg = (len(v) - nper) // nper + 1 | |
| acc = np.zeros(nper // 2 + 1) | |
| for k in range(nseg): | |
| X = np.fft.rfft(v[k*nper:(k+1)*nper] * win) | |
| acc += np.abs(X)**2 / (fs * wp) | |
| psd = acc / nseg | |
| psd[1:-1] *= 2.0 | |
| f = np.fft.rfftfreq(nper, dt) | |
| return f, np.sqrt(psd), fs | |
| f1, asd1, fs1 = measure(1) | |
| f16, asd16, fs16 = measure(16) | |
| fig, ax = plt.subplots(figsize=(9.2, 5.3)) | |
| m1, m16 = f1 > 0, f16 > 0 | |
| ax.semilogx(f1[m1]/1e9, asd1[m1]/1e-9, lw=1.6, color="#d08", alpha=0.6, | |
| label="OSR=1 (FOH inactive, flat to bw)") | |
| ax.semilogx(f16[m16]/1e9, asd16[m16]/1e-9, lw=2.6, color="#3b6ea5", | |
| label="OSR=16 (FOH active)") | |
| ff = f16[m16] | |
| # CORRECT ideal FOH: linear interpolation over the SAMPLE interval Ts, | |
| # so the response is sinc^2(f*Ts) = sinc^2(f/(2*bw)), null at 1/Ts = 2*bw. | |
| ax.semilogx(ff/1e9, (nd*np.sinc(ff*Ts)**2)/1e-9, lw=2.8, color="#2a8", ls="-.", | |
| label="nd*sinc$^2$(f*Ts) OSR=1 correct FOH (null at 1/Ts)") | |
| # WRONG reference (what was plotted before): sinc^2(f/fs), fs = fine-grid rate | |
| ax.semilogx(ff/1e9, (nd*np.sinc(ff/fs16)**2)/1e-9, lw=1.6, color="#fa0", ls=":", | |
| label="nd*sinc$^2$(f/fs) OSR=16 (fine-grid ZOH, not the FOH)") | |
| ax.axhline(nd/1e-9, color="#c44", ls="--", lw=2.6, label="target nd = 4 nV/$\\sqrt{Hz}$") | |
| ax.axvline(bw/1e9, color="#888", ls=":", lw=2.0, label="bw = 1 GHz") | |
| ax.set_xlabel("Frequency [GHz]"); ax.set_ylabel("ASD [nV/$\\sqrt{Hz}$]") | |
| ax.set_title("noise_gen.va: OSR=1 vs OSR=16 vs ideal FOH vs target nd") | |
| ax.set_xlim(f16[1]/1e9, fs16/2/1e9); ax.set_ylim(0, nd/1e-9*1.6) | |
| ax.grid(True, which="both", alpha=0.3); ax.legend(fontsize=9, loc="lower left") | |
| fig.tight_layout(); #fig.savefig("/home/claude/overlay.png", dpi=130) | |
| print("OSR=1 flat-band ASD:", f"{np.median(asd1[(f1>0)&(f1<bw/10)]):.3e}") | |
| print("OSR=16 flat-band ASD:", f"{np.median(asd16[(f16>0)&(f16<bw/10)]):.3e}") | |
| print("saved overlay.png"); plt.show() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment