Пример #1
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()
Пример #2
0
def construct_sfP_LTI(p_op, p_B_op, n_B_op, 
                    nf_sfP_p, nf_sfP_pB, nf_sfP_nB,
                    cload):
    '''
    Inputs:
        *_op:   Operating point information for a given device. A capital "B" 
                indicates a "bias" device, i.e. a device whose gm doesn't really
                factor into the final gain expression.
        nf_*:   Integer. Number of minimum channel width/length devices in parallel.
        cload:  Float. Load capacitance in farads.
    Outputs:
        Returns the LTICircuit constructed for the second-stage P-input 
        source follower.
    '''
    ckt = LTICircuit()
            ckt.add_transistor(p_op, 'tail_copy_sfP', 'out2', 'out', fg=nf_sfP_p)
            ckt.add_transistor(p_B_op, 'out', 'gnd', 'gnd', fg=nf_sfP_pB)
            ckt.add_transistor(n_B_op, 'tail_copy_sfP', 'gnd', 'gnd', fg=nf_sfP_nB)
            ckt.add_cap(cload, 'out', 'gnd')
Пример #3
0
            
            cond_print("ITAIL: {}".format(itail), debugMode)
            # cond_print("INPUT DEVICE CURRENT: {}".format(op_in['ibias']))
            
            # Include trimming devices
            idiff_trim_base = abs(op_trim_on['ibias']-op_trim_off['ibias'])
            if idiff_trim_base == 0:
                nf_trim = 1
                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]
Пример #4
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']
        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
        in_type = params['in_type']
        vdd = params['vdd']
        vincm = params['vincm']
        voutcm = params['voutcm']
        cload = params['cload']
        gain_min = params['gain']
        fbw_min = params['fbw']
        ugf_min = params['ugf']
        ibias_max = params['ibias']

        # Somewhat arbitrary vstar_min in this case
        optional_params = params['optional_params']
        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)
        vout1_opt = optional_params.get('vout1', None)

        n_in = in_type == 'n'
        vtest_in = vdd / 2 if n_in else -vdd / 2
        vtest_tail = vdd / 2 if n_in else -vdd / 2
        vtest_out = -vdd / 2 if n_in else vdd / 2

        vb_in = 0 if n_in else vdd
        vb_tail = 0 if n_in else vdd
        vb_load = vdd if n_in else 0
        vb_out = 0 if n_in else vdd

        # Estimate threshold of each device TODO can this be more generalized?
        vth_in = estimate_vth(is_nch=n_in,
                              vgs=vtest_in,
                              vbs=0,
                              db=db_dict['in'],
                              lch=l_dict['in'])
        vth_tail = estimate_vth(is_nch=n_in,
                                vgs=vtest_tail,
                                vbs=0,
                                db=db_dict['tail'],
                                lch=l_dict['tail'])
        vth_load = estimate_vth(is_nch=(not n_in),
                                vgs=vtest_out,
                                vbs=0,
                                db=db_dict['load'],
                                lch=l_dict['load'])
        vth_out = estimate_vth(is_nch=n_in,
                               vgs=vtest_in,
                               vbs=0,
                               db=db_dict['out'],
                               lch=l_dict['out'])

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

        # Get tail voltage range
        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}')

        # Get out1 common mode range
        vout1_min = vincm - vth_in if n_in else vth_load + vstar_min
        vout1_max = vdd + vth_load - vstar_min if n_in else vincm - vth_in
        if vout1_opt == None:
            vout1_vec = np.arange(vout1_min, vout1_max, res_vstep)
        else:
            vout1_vec = [vout1_opt]
            if vout1_opt < vout1_min or vout1_opt > vout1_max:
                warnings.warn(
                    f'vout11 {vout1_opt} falls outside recommended range ({vout1_min}, {vout1_max})'
                )

        # Sweep tail voltage
        for vtail in vtail_vec:
            # Sweep out1 common mode
            for vout1 in vout1_vec:
                in_op = db_dict['in'].query(vgs=vincm - vtail,
                                            vds=vout1 - vtail,
                                            vbs=vb_in - vtail)
                load_op = db_dict['load'].query(vgs=vout1 - vb_load,
                                                vds=vout1 - vb_load,
                                                vbs=0)
                load_copy_op = db_dict['load'].query(vgs=vout1 - vb_load,
                                                     vds=voutcm - vb_load,
                                                     vbs=0)
                out_op = db_dict['out'].query(vgs=voutcm - vb_out,
                                              vds=voutcm - vb_out,
                                              vbs=0)

                itail_min = 4 * in_op['ibias']

                # Step input device size (integer steps)
                nf_in_max = int(round(ibias_max / itail_min))
                nf_in_vec = np.arange(1, nf_in_max, 1)
                # if len(nf_in_vec) > 0:
                #     print(f'Number of input devices {min(nf_in_vec)} to {max(nf_in_vec)}')

                for nf_in in nf_in_vec:
                    itail = itail_min * nf_in

                    # Match load device size
                    load_match, nf_load = verify_ratio(in_op['ibias'] * 2,
                                                       load_op['ibias'], nf_in,
                                                       error_tol)

                    if not load_match:
                        print(f"load match {nf_load}")
                        # assert False, 'blep'
                        continue

                    iflip_max = (ibias_max - itail) / 2
                    nf_out_max = int(round(iflip_max / out_op['ibias']))
                    nf_out_vec = [1] if nf_out_max == 1 else np.arange(
                        1, nf_out_max, 1)
                    # if len(nf_out_vec) > 0:
                    #     print(f'Number of output devices {min(nf_out_vec)} to {max(nf_out_vec)}')

                    # Step output device size
                    for nf_out in nf_out_vec:
                        iflip_branch = out_op['ibias'] * nf_out
                        # Match load copy device size
                        load_copy_match, nf_load_copy = verify_ratio(
                            out_op['ibias'], load_copy_op['ibias'], nf_out,
                            error_tol)
                        if not load_copy_match:
                            print('load copy match')
                            continue

                        # Check target specs
                        ckt_half = LTICircuit()
                        ckt_half.add_transistor(in_op,
                                                'out',
                                                'in',
                                                'gnd',
                                                fg=nf_in * 2,
                                                neg_cap=False)
                        ckt_half.add_transistor(load_op,
                                                'out1',
                                                'out1',
                                                'gnd',
                                                fg=nf_load,
                                                neg_cap=False)
                        ckt_half.add_transistor(load_copy_op,
                                                'out',
                                                'out1',
                                                'gnd',
                                                fg=nf_load_copy,
                                                neg_cap=False)
                        ckt_half.add_transistor(out_op,
                                                'out',
                                                'out',
                                                'gnd',
                                                fg=nf_out,
                                                neg_cap=False)
                        ckt_half.add_cap(cload, 'out', 'gnd')

                        num, den = ckt_half.get_num_den(in_name='in',
                                                        out_name='out',
                                                        in_type='v')
                        # num = np.convolve(num, [-1]) # To get positive gain

                        wbw = get_w_3db(num, den)
                        if wbw == None:
                            wbw = 0
                        fbw = wbw / (2 * np.pi)

                        # wu, _ = get_w_crossings(num, den)
                        # if wu == None:
                        #     wu = 0
                        # ugf = wu/(2*np.pi)

                        # Rout = parallel(1/(nf_in*2*in_op['gds']),
                        #                 1/(nf_out*out_op['gm']),
                        #                 1/(nf_out*out_op['gds']))
                        # Gm = in_op['gm']*nf_in*2
                        # gain = Gm * Rout
                        # Cout = cload + (nf_in*2*in_op['cgs']) + (1+gain)*(nf_in*2*in_op['cds'])
                        # fbw = 1/(2*np.pi*Rout*Cout)
                        # ugf = Gm / Cout * (1/2*np.pi)
                        gain = -num[-1] / den[-1]
                        ugf = fbw * gain
                        if fbw < fbw_min:
                            print(f"fbw {fbw}")
                            continue

                        if ugf < ugf_min:
                            print(f"ugf {ugf}")
                            break

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

                        # Design matching tail
                        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"Tail gate from {vgtail_min} to {vgtail_max}")
                        for vgtail in vgtail_vec:
                            tail_op = db_dict['tail'].query(
                                vgs=vgtail - vb_tail,
                                vds=vtail - vb_tail,
                                vbs=0)
                            tail_match, nf_tail = verify_ratio(
                                in_op['ibias'] * 4, tail_op['ibias'], nf_in,
                                error_tol)

                            if not tail_match:
                                print("tail match")
                                continue

                            print('(SUCCESS)')
                            viable_op = dict(
                                nf_in=nf_in,
                                nf_load=nf_load,
                                nf_load_copy=nf_load_copy,
                                nf_out=nf_out,
                                nf_tail=nf_tail,
                                vgtail=vgtail,
                                gain=gain,
                                fbw=fbw,
                                ugf=ugf,
                                vtail=vtail,
                                vout1=vout1,
                                itail=itail,
                                iflip_branch=nf_out * out_op['ibias'],
                                ibias=itail + 2 * nf_out * out_op['ibias'])
                            print(viable_op)
                            viable_op_list.append(viable_op)
        self.other_params = dict(
            in_type=in_type,
            lch_dict=l_dict,
            w_dict={k: db.width_list[0]
                    for k, db in db_dict.items()},
            th_dict=th_dict)
        return viable_op_list
Пример #5
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
Пример #6
0
    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
Пример #7
0
def design_preamp_chain(db_n,
                        db_p,
                        lch,
                        w_finger,
                        vdd,
                        cload,
                        vin_signal,
                        vin_shift,
                        vtail_res,
                        gain_min,
                        fbw_min,
                        vb_p,
                        vb_n,
                        itail_max=10e-6,
                        error_tol=0.01):
    """
    Designs a preamplifier to go between the comparator and TIA.
    N-input differential pair with an active PMOS load.
    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).
        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)
        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.
        nf_load:        Integer. Number of fingers for active load devices.
        itail:          Float. Tail current of main amp (A).
        gain:           Float. Amp gain (V/V).
        fbw:            Float. Bandwidth (Hz).
        cin:            Float. Approximation of input cap (F).
        vtail:          Float. Tail voltage (V).
    """
    vincm = (vin_signal + vin_shift) / 2
    voutcm = vincm
    best_op = None
    best_itail = np.inf

    vthn = estimate_vth(db_n, vdd, 0)

    vtail_min = 0.15  # TODO: Avoid hardcoding?
    vtail_max = vincm
    vtail_vec = np.arange(vtail_min, vtail_max, vtail_res)
    for vtail in vtail_vec:
        print('VTAIL:\t{0}'.format(vtail))
        op_tail = db_n.query(vgs=voutcm, vds=vtail, vbs=vb_n)
        op_in = db_n.query(vgs=vincm - vtail,
                           vds=voutcm - vtail,
                           vbs=vb_n - vtail)
        op_load = db_p.query(vgs=voutcm - vdd,
                             vds=voutcm - vdd,
                             vbs=vb_p - vdd)

        nf_in_min = 2
        nf_in_max = min(100, int(round(.5 * itail_max / op_in['ibias'])))
        nf_in_vec = np.arange(nf_in_min, nf_in_max, 2)
        for nf_in in nf_in_vec:

            # Check if those bias points are feasible given the
            # device sizing quantization
            load_ratio_good, nf_load = verify_ratio(op_in['ibias'],
                                                    op_load['ibias'], nf_in,
                                                    error_tol)
            if not load_ratio_good:
                continue

            tail_ratio_good, nf_tail_half = verify_ratio(
                op_in['ibias'], op_tail['ibias'], nf_in, error_tol)
            nf_tail = nf_tail_half * 2

            if not tail_ratio_good:
                continue

            # Check if it's burning more power than previous solutions
            itail = op_tail['ibias'] * nf_tail
            if itail > best_itail:
                break

            # Check the half circuit for small signal parameters
            half_circuit = LTICircuit()
            half_circuit.add_transistor(op_in, 'out', 'in', 'tail', fg=nf_in)
            half_circuit.add_transistor(op_load,
                                        'out',
                                        'gnd',
                                        'gnd',
                                        fg=nf_load)
            half_circuit.add_transistor(op_tail,
                                        'tail',
                                        'gnd',
                                        'gnd',
                                        fg=nf_tail_half)
            half_circuit.add_cap(cload, 'out', 'gnd')
            num, den = half_circuit.get_num_den(in_name='in',
                                                out_name='out',
                                                in_type='v')
            gain = abs(num[-1] / den[-1])
            if gain < gain_min:
                print("(FAIL) GAIN:\t{0}".format(gain))
                break

            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

            cin = nf_in * (op_in['cgs'] + op_in['cgd'] * (1 + gain))

            best_itail = itail
            best_op = dict(nf_in=nf_in,
                           nf_tail=nf_tail,
                           nf_load=nf_load,
                           itail=itail,
                           gain=gain,
                           fbw=fbw,
                           vtail=vtail,
                           cin=cin)

    if best_op == None:
        raise ValueError("No viable solutions.")
    return best_op
Пример #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 _get_ss_lti(self, op_dict: Mapping[str, Any],
                    nf_dict: Mapping[str, int], cload: float):
        '''
        Inputs:
            op_dict: Dictionary of queried database operating points.
            nf_dict: Dictionary of number of fingers for devices.
            cload: Load capacitance in farads.
        Return:
            gain: Gain from the input to output in V/V
            fbw: 3dB bandwidth in Hz
        '''
        in_d = 'd' if nf_dict['opp'] > 0 else 'gnd'

        ckt = LTICircuit()
        ckt.add_cap(cload, 'out', 'gnd')
        ckt.add_transistor(op_dict['in'],
                           in_d,
                           'in',
                           'out',
                           fg=nf_dict['in'],
                           neg_cap=False)
        ckt.add_transistor(op_dict['same'],
                           'out',
                           'gnd',
                           'gnd',
                           fg=nf_dict['same'],
                           neg_cap=False)
        if nf_dict['opp'] > 0:
            ckt.add_transistor(op_dict['opp'],
                               'd',
                               'gnd',
                               'gnd',
                               fg=nf_dict['opp'],
                               neg_cap=False)

        num, den = ckt.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)

        return gain, fbw
Пример #10
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
Пример #11
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
Пример #12
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
Пример #13
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
Пример #14
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
Пример #15
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
Пример #16
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
Пример #17
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
Пример #18
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
Пример #19
0
def design_load(specs, db, ax_list=None, label=None):
    sim_env = specs['sim_env']
    vgs_res = specs['vgs_res']
    vdd = specs['vdd']
    voutcm = specs['voutcm']
    vstar_in = specs['vstar_in']
    vstar_load = specs['vstar_load']
    casc_scale = specs['casc_scale']

    vgs_idx = db.get_fun_arg_index('vgs')
    vstar_fun = db.get_function('vstar', env=sim_env)
    ibias_fun = db.get_function('ibias', env=sim_env)
    vgs_min, vgs_max = vstar_fun.get_input_range(vgs_idx)

    def zero_fun1(vm, vgs):
        arg = db.get_fun_arg(vgs=vgs, vds=vm-vdd, vbs=0)
        return (vstar_fun(arg) - vstar_load) * 1e3

    def zero_fun2(vc, vm, itarg):
        arg = db.get_fun_arg(vgs=vc-vm, vds=voutcm-vm, vbs=vdd-vm)
        return (ibias_fun(arg) * casc_scale - itarg) * 1e6

    num_pts = int(np.ceil((vgs_max - vgs_min) / vgs_res)) + 1
    vgs_list, vds_list, vcasc_list, ibias_list, ro_list = [], [], [], [], [] 
    p1_list, p2_list, gain_list = [], [], []
    for vgs_val in np.linspace(vgs_min, vgs_max, num_pts, endpoint=True):
        fout1 = zero_fun1(vdd - 5e-3, vgs_val)
        fout2 = zero_fun1(voutcm + 5e-3, vgs_val)
        if fout1 * fout2 > 0:
            continue
        vmid = opt.brentq(zero_fun1, voutcm + 5e-3, vdd - 5e-3, args=(vgs_val,))
        if vmid + vgs_max <= 0:
            continue
        barg = db.get_fun_arg(vgs=vgs_val, vds=vmid-vdd, vbs=0)
        bot_op = db.query(vbs=0, vds=vmid-vdd, vgs=vgs_val)
        ib = bot_op['ibias']
        args = (vmid, ib)
        fout1 = zero_fun2(vmid + vgs_max, *args)
        fout2 = zero_fun2(0, *args)
        if fout1 * fout2 > 0:
            continue
        vcasc = opt.brentq(zero_fun2, 0, vmid + vgs_max, args=args)
        top_op = db.query(vbs=vdd-vmid, vds=voutcm-vmid, vgs=vcasc-vmid)

        cir = LTICircuit()
        cir.add_transistor(bot_op, 'mid', 'gnd', 'gnd', 'gnd', fg=1)
        cir.add_transistor(top_op, 'out', 'gnd', 'mid', 'gnd', fg=casc_scale)
        cur_tf = cir.get_transfer_function('out', 'out', in_type='i')
        ro = cur_tf.num[-1] / cur_tf.den[-1]
        p1, p2 = cur_tf.poles
        p1, p2 = min(-p1, -p2), max(-p1, -p2)
        p2 /= 2 * np.pi

        # add approximation from input branch
        cpar = 1 / (ro * p1)
        p1 = 1 / (2 * np.pi * 2 * cpar * (ro / 4))
        cur_gain = 2 * ib / vstar_in * (ro / 4)

        vgs_list.append(vgs_val)
        vds_list.append(vmid-vdd)
        vcasc_list.append(vcasc)
        ibias_list.append(ib)
        ro_list.append(ro * ib)
        p1_list.append(p1)
        p2_list.append(p2)
        gain_list.append(cur_gain)

    if ax_list is not None:
        val_list_list = [vds_list, gain_list, p1_list, p2_list]
        ylabel_list = ['$V_{DS}$ (V)', 'Gain (V/V)',
                       '$p_{1o}$ (GHz)', '$p_{2o}$ (GHz)']
        sp_list = [1, 1, 1e-9, 1e-9]
        for ax, val_list, ylabel, sp in zip(ax_list, val_list_list,
                                            ylabel_list, sp_list):
            ax.plot(vgs_list, np.asarray(val_list) * sp, '-o', label=label)
            ax.set_ylabel(ylabel)

        ax_list[-1].set_xlabel('$V_{GS}$ (V)')
    return vgs_list[0], vds_list[0], vcasc_list[0]
Пример #20
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()
Пример #21
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,
                  vb_p,
                  vb_n,
                  itail_max=10e-6,
                  error_tol=.01):
    """
    Designs a preamplifier to go between the comparator and TIA.
    N-input differential pair with an active PMOS load.
    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)
        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.
        nf_load:        Integer. Number of fingers for active load devices.
        itail:          Float. Tail current of main amp (A).
        gain:           Float. Amp gain (V/V).
        fbw:            Float. Bandwidth (Hz).
        voutcm:         Float. Output common mode voltage (V).
        cin:            Float. Approximation of input cap (F).
    """
    vthn = estimate_vth(db_n, vdd, 0)
    vthp = estimate_vth(db_p, vdd, 0, mos_type="pmos")
    vincm = (vin_signal + vin_shift) / 2

    vstar_min = .15  # TODO: Avoid hardcoding?

    vtail_min = vstar_min
    vtail_max = vin_signal - vstar_min
    vtail_vec = np.arange(vtail_min, vtail_max, vtail_res)

    best_op = None
    best_itail = np.inf

    for vtail in vtail_vec:
        voutcm_min = max(vin_signal - vthn, vin_shift - vthn, vtail)
        voutcm_max = vdd - vstar_min
        voutcm_vec = np.arange(voutcm_min, voutcm_max, voutcm_res)

        for voutcm in voutcm_vec:
            op_tail = db_n.query(vgs=voutcm, vds=vtail, vbs=vb_n)
            # The "mean" of the input and load devices
            op_in = db_n.query(vgs=vincm - vtail,
                               vds=voutcm - vtail,
                               vbs=vb_n - vtail)
            op_load = db_p.query(vgs=vtail - vdd,
                                 vds=voutcm - vdd,
                                 vbs=vb_p - vdd)

            # 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:
                # Matching device ratios to sink the same amount of current
                # given the bias voltages
                tail_ratio_good, nf_tail = verify_ratio(
                    op_in['ibias'] / 2, op_tail['ibias'], nf_in, error_tol)
                if not tail_ratio_good:
                    continue

                itail = op_tail['ibias'] * nf_tail

                load_ratio_good, nf_load = verify_ratio(
                    op_in['ibias'], op_load['ibias'], nf_in, error_tol)

                if not load_ratio_good:
                    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_transistor(op_load,
                                       'outp',
                                       'gnd',
                                       'gnd',
                                       fg=nf_load)
                ckt_sym.add_transistor(op_load,
                                       'outn',
                                       'gnd',
                                       'gnd',
                                       fg=nf_load)
                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))
                    break
                print("(SUCCESS1)")

                if itail > best_itail:
                    break

                ##############################
                if False:
                    # 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)
                    op_loadsignal = db_p.query(vgs=vtail - vdd,
                                               vds=voutn - vdd,
                                               vbs=vb_p - vdd)
                    op_loadshift = db_p.query(vgs=vtail - vdd,
                                              vds=voutp - vdd,
                                              vbs=vb_p - vdd)
                    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_transistor(op_loadsignal, 'outn', 'gnd', 'gnd',
                                       nf_load)
                    ckt.add_transistor(op_loadshift, 'outp', 'gnd', 'gnd',
                                       nf_load)
                    ckt.add_cap(cload, 'outn', 'gnd')
                    ckt.add_cap(cload, 'outp', '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))
                        break
                    print("(SUCCESS2)")
                ################################
                op_signal = op_in

                if itail < best_itail:
                    best_itail = itail

                    best_op = dict(nf_in=nf_in,
                                   nf_tail=nf_tail,
                                   nf_load=nf_load,
                                   itail=itail,
                                   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
Пример #22
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)
Пример #23
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
Пример #24
0
    def make_ltickt(self, op_dict:Mapping[str,Any], nf_dict:Mapping[str,int], 
                    cload:float, opp_drain_conn:Mapping[str,str],
                    meas_side:str) -> LTICircuit:
        '''
        Constructs and LTICircuit for the amplifier.
        Input dictionary keys must include:
            in
            tail
            same_outer
            same_inner
            opp_inner
            opp_outer
        Inputs:
            op_dict: Dictionary of queried database operating points.
            nf_dict: Dictionary of number of fingers for devices.
            cload: Load capacitance in farads.
            meas_side: p = only p-input connected (n-input is held at AC ground); n = only n-input
                connected (p-input is held at AC ground); anything else = both inputs are
                connected to AC inputs
        Output:
            LTICircuit object of this amplifier with inputs as inp and/or inn, output as out.
        '''
        def conv_drain_conn(d_conn):
            return d_conn.replace('G', 'g').replace('D', 'd').replace('N', '').replace('P', '').replace('<', '').replace('>', '')
        
        diode_inner = opp_drain_conn[1] in ('GN<1>', 'GP<1>')
        diode_outer =  opp_drain_conn[0] in ('GN<0>', 'GP<0>')
        highswing_outer = opp_drain_conn[1] in ('GN<0>', 'GP<0>')

        opp_drain_conn_conv = [conv_drain_conn(d_conn) for d_conn in opp_drain_conn]
        ckt = LTICircuit()

        # Input pair
        inp_conn = 'gnd' if meas_side=='n' else 'inp'
        inn_conn = 'gnd' if meas_side=='p' else 'inn'
        ckt.add_transistor(op_dict['in'], 'out1n', inp_conn, 'tail', fg=nf_dict['in'], neg_cap=False)
        ckt.add_transistor(op_dict['in'], 'out1p', inn_conn, 'tail', fg=nf_dict['in'], neg_cap=False)

        # Tail
        ckt.add_transistor(op_dict['tail'], 'tail', 'gnd', 'gnd', fg=nf_dict['tail'], neg_cap=False)

        # Outer "same"-side cascode devices
        ckt.add_transistor(op_dict['same_outer'], 'out1n', 'gnd', 'gnd', fg=nf_dict['same_outer'], neg_cap=False)
        ckt.add_transistor(op_dict['same_outer'], 'out1p', 'gnd', 'gnd', fg=nf_dict['same_outer'], neg_cap=False)
        
        # Inner "same"-side cascode devices
        d_inner = 'g0' if highswing_outer else 'g1' if diode_inner else 'outx'
        ckt.add_transistor(op_dict['same_inner'], d_inner, 'gnd', 'out1n', fg=nf_dict['same_inner'], neg_cap=False)
        ckt.add_transistor(op_dict['same_inner'], 'out', 'gnd', 'out1p', fg=nf_dict['same_inner'], neg_cap=False)
        
        # Inner "opposite" side cascode devices
        g_inner = 'g0' if (highswing_outer and diode_inner) else 'g1' if diode_inner else 'gnd'
        g_outer = 'g0' if (highswing_outer or diode_inner) else 'gnd'
        d_outer = 'g0' if diode_outer else 'd0'
        ckt.add_transistor(op_dict['opp_inner'], d_inner, g_inner, d_outer, fg=nf_dict['opp_inner'], neg_cap=False)
        ckt.add_transistor(op_dict['opp_inner'], 'out', g_inner, f'{d_outer}x', fg=nf_dict['opp_inner'], neg_cap=False)
        
        # Inner "opposite" side cascode devices
        ckt.add_transistor(op_dict['opp_outer'], d_outer, g_outer, 'gnd', fg=nf_dict['opp_outer'], neg_cap=False)
        ckt.add_transistor(op_dict['opp_outer'], f'{d_outer}x', g_outer, 'gnd', fg=nf_dict['opp_outer'], neg_cap=False)
        
        # Load
        ckt.add_cap(cload, 'out', 'gnd')

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

        ckt = LTICircuit()
        ckt.add_transistor(op_dict['tail'], 'tail', 'gnd', 'gnd', fg=nf_dict['tail'])
        ckt.add_transistor(op_dict['in'], 'out', inn_conn, 'tail', fg=nf_dict['in'])
        ckt.add_transistor(op_dict['in'], 'outx', inp_conn, 'tail', fg=nf_dict['in'])
        ckt.add_transistor(op_dict['load'], 'outx', 'outx', 'gnd', fg=nf_dict['load'])
        ckt.add_transistor(op_dict['load'], 'out', 'outx', 'gnd', fg=nf_dict['load'])
        ckt.add_cap(cload, 'out', 'gnd')

        return ckt
Пример #26
0
def design_amp(specs, in_op, nbot_op, ntop_op, pbot_op, ptop_op):

    casc_nscale = specs['casc_nscale']
    casc_pscale = specs['casc_pscale']

    nbot_fg = 2
    ibias = nbot_op['ibias']
    ntop_fg = casc_nscale
    pbot_fg = ibias / pbot_op['ibias']
    ptop_fg = pbot_fg * casc_pscale
    in_fg = ibias / in_op['ibias']

    scale = 3
    cir = LTICircuit()
    cir.add_transistor(in_op, 'x', 'in', 'gnd', 'gnd', fg=in_fg * scale)
    cir.add_transistor(nbot_op, 'x', 'gnd', 'gnd', 'gnd', fg=nbot_fg * scale)
    cir.add_transistor(ntop_op, 'out', 'gnd', 'x', 'gnd', fg=ntop_fg * scale)
    cir.add_transistor(ptop_op, 'out', 'gnd', 'm', 'gnd', fg=ptop_fg * scale)
    cir.add_transistor(pbot_op, 'm', 'gnd', 'gnd', 'gnd', fg=pbot_fg * scale)
    cir.add_cap(20e-15 * scale, 'out', 'gnd')
    cur_tf = cir.get_transfer_function('in', 'out', in_type='v')
    gain = cur_tf.num[-1] / cur_tf.den[-1]
    p1, p2, p3 = sorted(-cur_tf.poles)
    p1 /= 2 * np.pi
    p2 /= 2 * np.pi
    p3 /= 2 * np.pi

    for name, op, fg in [('in', in_op, in_fg), ('nbot', nbot_op, nbot_fg),
                         ('ntop', ntop_op, ntop_fg),
                         ('ptop', ptop_op, ptop_fg),
                         ('pbot', pbot_op, pbot_fg)]:
        print('%s fg : \t%.3g' % (name, fg * scale))
        for val in ('gm', 'gds', 'gb', 'ibias'):
            if val in op:
                print('%s %s : \t%.6g' % (name, val, op[val] * scale * fg))

    print('vtail: %.6g' % (specs['vincm'] - in_op['vgs']))
    print('vmidn: %.6g' % (nbot_op['vds']))
    print('vmidp: %.6g' % (specs['vdd'] + pbot_op['vds']))
    print('vb1: %.6g' % (specs['vdd'] + pbot_op['vgs']))
    print('vb2: %.6g' % (specs['vdd'] + pbot_op['vds'] + ptop_op['vgs']))
    print('vb3: %.6g' % (nbot_op['vds'] + ntop_op['vgs']))
    print('vb4: %.6g' % (nbot_op['vgs']))

    print(gain, p1 / 1e9, p2 / 1e9, p3 / 1e9, nbot_fg * ibias)
Пример #27
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
Пример #28
0
def construct_openLoop(n_op, p_op, tail_op, 
    nf_n, nf_p, nf_tail,
    cload):
    '''
    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.
    Outputs:
        Returns the LTICircuit constructed for open loop analysis of 
        the amplifier.
    '''
    ckt = LTICircuit()
    # Left side
    ckt.add_transistor(n_op, 'out_copy', 'in', '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', 'gnd', '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 load
    ckt.add_cap(cload, 'out', 'gnd')
    
    return ckt
Пример #29
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
Пример #30
0
def design_TIA_inverter(db_n,
                        db_p,
                        sim_env,
                        vg_res,
                        rf_res,
                        vdd_nom,
                        vdd_vec,
                        cpd,
                        cload,
                        rdc_min,
                        fbw_min,
                        pm_min,
                        BER_max,
                        vos,
                        isw_pkpk,
                        vb_n,
                        vb_p,
                        error_tol=0.05,
                        ibias_max=20e-6):
    """
    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 in volts when sweeping gate voltage.
        rf_res:     Float. Step resolution in ohms when sweeping feedback 
                    resistance.
        vdd_nom:    Float. Nominal supply voltage in volts.
        vdd_vec:    Collection of floats. Elements should include the min and
                    max supply voltage in volts.
        cpd:        Float. Input parasitic capacitance in farads.
        cload:      Float. Output load capacitance in farads.
        rdc_min:    Float. Minimum DC transimpedance in ohms.
        fbw_min:    Float. Minimum bandwidth (Hz).
        pm_min:     Float. Minimum phase margin in degrees.
        BER_max:    Float. Maximum allowable bit error rate (as a fraction).
        vos:        Float. Input-referred DC offset for any subsequent
                    comparator stage as seen at the output
                    of the TIA.
        isw_pkpk:   Float. Input current peak-to-peak swing in amperes.
        vb_n/p:     Float. Back-gate/body voltage (V) of NMOS and PMOS, 
                    respectively.
        error_tol:  Float. Fractional tolerance for ibias error when computing 
                    the p-to-n ratio.
        ibias_max:  Float. Maximum bias current (A) allowed.
    Raises:
        ValueError: If unable to meet the specification requirements.
    Outputs:
        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.
    """
    # Finds all possible designs for one value of VDD, then
    # confirm which work with all other VDD values.
    possibilities = []

    vg_vec = np.arange(0, vdd_nom, vg_res)

    for vg in vg_vec:
        print("VIN:\t{0}".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_nom,
                               vds=vg - vdd_nom,
                               vbs=vb_p - vdd_nom)

        if np.isinf(ibias_max):
            nf_n_max = 200
        else:
            nf_n_max = int(round(ibias_max / n_op_info['ibias']))

        nf_n_vec = np.arange(1, nf_n_max, 1)
        for nf_n in nf_n_vec:
            # Number of fingers can only be integer,
            # so increase as necessary until you get
            # sufficiently accurate/precise bias + current match
            ratio_good, nf_p = verify_ratio(n_op_info['ibias'],
                                            p_op_info['ibias'], nf_n,
                                            error_tol)
            if not ratio_good:
                continue

            # Getting small signal parameters to constrain Rf
            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
            gds_p = p_op_info['gds'] * nf_p
            gds = abs(gds_n) + abs(gds_p)
            ro = 1 / gds

            # 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 * 2, rf_res)
            for rf in rf_vec:
                # With all parameters, check if it meets small signal spec
                meets_SS, SS_vals = verify_TIA_inverter_SS(
                    n_op_info, p_op_info, nf_n, nf_p, rf, cpd, cload, rdc_min,
                    fbw_min, pm_min)
                # With all parameters, estimate if it will meet noise spec
                meets_noise, BER = verify_TIA_inverter_BER(
                    n_op_info, p_op_info, nf_n, nf_p, rf, cpd, cload, BER_max,
                    vos, isw_pkpk)

                meets_spec = meets_SS  # and meets_noise
                # If it meets small signal spec, append it to the list
                # of possibilities
                if meets_spec:
                    possibilities.append(
                        dict(vg=vg,
                             vdd=vdd_nom,
                             nf_n=nf_n,
                             nf_p=nf_p,
                             rf=rf,
                             rdc=SS_vals['rdc'],
                             fbw=SS_vals['fbw'],
                             pm=SS_vals['pm'],
                             ibias=ibias_n,
                             BER=BER))
                elif SS_vals['fbw'] != None and SS_vals['fbw'] < fbw_min:
                    # Increasing resistor size won't help bandwidth
                    break

    # Go through all possibilities which work at the nominal voltage
    # and ensure functionality at other bias voltages
    # Remove any nonviable options
    print("{0} working at nominal VDD".format(len(possibilities)))
    for candidate in possibilities:
        nf_n = candidate['nf_n']
        nf_p = candidate['nf_p']
        rf = candidate['rf']
        for vdd in vdd_vec:
            new_op_dict = vary_supply(vdd, db_n, db_p, nf_n, nf_p, vb_n, vb_p)
            vg = new_op_dict['vb']
            n_op = new_op_dict['n_op']
            p_op = new_op_dict['p_op']

            # Confirm small signal spec is met
            meets_SS, scratch = verify_TIA_inverter_SS(n_op, p_op, nf_n, nf_p,
                                                       rf, cpd, cload, rdc_min,
                                                       fbw_min, pm_min)

            # Confirm noise spec is met
            meets_noise, BER = verify_TIA_inverter_BER(n_op, p_op, nf_n, nf_p,
                                                       rf, cpd, cload, BER_max,
                                                       vos, isw_pkpk)

            meets_spec = meets_SS  # and meets_noise

            if not meets_spec:
                possibilities.remove(candidate)
                break

    # Of the remaining possibilities, check for lowest power.
    # If there are none, raise a ValueError.
    if len(possibilities) == 0:
        raise ValueError("No final viable solutions")

    print("{0} working at all VDD".format(len(possibilities)))
    best_op = possibilities[0]
    for candidate in possibilities:
        best_op = choose_op_comparison(best_op, candidate)

    return best_op