Пример #1
0
    def _get_psrr_lti(self, op_dict, nf_dict, ser_type, amp_in, cload, cdecap_amp, rsource) -> float:
        '''
        Returns:
            psrr: PSRR (dB)
            fbw: Power supply -> output 3dB bandwidth (Hz)
        '''
        n_ser = ser_type == 'n'
        n_amp = amp_in == 'n'

        # Supply -> output gain
        ckt_sup = LTICircuit()
        ser_d = 'vdd' if n_ser else 'reg'
        ser_s = 'reg' if n_ser else 'vdd'
        inp_conn = 'gnd' if n_ser else 'reg'
        inn_conn = 'reg' if n_ser else 'gnd'
        tail_rail = 'gnd' if n_amp else 'vdd'
        load_rail = 'vdd' if n_amp else 'gnd'

        # Passives
        if rsource != 0:
            ckt_sup.add_res(rsource, 'vbat', 'vdd')
        ckt_sup.add_cap(cload, 'reg', 'gnd')
        ckt_sup.add_cap(cdecap_amp, 'out', 'reg')

        # Series device
        ckt_sup.add_transistor(op_dict['ser'], ser_d, 'out', ser_s, fg=nf_dict['ser'], neg_cap=False)

        # Amplifier
        ckt_sup.add_transistor(op_dict['amp_in'], 'outx', inp_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False)
        ckt_sup.add_transistor(op_dict['amp_in'], 'out', inn_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False)
        ckt_sup.add_transistor(op_dict['amp_tail'], 'tail', 'gnd', tail_rail, fg=nf_dict['amp_tail'], neg_cap=False)
        ckt_sup.add_transistor(op_dict['amp_load'], 'outx', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False)
        ckt_sup.add_transistor(op_dict['amp_load'], 'out', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False)

        if rsource == 0:
            num_sup, den_sup = ckt_sup.get_num_den(in_name='vdd', out_name='reg', in_type='v')
        else:
            num_sup, den_sup = ckt_sup.get_num_den(in_name='vbat', out_name='reg', in_type='v')
        gain_sup = num_sup[-1]/den_sup[-1]
        wbw_sup = get_w_3db(den_sup, num_sup)

        if gain_sup == 0:
            return float('inf')
        if wbw_sup == None:
            wbw_sup = 0
        fbw_sup = wbw_sup / (2*np.pi)

        psrr = 10*np.log10((1/gain_sup)**2)

        return psrr, fbw_sup
Пример #2
0
    def make_ltickt(self, op_dict: Mapping[str, Any], nf_dict: Mapping[str,
                                                                       int],
                    cload: float, rload: float, meas_side: str) -> LTICircuit:
        ckt = LTICircuit()
        inp_conn = 'gnd' if meas_side == 'n' else 'inp'
        inn_conn = 'gnd' if meas_side == 'p' else 'inn'

        ckt.add_transistor(op_dict['main_in'],
                           'main_outn',
                           inp_conn,
                           'main_tail',
                           fg=nf_dict['main_in'])
        ckt.add_transistor(op_dict['main_in'],
                           'main_outp',
                           inn_conn,
                           'main_tail',
                           fg=nf_dict['main_in'])
        # ckt.add_transistor(op_dict['main_tail'], 'main_tail', 'cmfb_outp', 'gnd', fg=nf_dict['main_tail'])
        ckt.add_transistor(op_dict['main_tail'],
                           'main_tail',
                           'gnd',
                           'gnd',
                           fg=nf_dict['main_tail'])
        ckt.add_res(rload, 'main_outn', 'gnd')
        ckt.add_res(rload, 'main_outp', 'gnd')

        ckt.add_cap(cload, 'main_outn', 'gnd')
        ckt.add_cap(cload, 'main_outp', 'gnd')

        # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_out1n', 'gnd', 'cmfb_tail', fg=nf_dict['cmfb_in']*2)
        # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_out1p', 'main_outn', 'cmfb_tail', fg=nf_dict['cmfb_in'])
        # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_out1p', 'main_outp', 'cmfb_tail', fg=nf_dict['cmfb_in'])
        # ckt.add_transistor(op_dict['cmfb_tail'], 'cmfb_tail', 'gnd', 'gnd', fg=nf_dict['cmfb_tail'])
        # ckt.add_transistor(op_dict['cmfb_load'], 'cmfb_out1n', 'cmfb_out1n', 'gnd', fg=nf_dict['cmfb_load'])
        # ckt.add_transistor(op_dict['cmfb_load'], 'cmfb_out1p', 'cmfb_out1p', 'gnd', fg=nf_dict['cmfb_load'])
        # ckt.add_transistor(op_dict['cmfb_load_copy'], 'cmfb_outp', 'cmfb_out1n', 'gnd', fg=nf_dict['cmfb_load_copy'])
        # ckt.add_transistor(op_dict['cmfb_load_copy'], 'cmfb_outn', 'cmfb_out1p', 'gnd', fg=nf_dict['cmfb_load_copy'])
        # ckt.add_transistor(op_dict['cmfb_out'], 'cmfb_outp', 'cmfb_outp', 'gnd', fg=nf_dict['cmfb_out'])
        # ckt.add_transistor(op_dict['cmfb_out'], 'cmfb_outn', 'cmfb_outn', 'gnd', fg=nf_dict['cmfb_out'])

        # assert False, 'blep'

        # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_outn', 'gnd', 'cmfb_tail', fg=nf_dict['cmfb_in']*2, neg_cap=False)
        # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_outp', 'main_outn', 'cmfb_tail', fg=nf_dict['cmfb_in'], neg_cap=False)
        # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_outp', 'main_outp', 'cmfb_tail', fg=nf_dict['cmfb_in'], neg_cap=False)
        # ckt.add_transistor(op_dict['cmfb_tail'], 'cmfb_tail', 'gnd', 'gnd', fg=nf_dict['cmfb_tail'], neg_cap=False)
        # ckt.add_transistor(op_dict['cmfb_out'], 'cmfb_outn', 'cmfb_outn', 'gnd', fg=nf_dict['cmfb_out'], neg_cap=False)
        # ckt.add_transistor(op_dict['cmfb_out'], 'cmfb_outp', 'cmfb_outp', 'gnd', fg=nf_dict['cmfb_out'], neg_cap=False)

        return ckt
Пример #3
0
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()
Пример #4
0
    def _get_loadreg_lti(self, op_dict, nf_dict, ser_type, amp_in, cload, cdecap_amp, rsource, vout, iout) -> float:
        '''
        Returns:
            loadreg: Load regulation for peak-to-peak load current variation of 20% (V/V)
        '''
        n_ser = ser_type == 'n'
        n_amp = amp_in == 'n'
        vdd = 'vdd' if rsource != 0 else 'gnd'

        # Supply -> output gain
        ckt = LTICircuit()
        ser_d = vdd if n_ser else 'reg'
        ser_s = 'reg' if n_ser else vdd
        inp_conn = 'gnd' if n_ser else 'reg'
        inn_conn = 'reg' if n_ser else 'gnd'
        tail_rail = 'gnd' if n_amp else vdd
        load_rail = vdd if n_amp else 'gnd'

        # Passives
        if rsource != 0:
            ckt.add_res(rsource, 'gnd', 'vdd')
        ckt.add_cap(cload, 'reg', 'gnd')
        ckt.add_cap(cdecap_amp, 'out', 'reg')

        # Series device
        ckt.add_transistor(op_dict['ser'], ser_d, 'out', ser_s, fg=nf_dict['ser'], neg_cap=False)

        # Amplifier
        ckt.add_transistor(op_dict['amp_in'], 'outx', inp_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_in'], 'out', inn_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_tail'], 'tail', 'gnd', tail_rail, fg=nf_dict['amp_tail'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_load'], 'outx', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_load'], 'out', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False)


        num, den = ckt.get_num_den(in_name='reg', out_name='reg', in_type='i')
        transimpedance = num[-1]/den[-1]

        loadreg = transimpedance*0.2*iout/vout

        return loadreg
Пример #5
0
def construct_feedback(n_op, p_op, tail_op,
    nf_n, nf_p, nf_tail,
    cload, cn=165e-15, cp=83e-15, r=70.71e3):
    '''
    Inputs:
        p/n/tail_op:    Operating point information for a given device.
                        NMOS (input), PMOS (active load), or tail device.
        nf_n/p/tail:    Integer. Number of minimum channel width/length
                        devices in parallel.
        cload:          Float. Load capacitance in farads.
        cn:             Float. Capacitance in farads attached to the negative input
                        terminal of the amplifier for the filter.
        cp:             Float. Capacitance in farads attached to the positive input
                        terminal of the amplifier for the filter.
        r:              Float. Resistance in ohms used external to the amplifier
                        for the filter.
    Outputs:
        Returns the LTICircuit constructed for closed-loop analysis of 
        the amplifier with the loop broken.
    '''
    ckt = LTICircuit()
    # Left side
    ckt.add_transistor(n_op, 'out_copy', 'inp', 'tail', fg=nf_n)
    ckt.add_transistor(p_op, 'out_copy', 'out_copy', 'gnd', fg=nf_p)
    
    # Right side
    ckt.add_transistor(n_op, 'out', 'inn', 'tail', fg=nf_n)
    ckt.add_transistor(p_op, 'out', 'out_copy', 'gnd', fg=nf_p)
    
    # Tail
    ckt.add_transistor(tail_op, 'tail', 'gnd', 'gnd', fg=nf_tail)
    
    # Adding additional passives
    ckt.add_cap(cload, 'out', 'gnd')
    ckt.add_cap(cn, 'inn', 'rmid')
    ckt.add_cap(cp, 'inp', 'gnd')
    ckt.add_res(r, 'in', 'rmid')
    ckt.add_res(r, 'rmid', 'inp')
    
    return ckt
Пример #6
0
    def _get_stb_lti(self, op_dict, nf_dict, ser_type, amp_in, cload, cdecap_amp, rsource) -> float:
        '''
        Returns:
            pm: Phase margin (degrees)
        '''
        ckt = LTICircuit()

        n_ser = ser_type == 'n'
        n_amp = amp_in == 'n'
        vdd = 'vdd' if rsource != 0 else 'gnd'
       
        # Series device
        ser_d = vdd if n_ser else 'reg'
        ser_s = 'reg' if n_ser else vdd
        ckt.add_transistor(op_dict['ser'], ser_d, 'out', ser_s, fg=nf_dict['ser'], neg_cap=False)

        # Passives
        ckt.add_cap(cload, 'reg', 'gnd')
        ckt.add_cap(cdecap_amp, 'out', 'reg')
        if rsource != 0:
            ckt.add_res(rsource, 'gnd', 'vdd')

        # Amplifier
        tail_rail = 'gnd' if n_amp else vdd
        load_rail = vdd if n_amp else 'gnd'
        inp_conn = 'gnd' if n_ser else 'amp_in'
        inn_conn = 'gnd' if not n_ser else 'amp_in' 
        ckt.add_transistor(op_dict['amp_in'], 'outx', inp_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_in'], 'out', inn_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_tail'], 'tail', 'gnd', tail_rail, fg=nf_dict['amp_tail'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_load'], 'outx', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_load'], 'out', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False)

        # Calculating stability margins
        num, den = ckt.get_num_den(in_name='amp_in', out_name='reg', in_type='v')
        pm, _ = get_stability_margins(np.convolve(num, [-1]), den)

        return pm
Пример #7
0
    def _get_loopgain_lti(self, op_dict, nf_dict, ser_type, amp_in, rsource) -> float:
        '''
        Returns:
            A: DC loop gain
        '''
        ckt = LTICircuit()

        n_ser = ser_type == 'n'
        n_amp = amp_in == 'n'
        vdd = 'vdd' if rsource != 0 else 'gnd'
       
        # Series device
        ser_d = vdd if n_ser else 'reg'
        ser_s = 'reg' if n_ser else vdd
        ckt.add_transistor(op_dict['ser'], ser_d, 'out', ser_s, fg=nf_dict['ser'], neg_cap=True)

        # Passives
        if rsource != 0:
            ckt.add_res(rsource, 'gnd', 'vdd')

        # Amplifier
        tail_rail = 'gnd' if n_amp else vdd
        load_rail = vdd if n_amp else 'gnd'
        inp_conn = 'gnd' if n_ser else 'amp_in'
        inn_conn = 'gnd' if not n_ser else 'amp_in' 
        ckt.add_transistor(op_dict['amp_in'], 'outx', inp_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_in'], 'out', inn_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_tail'], 'tail', 'gnd', tail_rail, fg=nf_dict['amp_tail'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_load'], 'outx', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False)
        ckt.add_transistor(op_dict['amp_load'], 'out', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False)

        # Calculating stability margins
        num, den = ckt.get_num_den(in_name='amp_in', out_name='reg', in_type='v')
        A = num[-1]/den[-1]

        return A
Пример #8
0
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
Пример #9
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
Пример #10
0
    def _get_stb_lti(self, op_dict, nf_dict, series_type, rload,
                     cload) -> float:
        '''
        Returns:
            pm: Phase margins (in degrees)
        '''
        ckt = LTICircuit()

        n_ser = series_type == 'n'

        # Series device
        ser_d = 'gnd' if n_ser else 'reg'
        ser_s = 'reg' if n_ser else 'gnd'
        ckt.add_transistor(op_dict['ser'],
                           ser_d,
                           'out',
                           ser_s,
                           fg=nf_dict['ser'],
                           neg_cap=False)

        # Load passives
        ckt.add_res(rload, 'reg', 'gnd')
        ckt.add_cap(rload, 'reg', 'gnd')
        # TODO include any compensation passives

        # Amplifier
        inp_conn = 'gnd' if n_ser else 'in'
        inn_conn = 'gnd' if not n_ser else 'in'
        ckt.add_transistor(op_dict['in'],
                           'outx',
                           inp_conn,
                           'tail',
                           fg=nf_dict['in'],
                           neg_cap=False)
        ckt.add_transistor(op_dict['in'],
                           'out',
                           inn_conn,
                           'tail',
                           fg=nf_dict['in'],
                           neg_cap=False)
        ckt.add_transistor(op_dict['tail'],
                           'tail',
                           'gnd',
                           'gnd',
                           fg=nf_dict['tail'],
                           neg_cap=False)
        ckt.add_transistor(op_dict['load'],
                           'outx',
                           'outx',
                           'gnd',
                           fg=nf_dict['load'],
                           neg_cap=False)
        ckt.add_transistor(op_dict['load'],
                           'out',
                           'outx',
                           'gnd',
                           fg=nf_dict['load'],
                           neg_cap=False)

        # Calculating stability margins
        num, den = ckt.get_num_den(in_name='in', out_name='reg', in_type='v')
        pm, _ = get_stability_margins(np.convolve(num, [-1]), den)

        return pm
Пример #11
0
def design_preamp_string(db_n,
                         lch,
                         w_finger,
                         vdd,
                         cload,
                         vin_signal,
                         vin_shift,
                         voutcm,
                         vtail_res,
                         gain_min,
                         fbw_min,
                         rload_res,
                         vb_n,
                         itail_max=10e-6,
                         error_tol=.01):
    """
    Designs a preamplifier to go between the comparator and TIA, stringing them
    together.
    Inputs:
    Outputs:
    """
    # Estimate vth for use in calculating biasing ranges
    vthn = estimate_vth(db_n, vdd, 0)
    vincm = (vin_signal + vin_shift) / 2

    vtail_min = .15  # TODO: Avoid hardcoding?
    vtail_max = vin_signal - vthn
    vtail_vec = np.arange(vtail_min, vtail_max, vtail_res)

    best_op = None
    best_itail = np.inf

    for vtail in vtail_vec:
        op_tail = db_n.query(vgs=voutcm, vds=vtail, vbs=vb_n)
        # The "mean" of the input devices
        op_in = db_n.query(vgs=vincm - vtail,
                           vds=voutcm - vtail,
                           vbs=vb_n - vtail)

        # Calculate the max tail size based on current limit
        # and size input devices accordingly
        nf_in_min = 2
        nf_in_max = int(round(itail_max / 2 / op_in['ibias']))
        nf_in_vec = np.arange(nf_in_min, nf_in_max, 1)
        for nf_in in nf_in_vec:
            amp_ratio_good, nf_tail = verify_ratio(op_in['ibias'] / 2,
                                                   op_tail['ibias'], nf_in,
                                                   error_tol)
            if not amp_ratio_good:
                continue

            itail = op_tail['ibias'] * nf_tail
            rload = (vdd - voutcm) / (itail / 2)

            # Input devices too small
            if op_in['gm'] * nf_in * rload < gain_min:
                continue

            # Check small signal parameters with symmetric circuit
            ckt_sym = LTICircuit()
            ckt_sym.add_transistor(op_in, 'outp', 'gnd', 'tail', fg=nf_in)
            ckt_sym.add_transistor(op_in, 'outn', 'inp', 'tail', fg=nf_in)
            ckt_sym.add_transistor(op_tail, 'tail', 'gnd', 'gnd', fg=nf_tail)
            ckt_sym.add_res(rload, 'outp', 'gnd')
            ckt_sym.add_res(rload, 'outn', 'gnd')
            ckt_sym.add_cap(cload, 'outp', 'gnd')
            ckt_sym.add_cap(cload, 'outn', 'gnd')
            num, den = ckt_sym.get_num_den(in_name='inp',
                                           out_name='outn',
                                           in_type='v')
            num_unintent, den_unintent = ckt_sym.get_num_den(in_name='inp',
                                                             out_name='outp',
                                                             in_type='v')
            gain_intentional = num[-1] / den[-1]
            gain_unintentional = num_unintent[-1] / den_unintent[-1]
            gain = abs(gain_intentional - gain_unintentional)
            wbw = get_w_3db(num, den)
            if wbw == None:
                wbw = 0
            fbw = wbw / (2 * np.pi)
            if fbw < fbw_min:
                print("(FAIL) BW:\t{0}".format(fbw))
                continue
            if gain < gain_min:
                print("(FAIL) GAIN:\t{0}".format(gain))
                continue

            print("(SUCCESS)")
            if itail > best_itail:
                continue

            # Check once again with asymmetric circuit
            vin_diff = vin_signal - vin_shift
            voutn = voutcm - gain * vin_diff / 2
            voutp = voutcm + gain * vin_diff / 2
            op_signal = db_n.query(vgs=vin_signal - vtail,
                                   vds=voutn - vtail,
                                   vbs=vb_n - vtail)
            op_shift = db_n.query(vgs=vin_shift - vtail,
                                  vds=voutp - vtail,
                                  vbs=vb_n - vtail)
            ckt = LTICircuit()
            ckt.add_transistor(op_shift, 'outp', 'gnd', 'tail', fg=nf_in)
            ckt.add_transistor(op_signal, 'outn', 'inp', 'tail', fg=nf_in)
            ckt.add_transistor(op_tail, 'tail', 'gnd', 'gnd', fg=nf_tail)
            ckt.add_res(rload, 'outp', 'gnd')
            ckt.add_res(rload, 'outn', 'gnd')
            ckt.add_cap(cload, 'outp', 'gnd')
            ckt.add_cap(cload, 'outn', 'gnd')
            num, den = ckt.get_num_den(in_name='inp',
                                       out_name='outn',
                                       in_type='v')
            num_unintent, den_unintent = ckt.get_num_den(in_name='inp',
                                                         out_name='outp',
                                                         in_type='v')
            gain_intentional = num[-1] / den[-1]
            gain_unintentional = num_unintent[-1] / den_unintent[-1]
            gain = abs(gain_intentional - gain_unintentional)
            wbw = get_w_3db(num, den)
            if wbw == None:
                wbw = 0
            fbw = wbw / (2 * np.pi)
            if fbw < fbw_min:
                print("(FAIL) BW:\t{0}".format(fbw))
                continue
            if gain < gain_min:
                print("(FAIL) GAIN:\t{0}".format(gain))
                continue

            print("(SUCCESS)")
            if itail < best_itail:
                best_itail = itail
                best_op = dict(nf_in=nf_in,
                               nf_tail=nf_tail,
                               itail=itail,
                               rload=rload,
                               gain=gain,
                               fbw=fbw,
                               voutcm=voutcm,
                               vtail=vtail,
                               cin=op_signal['cgg'] * nf_in)
    if best_op == None:
        raise ValueError("No viable solutions.")
    return best_op
Пример #12
0
def characterize_casc_amp(env_list,
                          fg_list,
                          w_list,
                          db_list,
                          vbias_list,
                          vload_list,
                          vtail_list,
                          vmid_list,
                          vcm,
                          vdd,
                          vin_max,
                          cw,
                          rw,
                          fanout,
                          ton,
                          k_settle_targ,
                          verr_max,
                          scale_res=0.1,
                          scale_min=0.25,
                          scale_max=20):
    # compute DC transfer function curve and compute linearity spec
    results = solve_casc_diff_dc(env_list,
                                 db_list,
                                 w_list,
                                 fg_list,
                                 vbias_list,
                                 vload_list,
                                 vtail_list,
                                 vmid_list,
                                 vdd,
                                 vcm,
                                 vin_max,
                                 verr_max,
                                 num_points=20)

    vin_vec, vmat_list, verr_list, gain_list = results

    # compute settling ratio
    fg_in, fg_casc, fg_load = fg_list[1:]
    db_in, db_casc, db_load = db_list[1:]
    w_in, w_casc, w_load = w_list[1:]
    fzin = 1.0 / (2 * ton)
    wzin = 2 * np.pi * fzin
    tvec = np.linspace(0, ton, 200, endpoint=True)
    scale_list = []
    for env, vload, vtail, vmid in zip(env_list, vload_list, vtail_list,
                                       vmid_list):
        # step 1: construct half circuit
        in_params = db_in.query(env=env,
                                w=w_in,
                                vbs=-vtail,
                                vds=vmid - vtail,
                                vgs=vcm - vtail)
        casc_params = db_casc.query(env=env,
                                    w=w_casc,
                                    vbs=-vmid,
                                    vds=vcm - vmid,
                                    vgs=vdd - vmid)
        load_params = db_load.query(env=env,
                                    w=w_load,
                                    vbs=0,
                                    vds=vcm - vdd,
                                    vgs=vload - vdd)
        circuit = LTICircuit()
        circuit.add_transistor(in_params, 'mid', 'in', 'gnd', fg=fg_in)
        circuit.add_transistor(casc_params, 'd', 'gnd', 'mid', fg=fg_casc)
        circuit.add_transistor(load_params, 'd', 'gnd', 'gnd', fg=fg_load)
        # step 2: get input capacitance
        zin = circuit.get_impedance('in', fzin)
        cin = (1 / zin).imag / wzin
        circuit.add_cap(cin * fanout, 'out', 'gnd')
        # step 3: find scale factor to achieve k_settle
        bin_iter = BinaryIterator(scale_min,
                                  None,
                                  step=scale_res,
                                  is_float=True)
        while bin_iter.has_next():
            # add scaled wired parasitics
            cur_scale = bin_iter.get_next()
            cap_cur = cw / 2 / cur_scale
            res_cur = rw * cur_scale
            circuit.add_cap(cap_cur, 'd', 'gnd')
            circuit.add_cap(cap_cur, 'out', 'gnd')
            circuit.add_res(res_cur, 'd', 'out')
            # get settling factor
            sys = circuit.get_voltage_gain_system('in', 'out')
            dc_gain = sys.freqresp(w=np.array([0.1]))[1][0]
            sgn = 1 if dc_gain.real >= 0 else -1
            dc_gain = abs(dc_gain)
            _, yvec = scipy.signal.step(
                sys, T=tvec)  # type: Tuple[np.ndarray, np.ndarray]
            k_settle_cur = 1 - abs(yvec[-1] - sgn * dc_gain) / dc_gain
            print('scale = %.4g, k_settle = %.4g' % (cur_scale, k_settle_cur))
            # update next scale factor
            if k_settle_cur >= k_settle_targ:
                print('save scale = %.4g' % cur_scale)
                bin_iter.save()
                bin_iter.down()
            else:
                if cur_scale > scale_max:
                    raise ValueError(
                        'cannot meet settling time spec at scale = %d' %
                        cur_scale)
                bin_iter.up()
            # remove wire parasitics
            circuit.add_cap(-cap_cur, 'd', 'gnd')
            circuit.add_cap(-cap_cur, 'out', 'gnd')
            circuit.add_res(-res_cur, 'd', 'out')
        scale_list.append(bin_iter.get_last_save())

    return vmat_list, verr_list, gain_list, scale_list
Пример #13
0
                itrim = 0
            else:
                vos_base = idiff_trim_base*rload
                nf_trim = ceil(vos_targ/vos_base)
                itrim = (op_trim_on['ibias']+op_trim_off['ibias'])*nf_trim

            # LTICircuit simulation
            circuit = LTICircuit()
            circuit.add_transistor(op_in, 'outp', 'gnd', 'tail', fg=nf_in)
            circuit.add_transistor(op_in, 'outn', 'inp', 'tail', fg=nf_in)
            circuit.add_transistor(op_trim_on, 'outn', 'gnd', 'gnd', fg=nf_trim)
            circuit.add_transistor(op_trim_off, 'outp', 'gnd', 'gnd', fg=nf_trim)
            circuit.add_transistor(op_tail, 'tail', 'vmirr', 'gnd', fg=nf_tail)
            circuit.add_transistor(op_mirr, 'vmirr', 'vmirr', 'gnd', fg=nf_mirr)
            circuit.add_transistor(op_dummy, 'gnd', 'gnd', 'vmirr', fg=nf_dummy)
            circuit.add_res(rload, 'outp', 'gnd')
            circuit.add_res(rload, 'outn', 'gnd')
            circuit.add_cap(cload, 'outp', 'gnd')
            circuit.add_cap(cload, 'outn', 'gnd')
            circuit.add_cap(dac_cap, 'tail', 'gnd')
            num, den = circuit.get_num_den(in_name='inp', out_name='outn', in_type='v')
            num_unintentional, den_unintentional = circuit.get_num_den(in_name='inp', out_name='outp', in_type='v')
            gain_intentional = num[-1]/den[-1]
            gain_unintentional = num_unintentional[-1]/den_unintentional[-1]
            gain = abs(gain_intentional-gain_unintentional)
            wbw = get_w_3db(num, den)
            if wbw == None:
                wbw = 0
            fbw = wbw/(2*np.pi)
            if fbw < bw_min:
                cond_print("Bandwidth fail: {}".format(fbw), debugMode)
Пример #14
0
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)
Пример #15
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
Пример #16
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
Пример #17
0
    def _get_psrr_lti(self, op_dict, nf_dict, series_type, amp_in, rload,
                      cload) -> float:
        '''
        Outputs:
            psrr: PSRR (dB)
            fbw: Power supply -> output 3dB bandwidth (Hz)
        '''
        n_ser = series_type == 'n'
        n_amp = amp_in == 'n'

        # Supply -> output gain
        ckt_sup = LTICircuit()
        ser_d = 'vdd' if n_ser else 'reg'
        ser_s = 'reg' if n_ser else 'vdd'
        inp_conn = 'gnd' if n_ser else 'reg'
        inn_conn = 'reg' if n_ser else 'gnd'
        tail_rail = 'gnd' if n_amp else 'vdd'
        load_rail = 'vdd' if n_amp else 'gnd'
        ckt_sup.add_transistor(op_dict['ser'],
                               ser_d,
                               'out',
                               ser_s,
                               fg=nf_dict['ser'],
                               neg_cap=False)
        ckt_sup.add_res(rload, 'reg', 'gnd')
        ckt_sup.add_cap(rload, 'reg', 'gnd')
        ckt_sup.add_transistor(op_dict['in'],
                               'outx',
                               inp_conn,
                               'tail',
                               fg=nf_dict['in'],
                               neg_cap=False)
        ckt_sup.add_transistor(op_dict['in'],
                               'out',
                               inn_conn,
                               'tail',
                               fg=nf_dict['in'],
                               neg_cap=False)
        ckt_sup.add_transistor(op_dict['tail'],
                               'tail',
                               'gnd',
                               tail_rail,
                               fg=nf_dict['tail'],
                               neg_cap=False)
        ckt_sup.add_transistor(op_dict['load'],
                               'outx',
                               'outx',
                               load_rail,
                               fg=nf_dict['load'],
                               neg_cap=False)
        ckt_sup.add_transistor(op_dict['load'],
                               'out',
                               'outx',
                               load_rail,
                               fg=nf_dict['load'],
                               neg_cap=False)

        num_sup, den_sup = ckt_sup.get_num_den(in_name='vdd',
                                               out_name='reg',
                                               in_type='v')
        gain_sup = num_sup[-1] / den_sup[-1]
        wbw_sup = get_w_3db(num_sup, den_sup)

        # Reference -> output gain
        # ckt_norm = LTICircuit()
        # ser_d = 'gnd' if n_ser else 'reg'
        # ser_s = 'reg' if n_ser else 'gnd'
        # inp_conn = 'in' if n_ser else 'reg'
        # inn_conn = 'reg' if n_ser else 'in'
        # ckt_norm.add_transistor(op_dict['ser'], ser_d, 'out', ser_s, fg=nf_dict['ser'], neg_cap=False)
        # ckt_norm.add_res(rload, 'reg', 'gnd')
        # ckt_norm.add_cap(rload, 'reg', 'gnd')
        # ckt_norm.add_transistor(op_dict['in'], 'outx', inp_conn, 'tail', fg=nf_dict['in'], neg_cap=False)
        # ckt_norm.add_transistor(op_dict['in'], 'out', inn_conn, 'tail', fg=nf_dict['in'], neg_cap=False)
        # ckt_norm.add_transistor(op_dict['tail'], 'tail', 'gnd', 'gnd', fg=nf_dict['tail'], neg_cap=False)
        # ckt_norm.add_transistor(op_dict['load'], 'outx', 'outx', 'gnd', fg=nf_dict['load'], neg_cap=False)
        # ckt_norm.add_transistor(op_dict['load'], 'out', 'outx', 'gnd', fg=nf_dict['load'], neg_cap=False)

        # num_norm, den_norm = ckt_norm.get_num_den(in_name='in', out_name='reg', in_type='v')
        # gain_norm = num_norm[-1]/den_norm[-1]

        if gain_sup == 0:
            return float('inf')
        if wbw_sup == None:
            wbw_sup = 0
        fbw_sup = wbw_sup / (2 * np.pi)

        psrr = 10 * np.log10((1 / gain_sup)**2)

        return psrr, fbw_sup
Пример #18
0
    def meet_spec(self, **params) -> List[Mapping[str, Any]]:
        """To be overridden by subclasses to design this module.

        Raises a ValueError if there is no solution.
        """
        ### Get DBs for each device
        specfile_dict = params['specfile_dict']
        in_type = params['in_type']
        l_dict = params['l_dict']
        th_dict = params['th_dict']
        sim_env = params['sim_env']

        # Databases
        db_dict = {
            k: get_mos_db(spec_file=specfile_dict[k],
                          intent=th_dict[k],
                          lch=l_dict[k],
                          sim_env=sim_env)
            for k in specfile_dict.keys()
        }

        ### Design devices
        vdd = params['vdd']
        vincm = params['vincm']
        cload = params['cload']
        gain_min, gain_max = params['gain_lim']
        fbw_min = params['fbw']
        ibias_max = params['ibias']
        optional_params = params['optional_params']

        # Pulling out optional parameters
        vstar_min = optional_params.get('vstar_min', 0.2)
        res_vstep = optional_params.get('res_vstep', 10e-3)
        error_tol = optional_params.get('error_tol', 0.01)

        n_in = in_type == 'n'

        # Estimate threshold of each device
        vtest = vdd / 2 if n_in else -vdd / 2
        vb = 0 if n_in else vdd

        vth_in = estimate_vth(is_nch=n_in,
                              vgs=vtest,
                              vbs=0,
                              db=db_dict['in'],
                              lch=l_dict['in'])
        vth_tail = estimate_vth(is_nch=n_in,
                                vgs=vtest,
                                vbs=0,
                                db=db_dict['tail'],
                                lch=l_dict['tail'])

        # Keeping track of operating points which work for future comparison
        viable_op_list = []

        # Sweep tail voltage
        vtail_min = vstar_min if n_in else vincm - vth_in
        vtail_max = vincm - vth_in if n_in else vdd - vstar_min
        vtail_vec = np.arange(vtail_min, vtail_max, res_vstep)
        print(f'Sweeping tail from {vtail_min} to {vtail_max}')
        for vtail in vtail_vec:
            voutcm_min = vincm - vth_in if n_in else 0
            voutcm_max = vdd if n_in else vincm - vth_in

            voutcm = optional_params.get('voutcm', None)

            if voutcm == None:
                voutcm_vec = np.arange(voutcm_min, voutcm_max, res_vstep)
            else:
                voutcm_vec = [voutcm]
            # Sweep output common mode
            for voutcm in voutcm_vec:
                in_op = db_dict['in'].query(vgs=vincm - vtail,
                                            vds=voutcm - vtail,
                                            vbs=vb - vtail)
                ibias_min = 2 * in_op['ibias']
                # Step input device size (integer steps)
                nf_in_max = int(round(ibias_max / ibias_min))
                nf_in_vec = np.arange(2, nf_in_max, 2)
                for nf_in in nf_in_vec:
                    ibias = ibias_min * nf_in
                    ibranch = ibias / 2
                    res_val = (vdd -
                               voutcm) / ibranch if n_in else voutcm / ibranch
                    # Check gain, bandwidth
                    ckt_half = LTICircuit()
                    ckt_half.add_transistor(in_op,
                                            'out',
                                            'in',
                                            'gnd',
                                            fg=nf_in,
                                            neg_cap=False)
                    ckt_half.add_res(res_val, 'out', 'gnd')
                    ckt_half.add_cap(cload, 'out', 'gnd')

                    num, den = ckt_half.get_num_den(in_name='in',
                                                    out_name='out',
                                                    in_type='v')
                    gain = -(num[-1] / den[-1])
                    wbw = get_w_3db(num, den)
                    if wbw == None:
                        wbw = 0
                    fbw = wbw / (2 * np.pi)

                    if gain < gain_min or gain > gain_max:
                        print(f'gain: {gain}')
                        break

                    if fbw < fbw_min:
                        print(f'fbw: {fbw}')
                        continue

                    # Design tail to current match
                    vgtail_min = vth_tail + vstar_min if n_in else vtail + vth_tail
                    vgtail_max = vtail + vth_tail if n_in else vdd + vth_tail - vstar_min
                    vgtail_vec = np.arange(vgtail_min, vgtail_max, res_vstep)
                    print(f'vgtail {vgtail_min} to {vgtail_max}')
                    for vgtail in vgtail_vec:
                        tail_op = db_dict['tail'].query(vgs=vgtail - vb,
                                                        vds=vtail - vb,
                                                        vbs=0)
                        tail_success, nf_tail = verify_ratio(
                            in_op['ibias'] * 2, tail_op['ibias'], nf_in,
                            error_tol)
                        if not tail_success:
                            print('tail match')
                            continue

                        viable_op = dict(nf_in=nf_in,
                                         nf_tail=nf_tail,
                                         res_val=res_val,
                                         voutcm=voutcm,
                                         vgtail=vgtail,
                                         gain=gain,
                                         fbw=fbw,
                                         vtail=vtail,
                                         ibias=tail_op['ibias'] * nf_tail,
                                         cmfb_cload=tail_op['cgg'] * nf_tail)
                        viable_op_list.append(viable_op)
                        print("\n(SUCCESS)")
                        print(viable_op)

        self.other_params = dict(
            in_type=in_type,
            l_dict=l_dict,
            w_dict={k: db.width_list[0]
                    for k, db in db_dict.items()},
            th_dict=th_dict)

        # assert False, 'blep'

        return viable_op_list
Пример #19
0
def design_preamp(db_n,
                  db_p,
                  lch,
                  w_finger,
                  vdd,
                  cload,
                  vin_signal,
                  vin_shift,
                  voutcm_res,
                  vtail_res,
                  gain_min,
                  fbw_min,
                  rload_res,
                  vb_p,
                  vb_n,
                  itail_max=10e-6,
                  error_tol=.01):
    """
    Designs a preamplifier to go between the comparator and TIA.
    Inputs:
        db_n/p:     Database with NMOS/PMOS info
        lch:        Float. Channel length (m)
        w_finger:   Float. Width of a single finger (m)
        vdd:        Float. Supply voltage (V)
        cload:      Float. Load capacitance (F)
        vin_signal: Float. Input bias voltage (V).
        vin_shift:  Float. Gate bias voltage for the non-signal-facing input
                    device (V).
        voutcm_res: Float. Resolution for output common mode voltage sweep
                    (V).
        vtail_res:  Float. Resolution for tail voltage of amplifying
                    and offset devices for sweep (V).
        gain_min:   Float. Minimum gain (V/V)
        fbw_min:    Float. Minimum bandwidth (Hz)
        rload_res:  Float. Load resistor resolution for sweep (ohms)
        vb_n/p:     Float. Body/back-gate voltage (V) of NMOS and PMOS devices.
        itail_max:  Float. Max allowable tail current of the main amplifier (A).
        error_tol:  Float. Fractional tolerance for ibias error when computing
                    device ratios.
    Raises:
        ValueError if there is no solution given the requirements.
    Outputs:
        nf_in:          Integer. Number of fingers for input devices.
        nf_tail:        Integer. Number of fingers for tail device.
        itail:          Float. Tail current of main amp (A).
        rload:          Float. Load resistance (ohm).
        gain:           Float. Amp gain (V/V).
        fbw:            Float. Bandwidth (Hz).
        voutcm:         Float. Output common mode voltage (V).
        cin:            Float. Approximation of input cap (F).
    Notes:
        Under construction.
    """
    # Estimate vth for use in calculating biasing ranges
    vthn = estimate_vth(db_n, vdd, 0)
    vincm = (vin_signal + vin_shift) / 2

    voutcm_min = vin_signal - vthn
    voutcm_max = vdd
    voutcm_vec = np.arange(voutcm_min, voutcm_max, voutcm_res)

    vtail_min = .15  # TODO: Avoid hardcoding?
    vtail_max = vin_signal - vthn
    vtail_vec = np.arange(vtail_min, vtail_max, vtail_res)

    best_op = None
    best_itail = np.inf

    # Sweep output common mode voltage
    for voutcm in voutcm_vec:
        # Sweep tail voltage
        for vtail in vtail_vec:
            op_tail = db_n.query(vgs=voutcm, vds=vtail, vbs=vb_n)
            # The "mean" of the input devices
            op_in = db_n.query(vgs=vincm - vtail,
                               vds=voutcm - vtail,
                               vbs=vb_n - vtail)

            # Calculate the max tail size based on current limit
            # and size input devices accordingly
            nf_in_min = 2
            nf_in_max = int(round(itail_max / 2 / op_in['ibias']))
            nf_in_vec = np.arange(nf_in_min, nf_in_max, 2)
            for nf_in in nf_in_vec:
                amp_ratio_good, nf_tail = verify_ratio(op_in['ibias'] / 2,
                                                       op_tail['ibias'], nf_in,
                                                       error_tol)
                if not amp_ratio_good:
                    continue

                itail = op_tail['ibias'] * nf_tail
                rload = (vdd - voutcm) / (itail / 2)

                # Check small signal parameters with symmetric circuit
                ckt_sym = LTICircuit()
                ckt_sym.add_transistor(op_in, 'outp', 'gnd', 'tail', fg=nf_in)
                ckt_sym.add_transistor(op_in, 'outn', 'inp', 'tail', fg=nf_in)
                ckt_sym.add_transistor(op_tail,
                                       'tail',
                                       'gnd',
                                       'gnd',
                                       fg=nf_tail)
                ckt_sym.add_res(rload, 'outp', 'gnd')
                ckt_sym.add_res(rload, 'outn', 'gnd')
                ckt_sym.add_cap(cload, 'outp', 'gnd')
                ckt_sym.add_cap(cload, 'outn', 'gnd')
                num, den = ckt_sym.get_num_den(in_name='inp',
                                               out_name='outn',
                                               in_type='v')
                num_unintent, den_unintent = ckt_sym.get_num_den(
                    in_name='inp', out_name='outp', in_type='v')
                gain_intentional = num[-1] / den[-1]
                gain_unintentional = num_unintent[-1] / den_unintent[-1]
                gain = abs(gain_intentional - gain_unintentional)
                wbw = get_w_3db(num, den)
                if wbw == None:
                    wbw = 0
                fbw = wbw / (2 * np.pi)
                if fbw < fbw_min:
                    print("(FAIL) BW:\t{0}".format(fbw))
                    continue
                if gain < gain_min:
                    print("(FAIL) GAIN:\t{0}".format(gain))
                    continue

                print("(SUCCESS)")
                if itail > best_itail:
                    continue

                # Check once again with asymmetric circuit
                vin_diff = vin_signal - vin_shift
                voutn = voutcm - gain * vin_diff / 2
                voutp = voutcm + gain * vin_diff / 2
                op_signal = db_n.query(vgs=vin_signal - vtail,
                                       vds=voutn - vtail,
                                       vbs=vb_n - vtail)
                op_shift = db_n.query(vgs=vin_shift - vtail,
                                      vds=voutp - vtail,
                                      vbs=vb_n - vtail)
                ckt = LTICircuit()
                ckt.add_transistor(op_shift, 'outp', 'gnd', 'tail', fg=nf_in)
                ckt.add_transistor(op_signal, 'outn', 'inp', 'tail', fg=nf_in)
                ckt.add_transistor(op_tail, 'tail', 'gnd', 'gnd', fg=nf_tail)
                ckt.add_res(rload, 'outp', 'gnd')
                ckt.add_res(rload, 'outn', 'gnd')
                ckt.add_cap(cload, 'outp', 'gnd')
                ckt.add_cap(cload, 'outn', 'gnd')
                num, den = ckt.get_num_den(in_name='inp',
                                           out_name='outn',
                                           in_type='v')
                num_unintent, den_unintent = ckt.get_num_den(in_name='inp',
                                                             out_name='outp',
                                                             in_type='v')
                gain_intentional = num[-1] / den[-1]
                gain_unintentional = num_unintent[-1] / den_unintent[-1]
                gain = abs(gain_intentional - gain_unintentional)
                wbw = get_w_3db(num, den)
                if wbw == None:
                    wbw = 0
                fbw = wbw / (2 * np.pi)
                if fbw < fbw_min:
                    print("(FAIL) BW:\t{0}".format(fbw))
                    continue
                if gain < gain_min:
                    print("(FAIL) GAIN:\t{0}".format(gain))
                    continue

                print("(SUCCESS)")
                if itail < best_itail:
                    best_itail = itail
                    best_op = dict(nf_in=nf_in,
                                   nf_tail=nf_tail,
                                   itail=itail,
                                   rload=rload,
                                   gain=gain,
                                   fbw=fbw,
                                   voutcm=voutcm,
                                   vtail=vtail,
                                   cin=op_signal['cgg'] * nf_in)
    if best_op == None:
        raise ValueError("No viable solutions.")
    return best_op