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
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
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
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)
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
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
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)
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
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)
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_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_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
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
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
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)")
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
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
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
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
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