TT Lab
Get started
Learn Learning paths Courses

My Cable Has an Echo

My Cable Has an Echo: Waveform Lab

Continue in TT Lab

Goal

Run a real transmission line model with ngspice and analyze arrival time, reflection, termination, tolerance, and numerical differences with Python. It is not a real-hardware measurement or PCB certification process.

Why it matters

A fast edge can create an instantaneous voltage higher than the supply. If the resistor location, the input units, or the sample times change, even a plausible graph becomes the answer to a different question. You build deliverables you can recheck by linking the circuit conditions, the waveforms, the analysis code, and the scope of verification. You need the basics of the shell, Python functions/lists/dictionaries, and voltage dividers. The expected time is 75 minutes, so extend with +time before the default 60 minutes ends (maximum 180 minutes). Files disappear when it ends, so keep them separately.

Steps

  1. You sent 3.3V and 5.5V arrived — Save volts=3.3, driver_ohm=10, series_ohm=0, z0_ohm=50, load_ohm=1000000000, delay_ns=2, rise_ns=0.2, step_ns=0.02, and stop_ns=14 to baseline.json. All files are under /root/signal-integrity. Run experiment.py --model /root/signal-integrity/baseline.json --out /root/signal-integrity/baseline to generate model.cir, trace.csv, and run.json, and check the input/TX/RX columns.
  2. Read the cable's clock — In arrival.py, implement first_crossing_ns(rows, threshold_v). rows is a list of [time_s, drive_v, tx_v, rx_v] in ascending time order. Return, in ns, the linearly interpolated time at which RX first reaches the threshold. If it is at or above the threshold from the first point, the first time; if it never does by the end, None. It is checked with the baseline waveform and a small separate sample together.
  3. Predict the voltage of the returning echo — Make predict(vs, source_ohm, z0_ohm, load_ohm) in reflections.py return an object containing launch_v, gamma_source, gamma_load, rx_first_v, and rx_second_v. source_ohm is the sum of the driver and the added series resistance. The inputs are positive finite numbers, and the load is a finite resistance, not infinity. The second received plateau is the first value plus the change from the next round trip.
  4. Reduce the echo with one resistor — From the baseline.json conditions, make series.json with only series_ohm changed to 40 and run it with --out /root/signal-integrity/series. Keep the baseline file. The total source resistance including driver_ohm=10 is 50Ω. In the generated waveform, check why the first transmitted voltage and the first received voltage differ from each other.
  5. The same 50Ω differs by location — From the baseline.json conditions, make parallel.json with only load_ohm changed to 50 and run it with --out /root/signal-integrity/parallel (series_ohm is 0). load_power(vs, source_ohm, load_ohm) in tradeoff.py returns the current_a and load_w of the DC state long after settling. It takes voltage V and resistance Ω as input, returns A and W, and checks the finite positive conditions.
  6. Compare nine cables outside the representative values — Run series.json with --out /root/signal-integrity/corners --corners. Keep the added 40Ω, and it generates 9 folders of driver 8/10/12Ω × Z0 45/50/55Ω and peaks.json. summarize(records) in summary.py returns count, min_peak_v, max_peak_v, and max_case from a non-empty [{case: name, peak_v: maximum V}, ...]. If the highest values tie, it is the name that comes first in dictionary order.
  7. Align by time instead of sample number — Run fine.json, which is series.json with only step_ns changed to 0.01, with --out /root/signal-integrity/fine. max_difference(coarse, fine) in convergence.py linearly interpolates fine at each time of coarse and returns the maximum absolute difference V of the TX/RX (col 2,3) voltages. The table is [time_s,drive_v,tx_v,rx_v], with two or more points, strictly increasing time, and the same start and end. It is also checked with a small table whose spacing differs from the real waveform.
  8. Bundle the evidence and leave the limits — Preserve the files of the earlier steps and write release.json. model_type=ideal_single_mode_lossless, measured=false, pcb_certified=false, and target_verification_required=true. In the sha256 object from the release.json format example, use as keys only the relative paths provided, and put in the lowercase 64-character SHA-256 value of the current file contents. The waveforms and analysis of the earlier steps are also verified again. Do not put backup files in the list.

Notes

First create the working folder with mkdir -p /root/signal-integrity. The command form is python3 /opt/lab/signal_integrity/experiment.py --model input_JSON --out new_output_folder . Distinguish the input and output formats in each step's file card. The output examples are abbreviated and cannot be submitted in place of the real solver output. The helper does not overwrite an existing output folder. To rerun, keep the previous folder under another name. The answer key does this automatically. The Python analysis functions take tables and numbers as arguments rather than reading files directly, return results, and do not include print logs. Numeric returns are checked with a relative 1e-6 or absolute 1e-8 tolerance, and JSON booleans are checked for the exact type. Each source and JSON file must be a regular file of at most 64KiB, and a waveform CSV a regular file of at most 2MiB, not a symbolic link. Function execution is limited to 4 seconds of CPU, 5 seconds of wall-clock time, and 256MiB of address space. The final grading rechecks the earlier steps' material, including the tolerance sub-waveforms. No extra package installation, internet, or real equipment is needed.

You sent 3.3V and 5.5V arrived

Save volts=3.3, driver_ohm=10, series_ohm=0, z0_ohm=50, load_ohm=1000000000, delay_ns=2, rise_ns=0.2, step_ns=0.02, and stop_ns=14 to baseline.json. All files are under /root/signal-integrity. Run experiment.py --model /root/signal-integrity/baseline.json --out /root/signal-integrity/baseline to generate model.cir, trace.csv, and run.json, and check the input/TX/RX columns.

The helper's full path is /opt/lab/signal_integrity/experiment.py, and you run it with python3. The input time is in ns and the CSV time is in s. The helper creates the output folder.

Read the cable's clock

In arrival.py, implement first_crossing_ns(rows, threshold_v). rows is a list of [time_s, drive_v, tx_v, rx_v] in ascending time order. Return, in ns, the linearly interpolated time at which RX first reaches the threshold. If it is at or above the threshold from the first point, the first time; if it never does by the end, None. It is checked with the baseline waveform and a small separate sample together.

In the first interval where a.rx < threshold <= b.rx, move the time by the fraction of the voltage. The numbers in rows are already floats, and this is not a function that opens a CSV.

Predict the voltage of the returning echo

Make predict(vs, source_ohm, z0_ohm, load_ohm) in reflections.py return an object containing launch_v, gamma_source, gamma_load, rx_first_v, and rx_second_v. source_ohm is the sum of the driver and the added series resistance. The inputs are positive finite numbers, and the load is a finite resistance, not infinity. The second received plateau is the first value plus the change from the next round trip.

Calculate the forward wave A and ΓL·ΓS separately, and check whether the second change is A×(1+ΓL)×ΓS×ΓL. Keep the sign even when the load is small or the source is large.

Reduce the echo with one resistor

From the baseline.json conditions, make series.json with only series_ohm changed to 40 and run it with --out /root/signal-integrity/series. Keep the baseline file. The total source resistance including driver_ohm=10 is 50Ω. In the generated waveform, check why the first transmitted voltage and the first received voltage differ from each other.

Putting 50 as the added resistance is the mistake of making the total source 60Ω. Check that the model in run.json and the input JSON you used are the same.

The same 50Ω differs by location

From the baseline.json conditions, make parallel.json with only load_ohm changed to 50 and run it with --out /root/signal-integrity/parallel (series_ohm is 0). load_power(vs, source_ohm, load_ohm) in tradeoff.py returns the current_a and load_w of the DC state long after settling. It takes voltage V and resistance Ω as input, returns A and W, and checks the finite positive conditions.

You need two things: generating the circuit file and implementing the function. Find the current from the total resistance, and calculate the load power from the square of that current and the load resistance.

Compare nine cables outside the representative values

Run series.json with --out /root/signal-integrity/corners --corners. Keep the added 40Ω, and it generates 9 folders of driver 8/10/12Ω × Z0 45/50/55Ω and peaks.json. summarize(records) in summary.py returns count, min_peak_v, max_peak_v, and max_case from a non-empty [{case: name, peak_v: maximum V}, ...]. If the highest values tie, it is the name that comes first in dictionary order.

Do not write the example list by hand; generate the real 9 waveforms. If you return only a single maximum, or break ties by list order, it fails in a separate condition.

Align by time instead of sample number

Run fine.json, which is series.json with only step_ns changed to 0.01, with --out /root/signal-integrity/fine. max_difference(coarse, fine) in convergence.py linearly interpolates fine at each time of coarse and returns the maximum absolute difference V of the TX/RX (col 2,3) voltages. The table is [time_s,drive_v,tx_v,rx_v], with two or more points, strictly increasing time, and the same start and end. It is also checked with a small table whose spacing differs from the real waveform.

If you line up row numbers with zip, the times differ. Find the two points in fine that bracket the current coarse time, and exclude the input voltage, col 1, from this function's comparison.

Bundle the evidence and leave the limits

Preserve the files of the earlier steps and write release.json. model_type=ideal_single_mode_lossless, measured=false, pcb_certified=false, and target_verification_required=true. In the sha256 object from the release.json format example, use as keys only the relative paths provided, and put in the lowercase 64-character SHA-256 value of the current file contents. The waveforms and analysis of the earlier steps are also verified again. Do not put backup files in the list.

If you modified the code or the circuit, first rerun and verify it, and make the hashes at the end. The files being the same and the real-hardware measurement being right are different claims.