def tf_vs_cfb(op_in, op_load, op_tail, cload, fg=2): fvec = np.logspace(6, 11, 1000) cvec = np.logspace(np.log10(5e-16), np.log10(5e-14), 5).tolist() scale_load = op_in['ibias'] / op_load['ibias'] * fg cir = LTICircuit() cir.add_transistor(op_in, 'mid', 'in', 'gnd', 'gnd', fg=fg) cir.add_transistor(op_load, 'mid', 'gnd', 'gnd', 'gnd', fg=scale_load) cir.add_transistor(op_load, 'out', 'mid', 'gnd', 'gnd', fg=scale_load) cir.add_transistor(op_tail, 'out', 'gnd', 'gnd', 'gnd', fg=fg) cir.add_cap(cload, 'out', 'gnd') gfb = op_load['gm'] * scale_load cir.add_conductance(gfb, 'mid', 'x') print('fg_in = %d, fg_load=%.3g, rfb = %.4g' % (fg, scale_load, 1 / gfb)) tf_list, lbl_list = [], [] for cval in cvec: cir.add_cap(cval, 'x', 'out') tf_list.append(cir.get_num_den('in', 'out')) cir.add_cap(-cval, 'x', 'out') lbl_list.append('$C_{f} = %.3g$f' % (cval * 1e15)) plot_tf(fvec, tf_list, lbl_list)
def funity_vs_scale2(op_in, op_load, op_tail, cload, phase_margin, fg=2): s2min = 1 s2max = 40 num_s = 100 cmin = 1e-16 cmax = 1e-9 ctol = 1e-17 cstep = 1e-15 scale_load = op_in['ibias'] / op_load['ibias'] * fg gfb = op_load['gm'] * scale_load s2vec = np.linspace(s2min, s2max, num_s).tolist() f0_list, pm0_list, f1_list, pm1_list, copt_list = [], [], [], [], [] for s2 in s2vec: cir = LTICircuit() cir.add_transistor(op_in, 'mid', 'in', 'gnd', 'gnd', fg=fg) cir.add_transistor(op_load, 'mid', 'gnd', 'gnd', 'gnd', fg=scale_load) cir.add_transistor(op_load, 'out', 'mid', 'gnd', 'gnd', fg=scale_load * s2) cir.add_transistor(op_tail, 'out', 'gnd', 'gnd', 'gnd', fg=fg * s2) cir.add_cap(cload, 'out', 'gnd') num, den = cir.get_num_den('in', 'out') f0_list.append(get_w_crossings(num, den)[0] / (2 * np.pi)) pm0_list.append(get_stability_margins(num, den)[0]) cir.add_conductance(gfb * s2, 'mid', 'x') copt = opt_cfb(phase_margin, cir, cmin, cmax, cstep, ctol) if copt is None: raise ValueError('oops, Cfb is None') cir.add_cap(copt, 'x', 'out') num, den = cir.get_num_den('in', 'out') f1_list.append(get_w_crossings(num, den)[0] / (2 * np.pi)) pm1_list.append(get_stability_margins(num, den)[0]) copt_list.append(copt) f, (ax0, ax1, ax2) = plt.subplots(3, sharex='all') ax0.plot(s2vec, np.array(copt_list) * 1e15) ax0.set_ylabel('Cf (fF)') ax1.plot(s2vec, pm1_list, label='Cf') ax1.plot(s2vec, pm0_list, label='no Cf') ax1.legend() ax1.set_ylabel('$\phi_{PM}$ (deg)') ax2.plot(s2vec, np.array(f1_list) * 1e-9, label='Cf') ax2.plot(s2vec, np.array(f0_list) * 1e-9, label='no Cf') ax2.legend() ax2.set_ylabel('$f_{UG}$ (GHz)') ax2.set_xlabel('$I_2/I_1$') plt.show()
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 _make_circuit(cls, env_idx, gm_db, load_db, vtail, vg, vmid, vout, vbias, vb_gm, vb_load, cload, cpar1, w_dict, th_dict, stack_dict, seg_dict, gz, neg_cap=False, no_fb=False): cur_env = gm_db.env_list[env_idx] gm_db.set_dsn_params(w=w_dict['tail'], intent=th_dict['tail'], stack=stack_dict['tail']) tail1_params = gm_db.query(env=cur_env, vbs=0, vds=vtail - vb_gm, vgs=vbias - vb_gm) tail2_params = gm_db.query(env=cur_env, vbs=0, vds=vout - vb_gm, vgs=vbias - vb_gm) gm_db.set_dsn_params(w=w_dict['in'], intent=th_dict['in'], stack=stack_dict['in']) gm1_params = gm_db.query(env=cur_env, vbs=vb_gm - vtail, vds=vmid - vtail, vgs=vg - vtail) load_db.set_dsn_params(w=w_dict['load'], intent=th_dict['load'], stack=stack_dict['diode']) diode1_params = load_db.query(env=cur_env, vbs=0, vds=vmid - vb_load, vgs=vmid - vb_load) diode2_params = load_db.query(env=cur_env, vbs=0, vds=vout - vb_load, vgs=vmid - vb_load) load_db.set_dsn_params(stack=stack_dict['ngm']) ngm1_params = load_db.query(env=cur_env, vbs=0, vds=vmid - vb_load, vgs=vmid - vb_load) ngm2_params = load_db.query(env=cur_env, vbs=0, vds=vout - vb_load, vgs=vmid - vb_load) cir = LTICircuit() # stage 1 cir.add_transistor(tail1_params, 'tail', 'gnd', 'gnd', 'gnd', fg=seg_dict['tail1'], neg_cap=neg_cap) cir.add_transistor(gm1_params, 'midp', 'inn', 'tail', 'gnd', fg=seg_dict['in'], neg_cap=neg_cap) cir.add_transistor(gm1_params, 'midn', 'inp', 'tail', 'gnd', fg=seg_dict['in'], neg_cap=neg_cap) cir.add_transistor(diode1_params, 'midp', 'midp', 'gnd', 'gnd', fg=seg_dict['diode1'], neg_cap=neg_cap) cir.add_transistor(diode1_params, 'midn', 'midn', 'gnd', 'gnd', fg=seg_dict['diode1'], neg_cap=neg_cap) cir.add_transistor(ngm1_params, 'midn', 'midp', 'gnd', 'gnd', fg=seg_dict['ngm1'], neg_cap=neg_cap) cir.add_transistor(ngm1_params, 'midp', 'midn', 'gnd', 'gnd', fg=seg_dict['ngm1'], neg_cap=neg_cap) # stage 2 cir.add_transistor(tail2_params, 'outp', 'gnd', 'gnd', 'gnd', fg=seg_dict['tail2'], neg_cap=neg_cap) cir.add_transistor(tail2_params, 'outn', 'gnd', 'gnd', 'gnd', fg=seg_dict['tail2'], neg_cap=neg_cap) cir.add_transistor(diode2_params, 'outp', 'midn', 'gnd', 'gnd', fg=seg_dict['diode2'], neg_cap=neg_cap) cir.add_transistor(diode2_params, 'outn', 'midp', 'gnd', 'gnd', fg=seg_dict['diode2'], neg_cap=neg_cap) cir.add_transistor(ngm2_params, 'outp', 'midn', 'gnd', 'gnd', fg=seg_dict['ngm2'], neg_cap=neg_cap) cir.add_transistor(ngm2_params, 'outn', 'midp', 'gnd', 'gnd', fg=seg_dict['ngm2'], neg_cap=neg_cap) # parasitic cap cir.add_cap(cpar1, 'midp', 'gnd') cir.add_cap(cpar1, 'midn', 'gnd') # load cap cir.add_cap(cload, 'outp', 'gnd') cir.add_cap(cload, 'outn', 'gnd') # feedback resistors if not no_fb: cir.add_conductance(gz, 'xp', 'midn') cir.add_conductance(gz, 'xn', 'midp') # diff-to-single conversion cir.add_vcvs(0.5, 'inp', 'gnd', 'in', 'gnd') cir.add_vcvs(-0.5, 'inn', 'gnd', 'in', 'gnd') cir.add_vcvs(1, 'out', 'gnd', 'outp', 'outn') return cir
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)