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 make_ltickt(self, op_dict: Mapping[str, Any], nf_dict: Mapping[str, int], cload: float, rload: float, meas_side: str) -> LTICircuit: ckt = LTICircuit() inp_conn = 'gnd' if meas_side == 'n' else 'inp' inn_conn = 'gnd' if meas_side == 'p' else 'inn' ckt.add_transistor(op_dict['main_in'], 'main_outn', inp_conn, 'main_tail', fg=nf_dict['main_in']) ckt.add_transistor(op_dict['main_in'], 'main_outp', inn_conn, 'main_tail', fg=nf_dict['main_in']) # ckt.add_transistor(op_dict['main_tail'], 'main_tail', 'cmfb_outp', 'gnd', fg=nf_dict['main_tail']) ckt.add_transistor(op_dict['main_tail'], 'main_tail', 'gnd', 'gnd', fg=nf_dict['main_tail']) ckt.add_res(rload, 'main_outn', 'gnd') ckt.add_res(rload, 'main_outp', 'gnd') ckt.add_cap(cload, 'main_outn', 'gnd') ckt.add_cap(cload, 'main_outp', 'gnd') # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_out1n', 'gnd', 'cmfb_tail', fg=nf_dict['cmfb_in']*2) # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_out1p', 'main_outn', 'cmfb_tail', fg=nf_dict['cmfb_in']) # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_out1p', 'main_outp', 'cmfb_tail', fg=nf_dict['cmfb_in']) # ckt.add_transistor(op_dict['cmfb_tail'], 'cmfb_tail', 'gnd', 'gnd', fg=nf_dict['cmfb_tail']) # ckt.add_transistor(op_dict['cmfb_load'], 'cmfb_out1n', 'cmfb_out1n', 'gnd', fg=nf_dict['cmfb_load']) # ckt.add_transistor(op_dict['cmfb_load'], 'cmfb_out1p', 'cmfb_out1p', 'gnd', fg=nf_dict['cmfb_load']) # ckt.add_transistor(op_dict['cmfb_load_copy'], 'cmfb_outp', 'cmfb_out1n', 'gnd', fg=nf_dict['cmfb_load_copy']) # ckt.add_transistor(op_dict['cmfb_load_copy'], 'cmfb_outn', 'cmfb_out1p', 'gnd', fg=nf_dict['cmfb_load_copy']) # ckt.add_transistor(op_dict['cmfb_out'], 'cmfb_outp', 'cmfb_outp', 'gnd', fg=nf_dict['cmfb_out']) # ckt.add_transistor(op_dict['cmfb_out'], 'cmfb_outn', 'cmfb_outn', 'gnd', fg=nf_dict['cmfb_out']) # assert False, 'blep' # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_outn', 'gnd', 'cmfb_tail', fg=nf_dict['cmfb_in']*2, neg_cap=False) # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_outp', 'main_outn', 'cmfb_tail', fg=nf_dict['cmfb_in'], neg_cap=False) # ckt.add_transistor(op_dict['cmfb_in'], 'cmfb_outp', 'main_outp', 'cmfb_tail', fg=nf_dict['cmfb_in'], neg_cap=False) # ckt.add_transistor(op_dict['cmfb_tail'], 'cmfb_tail', 'gnd', 'gnd', fg=nf_dict['cmfb_tail'], neg_cap=False) # ckt.add_transistor(op_dict['cmfb_out'], 'cmfb_outn', 'cmfb_outn', 'gnd', fg=nf_dict['cmfb_out'], neg_cap=False) # ckt.add_transistor(op_dict['cmfb_out'], 'cmfb_outp', 'cmfb_outp', 'gnd', fg=nf_dict['cmfb_out'], neg_cap=False) return ckt
def run_main(): interp_method = 'spline' sim_env = 'tt' nmos_spec = 'specs_mos_char/nch_w0d5.yaml' pmos_spec = 'specs_mos_char/pch_w0d5.yaml' intent = 'lvt' nch_db = get_db(nmos_spec, intent, interp_method=interp_method, sim_env=sim_env) nch_op = nch_db.query(vbs=0, vds=0.5, vgs=0.5) pprint.pprint(nch_op) # building circuit cir = LTICircuit() cir.add_transistor(nch_op, 'out', 'in', 'gnd', 'gnd', fg=2) cir.add_res(10e3, 'out', 'gnd') cir.add_cap(100e-15, 'out', 'gnd') # get gain/poles/zeros/bode plot trans_fun = cir.get_transfer_function('in', 'out', in_type='v') print('poles: %s' % trans_fun.poles) print('zeros: %s' % trans_fun.zeros) # note: don't use the gain attribute. For some reason it's broken print('gain: %.4g' % (trans_fun.num[-1] / trans_fun.den[-1])) fvec = np.logspace(5, 10, 1000) _, mag, phase = sig.bode(trans_fun, w=2 * np.pi * fvec) # get transient response state_space = cir.get_state_space('in', 'out', in_type='v') tvec = np.linspace(0, 1e-8, 1000) _, yvec = sig.step(state_space, T=tvec) _, (ax0, ax1) = plt.subplots(2, sharex=True) ax0.semilogx(fvec, mag) ax0.set_ylabel('Magnitude (dB)') ax1.semilogx(fvec, phase) ax1.set_ylabel('Phase (degrees)') ax1.set_xlabel('Frequency (Hz)') plt.figure(2) plt.plot(tvec, yvec) plt.ylabel('Output (V/V)') plt.xlabel('Time (s)') plt.show()
def _get_loadreg_lti(self, op_dict, nf_dict, ser_type, amp_in, cload, cdecap_amp, rsource, vout, iout) -> float: ''' Returns: loadreg: Load regulation for peak-to-peak load current variation of 20% (V/V) ''' n_ser = ser_type == 'n' n_amp = amp_in == 'n' vdd = 'vdd' if rsource != 0 else 'gnd' # Supply -> output gain ckt = LTICircuit() ser_d = vdd if n_ser else 'reg' ser_s = 'reg' if n_ser else vdd inp_conn = 'gnd' if n_ser else 'reg' inn_conn = 'reg' if n_ser else 'gnd' tail_rail = 'gnd' if n_amp else vdd load_rail = vdd if n_amp else 'gnd' # Passives if rsource != 0: ckt.add_res(rsource, 'gnd', 'vdd') ckt.add_cap(cload, 'reg', 'gnd') ckt.add_cap(cdecap_amp, 'out', 'reg') # Series device ckt.add_transistor(op_dict['ser'], ser_d, 'out', ser_s, fg=nf_dict['ser'], neg_cap=False) # Amplifier ckt.add_transistor(op_dict['amp_in'], 'outx', inp_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False) ckt.add_transistor(op_dict['amp_in'], 'out', inn_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False) ckt.add_transistor(op_dict['amp_tail'], 'tail', 'gnd', tail_rail, fg=nf_dict['amp_tail'], neg_cap=False) ckt.add_transistor(op_dict['amp_load'], 'outx', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False) ckt.add_transistor(op_dict['amp_load'], 'out', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False) num, den = ckt.get_num_den(in_name='reg', out_name='reg', in_type='i') transimpedance = num[-1]/den[-1] loadreg = transimpedance*0.2*iout/vout return loadreg
def construct_feedback(n_op, p_op, tail_op, nf_n, nf_p, nf_tail, cload, cn=165e-15, cp=83e-15, r=70.71e3): ''' Inputs: p/n/tail_op: Operating point information for a given device. NMOS (input), PMOS (active load), or tail device. nf_n/p/tail: Integer. Number of minimum channel width/length devices in parallel. cload: Float. Load capacitance in farads. cn: Float. Capacitance in farads attached to the negative input terminal of the amplifier for the filter. cp: Float. Capacitance in farads attached to the positive input terminal of the amplifier for the filter. r: Float. Resistance in ohms used external to the amplifier for the filter. Outputs: Returns the LTICircuit constructed for closed-loop analysis of the amplifier with the loop broken. ''' ckt = LTICircuit() # Left side ckt.add_transistor(n_op, 'out_copy', 'inp', 'tail', fg=nf_n) ckt.add_transistor(p_op, 'out_copy', 'out_copy', 'gnd', fg=nf_p) # Right side ckt.add_transistor(n_op, 'out', 'inn', 'tail', fg=nf_n) ckt.add_transistor(p_op, 'out', 'out_copy', 'gnd', fg=nf_p) # Tail ckt.add_transistor(tail_op, 'tail', 'gnd', 'gnd', fg=nf_tail) # Adding additional passives ckt.add_cap(cload, 'out', 'gnd') ckt.add_cap(cn, 'inn', 'rmid') ckt.add_cap(cp, 'inp', 'gnd') ckt.add_res(r, 'in', 'rmid') ckt.add_res(r, 'rmid', 'inp') return ckt
def _get_stb_lti(self, op_dict, nf_dict, ser_type, amp_in, cload, cdecap_amp, rsource) -> float: ''' Returns: pm: Phase margin (degrees) ''' ckt = LTICircuit() n_ser = ser_type == 'n' n_amp = amp_in == 'n' vdd = 'vdd' if rsource != 0 else 'gnd' # Series device ser_d = vdd if n_ser else 'reg' ser_s = 'reg' if n_ser else vdd ckt.add_transistor(op_dict['ser'], ser_d, 'out', ser_s, fg=nf_dict['ser'], neg_cap=False) # Passives ckt.add_cap(cload, 'reg', 'gnd') ckt.add_cap(cdecap_amp, 'out', 'reg') if rsource != 0: ckt.add_res(rsource, 'gnd', 'vdd') # Amplifier tail_rail = 'gnd' if n_amp else vdd load_rail = vdd if n_amp else 'gnd' inp_conn = 'gnd' if n_ser else 'amp_in' inn_conn = 'gnd' if not n_ser else 'amp_in' ckt.add_transistor(op_dict['amp_in'], 'outx', inp_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False) ckt.add_transistor(op_dict['amp_in'], 'out', inn_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False) ckt.add_transistor(op_dict['amp_tail'], 'tail', 'gnd', tail_rail, fg=nf_dict['amp_tail'], neg_cap=False) ckt.add_transistor(op_dict['amp_load'], 'outx', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False) ckt.add_transistor(op_dict['amp_load'], 'out', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False) # Calculating stability margins num, den = ckt.get_num_den(in_name='amp_in', out_name='reg', in_type='v') pm, _ = get_stability_margins(np.convolve(num, [-1]), den) return pm
def _get_loopgain_lti(self, op_dict, nf_dict, ser_type, amp_in, rsource) -> float: ''' Returns: A: DC loop gain ''' ckt = LTICircuit() n_ser = ser_type == 'n' n_amp = amp_in == 'n' vdd = 'vdd' if rsource != 0 else 'gnd' # Series device ser_d = vdd if n_ser else 'reg' ser_s = 'reg' if n_ser else vdd ckt.add_transistor(op_dict['ser'], ser_d, 'out', ser_s, fg=nf_dict['ser'], neg_cap=True) # Passives if rsource != 0: ckt.add_res(rsource, 'gnd', 'vdd') # Amplifier tail_rail = 'gnd' if n_amp else vdd load_rail = vdd if n_amp else 'gnd' inp_conn = 'gnd' if n_ser else 'amp_in' inn_conn = 'gnd' if not n_ser else 'amp_in' ckt.add_transistor(op_dict['amp_in'], 'outx', inp_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False) ckt.add_transistor(op_dict['amp_in'], 'out', inn_conn, 'tail', fg=nf_dict['amp_in'], neg_cap=False) ckt.add_transistor(op_dict['amp_tail'], 'tail', 'gnd', tail_rail, fg=nf_dict['amp_tail'], neg_cap=False) ckt.add_transistor(op_dict['amp_load'], 'outx', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False) ckt.add_transistor(op_dict['amp_load'], 'out', 'outx', load_rail, fg=nf_dict['amp_load'], neg_cap=False) # Calculating stability margins num, den = ckt.get_num_den(in_name='amp_in', out_name='reg', in_type='v') A = num[-1]/den[-1] return A
def characterize_casc_amp(env_list, lch, intent_list, fg_list, w_list, db_list, vbias_list, vload_list, vtail_list, vmid_list, vincm, voutcm, vdd, vin_clip, clip_ratio_min, cw, rw, fanout, ton, k_settle_targ, scale_min=0.25, scale_max=20): # compute DC transfer function curve and compute linearity spec ndb, pdb = db_list[0], db_list[-1] results = solve_casc_diff_dc(env_list, ndb, pdb, lch, intent_list, w_list, fg_list, vbias_list, vload_list, vtail_list, vmid_list, vdd, vincm, voutcm, vin_clip, clip_ratio_min, num_points=20) vmat_list, ratio_list, gain_list = results # compute settling ratio fg_tail, fg_in, fg_casc, fg_load = fg_list db_tail, db_in, db_casc, db_load = db_list w_tail, w_in, w_casc, w_load = w_list fzin = 1.0 / (2 * ton) wzin = 2 * np.pi * fzin tvec = np.linspace(0, ton, 200, endpoint=True) scale_list = [] cin_list = [] for env, vbias, vload, vtail, vmid, vmat in zip(env_list, vbias_list, vload_list, vtail_list, vmid_list, vmat_list): # step 1: get half circuit parameters and compute input capacitance in_par = db_in.query(env=env, w=w_in, vbs=-vtail, vds=vmid - vtail, vgs=vincm - vtail) casc_par = db_casc.query(env=env, w=w_casc, vbs=-vmid, vds=voutcm - vmid, vgs=vdd - vmid) load_par = db_load.query(env=env, w=w_load, vbs=0, vds=voutcm - vdd, vgs=vload - vdd) cir = LTICircuit() cir.add_transistor(in_par, 'mid', 'in', 'gnd', fg=fg_in) cir.add_transistor(casc_par, 'out', 'gnd', 'mid', fg=fg_casc) cir.add_transistor(load_par, 'out', 'gnd', 'gnd', fg=fg_load) zin = cir.get_impedance('in', fzin) cin = (1 / zin).imag / wzin cin_list.append(cin) # step 3A: construct differential circuit with vin_clip / 2. vts, vmps, vmns, vops, vons = vmat[-1, :] vinp = vincm + vin_clip / 4 vinn = vincm - vin_clip / 4 tail_par = db_tail.query(env=env, w=w_tail, vbs=0, vds=vts, vgs=vbias) inp_par = db_in.query(env=env, w=w_in, vbs=-vts, vds=vmns - vts, vgs=vinp - vts) inn_par = db_in.query(env=env, w=w_in, vbs=-vts, vds=vmps - vts, vgs=vinn - vts) cascp_par = db_casc.query(env=env, w=w_casc, vbs=-vmns, vds=vons - vmns, vgs=vdd - vmns) cascn_par = db_casc.query(env=env, w=w_casc, vbs=-vmps, vds=vops - vmps, vgs=vdd - vmps) loadp_par = db_load.query(env=env, w=w_load, vbs=0, vds=vons - vdd, vgs=vload - vdd) loadn_par = db_load.query(env=env, w=w_load, vbs=0, vds=vops - vdd, vgs=vload - vdd) cir = LTICircuit() cir.add_transistor(tail_par, 'tail', 'gnd', 'gnd', fg=fg_tail) cir.add_transistor(inp_par, 'midn', 'inp', 'tail', fg=fg_in) cir.add_transistor(inn_par, 'midp', 'inn', 'tail', fg=fg_in) cir.add_transistor(cascp_par, 'dn', 'gnd', 'midn', fg=fg_casc) cir.add_transistor(cascn_par, 'dp', 'gnd', 'midp', fg=fg_casc) cir.add_transistor(loadp_par, 'dn', 'gnd', 'gnd', fg=fg_load) cir.add_transistor(loadn_par, 'dp', 'gnd', 'gnd', fg=fg_load) cir.add_vcvs(0.5, 'inp', 'gnd', 'inac', 'gnd') cir.add_vcvs(-0.5, 'inn', 'gnd', 'inac', 'gnd') # step 3B: compute DC gain cir.add_vcvs(1, 'dac', 'gnd', 'dp', 'dn') dc_tf = cir.get_transfer_function('inac', 'dac') _, gain_vec = dc_tf.freqresp(w=[0.1]) dc_gain = gain_vec[0].real # step 3C add cap loading cload = fanout * cin cir.add_cap(cload, 'outp', 'gnd') cir.add_cap(cload, 'outn', 'gnd') cir.add_vcvs(1, 'outac', 'gnd', 'outp', 'outn') # step 4: find scale factor to achieve k_settle # noinspection PyUnresolvedReferences scale_swp = np.arange(scale_min, scale_max, 0.25).tolist() max_kset = 0 opt_scale = 0 for cur_scale in scale_swp: # add scaled wired parasitics cap_cur = cw / 2 / cur_scale res_cur = rw * cur_scale cir.add_cap(cap_cur, 'dp', 'gnd') cir.add_cap(cap_cur, 'dn', 'gnd') cir.add_cap(cap_cur, 'outp', 'gnd') cir.add_cap(cap_cur, 'outn', 'gnd') cir.add_res(res_cur, 'dp', 'outp') cir.add_res(res_cur, 'dn', 'outn') # get settling factor sys = cir.get_state_space('inac', 'outac') _, yvec = scipy.signal.step( sys, T=tvec) # type: Tuple[np.ndarray, np.ndarray] k_settle_cur = yvec[-1] / dc_gain # print('scale = %.4g, k_settle = %.4g' % (cur_scale, k_settle_cur)) # update next scale factor if k_settle_cur > max_kset: max_kset = k_settle_cur opt_scale = cur_scale else: # k_settle is decreasing, break break if max_kset >= k_settle_targ: break # remove wire parasitics cir.add_cap(-cap_cur, 'dp', 'gnd') cir.add_cap(-cap_cur, 'dn', 'gnd') cir.add_cap(-cap_cur, 'outp', 'gnd') cir.add_cap(-cap_cur, 'outn', 'gnd') cir.add_res(-res_cur, 'dp', 'outp') cir.add_res(-res_cur, 'dn', 'outn') if max_kset < k_settle_targ: raise ValueError('%s max kset = %.4g @ scale = %.4g, ' 'cannot meet settling time spec.' % (env, max_kset, opt_scale)) scale_list.append(opt_scale) return vmat_list, ratio_list, gain_list, scale_list, cin_list
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 _get_stb_lti(self, op_dict, nf_dict, series_type, rload, cload) -> float: ''' Returns: pm: Phase margins (in degrees) ''' ckt = LTICircuit() n_ser = series_type == 'n' # Series device ser_d = 'gnd' if n_ser else 'reg' ser_s = 'reg' if n_ser else 'gnd' ckt.add_transistor(op_dict['ser'], ser_d, 'out', ser_s, fg=nf_dict['ser'], neg_cap=False) # Load passives ckt.add_res(rload, 'reg', 'gnd') ckt.add_cap(rload, 'reg', 'gnd') # TODO include any compensation passives # Amplifier inp_conn = 'gnd' if n_ser else 'in' inn_conn = 'gnd' if not n_ser else 'in' ckt.add_transistor(op_dict['in'], 'outx', inp_conn, 'tail', fg=nf_dict['in'], neg_cap=False) ckt.add_transistor(op_dict['in'], 'out', inn_conn, 'tail', fg=nf_dict['in'], neg_cap=False) ckt.add_transistor(op_dict['tail'], 'tail', 'gnd', 'gnd', fg=nf_dict['tail'], neg_cap=False) ckt.add_transistor(op_dict['load'], 'outx', 'outx', 'gnd', fg=nf_dict['load'], neg_cap=False) ckt.add_transistor(op_dict['load'], 'out', 'outx', 'gnd', fg=nf_dict['load'], neg_cap=False) # Calculating stability margins num, den = ckt.get_num_den(in_name='in', out_name='reg', in_type='v') pm, _ = get_stability_margins(np.convolve(num, [-1]), den) return pm
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 characterize_casc_amp(env_list, fg_list, w_list, db_list, vbias_list, vload_list, vtail_list, vmid_list, vcm, vdd, vin_max, cw, rw, fanout, ton, k_settle_targ, verr_max, scale_res=0.1, scale_min=0.25, scale_max=20): # compute DC transfer function curve and compute linearity spec results = solve_casc_diff_dc(env_list, db_list, w_list, fg_list, vbias_list, vload_list, vtail_list, vmid_list, vdd, vcm, vin_max, verr_max, num_points=20) vin_vec, vmat_list, verr_list, gain_list = results # compute settling ratio fg_in, fg_casc, fg_load = fg_list[1:] db_in, db_casc, db_load = db_list[1:] w_in, w_casc, w_load = w_list[1:] fzin = 1.0 / (2 * ton) wzin = 2 * np.pi * fzin tvec = np.linspace(0, ton, 200, endpoint=True) scale_list = [] for env, vload, vtail, vmid in zip(env_list, vload_list, vtail_list, vmid_list): # step 1: construct half circuit in_params = db_in.query(env=env, w=w_in, vbs=-vtail, vds=vmid - vtail, vgs=vcm - vtail) casc_params = db_casc.query(env=env, w=w_casc, vbs=-vmid, vds=vcm - vmid, vgs=vdd - vmid) load_params = db_load.query(env=env, w=w_load, vbs=0, vds=vcm - vdd, vgs=vload - vdd) circuit = LTICircuit() circuit.add_transistor(in_params, 'mid', 'in', 'gnd', fg=fg_in) circuit.add_transistor(casc_params, 'd', 'gnd', 'mid', fg=fg_casc) circuit.add_transistor(load_params, 'd', 'gnd', 'gnd', fg=fg_load) # step 2: get input capacitance zin = circuit.get_impedance('in', fzin) cin = (1 / zin).imag / wzin circuit.add_cap(cin * fanout, 'out', 'gnd') # step 3: find scale factor to achieve k_settle bin_iter = BinaryIterator(scale_min, None, step=scale_res, is_float=True) while bin_iter.has_next(): # add scaled wired parasitics cur_scale = bin_iter.get_next() cap_cur = cw / 2 / cur_scale res_cur = rw * cur_scale circuit.add_cap(cap_cur, 'd', 'gnd') circuit.add_cap(cap_cur, 'out', 'gnd') circuit.add_res(res_cur, 'd', 'out') # get settling factor sys = circuit.get_voltage_gain_system('in', 'out') dc_gain = sys.freqresp(w=np.array([0.1]))[1][0] sgn = 1 if dc_gain.real >= 0 else -1 dc_gain = abs(dc_gain) _, yvec = scipy.signal.step( sys, T=tvec) # type: Tuple[np.ndarray, np.ndarray] k_settle_cur = 1 - abs(yvec[-1] - sgn * dc_gain) / dc_gain print('scale = %.4g, k_settle = %.4g' % (cur_scale, k_settle_cur)) # update next scale factor if k_settle_cur >= k_settle_targ: print('save scale = %.4g' % cur_scale) bin_iter.save() bin_iter.down() else: if cur_scale > scale_max: raise ValueError( 'cannot meet settling time spec at scale = %d' % cur_scale) bin_iter.up() # remove wire parasitics circuit.add_cap(-cap_cur, 'd', 'gnd') circuit.add_cap(-cap_cur, 'out', 'gnd') circuit.add_res(-res_cur, 'd', 'out') scale_list.append(bin_iter.get_last_save()) return vmat_list, verr_list, gain_list, scale_list
itrim = 0 else: vos_base = idiff_trim_base*rload nf_trim = ceil(vos_targ/vos_base) itrim = (op_trim_on['ibias']+op_trim_off['ibias'])*nf_trim # LTICircuit simulation circuit = LTICircuit() circuit.add_transistor(op_in, 'outp', 'gnd', 'tail', fg=nf_in) circuit.add_transistor(op_in, 'outn', 'inp', 'tail', fg=nf_in) circuit.add_transistor(op_trim_on, 'outn', 'gnd', 'gnd', fg=nf_trim) circuit.add_transistor(op_trim_off, 'outp', 'gnd', 'gnd', fg=nf_trim) circuit.add_transistor(op_tail, 'tail', 'vmirr', 'gnd', fg=nf_tail) circuit.add_transistor(op_mirr, 'vmirr', 'vmirr', 'gnd', fg=nf_mirr) circuit.add_transistor(op_dummy, 'gnd', 'gnd', 'vmirr', fg=nf_dummy) circuit.add_res(rload, 'outp', 'gnd') circuit.add_res(rload, 'outn', 'gnd') circuit.add_cap(cload, 'outp', 'gnd') circuit.add_cap(cload, 'outn', 'gnd') circuit.add_cap(dac_cap, 'tail', 'gnd') num, den = circuit.get_num_den(in_name='inp', out_name='outn', in_type='v') num_unintentional, den_unintentional = circuit.get_num_den(in_name='inp', out_name='outp', in_type='v') gain_intentional = num[-1]/den[-1] 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)
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_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_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_preamp(db_n, db_p, lch, w_finger, vdd, cload, vin_signal, vin_shift, voutcm_res, vtail_res, gain_min, fbw_min, rload_res, vb_p, vb_n, itail_max=10e-6, error_tol=.01): """ Designs a preamplifier to go between the comparator and TIA. 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) rload_res: Float. Load resistor resolution for sweep (ohms) 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. itail: Float. Tail current of main amp (A). rload: Float. Load resistance (ohm). gain: Float. Amp gain (V/V). fbw: Float. Bandwidth (Hz). voutcm: Float. Output common mode voltage (V). cin: Float. Approximation of input cap (F). Notes: Under construction. """ # Estimate vth for use in calculating biasing ranges vthn = estimate_vth(db_n, vdd, 0) vincm = (vin_signal + vin_shift) / 2 voutcm_min = vin_signal - vthn voutcm_max = vdd voutcm_vec = np.arange(voutcm_min, voutcm_max, voutcm_res) 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 # Sweep output common mode voltage for voutcm in voutcm_vec: # Sweep tail voltage 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, 2) 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) # 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