def pll_tf_step(pll_tf, tmax): """ Generate signal of step response of PLL tranfer function. """ sys = pll_tf_to_sys(pll_tf) t = np.arange(0, tmax, 1 / pll_tf["fclk"]) t, x = scipy.signal.step(sys, T=t, N=len(t)) return make_signal(td=x, fs=pll_tf["fclk"])
def sim_bbpd_pipll(fclk, fs, sim_steps, div_n, use_bbpd, bbpd_rms_jit, kdco1, kdco2, fine_bits, med_bits, fl_dco, krw_dco, lf_i_bits, lf_f_bits, lf_params_sc, lf_params_bbpd, tsettle_est, init_params, verbose=True, ignore_clk=False, *args, **kwargs): # init pll component objects if verbose and args: print("Unused args = %r"%args) if verbose and kwargs: print("Unused kwargs = %r"%kwargs) clk = ClockPhase(f=fclk, dt=1/float(fs), init_phase=init_params["clk"]) scpd = SCPDPhase(modulus=div_n, init_clk=init_params["clk"], init_out=init_params["scpd"], ignore_clk=ignore_clk) bbpd = BBPD(rms_jit=bbpd_rms_jit, fclk=fclk, init_clk=init_params["clk"], init_out=init_params["bbpd"], ignore_clk=ignore_clk) dco = DCOPhase(kdco1=kdco1, kdco2=kdco2, f0=fl_dco, dt=1/float(fs), krw=krw_dco, init_phase=init_params["osc"], quantize=True) lf = LoopFilterPIPhase(init_out=init_params["lf"], init_clk=init_params["clk"], int_bits=lf_i_bits, frac_bits=lf_f_bits, ignore_clk=ignore_clk, verbose=verbose, out_bits=fine_bits+med_bits, **lf_params_sc) bbpd_lf_switch = int(np.round(fclk*tsettle_est)) if bbpd_lf_switch ==0: bbpd_lf_switch = 1 print("LF gearswitch at %d samples"%bbpd_lf_switch) def eval_step(n, step): if n == bbpd_lf_switch and use_bbpd: if verbose: print("\n* Simulation step = %d, switching to BBPD optimized loop filter"%n) lf.change_filter(x1init=1, **lf_params_bbpd) # dco.clk_align() step["osc"] = dco.update(step["fine"], step["med"]) step["clk"] = clk.update() step["scpd"] = scpd.update(clk=step["clk"], xin=step["osc"]) step["bbpd"] = bbpd.update(clk=step["clk"], xin=step["osc"]) if n < bbpd_lf_switch: step["error"] = step["scpd"] else: step["error"] = step["bbpd"] step["lf"] = lf.update(xin=step["error"], clk=step["clk"]) fine, med = fine_med(step["lf"], fine_bits, med_bits) step["fine"] = fine step["med"] = med return step data = run_sim(eval_step, sim_steps, init_params) for k,v in data.items(): data[k] = make_signal(td=v, fs=fs) params = dict(fclk=fclk, fs=fs, sim_steps=sim_steps, div_n=div_n, use_bbpd=use_bbpd, bbpd_rms_jit=bbpd_rms_jit, kdco1=kdco1, kdco2=kdco2, fl_dco=fl_dco, krw_dco=krw_dco, lf_i_bits=lf_i_bits, lf_f_bits=lf_f_bits, lf_params_sc=lf_params_sc, lf_params_bbpd=lf_params_bbpd, fine_bits=fine_bits, med_bits=med_bits, tsettle_est=tsettle_est, init_params=init_params, verbose=verbose) for k,v in kwargs.items(): params[k] = v data["params"] = params return data
def osc_td(f0, fs, samples, k, seed=None, rw=[]): """ Compute oscillator time domain signal with random walk phase noise args: f0 - oscillator fundamental frequency fs - 1/tstep of simulation samples - length of signal to generate k - random walk phase gain seed - provide 32b value for predictable sequence """ t = np.arange(samples) / float(fs) if not any(rw): np.random.seed(seed=seed) rw = np.cumsum(np.random.choice([-1, 1], samples)) td = np.sin(k * rw + 2 * np.pi * f0 * t) return make_signal(td=td, fs=fs, autocompute_fd=True)
def make_pn_sig(fbin, fmax, pn_dco, pn_dco_df, fclk, _type, m, n, k, ki, fz, fp, delay, fn, bw, damping, a0, a1, b0, b1, b2, label="", *args, **kwargs): bins = int(round(2 * fmax / fbin)) freqs = fbin * (np.arange(bins) - int(bins / 2)) # a = pll_otf2(freqs, M, N, KDCO, KP, FZ, FP, DELAY) g = pll_tf(freqs, _type, k, fz, fp, delay) kpn = pn_dco * pn_dco_df**2 ndco = np.abs(1 - g)**2 * kpn / freqs**2 ndco[np.where(g == 1)] = 0 # ndco = min_ro_pn(fclk*n, freqs, posc, temp)*np.abs(1-g)**2 ntdc = tdc_pn(fclk, n, m, g) * np.abs(g)**2 psd = np.fft.fftshift(ndco + ntdc) * bins**2 if np.isnan(psd[0]): psd[0] = 0.5 * (psd[1] + psd[-1]) return make_signal(fd=np.sqrt(psd), fs=bins * fbin)
def pn_signal2(sim_data, div_n): """ Extract phase noise/error signal from pllsim data """ td = sim_data["osc"].td - div_n * sim_data["clk"].td return make_signal(td=td, fs=sim_data["clk"].fs)
def pn_signal(sim_data, div_n): """ Extract phase noise/error signal from pllsim data """ td = div_n * (sim_data["div"].td - sim_data["clk"].td) return make_signal(td=td, fs=sim_data["clk"].fs)
plt.subplot(3, 3, 8) pn_sig = pn_signal2(sim_data, div_n=round(FOSC / FCLK)) pn_sig = debias_pn(pn_sig, 0.5 * SIM_SPAN) plot_td(pn_sig, tmax=PLOT_SPAN, title="Phase error", dots=True) half = int(SIM_STEPS / 2) rms = np.std(pn_sig.td[half:]) print("\nSimulated RMS PN:\t\t%.2E rad -> %.2f dB" % (rms, 20 * np.log10(rms))) est_rms_pn_design = np.sqrt(lfs["bbpd"]["int_pn"]) print("Filter design RMS PN Est:\t%.2E rad -> %.2f dB" % (est_rms_pn_design, 20 * np.log10(est_rms_pn_design))) x = np.zeros(SIM_STEPS + 1) x[1:] = sim_data["lf"].td em = -np.cumsum(2 * np.pi * KDCO1 * np.diff(x) * (1 / float(FS))) em_pn = make_signal(em, fs=FS) em_pn = debias_pn(em_pn, 0.5 * SIM_SPAN) plot_td(em_pn, tmax=PLOT_SPAN, title="Emergent PN", dots=True) plt.subplot(3, 3, 9) pn = copy(pn_sig.td[int(np.ceil(GEAR_SW_T * FCLK)):]) em = copy(em_pn.td[int(np.ceil(GEAR_SW_T * FCLK)):]) pn[np.abs(pn) > 3 * rms] = np.nan em[np.abs(em) > 3 * rms] = np.nan plt.hist(pn, alpha=0.5, bins=50, density=True) plt.hist(em, alpha=0.5, bins=50, density=True) plt.figure(2) plot_pn_ssb2(pn_sig, dfmax=PN_FMAX, line_fit=False,
for n in range(SAMPLES)[1:]: clk_out[n] = clk.update() tdc_out[n] = tdc.update(clk=clk_out[n - 1], xin=div_out[n - 1]) # _tdc = TDC_SCALE*((TDC_OFFSET+tdc_out[n-1])%TDC_STEPS) _tdc = ((TDC_OFFSET + tdc_out[n - 1]) % TDC_STEPS) - TDC_OFFSET lf_out[n] = lf.update(xin=_tdc, clk=clk_out[n - 1]) osc_out[n] = dco.update(lf_out[n - 1]) div_out[n] = div.update(osc_out[n - 1], DIV_N) tdelta = time.clock() - t0 print("\nSimulation completed in %f s" % tdelta) ############################################################################### # Plot data ############################################################################### osc_sig_full = make_signal(td=osc_out[len(osc_out) / 2:], fs=FS) osc_sig = make_signal(td=osc_out[PLOT_SLICE], fs=FS) clk_sig = make_signal(td=clk_out[PLOT_SLICE], fs=FS) lf_sig = make_signal(td=lf_out[PLOT_SLICE], fs=FS) div_sig = make_signal(td=div_out[PLOT_SLICE], fs=FS) tdc_sig = make_signal(td=tdc_out[PLOT_SLICE], fs=FS) plt.subplot(2, 3, 1) plot_td(clk_sig, title="CLK") # razavify() plt.subplot(2, 3, 2) plot_td(osc_sig, title="DCO") # razavify() plt.subplot(2, 3, 3) plot_td(div_sig, title="DIV") # razavify()
data.append([]) for LSB_DF in np.geomspace(1e-6, 1e-3, 7): TREF = 1/float(FREF) dith = np.zeros(SA) for n in range(SA): trel = n*TSTEP/TREF - int(n*TSTEP/TREF) if trel >= 0 and trel < 0.5: dith[n] = 1 phase = np.zeros(SA) for n in range(1, SA): phase[n] = phase[n-1] + 2*np.pi*((F0-LSB_DF/2)+LSB_DF*dith[n])*TSTEP td = np.sin(phase) sig = make_signal(td=td, fs = F0*SA_CYC) # plot_pn_ssb(sig, f0=F0, fref=FREF) #print(20*np.log10(eval_model_pn(sig, F0, PN_DF))) data[-1].append(20*np.log10(meas_ref_spur(sig, F0, FREF))) print(data) data[-1][-1] = 0 # plt.xscale("log") # plt.yscale("log") plt.imshow(data, interpolation="gaussian", extent=[-6, -3, -3, -1]) plt.colorbar() CS = plt.contour(data, levels=[-120, -100, -80, -60, -40, -20], extent=[-6,-3,-1,-3], colors="w") plt.clabel(CS, inline=1, fontsize=8) plt.xticks([-6, -5, -4, -3], ["1e-6", "1e-5", "1e-4", "1e-3"]) plt.yticks([-3, -2, -1], ["1e-3", "1e-2", "1e-1"]) plt.xlabel("DCO LSB Resolution f_lsb/f0")
_D = np.abs(D) # Use DCO class model krwro = ro_rw_model_param(f0=F0, power=50e-6, temp=293, n=SAMPLES, tstep=1.0 / FS) dco = DCO(kdco=1, f0=F0, dt=1.0 / FS, krwro=krwro) e = np.zeros(SAMPLES) for n in range(SAMPLES): e[n] = dco.update(fctrl=100e6) #sig_c = make_signal(td=c, fs=FS) sig_d = make_signal(td=d, fs=FS) sig_e = make_signal(td=e, fs=FS) plot_pn_ssb(sig_d, f0=F0, dfmax=10e6) plot_pn_ssb(sig_e, f0=F0 + 100e6, dfmax=10e6) #print(20*np.log10(eval_model_pn(sig_c, 0, PN_DF))) meas = 20 * np.log10(eval_model_pn(sig_d, F0, PN_DF)) equ = 10 * np.log10(phase_noise_est(K, SAMPLES, PN_DF, 1.0 / FS)) print("Meas=%.2f, theory=%.2f, delta=%.2f" % (meas, equ, meas - equ)) meas_dco = 20 * np.log10(eval_model_pn(sig_e, F0 + 100e6, PN_DF)) print("Meas dco=%.2f" % meas_dco) # plt.plot(20*np.log10(_A)) # plt.plot(20*np.log10(_B)) # plt.plot(20*np.log10(_C)) # plt.plot(20*np.log10(_D))
fs = 16e6 dt = 1 / fs steps = 100000 pn = 10**(-80 / 10) df = 1e6 krw = rw_gain(pn, df, steps, dt, m=1) print("krw=", krw) dco = DCO(kdco, f0, dt, krw=krw) osc_sig = np.zeros(steps) for n in range(steps): osc_sig[n] = dco.update(fctrl=0) osc_sig = make_signal(td=osc_sig, fs=fs) plot_pn_ssb2(osc_sig, line_fit=True) #plot_pn_ar_model(osc_sig) plt.legend() plt.title( "DCO SSB Phase Noise [dBc/Hz],\n $\mathtt{krw}$ fitted to $L(\Delta f=10^6)$ = -80 dBc/Hz" ) razavify(loc="lower left", bbox_to_anchor=[0, 0]) plt.xticks([1e2, 1e3, 1e4, 1e5, 1e6, 1e7], ["$10^2$", "$10^3$", "$10^4$", "$10^5$", "$10^6$", "$10^7$"]) plt.tight_layout() plt.savefig("dco_rw_pn.pdf")
freqs = np.geomspace(1e3, 8e6, 1000) ntdc = pow_ntdc_post_lf(freqs, lf_params, FCLK) plt.semilogx(freqs, 10 * np.log10(ntdc), label="TDC noise") mses = [] bit_range = range(MIN_BITS - OPT_INT_BITS - 1, MAX_BITS - OPT_INT_BITS) for frac_bits in bit_range: lf_quant = LoopFilterIIRPhase(ignore_clk=True, int_bits=INT_BITS, frac_bits=frac_bits, quant_filt=False, **lf_params) filt_quant = np.zeros(SIM_STEPS) for n in range(SIM_STEPS): filt_quant[n] = lf_quant.update(x[n], 0) sig = make_signal(td=filt_quant, fs=FCLK) plot_pn_ar_model(sig, fmin=1e3) mse = np.var(filt_ideal - filt_quant) print(mse) mses.append(mse) plt.show() foo() # plt.semilogy(np.array(bit_range)+OPT_INT_BITS+1, mses) # plt.ylim((-1,1000)) # plt.xlim(MIN_BITS, MAX_BITS) # plt.xlabel("Data word length [bits]") # plt.ylabel("Quantization noise power [LSB^2]") # plt.title("Loop filter quantization noise versus data word length,\n Two's complement fixed point representation") # plt.grid() # razavify() # plt.tight_layout()
# krw = 4*np.pi*np.sqrt(FS*s0)/np.sqrt((2*FS)**2 + (2*np.pi*PN_DF)**2) # krw = 2*np.pi*np.sqrt(s0/FS) krw = rw_gain_fom(fom_db=DCO_FOM, fosc=FOSC, power=DCO_POWER, fs=FS) print("krw =", krw) error = np.zeros(SIM_STEPS) dco1 = DCOPhase(kdco1=0, kdco2=0, f0=FL_DCO, dt=1/float(FS), krw=krw, init_phase=0, quantize=False) dco2 = DCOPhase(kdco1=0, kdco2=0, f0=FL_DCO, dt=1/float(FS), krw=0, init_phase=0, quantize=False) for n in range(SIM_STEPS): error[n] = dco1.update(0,0)-dco2.update(0,0) # print(error) # plt.plot(error) E = np.fft.fft(error)/np.sqrt(FS*SIM_STEPS) plt.semilogx(f, 20*np.log10(np.abs(E[1:int(0.5*SIM_STEPS)]))) # plt.show() K = np.average(np.abs(E[1:int(0.5*SIM_STEPS)])**2*f**2) print("Fitted pn @ 1MHz = ", 10*np.log10(K/1e6**2)) print("rms_error =", np.std(error)) print("predicted rms error =", np.sqrt(2*K*(1/FBIN - 1/(0.5*FS)))) # plt.plot(K) # plt.show() pn_sig = make_signal(error, fs=FS) plot_pn_ssb2(pn_sig, dfmax=PN_FMAX, line_fit=False, tmin=0) plot_pn_ar_model(pn_sig, p=200, tmin=0) plt.show()
# phase_noise = osc_out-DIV_N*clk_out # pn_sig_full = make_signal(td=phase_noise[int(len(phase_noise)/2):], fs=FS) # pn_sig = make_signal(td=phase_noise[PLOT_SLICE], fs=FS) # osc_sig_full = make_signal(td=osc_out[len(osc_out)/2:], fs=FS) # osc_freq = make_signal(td=np.diff(osc_out[PLOT_SLICE])/(2*np.pi*DT), fs=FS) # clk_sig = make_signal(td=clk_out[PLOT_SLICE], fs=FS) # lf_sig = make_signal(td=lf_out[PLOT_SLICE], fs=FS) # div_sig = make_signal(td=div_out[PLOT_SLICE], fs=FS) # tdc_sig = make_signal(td=tdc_out[PLOT_SLICE], fs=FS) # bbpd_sig = make_signal(td=bbpd_out[PLOT_SLICE], fs=FS) # error_sig = make_signal(td=error[PLOT_SLICE], fs=FS) phase_noise = sim_data["osc"]-DIV_N*sim_data["clk"] pn_sig_full = make_signal(td=phase_noise[int(len(phase_noise)/2):], fs=FS) pn_sig = make_signal(td=phase_noise[PLOT_SLICE], fs=FS) osc_freq = make_signal(td=np.diff(sim_data["osc"][PLOT_SLICE])/(2*np.pi*DT), fs=FS) lf_sig = make_signal(td=sim_data["lf"][PLOT_SLICE], fs=FS) tdc_sig = make_signal(td=sim_data["tdc"][PLOT_SLICE], fs=FS) bbpd_sig = make_signal(td=sim_data["bbpd"][PLOT_SLICE], fs=FS) error_sig = make_signal(td=sim_data["error"][PLOT_SLICE], fs=FS) # plt.subplot(2,3,1) # plot_td(clk_sig, title="CLK") # razavify() plt.figure(3) plt.subplot(2,3,1) plot_td(osc_freq, title="Inst. DCO Frequency") # razavify()
def meas_inst_freq(signal): return make_signal(td=signal.fs * np.diff(signal.td) / (2 * np.pi), fs=signal.fs)
# Simulation loop ############################################################################### t0 = time.clock() for n in range(SAMPLES)[1:]: osc_out[n] = dco.update(lf_out[n - 1]) div_out[n] = osc_out[n] / float(DIV_N) clk_out[n] = clk.update() tdc_out[n] = tdc.update(clk=clk_out[n], xin=div_out[n]) bbpd_out[n] = bbpd.update(clk=clk_out[n], xin=div_out[n]) error[n] = tdc_out[n] + KBBPD * bbpd_out[n] lf_out[n] = lf.update(xin=error[n]) tdelta = time.clock() - t0 print("\nSimulation completed in %f s" % tdelta) lf_sig = make_signal(td=lf_out[PLOT_SLICE], fs=FS) osc_freq = make_signal(td=np.diff(osc_out[PLOT_SLICE]) / (2 * np.pi * DT), fs=FS) plt.subplot(1, 2, 1) plot_td(osc_freq, title="Inst. DCO Frequency") plt.subplot(1, 2, 2) plot_td(lf_sig, title="Loop Filter Output") plt.show() foo() # plt.subplot(2,3,1) # plt.plot(osc_out,) # plt.title("DCO") # plt.subplot(2,3,2) # plt.plot(div_out,)
def plot_pllsim_lf_inst_freq(pllsim_data): inst_freq = make_signal(td=pllsim_data["lf"].td*pllsim_data["params"]["kdco"], fs = pllsim_data["lf"].fs) plot_td(inst_freq) plt.ylabel("Frequency [Hz]")