Example #1
0
def design_constgmN(db_n,
                    db_p,
                    vdd,
                    vb_n,
                    vb_p,
                    K,
                    voutn,
                    voutp,
                    iref_max=5e-6,
                    error_tol=.1):
    """
    Inputs:
        db_n/p:
        vdd:
        vb_n/p:
        K:
        voutn/p:
        iref_max:
        error_tol:
    """
    op_n1 = db_n.query(vgs=voutn, vds=voutp, vbs=vb_n)
    op_p = db_p.query(vgs=voutp - vdd, vds=voutp - vdd, vbs=vb_p - vdd)

    nf_n1_min = 2
    if np.isinf(iref_max):
        nf_n1_max = 200
    else:
        nf_n1_max = int(round(iref_max / op_n1['ibias']))

    nf_n1_vec = np.arange(nf_n1_min, nf_n1_max, 2)
    for nf_n1 in nf_n1_vec:
        nf_nK = K * nf_n1
        p_match_good, nf_p = verify_ratio(op_n1['ibias'], op_p['ibias'], nf_n1,
                                          error_tol)
        if not p_match_good:
            continue

        ibias_1 = op_n1['ibias'] * nf_n1

        vr_iter = FloatBinaryIterator(low=0,
                                      high=voutp,
                                      tol=0,
                                      search_step=voutp / 2**10)
        # vr = vr_iter.get_next()

        while vr_iter.has_next():
            vr = vr_iter.get_next()
            op_nK = db_n.query(vgs=voutn - vr, vds=voutp - vr, vbs=vb_n - vr)

            ibias_K = op_nK['ibias'] * nf_nK
            ierror = (abs(ibias_1 - ibias_K)) / min(abs(ibias_1), abs(ibias_K))
            if ierror <= error_tol:
                return dict(nf_n1=nf_n1,
                            nf_nK=nf_nK,
                            nf_p=nf_p,
                            rsource=vr / ibias_K,
                            ibias_K=ibias_K,
                            ibias_1=ibias_1)
            elif ibias_K > ibias_1:
                vr_iter.up()
            else:
                vr_iter.down()
    raise ValueError("No viable solution.")
Example #2
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
Example #3
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
def design_LPF_AMP(db_n, db_p, db_bias, sim_env,
    vin, vdd_nom, cload,
    vtail_res,
    gain_min, fbw_min, pm_min,
    vb_n, vb_p, error_tol=0.1, ibias_max=20e-6, debugMode=False):
    '''
    Designs an amplifier with an N-input differential pair.
    Uses the LTICircuit functionality.
    Inputs:
        db_n/p:     Databases for non-biasing NMOS and PMOS device 
                    characterization data, respectively.
        db_bias:    Database for tail NMOS device characterization data.
        sim_env:    Simulation corner.
        vin:        Float. Input (and output and tail) bias voltage in volts.
        vtail_res:  Float. Step resolution in volts when sweeping tail voltage.
        vdd_nom:    Float. Nominal supply voltage in volts.
        cload:      Float. Output load capacitance in farads.
        gain_min:   Float. Minimum DC voltage gain in V/V.
        fbw_min:    Float. Minimum bandwidth (Hz) of open loop amp.
        pm_min:     Float. Minimum phase margin in degrees.
        vb_n/p:     Float. Back-gate/body voltage (V) of NMOS and PMOS, 
                    respectively (nominal).
        error_tol:  Float. Fractional tolerance for ibias error when computing 
                    the p-to-n ratio.
        ibias_max:  Float. Maximum bias current (A) allowed with nominal vdd.
    Raises:
        ValueError: If unable to meet the specification requirements.
    Outputs:
        A dictionary with the following key:value pairings:
        nf_n:
        nf_p:
        nf_tail:
        gain:           Float. DC voltage gain of both stages combined (V/V).
        fbw:            Float. Bandwdith (Hz).
        pm:             Float. Phase margin (degrees) for unity gain.
    '''
    possibilities = []
    vout = vin
    vstar_min = 0.15
    vtail_vec = np.arange(vstar_min, vout, vtail_res)
    for vtail in vtail_vec:
        cond_print("VTAIL: {0}".format(vtail), debugMode)
        n_op = db_n.query(vgs=vin-vtail, vds=vout-vtail, vbs=vb_n-vtail)
        p_op = db_p.query(vgs=vout-vdd_nom, vds=vout-vdd_nom, vbs=vb_p-vdd_nom)
        tail_op = db_bias.query(vgs=vout, vds=vtail, vbs=vb_n)
        
        idn_base = n_op['ibias']
        idp_base = p_op['ibias']
        idtail_base = tail_op['ibias']
        
        p_to_n = abs(idn_base/idp_base)
        tail_to_n = abs(idn_base/idtail_base)
        
        nf_n_max = int(round(abs(ibias_max/(idn_base*2))))
        nf_n_vec = np.arange(2, nf_n_max, 2)
        for nf_n in nf_n_vec:
            cond_print("\tNF_N: {0}".format(nf_n), debugMode)
            # Verify that sizing is feasible and gets sufficiently
            # close current matching
            p_good, nf_p = verify_ratio(idn_base, idp_base, p_to_n, 
                nf_n, error_tol)
                
            tail_good, nf_tail = verify_ratio(idn_base, idtail_base, tail_to_n,
                nf_n*2, error_tol)

            if not (p_good and tail_good):
                cond_print("\t\tP_BIAS: {0}\n\t\tT_BIAS: {1}".format(p_good, tail_good), debugMode)
                cond_print("\t\tP_SIZE: {0}\n\t\tT_SIZE: {1}".format(nf_p, nf_tail), debugMode)
                tail_error = abs(abs(idtail_base*nf_tail) - abs(idn_base*nf_n*2))/abs(idn_base*nf_n*2)
                cond_print("\t\tTAIL ERROR: {0}".format(tail_error), debugMode)
                continue
            # Devices are sized, check open loop amp SS spec
            openLoop_good, openLoop_params = verify_openLoop(n_op, p_op, tail_op, 
                                        nf_n, nf_p, nf_tail,
                                        gain_min, fbw_min,
                                        cload)
            if not openLoop_good:
                # Gain is set by the bias point
                if openLoop_params['gain'] < gain_min:
                    cond_print("\t\tGAIN: {0} (FAIL)".format(openLoop_params['gain']), debugMode)
                    break
                
                cond_print("\t\tBW: {0} (FAIL)".format(openLoop_params['fbw']), debugMode)
                # Bandwidth isn't strictly set by biasing
                continue
            
            # Check PM in feedback
            closedLoop_good, closedLoop_params = verify_closedLoop(n_op, p_op, tail_op,
                                        nf_n, nf_p, nf_tail, pm_min, 
                                        cload)
            if closedLoop_good:
                viable = dict(nf_n=nf_n,
                              nf_p=nf_p,
                              nf_tail=nf_tail,
                              gain=openLoop_params['gain'],
                              fbw=openLoop_params['fbw'],
                              pm=closedLoop_params['pm'],
                              ibias=abs(idtail_base*nf_tail),
                              vtail=vtail)
                possibilities.append(viable)
    if len(possibilities) == 0:
        return ValueError("No viable solutions.")
    else:
        print("{0} viable solutions".format(len(possibilities)))
        
    best_ibias = float('inf')
    best_op = None
    for candidate in possibilities:
        if candidate['ibias'] < best_ibias:
            best_op = candidate
            best_ibias = candidate['ibias']
    
    return best_op
Example #5
0
def design_levelshift_bias(db_n, vdd, voutcm, voutdiff, vb_n, 
        error_tol=0.01, vtail_res=5e-3, vstar_min=.15, ibias_max=2e-6):
    '''
    Designs gate biasing network for level shifting (current shunting) devices.
    Minimizes current consumption.
    Inputs:
        db_n:       Database for NMOS device characterization data.
        vb_n:       Float. Back-gate/body voltage (V) of NMOS devices.      
        error_tol:  Float. Fractional tolerance for ibias error when computing
                    device ratios.
        vtail_res:  Float. Resolution for sweeping tail voltage (V).
        vstar_min:  Float. Minimum vstar voltage (V) for design.
    Outputs:
        Returns a dictionary with the following:
        Rtop:       Float. Value of the top resistor (ohms).
        Rbot:       Float. Value of the lower resistor (ohms).
        nf_in:      Integer. Number of fingers for the "input" devices.
        nf_tail:    Integer. Number of fingers for the tail device.
        vtail:      Float. Tail voltage (V).
        ibias:      Float. Bias current (A).
    '''
    voutn = voutcm - voutdiff/2
    voutp = voutcm + voutdiff/2
    vgtail = voutn
    
    best_ibias = np.inf
    best_op = None
    
    # Sweep device tail voltage
    vtail_min = vstar_min
    vtail_max = voutn
    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=vgtail, vds=vtail, vbs=vb_n)
        op_in = db_n.query(vgs=vdd-vtail, vds=voutn-vtail, vbs=vb_n-vtail)
        
        # Based on max current, set upper limit on tail device size
        nf_tail_max = round(ibias_max/abs(op_tail['ibias']))
        if nf_tail_max < 1:
            print("FAIL: Tail too small")
            continue
        
        # Sweep size of tail device until good bias current matching
        # between the input and tail device is found
        nf_tail_vec = np.arange(2, nf_tail_max, 2)
        for nf_tail in nf_tail_vec:
            # Don't bother sizing up if the current consumption is higher
            # than the last viable solution
            ibias = abs(op_tail['ibias']*nf_tail)
            if ibias > best_ibias:
                break
                
            imatch_good, nf_in = verify_ratio(abs(op_tail['ibias']), abs(op_in['ibias']),
                nf_tail, error_tol)
            if imatch_good:
                print("SUCCESS")
                Rtop = (vdd-voutp)/ibias
                Rbot = voutdiff/ibias
                best_op = dict(
                    Rtop=Rtop,
                    Rbot=Rbot,
                    nf_in=nf_in,
                    nf_tail=nf_tail,
                    vtail=vtail,
                    ibias=ibias,
                    voutp=voutp,
                    voutn=voutn)
            else:
                print("FAIL: Bad current match")
    
    if best_op == None:
        raise ValueError("PREAMP BIAS: No solution")
    return best_op
Example #6
0
                             vbs=vb_n-vtail)
         op_trim_on = db_n.query(vgs=vtrimp-vtail,
                                 vds=voutcm-vtail,
                                 vbs=vb_n-vtail)
         op_trim_off = db_n.query(vgs=vtrimn-vtail,
                                 vds=voutcm-vtail,
                                 vbs=vb_n-vtail)
         
         # Calculate the max tail size based on current limit
         # and size input devices accordingly
         nf_tail_min = 2
         nf_tail_max = int(round(itail_max/op_tail['ibias'])
         nf_tail_vec = np.arange(nf_tail_min, nf_tail_max, 2)
         for nf_tail in nf_tail_vec:
             amp_ratio_good, nf_in = verify_ratio(op_tail['ibias'],
                                         op_in['ibias'],
                                         nf_tail, error_tol)
             if amp_ratio_good:
                 # Size offset devices
         
         
 
 ####################
 ####################
 # If there's no need for shifting or trim
 if vos_targ < 1e-6:
     vtail_trim = vout_cm
     op_trim_on = db_n.query(vgs=0, vds=0, vbs=vb_n-0)
     op_trim_off = db_n.query(vgs=0, vds=0, vbs=vb_n-0)
 # If there's need for shifting
 else:
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
Example #8
0
def design_preamp_string(db_n,
                         lch,
                         w_finger,
                         vdd,
                         cload,
                         vin_signal,
                         vin_shift,
                         voutcm,
                         vtail_res,
                         gain_min,
                         fbw_min,
                         rload_res,
                         vb_n,
                         itail_max=10e-6,
                         error_tol=.01):
    """
    Designs a preamplifier to go between the comparator and TIA, stringing them
    together.
    Inputs:
    Outputs:
    """
    # Estimate vth for use in calculating biasing ranges
    vthn = estimate_vth(db_n, vdd, 0)
    vincm = (vin_signal + vin_shift) / 2

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

    best_op = None
    best_itail = np.inf

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

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

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

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

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

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

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

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