# Multiline TRL¶

This example demonstrates how to use `skrf`

’s NIST-style Multiline calibration (`NISTMultilineTRL`

). First a simple application is presented, followed by a full simulation to demonstrate the improvements in calibration accuracy vs the number of lines. All data is used in the demonstration is generated by skrf, and the code for this is given at the end of the
example.

## Simple Multiline TRL¶

### Setup¶

```
[1]:
```

```
%matplotlib inline
import skrf
from skrf.media import CPW, Coaxial
import numpy as np
import matplotlib.pyplot as plt
skrf.stylely()
```

### Load data into skrf¶

```
[2]:
```

```
#load all measurement data into a dictionary
data = skrf.read_all_networks('multiline_trl_data/')
# pull out measurements by name into an ordered list
measured_names = ['thru','reflect','linep3mm','line2p3mm','line10mm']
measured = [data[k] for k in measured_names]
# switch terms
gamma_f,gamma_r = data['gamma_f'],data['gamma_r']
# DUT
dut_meas= data['DUT']
```

### Simple Multiline TRL¶

```
[3]:
```

```
# define the line lengths in meters (including thru)
l = [0, 0.3e-3, 2.3e-3, 10e-3]
#Do the calibration
cal = skrf.NISTMultilineTRL(
measured = measured, #Measured standards
Grefls = [-1], #Reflection coefficient of the reflect, -1 for short
l = l, #Lengths of the lines
er_est = 7, #Estimate of transmission line effective permittivity
switch_terms = (gamma_f, gamma_r) #Switch terms
)
#Correct the DUT using the above calibration
corrected = cal.apply_cal(dut_meas)
corrected.plot_s_db()
```

## Compare calibration’s with different combinations of lines¶

Here we loop through different line combinations to demonstrate the difference in calibration accuracy.

```
[4]:
```

```
# Run NIST Multiline TRL calibration with different combinations of lines
#Put through and reflect to their own list ...
mtr = measured[:2]
#and lines on their own
mlines = measured[2:]
# define the line lengths in meters
line_len = [0.3e-3, 2.3e-3, 10e-3]
cals = []
duts = []
line_combinations = [ [0], [1], [0,1,2]]
for used_lines in line_combinations:
m = mtr + [mlines[i] for i in used_lines]
#Add thru length to list of line lengths
l = [0] + [line_len[i] for i in used_lines]
#Do the calibration
cal = skrf.NISTMultilineTRL(
measured = m, #Measured standards
Grefls = [-1], #Reflection coefficient of the reflect, -1 for short
l = l, #Lengths of the lines
er_est = 7, #Estimate of transmission line effective permittivity
switch_terms = (gamma_f, gamma_r) #Switch terms
)
#Correct the DUT using the above calibration
corrected = cal.apply_cal(dut_meas)
corrected.name = 'DUT, lines {}'.format(used_lines)
duts.append(corrected)
cals.append(cal)
```

## Results and discussion¶

### Transmission of corrected DUT¶

Plot the corrected DUT with different amount of lines

```
[5]:
```

```
plt.figure()
plt.title('DUT S21')
for dut in duts:
dut.plot_s_db(m=1, n=0)
```

### S11 of corrected DUT with different amount of lines¶

S11 shows bigger changes.

With one line low frequencies are very noisy

With only the medium length line calibration is very inaccurate at frequencies where phase of the line is multiple of 180 degrees

With three lines calibration accuracy is excellent everywhere

```
[6]:
```

```
plt.figure()
plt.title('DUT S11')
for dut in duts:
dut.plot_s_db(m=0, n=0)
```

### Normalized standard deviation of different calibrations¶

This measures the accuracy of the calibration. Lower number means less noise. * TRL calibration with one 90 degrees long line has normalized standard deviation of 1. * With multiple lines normalized standard deviations less than one is possible.

```
[7]:
```

```
f_ghz = dut.frequency.f_scaled
plt.figure()
plt.title('Calibration normalized standard deviation')
for e, cal in enumerate(cals):
plt.plot(f_ghz, cal.nstd, label='Lines: {}'.format(line_combinations[e]))
plt.ylim([0,30])
plt.legend(loc='upper right')
dut.frequency.labelXAxis()
```

### Calculate effective permittivity of the transmission lines used in the calibration¶

Is there no existing way to get the real er_eff? Anyway, this is how it can be calculated from the propagation constant. CPW line propagation constant can be approximated as average of substrate and air permittivities, but this is not completely true at low frequencies when the line is lossy

```
[8]:
```

```
#define calibration standard media
freq = dut.frequency
cpw = CPW(freq, z0=50, w=40e-6, s=25e-6, ep_r=12.9,
t=5e-6, rho=2e-8)
#Get the cal with all the lines
cal = cals[-1]
#Plot the solved effective permittivity of the transmission lines
c = 299792458.0
real_er_eff = -(cpw.gamma/(2*np.pi*f_ghz*1e9/c))**2
plt.figure()
plt.title('CPW effective permittivity')
plt.plot(f_ghz, cal.er_eff.real, label='Solved er_eff')
plt.plot(f_ghz, real_er_eff.real, label='Actual er_eff')
plt.legend(loc='upper right')
```

```
[8]:
```

```
<matplotlib.legend.Legend at 0x7f2d40797a00>
```

Depending on the noise there might be some wrong choices for some lines during the propagation constant determination that cause small spikes in the solved effective permittivity. In general they don’t matter much, but incorrectly determined propagation constant affects the weighting of the lines and accuracy of the calibration at that frequency. With many lines though even if one line has incorrectly determined propagation constant the effect to the total calibration will be small.

Plot the phase of the solved reflection coefficient

### Is reflect correct?¶

Since we know the ideals in this simulation we can re-define them here, and compare the determined reflect to the actual reflect. (see below for simulation details)

```
[9]:
```

```
lines = [cpw.line(l, 'm') for l in line_len]
short = cpw.delay_short(10e-6, 'm')
actuals = [
cpw.thru(),
skrf.two_port_reflect(short, short),
]
actuals.extend(lines)
```

```
[10]:
```

```
plt.figure()
plt.title('Solved and actual reflection coefficient of the reflect standard')
cal.apply_cal(measured[1]).plot_s_deg(n=0, m=0)
actuals[1].plot_s_deg(n=0, m=0)
plt.show(block=True)
```

## Simulation to Generate Data¶

Here is how we made the data used above.

### Create frequency and Media¶

```
[11]:
```

```
freq = skrf.F(1,100,401)
#CPW media used for DUT and the calibration standards
cpw = CPW(freq, z0=50, w=40e-6, s=25e-6, ep_r=12.9,
t=5e-6, rho=2e-8)
#1.0 mm coaxial media for calibration error boxes
coax1mm = Coaxial(freq, z0=50, Dint=0.44e-3, Dout=1.0e-3, sigma=1e8)
f_ghz = cpw.frequency.f*1e-9
```

### Make realistic looking error networks.¶

Propagation constant determination is iterative and doesn’t work as well when the error networks are randomly generated

```
[12]:
```

```
X = coax1mm.line(1, 'm', z0=58, name='X', embed=True)
Y = coax1mm.line(1.1, 'm', z0=40, name='Y', embed=True)
plt.figure()
plt.title('Error networks')
X.plot_s_db()
Y.plot_s_db()
#Realistic looking switch terms
gamma_f = coax1mm.delay_load(0.2, 21e-3, 'm', z0=60, embed=True)
gamma_r = coax1mm.delay_load(0.25, 16e-3, 'm', z0=56, embed=True)
plt.figure()
plt.title('Switch terms')
gamma_f.plot_s_db()
gamma_r.plot_s_db()
```

### Generate Fictitious measurements¶

```
[13]:
```

```
#Lengths of the lines used in the calibration, units are in meters
line_len = [0.3e-3, 2.3e-3, 10e-3]
lines = [cpw.line(l, 'm') for l in line_len]
#Attenuator with mismatched feed lines
dut_feed = cpw.line(1.5e-3, 'm', z0=55, embed=True)
dut = dut_feed**cpw.attenuator(-10)**dut_feed
#Through and non-ideal short
#Real reflection coefficient is solved during the calibration
short = cpw.delay_short(10e-6, 'm')
actuals = [
cpw.thru(),
skrf.two_port_reflect(short, short),
]
actuals.extend(lines)
#Measured
measured = [X**k**Y for k in actuals]
#Switch termination
measured = [skrf.terminate(m, gamma_f, gamma_r) for m in measured]
#Add little noise to the measurements
for m in measured:
m.add_noise_polar(0.0005, 0.005)
names = ['thru','reflect','linep3mm','line2p3mm','line10mm']
for k,name in enumerate(names):
measured[k].name=name
#Noiseless DUT so that all the noise will be from the calibration
dut_meas = skrf.terminate(X**dut**Y, gamma_f, gamma_r)
dut_meas.name = 'DUT'
#Put through and reflect to their own list ...
mtr = measured[:2]
#and lines on their own
mlines = measured[2:]
# write data to disk
write_data = False
if write_data:
[k.write_touchstone(dir='multiline_trl_data/') for k in measured]
gamma_f.write_touchstone('multiline_trl_data/gamma_f.s1p')
gamma_r.write_touchstone('multiline_trl_data/gamma_r.s1p')
dut_meas.write_touchstone(dir='multiline_trl_data/')
```

```
[ ]:
```

```
```