def _find_min_cfb(cls, phase_margin, results): axis_names = ['corner', 'cfb'] corner_list = results['corner'] corner_sort_arg = np.argsort(corner_list) # type: Sequence[int] # rearrange array axis sweep_vars = results['sweep_params']['pm_vout'] order = [sweep_vars.index(name) for name in axis_names] pm_data = np.transpose(results['pm_vout'], axes=order) # determine minimum cfb cfb_vec = results['cfb'] cfb_idx_min = 0 for corner_idx in corner_sort_arg: bin_iter = BinaryIterator(cfb_idx_min, cfb_vec.size) while bin_iter.has_next(): cur_cfb_idx = bin_iter.get_next() pm = pm_data[corner_idx, cur_cfb_idx] if pm >= phase_margin: bin_iter.save() bin_iter.down() else: bin_iter.up() cfb_idx_min = bin_iter.get_last_save() if cfb_idx_min is None: # No solution; cannot make amplifier stable break if cfb_idx_min is None: raise ValueError('Cannot determine cfb.') else: cfb = cfb_vec[cfb_idx_min] return cfb.item()
def find_edge_size(self, # type: ResTech grid, # type: RoutingGrid core_info, # type: Dict[str, Any] is_lr_edge, # type: bool params, # type: Dict[str, Any] blk1, # type: int max_blk_ext, # type: int ): # type: (...) -> Tuple[int, Dict[str, Any]] """Compute resistor edge size that meets DRC rules. Calculate edge dimension (width for LR edge, height for TB edge) that meets DRC rules Parameters ---------- grid : RoutingGrid the RoutingGrid object. core_info : Dict[str, Any] core layout information dictionary. is_lr_edge : bool True if this is left/right edge, False if this is top/bottom edge. params : Dict[str, Any] the resistor parameters dictionary. blk1 : int dimension1 block size in resolution units. max_blk_ext : int maximum number of blocks we can extend by. Returns ------- n1 : int edge length in dimension 1 as number of blocks. layout_info : Dict[str, Any] the edge layout information dictionary. """ bin_iter = BinaryIterator(1, max_blk_ext + 2) ans = None while bin_iter.has_next(): n1 = bin_iter.get_next() if is_lr_edge: tmp = self.get_lr_edge_info(grid, core_info, n1 * blk1, **params) else: tmp = self.get_tb_edge_info(grid, core_info, n1 * blk1, **params) if tmp is None: bin_iter.up() else: ans = tmp bin_iter.save() bin_iter.down() if ans is None: raise ValueError('failed to find DRC clean core with maximum %d ' 'additional block pitches.' % max_blk_ext) return bin_iter.get_last_save(), ans
def interp1d_no_nan( tvec: np.ndarray, yvec: np.ndarray) -> Callable[[Union[float, np.ndarray]], np.ndarray]: tsize = len(tvec) if np.isnan(tvec[-1]): bin_iter = BinaryIterator(1, tsize + 1) while bin_iter.has_next(): delta = bin_iter.get_next() if np.isnan(tvec[tsize - delta]): bin_iter.save() bin_iter.up() else: bin_iter.down() tsize -= bin_iter.get_last_save() return interp1d(tvec[:tsize], yvec[:tsize], assume_sorted=True, copy=False)
def get_max_track_width(self, layer_id: int, num_tracks: int, tot_space: int, half_end_space: bool = False) -> int: """Compute maximum track width and space that satisfies DRC rule. Given available number of tracks and numbers of tracks needed, returns the maximum possible track width. Parameters ---------- layer_id : int the track layer ID. num_tracks : int number of tracks to draw. tot_space : int available number of tracks. half_end_space : bool True if end spaces can be half of minimum spacing. This is true if you're these tracks will be repeated, or there are no adjacent tracks. Returns ------- tr_w : int track width. """ bin_iter = BinaryIterator(1, None) while bin_iter.has_next(): tr_w = bin_iter.get_next() tr_sep = self.get_sep_tracks(layer_id, tr_w, tr_w) if half_end_space: used_tracks = tr_sep * num_tracks else: used_tracks = tr_sep * (num_tracks - 1) + 2 * self.get_sep_tracks( layer_id, tr_w, 1) if used_tracks > tot_space: bin_iter.down() else: bin_iter.save() bin_iter.up() opt_w = bin_iter.get_last_save() return opt_w
def check_density_rule_edge(cls, n0, s0, s1, area): # type: (int, int, int, int) -> int """Compute edge block dimension from density spec. Given edge width or height (as dimension 0), find the missing dimension (dimension 1) such that density rule is met. Parameters ---------- n0 : int edge length in dimension 0 as number of blocks. s0 : int dimension 0 block length in resolution units. s1 : int dimension 1 block length in resolution units. area : int the resistor area in the edge block that should be used for density spec. In resolution units squared. Returns ------- n1 : int edge length in dimension 1 as number of blocks. """ density = cls.get_res_density() # convert to float so we're doing floating point comparison area = float(area) bin_iter = BinaryIterator(1, None) a0 = n0 * s0 * s1 while bin_iter.has_next(): n1 = bin_iter.get_next() if area <= a0 * n1 * density: bin_iter.save() bin_iter.down() else: bin_iter.up() return bin_iter.get_last_save()
async def async_design(self, pinfo: Mapping[str, Any], nbits: int, rtol: float, atol: float, tbit: float, trf: float, cload: float, mc_params: Param, num_cores: int, target: Mapping[str, Any], delay_cell_params: Mapping[str, Any], **kwargs: Mapping[str, Any]) -> Mapping[str, Any]: td_min = target['td_min'] td_max = target['td_max'] t_max = target['t_max'] td_sigma = target['td_sigma'] tristate_seg = kwargs.get('tristate_seg', self.global_info['seg_min']) tristate_stack = kwargs.get('tristate_stack', 1) seg_buf_abs_max = kwargs.get('seg_buf_abs_max', 50) seg_buf_max_override = kwargs.get('seg_buf_max_override', None) seg_buf_min_override = kwargs.get('seg_buf_min_override', None) design_using_signoff = kwargs.get('design_using_signoff', False) mc_corner = kwargs.get('mc_corner', 'tt_25') mc_env_override = kwargs.get("mc_env_override", None) mc_worst_corner = kwargs.get("mc_worst_corner", True) plot_result: bool = kwargs.get('plot_result', False) dsn_monte_carlo: bool = kwargs.get('dsn_monte_carlo', True) gen_specs: Optional[Mapping[str, Any]] = kwargs.get('gen_cell_specs', None) gen_cell_args: Optional[Mapping[str, Any]] = kwargs.get( 'gen_cell_args', None) # 0. Setup design environments and the testbench manager if design_using_signoff: dsn_envs = self.global_info['signoff_envs'] dsn_env_names = dsn_envs['all_corners']['envs'] dsn_env_vdds = dsn_envs['all_corners']['vdd'] else: dsn_envs = self.global_info['dsn_envs'] dsn_env_names = [ env for dct in dsn_envs.values() for env in dct['env'] ] dsn_env_vdds = { e: dsn_envs[c]['vdd'] for c in dsn_envs.keys() for e in dsn_envs[c]['env'] } if not mc_worst_corner and not mc_env_override: raise ValueError( "If not performing mc on the worst corner, specify mc_env_override!" ) dut_pins = [ 'a_in', 'b_in', 'intout', 'VDD', 'VSS', f'sp<{nbits - 1}:0>', f'sn<{nbits - 1}:0>', 'a_in_buf' ] tbm_dict = {} for dsn_env in dsn_env_names: tbm_specs = self._get_tbm_specs( [dsn_env], dict(vdd={dsn_env: dsn_env_vdds[dsn_env]}), dut_pins, tbit, trf, cload, nbits, rtol, atol) tbm = cast(DigitalTranTB, self.make_tbm(DigitalTranTB, tbm_specs)) tbm_dict[dsn_env] = tbm tbm_params = dict() # 1. Setup the base phase interpolator and extract the input cap pwr_domains = dict(b_in=('VSS', 'VDD'), a_in=('VSS', 'VDD'), out=('VSS', 'VDD')) cin_dut_conns = dict(a_in=1) for i in range(nbits): for name, value in zip(['a_en', 'b_en', 'a_enb', 'b_enb'], [1, 0, 0, 1]): cin_dut_conns[f'{name}<{i}>'] = value pwr_domains[f'{name}<{i}>'] = ('VSS', 'VDD') gen_params = dict(cls_name=PhaseInterpolator.get_qualified_name(), params=dict( pinfo=pinfo, unit_params={ 'seg': tristate_seg, 'stack_p': tristate_stack, 'stack_n': tristate_stack }, inv_params={ 'seg': 2, 'stack_p': 1, 'stack_n': 1 }, nbits=nbits, draw_sub=True, export_outb=True, abut_tristates=True, )) pi_in_cap = await self._get_input_cap( gen_params, 'b_in', cload, cin_dut_conns, pwr_domains, [dict(pin='out', type='cap', value='c_out')], vdd=dsn_env_vdds['tt_25'], sim_envs=['tt_25']) # 2. Setup the base delay cell and extract it's input cap pin_names = ['bk1', 'ci_p', 'co_p', 'in_p', 'out_p'] pwr_domains = {pin_name: ('VSS', 'VDD') for pin_name in pin_names} cin_dut_conns = dict(bk1=1, ci_p=0) load_list = [ dict(pin='out_p', type='cap', value='c_out'), dict(pin='co_p', type='cap', value='c_out') ] gen_params = dict(cls_name=DelayCellCore.get_qualified_name(), params=dict(pinfo=pinfo, stand_alone=True, **delay_cell_params)) dc_in_cap = await self._get_input_cap(gen_params, 'ci_p', cload, cin_dut_conns, pwr_domains, load_list, vdd=dsn_env_vdds['tt_25'], sim_envs=['tt_25']) # 3. Size the delay cell to be able to drive the phase interpolator dc_up_scale_factor = int(round(pi_in_cap / dc_in_cap)) delay_cell_params['seg_dict']['in'] *= dc_up_scale_factor delay_cell_params['seg_dict']['out'] *= dc_up_scale_factor delay_cell_params['seg_dict']['sr'] *= dc_up_scale_factor nand_seg = delay_cell_params['seg_dict']['out'] * 2 inv_in_cap = self.global_info['cin_inv']['cin_per_seg'] inv_seg = int(round(np.sqrt(dc_in_cap * pi_in_cap) / inv_in_cap)) # 4. Upsize the buffer inverter on the output self.log('-' * 80) if seg_buf_min_override: self.log(f"Minimum Segments Overridden to {seg_buf_min_override}") min_seg_buf = seg_buf_min_override else: self.log('Find the min size for all the codes to be positive') seg_buf_min_iter = BinaryIterator(2, None, 2) while seg_buf_min_iter.has_next(): _seg_buf = seg_buf_min_iter.get_next() dut_params = self._update_dut_params( pinfo, nbits, tristate_seg, seg_buf=_seg_buf, seg_inv=inv_seg, seg_nand=nand_seg, num_cores=num_cores, dc_params=delay_cell_params) results = await self._measure_times(dsn_env_names, tbm_dict, dut_params, tbm_params, tbit, nbits, name=f'sim_min_{_seg_buf}') # find min and max delay step tstep_min = results['min_step'] tstep_max = results['max_step'] self.log( f"Got min delay {tstep_min}, max delay {tstep_max}, with " f"{_seg_buf} segments") if tstep_min < 0: seg_buf_min_iter.up() else: seg_buf_min_iter.save() seg_buf_min_iter.down() min_seg_buf = seg_buf_min_iter.get_last_save() self.log('-' * 80) if seg_buf_max_override: self.log(f'Maximum Segments Overridden to {seg_buf_max_override}') max_seg_buf = seg_buf_max_override else: self.log( 'Now find the maximum size for all the codes to be positive') seg_buf_max_iter = BinaryIterator(10, None, 2) max_reached = False while seg_buf_max_iter.has_next(): _seg_buf = seg_buf_max_iter.get_next() dut_params = self._update_dut_params( pinfo, nbits, tristate_seg, seg_buf=_seg_buf, seg_inv=inv_seg, seg_nand=nand_seg, num_cores=num_cores, dc_params=delay_cell_params) results = await self._measure_times(dsn_env_names, tbm_dict, dut_params, tbm_params, tbit, nbits, name=f'sim_max_{_seg_buf}') # find min and max delay step tstep_min = results['min_step'] tstep_max = results['max_step'] self.log( f"Got min delay {tstep_min}, max delay {tstep_max}, with " f"{_seg_buf} segments") if tstep_min < 0: seg_buf_max_iter.down() elif _seg_buf > seg_buf_abs_max: max_reached = True break else: seg_buf_max_iter.save() seg_buf_max_iter.up() max_seg_buf = seg_buf_max_iter.get_last_save( ) if not max_reached else seg_buf_abs_max self.log('-' * 80) self.log( f'Minimum Buffer segments to keep positive delays: {min_seg_buf}') self.log( f'Maximum Buffer segments to keep positive delays: {max_seg_buf}') seg_buf_bin_iter = BinaryIterator(min_seg_buf, max_seg_buf, 2) while seg_buf_bin_iter.has_next(): _seg_buf = seg_buf_bin_iter.get_next() dut_params = self._update_dut_params(pinfo, nbits, tristate_seg, seg_buf=_seg_buf, seg_inv=inv_seg, seg_nand=nand_seg, num_cores=num_cores, dc_params=delay_cell_params) results = await self._measure_times(dsn_env_names, tbm_dict, dut_params, tbm_params, tbit, nbits, 'sim_size') tdelay_max = results['max_dly'] tstep_min = results['min_step'] tstep_max = results['max_step'] if tdelay_max > t_max and tstep_min > td_min and tstep_max < td_max: # delay constraint violated, linearity constraint met seg_buf_bin_iter.down() elif tdelay_max < t_max and (tstep_min < td_min or tstep_max > td_max): # delay constraint met, linearity constraint violated seg_buf_bin_iter.up() elif tdelay_max < t_max and tstep_min > td_min and tstep_max < td_max: # both constraints met seg_buf_bin_iter.save_info((dut_params, results)) seg_buf_bin_iter.down() else: self.error( 'Both delay and linearity constraints violated, please relax specs.' ) seg_buf_final = seg_buf_bin_iter.get_last_save() if not seg_buf_final: self.error( "Design failed!, unable to meet linearity specs within range of inv sizes" ) self.log( f'Final output buffer size is {seg_buf_final}, before Monte Carlo sim.' ) dut_params, results = seg_buf_bin_iter.get_last_save_info() if dsn_monte_carlo: # 5. Monte Carlo simulations mc_tbm_dict = {} if mc_worst_corner: mc_envs = [mc_corner] mc_vdd = dict(vdd={mc_corner: dsn_env_vdds[mc_corner]}) mc_tbm_specs = self._get_tbm_specs([mc_corner], mc_vdd, dut_pins, tbit, trf, cload, nbits, rtol, atol) mc_tbm_specs['monte_carlo_params'] = mc_params mc_tbm = cast(DigitalTranTB, self.make_tbm(DigitalTranTB, mc_tbm_specs)) mc_tbm_dict[mc_corner] = mc_tbm else: # TODO mc_envs = ... ... dut_params = self._update_dut_params(pinfo, nbits, tristate_seg, seg_buf=seg_buf_final, seg_inv=inv_seg, seg_nand=nand_seg, num_cores=num_cores, dc_params=delay_cell_params) mc_results = await self._measure_times(mc_envs, mc_tbm_dict, dut_params, tbm_params, tbit, nbits, name='sim_mc_pre') mc_factor, sigma_max = self._get_mc_factor(mc_results, td_sigma) self.log(f'Max std. dev. is {sigma_max}') self.log(f'Upscale everything by {mc_factor}') self.log('-' * 80) # 6. Final verification seg_unit_final = int(np.ceil(tristate_seg * mc_factor)) seg_unit_final += seg_unit_final & 1 # layout constraint seg_buf_final = int(np.ceil(seg_buf_final * mc_factor)) seg_buf_final += seg_buf_final & 1 # layout constraint delay_cell_params_scale = copy.deepcopy(delay_cell_params) for key in delay_cell_params['seg_dict']: delay_cell_params_scale['seg_dict'][key] = int( np.ceil(delay_cell_params['seg_dict'][key] * mc_factor)) nand_seg = int(np.ceil(nand_seg * mc_factor)) inv_seg = int(np.ceil(inv_seg * mc_factor)) dut_params = self._update_dut_params( pinfo, nbits, seg_unit_final, seg_buf=seg_buf_final, seg_inv=inv_seg, seg_nand=nand_seg, num_cores=num_cores, dc_params=delay_cell_params_scale) results = await self._measure_times(dsn_env_names, tbm_dict, dut_params, tbm_params, tbit, nbits, name='sim_sized') mc_results = await self._measure_times(mc_envs, mc_tbm_dict, dut_params, tbm_params, tbit, nbits, name='sim_mc_post') _, sigma_max = self._get_mc_factor(mc_results, td_sigma) self.log(f'Final Sigma: {sigma_max}') self.log('-' * 80) else: seg_unit_final = tristate_seg delay_cell_params_scale = delay_cell_params self.log('-' * 80) self.log(f'dsn_envs: {dsn_env_names}') self.log(f'final results:\n{pprint.pformat(results, width=100)}') if plot_result: from matplotlib import pyplot as plt plt.figure(1) ax: Any = plt.subplot(2, 1, 1) xvec = np.arange(0, results['tdr_step'].shape[1]) for idx, sim_env in enumerate(dsn_env_names): tdr = results['tdrs'][idx, :-1].flatten() plt.step(xvec, tdr, where='mid', label=sim_env) ax.legend() ax.set_ylabel('Rise Delay (s)') ax = plt.subplot(2, 1, 2) for idx, sim_env in enumerate(dsn_env_names): tdr_step = results['tdr_step'][idx, :].flatten() ax.scatter(xvec, tdr_step, label=sim_env) ax.set_ylim(ymin=td_min, ymax=td_max) ax.legend() ax.set_ylabel('Rise Delay Step (s)') ax.set_xlabel('Code') plt.show() if gen_specs is not None and gen_cell_args is not None: gen_cell_specs = dict( lay_class=IPMarginTemplate.get_qualified_name(), params=dict( cls_name=GenericWrapper.get_qualified_name(), params=dict( cls_name=PhaseInterpolatorWithDelay.get_qualified_name( ), params=dut_params, ), ), **gen_specs, ) return dict(gen_specs=gen_cell_specs, gen_args=gen_cell_args) return dict( seg_unit=seg_unit_final, seg_buf=seg_buf_final, seg_dc=delay_cell_params_scale['seg_dict'], nand_seg=nand_seg, inv_seg=inv_seg, )
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
def design(self, itarg_list, vds2_list, ft_min, stack_list=None): # type: (List[float], List[float], float, Optional[List[int]]) -> None """Design the diode load. Parameters ---------- itarg_list : List[float] target single-ended bias current across simulation environments. vds2_list : List[float] list of op-amp stage 2 vds voltage across simulation environments. ft_min : float minimum transit frequency of the composit transistor. stack_list : Optional[List[int]] list of valid stack numbers. """ if stack_list is None: stack_list = self._stack_list vgs_idx = self._db.get_fun_arg_index('vgs') num_stack = len(stack_list) self._best_op = None best_score = None for intent in self._intent_list: for w in self._valid_widths: for idx1 in range(num_stack): stack1 = stack_list[idx1] self._db.set_dsn_params(w=w, intent=intent, stack=stack1) ib1 = self._db.get_function_list('ibias') gm1 = self._db.get_function_list('gm') gds1 = self._db.get_function_list('gds') cd1 = self._db.get_function_list('cdd') vgs1_min, vgs1_max = ib1[0].get_input_range(vgs_idx) for idx2 in range(idx1, num_stack): stack2 = stack_list[idx2] self._db.set_dsn_params(stack=stack2) ib2 = self._db.get_function_list('ibias') gm2 = self._db.get_function_list('gm') gds2 = self._db.get_function_list('gds') cd2 = self._db.get_function_list('cdd') vgs2_min, vgs2_max = ib2[0].get_input_range(vgs_idx) vgs_min = max(vgs1_min, vgs2_min) vgs_max = min(vgs1_max, vgs2_max) seg1_iter = BinaryIterator(2, None, step=2) while seg1_iter.has_next(): seg1 = seg1_iter.get_next() all_neg = True one_pass = False seg2_iter = BinaryIterator(0, None, step=2) while seg2_iter.has_next(): seg2 = seg2_iter.get_next() vgs_list, err_code = self._solve_vgs( itarg_list, seg1, seg2, ib1, ib2, vgs_min, vgs_max) if err_code < 0: # too few fingers seg2_iter.up() elif err_code > 0: # too many fingers seg2_iter.down() else: one_pass = True cur_score = self._compute_score( ft_min, seg1, seg2, gm1, gm2, gds1, gds2, cd1, cd2, vgs_list) if cur_score != -1: all_neg = False if cur_score < 0: seg2_iter.down() else: seg2_iter.save() seg2_iter.up() if best_score is None or cur_score > best_score: best_score = cur_score self._best_op = (intent, stack1, stack2, w, seg1, seg2, vgs_list, vds2_list) if seg2_iter.get_last_save() is None: # no solution for seg2 if all_neg and one_pass: # all solutions encountered have negative resistance, # this means we have insufficent number of diode fingers. seg1_iter.up() elif not one_pass: # exit immediately with no solutions at all; too many fingers seg1_iter.down() else: # all positive resistance solution break V*_min specs. # this means we have too many number of fingers. seg1_iter.down() else: seg1_iter.save() seg1_iter.up()
def draw_layout(self): # type: () -> None lch = self.params['lch'] ptap_w = self.params['ptap_w'] ntap_w = self.params['ntap_w'] wp = self.params['wp'] wn = self.params['wn'] thp = self.params['thp'] thn = self.params['thn'] nx = self.params['nx'] ny = self.params['ny'] fill_config = self.params['fill_config'] top_layer = self.params['top_layer'] sup_width = self.params['sup_width'] options = self.params['options'] show_pins = self.params['show_pins'] if options is None: options = {} # get power fill size w_tot, h_tot = self.grid.get_fill_size(top_layer, fill_config, unit_mode=True) w_tot *= nx h_tot *= ny # get number of fingers info = AnalogBaseInfo(self.grid, lch, 0, top_layer=top_layer) bin_iter = BinaryIterator(2, None) while bin_iter.has_next(): fg_cur = bin_iter.get_next() w_cur = info.get_placement_info(fg_cur).tot_width if w_cur < w_tot: bin_iter.save() bin_iter.up() elif w_cur > w_tot: bin_iter.down() else: bin_iter.save() break fg_tot = bin_iter.get_last_save() if fg_tot is None: raise ValueError('Decaep cell width exceed fill width.') self.draw_base(lch, fg_tot, ptap_w, ntap_w, [wn], [thn], [wp], [thp], ng_tracks=[1], pg_tracks=[1], n_orientations=['MX'], p_orientations=['R0'], top_layer=top_layer, min_height=h_tot, **options) if self.bound_box.height_unit > h_tot: raise ValueError('Decap cell height exceed fill height.') nmos = self.draw_mos_conn('nch', 0, 0, fg_tot, 0, 0) pmos = self.draw_mos_conn('pch', 0, 0, fg_tot, 2, 2, gate_pref_loc='s') vss_tid = self.make_track_id('pch', 0, 'g', 0) vdd_tid = self.make_track_id('nch', 0, 'g', 0) self.connect_to_substrate('ptap', nmos['d']) self.connect_to_substrate('ntap', pmos['s']) vss_g = self.connect_to_tracks([nmos['s'], pmos['g']], vss_tid) vdd_g = self.connect_to_tracks([pmos['d'], nmos['g']], vdd_tid) vss, vdd = self.fill_dummy(vdd_width=sup_width, vss_width=sup_width) vss.append(vss_g) vdd.append(vdd_g) self.add_pin('VSS', vss, label='VSS:', show=show_pins) self.add_pin('VDD', vdd, label='VDD:', show=show_pins)
def _design_stage2(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, f_unit, max_ref_ratio): seg_tail1 = seg_dict['tail1'] seg_diode1 = seg_dict['diode1'] seg_ngm1 = seg_dict['ngm1'] # step 1: find stage 2 unit size seg_gcd = gcd(gcd(seg_tail1, seg_diode1), seg_ngm1) if seg_gcd % 2 != 0: raise ValueError('All segment numbers must be even.') # divide seg_gcd by 2 to make sure all generated segment numbers are even seg_gcd //= 2 # make sure we have enough tail fingers for common mode feedback min_size = 2 if seg_tail1 // seg_gcd == 2 else 1 def ac_results_fun(cur_size): seg_dict['tail2'] = seg_tail1 // seg_gcd * cur_size seg_dict['diode2'] = seg_diode1 // seg_gcd * cur_size seg_dict['ngm2'] = seg_ngm1 // seg_gcd * cur_size cur_scale2 = cur_size / seg_gcd cur_gm2_list = [gm2 * cur_scale2 for gm2 in gm2_list] ac_results = self._find_rz_cf(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, cur_gm2_list, res_var, phase_margin) return ac_results def funity_fun(cur_size): ac_results_tmp = ac_results_fun(cur_size) fu_list = ac_results_tmp[0] if fu_list is None: return -1 # noinspection PyTypeChecker ans = min(fu_list) return ans # find min_size such that amplifier is stable min_bin_iter = BinaryIterator(min_size, None) while min_bin_iter.has_next(): test_size = min_bin_iter.get_next() test_fu = funity_fun(test_size) if test_fu >= 0: min_bin_iter.save() min_bin_iter.down() else: min_bin_iter.up() min_result = minimize_cost_golden(funity_fun, f_unit, offset=min_bin_iter.get_last_save()) if min_result.x is None: msg = 'Insufficient stage 1 current. funity_max=%.4g' raise StageOneCurrentError(msg % min_result.vmax) funity_list, rz_nom, cf_min, gain_list, f3db_list, pm_list = ac_results_fun( min_result.x) seg_tail2_tot = seg_dict['tail2'] seg_tail2 = (seg_tail2_tot // 4) * 2 seg_tailcm = seg_tail2_tot - seg_tail2 seg_tail_tot = 2 * (seg_dict['tail1'] + seg_tail2) seg_dict['tail2'] = seg_tail2 seg_dict['tailcm'] = seg_tailcm seg_dict['ref'] = max(2, -((-seg_tail_tot // max_ref_ratio) // 2) * 2) return dict( rz=rz_nom, cf=cf_min, gain=gain_list, f_3db=f3db_list, f_unity=funity_list, phase_margin=pm_list, )
def draw_layout(self): # type: () -> None w = self.params['w'] h_unit = self.params['h_unit'] sub_type = self.params['sub_type'] threshold = self.params['threshold'] top_layer = self.params['top_layer'] nser = self.params['nser'] ndum = self.params['ndum'] in_tr_info = self.params['in_tr_info'] out_tr_info = self.params['out_tr_info'] bias_idx = self.params['bias_idx'] vdd_tr_info = self.params['vdd_tr_info'] res_type = self.params['res_type'] res_options = self.params['res_options'] cap_spx = self.params['cap_spx'] cap_spy = self.params['cap_spy'] cap_margin = self.params['cap_margin'] half_blk_x = self.params['half_blk_x'] show_pins = self.params['show_pins'] res = self.grid.resolution lay_unit = self.grid.layout_unit w_unit = int(round(w / lay_unit / res)) if res_options is None: my_options = dict(well_end_mode=2) else: my_options = res_options.copy() my_options['well_end_mode'] = 2 # find resistor length info = ResArrayBaseInfo(self.grid, sub_type, threshold, top_layer=top_layer, res_type=res_type, grid_type=None, ext_dir='y', options=my_options, connect_up=True, half_blk_x=half_blk_x, half_blk_y=True) lmin, lmax = info.get_res_length_bounds() bin_iter = BinaryIterator(lmin, lmax, step=2) while bin_iter.has_next(): lcur = bin_iter.get_next() htot = info.get_place_info(lcur, w_unit, 1, 1)[3] if htot < h_unit: bin_iter.save() bin_iter.up() else: bin_iter.down() # draw resistor l_unit = bin_iter.get_last_save() nx = 2 * (nser + ndum) self.draw_array(l_unit * lay_unit * res, w, sub_type, threshold, nx=nx, ny=1, top_layer=top_layer, res_type=res_type, grid_type=None, ext_dir='y', options=my_options, connect_up=True, half_blk_x=half_blk_x, half_blk_y=True, min_height=h_unit) # connect resistors vdd, biasp, biasn, outp_h, outn_h, xl, xr = self.connect_resistors( ndum, nser, bias_idx) # draw MOM cap caplp, capln, caprp, caprn = self.draw_mom_cap(nser, xl, xr, cap_spx, cap_spy, cap_margin) # connect resistors to MOM cap, and draw metal resistors vm_layer = self.bot_layer_id + 1 self.connect_to_tracks(outp_h, capln.track_id) self.connect_to_tracks(outn_h, caprn.track_id) # connect outputs to horizontal tracks hm_layer = vm_layer + 1 pidx, nidx, tr_w = in_tr_info res_in_w = self.grid.get_track_width(hm_layer, tr_w, unit_mode=True) inp, inn = self.connect_differential_tracks(caplp, caprp, hm_layer, pidx, nidx, width=tr_w) tr_lower, tr_upper = inp.lower_unit, inp.upper_unit self.add_res_metal_warr(hm_layer, pidx, tr_lower - res_in_w, tr_lower, width=tr_w, unit_mode=True) self.add_res_metal_warr(hm_layer, nidx, tr_lower - res_in_w, tr_lower, width=tr_w, unit_mode=True) inp = self.add_wires(hm_layer, pidx, tr_lower - 2 * res_in_w, tr_lower - res_in_w, width=tr_w, unit_mode=True) inn = self.add_wires(hm_layer, nidx, tr_lower - 2 * res_in_w, tr_lower - res_in_w, width=tr_w, unit_mode=True) pidx, nidx, tr_w = out_tr_info res_out_w = self.grid.get_track_width(hm_layer, tr_w, unit_mode=True) self.connect_differential_tracks(capln, caprn, hm_layer, pidx, nidx, track_lower=tr_lower, track_upper=tr_upper, width=tr_w, unit_mode=True) self.add_res_metal_warr(hm_layer, pidx, tr_upper, tr_upper + res_out_w, width=tr_w, unit_mode=True) self.add_res_metal_warr(hm_layer, nidx, tr_upper, tr_upper + res_out_w, width=tr_w, unit_mode=True) outp = self.add_wires(hm_layer, pidx, tr_upper + res_out_w, tr_upper + 2 * res_out_w, width=tr_w, unit_mode=True) outn = self.add_wires(hm_layer, nidx, tr_upper + res_out_w, tr_upper + 2 * res_out_w, width=tr_w, unit_mode=True) # connect/export vdd if vdd_tr_info is None: self.add_pin('VDD_vm', vdd, label='VDD:', show=show_pins) else: self.add_pin('VDD_vm', vdd, label='VDD', show=show_pins) for tr_info in vdd_tr_info: tid = TrackID(hm_layer, tr_info[0], width=tr_info[1]) self.add_pin('VDD', self.connect_to_tracks(vdd, tid), show=show_pins) # add pins self.add_pin('biasp', biasp, show=show_pins) self.add_pin('biasn', biasn, show=show_pins) self.add_pin('outp', outp, show=show_pins) self.add_pin('outn', outn, show=show_pins) self.add_pin('inp', inp, show=show_pins) self.add_pin('inn', inn, show=show_pins) self._sch_params = dict( l=l_unit * lay_unit * res, w=w, intent=res_type, nser=nser, ndum=ndum, res_in_info=(hm_layer, res_in_w * res * lay_unit, res_in_w * res * lay_unit), res_out_info=(hm_layer, res_out_w * res * lay_unit, res_out_w * res * lay_unit), )
def draw_layout(self): # type: () -> None w = self.params['w'] h_unit = self.params['h_unit'] narr = self.params['narr'] sub_type = self.params['sub_type'] threshold = self.params['threshold'] top_layer = self.params['top_layer'] nser = self.params['nser'] ndum = self.params['ndum'] port_tr_w = self.params['port_tr_w'] res_type = self.params['res_type'] res_options = self.params['res_options'] cap_spx = self.params['cap_spx'] cap_spy = self.params['cap_spy'] cap_h_list = self.params['cap_h_list'] half_blk_x = self.params['half_blk_x'] show_pins = self.params['show_pins'] if nser % 2 != 0: raise ValueError('This generator only supports even nser.') res = self.grid.resolution lay_unit = self.grid.layout_unit w_unit = int(round(w / lay_unit / res)) if res_options is None: my_options = dict(well_end_mode=2) else: my_options = res_options.copy() my_options['well_end_mode'] = 2 # find resistor length info = ResArrayBaseInfo(self.grid, sub_type, threshold, top_layer=top_layer, res_type=res_type, ext_dir='y', options=my_options, connect_up=True, half_blk_x=half_blk_x, half_blk_y=True) lmin, lmax = info.get_res_length_bounds() bin_iter = BinaryIterator(lmin, lmax, step=2) while bin_iter.has_next(): lcur = bin_iter.get_next() htot = info.get_place_info(lcur, w_unit, 1, 1)[3] if htot < h_unit: bin_iter.save() bin_iter.up() else: bin_iter.down() # draw resistor l_unit = bin_iter.get_last_save() nx = 2 * ndum + narr * nser self.draw_array(l_unit * lay_unit * res, w, sub_type, threshold, nx=nx, ny=1, top_layer=top_layer, res_type=res_type, grid_type=None, ext_dir='y', options=my_options, connect_up=True, half_blk_x=half_blk_x, half_blk_y=True, min_height=h_unit) # get cap settings bot_layer = self.bot_layer_id + 1 for lay in range(bot_layer, top_layer + 1): if self.grid.get_direction(lay) == 'x': cap_spx = max( cap_spx, self.grid.get_line_end_space(lay, 1, unit_mode=True)) # connect resistors and draw MOM caps tmp = self._connect_resistors(narr, nser, ndum, cap_spx, port_tr_w, show_pins) rout_list, cap_x_list = tmp tmp = self._draw_mom_cap(cap_x_list, bot_layer, top_layer, cap_spy, cap_h_list, port_tr_w, show_pins) cout_list, ores_info, cres_info = tmp # connect bias resistor to cap for rout, cout in zip(rout_list, cout_list): self.connect_to_track_wires(rout, cout) # set schematic parameters self._sch_params = dict( narr=narr, ndum=ndum * 2, hp_params=dict( l=l_unit * lay_unit * res, w=w, intent=res_type, nser=nser, ndum=0, res_in_info=cres_info, res_out_info=ores_info, sub_name='VSS', ), )
def find_core_size( self, # type: ResTech grid, # type: RoutingGrid params, # type: Dict[str, Any] wres, # type: int hres, # type: int wblk, # type: int hblk, # type: int ext_dir, # type: str max_blk_ext, # type: int ): # type: (...) -> Tuple[int, int, Dict[str, Any]] """Compute resistor core size that meets DRC rules. Given current resistor block size and the block pitch, increase the resistor block size if necessary to meet DRC rules. Parameters ---------- grid : RoutingGrid the RoutingGrid object. params : Dict[str, Any] the resistor parameters dictionary. wres : int resistor core width, in resolution units. hres : int resistor core height, in resolution units. wblk : int the horizontal block pitch, in resolution units. hblk : int the vertical block pitch, in resolution units. ext_dir : Optional[str] if equal to 'x', then we will only stretch the resistor core horizontally. If equal to 'y', we will only stretch the resistor core vertically. Otherwise, we will find the resistor core with the minimum area that meets the density spec. max_blk_ext : int number of block pitches we can extend the resistor core size by. If we cannot find a valid core size by extending this many block pitches, we declare failure. Returns ------- nxblk : int width of the resistor core, in units of wblk. nyblk : int height of the resistor core, in units of hblk. layout_info : Dict[str, Any] the core layout information dictionary. """ nxblk = wres // wblk nyblk = hres // hblk ans = None x_only = (ext_dir == 'x') if x_only or (ext_dir == 'y'): # only extend X or Y direction if x_only: bin_iter = BinaryIterator(nxblk, nxblk + max_blk_ext + 1) else: bin_iter = BinaryIterator(nyblk, nyblk + max_blk_ext + 1) while bin_iter.has_next(): ncur = bin_iter.get_next() if x_only: wcur, hcur = ncur * wblk, hres else: wcur, hcur = wres, ncur * hblk tmp = self.get_core_info(grid, wcur, hcur, **params) if tmp is None: bin_iter.up() else: ans = tmp bin_iter.save() bin_iter.down() if ans is None: raise ValueError( 'failed to find DRC clean core with maximum %d ' 'additional block pitches.' % max_blk_ext) if x_only: nxblk = bin_iter.get_last_save() else: nyblk = bin_iter.get_last_save() return nxblk, nyblk, ans else: # extend in both direction opt_area = (nxblk + max_blk_ext + 1) * (nyblk + max_blk_ext + 1) # linear search in height, binary search in width # in this way, for same area, use height as tie breaker nxopt, nyopt = nxblk, nyblk for nycur in range(nyblk, nyblk + max_blk_ext + 1): # check if we should terminate linear search if nycur * nxblk >= opt_area: break bin_iter = BinaryIterator(nxblk, nxblk + max_blk_ext + 1) hcur = nycur * hblk while bin_iter.has_next(): nxcur = bin_iter.get_next() if nxcur * nycur >= opt_area: # this point can't beat current optimum bin_iter.down() else: tmp = self.get_core_info(grid, nxcur * wblk, hcur, **params) if tmp is None: bin_iter.up() else: # found new optimum ans, nxopt, nyopt = tmp, nxcur, nycur opt_area = nxcur * nycur bin_iter.down() if ans is None: raise ValueError( 'failed to find DRC clean core with maximum %d ' 'additional block pitches.' % max_blk_ext) return nxopt, nyopt, ans
def draw_layout(self): lch = self.params['lch'] ntap_w = self.params['ntap_w'] w = self.params['w'] fg_ref = self.params['fg_ref'] threshold = self.params['threshold'] out_xc_list = self.params['out_xc_list'] supply_tracks = self.params['supply_tracks'] em_specs = self.params['em_specs'] tr_widths = self.params['tr_widths'] tr_spaces = self.params['tr_spaces'] tot_width = self.params['tot_width'] guard_ring_nf = self.params['guard_ring_nf'] show_pins = self.params['show_pins'] # get AnalogBaseInfo hm_layer = self.mos_conn_layer + 1 ym_layer = hm_layer + 1 layout_info = AnalogBaseInfo(self.grid, lch, guard_ring_nf, top_layer=ym_layer, half_blk_y=False) # compute total number of fingers to achieve target width. bin_iter = BinaryIterator(2, None, step=2) while bin_iter.has_next(): fg_cur = bin_iter.get_next() w_cur = layout_info.get_placement_info(fg_cur).tot_width if w_cur < tot_width: bin_iter.save() bin_iter.up() elif w_cur > tot_width: bin_iter.down() else: bin_iter.save() break fg_tot = bin_iter.get_last_save() # find number of tracks needed for output tracks from EM specs hm_tr_w_out = self.grid.get_min_track_width(hm_layer, **em_specs) hm_tr_sp_out = self.grid.get_num_space_tracks(hm_layer, hm_tr_w_out, half_space=True) hm_w = self.grid.get_track_width(hm_layer, hm_tr_w_out, unit_mode=True) ym_tr_w = self.grid.get_min_track_width(ym_layer, bot_w=hm_w, **em_specs, unit_mode=True) # construct track width/space dictionary from EM specs tr_manager = TrackManager(self.grid, tr_widths, tr_spaces, half_space=True) tr_w_dict = { 'in': { hm_layer: tr_manager.get_width(hm_layer, 'in') }, 'out': { hm_layer: hm_tr_w_out, ym_tr_w: ym_tr_w }, } tr_sp_dict = { ('in', 'out'): { hm_layer: max(hm_tr_sp_out, tr_manager.get_space(hm_layer, ('in', 'out'))) }, } tr_manager = TrackManager(self.grid, tr_w_dict, tr_sp_dict, half_space=True) pw_list = [w, w, w] pth_list = [threshold, threshold, threshold] wire_names = dict( nch=[], pch=[ dict( ds=['out'], g=['in'], ), dict( g=['out'], ds=['out'], ds2=['out'], ), dict(g=['in'], ds=['out']), ], ) # draw transistor rows self.draw_base(lch, fg_tot, ntap_w, ntap_w, [], [], pw_list, pth_list, tr_manager=tr_manager, wire_names=wire_names, p_orientations=['MX', 'R0', 'R0'], guard_ring_nf=guard_ring_nf, pgr_w=ntap_w, ngr_w=ntap_w, top_layer=ym_layer, half_blk_y=False) outn_tid = self.get_wire_id('pch', 0, 'ds', wire_name='out') inp_tid = self.get_wire_id('pch', 0, 'g', wire_name='in') bias_tid = self.get_wire_id('pch', 1, 'g', wire_name='out') vdd_tid = self.get_wire_id('pch', 1, 'ds', wire_name='out') tail_tid = self.get_wire_id('pch', 1, 'ds2', wire_name='out') inn_tid = self.get_wire_id('pch', 2, 'g', wire_name='in') outp_tid = self.get_wire_id('pch', 2, 'ds', wire_name='out') out_pitch = out_xc_list[1] - out_xc_list[0] sd_pitch = layout_info.sd_pitch_unit if out_pitch % sd_pitch != 0: raise ValueError('Oops') fg = out_pitch // sd_pitch - fg_ref # draw transistors and connect inp_list = [] inn_list = [] tail_list = [] bias_list = [] vdd_m_list = [] outp_list = [] outn_list = [] layout_info = self.layout_info num_out = len(out_xc_list) for idx, xc in enumerate(out_xc_list): ym_idx = self.grid.coord_to_track(ym_layer, xc, unit_mode=True) vtid = TrackID(ym_layer, ym_idx, width=ym_tr_w) # find column index that centers on given track index x_coord = self.grid.track_to_coord(ym_layer, ym_idx, unit_mode=True) col_center = layout_info.coord_to_col(x_coord, unit_mode=True) col_idx = col_center - (fg // 2) # draw transistors if idx == 0: mref = self.draw_mos_conn('pch', 1, col_idx - fg_ref, fg_ref, 2, 0, d_net='ibias', diode_conn=True, gate_pref_loc='d') bias_list.append(mref['g']) bias_list.append(mref['d']) vdd_m_list.append(mref['s']) mtop = self.draw_mos_conn('pch', 2, col_idx, fg, 2, 0, s_net='ioutp', d_net='tail') mbot = self.draw_mos_conn('pch', 0, col_idx, fg, 0, 2, s_net='ioutn', d_net='tail') mtail = self.draw_mos_conn('pch', 1, col_idx, fg, 2, 0, gate_pref_loc='s', s_net='', d_net='tail') mref = self.draw_mos_conn('pch', 1, col_idx + fg, fg_ref, 2, 0, gate_pref_loc='d', diode_conn=True, d_net='ibias') # connect inp_list.append(mbot['g']) inn_list.append(mtop['g']) bias_list.append(mref['g']) bias_list.append(mref['d']) bias_list.append(mtail['g']) tail_list.append(mtop['d']) tail_list.append(mbot['d']) tail_list.append(mtail['d']) vdd_m_list.append(mtail['s']) vdd_m_list.append(mref['s']) outp_h = self.connect_to_tracks(mtop['s'], outp_tid) outp_list.append(outp_h) self.add_pin('ioutp', self.connect_to_tracks(outp_h, vtid), show=show_pins) outn_h = self.connect_to_tracks(mbot['s'], outn_tid) outn_list.append(outn_h) self.add_pin('ioutn', self.connect_to_tracks(outn_h, vtid), show=show_pins) self.connect_wires(outp_list) self.connect_wires(outn_list) self.add_pin('inp', self.connect_to_tracks(inp_list, inp_tid, track_lower=0, unit_mode=True), show=show_pins) self.add_pin('inn', self.connect_to_tracks(inn_list, inn_tid, track_lower=0, unit_mode=True), show=show_pins) ibias = self.connect_to_tracks(bias_list, bias_tid, track_lower=0, unit_mode=True) self.add_pin('ibias', ibias, show=show_pins) self.connect_to_tracks(tail_list, tail_tid, track_lower=ibias.lower_unit, track_upper=ibias.upper_unit, unit_mode=True) vdd_m = self.connect_to_tracks(vdd_m_list, vdd_tid) _, vdd_warrs = self.fill_dummy() vdd_warrs.append(vdd_m) right_tidx = 0 for tidx in supply_tracks: vtid = TrackID(ym_layer, tidx, width=ym_tr_w) right_tidx = max(right_tidx, tidx) self.add_pin('VDD', self.connect_to_tracks(vdd_warrs, vtid), show=show_pins) for xc in out_xc_list: tidx = self.grid.coord_to_track(ym_layer, xc, unit_mode=True) vtid = TrackID(ym_layer, tidx, width=ym_tr_w) self.add_pin('VDD', self.connect_to_tracks(vdd_m, vtid), show=show_pins) self.fill_box = bnd_box = self.bound_box for lay in range(1, self.top_layer): self.do_max_space_fill(lay, bnd_box, fill_pitch=3) self._sch_params = dict( lch=lch, w_dict={ 'in': w, 'tail': w }, th_dict={ 'in': threshold, 'tail': threshold }, seg_dict={ 'in': fg * num_out, 'tail': fg * num_out, 'ref': fg_ref * (num_out + 1) }, dum_info=self.get_sch_dummy_info(), ) ratio = fg * num_out / (fg_ref * (num_out + 1)) scale = num_out / ratio self._ibias_em_specs = em_specs.copy() for key in ['idc', 'iac_rms', 'iac_peak']: if key in em_specs: self._ibias_em_specs[key] *= scale