TT Lab
Get started
Learn Learning paths Courses

My Cable Has an Echo

Measure differential-pair skew and common-mode deviation

Continue in TT Lab

Goal

Find the arrival difference of two complementary signals, and judge the common-mode deviation and the differential threshold arrival time separately. The prerequisites are the earlier signal integrity lab and Python functions, tuples, JSON, and file handling. The expected time is 85 minutes. Extend with +time before the default 60 minutes end (maximum 180 minutes). Files disappear when the session ends. Keep your source and report separately.

Why it matters

Even with the same final differential voltage, the common mode and the receive time can differ. You can explain the effect of a design measure only by separating relative skew from absolute propagation delay, and the benefit and cost of a slow edge. It is a model of two ideal matched lines with no coupling. It is not real-hardware measurement, radiation certification, or coupled-line analysis.

Steps

  1. Create the directory with mkdir -p /root/differential-skew and then save all the implementations in /root/differential-skew/analyze.py. predict(config,t_ns) returns a (Vp,Vn) tuple. config is a dict of exactly three keys, delay_p_ns=1/1.5, skew_ns=-0.4/-0.2/0/0.2/0.4, and rise_ns=0.4/0.8, and each value is a non-bool int/float. t_ns is a finite non-negative non-bool int/float. Anything else is a ValueError. P=clip((t_ns−2−delay_p_ns)/rise_ns,0,1) and N=1−clip((t_ns−2−delay_p_ns−skew_ns)/rise_ns,0,1), and the input is preserved.
  2. modes(rows) converts a valid list of (seconds, Vp, Vn) tuples into a list of (ns, Vdiff, Vcm) tuples. Vdiff=Vp−Vn and Vcm=(Vp+Vn)/2. Do not change the input, and preserve all the samples and their order.
  3. crossing(points,level,direction) returns the first crossing time, linearly interpolated, from a valid (ns, voltage) table with strictly increasing time. direction is only rising/falling, and any other value is a ValueError. rising finds adjacent voltages a,b with alevel>=b. If there is no crossing, None, and a flat sample that stayed at the threshold from the start is not a crossing.
  4. metrics(rows) returns, from a valid (seconds, Vp, Vn) table with increasing time, an object with skew_ns = N's 0.5V falling crossing − P's 0.5V rising crossing, common_peak_v = the maximum of |Vcm−0.5| over all samples, received_ns = the first +0.8V rising crossing of Vdiff or None, and final_diff_v = the last Vdiff. If either 50% crossing is missing, it is a ValueError. Calculate from the samples and do not fill in from the input settings instead.
  5. assess(observed) returns an object with common_ok=(common_peak_v<=0.15), timing_ok=(received_ns is not None and <=4), and meets_spec=(both conditions true) from the metrics result. Boundary values are included. Do not merge the two failure reasons into one and erase them.
  6. analyze(folder) analyzes config.json and the wave.tsv read with simulator.read_wave. Preserve the observed metrics and assess results, and find the maximum absolute error max_voltage_error from comparing both voltages of every raw sample with predict. If error<=0.003, it is consistent, and passed is when both consistent and meets_spec are true. Include all the returned keys in the notes, and measured=False. Even on a mismatch, leave the observed values and do not change the raw files.
  7. campaign(values,folder) pre-validates the whole list of 1 to 8 non-duplicate configurations and creates a new folder. Generate the originals with simulator.capture(config,folder/case-NN) and then analyze. NN is from 00, and if a result's config differs from the request, it is a ValueError. It returns an object with results, all_pass as the logical AND of all passed, model=independent_matched_lossless_lines, and measured=False, and saves the same report.json. Preserve the failed conditions and the four original files, and an existing folder is rejected even if empty. A configuration error is rejected before creating the folder.

Notes

The provided helper is /opt/lab/fixtures/differential_skew/simulator.py. No internet or extra installation is needed. capture(config,new_folder) saves config.json, circuit.cir, wave.tsv, and ngspice.log and returns a Path. read_wave(path) checks the header, finite numbers, increasing time, a maximum 2.1ps sample spacing, and the whole 0 to 6ns range, and gives a list of (seconds, Vp, Vn) tuples. A truncated waveform is a ValueError. ngspice uses an adaptive time grid with a maximum spacing of 2ps. The sample maximum and the continuous-time peak can differ. The source's PWL transition starts at 2ns, and the two lines, sources, and terminations are each 50Ω.

The returned keys of step 6: config, observed (the metrics result), common_ok, timing_ok, meets_spec, max_voltage_error, consistent, passed, and measured. The last value is False. The voltage error tolerance of 0.003V for all raw samples and the learning specification of 0.15V and 4ns are different criteria. The general function numeric comparison tolerance is 1e-8 relative and absolute. Compare with the values actually found from the samples. For functions stated to take a valid numeric table, you need not add a separate parser.

After the last step, actually run the following six conditions. Each condition is (P delay ns, skew ns, ramp ns). The normal report also includes failing conditions, so all_pass=False. When you run again, use a new folder.

cd /root/differential-skew
PYTHONPATH=/opt/lab/fixtures/differential_skew python3 - <<'PY'
from analyze import campaign
values = [(1,0,.4),(1,.2,.4),(1,.2,.8),(1,.4,.8),(1.5,0,.8),(1,-.2,.4)]
configs = [dict(delay_p_ns=p,skew_ns=s,rise_ns=r) for p,s,r in values]
print(campaign(configs, '/root/differential-skew/run-01'))
PY

The file is a regular file of at most 64KiB and does not print on import. Grading uses independent samples and real engine conditions and does not change the student source. The check process's limits of 12 seconds of CPU, 8MiB of files, and 35 seconds overall are execution safeguards different from the study time.

Predict the arrival of the complementary signals

Create the directory with mkdir -p /root/differential-skew and then save all the implementations in /root/differential-skew/analyze.py. predict(config,t_ns) returns a (Vp,Vn) tuple. config is a dict of exactly three keys, delay_p_ns=1/1.5, skew_ns=-0.4/-0.2/0/0.2/0.4, and rise_ns=0.4/0.8, and each value is a non-bool int/float. t_ns is a finite non-negative non-bool int/float. Anything else is a ValueError. P=clip((t_ns−2−delay_p_ns)/rise_ns,0,1) and N=1−clip((t_ns−2−delay_p_ns−skew_ns)/rise_ns,0,1), and the input is preserved.

The matched receive amplitude is half the source amplitude. Do not average the two delays; add the skew only to the N delay.

Separate differential and common mode

modes(rows) converts a valid list of (seconds, Vp, Vn) tuples into a list of (ns, Vdiff, Vcm) tuples. Vdiff=Vp−Vn and Vcm=(Vp+Vn)/2. Do not change the input, and preserve all the samples and their order.

The differential is a difference and the common mode is an average. Convert the seconds of the waveform file to ns in the returned object.

Interpolate a directional threshold crossing

crossing(points,level,direction) returns the first crossing time, linearly interpolated, from a valid (ns, voltage) table with strictly increasing time. direction is only rising/falling, and any other value is a ValueError. rising finds adjacent voltages a,b with alevel>=b. If there is no crossing, None, and a flat sample that stayed at the threshold from the start is not a crossing.

The first sample above or below the threshold is not itself the exact crossing time. Interpolate between the two times by the voltage ratio.

Find the observed skew and common-mode deviation

metrics(rows) returns, from a valid (seconds, Vp, Vn) table with increasing time, an object with skew_ns = N's 0.5V falling crossing − P's 0.5V rising crossing, common_peak_v = the maximum of |Vcm−0.5| over all samples, received_ns = the first +0.8V rising crossing of Vdiff or None, and final_diff_v = the last Vdiff. If either 50% crossing is missing, it is a ValueError. Calculate from the samples and do not fill in from the input settings instead.

A negative skew is not an error. For the common mode, maximize the absolute value of the deviation after subtracting the 0.5V reference.

Judge the common mode and the receive time separately

assess(observed) returns an object with common_ok=(common_peak_v<=0.15), timing_ok=(received_ns is not None and <=4), and meets_spec=(both conditions true) from the metrics result. Boundary values are included. Do not merge the two failure reasons into one and erase them.

Even with zero skew, if both are delayed together, the timing specification fails. Not reaching the threshold is None, not 0ns.

Compare the raw evidence of the two voltages with the model

analyze(folder) analyzes config.json and the wave.tsv read with simulator.read_wave. Preserve the observed metrics and assess results, and find the maximum absolute error max_voltage_error from comparing both voltages of every raw sample with predict. If error<=0.003, it is consistent, and passed is when both consistent and meets_spec are true. Include all the returned keys in the notes, and measured=False. Even on a mismatch, leave the observed values and do not change the raw files.

If you add the same offset to both signals, the differential is the same but the common mode differs. Do not cross-check only the differential.

Compare the trade-off between the edge and the path delay

campaign(values,folder) pre-validates the whole list of 1 to 8 non-duplicate configurations and creates a new folder. Generate the originals with simulator.capture(config,folder/case-NN) and then analyze. NN is from 00, and if a result's config differs from the request, it is a ValueError. It returns an object with results, all_pass as the logical AND of all passed, model=independent_matched_lossless_lines, and measured=False, and saves the same report.json. Preserve the failed conditions and the four original files, and an existing folder is rejected even if empty. A configuration error is rejected before creating the folder.

Slowing the edge reduces the common-mode deviation but also reduces the timing margin. Do not keep only the good results.