def design_constgmN(db_n, db_p, vdd, vb_n, vb_p, K, voutn, voutp, iref_max=5e-6, error_tol=.1): """ Inputs: db_n/p: vdd: vb_n/p: K: voutn/p: iref_max: error_tol: """ op_n1 = db_n.query(vgs=voutn, vds=voutp, vbs=vb_n) op_p = db_p.query(vgs=voutp - vdd, vds=voutp - vdd, vbs=vb_p - vdd) nf_n1_min = 2 if np.isinf(iref_max): nf_n1_max = 200 else: nf_n1_max = int(round(iref_max / op_n1['ibias'])) nf_n1_vec = np.arange(nf_n1_min, nf_n1_max, 2) for nf_n1 in nf_n1_vec: nf_nK = K * nf_n1 p_match_good, nf_p = verify_ratio(op_n1['ibias'], op_p['ibias'], nf_n1, error_tol) if not p_match_good: continue ibias_1 = op_n1['ibias'] * nf_n1 vr_iter = FloatBinaryIterator(low=0, high=voutp, tol=0, search_step=voutp / 2**10) # vr = vr_iter.get_next() while vr_iter.has_next(): vr = vr_iter.get_next() op_nK = db_n.query(vgs=voutn - vr, vds=voutp - vr, vbs=vb_n - vr) ibias_K = op_nK['ibias'] * nf_nK ierror = (abs(ibias_1 - ibias_K)) / min(abs(ibias_1), abs(ibias_K)) if ierror <= error_tol: return dict(nf_n1=nf_n1, nf_nK=nf_nK, nf_p=nf_p, rsource=vr / ibias_K, ibias_K=ibias_K, ibias_1=ibias_1) elif ibias_K > ibias_1: vr_iter.up() else: vr_iter.down() raise ValueError("No viable solution.")
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
def design_preamp_chain(db_n, db_p, lch, w_finger, vdd, cload, vin_signal, vin_shift, vtail_res, gain_min, fbw_min, vb_p, vb_n, itail_max=10e-6, error_tol=0.01): """ Designs a preamplifier to go between the comparator and TIA. N-input differential pair with an active PMOS load. Inputs: db_n/p: Database with NMOS/PMOS info lch: Float. Channel length (m) w_finger: Float. Width of a single finger (m) vdd: Float. Supply voltage (V) cload: Float. Load capacitance (F) vin_signal: Float. Input bias voltage (V). vin_shift: Float. Gate bias voltage for the non-signal-facing input device (V). vtail_res: Float. Resolution for tail voltage of amplifying and offset devices for sweep (V). gain_min: Float. Minimum gain (V/V) fbw_min: Float. Minimum bandwidth (Hz) vb_n/p: Float. Body/back-gate voltage (V) of NMOS and PMOS devices. itail_max: Float. Max allowable tail current of the main amplifier (A). error_tol: Float. Fractional tolerance for ibias error when computing device ratios. Raises: ValueError if there is no solution given the requirements. Outputs: nf_in: Integer. Number of fingers for input devices. nf_tail: Integer. Number of fingers for tail device. nf_load: Integer. Number of fingers for active load devices. itail: Float. Tail current of main amp (A). gain: Float. Amp gain (V/V). fbw: Float. Bandwidth (Hz). cin: Float. Approximation of input cap (F). vtail: Float. Tail voltage (V). """ vincm = (vin_signal + vin_shift) / 2 voutcm = vincm best_op = None best_itail = np.inf vthn = estimate_vth(db_n, vdd, 0) vtail_min = 0.15 # TODO: Avoid hardcoding? vtail_max = vincm vtail_vec = np.arange(vtail_min, vtail_max, vtail_res) for vtail in vtail_vec: print('VTAIL:\t{0}'.format(vtail)) op_tail = db_n.query(vgs=voutcm, vds=vtail, vbs=vb_n) op_in = db_n.query(vgs=vincm - vtail, vds=voutcm - vtail, vbs=vb_n - vtail) op_load = db_p.query(vgs=voutcm - vdd, vds=voutcm - vdd, vbs=vb_p - vdd) nf_in_min = 2 nf_in_max = min(100, int(round(.5 * itail_max / op_in['ibias']))) nf_in_vec = np.arange(nf_in_min, nf_in_max, 2) for nf_in in nf_in_vec: # Check if those bias points are feasible given the # device sizing quantization load_ratio_good, nf_load = verify_ratio(op_in['ibias'], op_load['ibias'], nf_in, error_tol) if not load_ratio_good: continue tail_ratio_good, nf_tail_half = verify_ratio( op_in['ibias'], op_tail['ibias'], nf_in, error_tol) nf_tail = nf_tail_half * 2 if not tail_ratio_good: continue # Check if it's burning more power than previous solutions itail = op_tail['ibias'] * nf_tail if itail > best_itail: break # Check the half circuit for small signal parameters half_circuit = LTICircuit() half_circuit.add_transistor(op_in, 'out', 'in', 'tail', fg=nf_in) half_circuit.add_transistor(op_load, 'out', 'gnd', 'gnd', fg=nf_load) half_circuit.add_transistor(op_tail, 'tail', 'gnd', 'gnd', fg=nf_tail_half) half_circuit.add_cap(cload, 'out', 'gnd') num, den = half_circuit.get_num_den(in_name='in', out_name='out', in_type='v') gain = abs(num[-1] / den[-1]) if gain < gain_min: print("(FAIL) GAIN:\t{0}".format(gain)) break wbw = get_w_3db(num, den) if wbw == None: wbw = 0 fbw = wbw / (2 * np.pi) if fbw < fbw_min: print("(FAIL) BW:\t{0}".format(fbw)) continue cin = nf_in * (op_in['cgs'] + op_in['cgd'] * (1 + gain)) best_itail = itail best_op = dict(nf_in=nf_in, nf_tail=nf_tail, nf_load=nf_load, itail=itail, gain=gain, fbw=fbw, vtail=vtail, cin=cin) if best_op == None: raise ValueError("No viable solutions.") return best_op
def design_LPF_AMP(db_n, db_p, db_bias, sim_env, vin, vdd_nom, cload, vtail_res, gain_min, fbw_min, pm_min, vb_n, vb_p, error_tol=0.1, ibias_max=20e-6, debugMode=False): ''' Designs an amplifier with an N-input differential pair. Uses the LTICircuit functionality. Inputs: db_n/p: Databases for non-biasing NMOS and PMOS device characterization data, respectively. db_bias: Database for tail NMOS device characterization data. sim_env: Simulation corner. vin: Float. Input (and output and tail) bias voltage in volts. vtail_res: Float. Step resolution in volts when sweeping tail voltage. vdd_nom: Float. Nominal supply voltage in volts. cload: Float. Output load capacitance in farads. gain_min: Float. Minimum DC voltage gain in V/V. fbw_min: Float. Minimum bandwidth (Hz) of open loop amp. pm_min: Float. Minimum phase margin in degrees. vb_n/p: Float. Back-gate/body voltage (V) of NMOS and PMOS, respectively (nominal). error_tol: Float. Fractional tolerance for ibias error when computing the p-to-n ratio. ibias_max: Float. Maximum bias current (A) allowed with nominal vdd. Raises: ValueError: If unable to meet the specification requirements. Outputs: A dictionary with the following key:value pairings: nf_n: nf_p: nf_tail: gain: Float. DC voltage gain of both stages combined (V/V). fbw: Float. Bandwdith (Hz). pm: Float. Phase margin (degrees) for unity gain. ''' possibilities = [] vout = vin vstar_min = 0.15 vtail_vec = np.arange(vstar_min, vout, vtail_res) for vtail in vtail_vec: cond_print("VTAIL: {0}".format(vtail), debugMode) n_op = db_n.query(vgs=vin-vtail, vds=vout-vtail, vbs=vb_n-vtail) p_op = db_p.query(vgs=vout-vdd_nom, vds=vout-vdd_nom, vbs=vb_p-vdd_nom) tail_op = db_bias.query(vgs=vout, vds=vtail, vbs=vb_n) idn_base = n_op['ibias'] idp_base = p_op['ibias'] idtail_base = tail_op['ibias'] p_to_n = abs(idn_base/idp_base) tail_to_n = abs(idn_base/idtail_base) nf_n_max = int(round(abs(ibias_max/(idn_base*2)))) nf_n_vec = np.arange(2, nf_n_max, 2) for nf_n in nf_n_vec: cond_print("\tNF_N: {0}".format(nf_n), debugMode) # Verify that sizing is feasible and gets sufficiently # close current matching p_good, nf_p = verify_ratio(idn_base, idp_base, p_to_n, nf_n, error_tol) tail_good, nf_tail = verify_ratio(idn_base, idtail_base, tail_to_n, nf_n*2, error_tol) if not (p_good and tail_good): cond_print("\t\tP_BIAS: {0}\n\t\tT_BIAS: {1}".format(p_good, tail_good), debugMode) cond_print("\t\tP_SIZE: {0}\n\t\tT_SIZE: {1}".format(nf_p, nf_tail), debugMode) tail_error = abs(abs(idtail_base*nf_tail) - abs(idn_base*nf_n*2))/abs(idn_base*nf_n*2) cond_print("\t\tTAIL ERROR: {0}".format(tail_error), debugMode) continue # Devices are sized, check open loop amp SS spec openLoop_good, openLoop_params = verify_openLoop(n_op, p_op, tail_op, nf_n, nf_p, nf_tail, gain_min, fbw_min, cload) if not openLoop_good: # Gain is set by the bias point if openLoop_params['gain'] < gain_min: cond_print("\t\tGAIN: {0} (FAIL)".format(openLoop_params['gain']), debugMode) break cond_print("\t\tBW: {0} (FAIL)".format(openLoop_params['fbw']), debugMode) # Bandwidth isn't strictly set by biasing continue # Check PM in feedback closedLoop_good, closedLoop_params = verify_closedLoop(n_op, p_op, tail_op, nf_n, nf_p, nf_tail, pm_min, cload) if closedLoop_good: viable = dict(nf_n=nf_n, nf_p=nf_p, nf_tail=nf_tail, gain=openLoop_params['gain'], fbw=openLoop_params['fbw'], pm=closedLoop_params['pm'], ibias=abs(idtail_base*nf_tail), vtail=vtail) possibilities.append(viable) if len(possibilities) == 0: return ValueError("No viable solutions.") else: print("{0} viable solutions".format(len(possibilities))) best_ibias = float('inf') best_op = None for candidate in possibilities: if candidate['ibias'] < best_ibias: best_op = candidate best_ibias = candidate['ibias'] return best_op
def design_levelshift_bias(db_n, vdd, voutcm, voutdiff, vb_n, error_tol=0.01, vtail_res=5e-3, vstar_min=.15, ibias_max=2e-6): ''' Designs gate biasing network for level shifting (current shunting) devices. Minimizes current consumption. Inputs: db_n: Database for NMOS device characterization data. vb_n: Float. Back-gate/body voltage (V) of NMOS devices. error_tol: Float. Fractional tolerance for ibias error when computing device ratios. vtail_res: Float. Resolution for sweeping tail voltage (V). vstar_min: Float. Minimum vstar voltage (V) for design. Outputs: Returns a dictionary with the following: Rtop: Float. Value of the top resistor (ohms). Rbot: Float. Value of the lower resistor (ohms). nf_in: Integer. Number of fingers for the "input" devices. nf_tail: Integer. Number of fingers for the tail device. vtail: Float. Tail voltage (V). ibias: Float. Bias current (A). ''' voutn = voutcm - voutdiff/2 voutp = voutcm + voutdiff/2 vgtail = voutn best_ibias = np.inf best_op = None # Sweep device tail voltage vtail_min = vstar_min vtail_max = voutn vtail_vec = np.arange(vtail_min, vtail_max, vtail_res) for vtail in vtail_vec: print("VTAIL:\t{0}".format(vtail)) op_tail = db_n.query(vgs=vgtail, vds=vtail, vbs=vb_n) op_in = db_n.query(vgs=vdd-vtail, vds=voutn-vtail, vbs=vb_n-vtail) # Based on max current, set upper limit on tail device size nf_tail_max = round(ibias_max/abs(op_tail['ibias'])) if nf_tail_max < 1: print("FAIL: Tail too small") continue # Sweep size of tail device until good bias current matching # between the input and tail device is found nf_tail_vec = np.arange(2, nf_tail_max, 2) for nf_tail in nf_tail_vec: # Don't bother sizing up if the current consumption is higher # than the last viable solution ibias = abs(op_tail['ibias']*nf_tail) if ibias > best_ibias: break imatch_good, nf_in = verify_ratio(abs(op_tail['ibias']), abs(op_in['ibias']), nf_tail, error_tol) if imatch_good: print("SUCCESS") Rtop = (vdd-voutp)/ibias Rbot = voutdiff/ibias best_op = dict( Rtop=Rtop, Rbot=Rbot, nf_in=nf_in, nf_tail=nf_tail, vtail=vtail, ibias=ibias, voutp=voutp, voutn=voutn) else: print("FAIL: Bad current match") if best_op == None: raise ValueError("PREAMP BIAS: No solution") return best_op
vbs=vb_n-vtail) op_trim_on = db_n.query(vgs=vtrimp-vtail, vds=voutcm-vtail, vbs=vb_n-vtail) op_trim_off = db_n.query(vgs=vtrimn-vtail, vds=voutcm-vtail, vbs=vb_n-vtail) # Calculate the max tail size based on current limit # and size input devices accordingly nf_tail_min = 2 nf_tail_max = int(round(itail_max/op_tail['ibias']) nf_tail_vec = np.arange(nf_tail_min, nf_tail_max, 2) for nf_tail in nf_tail_vec: amp_ratio_good, nf_in = verify_ratio(op_tail['ibias'], op_in['ibias'], nf_tail, error_tol) if amp_ratio_good: # Size offset devices #################### #################### # If there's no need for shifting or trim if vos_targ < 1e-6: vtail_trim = vout_cm op_trim_on = db_n.query(vgs=0, vds=0, vbs=vb_n-0) op_trim_off = db_n.query(vgs=0, vds=0, vbs=vb_n-0) # If there's need for shifting else:
def design_TIA_inverter(db_n, db_p, sim_env, vg_res, rf_res, vdd_nom, vdd_vec, cpd, cload, rdc_min, fbw_min, pm_min, BER_max, vos, isw_pkpk, vb_n, vb_p, error_tol=0.05, ibias_max=20e-6): """ Designs a transimpedance amplifier with an inverter amplifier in resistive feedback. Uses the LTICircuit functionality. Inputs: db_n/p: Databases for NMOS and PMOS device characterization data, respectively. sim_env: Simulation corner. vg_res: Float. Step resolution in volts when sweeping gate voltage. rf_res: Float. Step resolution in ohms when sweeping feedback resistance. vdd_nom: Float. Nominal supply voltage in volts. vdd_vec: Collection of floats. Elements should include the min and max supply voltage in volts. cpd: Float. Input parasitic capacitance in farads. cload: Float. Output load capacitance in farads. rdc_min: Float. Minimum DC transimpedance in ohms. fbw_min: Float. Minimum bandwidth (Hz). pm_min: Float. Minimum phase margin in degrees. BER_max: Float. Maximum allowable bit error rate (as a fraction). vos: Float. Input-referred DC offset for any subsequent comparator stage as seen at the output of the TIA. isw_pkpk: Float. Input current peak-to-peak swing in amperes. vb_n/p: Float. Back-gate/body voltage (V) of NMOS and PMOS, respectively. error_tol: Float. Fractional tolerance for ibias error when computing the p-to-n ratio. ibias_max: Float. Maximum bias current (A) allowed. Raises: ValueError: If unable to meet the specification requirements. Outputs: A dictionary with the following key:value pairings: vg: Float. Input bias voltage. nf_n: Integer. NMOS number of channel fingers. nf_p: Integer. PMOS number of channel fingers. rf: Float. Value of feedback resistor. rdc: Float. Expected DC transimpedance. fbw: Float. Expected bandwidth (Hz). pm: Float. Expected phase margin. ibias: Float. Expected DC bias current. """ # Finds all possible designs for one value of VDD, then # confirm which work with all other VDD values. possibilities = [] vg_vec = np.arange(0, vdd_nom, vg_res) for vg in vg_vec: print("VIN:\t{0}".format(vg)) n_op_info = db_n.query(vgs=vg, vds=vg, vbs=vb_n - 0) p_op_info = db_p.query(vgs=vg - vdd_nom, vds=vg - vdd_nom, vbs=vb_p - vdd_nom) if np.isinf(ibias_max): nf_n_max = 200 else: nf_n_max = int(round(ibias_max / n_op_info['ibias'])) nf_n_vec = np.arange(1, nf_n_max, 1) for nf_n in nf_n_vec: # Number of fingers can only be integer, # so increase as necessary until you get # sufficiently accurate/precise bias + current match ratio_good, nf_p = verify_ratio(n_op_info['ibias'], p_op_info['ibias'], nf_n, error_tol) if not ratio_good: continue # Getting small signal parameters to constrain Rf inv = LTICircuit() inv.add_transistor(n_op_info, 'out', 'in', 'gnd', fg=nf_n) inv.add_transistor(p_op_info, 'out', 'in', 'gnd', fg=nf_p) inv_num, inv_den = inv.get_num_den(in_name='in', out_name='out', in_type='v') A0 = abs(inv_num[-1] / inv_den[-1]) gds_n = n_op_info['gds'] * nf_n gds_p = p_op_info['gds'] * nf_p gds = abs(gds_n) + abs(gds_p) ro = 1 / gds # Assume Rdc is negative, bound Rf rf_min = max(rdc_min * (1 + A0) / A0 + ro / A0, 0) rf_vec = np.arange(rf_min, rdc_min * 2, rf_res) for rf in rf_vec: # With all parameters, check if it meets small signal spec meets_SS, SS_vals = verify_TIA_inverter_SS( n_op_info, p_op_info, nf_n, nf_p, rf, cpd, cload, rdc_min, fbw_min, pm_min) # With all parameters, estimate if it will meet noise spec meets_noise, BER = verify_TIA_inverter_BER( n_op_info, p_op_info, nf_n, nf_p, rf, cpd, cload, BER_max, vos, isw_pkpk) meets_spec = meets_SS # and meets_noise # If it meets small signal spec, append it to the list # of possibilities if meets_spec: possibilities.append( dict(vg=vg, vdd=vdd_nom, nf_n=nf_n, nf_p=nf_p, rf=rf, rdc=SS_vals['rdc'], fbw=SS_vals['fbw'], pm=SS_vals['pm'], ibias=ibias_n, BER=BER)) elif SS_vals['fbw'] != None and SS_vals['fbw'] < fbw_min: # Increasing resistor size won't help bandwidth break # Go through all possibilities which work at the nominal voltage # and ensure functionality at other bias voltages # Remove any nonviable options print("{0} working at nominal VDD".format(len(possibilities))) for candidate in possibilities: nf_n = candidate['nf_n'] nf_p = candidate['nf_p'] rf = candidate['rf'] for vdd in vdd_vec: new_op_dict = vary_supply(vdd, db_n, db_p, nf_n, nf_p, vb_n, vb_p) vg = new_op_dict['vb'] n_op = new_op_dict['n_op'] p_op = new_op_dict['p_op'] # Confirm small signal spec is met meets_SS, scratch = verify_TIA_inverter_SS(n_op, p_op, nf_n, nf_p, rf, cpd, cload, rdc_min, fbw_min, pm_min) # Confirm noise spec is met meets_noise, BER = verify_TIA_inverter_BER(n_op, p_op, nf_n, nf_p, rf, cpd, cload, BER_max, vos, isw_pkpk) meets_spec = meets_SS # and meets_noise if not meets_spec: possibilities.remove(candidate) break # Of the remaining possibilities, check for lowest power. # If there are none, raise a ValueError. if len(possibilities) == 0: raise ValueError("No final viable solutions") print("{0} working at all VDD".format(len(possibilities))) best_op = possibilities[0] for candidate in possibilities: best_op = choose_op_comparison(best_op, candidate) return best_op
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