Newer
Older
# -*- coding: utf-8 -*-
"""
Space Baron Team
nats`
Transistor Auto Matcher
Take S parameters file input
"""
import os
import sys
import math
import cmath
def print_complex(name, c):
print("====")
print(name)
print("Rect: %f %fj" % (c.real , c.imag))
print("Polar: %f %fj" % (cmath.polar(c)[0] , math.degrees(cmath.polar(c)[1])))
print("====")
# needed for test on polar
def polar_add(ar, ai, br, bi):
a = cmath.rect(ar, math.degrees(ai))
b = cmath.rect(br, math.degrees(bi))
print_complex("polar res", a + b)
def pol2rec(ar, ai):
a = cmath.rect(ar, math.radians(ai))
return a
# Reac to value (f in MHz)
def reac2val(reac, f):
val = 0
f = f*1000000
if(reac < 0):
val = (1/(2*math.pi*f*reac))*-1
elif(reac > 0):
val = reac/(2*math.pi*f)
return val
def val2reac(val, f, c, unit):
reac = 0
f = f*1000000
if unit == "p":
val = val / 10**12
elif unit == "n":
val = val / 10**9
elif unit == "u":
val = val / 10**6
elif unit == "m":
val = val / 10**3
if c:
reac = 1/(2*math.pi*f*val)
else:
reac = 2*math.pi*f*val
return reac
def z2ref(z1, z0):
return (z1-z0)/(z1+z0)
# 1/Parallel Impedance = (1/Z0)+(1/Z1)
def z2par(z0, z1):
return (z0*z1)/(z0+z1)
def par2z(par, z0):
return (par*z0)/(z0-par)
# Calculate match
# input: initial Z and target Z | Low pass ? | freq in MHz
def matchL(zs, zl, lp, freq):
rs = cmath.real(zs)
rl = cmath.real(zl)
Q = math.sqrt(rl/rs-1)
xstmp = Q * rs
xltmp = Q * rl
return 0
def __init__(self, file, freq, config):
self.freq = freq
self.config = config
self.spformats = {}
self.parse_spar(file,freq)
''' Open S2P Files and extract data '''
def parse_spar(self, file, freq):
f = open(file, "r")
tmpspars = []
for line in f:
if line.startswith("#"):
sfor = line.strip().split()
self.spformats["UNIT"] = sfor[1]
self.spformats["TYPE"] = sfor[2]
self.spformats["FORMAT"] = sfor[3]
# Apparently the nominal Impedance is splitted: R 50
tmpline = line.strip().split()
if len(tmpline) >= 9: #We skip short line often used for noise parameter
tmpspars.append(tmpline)
# Parse options: we mainly need to know unit and complex format
# http://cp.literature.agilent.com/litweb/pdf/genesys200801/sim/linear_sim/sparams/touchstone_file_format.htms
self.spars = {}# 1: Freq 2: S11 3: S21 4: S12 5: S22
for l in tmpspars:
if(l[0] != "!" and l[0] != "#"):
tmp = []
if self.spformats["FORMAT"] == "RI" : # Real Imaginary
tmp.append(complex(float(l[1]), float(l[2]))) # S11
tmp.append(complex(float(l[3]), float(l[4]))) # S21
tmp.append(complex(float(l[5]), float(l[6]))) # S12
tmp.append(complex(float(l[7]), float(l[8]))) # S22
elif self.spformats["FORMAT"] == "MA" : # Magnitude Angle (in degree)
tmp.append(cmath.rect(float(l[1]), math.radians(float(l[2])))) # S11
tmp.append(cmath.rect(float(l[3]), math.radians(float(l[4])))) # S21
tmp.append(cmath.rect(float(l[5]), math.radians(float(l[6])))) # S12
tmp.append(cmath.rect(float(l[7]), math.radians(float(l[8])))) # S22
else :
print("Unsupported Number Format: %s" % self.spformats["FORMAT"])
exit(-1)
# We always set freq to MHz
freq = float(l[0])
if self.spformats["UNIT"] == "GHz":
freq = freq * 1000
elif self.spformats["UNIT"] == "KHz":
freq = freq / 1000
elif self.spformats["UNIT"] == "Hz":
freq = freq / 1000000
self.spars[freq] = tmp
''' Calculate John Rollett stability factor '''
def calc_stab(self):
S = self.S
self.K = (1 + cmath.polar(self.Ds)[0] ** 2 - cmath.polar(S[11])[0] ** 2 - \
cmath.polar(S[22])[0] ** 2) / (2 * cmath.polar(S[21])[0] * cmath.polar(S[12])[0])
''' Caculate Maximum Available Gain '''
def calc_mag(self):
S = self.S
self.B1 = 1 + cmath.polar(S[11])[0] ** 2 - cmath.polar(S[22])[0] ** 2 - cmath.polar(self.Ds)[0] ** 2
ksq = 0.0
if self.B1 < 0 :
ksq = math.sqrt(self.K**2 - 1)
else :
ksq = math.sqrt(self.K**2 - 1) * -1
self.MAG = 10 * math.log10(cmath.polar(S[21])[0] / cmath.polar(S[12])[0]) + \
''' Calculate Maximum Unilateral Gain (S12 = 0)'''
def calc_gum(self):
S = self.S
self.GUM = 10 * math.log10(cmath.polar(S[21])[0]**2 / ((1-cmath.polar(S[11])[0]**2)*(1-cmath.polar(S[22])[0]**2)))
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
def calc_stability_mag(self):
self.sparam = self.spars[self.freq]
self.S = {}
self.S[11] = self.sparam[0]
self.S[21] = self.sparam[1]
self.S[12] = self.sparam[2]
self.S[22] = self.sparam[3]
S = self.S
self.Ds = S[11] * S[22] - S[12] * S[21]
self.calc_stab()
if self.K < 1 :
print("Warning ! K factor %f < 1.0 , amplifier could be unstable" % self.K)
print("Undefined Maximum Available Gain")
else :
print("Stable amplifier design. K factor %f >= 1" % self.K)
self.calc_mag()
print("Maximum Available Gain: %fdB" % self.MAG)
def calc_scm(self):
S = self.S
if self.K > 1 :
self.C1 = S[11] - (self.Ds * complex.conjugate(S[22]))
self.C2 = S[22] - (self.Ds * complex.conjugate(S[11]))
# B1 is already calculated for MAG
self.B2 = 1 + cmath.polar(S[22])[0] ** 2 - cmath.polar(S[11])[0] ** 2 - cmath.polar(self.Ds)[0] ** 2
rad = 0
if self.B2 >= 0:
rad = self.B2 - math.sqrt(self.B2**2 - 4 * cmath.polar(self.C2)[0]**2)
elif self.B2 < 0:
rad = self.B2 + math.sqrt(self.B2**2 - 4 * cmath.polar(self.C2)[0]**2)
# Load Reflection magnitude
ref_mag = rad / (2 * cmath.polar(self.C2)[0])
self.load_ref = cmath.rect(ref_mag, -cmath.polar(self.C2)[1])
self.source_ref = complex.conjugate(S[11] + ((S[12]*S[21]*self.load_ref) / (1-(self.load_ref * S[22]))))
#Calculate Transducer gain
def calc_gt(self):
S = self.S
a = ((abs(S[21])**2)*(1-abs(self.source_ref)**2)*(1-abs(self.load_ref)**2))
b = abs((1-S[11]*self.source_ref)*(1-S[22]*self.load_ref)-S[12]*S[21]*self.load_ref*self.source_ref)**2
self.GT = 10*math.log10(a/b)
if __name__ == '__main__':
if(len(sys.argv) < 4):
print("Error: usage\nauto_match.py spar_file.s2p frequency configuration\n")
exit(-1)
fname = sys.argv[1]
freq = float(sys.argv[2])
config = sys.argv[3]
q1 = TransistorBlock(fname, freq, config)
q1.calc_stability_mag()
q1.calc_gum()
print("GUM: %fdB" % q1.GUM)
q1.calc_scm()
q1.calc_gt()
print("Result")
print("Optimum Source Reflection")
print_complex("Source Reflection", q1.source_ref)
print("Optimum Load Reflection")
print_complex("Load Reflection", q1.load_ref)
print("Transducer gain: %fdB" % q1.GT)
'''a = pol2rec(0.4,162) + ((pol2rec(0.04,60)*pol2rec(5.2,63)*pol2rec(0.487,39)) / (1 - pol2rec(0.487,39)*pol2rec(0.35,-39)))
print_complex("test", complex.conjugate(a))'''
# test ref to z
norm_R = complex(q1.spformats["R"], 0)
print_complex("Load Impedance", ref2z(q1.load_ref,norm_R))
print_complex("Source Impedance", ref2z(q1.source_ref,norm_R))