2022-02-21 20:18:14 +01:00
#!/usr/bin/env python3
""" Generate some figures showing the alignment of clocks
in a White Rabbit system with GrandMaster setup .
"""
import matplotlib . pyplot as plt
import numpy as np
import scipy . signal as sig
rng = np . random . default_rng ( 12345 )
### Functions
def pps ( t , t_start , width = 0.5 ) :
"""
Generate a PPS with width $ width $ and starting at $ t_start .
"""
return ( t > t_start ) & ( t < t_start + width )
def detect_rising_edges ( threshold , data ) :
"""
Detect rising edges in data .
https : / / stackoverflow . com / a / 50365462
"""
return np . flatnonzero ( ( data [ : - 1 ] < threshold ) & ( data [ 1 : ] > threshold ) ) + 1
def first_shared_edge ( x1 , x2 , threshold = 0.3 ) :
try :
length = len ( x2 )
except TypeError :
length = 1
x1_edges = detect_rising_edges ( threshold , x1 )
if length > 1 :
x2_edges = detect_rising_edges ( threshold , x2 )
else :
x2_edges = x2
start_edge = x1_edges [ x1_edges > x2_edges ] [ 0 ]
return start_edge
def aligned_pps ( t , clock_in , pps_in , width = None ) :
t_start = t [ first_shared_edge ( clock_in , pps_in ) ]
if width is not None :
return pps ( t , t_start , width )
else :
return pps ( t , t_start )
## Main
def main ( time_base = 10e-9 ) :
"""
Generate a figure showing the required GrandMaster inputs
with an aligned PPS out and DIO clock ,
and a random input event and its timestamp .
"""
clock_freq = 10e6 # Hz
dio_freq = 12.5 * clock_freq # Hz
pps_in_early = - 1.7 / clock_freq #s
2022-03-02 11:35:58 +01:00
pps_in_width = 1e2 / clock_freq #s
pps_out_width = 1e2 / clock_freq #s
2022-02-21 20:18:14 +01:00
t = np . linspace ( - 2.25 * 1 / clock_freq , 0.8 * 1 / clock_freq , 5000 ) #
random_event_idx = rng . integers ( len ( t ) * 2 / 3 , len ( t ) ) # Somewhere within the time space
random_event = t [ random_event_idx ]
## Create Grandmaster input signals
clock_in = ( sig . square ( 2 * np . pi * clock_freq * t ) + 1 ) / 2
pps_in = pps ( t , pps_in_early , pps_in_width )
## Determine output signal
clock_alignment = t [ first_shared_edge ( clock_in , pps_in ) ]
pps_out = aligned_pps ( t , clock_in , pps_in , width = pps_out_width )
dio = ( sig . square ( 2 * np . pi * dio_freq * ( t - clock_alignment ) ) + 1 ) / 2
## Random event timestamp
timestamped_event = t [ first_shared_edge ( dio , random_event_idx ) ]
2022-03-02 11:35:58 +01:00
2022-02-21 20:18:14 +01:00
# Create the figure
fig , axs = plt . subplots ( 4 , 1 , sharex = True , gridspec_kw = { ' hspace ' : 0 } , figsize = ( 16 , 4 ) )
## Plot signals
i = 0
axs [ i ] . set_ylabel ( " $ \ mathrm {PPS} _ \ mathrm {in} $ " , rotation = ' horizontal ' , ha = ' right ' , va = ' center ' )
axs [ i ] . plot ( t , pps_in , ' purple ' , label = " PPS in " )
i + = 1
axs [ i ] . set_ylabel ( " GM Clock \n ($10 \ , \ mathrm {MHz} $) " , rotation = ' horizontal ' , ha = ' right ' , va = ' center ' )
axs [ i ] . plot ( t , clock_in , label = ' 10MHz in ' )
i + = 1
axs [ i ] . set_ylabel ( " FMC DIO \n ($125 \ ,MHz$) " , rotation = ' horizontal ' , ha = ' right ' , va = ' center ' )
axs [ i ] . plot ( t , dio , ' y ' , label = ' DIO ' )
axs [ i ] . plot ( random_event , 0.5 , ' r* ' )
axs [ i ] . axvline ( timestamped_event , color = ' b ' )
i + = 1
axs [ i ] . set_ylabel ( " $ \ mathrm {PPS} _ \ mathrm {out} $ " , rotation = ' horizontal ' , ha = ' right ' , va = ' center ' )
axs [ i ] . plot ( t , pps_out , ' g ' , label = " PPS out " )
## Styling
for ax in axs :
ax . axvline ( clock_alignment , color = ' r ' , linestyle = ' -- ' )
ax . set_ylim ( - 0.2 , 1.2 )
ax . set_yticks ( [ ] )
ax . set_yticklabels ( [ ] )
ax . grid ( )
if time_base == 10e-9 :
axs [ - 1 ] . set_xlabel ( " Time (ns) " )
2022-03-02 11:35:58 +01:00
ticks = axs [ - 1 ] . get_xticks ( ) * 10 / time_base # 10 was experimentally determined
2022-02-21 20:18:14 +01:00
axs [ - 1 ] . set_xticklabels ( np . floor ( ticks ) )
else :
axs [ - 1 ] . set_xlabel ( " Time (s) " )
return fig , ( clock_alignment , random_event , timestamped_event )
if __name__ == " __main__ " :
from argparse import ArgumentParser
import os . path as path
parser = ArgumentParser ( description = __doc__ )
parser . add_argument ( " fname " , metavar = " path/to/figure[/] " , nargs = " ? " , help = " Location for generated figure, will append __file__ if a directory. If not supplied, figure is shown. " )
args = parser . parse_args ( )
if args . fname is not None and path . isdir ( args . fname ) :
args . fname = path . join ( args . fname , path . splitext ( path . basename ( __file__ ) ) [ 0 ] + " .pdf " )
###
fig , _ = main ( )
if args . fname is not None :
plt . savefig ( args . fname )
else :
plt . show ( )