Ejemplo n.º 1
0
    def _get_ss_lti(self, op_dict:Mapping[str,Any], 
                    nf_dict:Mapping[str,int], 
                    cload:float) -> Tuple[float,float]:
        ckt_p = self.make_ltickt(op_dict=op_dict, nf_dict=nf_dict, cload=cload, meas_side='p')
        p_num, p_den = ckt_p.get_num_den(in_name='inp', out_name='out', in_type='v')

        ckt_n = self.make_ltickt(op_dict=op_dict, nf_dict=nf_dict, cload=cload, meas_side='n')
        n_num, n_den = ckt_n.get_num_den(in_name='inn', out_name='out', in_type='v')

        num, den = num_den_add(p_num, np.convolve(n_num, [-1]),
                               p_den, n_den)

        num = np.convolve(num, [0.5])

        gain = num[-1]/den[-1]
        wbw = get_w_3db(num, den)
        pm, _ = get_stability_margins(num, den)
        ugw, _ = get_w_crossings(num, den)

        if wbw == None:
            wbw = 0
        fbw = wbw/(2*np.pi)

        if ugw == None:
            ugw = 0
        ugf = ugw/(2*np.pi)

        return gain, fbw, ugf, pm
Ejemplo n.º 2
0
    def _get_ss_lti(self, op_dict: Mapping[str, Any], nf_dict: Mapping[str,
                                                                       int],
                    cload: float, rload: float) -> Tuple[float, float]:
        '''
        Return:
            gain
            fbw
        '''
        ckt_p = self.make_ltickt(op_dict=op_dict,
                                 nf_dict=nf_dict,
                                 cload=cload,
                                 rload=rload,
                                 meas_side='p')
        p2p_num, p2p_den = ckt_p.get_num_den(in_name='inp',
                                             out_name='main_outp',
                                             in_type='v')
        p2n_num, p2n_den = ckt_p.get_num_den(in_name='inp',
                                             out_name='main_outn',
                                             in_type='v')

        ckt_n = self.make_ltickt(op_dict=op_dict,
                                 nf_dict=nf_dict,
                                 cload=cload,
                                 rload=rload,
                                 meas_side='n')
        n2p_num, n2p_den = ckt_n.get_num_den(in_name='inn',
                                             out_name='main_outp',
                                             in_type='v')
        n2n_num, n2n_den = ckt_n.get_num_den(in_name='inn',
                                             out_name='main_outn',
                                             in_type='v')

        # Diff gain for p-input and n-input
        p_num, p_den = num_den_add(p2p_num, np.convolve(p2n_num, [-1]),
                                   p2p_den, p2n_den)
        n_num, n_den = num_den_add(n2p_num, np.convolve(n2n_num, [-1]),
                                   n2p_den, n2n_den)

        # Superposition for two inputs
        num, den = num_den_add(p_num, np.convolve(n_num, [-1]), p_den, n_den)
        num = np.convolve(num, [0.5])

        # Calculate FoM using transfer function
        gain = num[-1] / den[-1]
        wbw = get_w_3db(num, den)

        if wbw == None:
            wbw = 0
        fbw = wbw / (2 * np.pi)

        return gain, fbw
Ejemplo n.º 3
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
Ejemplo n.º 4
0
    def dsn_passives(self, tdelay, k1, k2, C1, C2, R8, gain_target, scale_num):
        w0 = 1/tdelay

        b0 = 3 * w0**2
        a1 = 3 * w0
        a0 = 3 * w0**2

        # Change values to get the right DC gain
        gain_mult = gain_target/(b0 / a0)

        if scale_num:
            b0 = b0 * gain_mult
        else:
            a1 = a1 / gain_mult
            a0 = a0 / gain_mult

        # Transfer function
        num = np.asarray([b0])
        den =np.asarray([1, a1, a0])

        # Calculate remaining resistor values (R4 and R6 are removed)
        R1 = 1/(a1 * C1)
        R2 = k1 / (np.sqrt(a0)*C2)
        R3 = 1/(k1*k2) * 1/(np.sqrt(a0)*C1)
        R5 = k1*np.sqrt(a0) / (b0*C2)
        R7 = k2 * R8

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

        return dict(R1 = R1,
                    R2 = R2,
                    R3 = R3,
                    R5 = R5,
                    R7 = R7,
                    k1=k1,
                    k2=k2,
                    C1=C1,
                    C2=C2,
                    R8=R8,
                    gain = b0 / a0,
                    fbw = fbw)
Ejemplo n.º 5
0
    def _get_ss_lti(self, op_dict:Mapping[str,Any], 
                    nf_dict:Mapping[str,int], 
                    opp_drain_conn:Mapping[str,str],
                    cload:float) -> Tuple[float,float,float]:
        '''
        Inputs:
            op_dict: Dictionary of queried database operating points.
            nf_dict: Dictionary of number of fingers for devices.
            opp_drain_conn: Drain connection dictionary, a la n_drain_conn or p_drain_conn
            cload: Load capacitance in farads.
        Outputs:
            gain: Calculated DC gain in V/V
            fbw: Calculated 3dB frequency in Hz
            ugf: Calculated unity gain frequency in Hz
            pm: Simulated unity gain phase margin in degrees. Can also be NaN.
        '''
        ckt_p = self.make_ltickt(op_dict=op_dict, nf_dict=nf_dict, meas_side='p', 
                                 opp_drain_conn=opp_drain_conn, cload=cload)
        p_num, p_den = ckt_p.get_num_den(in_name='inp', out_name='out', in_type='v')

        ckt_n = self.make_ltickt(op_dict=op_dict, nf_dict=nf_dict, meas_side='n', 
                                 opp_drain_conn=opp_drain_conn, cload=cload)
        n_num, n_den = ckt_n.get_num_den(in_name='inn', out_name='out', in_type='v')

        # Superposition for inverting and noninverting inputs
        num, den = num_den_add(p_num, np.convolve(n_num, [-1]),
                               p_den, n_den)
        num = np.convolve(num, [0.5])

        # Calculate figures of merit using the LTICircuit transfer function
        gain = num[-1]/den[-1]
        wbw = get_w_3db(num, den)
        pm, _ = get_stability_margins(num, den)

        if wbw == None:
            wbw = 0
        fbw = wbw/(2*np.pi)

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

        return gain, fbw, ugf, pm
Ejemplo n.º 6
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
Ejemplo n.º 7
0
def verify_openLoop(n_op, p_op, tail_op, nf_n, nf_p, nf_tail,
    gain_min, fbw_min, cload):
    '''
    Inputs:
    Outputs:
    '''
    ckt = construct_openLoop(n_op, p_op, tail_op, nf_n, nf_p, nf_tail, cload)
            
    num, den = ckt.get_num_den(in_name='in', out_name='out', in_type='v')
    gain = abs(num[-1]/den[-1])
    # Check gain
    if gain < gain_min:
        # Biasing sets the gain
        return False, dict(gain=gain, fbw=0)

    # Check bandwidth
    fbw = get_w_3db(num, den)/(2*np.pi)
    if fbw < fbw_min:
        return False, dict(gain=gain, fbw=fbw)
    return True, dict(gain=gain, fbw=fbw)
Ejemplo n.º 8
0
    def _find_rz_cf(self,
                    gm_db,
                    load_db,
                    vtail_list,
                    vg_list,
                    vmid_list,
                    vout_list,
                    vbias_list,
                    vb_gm,
                    vb_load,
                    cload,
                    cpar1,
                    w_dict,
                    th_dict,
                    stack_dict,
                    seg_dict,
                    gm2_list,
                    res_var,
                    phase_margin,
                    cap_tol=1e-15,
                    cap_step=10e-15,
                    cap_min=1e-15,
                    cap_max=1e-9):
        """Find minimum miller cap that stabilizes the system.

        NOTE: This function assume phase of system for any miller cap value will not loop
        around 360, otherwise it may get the phase margin wrong.  This assumption should be valid
        for this op amp.
        """
        gz_worst = float(min(gm2_list))
        gz_nom = gz_worst * (1 - res_var)
        # find maximum Cf needed to stabilize all corners
        cf_min = cap_min
        for env_idx, (vtail, vg, vmid, vout, vbias) in \
                enumerate(zip(vtail_list, vg_list, vmid_list, vout_list, vbias_list)):
            cir = self._make_circuit(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_worst)

            bin_iter = FloatBinaryIterator(cf_min,
                                           None,
                                           cap_tol,
                                           search_step=cap_step)
            while bin_iter.has_next():
                cur_cf = bin_iter.get_next()
                cir.add_cap(cur_cf, 'outp', 'xp')
                cir.add_cap(cur_cf, 'outn', 'xn')
                num, den = cir.get_num_den('in', 'out')
                cur_pm, _ = get_stability_margins(num, den)
                if cur_pm < phase_margin:
                    if cur_cf > cap_max:
                        # no way to make amplifier stable, just return
                        return None, None, None, None, None, None
                    bin_iter.up()
                else:
                    bin_iter.save()
                    bin_iter.down()
                cir.add_cap(-cur_cf, 'outp', 'xp')
                cir.add_cap(-cur_cf, 'outn', 'xn')

            # bin_iter is guaranteed to save at least one value, so don't need to worry about
            # cf_min being None
            cf_min = bin_iter.get_last_save()

        # find gain, unity gain bandwidth, and phase margin across corners
        gain_list, f3db_list, funity_list, pm_list = [], [], [], []
        for env_idx, (vtail, vg, vmid, vout, vbias) in \
                enumerate(zip(vtail_list, vg_list, vmid_list, vout_list, vbias_list)):
            cir = self._make_circuit(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_nom)
            cir.add_cap(cf_min, 'outp', 'xp')
            cir.add_cap(cf_min, 'outn', 'xn')
            num, den = cir.get_num_den('in', 'out')
            pn = np.poly1d(num)
            pd = np.poly1d(den)
            gain_list.append(abs(pn(0) / pd(0)))
            f3db_list.append(get_w_3db(num, den) / 2 / np.pi)
            funity_list.append(get_w_crossings(num, den)[0] / 2 / np.pi)
            pm_list.append(get_stability_margins(num, den)[0])

        return funity_list, 1 / gz_nom, cf_min, gain_list, f3db_list, pm_list
Ejemplo n.º 9
0
def verify_TIA_inverter_SS(n_op_info, p_op_info, nf_n, nf_p, rf, cpd, cload,
                           rdc_min, fbw_min, pm_min):
    """
    Inputs:
        n/p_op_info:    The MOSDBDiscrete library for the NMOS and PMOS
                        devices in the bias point for verification.
        nf_n/p:         Integer. Number of channel fingers for the NMOS/PMOS.
        rf:             Float. Value of the feedback resistor in ohms.
        cpd:            Float. Input capacitance not from the TIA in farads.
        cload:          Float. Output capacitance not from the TIA in farads.
        rdc_min:        Float. Minimum DC transimpedance in ohms.
        fbw_min:        Float. Minimum bandwidth (Hz).
        pm_min:         Float. Minimum phase margin in degrees.
    Outputs:
        Returns two values
        The first is True if the spec is met, False otherwise.
        The second is a dictionary of values for rdc (DC transimpedance, V/I), 
        bw (bandwidth, Hz), and pm (phase margin, deg) if computed. None otherwise.
    """
    # Getting relevant small-signal parameters
    gds_n = n_op_info['gds'] * nf_n
    gds_p = p_op_info['gds'] * nf_p
    gds = gds_n + gds_p

    gm_n = n_op_info['gm'] * nf_n
    gm_p = p_op_info['gm'] * nf_p
    gm = gm_n + gm_p

    cgs_n = n_op_info['cgs'] * nf_n
    cgs_p = p_op_info['cgs'] * nf_p
    cgs = cgs_n + cgs_p

    cds_n = n_op_info['cds'] * nf_n
    cds_p = p_op_info['cds'] * nf_p
    cds = cds_n + cds_p

    cgd_n = n_op_info['cgd'] * nf_n
    cgd_p = p_op_info['cgd'] * nf_p
    cgd = cgd_n + cgd_p

    # Circuit for GBW
    circuit = LTICircuit()
    circuit.add_transistor(n_op_info, 'out', 'in', 'gnd', fg=nf_n)
    circuit.add_transistor(p_op_info, 'out', 'in', 'gnd', fg=nf_p)
    circuit.add_res(rf, 'in', 'out')
    circuit.add_cap(cpd, 'in', 'gnd')
    circuit.add_cap(cload, 'out', 'gnd')

    # Check gain
    num, den = circuit.get_num_den(in_name='in', out_name='out', in_type='i')
    rdc = num[-1] / den[-1]

    if abs(round(rdc)) < round(rdc_min):
        print("GAIN:\t{0} (FAIL)".format(rdc))
        return False, dict(rdc=rdc, fbw=None, pm=None)

    # Check bandwidth
    fbw = get_w_3db(num, den) / (2 * np.pi)
    if fbw < fbw_min or np.isnan(fbw):
        print("BW:\t{0} (FAIL)".format(fbw))
        return False, dict(rdc=rdc, fbw=fbw, pm=None)

    # Check phase margin by constructing an LTICircuit first
    circuit2 = LTICircuit()
    """circuit2.add_transistor(n_op_info, 'out', 'in', 'gnd', fg=nf_n)
    circuit2.add_transistor(p_op_info, 'out', 'in', 'gnd', fg=nf_p)
    circuit2.add_cap(cpd, 'in', 'gnd')
    circuit2.add_cap(cload, 'out', 'gnd')
    circuit2.add_res(rf, 'in', 'break')
    # Cancel Cgd to correctly break loop
    circuit2.add_cap(-cgd, 'in' , 'out')
    circuit.add_cap(cgd, 'in', 'break')"""

    circuit2.add_conductance(gds, 'out', 'gnd')
    circuit2.add_cap(cgs + cpd, 'in', 'gnd')
    circuit2.add_cap(cds + cload, 'out', 'gnd')
    circuit2.add_cap(cgd, 'in', 'out')
    circuit2.add_res(rf, 'in', 'out')

    loopBreak = circuit2.get_transfer_function(in_name='in',
                                               out_name='out',
                                               in_type='i')
    pm, gainm = get_stability_margins(loopBreak.num * gm, loopBreak.den)
    if pm < pm_min or np.isnan(pm):
        print("PM:\t{0} (FAIL)\n".format(pm))
        return False, dict(rdc=rdc, fbw=fbw, pm=pm)
    print("SUCCESS\n")
    return True, dict(rdc=rdc, fbw=fbw, pm=pm)
Ejemplo n.º 10
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
Ejemplo n.º 11
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
Ejemplo n.º 12
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
Ejemplo n.º 13
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
Ejemplo n.º 14
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
Ejemplo n.º 15
0
 circuit.add_transistor(op_trim_on, 'outn', 'gnd', 'gnd', fg=nf_trim)
 circuit.add_transistor(op_trim_off, 'outp', 'gnd', 'gnd', fg=nf_trim)
 circuit.add_transistor(op_tail, 'tail', 'vmirr', 'gnd', fg=nf_tail)
 circuit.add_transistor(op_mirr, 'vmirr', 'vmirr', 'gnd', fg=nf_mirr)
 circuit.add_transistor(op_dummy, 'gnd', 'gnd', 'vmirr', fg=nf_dummy)
 circuit.add_res(rload, 'outp', 'gnd')
 circuit.add_res(rload, 'outn', 'gnd')
 circuit.add_cap(cload, 'outp', 'gnd')
 circuit.add_cap(cload, 'outn', 'gnd')
 circuit.add_cap(dac_cap, 'tail', 'gnd')
 num, den = circuit.get_num_den(in_name='inp', out_name='outn', in_type='v')
 num_unintentional, den_unintentional = circuit.get_num_den(in_name='inp', out_name='outp', in_type='v')
 gain_intentional = num[-1]/den[-1]
 gain_unintentional = num_unintentional[-1]/den_unintentional[-1]
 gain = abs(gain_intentional-gain_unintentional)
 wbw = get_w_3db(num, den)
 if wbw == None:
     wbw = 0
 fbw = wbw/(2*np.pi)
 if fbw < bw_min:
     cond_print("Bandwidth fail: {}".format(fbw), debugMode)
     cond_print("NF,IN: {}".format(nf_in), debugMode)
     cond_print("NF,TAIL: {}".format(nf_tail), debugMode)
     cond_print("NF,MIRR: {}".format(nf_mirr), debugMode)
     cond_print("NF,DUMMY: {}".format(nf_dummy), debugMode)
     continue
 elif gain < gain_min:
     cond_print("Gain Low: {}".format(gain), debugMode)
     continue
 else:
     cond_print("(SUCCESS)")
Ejemplo n.º 16
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
Ejemplo n.º 17
0
def design_LPF_AMP(db_n, db_p, db_bias, sim_env,
    vin, vdd_nom, vdd_vec, cload,
    gain_min, fbw_min, pm_min,
    vb_n, vb_p, error_tol=0.1, ibias_max=20e-6):
    '''
    Designs an amplifier with an N-input differential pair
    with a source follower. 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.
        vdd_vec:    Collection of floats. Elements should include the min and
                    max 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).
        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_cs_n/pB/nB:  Integer. Number of minimum device widths for common source
                        N-input/active P-load/tail device.
        nf_sfN_n/pB/nB: Integer. Number of minimum device widths for source follower
                        N-input/P-bias/N-bias.
        nf_sfP_p/pB/nB: Integer. Number of minimum device widths for source follower
                        P-input/P-bias/N-bias.
        vtail:          Float. DC tail voltage for the common source stage.
                        This value is reused throughout the circuit.
        gain_cs:        Float. DC voltage gain of the CS amplifier (V/V).
        gain_sfN:       Float. DC voltage gain of the first source follower (V/V).
        gain_sfP:       Float. DC voltage gain of the second source follower (V/V).
        gain:           Float. DC voltage gain of both stages combined (V/V).
        fbw:            Float. Bandwdith (Hz).
        pm:             Float. Phase margin (degrees) for unity gain.
    '''
    possibilities = []
    ibias_budget = ibias_max
    
    # Given this amplifier will be unity gain feedback at DC
    vout = vin
    
    vstar_min = 0.15
    vtail_vec = np.arange(vstar_min, vout, vtail_res)
    for vtail in vtail_vec:
        print("\nVTAIL: {0}".format(vtail))
        n_op = db_n.query(vgs=vin-vtail, vds=vout-vtail, vbs=vb_n-vtail)
        p_B_op = db_p.query(vgs=vout-vdd_nom, vds=vout-vdd_nom, vbs=vb_p-vdd_nom)
        n_B_op = db_bias.query(vgs=vout-0, vds=vtail-0, vbs=vb_n-0)
        p_op = db_p.query(vgs=vtail-vout, vds=vtail-vout, vbs=vb_p-vout)
    
        # Finding the ratio between devices to converge to correct biasing
        idn_base = n_op['ibias']
        idp_B_base = p_B_op['ibias']
        idn_B_base = n_B_op['ibias']
        idp_base = p_op['ibias']
                
        pB_to_n = abs(idn_base/idp_B_base)
        nB_to_n = abs(idn_base/idn_B_base)
        pB_to_p = abs(idp_base/idp_B_base)
        nB_to_p = abs(idp_base/idn_B_base)
        
        ### P-input SF ###
        sfP_mult_max = int(round(abs(ibias_budget/idn_base)))
        sfP_mult_vec = np.arange(1, sfP_mult_max, 1)
        for sfP_mult in sfP_mult_vec in 
            nf_sfP_p = sfP_mult
            
            # Verify that the sizing is feasible and gets sufficiently
            # good current matching
            pB_sfP_good, nf_sfP_pB = verify_ratio(idp_base, idp_B_base, pB_to_p, 
                                        nf_sfP_p, error_tol)
            nB_sfP_good, nf_sfP_nB = verify_ratio(idp_base, idn_B_base, nB_to_p,
                                        nf_sfP_p, error_tol)
            if not (pB_sfP_good and nB_sfP_good):
                continue
            
            # Check SF2 bandwidth; size up until it meets
            ckt = construct_sfP_LTI(p_op, p_B_op, n_B_op, 
                    nf_sfP_p, nf_sfP_pB, nf_sfP_nB,
                    cload)
            sfP_num, sfP_den = ckt.get_num_den(in_name='out2', out_name='out', in_type='v')
            fbw_sfP = get_w_3db(sfP_num, sfP_den)/(2*np.pi)
            
            # No need to keep sizing up
            if fbw_sfP >= fbw_min:
                break
        
        # Final stage can never meet bandwidth given biasing
        if fbw_sfP < fbw_min:
            print("SFP: BW failure\n")
            continue
        
        # Sizing ratio failure
        if 0 in [nf_sfP_pB, nf_sfP_nB]:
            print("SFP: sizing ratio failure\n")
            continue
        
        ibias_sfP = idp_base * nf_sfP_p
        ibias_budget = ibias_budget - ibias_sfP
        print("Remaining budget\t:\t {0}".format(ibias_budget))
        
        ### N-input SF ###
        sfN_mult_max = int(round(abs(ibias_budget/idn_base)))
        sfN_mult_vec = np.arange(1, sfN_mult_max, 1)
        for sfN_mult in sfN_mult_vec:
            nf_sfN_n = sfN_mult
            
            # Repeat the same feasibility + current matching check
            pB_sfN_good, nf_sfN_pB = verify_ratio(idn_base, idp_B_base, 
                                            pB_to_n, nf_sfN_n, error_tol)
            nB_sfN_good, nf_sfN_pN = verify_ratio(idn_base, idn_B_base,
                                            nB_to_n, nf_sfN_n, error_tol)
            if not (pB_sfn_good and nB_sfN_good):
                continue
            
            # Check SF1 bandwidth; size up until it meets
            ckt = construct_sfN_LTI(p_op, p_B_op, n_B_op, 
                    nf_sfP_p, nf_sfP_pB, nf_sfP_nB,
                    cload,
                    n_op,
                    nf_sfN_n, nf_sfN_pB, nf_sfN_nB)
            sfN_num, sfN_den = ckt.get_num_den(in_name='out1', out_name='out', in_type='v')
            fbw_sfN = get_w_3db(sfN_num, sfN_den)/(2*np.pi)
            
            # No need to keep sizing up
            if fbw_sfN >= fbw_min:
                break
            
        # Second stage can never meet bandwidth given restrictions
        if fbw_sfN < fbw_min:
            print("SFN: BW failure\n")
            continue
        
        # Sizing ratio failure
        if 0 in [nf_sfN_pB, nf_sfN_nB]:
            print("SFN: sizing ratio failure\n")
            continue
        
        ibias_sfN = idn_base * nf_sfN_n
        ibias_budget = ibias_budget - ibias_sfN
        print("Remaining budget\t:\t {0}".format(ibias_budget))
        
        ### CS input ###
        cs_mult_max = int(round(abs(ibias_budget/idn_base)))
        cs_mult_vec = np.arange(1, cs_mult_max, 1)
        for cs_mult in cs_mult_vec:
            nf_cs_n = cs_mult_vec
            
            # Verify that the sizing is feasible and gets sufficiently
            # good current matching
            pB_cs_good, nf_cs_pB = verify_ratio(idn_base, idp_B_base, pB_to_n, nf_cs_n, 
                                        error_tol)
            nB_cs_good, nf_cs_nB = verify_ratio(idn_base, idn_B_base, nB_to_n, nf_cs_n*2,
                                        error_tol)
            if not (pB_cs_good and nB_cs_good):
                continue 
            
            # Check combined stages' small signal
            ckt = construct_total_LTI(p_op, p_B_op, n_B_op, 
                    nf_sfP_p, nf_sfP_pB, nf_sfP_nB,
                    cload,
                    n_op,
                    nf_sfN_n, nf_sfN_pB, nf_sfN_nB,
                    nf_cs_n, nf_cs_pB, nf_cs_nB)
            total_num, total_den = ckt.get_num_den(in_name='in', out_name='out', in_type='v')
            
            # Check cumulative gain
            gain_total = abs(total_num[-1]/total_den[-1])
            if gain_total < gain_min:
                print("CS: A0 failure {0}\n".format(gain_total))
                break # Biasing sets the gain
            
            # Check cumulative bandwidth
            fbw_total = get_w_3db(total_num, total_den)/(2*np.pi)
            if fbw_total < fbw_min:
                print("CS: BW failure {0}\n".format(fbw_total))
                continue
                
            # Check phase margin (TODO?)
            loopBreak = ckt.get_transfer_function(in_name='out', out_name='in', in_type='v')
            pm, gainm = get_stability_margins(loopBreak.num, loopBreak.den)
            if pm < pm_min or isnan(pm):
                print("CS: PM failure {0}\n".format(pm))
                continue
                
            # If gain, bw, and PM are met, no need to keep sizing up
            break

        # Sizing ratio failure
        if 0 in [nf_cs_pB, nf_cs_nB]:
            print("CS: sizing ratio failure\n")
            continue
        
        # Iterated, biasing condition + constraints can't meet spec
        if fbw_total < fbw_min or gain_total < gain_min or pm < pm_min:
            continue
        else:
            print("HALLELUJAH SUCCESS\n")
            cs_num, cs_den = ckt.get_num_den(in_name='in', out_name='out1', in_type='v')
            sfN_num, sfN_den = ckt.get_num_den(in_name='out1', out_name='out2', in_type='v')
            sfP_num, sfP_den = ckt.get_num_den(in_name='out2', out_name='out', in_type='v')
            
            gain_cs = abs(cs_num[-1]/cs_den[-1])
            gain_sfN = abs(sfN_num[-1]/sfN_den[-1])
            gain_sfP = abs(sfP_num[-1]/sfP_den[-1])
            viable = dict(
                        nf_cs_n     = nf_cs_n,
                        nf_cs_pB    = nf_cs_pB,
                        nf_cs_nB    = nf_cs_nB,
                        nf_sfN_n    = nf_sfN_n,
                        nf_sfN_pB   = nf_sfN_pB,
                        nf_sfN_nB   = nf_sfN_nB,
                        nf_sfP_p    = nf_sfP_p,
                        nf_sfP_pB   = nf_sfP_pB,
                        nf_sfP_nB   = nf_sfP_nB,
                        vtail       = vtail,
                        gain_cs     = gain_cs,
                        gain_sfN    = gain_sfN,
                        gain_sfP    = gain_sfP,
                        gain        = gain_total,
                        fbw         = fbw_total,
                        pm          = pm)
            pprint.pprint(viable)
            print("\n")
            possibilities.append([viable])
        
    # TODO: Check all other VDDs
    return possibilities
Ejemplo n.º 18
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
Ejemplo n.º 19
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
Ejemplo n.º 20
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
def design_diffAmpP(db_n,
                    db_p,
                    sim_env,
                    vdd,
                    cload,
                    vincm,
                    T,
                    gain_min,
                    fbw_min,
                    pm_min,
                    inoiseout_std_max,
                    vb_p,
                    vb_n,
                    error_tol=0.05):
    """
    Inputs:
    Returns:
    """
    # Constants
    kBT = 1.38e-23 * T

    # TODO: change hardcoded value
    vstar_min = 0.15

    # find the best operating point
    best_ibias = float('inf')
    best_op = None

    # (loosely) constrain tail voltage
    # TODO: replace with binary search
    vtail_min = vincm + 2 * vstar_min
    vtail_max = vdd
    vtail_vec = np.arange(vtail_min, vtail_max, 10e-3)

    # sweep tail voltage
    for vtail in vtail_vec:
        # (loosely) constrain output DC voltage
        # TODO: replace with binary search
        vout_min = vstar_min
        vout_max = vtail - vstar_min
        vout_vec = np.arange(vout_min, vout_max, 10e-3)
        # sweep output DC voltage
        for vout in vout_vec:
            in_op = db_p.query(vgs=vincm - vtail,
                               vds=vout - vtail,
                               vbs=vb_p - vtail)
            load_op = db_n.query(vgs=vout, vds=vout, vbs=vb_n - 0)
            # TODO: constrain number of input devices
            nf_in_min = 4
            nf_in_max = 30
            nf_in_vec = np.arange(nf_in_min, nf_in_max, 2)
            for nf_in in nf_in_vec:
                ibranch = abs(in_op['ibias'] * nf_in)
                if ibranch * 2 > best_ibias:
                    continue
                # matching NMOS and PMOS bias current
                nf_load = int(abs(round(ibranch / load_op['ibias'])))
                if nf_load < 1:
                    continue
                iload = load_op['ibias'] * nf_load
                ibranch_error = (abs(iload) - abs(ibranch)) / abs(ibranch)
                if ibranch_error > error_tol:
                    continue

                # create LTICircuit
                amp = LTICircuit()
                amp.add_transistor(in_op, 'out', 'in', 'gnd', 'gnd', fg=nf_in)
                amp.add_transistor(load_op,
                                   'out',
                                   'gnd',
                                   'gnd',
                                   'gnd',
                                   fg=nf_load)
                amp.add_cap(cload, 'out', 'gnd')
                num, den = amp.get_num_den(in_name='in',
                                           out_name='out',
                                           in_type='v')

                gm = in_op['gm'] * nf_in
                ro = 1 / (in_op['gds'] * nf_in + load_op['gds'] * nf_load)

                # Check against gain
                gain = abs(num[-1] / den[-1])
                if gain < gain_min:
                    print("GAIN: {0:.2f} (FAIL)\n".format(gain))
                    continue
                print("GAIN: {0:.2f}".format(gain))

                # Check against bandwidth
                wbw = get_w_3db(num, den)
                if wbw == None:
                    print("BW: None (FAIL)\n")
                    continue
                fbw = wbw / (2 * np.pi)
                if fbw < fbw_min:
                    print("BW: {0:.2f} (FAIL)\n".format(fbw))
                    continue
                print("BW: {0:.2f}".format(fbw))
                pm, gainm = get_stability_margins(num, den)

                # Check against phase margin
                if pm < pm_min or isnan(pm):
                    print("PM: {0:.2f} (FAIL)\n".format(pm))
                    continue
                print("PM: {0:.2f}".format(pm))

                # Check against noise
                inoiseout_std = np.sqrt(
                    4 * kBT * (in_op['gamma'] * in_op['gm'] * nf_in * 2 +
                               load_op['gamma'] * load_op['gm'] * nf_load * 2))
                if inoiseout_std > inoiseout_std_max:
                    print("INOISE STD: {} (FAIL)\n".format(inoiseout_std))
                    continue
                print("INOISE STD: {}".format(inoiseout_std))

                # Check against best bias current
                if ibranch * 2 < best_ibias:
                    biasing_spec = dict(
                        db_n=db_n,
                        db_p=db_p,
                        sim_env=sim_env,
                        vdd=vdd,
                        vout=vout,
                        vincm=vincm,
                        nf_in=nf_in,
                        nf_load=nf_load,
                        vtail_target=vtail,
                        itail_target=ibranch * 2,
                        iref_res=10e-9,
                        vb_p=vb_p,
                        vb_n=vb_n,
                        error_tol=error_tol,
                    )
                    biasing_params = design_biasing(**biasing_spec)
                    if biasing_params == None:
                        print("BIASING PARAMS (FAIL)\n")
                        continue
                    print("(SUCCESS)\n")
                    best_ibias = ibranch * 2
                    best_op = dict(
                        itail=best_ibias,
                        nf_in=nf_in,
                        nf_load=nf_load,
                        vout=vout,
                        vtail=vtail,
                        gain=gain,
                        fbw=fbw,
                        pm=pm,
                        nf_tail=biasing_params['nf_tail'],
                        nf_bias_tail=biasing_params['nf_bias_tail'],
                        nf_bias_in=biasing_params['nf_bias_in'],
                        nf_bias_loadM=biasing_params['nf_bias_loadM'],
                        nf_bias_load1=biasing_params['nf_bias_load1'],
                        iref=biasing_params['iref'],
                        inoiseout_std=inoiseout_std)
                    break
    if best_op == None:
        raise ValueError("No solution for P-in diffamp")
    return best_op