from __future__ import division

# import cppsimdata module
import sys
import os
cppsimsharedhome = os.getenv("CPPSIMSHAREDHOME")
if cppsimsharedhome != None:
   CPPSIMSHARED_PATH = '%s' % cppsimsharedhome
else:
   HOME = os.getenv("HOME")
   CPPSIMSHARED_PATH = '%s/CppSim/CppSimShared' % HOME

sys.path.append(CPPSIMSHARED_PATH + '/Python')
from cppsimdata import *

# import pylab package
from pylab import *

from scipy.signal import lfilter,welch,hann
from scipy import fft

plot_cppsim_results = 1
plot_period_jitter_results = 0
plot_gain_and_phase_margin = 0

fref = 20e6
fvco = 1.84e9

f = logspace(log10(1e3),log10(20e6),100e3)
s = 1j*2*pi*f
Ts = 1/fref

###### PLL Dynamic Models ######

## PD - assumes XOR-based PFD
Kd = 1/pi

## Charge Pump
Kcp = 1.5e-6

## Loop Filter
lf_gain = 1/30e-12 # corresponds to overall loop filter cap in classical loop filter topology used in charge pump designs
fz = 11.8e3 # Hz
fp = 127.2e3 # Hz
H_lf = lf_gain*(1 + s/(2*pi*fz))/(s*(1 + s/(2*pi*fp)))

## VCO
Kv = 30e6
H_vco = 2*pi*Kv/s

## Divider
N = fvco/fref

## Overall
A = Kd*Kcp*H_lf*H_vco*1/N

#semilogx(f,20*log10(abs(A)))
#grid(True,which='both')
#show()
#sys.exit()

###### PLL Noise Models ######
## See paper:  "A modeling approach for Delta-Sigma fractional-N frequency synthesizers allowing straightforward noise analysis"
##              M.H. Perrott, M.D. Trott, C.G. Sodini, IEEE J. Solid-State Circuits, vol. 37, pp. 1028-1038, Aug. 2002

# Charge pump noise - Assumes XOR PD in which case CP noise is always present but alternates between Up/Down noise processes
#                     Note 1:  XOR PD is assumed to have even duty cycle in switching between Up/Down states
#                     Note 2:  Tristate based PFD will have different expressions due to absence of CP noise when Up/Down not asserted
fcorner_cp = 1e3
fcorner_cp_effective = fcorner_cp*0.5
S_th_cp_effective = (0.6e-12**2)*0.5 + (1.5e-12**2)*0.5 # A^2/Hz single-sided - average CP noise with even duty cycle of XOR PD
S_th_cp_effective_ds = S_th_cp_effective/2 # Convert from single-sided to double-sided A^2/Hz
S_cp = S_th_cp_effective_ds*abs(1 + fcorner_cp_effective/f)  

# Transfer function from Charge Pump to PLL output and corresponding PLL output spectrum calculation
A_cp = H_lf*H_vco
B_cp = 1/N*Kd*Kcp
G_cp = A_cp/(1 + B_cp*A_cp)
S_out_cp = abs(G_cp)**2*S_cp 

# VCO intrinsic phase noise
noise_buffer = -170 ## dBc/Hz - this is not modeled in sd_synth_fast example so set to very low value
noise_vco_at_foffset = -146 ## dBc/Hz
foffset = 10e6 ## Hz
fcorner_vco = 1e3 ## flicker corner in units of Hz
noise_vco_in = (foffset/Kv)**2*10**(noise_vco_at_foffset/10.0)
noise_buffer_val = 10**(noise_buffer/10)
S_vco_pn = abs(H_vco)**2*noise_vco_in*abs(1 + fcorner_vco/f) + noise_buffer_val

# Transfer function from VCO to PLL output and corresponding PLL output spectrum calculation
A_vco = 1
B_vco = Kd*Kcp*H_lf*H_vco*1/N
G_vco = A_vco/(1 + B_vco*A_vco)
S_out_vco_pn = abs(G_vco)**2*S_vco_pn

# Delta-Sigma dithering noise for divider
ds_ord = 3 # Delta-Sigma order
z_ds = exp(1j*2*pi*f*Ts)
H_ds = (1 - z_ds**(-1))**ds_ord
S_ds = 1/12*abs(H_ds)**2

# Transfer function from Divider dithering to PLL output and corresponding PLL output spectrum calculation
A_div = A
B_div = 1
G_div = A_div/(1 + B_div*A_div)
S_out_div = 1/Ts*abs(Ts*G_div)**2*abs(2*pi*z_ds**(-1)/(1 - z_ds**(-1)))**2*S_ds

## Overall Phase Noise
S_out = S_out_vco_pn + S_out_cp + S_out_div

## calculate integrated jitter and period jitter based on phase noise S_out_tot
fstart=10e3
fend=20e6
# Tperiod below is for period jitter calculations
Tperiod = 1/fvco
i_start_raw = argwhere(f >= fstart)[0]
i_start = i_start_raw[0]
i_end_raw = argwhere(f >= fend)[0]
i_end = i_end_raw[0]

integ_pn = 0.0
for i in list(range((i_start+1),i_end+1)):
   integ_pn = integ_pn + (S_out[i] + S_out[i-1])*(f[i] - f[i-1])
integ_jitter = sqrt(integ_pn)/(2*pi*fvco)
integ_jitter_ps = integ_jitter*1e12


print('\nIntegrated jitter (fstart = %4.1f kHz, fend = %4.1f MHz, out_freq = %5.3f GHz) = %5.3f ps (rms)' % (fstart*1e-3,fend*1e-6,fvco*1e-9,integ_jitter_ps))

fig_pn = figure(1,figsize=(12,8))
fig_pn.clf()
# set line thickness in plots to 2
rcParams['lines.linewidth'] = 2

dir_name = os.getcwd()
file_basename = 'test_noise'
file_suffix_val = 'tr0'
sig_name = 'noiseout'

if plot_cppsim_results == 1:
   file_name= '%s/%s.%s' % (dir_name,file_basename,file_suffix_val)
   if os.path.isfile(file_name):
      print("\n... Opening file '%s'..." % file_name)
   else:
      print("... Stop:  file '%s' does not exist..." % file_name)
      sys.exit()

   data = CppSimData(file_name)
   vin = data.evalsig(sig_name)
   t = data.evalsig('TIME')
   t_delta = lfilter([1,-1],[1],t)
   Ts = mean(t_delta[1:-1])
   fs = 1/Ts
   f_sim,Pxx_db = calc_pll_phasenoise(vin,Ts)
   semilogx(f_sim,Pxx_db,'k')
   
semilogx(f,10*log10(S_out_vco_pn),'b-',label='VCO Phase Noise')
semilogx(f,10*log10(S_out_cp),'m-',label='CP Noise')
semilogx(f,10*log10(S_out_div),'g-',label='Delta-Sigma Noise')
semilogx(f,10*log10(S_out),'r-',label='Overall')
axis([1e3, 20e6, -180, -60])
xlabel('Offset Frequency (Hz)')
ylabel('dBc/Hz')
title('PLL Phase Noise (Carrier Freq = %5.3f GHz)' % (fvco*1e-9))
grid(True,which='both')
legend(loc='upper right',prop={'size':15})
fig_pn.show()

if plot_period_jitter_results == 1:
   # Period jitter calculations (assuming PLL output is divided down without buffer noise dominating)
   fig_pj = figure(2,figsize=(12,8))
   fig_pj.clf()
   # set line thickness in plots to 2
   rcParams['lines.linewidth'] = 2

   fclk_list = [10e6, 20e6, 50e6]
   for fclk_val in fclk_list:
      Tperiod = 1/fclk_val
      S_out_pj = S_out*(2*sin(pi*f*Tperiod))**2
      fclk_val_mhz = fclk_val*1e-6 

      integ_pn = 0.0
      for i in list(range((i_start+1),i_end+1)):
         integ_pn = integ_pn + (S_out_pj[i] + S_out_pj[i-1])*(f[i] - f[i-1])
         integ_jitter_pj = sqrt(integ_pn)/(2*pi*fvco)
         integ_jitter_pj_ps = integ_jitter_pj*1e12

      plot_label = '%5.3fMHz Clk: period jitter = %5.3f ps (rms)' % (fclk_val_mhz, integ_jitter_pj_ps)  
      semilogx(f,10*log10(S_out_pj),label=plot_label)

   axis([1e3, 100e6, -150, -90])
   xlabel('Offset Frequency (Hz)')
   ylabel('dBc/Hz')
   title('Weighted PLL Phase Noise (Without Buffer Noise) for Period Jitter Calc (Carrier Freq = %5.3f GHz)' % (fvco*1e-9))
   grid(True,which='both')
   legend(loc='upper right',prop={'size':15})

   fig_pj.show()

if plot_gain_and_phase_margin == 1:
   ## Calculate Open Loop Magnitude and Phase, Unity Gain Crossover, 
   ## and Phase Margin based on open loop transfer A
   ind_vals = np.arange( len(A) )[abs(A) <= 1]
   f_crossover = f[ind_vals[0]]
   angle_crossover = 180/pi*np.angle(A[ind_vals[0]])
   # print("crossover frequency = %5.1f kHz, phase margin = %5.1f degrees" % (f_crossover*1e-3, 180+angle_crossover))

   fig_mp = figure(3,figsize=(12,8))
   fig_mp.clf()
   rcParams['lines.linewidth'] = 2

   subplot(211)
   semilogx(f,20*log10(abs(A)),'m')
   title('Magnitude of Open Loop Response (Unity Gain Crossover = %5.1f kHz)' % (f_crossover*1e-3));
   ylabel('dB')
   grid(True,which='both')

   subplot(212)
   semilogx(f,180/pi*np.unwrap(np.angle(A)),'m')
   title('Phase of Open Loop Response (Phase Margin = %5.1f Degrees)' % (180+angle_crossover));
   ylabel('Degrees')
   xlabel('Frequency (Hz)')
   grid(True,which='both')
   fig_mp.show()


