ism-transceiver-915mhz/sim/low_noise2.ipynb

2.4 MiB

In [4]:
import numpy as np

import skrf
from skrf.media import DistributedCircuit
import skrf.frequency as freq
import skrf.network as net
import skrf.util

import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['figure.figsize'] = [10, 10]

f = freq.Frequency(0.4, 2, 1001)
tem = DistributedCircuit(f, z0=50)

bjt = net.Network('BFU520_Spar_NF_400MHz-2GHz/BFU520_05V0_005mA_NF_SP.s2p').interpolate(f)

bjt
Out[4]:
2-Port Network: 'BFU520_05V0_005mA_NF_SP',  0.4-2.0 GHz, 1001 pts, z0=[50.+0.j 50.+0.j]
In [5]:
bjt.plot_s_smith()
In [6]:
# let's add an inductor to the emitter
# to do this we need a function that lets you combine two port networks in series
# so we can model it
from scipy.interpolate import interp1d

def series(a, b):
  newz = a.z + b.z

  ret = net.Network.from_z(newz, frequency=a.frequency)
  noiseless = not a.noisy and not b.noisy
  if noiseless:
    return ret
  if a.noisy:
    an = a.n
    af = a.f_noise
  else:
    an = np.zeros(b.n.shape)
    af = b.f_noise
  if b.noisy:
    bn = b.n
    bf = b.f_noise
  else:
    bn = np.zeros(a.n.shape)
    bf = a.f_noise

  if bf != af:
    # TODO interpolate noise values for b into a frequency ranges
    # bn = ??? 
    raise NotImplementedError

  a_real = interp1d(a.frequency.f, a.a.real, 
          axis=0, kind=net.Network.noise_interp_kind)
  a_imag = interp1d(a.frequency.f, a.a.imag, 
          axis=0, kind=net.Network.noise_interp_kind)
  a_abcd = a_real(af.f) + 1.j * a_imag(af.f)

  b_real = interp1d(b.frequency.f, b.a.real, 
          axis=0, kind=net.Network.noise_interp_kind)
  b_imag = interp1d(b.frequency.f, b.a.imag, 
          axis=0, kind=net.Network.noise_interp_kind)
  b_abcd = b_real(bf.f) + 1.j * b_imag(bf.f)

  # calculate noise
  # based on https://onlinelibrary.wiley.com/doi/pdf/10.1002/9781119073093.app3
  a1 = 1
  b1 = (b_abcd[:,0,0] - a_abcd[:,0,0])/(a_abcd[:,1,0] + b_abcd[:,1,0])
  c1 = 0
  d1 = b_abcd[:,1,0]/(a_abcd[:,1,0] + b_abcd[:,1,0])

  a2 = 1
  b2 = (a_abcd[:,0,0] - b_abcd[:,0,0])/(a_abcd[:,1,0] + b_abcd[:,1,0])
  c2 = 0
  d2 = a_abcd[:,1,0]/(a_abcd[:,1,0] + b_abcd[:,1,0])

  ee = (a1*np.conj(a1)*an[:,0,0] + b1*np.conj(b1)*bn[:,1,1] +
          a2*np.conj(a2)*bn[:,1,1] + b2*np.conj(b2)*bn[:,1,1] +
      a1*np.conj(b1)*an[:,0,1] + b1*np.conj(a1)*an[:,1,0] +
          a2*np.conj(b2)*bn[:,0,1] + b2*np.conj(a2)*bn[:,1,0])
  ii = (c1*np.conj(c1)*an[:,0,0] + d1*np.conj(d1)*an[:,1,1] +
          c2*np.conj(c2)*bn[:,0,0] + d2*np.conj(d2)*bn[:,1,1] +
      c1*np.conj(d1)*an[:,0,1] + d1*np.conj(c1)*an[:,1,0] +
        c2*np.conj(d2)*bn[:,0,1] + d2*np.conj(c2)*bn[:,1,0])
  ei = (a1*np.conj(c1)*an[:,0,0] + b1*np.conj(d1)*an[:,1,1] + 
          a2*np.conj(c2)*bn[:,0,0] + b2*np.conj(d2)*bn[:,1,1] + 
      a1*np.conj(d1)*an[:,0,1] + b1*np.conj(c1)*an[:,1,0] + 
        a2*np.conj(d2)*bn[:,0,1] + b2*np.conj(c2)*bn[:,1,0])
  ie = np.conj(ei)

  ret.noise = np.moveaxis(np.array([[ee, ei], [ie, ii]]), 2, 0)
  ret.noise_freq = af
  return ret
In [7]:
bjt_degen = series(bjt, tem.shunt_inductor(1e-9))
bjt_degen.plot_s_smith()

idx_900mhz = skrf.util.find_nearest_index(bjt.f, 915.e+6)
bjt_degen.z_opt[idx_900mhz]
Out[7]:
(58.28534801283137+12.18507159830411j)
In [8]:
# calculate the stability circles for the source and load impedances

idx_900mhz = skrf.util.find_nearest_index(bjt_degen.f, 915.e+6)

sqabs = lambda x: np.square(np.absolute(x))

delta = bjt_degen.s11.s*bjt_degen.s22.s - bjt_degen.s12.s*bjt_degen.s21.s
rl = np.absolute((bjt_degen.s12.s * bjt_degen.s21.s)/(sqabs(bjt_degen.s22.s) - sqabs(delta)))
cl = np.conj(bjt_degen.s22.s - delta*np.conj(bjt_degen.s11.s))/(sqabs(bjt_degen.s22.s) - sqabs(delta))

rl_900mhz = rl[idx_900mhz][0, 0]
cl_900mhz = cl[idx_900mhz][0, 0]

rl_900mhz, cl_900mhz
Out[8]:
(3.048555602935056, (3.001540263823371+2.6459699316881777j))
In [9]:
def calc_circle(c, r):
    theta = np.linspace(0, 2*np.pi, 1000)
    return c + r*np.exp(1.0j*theta)

def plot_smith(pts):
    n = net.Network(s=pts)
    n.plot_s_smith()
    
cl_points = calc_circle(cl_900mhz, rl_900mhz)
plot_smith(cl_points)
In [10]:
rs = np.absolute((bjt_degen.s12.s * bjt_degen.s21.s)/(sqabs(bjt_degen.s11.s) - sqabs(delta)))
cs = np.conj(bjt_degen.s11.s - delta*np.conj(bjt_degen.s22.s))/(sqabs(bjt_degen.s11.s) - sqabs(delta))

rs_900mhz = rs[idx_900mhz][0, 0]
cs_900mhz = cs[idx_900mhz][0, 0]

rs_900mhz, cs_900mhz
Out[10]:
(3.267143687859595, (1.6731769925739743-1.6577383735222198j))
In [11]:
cs_points = calc_circle(cs_900mhz, rs_900mhz)
plot_smith(cs_points)
In [12]:
# let's plot all of them
# output stability first

for i, f in enumerate(bjt.f):
    # decimate it a little
    if i % 100 != 0:
        continue
    n = net.Network(name=str(f/1.e+9), s=calc_circle(cl[i][0, 0], rl[i][0, 0]))
    n.plot_s_smith()
In [13]:
# input stability
for i, f in enumerate(bjt.f):
    if i % 100 != 0:
        continue
    n = net.Network(name=str(f/1.e+9), s=calc_circle(cs[i][0, 0], rs[i][0, 0]))
    n.plot_s_smith()
In [14]:
# let's draw some constant noise circles
# first we grab the noise parameters for our target frequency from the network model
idx_915mhz = skrf.util.find_nearest_index(bjt_degen.f, 915.e+6)

# we need the normalized equivalent noise and optimum source coefficient to calculate the constant noise circles
rn = bjt_degen.rn[idx_915mhz]/50
gamma_opt = bjt_degen.g_opt[idx_915mhz]
fmin = bjt_degen.nfmin[idx_915mhz]

for nf_added in [0, 0.05, 0.1, 0.2, 0.3]:
    nf = 10**(nf_added/10) * fmin
    
    N = (nf - fmin)*abs(1+gamma_opt)**2/(4*rn)
    c_n = gamma_opt/(1+N)
    r_n = 1/(1-N)*np.sqrt(N**2 + N*(1-abs(gamma_opt)**2))
    
    n = net.Network(name=str(nf_added), s=calc_circle(c_n, r_n))
    n.plot_s_smith()

print("the optimum source reflection coefficient is ", gamma_opt)
the optimum source reflection coefficient is  (0.08806137825258742+0.10261810672607581j)
In [15]:
gamma_s = bjt_degen.g_opt[idx_900mhz]
gamma_s
Out[15]:
(0.08806137825258742+0.10261810672607581j)
In [16]:
# so I need to calculate the load reflection coefficient to get a conjugate match when the input sees 50 ohms
gamma_l = np.conj(bjt_degen.s22.s - bjt_degen.s21.s*gamma_s*bjt_degen.s12.s/(1-bjt_degen.s11.s*gamma_s))
is_gamma_l_stable = np.absolute(gamma_l[idx_900mhz, 0, 0] - cl_900mhz) > rl_900mhz

gamma_l = gamma_l[idx_900mhz, 0, 0]
gamma_l, is_gamma_l_stable
Out[16]:
((0.5393310600766553+0.2653012814090778j), True)
In [17]:
def calc_matching_network_vals(z1, z2):
    flipped = ((abs(np.imag(z2)) < 1e-6 and np.real(z1) < np.real(z2)) or
            (abs(np.imag(z2)) > 1e-6 and np.real(z1) < np.real(1/(1/z2-1/(1.j*np.imag(z2))))))
    if flipped:
        z2, z1 = z1, z2
        
    # cancel out the imaginary parts of both input and output impedances    
    z1_par = 1e+10
    if abs(np.imag(z1)) > 1e-6:
        # parallel something to cancel out the imaginary part of
        # z1's impedance
        z1_par = 1/(-1j*np.imag(1/z1))
        z1 = 1/(1./z1 + 1/z1_par)
    z2_ser = 0.0
    if abs(np.imag(z2)) > 1e-6:
        z2_ser = -1j*np.imag(z2)
        z2 = z2 + z2_ser
        
    Q = np.sqrt((np.real(z1) - np.real(z2))/np.real(z2))
    x1 = -1.j * np.real(z1)/Q
    x2 = 1.j * np.real(z2)*Q
    
    x1_tot = 1/(1/z1_par + 1/x1)
    x2_tot = z2_ser + x2
    if flipped:
        return x2_tot, x1_tot
    else:
        return x1_tot, x2_tot

z_l = net.s2z(np.array([[[gamma_l]]]))[0,0,0]
# note that we're matching against the conjugate;
# this is because we want to see z_l from the BJT side
# if we plugged in z the matching network would make
# the 50 ohms look like np.conj(z) to match against it, so
# we use np.conj(z_l) so that it'll look like z_l from the BJT's side
z_par, z_ser = calc_matching_network_vals(np.conj(z_l), 50)
z_l, z_par, z_ser
Out[17]:
((113.01057791499088+93.87851330128244j),
 -225.08350870216864j,
 83.96308091204551j)
In [18]:
# let's calculate what the component values are
c_par = np.real(1/(2j*np.pi*915e+6*z_par))
l_ser = np.real(z_ser/(2j*np.pi*915e+6))

c_par, l_ser
Out[18]:
(7.727790877202402e-13, 1.4604523895493778e-08)
In [19]:
# the capacitance is kind of low but the inductance seems reasonable
# let's test it out

output_network = tem.shunt_capacitor(c_par) ** tem.inductor(l_ser)

amplifier = tem.inductor(0.9e-9) ** bjt_degen ** output_network

amplifier.plot_s_smith()
In [20]:
amplifier.s11.plot_s_db()
amplifier.s22.plot_s_db()
10*np.log10(amplifier.nf(50.)[idx_900mhz])
Out[20]:
0.7680949467294812
In [61]:
amplifier.s21.plot_s_db()
20*np.log10(np.abs(amplifier.s21.s[idx_900mhz,0,0]))
Out[61]:
16.199434396586287
In [21]:
# that's pretty good but let's try to optimize the inductor value so we can get conjugate matching
# at the same time as minimum noise figure

nf_min = 10e+9
lval = 0.0
gamma_s_opt = None
gamma_l_opt = None
best_zs_opt = None
for l in np.linspace(0.1e-9, 3e-9, 30):
    bjt_degen = (series(bjt, tem.shunt_inductor(l)) ** tem.shunt(tem.resistor(100.) ** tem.short()))
    #bjt_degen.plot_s_smith()

    idx_900mhz = skrf.util.find_nearest_index(bjt.f, 915.e+6)
    zs_opt = bjt_degen.z_opt[idx_900mhz]
    
    bjt_s11 = bjt_degen.s11.s[idx_900mhz,0,0]
    bjt_s12 = bjt_degen.s12.s[idx_900mhz,0,0]
    bjt_s21 = bjt_degen.s21.s[idx_900mhz,0,0]
    bjt_s22 = bjt_degen.s22.s[idx_900mhz,0,0]

    delta2 = bjt_s11*bjt_s22 - bjt_s12*bjt_s21

    B1 = 1 + sqabs(bjt_s11) - sqabs(bjt_s22) - sqabs(delta2)
    B2 = 1 + sqabs(bjt_s22) - sqabs(bjt_s11) - sqabs(delta2)
    C1 = bjt_s11 - delta2*np.conj(bjt_s22)
    C2 = bjt_s22 - delta2*np.conj(bjt_s11)

    gamma_s = (B1 - np.sqrt(np.square(B1) - 4*sqabs(C1) + 0j))/(2*C1)
    gamma_l = (B2 - np.sqrt(np.square(B2) - 4*sqabs(C2) + 0j))/(2*C2)

    zs_loop = net.s2z(np.array([[[gamma_s]]]))[0,0,0]
    nf_loop = bjt_degen.nf(zs_loop)[idx_900mhz]
    print(l, nf_loop, zs_loop, gamma_s, gamma_l)
    if nf_loop < nf_min:
        nf_min = nf_loop
        lval = l
        gamma_s_opt = gamma_s
        gamma_l_opt = gamma_l
        best_zs_opt = zs_opt
        
(lval, best_zs_opt, gamma_s_opt, gamma_l_opt, nf_min)
1e-10 1.3900475578608422 (15.589484261362852+30.24672508183853j) (-0.25726347008697875+0.5797896257824454j) (0.01921344167750416+0.4804570561093426j)
1.9999999999999998e-10 1.3214820621483216 (19.871261447832566+30.3036449485149j) (-0.2046137678769628+0.5224492468780039j) (0.02921750739511757+0.4141828801383059j)
3e-10 1.2825322861433097 (23.792863931140765+30.395615742294392j) (-0.15857461662189548+0.4772221456057909j) (0.036697462829114895+0.36811062319935306j)
4e-10 1.2575313021979648 (27.509542151174738+30.522791662786855j) (-0.1169535977042613+0.4398496109451048j) (0.04254400529850106+0.333646196123854j)
4.999999999999999e-10 1.240427496680039 (31.095318662162555+30.685314349216696j) (-0.07867645044628213+0.40815581600871137j) (0.047248585560373195+0.3066581128931108j)
5.999999999999999e-10 1.2283136014013918 (34.59111948386383+30.88331269051158j) (-0.04311957436100516+0.3808317963533918j) (0.05111385747811701+0.2848405660379034j)
7e-10 1.2195925662630798 (38.021749147091626+31.116902645935046j) (-0.009876557261496659+0.3570052949550634j) (0.054339786677358566+0.26677972175110554j)
7.999999999999999e-10 1.2133030344352733 (41.40320867251555+31.386187076615894j) (0.021341126921070958+0.33605352504306124j) (0.05706523800337493+0.2515499395537356j)
8.999999999999999e-10 1.2088264433988598 (44.74628936991484+31.691255588314224j) (0.050752048388071326+0.3175096317890175j) (0.05939033697247747+0.23851498189463924j)
9.999999999999999e-10 1.2057440770328982 (48.05850885456118+32.03218438574579j) (0.07853015056622006+0.30101102359966475j) (0.06138944575196276+0.22722070659088744j)
1.1e-09 1.2037609435589798 (51.34522918891365+32.409036138771825j) (0.1048191064827071+0.28626853144374426j) (0.06311913788377657+0.21733299045520182j)
1.2e-09 1.2026623956414955 (54.61033884373506+32.82185986073742j) (0.1297409381052988+0.27304682584684425j) (0.06462332538559146+0.2085998198610935j)
1.3e-09 1.2022880294195262 (57.856687339212456+33.270690799230614j) (0.153401455451696+0.26115133981594946j) (0.06593667766001984+0.20082710622244201j)
1.4e-09 1.2025152647730442 (61.08637251821815+33.755550339505014j) (0.175893845865296+0.2504191660991334j) (0.06708697145191617+0.19386269100903716j)
1.5e-09 1.2032486244615173 (64.30093632583265+34.27644592079647j) (0.19730114203435614+0.24071249877877188j) (0.06809674797998028+0.18758544149677234j)
1.5999999999999999e-09 1.2044125098895724 (67.5015017824763+34.83337096573673j) (0.2176979850258967+0.23191377030468094j) (0.06898450749547858+0.1818976220713538j)
1.7e-09 1.2059461993871836 (70.68887102292462+35.426304823047346j) (0.2371519289111882+0.2239219579320831j) (0.06976558702732555+0.1767194362811867j)
1.8e-09 1.2078003028321604 (73.8635968875682+36.05521272367453j) (0.2557244377285478+0.21664972112095204j) (0.07045281628112376+0.17198504474020765j)
1.8999999999999996e-09 1.209934196224326 (77.02603613928298+36.72004575050037j) (0.273471669598502+0.21002114481573908j) (0.0710570151368684+0.16763960915634543j)
1.9999999999999997e-09 1.212314131217532 (80.17638965860164+37.42074082174482j) (0.2904451092138637+0.20396993446003892j) (0.07158737607449397+0.16363706406055034j)
2.0999999999999998e-09 1.214911819250135 (83.31473324570224+38.15722068814484j) (0.30669208923151825+0.19843795439528003j) (0.07205176170252688+0.1599384137774319j)
2.2e-09 1.217703355603832 (86.44104153810089+38.92939394397572j) (0.3222562280454682+0.19337403169944503j) (0.07245693877482173+0.15651041454648665j)
2.3e-09 1.220668391000555 (89.55520680984552+39.73715505195371j) (0.33717780301800293+0.18873296823126523j) (0.0728087640910194+0.1533245431253804j)
2.4e-09 1.2237894861743486 (92.65705391509236+40.58038438203112j) (0.3514940727243806+0.18447471807831856j) (0.07311233352352693+0.1503561812602264j)
2.5e-09 1.227051603542656 (95.74635229248518+41.4589482640757j) (0.36523955806700525+0.18056369787814414j) (0.0733721024883827+0.1475839647453j)
2.5999999999999997e-09 1.2304417028821937 (98.82282570422745+42.372699054393685j) (0.37844628958886767+0.17696820492936355j) (0.0735919840866653+0.14498925933958884j)
2.6999999999999998e-09 1.2339484168021004 (101.88616021142569+43.321475216035914j) (0.3911440265579024+0.1736599235038339j) (0.07377542962907986+0.14255573543516495j)
2.8e-09 1.2375617880818317 (104.93601076322804+44.305101412798535j) (0.4033604521470296+0.17061350388650845j) (0.0739254951465614+0.14026902030797267j)
2.9e-09 1.2412730554336642 (107.97200668682913+45.323388616805694j) (0.4151213481332155+0.16780620179614286j) (0.0740448966671102+0.13811641184022547j)
3e-09 1.2450744775075475 (110.99375629872284+46.37613422953463j) (0.42645075187475134+0.16521756824501274j) (0.07413605642292963+0.13608664133546192j)
Out[21]:
(1.3e-09,
 (58.26076012390622+10.540266898440597j),
 (0.153401455451696+0.26115133981594946j),
 (0.06593667766001984+0.20082710622244201j),
 1.2022880294195262)
In [63]:
# looks like the best result's at 1.5 nH; let's figure out the matching network and see how it performs

z_s_opt = net.s2z(np.array([[[gamma_s_opt]]]))[0,0,0]
z_l_opt = net.s2z(np.array([[[gamma_l_opt]]]))[0,0,0]

x_s_1, x_s_2 = calc_matching_network_vals(np.conj(z_s_opt), 50)
x_l_1, x_l_2 = calc_matching_network_vals(np.conj(z_l_opt), 50)

z_s_opt, z_l_opt, x_s_1, x_s_2, x_l_1, x_l_2
Out[63]:
((55.333286928027086+32.918487364054947j),
 (51.957243035595226+22.548375803065557j),
 -674.8180277996674j,
 35.29654402780441j,
 -1219.261937289769j,
 24.230945901247118j)
In [64]:
c_s_shunt = np.real(1/(2j*np.pi*915e+6*x_s_1))
#c_s_ser = np.real(1/(2j*np.pi*915e+6*x_s_2))
l_s_ser = np.real(x_s_2/(2j*np.pi*915e+6))
c_l_shunt = np.real(1/(2j*np.pi*915e+6*x_l_1))
l_l_ser = np.real(x_l_2/(2j*np.pi*915e+6))

c_s_shunt, l_s_ser, c_l_shunt, l_l_ser
Out[64]:
(2.5775812344979314e-13,
 6.139474815394305e-09,
 1.426599348310454e-13,
 4.214726574836917e-09)
In [65]:
input_match_front_end = tem.inductor(l_s_ser) ** tem.shunt_capacitor(c_s_shunt)
output_match_front_end = tem.shunt_capacitor(c_l_shunt) ** tem.inductor(l_l_ser)
bjt_degen  = (series(bjt, tem.shunt_inductor(lval)) ** tem.shunt(tem.resistor(100.) ** tem.short()))
front_end = (input_match_front_end ** bjt_degen ** output_match_front_end)
    
front_end.plot_s_smith()
In [66]:
front_end.s11.plot_s_db()
front_end.s22.plot_s_db()
10*np.log10(front_end.nf(50.)[idx_900mhz]), 20*np.log10(np.abs(front_end.s21.s[idx_900mhz,0,0]))
Out[66]:
(0.80095866802666904, 11.647015317831638)
In [67]:
# so this design trades off 4 dB of gain to reduce the NF by ~1 dB, compared with the design below
# let's check for stability, and then calculate the tradeoff to see if it's worthwhile

rs = np.absolute((front_end.s12.s * front_end.s21.s)/(sqabs(front_end.s11.s) - sqabs(delta)))
cs = np.conj(front_end.s11.s - delta*np.conj(front_end.s22.s))/(sqabs(front_end.s11.s) - sqabs(delta))

# input stability
for i, f in enumerate(bjt.f):
    if i % 100 != 0:
        continue
    n = net.Network(name=str(f/1.e+9), s=calc_circle(cs[i][0, 0], rs[i][0, 0]))
    n.plot_s_smith()
In [68]:
# output stability

for i, f in enumerate(bjt.f):
    # decimate it a little
    if i % 100 != 0:
        continue
    n = net.Network(name=str(f/1.e+9), s=calc_circle(cl[i][0, 0], rl[i][0, 0]))
    n.plot_s_smith()
In [69]:
# so it's stable at higher frequencies, but could oscillate if it sees too inductive a load at lower frequencies
# it probably can't be used because of the instability at low frequencies
# I think it makes sense to accept the higher NF of the design below, but I'll do the math later to verify
In [70]:
# let's stabilize the transistor by adding some parallel resistance
# and then simultaneous conjugate matching so that both the input and output are matched to 50 ohms
# without any emitter degeneration

# let's calculate the stability factor to see where it's unstable
sqabs = lambda x: np.square(np.absolute(x))
delta = bjt.s11.s*bjt.s22.s - bjt.s12.s*bjt.s21.s
K = ((1 - sqabs(bjt.s11.s) - sqabs(bjt.s22.s) + sqabs(delta))/(2*np.absolute(bjt.s12.s*bjt.s21.s)))[:,0,0]

plt.plot(bjt.f, K)
Out[70]:
[<matplotlib.lines.Line2D at 0x7f891d78c6d8>]
In [71]:
#so it's basically always unstable
#let's add a 250 ohm shunt to the output and see how much that improves it

bjt_comp = bjt ** tem.shunt(tem.resistor(75.) ** tem.short())
bjt_comp.plot_s_smith()
In [72]:
# let's calculate K again
delta2 = bjt_comp.s11.s*bjt_comp.s22.s - bjt_comp.s12.s*bjt_comp.s21.s
K2 = ((1 - sqabs(bjt_comp.s11.s) - sqabs(bjt_comp.s22.s) + sqabs(delta2))/(2*np.absolute(bjt_comp.s12.s*bjt_comp.s21.s)))[:,0,0]

plt.plot(bjt_comp.f, K2)
Out[72]:
[<matplotlib.lines.Line2D at 0x7f891c475208>]
In [73]:
# that's a pretty nice improvement! let's check the delta to be sure, and
# find the new source stability circles to see if we need to add some series resistance there

# K > 1 and |delta| < 1 for stability
plt.plot(bjt_comp.f, np.absolute(delta2[:,0,0]))
Out[73]:
[<matplotlib.lines.Line2D at 0x7f891d65e940>]
In [74]:
rs2 = np.absolute((bjt_comp.s12.s * bjt_comp.s21.s)/(sqabs(bjt_comp.s11.s) - sqabs(delta2)))
cs2 = np.conj(bjt_comp.s11.s - delta2*np.conj(bjt_comp.s22.s))/(sqabs(bjt_comp.s11.s) - sqabs(delta2))

# input stability
for i, f in enumerate(bjt_comp.f):
    if i % 100 != 0:
        continue
    n = net.Network(name=str(f/1.e+9), s=calc_circle(cs2[i][0, 0], rs2[i][0, 0]))
    n.plot_s_smith()
In [75]:
# that doesn't look too bad, so let's move forward and try to conjugate match
delta2 = bjt_comp.s11.s*bjt_comp.s22.s - bjt_comp.s12.s*bjt_comp.s21.s

B1 = 1 + sqabs(bjt_comp.s11.s) - sqabs(bjt_comp.s22.s) - sqabs(delta2)
B2 = 1 + sqabs(bjt_comp.s22.s) - sqabs(bjt_comp.s11.s) - sqabs(delta2)
C1 = bjt_comp.s11.s - delta2*np.conj(bjt_comp.s22.s)
C2 = bjt_comp.s22.s - delta2*np.conj(bjt_comp.s11.s)

gamma_s_all = (B1 - np.sqrt(np.square(B1) - 4*sqabs(C1) + 0j))/(2*C1)
gamma_l_all = (B2 - np.sqrt(np.square(B2) - 4*sqabs(C2) + 0j))/(2*C2)

gamma_s = gamma_s_all[idx_900mhz, 0, 0]
gamma_l = gamma_l_all[idx_900mhz, 0, 0]

z_s = net.s2z(np.array([[[gamma_s]]]))[0,0,0]
z_l = net.s2z(np.array([[[gamma_l]]]))[0,0,0]

# this section broke when I switched to a lower bias current so just ignore this
z_s, z_l
Out[75]:
((14.453769011425337+31.967494868137187j),
 (28.41148437701095+33.098424068469981j))
In [76]:
x_s_1, x_s_2 = calc_matching_network_vals(np.conj(z_s), 50)
x_l_1, x_l_2 = calc_matching_network_vals(np.conj(z_l), 50)

x_s_1, x_s_2, x_l_1, x_l_2
Out[76]:
(54.634144641220715j,
 (1.0165472266293926e-07-31.88333775860665j),
 57.8645657612208j,
 (3.290115549532768e-07-57.35952884685131j))
In [77]:
c_s_shunt = np.real(1/(2j*np.pi*915e+6*x_s_1))
c_s_ser = np.real(1/(2j*np.pi*915e+6*x_s_2))
l_s_ser = np.real(x_s_2/(2j*np.pi*915e+6))
c_l_shunt = np.real(1/(2j*np.pi*915e+6*x_l_1))
l_l_ser = np.real(x_l_2/(2j*np.pi*915e+6))

c_s_shunt2 = np.real(1/(2j*np.pi*915e+6*x_s_2))
l_s_ser2 = np.real(x_s_1/(2j*np.pi*915e+6))

c_l_shunt2 = np.real(1/(2j*np.pi*915e+6*x_l_2))
l_l_ser2 = np.real(x_l_1/(2j*np.pi*915e+6))

#c_s_shunt, c_s_ser, c_l_shunt, l_l_ser
c_s_shunt2, l_s_ser2, c_l_shunt2, l_l_ser2
Out[77]:
(5.4555087623716236e-12,
 9.503053749997663e-09,
 3.032448697062142e-12,
 1.0064952645644079e-08)
In [78]:
#input_network2 = tem.capacitor(c_s_ser) ** tem.shunt_capacitor(c_s_shunt)
#input_network2 = tem.inductor(l_s_ser) ** tem.shunt_capacitor(c_s_shunt)
input_network2 = tem.shunt_capacitor(c_s_shunt2) ** tem.inductor(l_s_ser2)

#output_network2 = tem.shunt_capacitor(c_l_shunt) ** tem.inductor(l_l_ser)
output_network2 = tem.inductor(l_l_ser2) ** tem.shunt_capacitor(c_l_shunt2)

amplifier2 = input_network2 ** bjt_comp ** output_network2
#amplifier2 = input_network2 ** bjt #** output_network2

amplifier2.plot_s_smith()
In [79]:
amplifier2.s21.plot_s_db()
20*np.log10(np.abs(amplifier2.s21.s[idx_900mhz, 0, 0]))
Out[79]:
16.20111883488946
In [80]:
amplifier2.s11.plot_s_db()
In [81]:
amplifier2.s22.plot_s_db()
In [82]:
10*np.log10(amplifier2.nf(50.)[idx_900mhz])
Out[82]:
1.5225393220600005
In [83]:
# check stability again
delta2 = amplifier2.s11.s*amplifier2.s22.s - amplifier2.s12.s*amplifier2.s21.s
K2 = ((1 - sqabs(amplifier2.s11.s) - sqabs(amplifier2.s22.s) + sqabs(delta2))/(2*np.absolute(amplifier2.s12.s*amplifier2.s21.s)))[:,0,0]

plt.plot(amplifier2.f, K2)
Out[83]:
[<matplotlib.lines.Line2D at 0x7f891c36d748>]
In [84]:
# let's do a comparison between chaining two of the non degenerated conjugate matched stages together
# versus the degenerated stage followed by a non degenerated one

K1 = 10**(11.46/20.)
F1 = 10**(0.8/10.)

K2 = 10**(16.2/20.)
F2 = 10**(1.52/10.)

# calculating the noise figures using the Friis formula, and neglecting the noise in the gain stages after the second one
10.*np.log10(F1 + (F2-1)/K1), 10.*np.log10(F2 + (F2-1)/K2)
Out[84]:
(1.186874955284092, 1.7142273529798011)
In [ ]: