Exemple #1
0
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)
Exemple #2
0
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()
Exemple #3
0
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
Exemple #5
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
Exemple #6
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']
    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)