Skip to content

Instantly share code, notes, and snippets.

@raytroop
Created May 24, 2026 15:37
Show Gist options
  • Select an option

  • Save raytroop/dba921298532e162f088f496a3f25e02 to your computer and use it in GitHub Desktop.

Select an option

Save raytroop/dba921298532e162f088f496a3f25e02 to your computer and use it in GitHub Desktop.
"""
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