154 lines
4.6 KiB
Python
Raw Normal View History

2025-02-06 18:58:23 +01:00
from sympy import Symbol, log, sin
from scipy import constants
from quantiphy import Quantity
import control as ct
eval_constants = {
"k": constants.k, # Boltzmann contant
"T": constants.convert_temperature(25.0, "Celsius", "Kelvin"),
"q": constants.e, # Elementary charge
}
_pi = constants.pi
_kT = constants.Boltzmann * constants.convert_temperature(25.0, "Celsius", "Kelvin")
class Crystal:
def __init__(self, frequency: float = 4.8e7, noise_floor_dBc: float = -160):
self.frequency = frequency
self.noise_floor_dBc = noise_floor_dBc
## TODO low-frequency jitter offset contribution
def xtal_jitter(self, bandwidth, ref_frequency):
"""XTAL jitter estimate over bandwidth"""
return Quantity(
(
2
* 10 ** (self.noise_floor_dBc / 10)
* bandwidth
/ (ref_frequency * 2 * _pi) ** 2
)
** 0.5,
units="s_rms",
)
class Oscillator:
"""
VCO Phase-Noise Model of LC Oscillator
Bandwidth upper limit is set by the refence clock
Back off by 10x in sampled systems for stability
More accurately take xtal into account and equate jitter
Reference B. Razavi, "Jitter-Power Trade-Offs in PLLs,"
in IEEE Transactions on Circuits and Systems I: Regular Papers,
vol. 68, no. 4, pp. 1381-1387, April 2021, doi: 10.1109/TCSI.2021.3057580.
"""
def __init__(
self,
f_center: float = 5e9,
vout_max: float = 1.0,
ibias: float = 5.0e-3,
q_factor: float = 10,
eta: float = 2.4, # noise excess factor (1+gamma)
):
self.R = _pi * vout_max / ibias / 2
self.f_center = f_center
self.vout_max = vout_max
self.q_factor = q_factor
self.ibias = ibias
self.eta = eta
@property
def vco_alpha(self):
"""Oscillator Noise Figure alpha"""
return (
_kT / (self.q_factor**2) * self.eta * (_pi / self.ibias) ** 2 / self.R / 8
)
def pll_phase_noise(self, pll_bandwidth):
"""Estimate on a LC oscillator phase-noise as alpha/f**2"""
return self.vco_alpha * (self.f_center / pll_bandwidth) ** 2
def pll_jitter(self, pll_bandwidth, ref_frequency):
"""PLL jitter estimate due to VCO"""
return Quantity(
(
4
* self.pll_phase_noise(pll_bandwidth)
* pll_bandwidth
/ (ref_frequency * 2 * _pi) ** 2
)
** 0.5,
units="s_rms",
)
def opt_bandwidth(self, ref_xtal: Crystal):
"""Optimal PLL bandwidth for matching reference noise."""
f_ratio = self.f_center / ref_xtal.frequency
eff_ref_noise = 2 * 10 ** (ref_xtal.noise_floor_dBc / 10) * f_ratio**2
return Quantity(
(4 * self.vco_alpha * self.f_center**2 / _pi / eff_ref_noise) ** 0.5,
units="Hz",
)
def jitter_tol(self, adc_tol_db: float, adc_resolution: float, adc_clock: float):
"""Jitter tolerance calculation for m-dB penalty in ADC performance"""
return Quantity(
(
(10 ** (adc_tol_db / 10) - 1)
/ (3 * _pi**2 * adc_clock**2 * 2 ** (2 * adc_resolution - 1))
)
** 0.5,
units="s_rms",
)
# LC Tank Parameter Selection
# E. Hegazi, H. Sjoland and A. A. Abidi,
# "A filtering technique to lower LC oscillator phase noise,"
# in IEEE Journal of Solid-State Circuits,
# vol. 36, no. 12, pp. 1921-1930, Dec. 2001, doi: 10.1109/4.972142
# A. Mazzanti and P. Andreani,
# "Class-C Harmonic CMOS VCOs, With a General Result on Phase Noise,"
# in IEEE Journal of Solid-State Circuits,
# vol. 43, no. 12, pp. 2716-2729, Dec. 2008,
class Divider:
# Error-Feedback Topology
# https://www.youtube.com/watch?v=t1TY-D95CY8&t=4373s
ω = Symbol("ω")
def __init__(self, order: int = 2, fb_int:int = 100, fb_frac:float = 0.1337):
self.fb_int = fb_int
self.fb_frac = fb_frac
self.order = order
@property
def noise_function_approx(self):
return (_pi / 3 / self.ω) ** self.order
@property
def noise_function_exact(self):
return (2 * sin(self.ω / 2)) ** self.order
@property
def psd_out(self):
return self.noise_function_exact**2 / 12
@property
def modulator_c2p(self):
return ct.tf([0,1],[1,-1],dt=True)*(2*_pi/(self.fb_int+self.fb_frac))
@property
def pll_ntf(self):
pass
@property
def modulator_response_ss(self):
return ct.tf2ss(self.modulator_c2p) * ct.tf2ss(self.pll_ntf)