Source code for cbadc.synthesis.quadrature

"""Synthesise functions for chain-of-integrators control-bounded ADCs."""

from cbadc.analog_frontend import AnalogFrontend
from cbadc.analog_system import AnalogSystem
from cbadc.synthesis.leap_frog import g_i, get_leap_frog
from cbadc.digital_control.digital_control import DigitalControl, Clock
from cbadc.analog_signal import StepResponse
from cbadc.analog_system.topology import stack
from cbadc.fom import enob_to_snr, snr_from_dB, snr_to_enob

import numpy as np
import logging

logger = logging.getLogger(__name__)


def _analog_system_factory(N, beta, rho, kappa, gamma, omega_p):
    A = np.zeros((2 * N, 2 * N))
    B = np.zeros((2 * N, 2))
    CT = np.eye(2 * N)
    Gamma = kappa * np.eye(2 * N)
    Gamma_tildeT = -np.sign(kappa) * np.eye(2 * N)
    for i in range(N):
        A[2 * i : 2 * i + 2, 2 * i : 2 * i + 2] = np.array(
            [[rho, -omega_p], [omega_p, rho]]
        )
        if i < N - 1:
            A[2 * i : 2 * i + 2, 2 * (i + 1) : 2 * (i + 1) + 2] = gamma * np.eye(2)
            A[2 * (i + 1) : 2 * (i + 1) + 2, 2 * i : 2 * i + 2] = beta * np.eye(2)

        B[:2, :] = beta * np.eye(2)

    return AnalogSystem(A, B, CT, Gamma, Gamma_tildeT)


[docs]def get_bandpass(**kwargs) -> AnalogFrontend: if not all(param in kwargs for param in ("analog_frontend", "fc")): raise NotImplementedError analog_frontend_baseband = kwargs["analog_frontend"] analog_system_baseband = analog_frontend_baseband.analog_system digital_control_baseband = analog_frontend_baseband.digital_control N = analog_system_baseband.N beta = analog_system_baseband.Gamma[0, 0] T = digital_control_baseband.clock.T fc = kwargs["fc"] omega_c = 2.0 * np.pi * fc analog_system = stack([analog_system_baseband, analog_system_baseband]) analog_system.A[:N, N:] = -omega_c * np.eye(N) analog_system.A[N:, :N] = omega_c * np.eye(N) if not "modulate" in kwargs: # kappa = beta * T * omega_c / (2 * np.sin(omega_c * T / 2)) phi: float = kwargs.get("phi", 0.0) delta_DC: float = kwargs.get("delta_DC", 0.0) Gamma = np.zeros((2 * N, 2 * N)) Gamma_tildeT = np.zeros((2 * N, 2 * N)) kappa = beta * T * omega_c / (2 * np.sin(omega_c * T / 2)) * np.cos(phi) bar_kappa = beta * T * omega_c / (2 * np.sin(omega_c * T / 2)) * np.sin(phi) tilde_kappa = -1 / (beta * T) * np.cos(omega_c * (T / 2 + delta_DC) - phi) bar_tilde_kappa = -1 / (beta * T) * np.sin(omega_c * (T / 2 + delta_DC) - phi) Gamma[:N, :N] = kappa * np.eye(N) Gamma[N:, N:] = kappa * np.eye(N) Gamma[:N, N:] = -bar_kappa * np.eye(N) Gamma[N:, :N] = bar_kappa * np.eye(N) Gamma_tildeT[:N, :N] = tilde_kappa * np.eye(N) Gamma_tildeT[N:, N:] = tilde_kappa * np.eye(N) Gamma_tildeT[:N, N:] = -bar_tilde_kappa * np.eye(N) Gamma_tildeT[N:, :N] = bar_tilde_kappa * np.eye(N) analog_system.Gamma = Gamma analog_system.Gamma_tildeT = Gamma_tildeT digital_control = DigitalControl( digital_control_baseband.clock, 2 * digital_control_baseband.M, ) if "scalar_input" in kwargs: analog_system.B[analog_system.N // 2 :, 0] = -analog_system.B[ analog_system.N // 2 :, 1 ] analog_system.B = analog_system.B[:, 0].reshape((analog_system.N, 1)) analog_system.B_tilde = ( analog_system.B_tilde[:, 0] - analog_system.B_tilde[:, 1] ).reshape((-1, 1)) return AnalogFrontend( analog_system=AnalogSystem( analog_system.A, analog_system.B, analog_system.CT, analog_system.Gamma, analog_system.Gamma_tildeT, B_tilde=analog_system.B_tilde, A_tilde=analog_system.A_tilde, ), digital_control=digital_control, )
[docs]def get_chain_of_oscillators(**kwargs) -> AnalogFrontend: """Quick parameterize a chain-of-integrator ADC Returns a parameterized analog system and digital control corresponding to a given target specification. The following examples demonstrate the valid input specifications Parameters ---------- ENOB: `float` targeted effective number of bits. N: `int` system order. BW: `float` target bandwidth in Hz fp: `float`, oscillator resonant frequency in Hz xi: `float`, `optional` a proportionality constant, defaults to 0.0016. local_feedback: `bool`, `optional` include local feedback, defaults to False. excess_delay: `float`, `optional` delay control actions by an excess delay, defaults to 0. finite_gain: `bool`, `optional` include finite gain, defaults to False. Returns ------- : (:py:class:`cbadc.analog_system.ChainOfIntegrators`, :py:class:`cbadc.digital_control.DigitalControl`) returns an analog system and digital control tuple """ finite_gain = kwargs.get("finite_gain", False) if all(param in kwargs for param in ("ENOB", "N", "BW", "fp")): SNR = enob_to_snr(kwargs["ENOB"]) snr = snr_from_dB(SNR) N = kwargs["N"] omega_BW = 2.0 * np.pi * kwargs["BW"] xi = kwargs.get("xi", 4e-3) gamma = (xi / g_i(N) * snr) ** (1.0 / (2.0 * N)) omega_BW = omega_BW / 2.0 beta = -omega_BW * (2.0 * gamma) alpha = omega_BW / (2.0 * gamma) rho = 0 if "local_feedback" in kwargs and kwargs["local_feedback"] is True: rho = -(omega_BW / 2.0) / gamma * 1e-2 * 0 T = 1.0 / np.abs(2.0 * beta * 10) kappa = beta omega_p = 2 * np.pi * kwargs["fp"] analog_system = _analog_system_factory(N, beta, rho, kappa, alpha, omega_p) t0 = T * kwargs.get("excess_delay", 0.0) if kwargs.get("digital_control") == "switch_cap": raise NotImplementedError else: impulse_response = StepResponse(t0) digital_control = DigitalControl( Clock(T), 2 * N, impulse_response=impulse_response ) digital_control = DigitalControl(Clock(T), 2 * N) if finite_gain: analog_system.A += -omega_BW / (gamma ** (2 * N)) * np.eye(N) return AnalogFrontend(analog_system, digital_control) elif all(param in kwargs for param in ("SNR", "N", "BW", "fp")): return get_chain_of_oscillators(ENOB=snr_to_enob(kwargs["SNR"]), **kwargs) elif all(param in kwargs for param in ("OSR", "N", "BW", "fp")): N = kwargs["N"] BW = kwargs["BW"] OSR = kwargs["OSR"] T = 1.0 / (2 * BW * OSR) beta = 1 / (2 * T) kappa = beta rho = kwargs.get("rho", 0.0) omega_BW = 2.0 * np.pi * BW alpha = -((omega_BW / 2) ** 2) / beta omega_p = 2 * np.pi * kwargs["fp"] analog_system = _analog_system_factory(N, beta, rho, kappa, alpha, omega_p) digital_control = DigitalControl(Clock(T), N) return AnalogFrontend(analog_system, digital_control) elif all(param in kwargs for param in ("OSR", "N", "T", "fp")): N = kwargs["N"] T = kwargs["T"] OSR = kwargs["OSR"] BW = 1.0 / (2 * T * OSR) rho = kwargs.get("rho", 0.0) fp = kwargs["fp"] return get_chain_of_oscillators(N=N, OSR=OSR, BW=BW, rho=rho, fp=fp) raise NotImplementedError
[docs]def get_parallel_leapfrog(**kwargs) -> AnalogSystem: """Quick parameterize a chain-of-integrator ADC Returns a parameterized analog system and digital control corresponding to a given target specification. The following examples demonstrate the valid input specifications Parameters ---------- ENOB: `float` targeted effective number of bits. N: `int` per system order. M: `int` system width BW: `float` target bandwidth in Hz fp: `float`, oscillator resonant frequency in Hz xi: `float`, `optional` a proportionality constant, defaults to 0.0016. local_feedback: `bool`, `optional` include local feedback, defaults to False. excess_delay: `float`, `optional` delay control actions by an excess delay, defaults to 0. finite_gain: `bool`, `optional` include finite gain, defaults to False. Returns ------- : (:py:class:`cbadc.analog_system.ChainOfIntegrators`, :py:class:`cbadc.digital_control.DigitalControl`) returns an analog system and digital control tuple """ # finite_gain = kwargs.get("finite_gain", False) if all(param in kwargs for param in ("ENOB", "N", "M", "BW", "fp")): ENOB = kwargs["ENOB"] N = kwargs["N"] M = kwargs["M"] BW = kwargs["BW"] fp = kwargs["fp"] omega_p = 2 * np.pi * fp TotMN = np.sum([i for i in range(1, M)]) poles = kwargs.get("fps", np.ones(TotMN) * omega_p) # SNR = enob_to_snr(kwargs['ENOB']) # snr = snr_from_dB(SNR) # omega_BW = 2.0 * np.pi * kwargs['BW'] # xi = kwargs.get('xi', 4e-3) # gamma = (xi / g_i(N) * snr) ** (1.0 / (2.0 * N)) # omega_BW = omega_BW / 2.0 # beta = -omega_BW * (2.0 * gamma) # alpha = omega_BW / (2.0 * gamma) # rho = 0 # T = 1.0 / np.abs(2.0 * beta * 10) # kappa = beta # omega_p = 2 * np.pi * kwargs['fp'] analog_system = stack( [get_leap_frog(ENOB=ENOB, N=N, BW=BW).analog_system for _ in range(M)] ) if M == 3: analog_system.A[N : 2 * N, 0:N] = poles[0] * np.eye(N) analog_system.A[ 0:N, N : 2 * N, ] = -poles[ 0 ] * np.eye(N) analog_system.A[2 * N : 3 * N, 0:N] = poles[1] * np.eye(N) analog_system.A[0:N, 2 * N : 3 * N] = -poles[1] * np.eye(N) analog_system.A[2 * N : 3 * N, N : 2 * N] = poles[2] * np.eye(N) analog_system.A[N : 2 * N, 2 * N : 3 * N] = -poles[2] * np.eye(N) elif M == 4: analog_system.A[N : 2 * N, 0:N] = poles[0] * np.eye(N) analog_system.A[ 0:N, N : 2 * N, ] = -poles[ 0 ] * np.eye(N) analog_system.A[2 * N : 3 * N, 0:N] = poles[1] * np.eye(N) analog_system.A[0:N, 2 * N : 3 * N] = -poles[1] * np.eye(N) analog_system.A[2 * N : 3 * N, N : 2 * N] = poles[2] * np.eye(N) analog_system.A[N : 2 * N, 2 * N : 3 * N] = -poles[2] * np.eye(N) analog_system.A[3 * N : 4 * N, 0:N] = poles[3] * np.eye(N) analog_system.A[0:N, 3 * N : 4 * N] = -poles[3] * np.eye(N) analog_system.A[3 * N : 4 * N, N : 2 * N] = poles[4] * np.eye(N) analog_system.A[N : 2 * N, 3 * N : 4 * N] = -poles[4] * np.eye(N) analog_system.A[3 * N : 4 * N, 2 * N : 3 * N] = poles[5] * np.eye(N) analog_system.A[2 * N : 3 * N, 3 * N : 4 * N] = -poles[5] * np.eye(N) else: for i in range(1, M): for j in range(i): print(i, j, (M - 1 - j), (M - j)) analog_system.A[i * N : (i + 1) * N, j * N : (j + 1) * N] = poles[ i + j * M ] * np.eye( N ) # / (i + 1) * (j) analog_system.A[ (M - i - 1) * N : (M - i) * N, (M - 1 - j) * N : (M - j) * N ] = -poles[-(i + j * M)] * np.eye( N ) # / (i + 1) * (j ) analog_system.B = np.sum(analog_system.B, axis=1).reshape( (analog_system.B.shape[0], 1) ) analog_system.D = np.sum(analog_system.D, axis=1).reshape( (analog_system.D.shape[0], 1) ) analog_system.B_tilde = np.sum(analog_system.B_tilde, axis=1).reshape( (analog_system.B_tilde.shape[0], 1) ) analog_system.L = 1 return AnalogSystem( analog_system.A, analog_system.B, analog_system.CT, analog_system.Gamma, analog_system.Gamma_tildeT, ) # t0 = T * kwargs.get('excess_delay', 0.0) # if kwargs.get('digital_control') == 'switch_cap': # raise NotImplementedError # else: # impulse_response = StepResponse(t0) # digital_control = DigitalControl( # Clock(T), 2 * N, impulse_response=impulse_response # ) # digital_control = DigitalControl(Clock(T), 2 * N) # if finite_gain: # analog_system.A += -omega_BW / (gamma ** (2 * N)) * np.eye(N) # return AnalogFrontend(analog_system, digital_control) raise NotImplementedError