ifft of sampling continuous-time transfer
function
1 2 3 4 5 6 7 8 9 10 11 12 13 14
deffreq2impulse(H, f): #Returns the impulse response, h, and (optionally) the step response, #hstep, for a system with complex frequency response stored in the array H #and corresponding frequency vector f. The time array is #returned in t. The frequency array must be linearly spaced.
Hd = np.concatenate((H,np.conj(np.flip(H[1:H.size-1])))) h = np.real(np.fft.ifft(Hd)) #hstep = sp.convolve(h,np.ones(h.size)) #hstep = hstep[0:h.size] t= np.linspace(0,1/f[1],h.size+1) t = t[0:-1]
return h,t
Maybe, the more straightforward method is sampling impulse response
of continuous-time transfer function directly
## freq2impulse(H, f), ifft - using sample of continuous-time tranfer function f = w/(2*np.pi) Hd = np.concatenate((H,np.conj(np.flip(H[1:H.size-1])))) hd = np.real(np.fft.ifft(Hd)) t= np.linspace(0,1/f[1],hd.size+1) t = t[0:-1]
## continuous-time transfer function - impulse t, hc = signal.impulse(([1], [1/wbw, 1]), T = t)
## hd(kTs) = Ts*hc(kTs) plt.figure(figsize = (14,10)) plt.plot(t, hc, t, hd/Ts, '--r', linewidth=3) plt.grid(); plt.legend(['impulse response by continuous-time transfer function','impulse response/Ts by ifft of sampling transfer function']) plt.ylabel('Mag'); plt.xlabel('time (s)'); plt.show()
#oversampled signal signal_ideal = np.repeat(signal_BR, samples_per_symbol)
#eye diagram of ideal signal sdp.simple_eye(signal_ideal, samples_per_symbol*3, 100, dt, "{}Gbps 2-PAM Signal".format(data_rate/1e9),linewidth=1.5, res=200)
#max frequency for constructing discrete transfer function max_f = 1/dt
#max_f in rad/s max_w = max_f*2*np.pi
#heuristic to get a reasonable impulse response length ir_length = int(4/(freq_bw*dt))
#calculate discrete transfer function of low-pass filter with pole at freq_bw w, H = sp.signal.freqs([freq_bw*(2*np.pi)], [1,freq_bw*(2*np.pi)], np.linspace(0,0.5*max_w,ir_length*4))
#find impluse response of low-pass filter h, t = sdp.freq2impulse(H,f)
defshift_signal(signal, samples_per_symbol): """Shifts signal vector so that 0th element is at centre of eye, heuristic Parameters ---------- signal: array signal vector at RX samples_per_symbol: int number of samples per UI Returns ------- array signal shifted so that 0th element is at centre of eye """ #Loss function evaluated at each step from 0 to steps_per_signal loss_vec = np.zeros(samples_per_symbol) for i inrange(samples_per_symbol): samples = signal[i::samples_per_symbol] #add loss for samples that are close to threshold voltage loss_vec[i] += np.sum(abs(samples)**2) #find shift index with least loss shift = np.where(loss_vec == np.max(loss_vec))[0][0] # plt.plot(np.linspace(0,samples_per_symbol-1,samples_per_symbol),loss_vec) # plt.show() return np.copy(signal[shift+1:])
lms_equalizer performs offline
adaptation and it fail due to DFE Error Propagation if
reference is None
deflms_equalizer(y, mu, N, w_ffe, FFE_pre, w_dfe, voltage_levels, alpha=None, reference=None, update_rate=1): """ voltage_levels: array discrete voltage levels to use in DFE optimization alpha: array (optional) used to fix the first `len(alpha)` weights in `w_dfe`, starting from the first pre-tap. these weights will not be optimized and will stay constant throughout LMS updates. reference: array (optional) if provided, LMS will attempt to optimize according to the error between the signal and reference, rather than estimating it based on the cloest voltage level. `len(reference)` may be less than `len(y)`, in which case only the first `len(reference)` updates will be optimized in this manner. """ min_delay = max(FFE_post, DFE_taps) for k inrange(min_delay, N - FFE_pre): y_k = y[k - FFE_post:k + FFE_pre + 1][::-1] z_k = z[k - DFE_taps:k][::-1]
# alpha takes over the first len(alpha) terms of w_dfe if is_cooptimizing and alpha isnotNone: w_dfe[:len(alpha)] = alpha
defFFE(self,tap_weights, n_taps_pre): """Behavioural model of FFE. Input signal is self.signal, this method modifies self.signal Parameters ---------- tap_weights: array DFE tap weights n_taps_pre: int number of precursor taps """
defprqs10(seed): """Genterates PRQS10 sequence Parameters ---------- seed : int seed used to generate sequence should be greater than 0 and less than 2^20 Returns ------- array: PRQS10 sequence """ a = prbs20(seed) shift = int((2**20-1)/3) b = np.hstack((a[shift:],a[:shift])) c = np.vstack((a,b)) # c[0,:] MSB, c[1,:] LSB
pqrs = np.zeros(a.size,dtype = np.uint8) for i inrange(a.size): pqrs[i] = grey_encode(c[:,i]) return pqrs
defprqs12(seed): a = prbs24(seed) shift = int((2**24-1)/3) b = np.hstack((a[shift:],a[:shift])) c = np.vstack((a,b))
pqrs = np.zeros(a.size,dtype = np.uint8) for i inrange(a.size): if (i%1e5 == 0): print("i =", i) pqrs[i] = grey_encode(c[:,i]) return pqrs
Gaussian distribution jitter
TX signal with jitter
The accuracy of the jitter model is constrained by the
resolution defined as
sample_time = UI/samples_per_symbol within the
gaussian_jitter implementation.
defgaussian_jitter(signal_ideal, UI,n_symbols,samples_per_symbol,stdev): """Generates the TX waveform from ideal, square, self.signal_ideal with jitter Parameters ---------- signal_ideal: array ideal,square transmitter voltage waveform UI: float length of one unit interval in seconds n_symbols: int number of symbols in signal_ideal samples_per_symbol: int number of samples in signal_ideal corresponding to one UI stdev: standard deviation of gaussian jitter in seconds stdev_div_UI : float standard deviation of jitter distribution as a pct of UI """
function gen_wvfm(bits; tui, osr) #helper function used to generate oversampled waveform Vbits = kron(bits, ones(osr)) #Kronecker product to create oversampled waveform dt = tui/osr tt = 0:dt:(length(Vbits)-1)*dt
return tt, Vbits end
first order RC response, normalized to the time
step\[
\frac{\alpha}{s+\alpha} \overset{\mathcal{L}^{-1}}{\longrightarrow}
\alpha\cdot e^{-\alpha t}
\]
The integral of impulse response of low pass RC filter \(\int_{0}^{+\infty} \alpha\cdot e^{-\alpha t}dt =
1\) — sum(ir*dt)
1 2 3 4 5 6 7 8 9 10 11
function gen_ir_rc(dt,bw,t_len) #helper function that directly calculates a first order RC response, normalized to the time step tt = [0:dt:t_len-dt;]
#checkout the intuitive symbols! ω = (2*π*bw) ir = ω*exp.(-tt*ω) ir .= ir/sum(ir*dt)
return ir end
sum(ir)*dt = 1, i.e. the step response
1
1 2
out = conv(ir, vbits)*tui/osr lines(tt, out[1:length(vbits)])
1 2 3 4 5 6 7 8 9 10 11 12
#call our convolution function; let's keep the input memory zero for now #change the drv parameters in the struct definition to see the waveform/eye change u_conv!(drv.Vo_conv, Vosr, drv.ir, Vi_mem=zeros(1), gain = drv.swing * param.dt);
#we will also create a non-mutating u_conv function for other uses function u_conv(input, ir; Vi_mem = zeros(1), gain = 1) vconv = gain .* conv(ir, input) vconv[eachindex(Vi_mem)] += Vi_mem
Because each symbol's width is determined by two
edges, \(\Delta tt\_\text{ext}\) is is
one greater than \(V_\text{ext}\)
1 2 3 4 5 6 7 8 9 10
function drv_jitter_tvec!(tt_Vext, Δtt_ext, osr) for n = 1:lastindex(Δtt_ext)-1 tt_Vext[(n-1)*osr+1:n*osr] .= LinRange((n-1)*osr+Δtt_ext[n], n*osr+Δtt_ext[n+1], osr+1)[1:end-1] end
itp = linear_interpolation(drv.tt_Vext, drv.Vext); #itp is a function object
itp = linear_interpolation(drv.tt_Vext, drv.Vext) create
linear interpolation only, extrapolation shall
not be used to avoid inducing any error
1 2 3 4 5
#note here tt_uniform is shifted by prev_nui/2 to give wiggle room for sampling "before" and "after" the current block. This is necessary for sinusoidal jitter tt_uniform = (0:param.blk_size_osr-1) .+ drv.prev_nui/2*param.osr;
#To interpolate, use the itp object like a function and broadcast to a vector Vfir = itp.(tt_uniform);
tt_uniform is shifted by prev_nui/2 to give
wiggle room for sampling "before" and "after" the current block.
This is necessary for sinusoidal jitter
DCD and RJ have much smaller shifts (typically <1 UI), but SJ can
oscillate over multiple UI widths, requiring this centered
offset to prevent interpolation boundary errors.
1 2 3 4 5 6 7 8 9 10 11 12
function drv_jitter_tvec!(tt_Vext, Δtt_ext, osr) for n = 1:lastindex(Δtt_ext)-1 # Starting from 0 tt_Vext[(n-1)*osr+1:n*osr] .= LinRange((n-1)*osr+Δtt_ext[n], n*osr+Δtt_ext[n+1], osr+1)[1:end-1] # drop the last end
returnnothing end
# Starting from 0 tt_uniform::Vector = (0:param.blk_size_osr-1) .+ prev_nui/2*param.osr
This creates tt_Vext starting from
(n-1)*osr, which means:
First segment: 0*osr + offset to
1*osr + offset = starts at 0
Second segment: 1*osr + offset to
2*osr + offset
etc.
tt_uniform must align with this same time
scale for interpolation to work correctly.
Starting from 0 ensures tt_uniform
aligns with the OSR sample grid and matches the time values in
tt_Vext for correct interpolation.
\[
\sigma_n^2 = 4\cdot k_B T \cdot R \cdot \frac{f_s}{2} = k_B T \cdot R
\cdot 2f_s= \frac{\color{red}{2}}{T_s} \cdot 10^{\frac{-174 - 30}{10}}
\cdot R
\]
noise_rms::Float64 = sqrt(2/param.dt*10^((noise_dbm_hz-30.0)/10)
rather than
sqrt(0.5/param.dt*10^((noise_dbm_hz-30.0)/10)
The margin of 10 (pi_max_code - 10)
ensures that even if the CDR makes a moderately large step (up to ±10
codes), it won't be mistaken for a wrap. This is safe as long as the CDR
loop bandwidth is low enough that per-update code changes stay well
below 10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
function clkgen_pi_itp_top!(clkgen; pi_code)
Δpi_code = pi_code-pi_code_prev if abs(Δpi_code) > pi_wrap_ui_Δcode pi_wrap_ui -= sign(Δpi_code)*pi_ui_cover end
for n = findall(eslc_nvec.!=0) if Sd_val[n:n+2] in filt_patterns vote = sign(Se[n][1].-0.5)*sign(Sd_val[n]-Sd_val[n+2]) ki_accum += ki*vote pd_accum += pd_gain*(kp*vote + ki_accum) end end
Wraps pd_accum into [0, pi_bnd) (i.e.,
[0, 256) for 8-bit PI), then floors it to produce the
integer pi_code that feeds back to
clkgen_pi_itp_top!. This is the modular
accumulator — the same wrapping that
pi_wrap_ui in the clock generator
unwraps.
# [[eslc_ref_code], [], []. []] eslc_ref_vec = [eslc_ref_code*ones(Int,n) for n in Neslc_per_phi] end
Sign-Sign Mueller-Muller CDR -
cdr_top!
pattern
main cursor
Se (assuming \(h_0=\operatorname{dLev}\))
vote
011
\(s_{011}=-h_1+h_0+h_{-1}\)
\(\operatorname{sign}({-h_1+h_{-1}})\)
\(\operatorname{sign}({h_1-h_{-1}})\)
110
\(s_{110}=h_1+h_0-h_{-1}\)
\(\operatorname{sign}({h_1-h_{-1}})\)
\(\operatorname{sign}({h_1-h_{-1}})\)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
function cdr_top!(cdr, Sd, Se) pi_bnd = 2^pi_res Sd_val = [Sd_prev; [sum(dvec) for dvec in Sd]]
for n = findall(eslc_nvec.!=0) if Sd_val[n:n+2] in filt_patterns vote = sign(Se[n][1].-0.5)*sign(Sd_val[n]-Sd_val[n+2]) ki_accum += ki*vote pd_accum += pd_gain*(kp*vote + ki_accum) end end
Hm_ds_interp=spline(fds_m,Hm_ds,f_ds_interp); % Interpolate for FFT point number figure(Name='spline function') plot(fds_m, Hm_ds, '-rs', LineWidth=2) hold on plot(f_ds_interp, Hm_ds_interp, '--bo', LineWidth=2) legend('org', 'interpolated'); grid on
impulse response from ifft of interpolated frequency
response
% Generate Random Data nt=1e3; %number of bits m=rand(1,nt+1); %random numbers between 1 and zero, will be quantized later m=-1*sign(m-0.5).^2+sign(m-0.5)+1;
% TX FIR Equalization Taps eq_taps=[1]; m_fir=filter(eq_taps,1,m);
for ( i=55:floor(size(data_channel,2) / bit_period)-500) eye_data(:,j) = 2*data_channel(floor((bit_period*(i-1)))+offset: floor((bit_period*(i+1)))+offset); j=j+1; end
time=0:2*bit_period; plot(time,eye_data);
If your 2D array represents multiple data series (e.g., each
column is a separate line seriessharing the same x-axis
values), the plot() function is the most
straightforward method.
% Take 10 pre-cursor, cursor, and 90 post-cursor samples sample_offset=opt_sample*bit_period; fori=1:101 sample_points(i)=max_data_ch_idx+sample_offset+(i-11)*bit_period; end sample_values=data_channel(sample_points); sample_points=(sample_points-max_data_ch_idx)./bit_period;
% Include DFE Equalization dfe_tap_num=2; dfe_taps(1:dfe_tap_num)=sample_values(12:12+dfe_tap_num-1); % h1, h2...
% Note this isn't a strtict DFE implementation - as I am not making a % decision on the incoming data. Rather, I am just using the known data % that I transmitted, delay matching this with the channel data, and using % it to subtract the ISI after weighting with the tap values. But, I think % it is good enough for these simulations. m_dfe=filter(dfe_taps,1,m); m_dfe_dr=reshape(repmat(m_dfe,bit_period,1),1,bit_period*size(m_dfe,2));
data_channel=data_channel'; dfe_fb_offset=floor(bit_period/2); % Point at which the DFE taps are subtracted - can be anything from 0 to UI-1*time_step data_channel_dfe=data_channel(channel_delay+dfe_fb_offset:channel_delay+dfe_fb_offset+size(m_dfe_dr,2)-1)-m_dfe_dr;
1 2 3 4 5 6 7
plot(m_dr, '--', LineWidth=2); hold on plot(data_channel(channel_delay:end), '--', LineWidth=2) hold on plot(data_channel(channel_delay+dfe_fb_offset:channel_delay+dfe_fb_offset+size(m_dfe_dr,2)-1), '--', LineWidth=2) plot(m_dfe_dr, LineWidth=2); plot(data_channel_dfe, LineWidth=2) xlim([1000, 3000]); ylim([-0.05, 0.3]); xlabel('samples'); grid on legend('lshift channel\_delay', 'lshift channel\_delay + 1/2UI', 'dfe filter', 'after dfe')
X. Chu, W. Guo, J. Wang, F. Wu, Y. Luo and Y. Li, "Fast and Accurate
Estimation of Statistical Eye Diagram for Nonlinear High-Speed Links,"
in IEEE Transactions on Very Large Scale Integration (VLSI) Systems,
vol. 29, no. 7, pp. 1370-1378, July 2021 [https://sci-hub.se/10.1109/TVLSI.2021.3082208]
HSPICE® User Guide: Signal Integrity Modeling and Analysis, Version
Q-2020.03, March 2020
IA Title: Common Electrical I/O (CEI) - Electrical and Jitter
Interoperability agreements for 6G+ bps, 11G+ bps, 25G+ bps I/O and 56G+
bps IA # OIF-CEI-04.0 December 29, 2017 [pdf]
J. Park and D. Kim, "Statistical Eye Diagrams for High-Speed
Interconnects of Packages: A Review," in IEEE Access, vol. 12,
pp. 22880-22891, 2024 [pdf]
function int2bits(num, nbit) return [Bool((num>>k)%2) for k in nbit-1:-1:0] end
generate PAM symbols
here Big Endian
1 2 3 4 5
#generate PAM symbols fill!(So, zero(Float64)) #reset So to all 0 for n = 1:bits_per_sym @. So = So + 2^(bits_per_sym-n)*So_bits[n:bits_per_sym:end] end
1 2 3 4 5 6
function int2bits(num, nbit) return [Bool((num>>k)%2) for k in nbit-1:-1:0] end
FIR filter typically is much shorter (<10 taps) than the symbol
vector, using FFT convolution might be an overkill. For
optimization, a simple shift-and-add filter
function can be written
function bist_prbs_gen(;poly, inv, Nsym, seed) seq = Vector{Bool}(undef,Nsym) for n = 1:Nsym seq[n] = inv for p in poly seq[n] ⊻= seed[p] end seed .= [seq[n]; seed[1:end-1]] end return seq, seed end
1 2 3 4 5 6 7 8 9 10 11 12
%% Matlab
function[seq, seed] = bist_prbs_gen(poly,inv, Nsym, seed) seq = zeros(1,Nsym); for n = 1:Nsym seq(n) = inv; for p = poly seq(n) = xor(seq(n), seed(p)); end seed = [seq(n), seed(1:end-1)]; end end
Lim, Byong Chan, M. Horowitz, "Error Control and Limit Cycle
Elimination in Event-Driven Piecewise Linear Analog Functional Models,"
in IEEE Transactions on Circuits and Systems I: Regular Papers, vol. 63,
no. 1, pp. 23-33, Jan. 2016 [https://sci-hub.se/10.1109/TCSI.2015.2512699]
—, J. -E. Jang, J. Mao, J. Kim and M. Horowitz, "Digital Analog
Design: Enabling Mixed-Signal System Validation," in IEEE Design
& Test, vol. 32, no. 1, pp. 44-52, Feb. 2015 [http://iot.stanford.edu/pubs/lim-mixed-design15.pdf]
— , Mao, James & Horowitz, Mark & Jang, Ji-Eun & Kim,
Jaeha. (2015). Digital Analog Design: Enabling Mixed-Signal System
Validation. Design & Test, IEEE. 32. 44-52. [https://iot.stanford.edu/pubs/lim-mixed-design15.pdf]
S. Liao and M. Horowitz, "A Verilog piecewise-linear analog behavior
model for mixed-signal validation," Proceedings of the IEEE 2013
Custom Integrated Circuits Conference, San Jose, CA, USA, 2013 [https://sci-hub.se/10.1109/CICC.2013.6658461]
—, M. Horowitz, "A Verilog Piecewise-Linear Analog Behavior Model for
Mixed-Signal Validation," in IEEE Transactions on Circuits and
Systems I: Regular Papers, vol. 61, no. 8, pp. 2229-2235, Aug. 2014
[https://sci-hub.se/10.1109/TCSI.2014.2332265]
Ji-Eun Jang et al. “True event-driven simulation of
analog/mixed-signal behaviors in SystemVerilog: A decision-feedback
equalizing (DFE) receiver example”. In: Proceedings of the IEEE 2012
Custom Integrated Circuits Conference. 2012 [https://sci-hub.se/10.1109/CICC.2012.6330558]
—, Si-Jung Yang, and Jaeha Kim. “Event-driven simulation of Volterra
series models in SystemVerilog”. In: Proceedings of the IEEE 2013 Custom
Integrated Circuits Conference. 2013 [https://sci-hub.se/10.1109/CICC.2013.6658460]
T. Wen and T. Kwasniewski, "Phase Noise Simulation and Modeling of
ADPLL by SystemVerilog," 2008 IEEE International Behavioral Modeling
and Simulation Workshop, San Jose, CA, USA, 2008 [slides,
paper]
Jaeha Kim,Scientific Analog. UCIe PHY Modeling and Simulation with
XMODEL [pdf]
S. Katare, "Novel Framework for Modelling High Speed Interface Using
Python for Architecture Evaluation," 2020 IEEE REGION 10 CONFERENCE
(TENCON), Osaka, Japan, 2020 [https://sci-hub.se/10.1109/TENCON50793.2020.9293846]