mirror of
https://github.com/lleene/hugo-site.git
synced 2025-03-10 14:02:43 +01:00
154 lines
4.6 KiB
Python
154 lines
4.6 KiB
Python
|
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)
|