Analyze crosstalk pulses caused by edges on a neighbouring trace
Goal
Separate the effects of the edge, the coupling C, and the victim line resistance in a lumped capacitive coupling model. The prerequisites are Python functions, JSON, file handling, and RC time constants. The expected time is 85 minutes, so extend with +time before 60 minutes (maximum 180 minutes). Your work disappears when the session ends.
Why it matters
You must check that a waveform with only the peak made small, or a report with only the settings changed, matches the real evidence. You preserve the voltage polarity and the dwell above the threshold and back out the decay from the observed tail. It is an educational comparison of an ideal R and C model, not distributed-element NEXT/FEXT or real-board certification.
Steps
- Start with mkdir -p /root/capacitive-crosstalk and save all the functions in /root/capacitive-crosstalk/analyze.py. parameters(config) returns an object with tau_ns and ratio. config is a dict of exactly three keys, rise_ns (0.2/1/2), coupling_pf (0.2/1/2), and victim_ohm (50/200/1000). The values are non-bool int/float, and invalid input is a ValueError. Cv=2pF is fixed, tau_ns=Rv*(Cv+Cc)*0.001, and ratio=Cc/(Cv+Cc). Do not change the input or print on import.
- predict(config,t_ns) returns the victim line voltage V by the formula g(t−2)−g(t−12) from the reading. The time is a finite non-bool int/float from 0 to 60ns, and out of range is a ValueError. The rise starts at 2ns, the fall starts at 12ns, and both edge lengths are rise_ns. Include the case where the decay tail of the earlier rise remains during the fall.
- extrema(rows) returns an object with positive_v, positive_ns, negative_v, and negative_ns from a valid list of (ns, aggressor V, victim V) tuples with increasing time. positive is the victim line's maximum and negative is its minimum, and if tied, pick the first time. The input is non-empty and includes a 0V sample. Do not substitute absolute values or the aggressor column.
- dwell(rows,threshold_v=0.15) returns the total time in ns for which |victim V|>threshold_v from a valid (ns, aggressor V, victim V) table with increasing time. Linearly interpolate the original-signed voltage of each interval and take both ± threshold crossings into account. A flat interval equal to the threshold is excluded. threshold_v is a finite positive non-bool number, and otherwise a ValueError. Do not assume only uniform spacing.
- infer_tau(t1,v1,t2,v2) returns (t2−t1)/ln(v1/v2) in ns. All are finite non-bool int/float, and t1
- analyze(folder) reads config.json and the provided simulator.read_trace(folder/trace.tsv) and implements the return contract below. The rows read are in ns. Compare the victim line predict and the aggressor's linear edge prediction at each time with the two real voltages and find the maximum absolute error. consistent is error≤0.002V. The two observed tail points are the rise end +0.5ns and +1.5ns, and if infer_tau rejects them, None. Do not modify the original.
- campaign(values,folder) pre-validates the whole list of 1 to 8 non-duplicate configuration dicts and calls simulator.capture(cfg,folder/case-NN) and analyze in a new folder. NN is from 00 in order. If the observed config differs from the request, it is a ValueError. It returns results, all_pass as the logical AND of all passed, model=lumped_capacitive_coupling, and measured=False, and saves the same content in report.json. An existing folder is rejected even if empty, and on an input error it does not create the folder either. Preserve the failed conditions and the four original files.
Notes
- The provided helper /opt/lab/fixtures/capacitive_crosstalk/simulator.py is an image asset and is not modified. After running import sys; sys.path.insert(0, '/opt/lab/fixtures/capacitive_crosstalk'), use it as from simulator import capture, read_trace. capture(config,new_folder) preserves config.json, circuit.cir, trace.tsv, and ngspice.log. read_trace returns all 6001 samples at 0.01ns spacing from 0 to 60ns, and rejects truncated tables and non-finite values with a ValueError.
- analyze returns: config, peaks (the extrema object), dwell_ns, inferred_tau_ns, max_error_v, consistent, meets_spec, passed, and measured. meets_spec is the observed maximum absolute value ≤0.2V and dwell_ns≤1.0ns. passed is true only when both consistent and meets_spec are true, and measured=False. Even if the observed waveform's sign or the aggressor line differs, it is a mismatch.
- In the campaign, from the default (tr=0.2,Cc=1,Rv=200), try comparing tr=2, Cc=0.2, and Rv=50 one at a time. If you change all three at once, it is hard to separate the cause of the improvement. Use a new folder name on every run.
Find the time constant from the total capacitance
Start with mkdir -p /root/capacitive-crosstalk and save all the functions in /root/capacitive-crosstalk/analyze.py. parameters(config) returns an object with tau_ns and ratio. config is a dict of exactly three keys, rise_ns (0.2/1/2), coupling_pf (0.2/1/2), and victim_ohm (50/200/1000). The values are non-bool int/float, and invalid input is a ValueError. Cv=2pF is fixed, tau_ns=Rv*(Cv+Cc)*0.001, and ratio=Cc/(Cv+Cc). Do not change the input or print on import.
The coupling C also goes into the victim line's time constant. Check the factor that converts pF×Ω to ns.
Preserve the sign of the rising and falling pulses
predict(config,t_ns) returns the victim line voltage V by the formula g(t−2)−g(t−12) from the reading. The time is a finite non-bool int/float from 0 to 60ns, and out of range is a ValueError. The rise starts at 2ns, the fall starts at 12ns, and both edge lengths are rise_ns. Include the case where the decay tail of the earlier rise remains during the fall.
Make the response to one edge into a function, and then superpose by time shifting and sign inversion. Do not reset the state to 0 at the fall.
Read the positive and negative extrema separately
extrema(rows) returns an object with positive_v, positive_ns, negative_v, and negative_ns from a valid list of (ns, aggressor V, victim V) tuples with increasing time. positive is the victim line's maximum and negative is its minimum, and if tied, pick the first time. The input is non-empty and includes a 0V sample. Do not substitute absolute values or the aggressor column.
Do not turn the negative value of the fall into a positive one. If the time constant is large, the magnitudes of the two peaks can differ.
Exclude the time between the two thresholds
dwell(rows,threshold_v=0.15) returns the total time in ns for which |victim V|>threshold_v from a valid (ns, aggressor V, victim V) table with increasing time. Linearly interpolate the original-signed voltage of each interval and take both ± threshold crossings into account. A flat interval equal to the threshold is excluded. threshold_v is a finite positive non-bool number, and otherwise a ValueError. Do not assume only uniform spacing.
The middle of an interval that changes from negative to positive can be below the threshold. If you apply abs before interpolation, that part disappears.
Back out the time constant from the observed tail
infer_tau(t1,v1,t2,v2) returns (t2−t1)/ln(v1/v2) in ns. All are finite non-bool int/float, and t1
Use the natural logarithm and use the time difference. The value must be the same even if you change the voltage scale and the time origin.
Check the model against the real waveform
analyze(folder) reads config.json and the provided simulator.read_trace(folder/trace.tsv) and implements the return contract below. The rows read are in ns. Compare the victim line predict and the aggressor's linear edge prediction at each time with the two real voltages and find the maximum absolute error. consistent is error≤0.002V. The two observed tail points are the rise end +0.5ns and +1.5ns, and if infer_tau rejects them, None. Do not modify the original.
Get the observed peaks and dwell from the table and the expected waveform from the settings. Even a fake 0V waveform that meets the specification must fail the evidence check.
Leave an evidence report for each mitigation method
campaign(values,folder) pre-validates the whole list of 1 to 8 non-duplicate configuration dicts and calls simulator.capture(cfg,folder/case-NN) and analyze in a new folder. NN is from 00 in order. If the observed config differs from the request, it is a ValueError. It returns results, all_pass as the logical AND of all passed, model=lumped_capacitive_coupling, and measured=False, and saves the same content in report.json. An existing folder is rejected even if empty, and on an input error it does not create the folder either. Preserve the failed conditions and the four original files.
From the reference condition, change the edge, the coupling C, and the resistance one at a time and compare the causes. Do not delete failed results; preserve them.