"""Analog clock signals.
"""
from cbadc.analog_signal._analog_signal import _AnalogSignal
from sympy import Piecewise
import numpy as np
[docs]class Clock(_AnalogSignal):
"""An analog computer clock signal.
Specifically, based on a clock period T a
clock represents an analog clock edge signal.
Parameters
----------
T: `float`
the clock period
tt: `float`
transition time from one clock edge to the other, defaults
to 1ps.
td: `float`
a global delay for the clock transition, defaults to 0.
duty_cycle: `float`
a number (0, 1] representing how long the signal
is high (1) relatively low (-1), defaults to 0.5.
max_swing: `float`
largest permissible swing of the clock signal
Attributes
----------
T: `float`
the clock period.
tt: `float`
the transition time.
td: `float`
any global time delay.
duty_cycle: `float`
the duty cycle
max_value: `float`
the largest value of the clock signal. Similarly,
the smallest value is -max_value.
"""
def __init__(
self,
T: float,
tt: float = 1e-14,
td: float = 0.0,
duty_cycle: float = 0.5,
max_swing: float = 2.0,
) -> None:
super().__init__()
self.T = T
self.tt = tt
self._tt_2 = self.tt / 2.0
self.td = td
self.duty_cycle = duty_cycle
self._neq_pulse_time = self.duty_cycle * self.T
self.max_value = max_swing / 2.0
if duty_cycle > 1.0 or duty_cycle <= 0.0:
raise Exception(
f"duty_cycle must be a number between 0 and up to 1. Not {duty_cycle}"
)
if tt > self.max_step():
raise Exception(
"transition time tt can't be longer than smallest clock period"
)
if td > T:
raise Exception(
"Does not make sense to have longer global delay than time period."
)
def max_step(self):
return self.T * min(self.duty_cycle, 1 - self.duty_cycle)
def _pos_edge(self, t):
tmp = (t - self._tt_2) / self._tt_2
if tmp > self.max_value:
return tmp / tmp
elif tmp < -self.max_value:
return -tmp / tmp
return tmp
def clock_edge(self, t):
t_ = (t - self.td + self._tt_2) % self.T
if t_ > self._neq_pulse_time:
return -self._pos_edge(t_ - self._neq_pulse_time)
else:
return self._pos_edge(t_)
[docs] def next_tick(self, t):
"""Return time of next positive
clock edge relative to time t
Parameters
----------
t: `float`
current time
Returns
-------
: `float`
the time of next positive clock edge.
"""
t_ = (t - self.td) % self.T
if np.allclose(t_, 0):
return t
return t + self.T - t_
[docs] def symbolic(self) -> Piecewise:
"""Returns as symbolic exression
Returns
-------
: :py:class:`sympy.Symbol`
the resulting function
"""
t_ = (self.t - self.td + self._tt_2) % self.T
return Piecewise(
(-self._pos_edge(t_), t_ > self.duty_cycle * self.T),
(self._pos_edge(t_), True),
)
[docs] def evaluate(self, t: float) -> float:
"""Evaluate the signal at time :math:`t`.
Parameters
----------
t : `float`
the time instance for evaluation.
Returns
-------
float
The analog signal value
"""
return self.clock_edge(t)
def __str__(self):
return f"Clock(T={self.T}, tt={self.tt}, td={self.td}, duty_cycle={self.duty_cycle}, max_swing={self.max_value * 2})"
[docs]def delay_clock_by_duty_cycle(clock: Clock, delay: float = 0):
"""Create a delayed clock version of
an already existing clock signal.
Parameters
----------
clock: :py:class:`cbadc.analog_signal.Clock`
the clock from which the new clock is derived
delay: `float`
the amount of delay, defaults to the duty cycle
which can be interpreted as if the positive edge
of the new clock overlaps with the negative edge
of the former.
"""
if not delay:
phase_delay_to_readout = clock.duty_cycle * clock.T
else:
phase_delay_to_readout = delay
return Clock(
T=clock.T,
tt=clock.tt,
td=clock.td + phase_delay_to_readout,
max_swing=clock.max_value * 2,
)