Source code for cbadc.circuit.ota
from typing import Tuple
from . import Terminal, SubCircuitElement, Ground
from ..analog_frontend import AnalogFrontend
from ..analog_system import AnalogSystem
from ..digital_control import DigitalControl as NominalDigitalControl
from ..digital_control.dither_control import DitherControl as NominalDitherControl
from .components.passives import Resistor, Capacitor
import numpy as np
from .components.summer import DifferentialSummer
from .components.comparator import DifferentialOutputClockedComparator
from .components.opamp import OTA
from .digital_control import DigitalControl, DitherControl
from .analog_frontend import CircuitAnalogFrontend
[docs]class MultiInputOTA(SubCircuitElement):
"""A multi-input OTA.
Parameters
----------
instance_name : str
The instance name of the OTA.
analog_system : AnalogSystem
The analog system to which this OTA belongs.
index : int
The index of the OTA. To determine between which states the integrating capacitor belongs.
C : float
The integration capacitance.
DC_gain : float
The DC gain of the OTA.
"""
def __init__(
self,
instance_name: str,
analog_system: AnalogSystem,
index: int,
C: float,
DC_gain: float,
):
super().__init__(
instance_name,
f"multi_input_ota_{index}",
[Terminal("VSS"), Terminal("VDD"), Terminal("VCM")]
+ [Terminal(f"X{i}_P") for i in range(analog_system.N)]
+ [Terminal(f"X{i}_N") for i in range(analog_system.N)]
+ [Terminal(f"IN{i}_P") for i in range(analog_system.L)]
+ [Terminal(f"IN{i}_N") for i in range(analog_system.L)]
+ [Terminal(f"S{i}_P") for i in range(analog_system.M)]
+ [Terminal(f"S{i}_N") for i in range(analog_system.M)],
)
for n in range(analog_system.N):
if analog_system.A[index, n] != 0.0:
self._generate_ota_coupling(
analog_system.A[index, n] * C,
f"G_a_{index}_{n}",
(self[f"X{n}_P"], self[f"X{n}_N"]),
(self[f"X{index}_P"], self[f"X{index}_N"]),
)
for l in range(analog_system.L):
if analog_system.B[index, l] != 0.0:
self._generate_ota_coupling(
analog_system.B[index, l] * C,
f"G_b_{index}_{l}",
(
self[f"IN{l}_P"],
self[f"IN{l}_N"],
),
(self[f"X{index}_P"], self[f"X{index}_N"]),
)
if analog_system.Gamma is None:
raise ValueError("Gamma matrix must be defined")
for m in range(analog_system.M):
if analog_system.Gamma[index, m] != 0.0:
self._generate_ota_coupling(
analog_system.Gamma[index, m] * C,
f"G_gamma_{index}_{m}",
(
self[f"S{m}_P"],
self[f"S{m}_N"],
),
(self[f"X{index}_P"], self[f"X{index}_N"]),
)
# Finite DC gain
if DC_gain is not np.inf:
R_DC = DC_gain / (float(np.max(np.abs(analog_system.B))) * C)
_RP = Resistor("Rp", 2 * R_DC)
_RN = Resistor("Rn", 2 * R_DC)
self.add(_RP, _RN)
self.connects(
(self[f"X{index}_P"], _RP[0]),
(self[f"X{index}_N"], _RN[1]),
(self["VCM"], _RP[1]),
(self["VCM"], _RN[0]),
)
def _generate_ota_coupling(
self,
gm,
instance_name: str,
input_pair: Tuple[Terminal, Terminal],
out_pair: Tuple[Terminal, Terminal],
):
ota = OTA(
instance_name,
"ota",
np.abs(gm),
)
self.add(ota)
self.connects(
(self["VSS"], ota["VSS"]),
(self["VDD"], ota["VDD"]),
(out_pair[0], ota["OUT_P"]),
(out_pair[1], ota["OUT_N"]),
)
# Input polarity
if gm < 0:
self.connects((input_pair[0], ota["IN_P"]), (input_pair[1], ota["IN_N"]))
else:
self.connects((input_pair[1], ota["IN_P"]), (input_pair[0], ota["IN_N"]))
[docs]class GmCFrontend(CircuitAnalogFrontend):
"""A transconductance integrator based analog frontend.
Parameters
----------
analog_frontend : AnalogFrontend
The analog frontend to which this circuit belongs.
vdd_voltage : float, optional
The supply voltage, by default 1.2
in_high : float, optional
The high input voltage threshold for the comparator, by default 0.0
in_low : float, optional
The low input voltage threshold for the comparator, by default 0.0
C_int : float, optional
The integration capacitance, by default 100e-15
DC_gain : float, optional
The DC gain of the OTA, by default 1e6
"""
C: float
def __init__(
self,
analog_frontend: AnalogFrontend,
vdd_voltage: float = 1.2,
in_high=0.0,
in_low=0.0,
C_int: float = 100e-15,
DC_gain: float = 1e6,
):
self.C = C_int
super().__init__(
analog_frontend,
vdd_voltage,
in_high,
in_low,
subckt_name="ota_frontend",
instance_name="Xaf",
)
self._generate_integrators(analog_frontend.analog_system, C_int, DC_gain)
def _generate_integrators(
self, analog_system: AnalogSystem, C_int: float, DC_gain: float
):
for n in range(analog_system.N):
gmc = MultiInputOTA(f"gm{n}", analog_system, n, C_int, DC_gain)
self.add(gmc)
self.connects(
(self["VSS"], gmc["VSS"]),
(self["VDD"], gmc["VDD"]),
(self["VCM"], gmc["VCM"]),
)
# States
for nn in range(analog_system.N):
self.connects(
(self.xp[nn], gmc[f"X{nn}_P"]),
(self.xn[nn], gmc[f"X{nn}_N"]),
)
# Inputs
for l in range(analog_system.L):
self.connects(
(self[f"IN{l}_P"], gmc[f"IN{l}_P"]),
(self[f"IN{l}_N"], gmc[f"IN{l}_N"]),
)
# Outputs
for m in range(analog_system.M):
self.connects(
(self[f"OUT{m}_P"], gmc[f"S{m}_P"]),
(self[f"OUT{m}_N"], gmc[f"S{m}_N"]),
)
# Integrating capacitors
_cap_p = Capacitor(f"CP_{n}", 2 * C_int)
_cap_n = Capacitor(f"CN_{n}", 2 * C_int)
self.add(_cap_p, _cap_n)
self.connects(
(self.xp[n], _cap_p[0]),
(self["VCM"], _cap_p[1]),
(self.xn[n], _cap_n[1]),
(self["VCM"], _cap_n[0]),
)