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

615 KiB

In [1]:
import numpy as np

import skrf
import skrf.media as media
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 = media.DistributedCircuit(f, z0=50)

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

bjt
Out[1]:
2-Port Network: 'BFU520_05V0_003mA_NF_SP',  0.4-2.0 GHz, 1001 pts, z0=[50.+0.j 50.+0.j]
In [2]:
bjt.plot_s_smith()
In [3]:
# calculate the stability circles for the source and load impedances

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

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

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

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

rl_900mhz, cl_900mhz
Out[3]:
(2.4087748915771234, (0.8163269481036434+2.8250725890510995j))
In [4]:
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 [5]:
rs = np.absolute((bjt.s12.s * bjt.s21.s)/(sqabs(bjt.s11.s) - sqabs(delta)))
cs = np.conj(bjt.s11.s - delta*np.conj(bjt.s22.s))/(sqabs(bjt.s11.s) - sqabs(delta))

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

rs_900mhz, cs_900mhz
Out[5]:
(3.2273035636098206, (-2.6077959539714635+2.662169442645758j))
In [6]:
cs_points = calc_circle(cs_900mhz, rs_900mhz)
plot_smith(cs_points)
In [7]:
# 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 [8]:
# 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 [9]:
# so not very useful, because the transistor isn't unconditionally stable
# time to draw the circles of constant gain and try to find a useful point

K = (1 - sqabs(bjt.s11.s) - sqabs(bjt.s22.s) - sqabs(delta))/(2*np.absolute((bjt.s12.s)*(bjt.s21.s)))
G_msg = np.absolute(bjt.s21.s)/np.absolute(bjt.s12.s)
10*np.log10(G_msg[idx_900mhz, 0, 0])
Out[9]:
18.162936250002936
In [10]:
# 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.f, 915.e+6)

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

for nf_added in [0, 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.22496922154681992+0.25679818766727663j)
In [11]:
# let's see if we can get a good NF with a point closer to a conjugate match to maximize gain
gamma_s = np.conj(bjt.s11.s[idx_900mhz, 0, 0])
gamma_s
Out[11]:
(-0.15947683947506197+0.5979940278898868j)
In [12]:
# that looks pretty close to unstable so let's just match to 50 ohms, which gives <0.1 dB additional NF
gamma_s = 0.0
In [22]:
# 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.s22.s - bjt.s21.s*gamma_s*bjt.s12.s/(1-bjt.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[22]:
((0.5801464368587828+0.25984561092937586j), True)
In [23]:
def calc_matching_network_vals(z1, z2):
    flipped = np.real(z1) < np.real(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[23]:
((122.21458153380792+106.58288288611662j),
 -227.59098445995284j,
 90.8749465076289j)
In [24]:
# 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[24]:
(7.642650209913709e-13, 1.5806772611913347e-08)
In [25]:
# the capacitance is kind of low but the inductance seems reasonable
# let's test it out

output_network = tem.inductor(l_ser)

amplifier = bjt ** output_network

amplifier.plot_s_smith()
In [26]:
amplifier.s11.plot_s_db()
In [110]:
# the circuit is unstable so let's move the input impedance to try to stabilize it
# let's test a bunch of points and see we can find a stable one
nf = 10**(0.1/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))

bjt_s22 = bjt.s22.s[idx_900mhz, 0, 0]
bjt_s21 = bjt.s21.s[idx_900mhz, 0, 0]
bjt_s11 = bjt.s11.s[idx_900mhz, 0, 0]
bjt_s12 = bjt.s12.s[idx_900mhz, 0, 0]

for i in range(0, 100):
    gamma_s = c_n + r_n*np.exp(-2j*np.pi*i/100.)
    #z_s = net.s2z(np.array([[[gamma_s]]]))[0,0,0]
    is_gamma_s_stable = np.absolute(gamma_s - cs_900mhz) > rs_900mhz
    if not is_gamma_s_stable:
        continue
    
    gamma_l = np.conj(bjt_s22 + bjt_s21*gamma_s*bjt_s12/(1-bjt_s11*gamma_s))
    is_gamma_l_stable = np.absolute(gamma_l - cl_900mhz) > rl_900mhz
    if not is_gamma_l_stable:
        continue

    print(gamma_s, gamma_l)
(0.4962586711014507+0.23917093695061845j) (0.18228051694780179+0.36506979517749194j)
(0.49569287122840444+0.2211668930637062j) (0.19129931295477226+0.3623052098214076j)
(0.4939977045628752+0.20323390291275034j) (0.20028409631888006+0.3600278893091544j)
(0.49117986115324375+0.18544273981707285j) (0.20921875018082758+0.3582180066051195j)
(0.4872504617400978+0.167863617369403j) (0.21808932998905928+0.35685677017352446j)
(0.48222501386774913+0.1505659123349047j) (0.22688378378536478+0.35592649617299665j)
(0.47612335068304124+0.1336178908527794j) (0.23559169497464763+0.35541065392126203j)
(0.4689695526629794+0.11708643902100246j) (0.24420404670148016+0.35529388986894j)
(0.4607918525800893+0.10103679892745204j) (0.25271300656744233+0.35556203449564405j)
(0.45162252408056236+0.0855323111691943j) (0.26111173017347444+0.35620209580375295j)
(0.44149775431491856+0.07063416487608437j) (0.2693941818269482+0.357202242434259j)
(0.43045750112385545+0.05640115622522554j) (0.2775549706866734+0.3585517788602209j)
(0.41854533534290633+0.042889456399319625j) (0.28558920060898085+0.360241114619539j)
(0.4058082688482577+0.03015238990467098j) (0.29349233198746516+0.3622617291218278j)
(0.39229656902235177+0.018240224123721865j) (0.3012600539349707+0.3646061331954502j)
(0.378063560371493+0.007199970932658811j) (0.3088881652292636+0.36726782822171494j)
(0.363165414078383-0.002924798832985076j) (0.31637246252647644+0.37024126342555364j)
(0.34766092632012524-0.012094127332512j) (0.3237086344339431+0.3735217916479341j)
(0.3316112862265749-0.020271827415402094j) (0.3308921601232806+0.3771056237076528j)
(0.315079834394798-0.027425625435463924j) (0.3379182112538142+0.3809897812623942j)
(0.2981318129126726-0.03352728862017182j) (0.34478155606515204+0.385172047895036j)
(0.2808341078781743-0.03855273649252047j) (0.351476464586443+0.389650917975669j)
(0.2632549854305045-0.04248213590566638j) (0.357996614000061+0.39442554267770663j)
(0.24546382233482703-0.04529997931529786j) (0.36433499329155095+0.399495672353332j)
(0.22753083218387116-0.04699514598082713j) (0.37048380641893586+0.40486159429534097j)
(0.2095267882969589-0.04756094585387341j) (0.3764343733471879+0.4105240647256943j)
(0.19152274441004663-0.04699514598082713j) (0.38217702842312085+0.4164842336528243j)
(0.1735897542590908-0.04529997931529786j) (0.38770101571864846+0.42274356102767774j)
(0.15579859116341327-0.042482135905666435j) (0.392994381154088+0.4293037224011962j)
(0.13821946871574342-0.03855273649252047j) (0.3980438614372531+0.43616650204314683j)
(0.12092176368124521-0.033527288620171875j) (0.4028347701294154+0.4433336712251593j)
(0.10397374219911985-0.027425625435463924j) (0.4073508814885588+0.4508068491027341j)
(0.3150798343947978+0.5057674993367008j) (0.05274107719448451+0.5342518902430446j)
(0.3316112862265746+0.49861370131663907j) (0.05416855518777264+0.5196006698236785j)
(0.34766092632012535+0.49043600123374886j) (0.05665183373606597+0.5054463468155599j)
(0.36316541407838304+0.48126667273422197j) (0.060098597098023565+0.49185614160339275j)
(0.3780635603714929+0.47114190296857816j) (0.06441595146252682+0.47888268818870067j)
(0.39229656902235166+0.4601016497775151j) (0.06951237636248636+0.4665652909404166j)
(0.4058082688482575+0.4481894839965661j) (0.07529927169619566+0.45493133676395675j)
(0.4185453353429062+0.4354524175019173j) (0.08169212687221261+0.44399777410936553j)
(0.4304575011238555+0.4219407176760114j) (0.08861135106767731+0.43377258854509937j)
(0.4414977543149185+0.40770770902515263j) (0.09598281023821892+0.42425622230939924j)
(0.45162252408056225+0.3928095627320427j) (0.10373811858915072+0.4154429011792203j)
(0.4607918525800892+0.37730507497378507j) (0.11181473095674116+0.40732184555591266j)
(0.46896955266297935+0.36125543488023454j) (0.12015587906971015+0.39987835364859686j)
(0.47612335068304124+0.34472398304845747j) (0.12871038988714162+0.39309475311149156j)
(0.48222501386774913+0.32777596156633226j) (0.13743241885223617+0.38695122370378676j)
(0.48725046174009773+0.31047825653183403j) (0.14628112547596767+0.3814264978213311j)
(0.49117986115324375+0.29289913408416407j) (0.15522031351425608+0.3764984484566931j)
(0.4939977045628752+0.27510797098848666j) (0.16421805333322775+0.37214457562338343j)
(0.49569287122840444+0.25717498083753065j) (0.17324629997268026+0.3683424028404304j)
In [202]:
#(0.4689695526629794+0.11708643902100246j) (0.24420404670148016+0.35529388986894j)
gamma_s = (0.13821946871574342-0.03855273649252047j)
gamma_l = (0.3980438614372531+0.43616650204314683j)
gamma_s = bjt.g_opt[idx_915mhz]

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

z_s, z_l
Out[202]:
((66.2631010551724-38.52253932822274j), (58.93311731910662-78.9309614005669j))
In [203]:
x_s_1, x_s_2 = calc_matching_network_vals(z_s, 50)
x_l_1, x_l_2 = calc_matching_network_vals(z_l, 50)

x_s_1, x_s_2, x_l_1, x_l_2
Out[203]:
(-297.56843939433804j,
 43.965021073542395j,
 -941.2755436141462j,
 75.71255337677657j)
In [204]:
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_1))
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_ser = -np.real(1/(2j*np.pi*915e+6*x_s_1))
l_s_shunt = -np.real(x_s_2/(2j*np.pi*915e+6))


#c_s_shunt, c_s_ser, c_l_shunt, l_l_ser
c_s_shunt, l_s_ser, c_l_shunt, l_l_ser
Out[204]:
(5.845372206466672e-13,
 7.647268226222535e-09,
 1.8479161569190324e-13,
 1.3169428550844769e-08)
In [205]:
#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)
output_network2 = tem.shunt_capacitor(c_l_shunt) ** tem.inductor(l_l_ser)

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

amplifier2.plot_s_smith()
In [206]:
amplifier2.s21.plot_s_db()
In [207]:
amplifier2.s11.plot_s_db()
In [208]:
amplifier2.s22.plot_s_db()
In [209]:
10*np.log10(amplifier2.nf(50.)[idx_900mhz])
Out[209]:
0.7063394274281913
In [210]:
cascade = amplifier2 ** tem.resistor(22) ** amplifier2
cascade.plot_s_smith()
In [211]:
cascade.s21.plot_s_db()
In [212]:
10*np.log10(cascade.nf(50.)[idx_900mhz])
Out[212]:
0.7351173919471382
In [213]:
cascade.s22.plot_s_db()
In [214]:
cascade.s11.plot_s_db()
In [ ]:
 
In [ ]: