fix helper PLL glitches

sim: rename collector to phase_collector
sim: add period_collector for gtx tags
sim: trigger helper PLL after period_collector_r
sim: remove cycle slip compensation
sim: generator gtx in time loop
wave_gen: replace square generator with phase accumulator
wave_gen&wrpll: add white_noise generator
docs: remove section & png about cycle slipping and deglitcher failure
This commit is contained in:
morgan 2023-12-12 13:32:22 +08:00
parent da5908e754
commit 0a66f4c343
6 changed files with 114 additions and 153 deletions

View File

@ -49,20 +49,3 @@ source .venv/bin/activate
## Limitation ## Limitation
As the simulation is not cycle nor delay accurate, there will be more glitches than the hardware implementation As the simulation is not cycle nor delay accurate, there will be more glitches than the hardware implementation
### Helper PLL glitches (remedies are added to follow hardware behavior)
- Cycle slipping issue will appear as $|\Delta{period}| \sim N$
- During hardware testing, slipping issue is not common
- It's recommended to turn cycle_slip_comp ON to reduce slipping and have a more accurate simulation
![cycle_slip](img/cycle_slipping.png)
- Deglitcher fail issue will appear as $|\Delta{period}| \sim N/2$
- There are no such issue for hardware
- It's recommended to set blind_period higher than the hardware setting (around 300 is sufficient)
![deglitch_fail](img/deglitch_fail.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@ -6,7 +6,8 @@ from wave_gen import square
@njit @njit
def simulation_jit( def simulation_jit(
time, time,
gtx, gtx_freq,
gtx_jitter,
helper_pll, helper_pll,
main_pll, main_pll,
h_KP, h_KP,
@ -16,13 +17,13 @@ def simulation_jit(
m_KI, m_KI,
m_KD, m_KD,
dcxo_freq, dcxo_freq,
dcxo_jitter_SD, h_jitter,
m_jitter,
base_adpll, base_adpll,
N, N,
adpll_write_period, adpll_write_period,
blind_period, blind_period,
start_up_delay, start_up_delay,
cycle_slip_comp=True,
helper_init_freq=0 helper_init_freq=0
): ):
@ -30,6 +31,7 @@ def simulation_jit(
main = np.zeros(arr_len, dtype=np.int8) main = np.zeros(arr_len, dtype=np.int8)
helper = np.zeros(arr_len, dtype=np.int8) helper = np.zeros(arr_len, dtype=np.int8)
gtx = np.zeros(arr_len, dtype=np.int8)
gtx_tag = 0 gtx_tag = 0
gtx_ready = 0 gtx_ready = 0
@ -40,7 +42,6 @@ def simulation_jit(
main_ready = 0 main_ready = 0
main_FF = 0 main_FF = 0
main_beating = np.zeros(arr_len, dtype=np.int8) main_beating = np.zeros(arr_len, dtype=np.int8)
collector_r = 0
phase_err_arr = np.zeros(arr_len, dtype=np.int16) phase_err_arr = np.zeros(arr_len, dtype=np.int16)
period_err_arr = np.zeros(arr_len, dtype=np.int16) period_err_arr = np.zeros(arr_len, dtype=np.int16)
@ -50,15 +51,20 @@ def simulation_jit(
helperfreq = np.zeros(arr_len, dtype=np.int32) helperfreq = np.zeros(arr_len, dtype=np.int32)
mainfreq = np.zeros(arr_len, dtype=np.int32) mainfreq = np.zeros(arr_len, dtype=np.int32)
# initial condition phase_collector_r = 0
main_init_offset = (np.random.uniform(0, 360)) / (360 * dcxo_freq) colr_gtx_tag = colr_main_tag = 0
helper_init_offset = (np.random.uniform(0, 360)) / (360 * dcxo_freq) phase_collector_state = 0
# intermediate values period_collector_r = 0
helper_jitter = np.random.normal(0, dcxo_jitter_SD) colr_last_gtx_tag = 0
main_jitter = np.random.normal(0, dcxo_jitter_SD) beating_period = 0
helper_cycle_num = 0 period_collector_state = 0
main_cycle_num = 0
# initial condition
timestep = time[1] - time[0]
gtx_phase = 0
h_phase = np.random.uniform(0, 360)
m_phase = np.random.uniform(0, 360)
if main_pll and not helper_pll: if main_pll and not helper_pll:
helper_freq = helper_init_freq helper_freq = helper_init_freq
@ -66,12 +72,10 @@ def simulation_jit(
helper_freq = dcxo_freq * (1 + base_adpll * 0.0001164 / 1_000_000) * ((N-1) / N) helper_freq = dcxo_freq * (1 + base_adpll * 0.0001164 / 1_000_000) * ((N-1) / N)
main_freq = dcxo_freq * (1 + base_adpll * 0.0001164 / 1_000_000) main_freq = dcxo_freq * (1 + base_adpll * 0.0001164 / 1_000_000)
FSM_state = 0
coll_gtx_tag = coll_main_tag = 0
last_gtx_tag = last_gtx_FF = last_gtx_beat = 0 last_gtx_tag = last_gtx_FF = last_gtx_beat = 0
last_main_tag = last_main_FF = last_main_beat = 0 last_main_tag = last_main_FF = last_main_beat = 0
last_helper = last_main = 0 last_helper = 0
counter = 0 counter = 0
gtx_blind_counter = 0 gtx_blind_counter = 0
@ -84,38 +88,32 @@ def simulation_jit(
wait_main = True wait_main = True
# firmware values # firmware values
FW_gtx_tag = FW_last_gtx_tag = 0 FW_gtx_tag = 0
FW_main_tag = 0 FW_main_tag = 0
period_err = last_period_err = 0 period_err = last_period_err = 0
h_prop = h_integrator = h_derivative = 0 h_prop = h_integrator = h_derivative = 0
h_adpll = base_adpll h_adpll = base_adpll
h_i2c_active = False period_colr_arm = h_i2c_active = False
phase_err = last_phase_err = 0 phase_err = last_phase_err = 0
m_prop = m_integrator = m_derivative = 0 m_prop = m_integrator = m_derivative = 0
m_adpll = base_adpll m_adpll = base_adpll
m_i2c_active = False phase_colr_arm = m_i2c_active = False
adpll_active = False
adpll_max = 8161512 adpll_max = 8161512
def clip(n, minn, maxn): return max(min(maxn, n), minn) def clip(n, minn, maxn): return max(min(maxn, n), minn)
for i, t in enumerate(time): for i, t in enumerate(time):
helper[i], helper_cycle_num, helper_jitter = square( h_phase += 360 * helper_freq * (timestep + h_jitter[i])
t + helper_init_offset, helper_freq, dcxo_jitter_SD, helper_jitter, helper_cycle_num) helper[i] = square(h_phase)
main[i], main_cycle_num, main_jitter = square(
t + main_init_offset, main_freq, dcxo_jitter_SD, main_jitter, main_cycle_num)
# continuous glitchless output, assume very small frequency change m_phase += 360 * main_freq * (timestep + m_jitter[i])
if h_i2c_active and helper[i]: main[i] = square(m_phase)
helper_freq = dcxo_freq * (1 + h_adpll * 0.0001164 / 1_000_000) * ((N-1) / N)
h_i2c_active = False
if m_i2c_active and main[i]: gtx_phase += 360 * gtx_freq * (timestep + gtx_jitter[i])
main_freq = dcxo_freq * (1 + m_adpll * 0.0001164 / 1_000_000) gtx[i] = square(gtx_phase)
m_i2c_active = False
if not last_helper and helper[i]: if not last_helper and helper[i]:
@ -130,12 +128,15 @@ def simulation_jit(
main_blind_counter, main_blinded, blind_period, main_blind_counter, main_blinded, blind_period,
last_main_beat, last_main_tag, counter) last_main_beat, last_main_tag, counter)
collector_r, wait_gtx, wait_main, coll_gtx_tag, coll_main_tag, FSM_state = Collector_FSM(gtx_ready, main_ready, gtx_tag, main_tag, phase_collector_r, wait_gtx, wait_main, colr_gtx_tag, colr_main_tag, phase_collector_state = phase_collector_FSM(gtx_ready, main_ready, gtx_tag, main_tag,
wait_gtx, wait_main, coll_gtx_tag, coll_main_tag, FSM_state) wait_gtx, wait_main, colr_gtx_tag, colr_main_tag, phase_collector_state)
if collector_r: if phase_collector_r:
FW_gtx_tag = coll_gtx_tag FW_gtx_tag = colr_gtx_tag
FW_main_tag = coll_main_tag FW_main_tag = colr_main_tag
period_collector_r, colr_last_gtx_tag, beating_period, period_collector_state = period_collector_FSM(gtx_ready, gtx_tag,
colr_last_gtx_tag, period_collector_state)
counter += 1 counter += 1
@ -158,20 +159,11 @@ def simulation_jit(
if i > start_up_delay: if i > start_up_delay:
# Firmware filters # Firmware filters
if adpll_active and collector_r:
if cycle_slip_comp:
period = FW_gtx_tag - FW_last_gtx_tag
if period > 3 * N/2:
period = period - N
period_err = N - period
else:
period_err = (N - (FW_gtx_tag - FW_last_gtx_tag))
tag_diff = ((FW_main_tag - FW_gtx_tag) % N) if period_colr_arm and period_collector_r:
if tag_diff > N/2:
phase_err = tag_diff - N period_colr_arm = False
else: period_err = N - beating_period
phase_err = tag_diff
if helper_pll: if helper_pll:
h_prop = period_err * h_KP h_prop = period_err * h_KP
@ -180,8 +172,18 @@ def simulation_jit(
h_adpll = clip(int(base_adpll + h_prop + h_integrator + h_derivative), -adpll_max, adpll_max) h_adpll = clip(int(base_adpll + h_prop + h_integrator + h_derivative), -adpll_max, adpll_max)
last_period_err = period_err last_period_err = period_err
helper_freq = dcxo_freq * (1 + h_adpll * 0.0001164 / 1_000_000) * ((N-1) / N)
h_i2c_active = True h_i2c_active = True
if phase_colr_arm and phase_collector_r:
phase_colr_arm = False
tag_diff = ((FW_main_tag - FW_gtx_tag) % N)
if tag_diff > N/2:
phase_err = tag_diff - N
else:
phase_err = tag_diff
if main_pll: if main_pll:
m_prop = phase_err * m_KP m_prop = phase_err * m_KP
m_integrator += phase_err * m_KI m_integrator += phase_err * m_KI
@ -189,16 +191,13 @@ def simulation_jit(
m_adpll = clip(int(base_adpll + m_prop + m_integrator + m_derivative), -adpll_max, adpll_max) m_adpll = clip(int(base_adpll + m_prop + m_integrator + m_derivative), -adpll_max, adpll_max)
last_phase_err = phase_err last_phase_err = phase_err
main_freq = dcxo_freq * (1 + m_adpll * 0.0001164 / 1_000_000)
m_i2c_active = True m_i2c_active = True
adpll_active = False
if i % adpll_write_period == 0: if i % adpll_write_period == 0:
adpll_active = True period_colr_arm = phase_colr_arm = True
FW_last_gtx_tag = FW_gtx_tag
last_helper = helper[i] last_helper = helper[i]
last_main = main[i]
# Data # Data
period_err_arr[i] = period_err period_err_arr[i] = period_err
@ -208,7 +207,7 @@ def simulation_jit(
helperfreq[i] = helper_freq helperfreq[i] = helper_freq
mainfreq[i] = main_freq mainfreq[i] = main_freq
return period_err_arr, phase_err_arr, helper_adpll_arr, main_adpll_arr, gtx_beating, main_beating, helper, main, helperfreq, mainfreq return period_err_arr, phase_err_arr, helper_adpll_arr, main_adpll_arr, gtx_beating, main_beating, gtx, helper, main, helperfreq, mainfreq
@njit @njit
@ -237,37 +236,55 @@ def Deglitcher(beating, t_out, t_ready, blind_counter, blinded, blind_period, la
@njit @njit
def Collector_FSM(g_tag_r, m_tag_r, gtx_tag, main_tag, wait_gtx, wait_main, coll_gtx_tag, coll_main_tag, FSM_state): def phase_collector_FSM(g_tag_r, m_tag_r, gtx_tag, main_tag, wait_gtx, wait_main, colr_gtx_tag, colr_main_tag, FSM_state):
collector_r = 0 collector_r = 0
match FSM_state: match FSM_state:
case 0: # IDEL case 0: # IDEL
if g_tag_r and m_tag_r: if g_tag_r and m_tag_r:
coll_gtx_tag = gtx_tag colr_gtx_tag = gtx_tag
coll_main_tag = main_tag colr_main_tag = main_tag
FSM_state = 3 # OUTPUT FSM_state = 3 # OUTPUT
elif g_tag_r: elif g_tag_r:
coll_gtx_tag = gtx_tag colr_gtx_tag = gtx_tag
wait_main = True wait_main = True
FSM_state = 2 # WAITMAIN FSM_state = 2 # WAITMAIN
elif m_tag_r: elif m_tag_r:
coll_main_tag = main_tag colr_main_tag = main_tag
wait_gtx = True wait_gtx = True
FSM_state = 1 # WAITGTX FSM_state = 1 # WAITGTX
case 1: # WAITGTX case 1: # WAITGTX
if g_tag_r: if g_tag_r:
coll_gtx_tag = gtx_tag colr_gtx_tag = gtx_tag
FSM_state = 3 # OUTPUT FSM_state = 3 # OUTPUT
case 2: # WAITMAIN case 2: # WAITMAIN
if m_tag_r: if m_tag_r:
coll_main_tag = main_tag colr_main_tag = main_tag
FSM_state = 3 # OUTPUT FSM_state = 3 # OUTPUT
case 3: # OUTPUT case 3: # OUTPUT
wait_gtx = wait_main = False wait_gtx = wait_main = False
collector_r = 1 collector_r = 1
FSM_state = 0 FSM_state = 0
return collector_r, wait_gtx, wait_main, coll_gtx_tag, coll_main_tag, FSM_state return collector_r, wait_gtx, wait_main, colr_gtx_tag, colr_main_tag, FSM_state
@njit
def period_collector_FSM(g_tag_r, gtx_tag, colr_last_gtx_tag, FSM_state):
collector_r = 0
beating_period = 0
match FSM_state:
case 0: # IDEL
if g_tag_r:
colr_last_gtx_tag = gtx_tag
FSM_state = 1
case 1: # OUTPUT
if g_tag_r:
beating_period = gtx_tag - colr_last_gtx_tag
collector_r = 1
FSM_state = 0 # IDEL
return collector_r, colr_last_gtx_tag, beating_period, FSM_state

View File

@ -1,69 +1,28 @@
import numpy as np import numpy as np
import numba as nb
from numba import njit from numba import njit
@njit def white_noise(low, high, size, seed=None):
def square(time, freq, jitter_SD, jitter, cycle_num): return np.random.default_rng(seed).uniform(low, high, size)
"""
A scipy like square wave with jitter
Parameters
----------
time : timestamp, in seconds
freq : frequency in Hz
jitter_SD : standard deviation of jitter, in seconds
jitter : gaussian noise with `jitter_SD` as its standard deviation
cycle_num : time // period = cycle_num
----------
"""
period = 1/(freq)
quarter = (period / 4)
out = 0
# A T/4 shift is applied to the following to match scipy square fn
# ┌──────┐
# ───┘ └────
# T/4 3T/4
nth_cycle, t = np.divmod(time + period / 4, period)
if t >= (quarter + jitter) and t <= (3*quarter + jitter):
out = 1
else:
out = 0
# update jitter every cycle
if nth_cycle != cycle_num:
jitter = np.random.normal(0, jitter_SD)
cycle_num = nth_cycle
return out, cycle_num, jitter
@njit @njit(fastmath=True)
def square_arr(time, freq, jitter_SD): def square_with_jitter(time, freq, jitter):
""" n = len(time)
wave = np.empty(n)
A scipy like square wave with jitter timestep = time[1] - time[0]
Parameters
----------
time : numpy array
freq : frequency in Hz
jitter_SD : standard deviation of jitter, in seconds
----------
"""
wave = np.zeros(len(time))
jitter = np.random.normal(0, jitter_SD)
cycle_num = 0
for i, t in enumerate(time):
wave[i], cycle_num, jitter = square(t, freq, jitter_SD, jitter, cycle_num)
phase = 0.
for i in range(n):
phase += 360 * freq * (timestep + jitter[i])
wave[i] = square(phase)
return wave return wave
@njit
def square(x):
if np.mod(x, 360) < 180:
return 0
else:
return 1

View File

@ -1,5 +1,5 @@
import numpy as np import numpy as np
from wave_gen import square_arr from wave_gen import white_noise
from sim import simulation_jit from sim import simulation_jit
@ -12,16 +12,16 @@ class WRPLL_simulator():
helper_filter, helper_filter,
main_filter, main_filter,
gtx_freq, gtx_freq,
gtx_jitter_SD, gtx_jitter,
dcxo_freq, dcxo_freq,
dcxo_jitter_SD, dcxo_jitter,
freq_acquisition_SD, freq_acquisition_SD,
N, N,
adpll_write_period, adpll_write_period,
blind_period, blind_period,
start_up_delay, start_up_delay,
cycle_slip_comp, helper_init_freq=None,
helper_init_freq=None seed=None
): ):
self.time = time self.time = time
@ -34,13 +34,15 @@ class WRPLL_simulator():
self.m_KD = main_filter["KD"] self.m_KD = main_filter["KD"]
# init condition # init condition
self.gtx_freq = gtx_freq
self.gtx_jitter = white_noise(-gtx_jitter, gtx_jitter, len(time), seed)
self.dcxo_freq = dcxo_freq self.dcxo_freq = dcxo_freq
self.dcxo_jitter_SD = dcxo_jitter_SD self.h_jitter = white_noise(-dcxo_jitter, dcxo_jitter, len(time), seed)
self.m_jitter = white_noise(-dcxo_jitter, dcxo_jitter, len(time), seed)
self.N = N self.N = N
self.helper_init_freq = helper_init_freq self.helper_init_freq = helper_init_freq
self.gtx = square_arr(time, gtx_freq, gtx_jitter_SD)
# freq_acquisition() error # freq_acquisition() error
freq_diff = gtx_freq - dcxo_freq + np.random.normal(0, freq_acquisition_SD) freq_diff = gtx_freq - dcxo_freq + np.random.normal(0, freq_acquisition_SD)
self.base_adpll = int(freq_diff * (1 / dcxo_freq) * (1e6 / 0.0001164)) self.base_adpll = int(freq_diff * (1 / dcxo_freq) * (1e6 / 0.0001164))
@ -49,7 +51,6 @@ class WRPLL_simulator():
self.adpll_write_period = adpll_write_period self.adpll_write_period = adpll_write_period
self.blind_period = blind_period self.blind_period = blind_period
self.start_up_delay = start_up_delay self.start_up_delay = start_up_delay
self.cycle_slip_comp = cycle_slip_comp
if type(self.sim_mode) is not str: if type(self.sim_mode) is not str:
raise ValueError(f"pll_type {type(self.sim_mode)} is not a string") raise ValueError(f"pll_type {type(self.sim_mode)} is not a string")
@ -68,9 +69,10 @@ class WRPLL_simulator():
def run(self): def run(self):
print("running simulation...") print("running simulation...")
self.period_err, self.phase_err, self.helper_adpll, self.main_adpll, self.gtx_beating, self.main_beating, self.helper, self.main, self.helperfreq, self.mainfreq = simulation_jit( self.period_err, self.phase_err, self.helper_adpll, self.main_adpll, self.gtx_beating, self.main_beating, self.gtx, self.helper, self.main, self.helperfreq, self.mainfreq = simulation_jit(
self.time, self.time,
self.gtx, self.gtx_freq,
self.gtx_jitter,
self.helper_pll, self.helper_pll,
self.main_pll, self.main_pll,
self.h_KP, self.h_KP,
@ -80,13 +82,13 @@ class WRPLL_simulator():
self.m_KI, self.m_KI,
self.m_KD, self.m_KD,
self.dcxo_freq, self.dcxo_freq,
self.dcxo_jitter_SD, self.h_jitter,
self.m_jitter,
self.base_adpll, self.base_adpll,
self.N, self.N,
self.adpll_write_period, self.adpll_write_period,
self.blind_period, self.blind_period,
self.start_up_delay, self.start_up_delay,
self.cycle_slip_comp,
self.helper_init_freq self.helper_init_freq
) )
print("Done!") print("Done!")