def run_main(): interp_method = 'spline' sim_env = 'tt' nmos_spec = 'specs_mos_char/nch_w0d5.yaml' pmos_spec = 'specs_mos_char/pch_w0d5.yaml' intent = 'lvt' nch_db = get_db(nmos_spec, intent, interp_method=interp_method, sim_env=sim_env) nch_op = nch_db.query(vbs=0, vds=0.5, vgs=0.5) pprint.pprint(nch_op) # building circuit cir = LTICircuit() cir.add_transistor(nch_op, 'out', 'in', 'gnd', 'gnd', fg=2) cir.add_res(10e3, 'out', 'gnd') cir.add_cap(100e-15, 'out', 'gnd') # get gain/poles/zeros/bode plot trans_fun = cir.get_transfer_function('in', 'out', in_type='v') print('poles: %s' % trans_fun.poles) print('zeros: %s' % trans_fun.zeros) # note: don't use the gain attribute. For some reason it's broken print('gain: %.4g' % (trans_fun.num[-1] / trans_fun.den[-1])) fvec = np.logspace(5, 10, 1000) _, mag, phase = sig.bode(trans_fun, w=2 * np.pi * fvec) # get transient response state_space = cir.get_state_space('in', 'out', in_type='v') tvec = np.linspace(0, 1e-8, 1000) _, yvec = sig.step(state_space, T=tvec) _, (ax0, ax1) = plt.subplots(2, sharex=True) ax0.semilogx(fvec, mag) ax0.set_ylabel('Magnitude (dB)') ax1.semilogx(fvec, phase) ax1.set_ylabel('Phase (degrees)') ax1.set_xlabel('Frequency (Hz)') plt.figure(2) plt.plot(tvec, yvec) plt.ylabel('Output (V/V)') plt.xlabel('Time (s)') plt.show()
def design_amp(specs, in_op, nbot_op, ntop_op, pbot_op, ptop_op): casc_nscale = specs['casc_nscale'] casc_pscale = specs['casc_pscale'] nbot_fg = 2 ibias = nbot_op['ibias'] ntop_fg = casc_nscale pbot_fg = ibias / pbot_op['ibias'] ptop_fg = pbot_fg * casc_pscale in_fg = ibias / in_op['ibias'] scale = 3 cir = LTICircuit() cir.add_transistor(in_op, 'x', 'in', 'gnd', 'gnd', fg=in_fg * scale) cir.add_transistor(nbot_op, 'x', 'gnd', 'gnd', 'gnd', fg=nbot_fg * scale) cir.add_transistor(ntop_op, 'out', 'gnd', 'x', 'gnd', fg=ntop_fg * scale) cir.add_transistor(ptop_op, 'out', 'gnd', 'm', 'gnd', fg=ptop_fg * scale) cir.add_transistor(pbot_op, 'm', 'gnd', 'gnd', 'gnd', fg=pbot_fg * scale) cir.add_cap(20e-15 * scale, 'out', 'gnd') cur_tf = cir.get_transfer_function('in', 'out', in_type='v') gain = cur_tf.num[-1] / cur_tf.den[-1] p1, p2, p3 = sorted(-cur_tf.poles) p1 /= 2 * np.pi p2 /= 2 * np.pi p3 /= 2 * np.pi for name, op, fg in [('in', in_op, in_fg), ('nbot', nbot_op, nbot_fg), ('ntop', ntop_op, ntop_fg), ('ptop', ptop_op, ptop_fg), ('pbot', pbot_op, pbot_fg)]: print('%s fg : \t%.3g' % (name, fg * scale)) for val in ('gm', 'gds', 'gb', 'ibias'): if val in op: print('%s %s : \t%.6g' % (name, val, op[val] * scale * fg)) print('vtail: %.6g' % (specs['vincm'] - in_op['vgs'])) print('vmidn: %.6g' % (nbot_op['vds'])) print('vmidp: %.6g' % (specs['vdd'] + pbot_op['vds'])) print('vb1: %.6g' % (specs['vdd'] + pbot_op['vgs'])) print('vb2: %.6g' % (specs['vdd'] + pbot_op['vds'] + ptop_op['vgs'])) print('vb3: %.6g' % (nbot_op['vds'] + ntop_op['vgs'])) print('vb4: %.6g' % (nbot_op['vgs'])) print(gain, p1 / 1e9, p2 / 1e9, p3 / 1e9, nbot_fg * ibias)
def design_inverter_tia_lti(db_n, db_p, sim_env, vg_res, rf_res, vdd, cpd, cload, rdc_min, fbw_min, pm_min, vb_n, vb_p): """ Designs a transimpedance amplifier with an inverter amplifier in resistive feedback. Uses the LTICircuit functionality. Inputs: db_n/p: Databases for NMOS and PMOS device characterization data, respectively. sim_env: Simulation corner. vg_res: Float. Step resolution when sweeping gate voltage. rf_res: Float. Step resolution when sweeping feedback resistance. vdd: Float. Supply voltage. cpd: Float. Input parasitic capacitance. cload: Float. Output load capacitance. rdc_min: Float. Minimum DC transimpedance. fbw_min: Float. Minimum bandwidth (Hz). pm_min: Float. Minimum phase margin. vb_n/p: Float. Back-gate/body voltage of NMOS and PMOS, respectively. Raises: ValueError: If unable to meet the specification requirements. Returns: A dictionary with the following key:value pairings: vg: Float. Input bias voltage. nf_n: Integer. NMOS number of channel fingers. nf_p: Integer. PMOS number of channel fingers. rf: Float. Value of feedback resistor. rdc: Float. Expected DC transimpedance. fbw: Float. Expected bandwidth (Hz). pm: Float. Expected phase margin. ibias: Float. Expected DC bias current. """ ibn_fun = db_n.get_function('ibias', env=sim_env) ibp_fun = db_p.get_function('ibias', env=sim_env) # Get sweep values (Vg, Vd) vg_min = 0 vg_max = vdd vg_vec = np.arange(vg_min, vg_max, vg_res) nf_n_vec = np.arange(1, 20, 1) # DEBUGGING: Is there a non-brute force way of setting this? # Find the best operating point best_ibias = float('inf') best_op = None for vg in vg_vec: vdd_vd_ratio = vdd/vg #print("\nVD/VG: {}".format(vg)) n_op_info = db_n.query(vgs=vg, vds=vg, vbs=vb_n-0) p_op_info = db_p.query(vgs=vg-vdd, vds=vg-vdd, vbs=vb_p-vdd) # Find ratio of fingers to get desired output common mode ibias_n = n_op_info['ibias'] ibias_p = p_op_info['ibias'] pn_match = abs(ibias_n/ibias_p) pn_ratio = pn_match/(vdd_vd_ratio - 1) # DEBUGGING: Won't be exact if pn_ratio == 0: continue # Sweep the number of fingers to minimize power for nf_n in nf_n_vec: nf_p = int(round(nf_n * pn_ratio)) if nf_p <= 0: continue ibias_error = abs(abs(ibias_p)*nf_p-abs(ibias_n)*nf_n)/(abs(ibias_n)*nf_n) if ibias_error > 0.05: continue print("N/P: {}/{} fingers".format(nf_n, nf_p)) # Finding amplifier ss parameters inv = LTICircuit() inv.add_transistor(n_op_info, 'out', 'in', 'gnd', fg=nf_n) inv.add_transistor(p_op_info, 'out', 'in', 'gnd', fg=nf_p) inv_num, inv_den = inv.get_num_den(in_name='in', out_name='out', in_type='v') A0 = abs(inv_num[-1]/inv_den[-1]) gds_n = n_op_info['gds'] * nf_n gm_n = n_op_info['gm'] * nf_n cgs_n = n_op_info['cgs'] * nf_n cgd_n = n_op_info['cgd'] * nf_n cds_n = n_op_info['cds'] * nf_n cgb_n = n_op_info.get('cgb', 0) * nf_n cdb_n = n_op_info.get('cdb', 0) * nf_n cdd_n = n_op_info['cdd'] * nf_n cgg_n = n_op_info['cgg'] * nf_n gds_p = p_op_info['gds'] * nf_p gm_p = p_op_info['gm'] * nf_p cgs_p = p_op_info['cgs'] * nf_p cgd_p = p_op_info['cgd'] * nf_p cds_p = p_op_info['cds'] * nf_p cgb_p = p_op_info.get('cgb', 0) * nf_p cdb_p = p_op_info.get('cdb', 0) * nf_p cdd_p = p_op_info['cdd'] * nf_p cgg_p = p_op_info['cgg'] * nf_p gm = abs(gm_n) + abs(gm_p) gds = abs(gds_n) + abs(gds_p) ro = 1/gds cgs = cgs_n + cgs_p cds = cds_n + cds_p cgb = cgb_n + cgb_p cdb = cdb_n + cdb_p cgd = cgd_n + cgd_p cdd = cdd_n + cdd_p cgg = cgg_n + cgg_p # Assume Rdc is negative, bound Rf rf_min = max(rdc_min*(1+A0)/A0 + ro/A0, 0) rf_vec = np.arange(rf_min, rdc_min*5, rf_res) # Sweep values of Rf to check f3dB and PM spec for rf in rf_vec: # Circuit for GBW circuit = LTICircuit() circuit.add_transistor(n_op_info, 'out', 'in', 'gnd', fg=nf_n) circuit.add_transistor(p_op_info, 'out', 'in', 'gnd', fg=nf_p) circuit.add_res(rf, 'in', 'out') circuit.add_cap(cpd, 'in', 'gnd') circuit.add_cap(cload, 'out', 'gnd') # Determining if it meets spec num, den = circuit.get_num_den(in_name='in', out_name='out', in_type='i') rdc = num[-1]/den[-1] if abs(rdc) < rdc_min-1e-8: print("RDC: {0:.2f} (FAIL)\n".format(rdc)) continue else: print("RDC: {0:.2f}".format(rdc)) fbw = get_w_3db(num, den)/(2*np.pi) if fbw < fbw_min or isnan(fbw): print("BW: {} (FAIL)\n".format(fbw)) break # Increasing Rf isn't going to help else: print("BW: {}".format(fbw)) # Circuit for phase margin # miller = (1-gm*rf)/(ro+rf)*ro circuit2 = LTICircuit() circuit2.add_conductance(gds, 'out', 'gnd') circuit2.add_cap(cgg+cpd, 'in', 'gnd') circuit2.add_cap(cdd+cload, 'out', 'gnd') circuit2.add_cap(cgd, 'in', 'out') circuit2.add_res(rf, 'in', 'out') loopBreak = circuit2.get_transfer_function(in_name='out', out_name='in', in_type='i') pm, gainm = get_stability_margins(loopBreak.num*gm, loopBreak.den) if pm < pm_min or isnan(pm): print("PM: {} (FAIL)\n".format(pm)) continue else: print("PM: {}\n".format(pm)) if ibias_n*nf_n < best_ibias: best_ibias = ibias_n*nf_n best_op = dict( vg=vg, nf_n=nf_n, nf_p=nf_p, rf=rf, rdc=rdc, fbw=fbw, pm=pm, ibias=best_ibias) if best_op == None: raise ValueError("No solutions.") return best_op
def characterize_casc_amp(env_list, lch, intent_list, fg_list, w_list, db_list, vbias_list, vload_list, vtail_list, vmid_list, vincm, voutcm, vdd, vin_clip, clip_ratio_min, cw, rw, fanout, ton, k_settle_targ, scale_min=0.25, scale_max=20): # compute DC transfer function curve and compute linearity spec ndb, pdb = db_list[0], db_list[-1] results = solve_casc_diff_dc(env_list, ndb, pdb, lch, intent_list, w_list, fg_list, vbias_list, vload_list, vtail_list, vmid_list, vdd, vincm, voutcm, vin_clip, clip_ratio_min, num_points=20) vmat_list, ratio_list, gain_list = results # compute settling ratio fg_tail, fg_in, fg_casc, fg_load = fg_list db_tail, db_in, db_casc, db_load = db_list w_tail, w_in, w_casc, w_load = w_list fzin = 1.0 / (2 * ton) wzin = 2 * np.pi * fzin tvec = np.linspace(0, ton, 200, endpoint=True) scale_list = [] cin_list = [] for env, vbias, vload, vtail, vmid, vmat in zip(env_list, vbias_list, vload_list, vtail_list, vmid_list, vmat_list): # step 1: get half circuit parameters and compute input capacitance in_par = db_in.query(env=env, w=w_in, vbs=-vtail, vds=vmid - vtail, vgs=vincm - vtail) casc_par = db_casc.query(env=env, w=w_casc, vbs=-vmid, vds=voutcm - vmid, vgs=vdd - vmid) load_par = db_load.query(env=env, w=w_load, vbs=0, vds=voutcm - vdd, vgs=vload - vdd) cir = LTICircuit() cir.add_transistor(in_par, 'mid', 'in', 'gnd', fg=fg_in) cir.add_transistor(casc_par, 'out', 'gnd', 'mid', fg=fg_casc) cir.add_transistor(load_par, 'out', 'gnd', 'gnd', fg=fg_load) zin = cir.get_impedance('in', fzin) cin = (1 / zin).imag / wzin cin_list.append(cin) # step 3A: construct differential circuit with vin_clip / 2. vts, vmps, vmns, vops, vons = vmat[-1, :] vinp = vincm + vin_clip / 4 vinn = vincm - vin_clip / 4 tail_par = db_tail.query(env=env, w=w_tail, vbs=0, vds=vts, vgs=vbias) inp_par = db_in.query(env=env, w=w_in, vbs=-vts, vds=vmns - vts, vgs=vinp - vts) inn_par = db_in.query(env=env, w=w_in, vbs=-vts, vds=vmps - vts, vgs=vinn - vts) cascp_par = db_casc.query(env=env, w=w_casc, vbs=-vmns, vds=vons - vmns, vgs=vdd - vmns) cascn_par = db_casc.query(env=env, w=w_casc, vbs=-vmps, vds=vops - vmps, vgs=vdd - vmps) loadp_par = db_load.query(env=env, w=w_load, vbs=0, vds=vons - vdd, vgs=vload - vdd) loadn_par = db_load.query(env=env, w=w_load, vbs=0, vds=vops - vdd, vgs=vload - vdd) cir = LTICircuit() cir.add_transistor(tail_par, 'tail', 'gnd', 'gnd', fg=fg_tail) cir.add_transistor(inp_par, 'midn', 'inp', 'tail', fg=fg_in) cir.add_transistor(inn_par, 'midp', 'inn', 'tail', fg=fg_in) cir.add_transistor(cascp_par, 'dn', 'gnd', 'midn', fg=fg_casc) cir.add_transistor(cascn_par, 'dp', 'gnd', 'midp', fg=fg_casc) cir.add_transistor(loadp_par, 'dn', 'gnd', 'gnd', fg=fg_load) cir.add_transistor(loadn_par, 'dp', 'gnd', 'gnd', fg=fg_load) cir.add_vcvs(0.5, 'inp', 'gnd', 'inac', 'gnd') cir.add_vcvs(-0.5, 'inn', 'gnd', 'inac', 'gnd') # step 3B: compute DC gain cir.add_vcvs(1, 'dac', 'gnd', 'dp', 'dn') dc_tf = cir.get_transfer_function('inac', 'dac') _, gain_vec = dc_tf.freqresp(w=[0.1]) dc_gain = gain_vec[0].real # step 3C add cap loading cload = fanout * cin cir.add_cap(cload, 'outp', 'gnd') cir.add_cap(cload, 'outn', 'gnd') cir.add_vcvs(1, 'outac', 'gnd', 'outp', 'outn') # step 4: find scale factor to achieve k_settle # noinspection PyUnresolvedReferences scale_swp = np.arange(scale_min, scale_max, 0.25).tolist() max_kset = 0 opt_scale = 0 for cur_scale in scale_swp: # add scaled wired parasitics cap_cur = cw / 2 / cur_scale res_cur = rw * cur_scale cir.add_cap(cap_cur, 'dp', 'gnd') cir.add_cap(cap_cur, 'dn', 'gnd') cir.add_cap(cap_cur, 'outp', 'gnd') cir.add_cap(cap_cur, 'outn', 'gnd') cir.add_res(res_cur, 'dp', 'outp') cir.add_res(res_cur, 'dn', 'outn') # get settling factor sys = cir.get_state_space('inac', 'outac') _, yvec = scipy.signal.step( sys, T=tvec) # type: Tuple[np.ndarray, np.ndarray] k_settle_cur = yvec[-1] / dc_gain # print('scale = %.4g, k_settle = %.4g' % (cur_scale, k_settle_cur)) # update next scale factor if k_settle_cur > max_kset: max_kset = k_settle_cur opt_scale = cur_scale else: # k_settle is decreasing, break break if max_kset >= k_settle_targ: break # remove wire parasitics cir.add_cap(-cap_cur, 'dp', 'gnd') cir.add_cap(-cap_cur, 'dn', 'gnd') cir.add_cap(-cap_cur, 'outp', 'gnd') cir.add_cap(-cap_cur, 'outn', 'gnd') cir.add_res(-res_cur, 'dp', 'outp') cir.add_res(-res_cur, 'dn', 'outn') if max_kset < k_settle_targ: raise ValueError('%s max kset = %.4g @ scale = %.4g, ' 'cannot meet settling time spec.' % (env, max_kset, opt_scale)) scale_list.append(opt_scale) return vmat_list, ratio_list, gain_list, scale_list, cin_list
def design_load(specs, db, ax_list=None, label=None): sim_env = specs['sim_env'] vgs_res = specs['vgs_res'] vdd = specs['vdd'] voutcm = specs['voutcm'] vstar_in = specs['vstar_in'] vstar_load = specs['vstar_load'] casc_scale = specs['casc_scale'] vgs_idx = db.get_fun_arg_index('vgs') vstar_fun = db.get_function('vstar', env=sim_env) ibias_fun = db.get_function('ibias', env=sim_env) vgs_min, vgs_max = vstar_fun.get_input_range(vgs_idx) def zero_fun1(vm, vgs): arg = db.get_fun_arg(vgs=vgs, vds=vm-vdd, vbs=0) return (vstar_fun(arg) - vstar_load) * 1e3 def zero_fun2(vc, vm, itarg): arg = db.get_fun_arg(vgs=vc-vm, vds=voutcm-vm, vbs=vdd-vm) return (ibias_fun(arg) * casc_scale - itarg) * 1e6 num_pts = int(np.ceil((vgs_max - vgs_min) / vgs_res)) + 1 vgs_list, vds_list, vcasc_list, ibias_list, ro_list = [], [], [], [], [] p1_list, p2_list, gain_list = [], [], [] for vgs_val in np.linspace(vgs_min, vgs_max, num_pts, endpoint=True): fout1 = zero_fun1(vdd - 5e-3, vgs_val) fout2 = zero_fun1(voutcm + 5e-3, vgs_val) if fout1 * fout2 > 0: continue vmid = opt.brentq(zero_fun1, voutcm + 5e-3, vdd - 5e-3, args=(vgs_val,)) if vmid + vgs_max <= 0: continue barg = db.get_fun_arg(vgs=vgs_val, vds=vmid-vdd, vbs=0) bot_op = db.query(vbs=0, vds=vmid-vdd, vgs=vgs_val) ib = bot_op['ibias'] args = (vmid, ib) fout1 = zero_fun2(vmid + vgs_max, *args) fout2 = zero_fun2(0, *args) if fout1 * fout2 > 0: continue vcasc = opt.brentq(zero_fun2, 0, vmid + vgs_max, args=args) top_op = db.query(vbs=vdd-vmid, vds=voutcm-vmid, vgs=vcasc-vmid) cir = LTICircuit() cir.add_transistor(bot_op, 'mid', 'gnd', 'gnd', 'gnd', fg=1) cir.add_transistor(top_op, 'out', 'gnd', 'mid', 'gnd', fg=casc_scale) cur_tf = cir.get_transfer_function('out', 'out', in_type='i') ro = cur_tf.num[-1] / cur_tf.den[-1] p1, p2 = cur_tf.poles p1, p2 = min(-p1, -p2), max(-p1, -p2) p2 /= 2 * np.pi # add approximation from input branch cpar = 1 / (ro * p1) p1 = 1 / (2 * np.pi * 2 * cpar * (ro / 4)) cur_gain = 2 * ib / vstar_in * (ro / 4) vgs_list.append(vgs_val) vds_list.append(vmid-vdd) vcasc_list.append(vcasc) ibias_list.append(ib) ro_list.append(ro * ib) p1_list.append(p1) p2_list.append(p2) gain_list.append(cur_gain) if ax_list is not None: val_list_list = [vds_list, gain_list, p1_list, p2_list] ylabel_list = ['$V_{DS}$ (V)', 'Gain (V/V)', '$p_{1o}$ (GHz)', '$p_{2o}$ (GHz)'] sp_list = [1, 1, 1e-9, 1e-9] for ax, val_list, ylabel, sp in zip(ax_list, val_list_list, ylabel_list, sp_list): ax.plot(vgs_list, np.asarray(val_list) * sp, '-o', label=label) ax.set_ylabel(ylabel) ax_list[-1].set_xlabel('$V_{GS}$ (V)') return vgs_list[0], vds_list[0], vcasc_list[0]
def design_inv_tia(specs, pch_op_info, nch_op_info, pch_scale): sim_env = specs['sim_env'] pch_db = specs['pch_db'] nch_db = specs['nch_db'] vgs_res = specs['vgs_res'] vdd = specs['vdd'] rdc_targ = specs['rdc'] f3db_targ = specs['f3db'] pm_targ = specs['pm'] cin = specs['cin'] cl = specs['cl'] scale_min = specs['scale_min'] scale_max = specs['scale_max'] n_scale = specs['n_scale'] rf_min = specs['rf_min'] rf_max = specs['rf_max'] n_rf = specs['n_rf'] pch_ibias = pch_op_info['ibias'] nch_ibias = nch_op_info['ibias'] gmp = pch_op_info['gm'] * pch_scale gmn = nch_op_info['gm'] gm_tot = gmp + gmn gdsp = pch_op_info['gds'] * pch_scale gdsn = nch_op_info['gds'] rop = 1 / gdsp ron = 1 / gdsn ro_tot = rop * ron / (rop + ron) gds_tot = 1 / ro_tot cgsp = pch_op_info['cgs'] * pch_scale cgsn = nch_op_info['cgs'] cgs_tot = cgsp + cgsn cgbp = pch_op_info['cgb'] * pch_scale cgbn = nch_op_info['cgb'] cgb_tot = cgbp + cgbn cgdp = pch_op_info['cgd'] * pch_scale cgdn = nch_op_info['cgd'] cgd_tot = cgdp + cgdn cggp = pch_op_info['cgg'] * pch_scale cggn = nch_op_info['cgg'] cgg_tot = cggp + cggn cdsp = pch_op_info['cds'] * pch_scale cdsn = nch_op_info['cds'] cds_tot = cdsp + cdsn cdbp = pch_op_info['cdb'] * pch_scale cdbn = nch_op_info['cdb'] cdb_tot = cdbp + cdbn cddp = pch_op_info['cdd'] * pch_scale cddn = nch_op_info['cdd'] cdd_tot = cddp + cddn scale_vec = np.linspace(scale_min, scale_max, n_scale) for scale in scale_vec: for rf in np.linspace(rf_min, rf_max, n_rf): # Build circuit cir = LTICircuit() cir.add_transistor(pch_op_info, 'out', 'in', 'gnd', 'gnd', fg=scale * pch_scale) cir.add_transistor(nch_op_info, 'out', 'in', 'gnd', 'gnd', fg=scale) cir.add_res(rf, 'out', 'in') cir.add_cap(cin, 'in', 'gnd') cir.add_cap(cl, 'out', 'gnd') # Get gain/poles/zeros/Bode plot # Note: any in_type other than 'v' results in current source input tf = cir.get_transfer_function('in', 'out', in_type='i') rdc = np.absolute(tf.num[-1] / tf.den[-1]) w3db = get_w_3db(tf.num, tf.den) f3db = w3db / (2 * np.pi) cin_tot = cin + cgg_tot * scale cl_tot = cl + cdd_tot * scale cgd_scaled = cgd_tot * scale gm_scaled = gm_tot * scale gds_scaled = gds_tot * scale cir_open_loop = LTICircuit() cir_open_loop.add_vccs(gm_scaled, 'out', 'gnd', 'vt', 'gnd') cir_open_loop.add_conductance(gds_scaled, 'out', 'gnd') cir_open_loop.add_cap(cl_tot, 'out', 'gnd') cir_open_loop.add_res(rf, 'out', 'vr') cir_open_loop.add_cap(cgd_scaled, 'out', 'vr') cir_open_loop.add_cap(cin_tot, 'vr', 'gnd') tf_open_loop = cir_open_loop.get_transfer_function('vt', 'vr', in_type='v') pm, gain_margin = get_stability_margins(tf_open_loop.num, tf_open_loop.den) pm = pm - 180 if rdc >= rdc_targ and f3db >= f3db_targ and pm >= pm_targ: ibias = scale * nch_ibias design = dict(IB=ibias, Scale=scale, Rf=rf, RDC=rdc, f3dB=f3db, PM=pm) return design
def design_inv_tia(specs, pch_op_info, nch_op_info, pch_scale): sim_env = specs['sim_env'] pch_db = specs['pch_db'] nch_db = specs['nch_db'] vgs_res = specs['vgs_res'] vdd = specs['vdd'] isw = specs['isw'] ber_targ = specs['ber'] voff = specs['voff'] snr_like_targ = specs['snr_like'] pmos_noise_scale = specs['pmos_noise_scale'] # rdc_targ = specs['rdc'] noise_const = specs['noise_const'] tper = specs['tper'] f_factor = specs['f_factor'] # f3db_targ = specs['f3db'] f3db_targ = f_factor * 1 / tper pm_targ = specs['pm'] cin = specs['cin'] cl = specs['cl'] scale_min = specs['scale_min'] scale_max = specs['scale_max'] n_scale = specs['n_scale'] rf_min = specs['rf_min'] rf_max = specs['rf_max'] n_rf = specs['n_rf'] pch_ibias = pch_op_info['ibias'] nch_ibias = nch_op_info['ibias'] gmp = pch_op_info['gm'] * pch_scale gmn = nch_op_info['gm'] gm_tot = gmp + gmn gammap = pch_op_info['gamma'] gamman = nch_op_info['gamma'] gdsp = pch_op_info['gds'] * pch_scale gdsn = nch_op_info['gds'] rop = 1 / gdsp ron = 1 / gdsn ro_tot = rop * ron / (rop + ron) gds_tot = 1 / ro_tot cgsp = pch_op_info['cgs'] * pch_scale cgsn = nch_op_info['cgs'] cgs_tot = cgsp + cgsn cgbp = pch_op_info['cgb'] * pch_scale cgbn = nch_op_info['cgb'] cgb_tot = cgbp + cgbn cgdp = pch_op_info['cgd'] * pch_scale cgdn = nch_op_info['cgd'] cgd_tot = cgdp + cgdn cggp = pch_op_info['cgg'] * pch_scale cggn = nch_op_info['cgg'] cgg_tot = cggp + cggn cdsp = pch_op_info['cds'] * pch_scale cdsn = nch_op_info['cds'] cds_tot = cdsp + cdsn cdbp = pch_op_info['cdb'] * pch_scale cdbn = nch_op_info['cdb'] cdb_tot = cdbp + cdbn cddp = pch_op_info['cdd'] * pch_scale cddn = nch_op_info['cdd'] cdd_tot = cddp + cddn scale_vec = np.linspace(scale_min, scale_max, n_scale) for scale in scale_vec: for rf in np.linspace(rf_max, rf_min, n_rf): # Build circuit cir = LTICircuit() cir.add_transistor(pch_op_info, 'out', 'in', 'gnd', 'gnd', fg=scale * pch_scale) cir.add_transistor(nch_op_info, 'out', 'in', 'gnd', 'gnd', fg=scale) cir.add_res(rf, 'out', 'in') cir.add_cap(cin, 'in', 'gnd') cir.add_cap(cl, 'out', 'gnd') # Get gain/poles/zeros/Bode plot # Note: any in_type other than 'v' results in current source input tf = cir.get_transfer_function('in', 'out', in_type='i') rdc = np.absolute(tf.num[-1] / tf.den[-1]) w3db = get_w_3db(tf.num, tf.den) f3db = w3db / (2 * np.pi) if f3db >= f3db_targ: # Noise in_gmp = noise_const * gammap * gmp in_gmn = noise_const * gamman * gmn in_rf = noise_const / rf tf_gm = cir.get_transfer_function('out', 'out', in_type='i') dc_gm = tf_gm.num[-1] / tf_gm.den[-1] wo2_gm = tf_gm.den[-1] / tf_gm.den[0] woQ_gm = tf_gm.den[-1] / tf_gm.den[1] wz_gm = tf_gm.num[-1] / tf_gm.num[0] von_gmp = pmos_noise_scale * in_gmp * dc_gm**2 * woQ_gm / 4 * ( wo2_gm / wz_gm**2 + 1) von_gmn = in_gmn * dc_gm**2 * woQ_gm / 4 * (wo2_gm / wz_gm**2 + 1) cir.add_vccs(1, 'out', 'in', 'vn', 'gnd') tf_rf = cir.get_transfer_function('vn', 'out', in_type='v') dc_rf = tf_rf.num[-1] / tf_rf.den[-1] wo2_rf = tf_rf.den[-1] / tf_rf.den[0] woQ_rf = tf_rf.den[-1] / tf_rf.den[1] wz_rf = tf_rf.num[-1] / tf_rf.num[0] von_rf = in_rf * dc_rf**2 * woQ_rf / 4 * (wo2_rf / wz_rf**2 + 1) von = von_gmp + von_gmn + von_rf # Signal vo = isw * rdc snr_like = (vo - voff) / np.sqrt(von) if snr_like >= snr_like_targ: cin_tot = cin + cgg_tot * scale cl_tot = cl + cdd_tot * scale cgd_scaled = cgd_tot * scale gm_scaled = gm_tot * scale gds_scaled = gds_tot * scale # Build open loop circuit for phase margin cir_open_loop = LTICircuit() cir_open_loop.add_vccs(gm_scaled, 'out', 'gnd', 'vt', 'gnd') # *** cir_open_loop.add_conductance(gds_scaled, 'out', 'gnd') cir_open_loop.add_cap(cl_tot, 'out', 'gnd') cir_open_loop.add_res(rf, 'out', 'vr') cir_open_loop.add_cap(cgd_scaled, 'out', 'vr') cir_open_loop.add_cap(cin_tot, 'vr', 'gnd') tf_open_loop = cir_open_loop.get_transfer_function( 'vt', 'vr', in_type='v') pm, gm = get_stability_margins(tf_open_loop.num, tf_open_loop.den) pm = pm - 180 if pm >= pm_targ: ibias = scale * nch_ibias design = dict(IB=ibias, Scale=scale, Rf=rf, RDC=rdc, f3dB=f3db, PM=pm, Von_Rf=von_rf, Von_gmp=von_gmp, Von_gmn=von_gmn, Von=von, SNR_Like=snr_like) return design
def verify_TIA_inverter_SS(n_op_info, p_op_info, nf_n, nf_p, rf, cpd, cload, rdc_min, fbw_min, pm_min): """ Inputs: n/p_op_info: The MOSDBDiscrete library for the NMOS and PMOS devices in the bias point for verification. nf_n/p: Integer. Number of channel fingers for the NMOS/PMOS. rf: Float. Value of the feedback resistor in ohms. cpd: Float. Input capacitance not from the TIA in farads. cload: Float. Output capacitance not from the TIA in farads. rdc_min: Float. Minimum DC transimpedance in ohms. fbw_min: Float. Minimum bandwidth (Hz). pm_min: Float. Minimum phase margin in degrees. Outputs: Returns two values The first is True if the spec is met, False otherwise. The second is a dictionary of values for rdc (DC transimpedance, V/I), bw (bandwidth, Hz), and pm (phase margin, deg) if computed. None otherwise. """ # Getting relevant small-signal parameters gds_n = n_op_info['gds'] * nf_n gds_p = p_op_info['gds'] * nf_p gds = gds_n + gds_p gm_n = n_op_info['gm'] * nf_n gm_p = p_op_info['gm'] * nf_p gm = gm_n + gm_p cgs_n = n_op_info['cgs'] * nf_n cgs_p = p_op_info['cgs'] * nf_p cgs = cgs_n + cgs_p cds_n = n_op_info['cds'] * nf_n cds_p = p_op_info['cds'] * nf_p cds = cds_n + cds_p cgd_n = n_op_info['cgd'] * nf_n cgd_p = p_op_info['cgd'] * nf_p cgd = cgd_n + cgd_p # Circuit for GBW circuit = LTICircuit() circuit.add_transistor(n_op_info, 'out', 'in', 'gnd', fg=nf_n) circuit.add_transistor(p_op_info, 'out', 'in', 'gnd', fg=nf_p) circuit.add_res(rf, 'in', 'out') circuit.add_cap(cpd, 'in', 'gnd') circuit.add_cap(cload, 'out', 'gnd') # Check gain num, den = circuit.get_num_den(in_name='in', out_name='out', in_type='i') rdc = num[-1] / den[-1] if abs(round(rdc)) < round(rdc_min): print("GAIN:\t{0} (FAIL)".format(rdc)) return False, dict(rdc=rdc, fbw=None, pm=None) # Check bandwidth fbw = get_w_3db(num, den) / (2 * np.pi) if fbw < fbw_min or np.isnan(fbw): print("BW:\t{0} (FAIL)".format(fbw)) return False, dict(rdc=rdc, fbw=fbw, pm=None) # Check phase margin by constructing an LTICircuit first circuit2 = LTICircuit() """circuit2.add_transistor(n_op_info, 'out', 'in', 'gnd', fg=nf_n) circuit2.add_transistor(p_op_info, 'out', 'in', 'gnd', fg=nf_p) circuit2.add_cap(cpd, 'in', 'gnd') circuit2.add_cap(cload, 'out', 'gnd') circuit2.add_res(rf, 'in', 'break') # Cancel Cgd to correctly break loop circuit2.add_cap(-cgd, 'in' , 'out') circuit.add_cap(cgd, 'in', 'break')""" circuit2.add_conductance(gds, 'out', 'gnd') circuit2.add_cap(cgs + cpd, 'in', 'gnd') circuit2.add_cap(cds + cload, 'out', 'gnd') circuit2.add_cap(cgd, 'in', 'out') circuit2.add_res(rf, 'in', 'out') loopBreak = circuit2.get_transfer_function(in_name='in', out_name='out', in_type='i') pm, gainm = get_stability_margins(loopBreak.num * gm, loopBreak.den) if pm < pm_min or np.isnan(pm): print("PM:\t{0} (FAIL)\n".format(pm)) return False, dict(rdc=rdc, fbw=fbw, pm=pm) print("SUCCESS\n") return True, dict(rdc=rdc, fbw=fbw, pm=pm)