Open denzchoe opened 2 years ago
Hello @denzchoe Thank you for posting this observation !
I am still quite certain about this factor 2 removal for this reason : in Python
implementation, IRFFT is used, that take only the positive frequency coefficients and assume another half which is complex conjugate. This return about twice the number of points as initial coefficient array. I felt I need to add this 2 coefficient because of that. However, Matlab
used a makeSymetric
function, which led to the same number of point. The irfft of S21 is used to determine the length of the line in sample by finding the peak (what an elegant way!) and impedance in step response at corresponding position.
Nevertheless, it is well possible that there could be something like an incorrect fft shift or other that cause length or impedance to be wrongly determined and that would have escaped testing with very uniform and well matched examples.
I would advise to do a sanity check with the latest AICC tools that use the Matlab code to see if the results are closer to old or current Python Implementation.
It would also be of interest to plot step response of 2xthru, fdf, dut, fixture on the same plot (so we can see if fixture looks like half 2xthru or not).
Keep brave !
Side note : on the scikit google group, there was a mention of this paper by Jason Ellison, Stefen B. Smith, Sedig Agili, quite recently. But I did not have the chance to access to it yet.
Hello @denzchoe I post here another observation that may be of interest. By using 2xthru deembedding to remove a 100 mm CPWG line from a 200mm CPWG line to remove connectors and launches effects, I have seen that 180° jumps appeared in the insertion loss phase of sides models.
I have located the portion of the code that cause it, but it seems coherent with the matlab source. https://github.com/scikit-rf/scikit-rf/blob/f9d0c6f1c5b8d271181ca40e3e892f73a4b599d4/skrf/calibration/deembedding.py#L1153-L1161 https://github.com/scikit-rf/scikit-rf/blob/f9d0c6f1c5b8d271181ca40e3e892f73a4b599d4/skrf/calibration/deembedding.py#L1164-L1172
The phase jump issue is related into choosing the proper solution to sqrt
. The sign of the solution should change at each e10
or e01
phase transition but I wonder if I have some artifact that trigger excess transitions.
Got it : small positive pikes due to calibration error and measurement noise are causing wrong transitions to be triggered
saw the latest fix @mhuser; Thanks for discovering and detailing it. Very insightful!
Ok, I am beginning to understand the limits of this deembedding method now. I was wondering why it works on some of my measured structure, and not in one of them. So it appears that there are some criteria to hit. And 1 of them is this.. (see fig below) Without a length long enough, the TDR will extract the incorrect Impedance value from the 2xthru midpoint. Looks like some background to it is required.
Image source, Page 34, 10.1109/IEEESTD.2021.9316329
Nice finding ! Sounds the pitfall with TDR/ifft based measurements : to see small details, upper frequency bound should be increased but soon hit the limit of the instrument.
I have not added the ability to use the Z-transform instead of fft. See this ieeep370 commit description. They speak of "short structures" with few points in TDR. Could this help with you deembedding challenge ?
Dr Hackl also had an application where the maximum available frequency was high enough to achieve one TDR point during construction of the errorbox reflection term. This led to integrating a Z-transform version of the code for very small structures. The specific application for this is on die de-embedding of interconnects. However, it can be useful at low frequencies with a relatively short 2x-thru (< 15mm ).
Oh, hmm my s2xthru structure total length is 25 mm in length. Not sure how useful it can be for me.. But certainly, the theory is applicable for very short die structures..
Back to the 2xthru midpoint, by altering the z11x
value here, I managed to get a more causal data of my side1
& side2
output.
dcs11 = self.DC(s11,f)
t11 = np.fft.fftshift(np.fft.irfft(concatenate(([dcs11], s11)), axis=0), axes=0)
step11 = self.makeStep(t11)
z11 = -self.z0 * (step11 + 1) / (step11 - 1)
z11x = z11[x] # edit here via debug mode on Spyder
temp = Network(frequency = self.s2xthru.frequency, s = s, z0 = self.z0)
temp.renormalize(z11x)
Let's see 1 example.
The following example is from a Measured s2xthru, fdf.
All deembedding done using method IEEEP370_SE_NZC_2xThru
Simulated dut is also shown as reference.
Also contain plots from AICC tools (vBeta & vR1)
z11x
extracted at midpoint was 53.3 ΩObservations:
i. constructed side1 & side2 has high S11 @low frequency
ii. AICC_vR1.s11 also has high S11. I am not sure why this is happening? deembed_dut.s11 is higher than sim_dut
iii. Midpoint of s2xthru in TDR
iv. side1 & side2 (terminating-side impedance is not at 50 Ω anymore in Time Domain)
z11x
forced to be 49 ΩObservations:
v. constructed side1 & side2 has low S11 now @low frequency
vi. deembed_dut.s11 is similar to sim_dut < 30 GHz. I say this is acceptable enough for me.
vii. side1 & side2 (terminating-side impedance is now closest to 50 Ω in Time Domain)
viii. Electrical Length of AICC tools doesn't look correct. Again I don't know why this is happening.
So from what I see; my constructed side1 and side2 can be more causal if I tweak the value at z11x
??
And if I leave it at default, the side1 and side2 S-parameters look funny in both Frequency and Time Domain.
I am not even sure how do I explain this phenomenon or why this is even happening. Just sharing what I am observing.
Additionally, I cross-verify my P370 deembed_dut with a Commercial De-embedding Software. The results generated from the Commercial Software is closer to sim_dut (i.e.: is like when I tweak z11x
)
Here is how the s2xthru looks like in CAD. Unlike the regular examples in P370, this is a coaxial based fixture with discontinuities at different joints.
Thank you for reporting this ! If you share your 2xthru measurement (maybe per email), I am interested to look at the impedance shape after inverse fft.
So from what I see; my constructed side1 and side2 can be more causal if I tweak the value at
z11x
This could be the case if the algorithm failed to compute dc points or midpoints. I propose to add a verbose
parameter to the method for advanced users that would open a plot to see how the x
length and z11x
parameters and if a wrong dc point is not inserting a slope in the curves.
Unlike the regular examples in P370, this is a coaxial-based fixture
This should give even better results: all the fields are in the same homogenous dielectric for a given section of the line, not like microstrip with some going in the air and some in the dielectric.
Example of verbose = True
diagnostic for 25mm PTFE dielectric coax:
import skrf
from skrf.media import Coaxial
from skrf.calibration import IEEEP370_SE_NZC_2xThru
f = skrf.Frequency(0.005, 50, 10000, unit='GHz')
coax = Coaxial(frequency=f, Dint=1e-3, Dout=2e-3, epsilon_r = 2.1, z0 = 50.)
line = coax.line(d = 25.0e-3, unit = 'm', embed = True, z0 = coax.Z0)
line.name = '2x-Thru'
dm = IEEEP370_SE_NZC_2xThru(dummy_2xthru = line, name = '2xthru', verbose = True)
On this artificial example, there is a very decent points number in the time domain and the impedance is nice flat.
Hello @denzchoe thank you a lot for the meas_s2xthru.s2p
!
I exercised the verbose = True
parameter that has been merged yesterday and here is the result. The image of the artifact is pasted below for reference.:
z11x
a lot.meas_s2xthru['0.01-30GHz']
I tried this with f_stop
values 45 or 30 in GHz:
# load data
s2xthru = rf.Network('meas_s2xthru.s2p')
# new frequency axis
f_start = 0.01 # GHz
f_stop = 45. # GHz
n = int(f_stop / f_start)
f_new = rf.Frequency(f_start, f_stop, n, 'GHz')
# interpolate
s2xthru_i = s2xthru.interpolate(f_new)
# diagnostic
dm = IEEEP370_SE_NZC_2xThru(dummy_2xthru = s2xthru_i, name = '2xthru', verbose = True)
f_stop = 30
f_stop = 45
if the frequency is reduced furthermore, the z11x value converge to 51 ohm.
Hello @denzchoe I had some time to think about your deembedding challenge.
In most cases, the delay due to the physical length of the 2xthru is probably not an integer multiple of time-domain sampling time. This may cause a ± 1 sample error in midpoint determination. For cases with a uniform impedance zone in the middle (such as a segment of transmission line), this should not cause an unacceptable error.
For this reason, the above issue is probably not due to interpolation to a harmonic sweep, the original measurement being very close to this with only one point more.
It would remain interesting for comparison to see if simulated 2x-Thru also exhibits such an impedance variation in the middle of the fixtures joint (without doing too much hypothesis for the reason of this discontinuity).
Maybe the ZC algorithm has more deembedding power for such a case : it offer bandwidth limiting, Nyquist rate enforcement (caring for the delay not being an integer number in my understanding) and offers a verbose mode showing how it cascades chunks of various impedance. In the other hand it looks like a more 'dangerous' deembedding being able to change slightly the data.
Thank you for taking your time to think about this.. Yes it is indeed a demanding challenge.
It would remain interesting for comparison to see if simulated 2x-Thru also exhibits such an impedance variation in the middle of the fixtures joint (without doing too much hypothesis for the reason of this discontinuity).
I can provide you with this. Will send to your email.
In most cases, the delay due to the physical length of the 2xthru is probably not an integer multiple of time-domain sampling time. This may cause a ± 1 sample error in midpoint determination. For cases with a uniform impedance zone in the middle (such as a segment of transmission line), this should not cause an unacceptable error.
This is definitely the most reasonable explanation. Coming from the perspective of physical dimensions; the time-constant of 50 GHz signal will be at 20 ps long. The wavelength of that frequency at Er = 1.0, is about 236 mils. The length of the small discontinuity in the midpoint is about 28 mils. A 50 GHz VNA does not have the resolution to capture the z11x
value accurately. But that doesn't mean it failed. (I think). Because I have tested it on a the same simulated structure; and every-time I picked the z11x
that output side1
& side2
that looks reasonable; the deembed dut output comes out closest to the simulated_dut.
- Legacy x//2 behaviour was picking impedance on the far left, an impedance of almost 50 ohm was always taken.
I guess the reason why Legacy code version appears to be accurate previously was because of the new observations we made recently.
- At least, we can see that the z11x choice is difficult in this case. I think the overall 2x-Thru is long enough, but the details of the interface at the middle are very small which make the x choice tricky. Maybe it could be interesting to have an option to manually force impedance.
I think it does. I think we should allow this option accessible. What do you think?
Maybe the ZC algorithm has more deembedding power for such a case : it offer bandwidth limiting, Nyquist rate enforcement (caring for the delay not being an integer number in my understanding) and offers a verbose mode showing how it cascades chunks of various impedance. In the other hand it looks like a more 'dangerous' deembedding being able to change slightly the data.
This is exactly what I am seeing. At lower frequencies; it seems accurate. However, because my actual fdf
structure has a resonant that begins at 32 GHz, I believe the makeTL
function cannot accurately recreate the txline. From time-domain, there is an impedance captured (seeing how makeTL
is dependable on getz
which extracts Impedance from Time-Domain).
But in frequency-domain, some kind of parasitic modes probably interfered the dominant TEM mode of the Coaxial at 32 GHz.
So when makeTL
function executes, it did not accurately recreate the actual txline.
Thus after deembedding, the deembed dut goes wonky after 30 GHz. It did changed my data as you have predicted. Perhaps ZC algorithm is not suitable for my use-case. But certainly for others..
- At least, we can see that the z11x choice is difficult in this case. I think the overall 2x-Thru is long enough, but the details of the interface at the middle are very small which make the x choice tricky. Maybe it could be interesting to have an option to manually force impedance.
I think it does. I think we should allow this option accessible. What do you think?
Yes I think it is worth to try! Done in https://github.com/scikit-rf/scikit-rf/pull/708
forced_z11x = None
(default behaviour):
forced_z11x = 48
:
Comparison:
Hello @denzchoe ,
Thank you a lot for your sim_2xthru.s2p
file !
Measurement:
Simulation:
The simulation is consistent with the measurement in exhibiting a non-uniform impedance around mid-point. I think it is a characteristic of this artifact.
forced_z11x = 49
looks like a magic value, providing a good match at low frequency.
I have tweaked an old optimization code that tries to build a model with a low S11 at the lower frequency range. However, I still wonder if there is a sense doing this, but I paste it here for reference. below code output : z11x_opt = 49.035 ohm, close to your results.
from matplotlib import pyplot as plt
import skrf as rf
from skrf.calibration import IEEEP370_SE_NZC_2xThru
from scipy.optimize import minimize
# load data
s2xthru = rf.Network('meas_s2xthru.s2p')
# build closest harmonic sweep and interpolate
f_start = 0.01 # GHz
f_stop = 50. # GHz
n = int(f_stop / f_start)
n_lim = n // 10
fnew = rf.Frequency(f_start, f_stop, n, 'GHz')
s2xthru_i = s2xthru.interpolate(fnew, basis = 't', coords = 'cart', kind = 'cubic')
def model(x, n_lim):
dm = IEEEP370_SE_NZC_2xThru(dummy_2xthru = s2xthru_i, name = '2xthru',
forced_z11x = x[0])
delta = dm.s_side1.s_mag[:n_lim, 0, 0]
return sum(delta**2)
x11x_0 = [50.]
opt = res = minimize(model, x11x_0, args = (n_lim), bounds=[(40., 60.)])
z11x_opt = opt.x[0]
print('z11x_opt = {:.3f} ohm'.format(z11x_opt))
dm = IEEEP370_SE_NZC_2xThru(dummy_2xthru = s2xthru_i, name = '2xthru',
forced_z11x = z11x_opt)
fix1 = dm.s_side1
plt.figure()
fix1.plot_s_db(0, 0)
Yes @mhuser ; that optimization code would be useful. I was trying to get one out myself (yet to start). But looks like yours came in most handy. I am not sure how useful it will be either, In the terms of causality and the sense of it. Bookmarking this for future usage. Let me test it on more use-cases. I have plenty of NISTMultilineTRL lines in the past. I can check how accurate NZC is against all of them when I find the time to do it later. Optimization would speed up the process.
Close the Issue?
I have plenty of NISTMultilineTRL lines in the past. I can check how accurate NZC is against all of them when I find the time to do it later.
Sounds great!
Close the Issue?
I think we now have understood why the results were better with the factor 2 on the length x: the algorithm was picking z11x in a zone of flat 50 ohm impedance on the left.
It followed we found useful features to add to the NZC 2xThru method such as verbose
, forced_z11x
and use_z_instead_fft
that help to investigate more on specific cases or get insights of internal process!
This help to make the code more robust, tested and better understood.
from matplotlib import pyplot as plt import skrf as rf from skrf.calibration import IEEEP370_SE_NZC_2xThru from scipy.optimize import minimize # load data s2xthru = rf.Network('meas_s2xthru.s2p') # build closest harmonic sweep and interpolate f_start = 0.01 # GHz f_stop = 50. # GHz n = int(f_stop / f_start) n_lim = n // 10 fnew = rf.Frequency(f_start, f_stop, n, 'GHz') s2xthru_i = s2xthru.interpolate(fnew, basis = 't', coords = 'cart', kind = 'cubic') def model(x, n_lim): dm = IEEEP370_SE_NZC_2xThru(dummy_2xthru = s2xthru_i, name = '2xthru', forced_z11x = x[0]) delta = dm.s_side1.s_mag[:n_lim, 0, 0] return sum(delta**2) x11x_0 = [50.] opt = res = minimize(model, x11x_0, args = (n_lim), bounds=[(40., 60.)]) z11x_opt = opt.x[0] print('z11x_opt = {:.3f} ohm'.format(z11x_opt)) dm = IEEEP370_SE_NZC_2xThru(dummy_2xthru = s2xthru_i, name = '2xthru', forced_z11x = z11x_opt) fix1 = dm.s_side1 plt.figure() fix1.plot_s_db(0, 0)
Hmm... something is not right with this Optimization script.
I am constantly getting the answer of whatever input is over here x11x_0 = [50.]
Hmm... something is not right with this Optimization script. I am constantly getting the answer of whatever input is over here
x11x_0 = [50.]
Could you try to replace forced_z11x
by forced_z0_line
twice ?
damnit, rookie mistake.. Forgot that the library changed the name of the parameter.. 😅.
Works now!
😉 Python store extra parameters in *args, so it triggered no error
Just an Observation Posting
Background
The DUT & fdf (fixture dut fixture) that I had simulated & measured has about >15 dB return loss @20 GHz.
The s2xthru (fixture to fixture) is < 20 dB @30 GHz.
The Observation
disclaimer, may not be conclusive
@mhuser, while I am running some test for NZC; , using various EM Simulated objects, it appears that for NZC, by including the factor 2, my deembedded results are much closer to the source DUT. (Both Time and Frequency Domain plots) After you have removed the factor 2 (in scikit-rf 0.22.1), my deembedded results deviates away from the source DUT.
I begin looking at it ever since my de-embedded measured results from your old fork code is different from the currently merged code
Seems like it appears that old fork code is more accurate? Perhaps it only affects my current DUT that is poorly matched whether it is simulated / measured scenario?
The changes I made is in reflection of your old fork code. See link below for comparison https://github.com/denzchoe/scikit-rf/compare/denzchoe-to-compare-only...denzchoe-vs-only1?diff=split
Will fill in more details much later. Let me dig a little further. Posting to report on Observation only