Strategy to color crosstalk source

  • Thread starter Thread starter yefj
  • Start date Start date
Join the discussion
Registration is free. Ask a follow-up in this thread, or start your own.
28 replies · 3K views
yefj
Messages
151
Reaction score
2
Hello ,given a layout with multilayer traces.On certain trace there is a high distrotion due to cross talk.
Is there a strategy to see from where the cross talk is coming from?
To color different contributions of the spectrum noise and have indiction the source of it?
Is there such a thing?
Thanks.
 
Engineering news on Phys.org
yefj said:
Is there a strategy to see from where the cross talk is coming from?
Feed a PRBS or a wide-band noise into one line, while you terminate both ends of the other line, then look for the signal in the other line's terminations.

It seems that you do not have cross-talk between what are assumed to be uncorrelated signals, but coupling between phase locked signals. The cross-talk analysis is not expecting correlated channels, so it might be breaking that assumption.

Keep the signals far apart. Use tracks above and below the PCB, not one track above a shared ground plane.
 
Hello baluncore , I have intresting phenomena as following.
we have three differential lines one victim and two agressors.all of them are terminated at the same end of the line.The cross talk is measured by SDD21 so it crostal from one differential mode to another differential mode.

And I get a very intresting phenomena where the differential line which is far from the victim has the same crosstalk levelel as differntial line which is much closer.
How could it happen?
Thanks.
 
update:
I have a situation as shown below where there are 3 differential lines one is a victim and two agressors.
I have one agressor is far away then the other agressor .
what is the best way to see the crosstalk between the differential ines.
buttom line We have digital data which is getting distorted in the time domain.
Differential mode cross talk is a mode that calculates the crosstalk from differential line.
maybe its better to analise the cross talk by common mode Scc21?
What is the best method to do in VNA?

update :

there are plots shown below how to know what is the dominant factor for distorsion?
f3.webp
f2.webp
f1.webp
 
Last edited:
yefj said:
And I get a very intresting phenomena where the differential line which is far from the victim has the same crosstalk levelel as differntial line which is much closer.
How could it happen?
When two tracks are above a ground plane, the crosstalk is reflected backwards into the victim.
Analysis of backward wave couplers comes down to identifying if the odd or even mode of coupling is operating. Maybe you should look at both ends of the victim track, by treating your system as a four port network.

A travelling wave antenna is fed from the back, but terminated in the front, so energy that is not radiated forwards, is absorbed in the front termination resistor. Signals from behind are also absorbed. Maybe the same sort of thing is occurring with your experiment.

Since you will not answer questions, provide an accurate model of the test jig, and do not show where the ground plane is, or define what you mean by a differential line, it is impossible to answer your question.
 
Hello Baluncore thank you for your responce, I did show the system simulation shown below.
I get common mode and differential mode comapison between the far agressor effect ofdifferential line and the close differential agressor line.
few questions?
1. which mode is more dominant in ruining the digital data?
2. could you please give me a manual for the comulative IFFT of the crosstalk which is basickally how much energy traveled at each pulse of the cross talk IFFT time responce?
f3.webp
f1.webp

f1.webp
 
Baluncore said:
Analysis of backward wave couplers comes down to identifying if the odd or even mode of coupling is operating. Maybe you should look at both ends of the victim track, by treating your system as a four port network.
I think this is very relevant. All four ports will be involved. If the 'output port' is not terminated well then the reflected wave would appear at the fourth port. Using a range of terminations on the output should show this effect.
I don't know what your simulation involves or what assumptions are made but you can't take anything for granted if you don't specify all the parameters. Look into it a bit closer and you may get a clue.

Presumably the terms 'victim' and 'aggressor' refer to 'wanted signals' and 'interfering signals'(?). Am I correct when I understand that a noise signal is involved? Would it be easier to use a swept cw signal for analysing the performance? Is there a hardware version of the setup?
 
Hello, I have an update with a dilema regarding this cross talk issue.
I have a multilayer PCB with differential traces .
I measured SDD21 of agressor to victim and found a very high cross talk.

Then I though to measure the effective permitivty er threw the phase delay and i saw that the efective permitivy is much higher then what it needs to be.

Few question:
1.when we are designing multilayer PCB do we expect the effective permitivity to be such that we will get 100Ohm characteristic impedance?
2.suppose my core by manufacturer permitivity but I get another effective permitivity is it a sign of trouble?
3.given multilayer pcb what is the transmission line structure name for which we design the 100Ohm carateristic impedance line?
4. can difference in effective perimitivity cause cross talk?
Thanks.
 
yefj said:
.given multilayer pcb what is the transmission line structure name for which we design the 100Ohm carateristic impedance line?
4. can difference in effective perimitivity cause cross talk?
If you have doubts about the permittivity then you can't be sure of the Impedance so you can't be sure that the lines are terminated correctly. Mis-termination will cause imbalance and you won't know what's going on.
You have to sort things one at a time in order of priorities.
Permittivity is an intrinsic quantity. If you're getting unexpected results then I think you need to find out more about what would cause this suspected different effectivity? Maybe you need to go deeper into the basics as to where the change in performance can be explained in terms of a modification of the permittivity of the material.
 
yefj said:
1.when we are designing multilayer PCB do we expect the effective permitivity to be such that we will get 100Ohm characteristic impedance?
For transmission line applications and circuits where parasitic capacitance is critical, you must specify the permittivity and the tolerance. Often, we would specify the exact dielectric material that should be used in our PCBs that had strict requirements like that. It should be part of the Fab Drawing in the Notes section, and you should be sure to make it clear to the PCB manufacturer that it is a strict requirement. Obviously the dimensions of the diffferent layers need to be specified and have tight tolerances as well.

yefj said:
4. can difference in effective perimitivity cause cross talk?
Perhaps, but I'd be more concerned with the poor signal quality (ringing, etc.) from the impedance mismatch with your terminations on the TLs.
 
Reply
  • Like
Likes   Reactions: sophiecentaur
yefj said:
1.when we are designing multilayer PCB do we expect the effective permitivity to be such that we will get 100Ohm characteristic impedance?
The man was Ohm, the unit is the ohm.
The term permittivity is spelled with tt, not t.
If you could NOT design a line with Zo = 100 ohm, then there would be a problem.

yefj said:
2.suppose my core by manufacturer permitivity but I get another effective permitivity is it a sign of trouble?
It shows you are using the wrong equation or geometry when calculating permittivity of lines.
Do you use US inches, mil or thou. Or metric, SI millimetres. Please give a reference, or a link, to the equations and line geometry you are using.

yefj said:
3.given multilayer pcb what is the transmission line structure name for which we design the 100Ohm carateristic impedance line?
There are several possible geometries. You have never answered a question we have asked about what geometry or equations you are using. You think we can read your mind when you write "differential lines". Your brightly coloured pictures are useless because you give us no detailed cross-section of the tracks or ground plane, or specify the dielectric material.

yefj said:
4. can difference in effective perimitivity cause cross talk?
No. But if you use the wrong dielectric material or geometry equations, then Zo will be wrong, so you will have a mismatch between the instrument and the lines, which will give you different S parameters, that will confuse you.

A. What is the dielectric material used for your PCB?
B. What are the equations for the Zo geometry you use?
If you do not answer questions, we cannot help you.
 
Reply
  • Agree
Likes   Reactions: berkeman
Hello Baluncore I am using the following calculator taken from the article the impedance .
I am using high speed pcb substrate with permitivity of about 3.1 as the manufacturere and the link below said hope its helping to understand better the situation .
The question is about physics causing parasitic effects which we not taken into account when designing the differtial line.

Suppose we have two edge coupled differential lines which by individual work fine.
However when the manufacturer makes the substrate permitity between the lines higher then usual then we get crosstalk between the differential lines.

few questions:
1.When I calculated the phase delay of the agressor and extracted its individual effective permitivity.
maybe the manufacturer made a situation where the capacitance between the differential lines got much bigger.Why my individual phase delay of SDD22 (agressor) cannot be used as a sign for cross talk?
2.What could be done to reduce this cross talk?

https://www.signalintegrityjournal....-is-differential-impedance-and-why-do-we-care
https://www.signalintegrityjournal....ts-physics-chemistry-of-copper-clad-laminates
1788489763070.webp

1788488859287.webp
 
The pictures you show are from the publication. They do not show the section you are actually using. How do you connect to a differential pair, and how do you terminate the pair. How do you maintain phase tracking between the two lines.

Your differential pairs may be too close together. The 3W/5W Rule: To eliminate crosstalk, the spacing between two separate differential pairs should be at least 5 times the internal gap of a single pair.
 
Hello Baluncore, thank you very much for the responce.
I am talking purely on a very delicate issue in physics which I think is playing a role here.
I imagined that the cross talk got stronger because the dielectric constant between the differential lines got bigger or the surface area of the conductors got bigger .basicaly its the capacitor formula shown bellow.

I measured the Phase delay of SDD22 ( the return loss of the differential line agressor).
because its return loss and the delay is twice (round trip) I divided the time results by 2 then I tool the real life length of this line and calculated the effective dielectrics , by the formula

few questions:
1.does the designer planning the effective dielectic constant (or phase delay) when designing a differential line?

3.can it show that if the effective dielectic is not what the designer ment then the manufacturer also ruined the dielectrics between the differential line?
Is there some truth in my method that if the effective dilectric constant is not what the designer ment then the parasitic capacitance between the lines got ruined by the manufacturer also?
Thanks.
1788521771298.webp

1788521911997.webp

1788521566955.webp
 
Hello ,Is the a 3D structure I could simulate in EM solver which I could learn how to analyze its cross talk situation?Thanks.
 
Hello I found a method as following I am doing step responce from the agressor and see what voltage comes to the victim an after how much time.
the problem is that pcb is a complex thing i cannot just do derivative of the phase to find velocity.
Is there a general method to find phase velocity in order to find the location of the cross talk?
Thanks.
 
yefj said:
Is there a general method to find phase velocity in order to find the location of the cross talk?
I think it is group velocity that is important, not phase velocity. Group velocity will be sub-luminal, Vg=c/√Er. Phase velocity will be super-luminal, approaching and possibly equalling infinity. A simple TDR, along a track of known length, will give you Zo and vf, and therefore allow you to compute Er for that PCB material.

A PCB can be modelled as a directional coupler, or a massive scattering matrix. Cross talk does not come from one point, it comes from all elements of the aggressor reacting with all elements of the victim. That is the vector sum of all internal signals, via every possible delay path, including ground plane reflections from nearby.

The dielectric constant of the PCB insulation will be accurate since the material is well-defined and known. It is only when you try to use FRP above one GHz that you may begin to notice variations in Er, but you should be using PTFE at those frequencies. If you think the manufacturer has made it wrong, go back to check your work and find the mistake in your design.

There are ways to minimise cross talk by building fences of vias between the external ground planes.
 
Hello Baluncore,I have made a another good method to find crosstalk position between the lines.
on one hand we put the TDR of the victim (step responce IFFT into impedance)
on the other hand IFFT of SDD21 pure step responce which basickally says that we put one volt step threw the victim and the voltage at measure the voltage threw the agressor.
The experiment is as shown below by each port is two lines differential ones.
My expiriment is basickally doing NEXT measurement when port 4 and 2 are terminated.
The problem is that the event of the crosstalki witnessed very far at the end of the transmission line.
So the voltage was very weak and SNR was not so good.
I cannot measure crosstalk from ports 4 and 2 because there a chip soldered to it.
I put 1V from port 1 and I measure events from ports 3.
I know that these differential lines attenuate the signal when doing a round trip.

Is there a way to imagine what would be the voltage on port 4 if the exitation was from port 2 given the results I got from the situation of ports 1 and 3?
Thanks.

1789132393899.webp
 
Your picture shows a directional coupler. The device is reciprocal so you can flip it upside down or end-to-end to test other configurations.

Without a ground plane, the signal will be forward-coupled. Port 1 delivers some coupled energy to port 4, while most direct "through" energy goes to port 2.

With a ground plane, the signal will be backward-coupled. Port 1 delivers some coupled energy to port 3, while direct "through" energy goes to port 2.

The signals are coupled over the entire length of the structure. The relative phase of the coupled signal output, to the original, does not show a single point where reflected occurred.

The coupled signal is the vector sum of all the elements of the scattering matrix between the two lines, and the ground plane reflections, if present. There will also be inversions of the coupled phase, just to confuse your guess at a "single point of crosstalk".
 
Hello BalunCore ,Could you reccomend me a manual on SDD param loss?
simple differential transmission line that I want to understant how the SDD21 straight line getting attenuated?
Thanks.
 
Baluncore said:
With a ground plane, the signal will be backward-coupled.
I seem to remember that the coupling coefficient of a coupler is based on the ratio of balanced and unbalanced impedances (for strip line or micro strip ) and wouldn't you assume the presence of a ground plane or planes - in order to contain the signals and control crosstalk (to define the impedances, basically) ? The directivity of the coupler will require something like a quarter wave length.

Or is this a 'new fangled' system in which there are other considerations?
 
yefj said:
I imagined that the cross talk got stronger because the dielectric constant between the differential lines got bigger or the surface area of the conductors got bigger .
How would the dielectric constant change? It's a property of the dielectric. If the answers you are getting are not what you expected then doesn't it just indicate that the simple formula you are using for Capacity makes assumptions and works best for capacitors with a large area. In the case of your lines, the field patterns cannot be assumed to be uniform - as when the area is great and the separation is small. Have you looked into this or have you just taken that formula and assumed it applies to your problem? This link, for instance.
 
Hello , I started smaller to test the step responce issue. . I have built a basic through differential line as shown below.
The S21 is very good almost 0dB.
Step responce means we sent 1V step from port 1 and we expect most of it to reach at port 2 then maybe little peaces will arrive later.

To my surprise at the first time event port 2 gets 0.9V then 0.95V then 1V as if its accumulating the sent 1V on the other side (port 2)
I remmember microwave course with the echo diagram of transmission line .but I expected it to behave differently .maybe voltage is not the right metric maybe we I need to count the charge being sent from one port to the other?
What is the thing that is being accumulated on the other side of the port?
What caused the voltage to jump from 0.9V to 0.95V on the other side?


S2p file is attached in the link.
IFFT step responce script which produces the plots is shown below
Thanks.
https://tauex-my.sharepoint.com/:u:...CRqDVI-mSGUrUAdwI_yDS2sEkGPsEA7ptJyU?e=FLULVH

1789307580353.webp
1789307180584.webp
1789307140353.webp
1789307056344.webp

Code:
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import windows
from pathlib import Path


# ============================================================
# SETTINGS
# ============================================================

filename = Path(
    r"C:\Users\roger\Documents\cst_prj\diff_line_new_diff_1_1.s2p"
)

VIN_STEP = 1.0

# Expected S2P frequency grid
F_START = 0.01e9       # 0.01 GHz = 10 MHz
F_STOP  = 67e9         # 67 GHz
DF      = 10e6         # 10 MHz

# Plot range
T_MAX_PS = 3000

WINDOW_TYPES = [
    "rectangular",
    "hann",
    "hamming",
    "blackman",
    "kaiser"
]

KAISER_BETA = 6.0


# ============================================================
# READ S2P
# ============================================================

def read_s2p(filename):

    unit_scale = {
        "hz": 1.0,
        "khz": 1e3,
        "mhz": 1e6,
        "ghz": 1e9
    }

    freq_unit = "ghz"
    data_format = "ma"

    values = []

    with open(filename, "r") as file:

        for line in file:

            # Remove comments
            line = line.split("!")[0].strip()

            if not line:
                continue

            # Touchstone option line
            if line.startswith("#"):

                parts = line.lower().split()

                freq_unit = parts[1]
                data_format = parts[3]

                continue

            # Ignore Touchstone 2.0 keyword lines
            if line.startswith("["):
                continue

            values.extend(
                float(x) for x in line.split()
            )

    values = np.asarray(values)

    if len(values) % 9 != 0:
        raise ValueError(
            "S2P file does not contain groups of 9 numbers."
        )

    data = values.reshape(-1, 9)

    f = data[:, 0] * unit_scale[freq_unit]


    def to_complex(a, b):

        if data_format == "ma":

            return a * np.exp(
                1j * np.deg2rad(b)
            )

        elif data_format == "db":

            mag = 10 ** (a / 20)

            return mag * np.exp(
                1j * np.deg2rad(b)
            )

        elif data_format == "ri":

            return a + 1j*b

        else:

            raise ValueError(
                f"Unsupported format: {data_format}"
            )


    s11 = to_complex(
        data[:, 1],
        data[:, 2]
    )

    s21 = to_complex(
        data[:, 3],
        data[:, 4]
    )

    s12 = to_complex(
        data[:, 5],
        data[:, 6]
    )

    s22 = to_complex(
        data[:, 7],
        data[:, 8]
    )

    return f, s11, s21, s12, s22


# ============================================================
# LOAD DATA
# ============================================================

f, s11, s21, s12, s22 = read_s2p(filename)


print("\nFILE INFORMATION")
print("----------------")

print(
    f"Number of measured points = {len(f)}"
)

print(
    f"Start frequency = {f[0]/1e9:.6f} GHz"
)

print(
    f"Stop frequency  = {f[-1]/1e9:.6f} GHz"
)

print(
    f"Frequency step  = {(f[1]-f[0])/1e6:.6f} MHz"
)


# ============================================================
# CHECK EXPECTED GRID
# ============================================================

expected_f = np.arange(
    F_START,
    F_STOP + DF/2,
    DF
)

print(
    f"Expected number of points = {len(expected_f)}"
)


# If the CST frequencies differ slightly due to numerical
# formatting, interpolate exactly onto the desired grid.

if (
    len(f) != len(expected_f)
    or not np.allclose(f, expected_f, atol=1.0)
):

    print(
        "\nFrequency grid is not exactly "
        "10 MHz -> 67 GHz in 10 MHz steps."
    )

    print(
        "Interpolating onto exact grid..."
    )


    def interp_complex(xnew, x, y):

        real = np.interp(
            xnew,
            x,
            np.real(y)
        )

        imag = np.interp(
            xnew,
            x,
            np.imag(y)
        )

        return real + 1j*imag


    s11 = interp_complex(
        expected_f, f, s11
    )

    s21 = interp_complex(
        expected_f, f, s21
    )

    s12 = interp_complex(
        expected_f, f, s12
    )

    s22 = interp_complex(
        expected_f, f, s22
    )

    f = expected_f


else:

    print(
        "\nFrequency grid is already correct."
    )


# ============================================================
# ESTIMATE DC
#
# Linear complex extrapolation using first two points.
# Same idea as your MATLAB code.
# ============================================================

def estimate_dc(f, s):

    slope = (
        s[1] - s[0]
    ) / (
        f[1] - f[0]
    )

    dc = (
        s[0]
        - slope * f[0]
    )

    # Prevent magnitude > 1
    if abs(dc) > 1:

        dc = dc / abs(dc)

    return dc


s11_dc = estimate_dc(f, s11)
s21_dc = estimate_dc(f, s21)
s12_dc = estimate_dc(f, s12)
s22_dc = estimate_dc(f, s22)


print("\nESTIMATED DC")
print("------------")

print(
    f"S11(0) = {s11_dc}"
)

print(
    f"S21(0) = {s21_dc}"
)

print(
    f"S12(0) = {s12_dc}"
)

print(
    f"S22(0) = {s22_dc}"
)


# Add DC point

f = np.insert(
    f,
    0,
    0.0
)

s11 = np.insert(
    s11,
    0,
    s11_dc
)

s21 = np.insert(
    s21,
    0,
    s21_dc
)

s12 = np.insert(
    s12,
    0,
    s12_dc
)

s22 = np.insert(
    s22,
    0,
    s22_dc
)


# ============================================================
# CREATE FULL ± FREQUENCY SPECTRUM
#
# Positive side:
#
# 0, 10 MHz, ..., 67 GHz
#
# Negative side:
#
# -66.99 GHz, ..., -10 MHz
#
# 67 GHz acts as the Nyquist frequency.
# ============================================================

s21_full = np.concatenate(
    [
        s21,
        np.conj(
            s21[-2:0:-1]
        )
    ]
)


N = len(s21_full)


# ============================================================
# TIME AXIS
# ============================================================

dt = 1 / (N * DF)

t = np.arange(N) * dt


print("\nTIME DOMAIN INFORMATION")
print("-----------------------")

print(
    f"Full FFT points = {N}"
)

print(
    f"Time resolution = {dt*1e12:.4f} ps"
)

print(
    f"Total time window = {1/DF*1e9:.4f} ns"
)

print(
    f"Nyquist frequency = {1/(2*dt)/1e9:.4f} GHz"
)


# ============================================================
# WINDOW FUNCTION
# ============================================================

def make_window(name, N):

    name = name.lower()

    if name == "rectangular":

        w_centered = np.ones(N)

    elif name == "hann":

        w_centered = windows.hann(
            N,
            sym=False
        )

    elif name == "hamming":

        w_centered = windows.hamming(
            N,
            sym=False
        )

    elif name == "blackman":

        w_centered = windows.blackman(
            N,
            sym=False
        )

    elif name == "kaiser":

        w_centered = windows.kaiser(
            N,
            beta=KAISER_BETA,
            sym=False
        )

    else:

        raise ValueError(
            f"Unknown window: {name}"
        )


    # The normal window has its maximum in the center.
    #
    # FFT data ordering is:
    #
    # DC, +f ... +Fmax, -Fmax ... -df
    #
    # Therefore shift the window so its maximum is at DC.

    w = np.fft.ifftshift(
        w_centered
    )


    # Force DC gain = 1
    w = w / w[0]

    return w


# ============================================================
# CALCULATE S21 STEP RESPONSE
# ============================================================

responses = {}


for window_name in WINDOW_TYPES:

    w = make_window(
        window_name,
        N
    )


    # Apply frequency window
    S21_windowed = (
        s21_full * w
    )


    # Impulse response
    h21 = np.fft.ifft(
        S21_windowed
    )


    # Should be essentially real due to Hermitian spectrum
    h21 = np.real(
        h21
    )


    # Step response
    #
    # Integral of impulse response
    # in discrete FFT representation

    step = np.cumsum(
        h21
    )


    # 1 V incident voltage at port 1
    Vout = VIN_STEP * step


    responses[window_name] = Vout


# ============================================================
# STEP RESPONSE PLOT
# ============================================================

plt.figure(
    figsize=(12, 7)
)


for name, Vout in responses.items():

    if name == "rectangular":

        label = (
            "Rectangular / no extra window"
        )

    elif name == "kaiser":

        label = (
            f"Kaiser β={KAISER_BETA}"
        )

    else:

        label = name.capitalize()


    plt.plot(
        t * 1e12,
        Vout,
        linewidth=1.5,
        label=label
    )


plt.xlabel(
    "Time [ps]"
)

plt.ylabel(
    "Port 2 voltage [V]"
)

plt.title(
    "S21 Step Response\n"
    "1 V Incident Step at Port 1"
)

plt.xlim(
    0,
    T_MAX_PS
)

plt.grid(
    True,
    alpha=0.25
)

plt.minorticks_on()

plt.grid(
    which="minor",
    alpha=0.08
)

plt.legend()

plt.tight_layout()

plt.show()


# ============================================================
# PLOT WINDOWS
# ============================================================

freq_full = np.fft.fftfreq(
    N,
    d=dt
)

order = np.argsort(
    freq_full
)


plt.figure(
    figsize=(12, 7)
)


for name in WINDOW_TYPES:

    w = make_window(
        name,
        N
    )


    if name == "rectangular":

        label = (
            "Rectangular / no window"
        )

    elif name == "kaiser":

        label = (
            f"Kaiser β={KAISER_BETA}"
        )

    else:

        label = name.capitalize()


    plt.plot(
        freq_full[order] / 1e9,
        w[order],
        label=label
    )


plt.xlabel(
    "Frequency [GHz]"
)

plt.ylabel(
    "Window amplitude"
)

plt.title(
    "Frequency-Domain Windows"
)

plt.xlim(
    -67,
    67
)

plt.grid(
    True,
    alpha=0.25
)

plt.legend()

plt.tight_layout()

plt.show()
 
yefj said:
What is the thing that is being accumulated on the other side of the port?
Signal is remaining in the transmission line resonator.

The impedance of the transmission line, is not matched to the load on port 2.

First, you are seeing most of the step transmitted to port 2, about 89%.
Then, energy reflected from port 2, returns to the step generator at port 1, where it is reflected back to port 2, and added to the initial transmitted step.

You need to change the design of your transmission line to match the termination impedance. You can calculate the line impedance through the transmission coefficient from the step height. I believe your line impedance is now too low.
 
update :
Hello , I have simulated two differential lines as shown in the photo and got its step responce for every port as shown in the photo.
Chatgpt says below that localised jump in voltage at S31 (victim agressor) can suggest localised area where is happens .
Is it true?
Thanks.


diff_line_new_diff_two_4_lines_1
1789323133195.webp
1789321725213.webp

1789321751075.webp


Code:
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path

# ============================================================
# SETTINGS
# ============================================================
filename = Path(
    r"C:\Users\roger\Documents\cst_prj\diff_line_new_diff_two_4_lines_1.s4p"
)
VIN_STEP = 1.0
DF = 10e6                  # 10 MHz frequency spacing
T_MIN_PS = -500
T_MAX_PS = 3000
SETTLING_TOL = 0.02        # ±2%

# ============================================================
# READ S4P
#
# Touchstone ordering:
#
# S11 S21 S31 S41
# S12 S22 S32 S42
# S13 S23 S33 S43
# S14 S24 S34 S44
#
# ============================================================
def read_s4p(filename):
    values = []
    freq_unit = "ghz"
    data_format = "ma"
    unit_scale = {
        "hz": 1.0,
        "khz": 1e3,
        "mhz": 1e6,
        "ghz": 1e9
    }
    with open(filename, "r") as file:
        for line in file:
            # Remove comments
            line = line.split("!")[0].strip()
            if not line:
                continue
            # Touchstone option line
            if line.startswith("#"):
                parts = line.lower().split()
                freq_unit = parts[1]
                data_format = parts[3]
                continue
            # Ignore Touchstone 2.0 keyword lines
            if line.startswith("["):
                continue
            values.extend(
                float(x) for x in line.split()
            )

    values = np.asarray(values)
    # Each 4-port frequency point has:
    #
    # 1 frequency
    # +
    # 16 complex S-parameters × 2 numbers
    #
    # = 33 numbers
    if len(values) % 33 != 0:
        raise ValueError(
            "Could not interpret the file as a standard S4P file."
        )

    data = values.reshape(-1, 33)
    f = data[:, 0] * unit_scale[freq_unit]

    def convert(a, b):
        if data_format == "ma":
            return (
                a
                * np.exp(
                    1j * np.deg2rad(b)
                )
            )
        elif data_format == "db":
            mag = 10 ** (a / 20)
            return (
                mag
                * np.exp(
                    1j * np.deg2rad(b)
                )
            )
        elif data_format == "ri":
            return a + 1j*b
        else:
            raise ValueError(
                f"Unknown Touchstone format: {data_format}"
            )

    # First column of S-matrix
    #
    # S11 -> columns 1,2
    # S21 -> columns 3,4
    # S31 -> columns 5,6
    # S41 -> columns 7,8
    s11 = convert(
        data[:, 1],
        data[:, 2]
    )
    s21 = convert(
        data[:, 3],
        data[:, 4]
    )
    s31 = convert(
        data[:, 5],
        data[:, 6]
    )
    s41 = convert(
        data[:, 7],
        data[:, 8]
    )

    return f, s11, s21, s31, s41

# ============================================================
# LOAD FILE
# ============================================================
f, s11, s21, s31, s41 = read_s4p(filename)

print("\nFILE INFORMATION")
print("----------------")
print(f"Points          = {len(f)}")
print(f"Start frequency = {f[0]/1e9:.3f} GHz")
print(f"Stop frequency  = {f[-1]/1e9:.3f} GHz")
print(f"Frequency step  = {(f[1]-f[0])/1e6:.3f} MHz")

# ============================================================
# CHECK FREQUENCY SPACING
# ============================================================
measured_df = np.diff(f)
if not np.allclose(
    measured_df,
    DF,
    rtol=1e-6,
    atol=1.0
):
    raise ValueError(
        "Frequency points are not uniformly spaced by 10 MHz."
    )

# ============================================================
# ESTIMATE DC
#
# Linear complex extrapolation from first two measured points.
#
# For a real time-domain response, the DC bin must be real.
# ============================================================
def estimate_dc(f, s):
    slope = (
        s[1] - s[0]
    ) / (
        f[1] - f[0]
    )
    dc_complex = (
        s[0]
        - slope * f[0]
    )
    return complex(
        np.real(dc_complex),
        0.0
    )

s11_dc = estimate_dc(f, s11)
s21_dc = estimate_dc(f, s21)
s31_dc = estimate_dc(f, s31)
s41_dc = estimate_dc(f, s41)

print("\nESTIMATED DC")
print("------------")
print("S11(0) =", s11_dc)
print("S21(0) =", s21_dc)
print("S31(0) =", s31_dc)
print("S41(0) =", s41_dc)

# ============================================================
# ADD DC POINT
# ============================================================
f = np.insert(
    f,
    0,
    0.0
)
s11 = np.insert(
    s11,
    0,
    s11_dc
)
s21 = np.insert(
    s21,
    0,
    s21_dc
)
s31 = np.insert(
    s31,
    0,
    s31_dc
)
s41 = np.insert(
    s41,
    0,
    s41_dc
)

# ============================================================
# CREATE TWO-SIDED HERMITIAN SPECTRUM
#
# Positive:
#
# 0, 10 MHz, ... 67 GHz
#
# Negative:
#
# -67 GHz, ... -10 MHz
#
# This produces an odd-length FFT record.
# ============================================================
def make_full_spectrum(s):
    return np.concatenate(
        [
            s,
            np.conj(
                s[-1:0:-1]
            )
        ]
    )

S11_full = make_full_spectrum(s11)
S21_full = make_full_spectrum(s21)
S31_full = make_full_spectrum(s31)
S41_full = make_full_spectrum(s41)

N = len(S21_full)

# ============================================================
# TIME AXIS
# ============================================================
dt = 1.0 / (N * DF)

# After fftshift:
#
# -50 ns ........ 0 ........ +50 ns
#
t = (
    np.arange(N)
    - N // 2
) * dt
t_ps = t * 1e12

print("\nTIME DOMAIN")
print("-----------")
print(f"FFT points       = {N}")
print(f"Time spacing     = {dt*1e12:.4f} ps")
print(f"Full time period = {1/DF*1e9:.2f} ns")
print(
    f"Centered axis    = "
    f"{t[0]*1e9:.2f} ns "
    f"to {t[-1]*1e9:.2f} ns"
)

# ============================================================
# FFT FREQUENCY AXIS
# ============================================================
freq_full = np.fft.fftfreq(
    N,
    d=dt
)
FMAX = f[-1]

# ============================================================
# HAMMING FREQUENCY WINDOW
#
# w(0) = 1
#
# w(±FMAX) ≈ 0.08
#
# ============================================================
x = (
    np.abs(freq_full)
    / FMAX
)
w = (
    0.54
    +
    0.46
    * np.cos(
        np.pi * x
    )
)
w[x > 1.0] = 0.0

# ============================================================
# STEP RESPONSE
#
# IMPORTANT:
#
# Raw IFFT ordering:
#
# 0 -> positive time -> negative time
#
# fftshift changes this to:
#
# negative time -> 0 -> positive time
#
# THEN cumsum performs the step integral in chronological order.
# ============================================================
def step_response(S):
    # Frequency-domain window
    S_windowed = (
        S * w
    )

    # Raw IFFT
    impulse_raw = np.fft.ifft(
        S_windowed
    )

    # Put time in chronological order
    impulse_shifted = np.fft.fftshift(
        impulse_raw
    )

    # Hermitian spectrum should give real time response
    impulse_shifted = np.real(
        impulse_shifted
    )

    # Step response = integral of impulse response
    step = np.cumsum(
        impulse_shifted
    )

    return VIN_STEP * step

# ============================================================
# CALCULATE RESPONSES
# ============================================================
V11 = step_response(
    S11_full
)
V21 = step_response(
    S21_full
)
V31 = step_response(
    S31_full
)
V41 = step_response(
    S41_full
)

# ============================================================
# FINAL VALUE CHECK
#
# For a 1 V step:
#
# final voltage = Re[Sij(0)] × 1 V
#
# ============================================================
print("\nFINAL VALUE CHECK")
print("-----------------")

print(
    f"S21 final    = {V21[-1]:.8f} V"
)
print(
    f"S21 expected = {np.real(s21_dc)*VIN_STEP:.8f} V"
)

print()
print(
    f"S31 final    = {V31[-1]:.8f} V"
)
print(
    f"S31 expected = {np.real(s31_dc)*VIN_STEP:.8f} V"
)

print()
print(
    f"S41 final    = {V41[-1]:.8f} V"
)
print(
    f"S41 expected = {np.real(s41_dc)*VIN_STEP:.8f} V"
)

print()
print(
    f"S11 final    = {V11[-1]:.8f} V"
)
print(
    f"S11 expected = {np.real(s11_dc)*VIN_STEP:.8f} V"
)

# ============================================================
# 2% SETTLING TIME
#
# Settling time is still CALCULATED,
# but it will NOT be drawn automatically on the graphs.
# ============================================================
def settling_time(
    response,
    final_value,
    tolerance=0.02
):
    final_value = (
        np.real(final_value)
        * VIN_STEP
    )

    band = (
        tolerance
        * abs(final_value)
    )

    lower = (
        final_value
        - band
    )

    upper = (
        final_value
        + band
    )

    # Only examine t >= 0
    positive_indices = np.where(
        t >= 0
    )[0]

    response_positive = response[
        positive_indices
    ]

    inside = (
        (response_positive >= lower)
        &
        (response_positive <= upper)
    )

    # Work backward:
    #
    # True means:
    # every sample after this point
    # remains inside the band.
    stable = np.logical_and.accumulate(
        inside[::-1]
    )[::-1]

    valid = np.where(
        stable
    )[0]

    if len(valid) == 0:
        return (
            None,
            final_value,
            lower,
            upper
        )

    index = positive_indices[
        valid[0]
    ]

    return (
        t_ps[index],
        final_value,
        lower,
        upper
    )

# ============================================================
# CALCULATE SETTLING VALUES
# ============================================================
ts21, ss21, low21, high21 = settling_time(
    V21,
    s21_dc,
    SETTLING_TOL
)
ts31, ss31, low31, high31 = settling_time(
    V31,
    s31_dc,
    SETTLING_TOL
)
ts41, ss41, low41, high41 = settling_time(
    V41,
    s41_dc,
    SETTLING_TOL
)
ts11, ss11, low11, high11 = settling_time(
    V11,
    s11_dc,
    SETTLING_TOL
)

print("\n2% SETTLING TIMES")
print("-----------------")

print(
    f"S21 = {ts21} ps"
)
print(
    f"S31 = {ts31} ps"
)
print(
    f"S41 = {ts41} ps"
)
print(
    f"S11 = {ts11} ps"
)

# ============================================================
# DATA FOR PLOTS
# ============================================================
responses = [
    V21,
    V31,
    V41,
    V11
]

names = [
    "S21",
    "S31",
    "S41",
    "S11"
]

ylabels = [
    "Port 2 voltage [V]",
    "Port 3 voltage [V]",
    "Port 4 voltage [V]",
    "Reflected voltage [V]"
]

steady_values = [
    ss21,
    ss31,
    ss41,
    ss11
]

lower_values = [
    low21,
    low31,
    low41,
    low11
]

upper_values = [
    high21,
    high31,
    high41,
    high11
]

# ============================================================
# CREATE FOUR STACKED PLOTS
# ============================================================
fig, ax = plt.subplots(
    4,
    1,
    figsize=(13, 13),
    sharex=True
)

for i in range(4):

    # --------------------------------------------------------
    # Step response
    # --------------------------------------------------------
    ax[i].plot(
        t_ps,
        responses[i],
        linewidth=1.6
    )

    # --------------------------------------------------------
    # t = 0 reference
    # --------------------------------------------------------
    ax[i].axvline(
        0,
        linestyle=":",
        linewidth=1.0,
        alpha=0.5
    )

    # --------------------------------------------------------
    # Final DC value
    # --------------------------------------------------------
    ax[i].axhline(
        steady_values[i],
        linestyle="--",
        linewidth=1.2,
        label=(
            f"DC final = "
            f"{steady_values[i]:.6f} V"
        )
    )

    # --------------------------------------------------------
    # ±2% band
    # --------------------------------------------------------
    ax[i].axhspan(
        lower_values[i],
        upper_values[i],
        alpha=0.12,
        label="±2% band"
    )

    # --------------------------------------------------------
    # IMPORTANT:
    #
    # NO settling-time vertical line
    # NO settling-time annotation
    #
    # Nothing is marked automatically at startup.
    # --------------------------------------------------------

    ax[i].set_ylabel(
        ylabels[i]
    )

    ax[i].set_title(
        f"{names[i]} Step Response"
    )

    ax[i].grid(
        True,
        alpha=0.3
    )

    ax[i].minorticks_on()

    ax[i].legend(
        loc="best"
    )

# ============================================================
# X AXIS
# ============================================================
ax[-1].set_xlabel(
    "Time [ps]"
)

ax[-1].set_xlim(
    T_MIN_PS,
    T_MAX_PS
)

# ============================================================
# MAIN TITLE
# ============================================================
fig.suptitle(
    "1 V Step at Port 1 — Hamming Window\n"
    "Shifted IFFT Time Data",
    fontsize=14
)

# ============================================================
# PERMANENT SYNCHRONIZED CLICK MARKERS
#
# LEFT CLICK ON ANY GRAPH:
#
# same time point is marked on all four graphs.
#
# There are NO markers before the first click.
# ============================================================
marker_number = 0

def onclick(event):
    global marker_number

    # Left mouse only
    if event.button != 1:
        return

    # Click must be inside one of the plots
    if event.inaxes not in ax:
        return

    if event.xdata is None:
        return

    # --------------------------------------------------------
    # Find nearest actual IFFT time sample
    # --------------------------------------------------------
    index = np.argmin(
        np.abs(
            t_ps
            - event.xdata
        )
    )

    selected_time = (
        t_ps[index]
    )

    values = [
        V21[index],
        V31[index],
        V41[index],
        V11[index]
    ]

    marker_number += 1

    # --------------------------------------------------------
    # Put same-time marker on all four plots
    # --------------------------------------------------------
    for i in range(4):

        ax[i].plot(
            selected_time,
            values[i],
            marker="o",
            markersize=7
        )

        ax[i].axvline(
            selected_time,
            linestyle=":",
            linewidth=0.8,
            alpha=0.5
        )

        ax[i].annotate(
            (
                f"M{marker_number}\n"
                f"t = {selected_time:.2f} ps\n"
                f"V = {values[i]:.6f} V"
            ),
            xy=(
                selected_time,
                values[i]
            ),
            xytext=(
                12,
                15
            ),
            textcoords="offset points",
            bbox=dict(
                boxstyle="round",
                alpha=0.8
            ),
            arrowprops=dict(
                arrowstyle="->"
            )
        )

    # --------------------------------------------------------
    # Terminal output
    # --------------------------------------------------------
    print(
        f"\nMarker M{marker_number}"
    )

    print(
        f"Time = {selected_time:.3f} ps"
    )

    print(
        f"S21 = {values[0]:.8f} V"
    )

    print(
        f"S31 = {values[1]:.8f} V"
    )

    print(
        f"S41 = {values[2]:.8f} V"
    )

    print(
        f"S11 = {values[3]:.8f} V"
    )

    fig.canvas.draw_idle()

# ============================================================
# ACTIVATE MOUSE CALLBACK
# ============================================================
fig.canvas.mpl_connect(
    "button_press_event",
    onclick
)

# ============================================================
# DISPLAY
# ============================================================
plt.tight_layout()
plt.show()
 
yefj said:
Chatgpt says below that localised jump in voltage at S31 (victim agressor) can suggest localised area where is happens .
Is it true?
ChatGPT learns from surveying turds in a sewer.

If the track ran across a PCB past many other signals, then you might identify where the crosstalk was coupled from, but for your example of two parallel test tracks it is not possible as the coupling is over the full length of the test tracks.
 
Hello Baluncore, could you please draw in general the differential line structure you mean so I could try to simulate this principle ?
Thanks.

"If the track ran across a PCB past many other signals, then you might identify where the crosstalk was coupled from"
 
yefj said:
could you please draw in general the differential line structure you mean so I could try to simulate this principle ?
You need a fabricated commercial board with cross talk problems before you can start testing the concept. You are not ready for that problem.