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
pi_wrap_ui accumulates full UI wraps across blocks.
Combined with Φstart = (cur_subblk-1)*subblk_size*osr (the
sub-block offset), this means Φo_subblk coordinates are in
the global sample coordinate system relative to the
start of the current block — which is exactly what
tt_Vext's centered coordinate system expects
1 2 3 4 5 6 7 8 9 10 11 12 13 14
function cdr_top!(cdr, Sd, Se)
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.
These Φ values are in the same coordinate system as
tt_Vext. Because tt_Vext is centered
(-8*osr ... 8*osr + blk_size_osr - 1), the nominal sampling
phases at 0, osr, 2*osr, ... fall right in the center of
the buffer
Different prev_nui Sizes
Drv
Splr
prev_nui
4
16
Headroom per side
2 UIs
8 UIs
Reason
TX jitter is bounded (DCD ≪ 1UI, RJ/SJ typically < 1UI)
CDR PI can accumulate large phase offsets over time
(pi_wrap_ui multiples of 4 UI)
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]
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]
StatOpt is a statistical eye
analysis and link optimization tool for
wireline communications, developed in both MATLAB and Python 3
The tool uses statistical methods to model various
wireline effects and to estimate the link performance metrics such as
the bit error rate and eye dimensions (eye's horizontal and vertical
openings)
Seasim (Statistical Eye Analysis Simulator)
is a statistical channel simulation tool provided by
the PCI-SIG to help designers evaluate the signal integrity and
compliance of PCI Express (PCIe) links
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]