def evaluate(self, design_list): # type: (List[Design]) -> List subckts_yaml_files = dict( zip(self.subckts_template.keys(), [[]] * len(self.subckts_template))) top_level_yaml_files = [] for dsn_num, design in enumerate(design_list): top_specs = deepcopy(self.ver_specs) layout_update = top_specs['layout_params'] measurement_update = top_specs['measurements'][0] params_dict = dict(zip(self.params_vec.keys(), design)) for key, value_idx in params_dict.items(): params_dict[key] = self.params_vec[key][value_idx] self.impose_constraints(params_dict) for key, value in params_dict.items(): next_key = self.decend(key) if next_key in self.param_choices_layout.keys(): self.update_with_unmerged_key(layout_update, next_key, value) elif next_key in self.param_choices_measurement.keys(): self.update_with_unmerged_key(measurement_update, next_key, value) # for each subckt we partition the updated layout into individual files subckts_template = deepcopy(self.subckts_template) for subckt_key, subckt in subckts_template.items(): subckt['layout_params'].update(**layout_update[subckt_key]) fname = os.path.join(self.subckts_yaml_dirs[subckt_key], 'params_{}.yaml'.format(str(design.id))) with open(fname, 'w') as f: yaml.dump(subckt, f) subckts_yaml_files[subckt_key].append(fname) # for top level we create the individual yaml files fname = os.path.join(self.top_level_dir, 'params_top_' + str(design.id) + '.yaml') with open_file(fname, 'w') as f: yaml.dump(top_specs, f) top_level_yaml_files.append(fname) top_specs = deepcopy(self.ver_specs) for key, value in subckts_yaml_files.items(): subckts_template = deepcopy(self.subckts_template) subckts_template[key]['sweep_params']['swp_spec_file'] = value subckts_template[key]['root_dir'] = os.path.join( top_specs['root_dir'], key) with open_file(self.subckts_main_file[key], 'w') as f: yaml.dump(subckts_template[key], f) top_specs['sweep_params']['swp_spec_file'] = top_level_yaml_files with open_file(self.top_level_main_file, 'w') as f: yaml.dump(top_specs, f) results = self.generate_and_sim() return self.process_results(results)
async def verify_design(self, lib_name: str, dsn_name: str, load_from_file: bool = False) -> None: """Run all measurements on the given design. Parameters ---------- lib_name : str library name. dsn_name : str design cell name. load_from_file : bool If True, then load existing simulation data instead of running actual simulation. """ meas_list = self.specs['measurements'] summary_fname = self.specs['summary_fname'] view_name = self.specs['view_name'] env_list = self.specs['env_list'] wrapper_list = self.specs['dut_wrappers'] wrapper_lookup = {'': dsn_name} for wrapper_config in wrapper_list: wrapper_type = wrapper_config['name'] wrapper_lookup[wrapper_type] = self.get_wrapper_name( dsn_name, wrapper_type) result_summary = {} dsn_data_dir = os.path.join(self._root_dir, dsn_name) for meas_specs in meas_list: meas_type = meas_specs['meas_type'] meas_package = meas_specs['meas_package'] meas_cls_name = meas_specs['meas_class'] out_fname = meas_specs['out_fname'] meas_name = self.get_measurement_name(dsn_name, meas_type) data_dir = self.get_measurement_directory(dsn_name, meas_type) meas_module = importlib.import_module(meas_package) meas_cls = getattr(meas_module, meas_cls_name) meas_manager = meas_cls(data_dir, meas_name, lib_name, meas_specs, wrapper_lookup, [(dsn_name, view_name)], env_list) print('Performing measurement %s on %s' % (meas_name, dsn_name)) meas_res = await meas_manager.async_measure_performance( self.prj, load_from_file=load_from_file) print('Measurement %s finished on %s' % (meas_name, dsn_name)) with open_file(os.path.join(data_dir, out_fname), 'w') as f: yaml.dump(meas_res, f) result_summary[meas_type] = meas_res with open_file(os.path.join(dsn_data_dir, summary_fname), 'w') as f: yaml.dump(result_summary, f)
def design(amp_dsn_specs, amp_char_specs_fname, amp_char_specs_out_fname): nch_config = amp_dsn_specs['nch_config'] pch_config = amp_dsn_specs['pch_config'] print('create transistor database') nch_db = MOSDBDiscrete([nch_config]) pch_db = MOSDBDiscrete([pch_config]) nch_db.set_dsn_params(**amp_dsn_specs['nch']) pch_db.set_dsn_params(**amp_dsn_specs['pch']) result = design_amp(amp_dsn_specs, nch_db, pch_db) if result is None: raise ValueError('No solution.') pprint.pprint(result) # update characterization spec file amp_char_specs = read_yaml(amp_char_specs_fname) # update bias var_dict = amp_char_specs['measurements'][0]['testbenches']['ac'][ 'sim_vars'] for key in ('vtail', 'vindc', 'voutdc'): var_dict[key] = result[key] for key in ('vdd', 'cload'): var_dict[key] = amp_dsn_specs[key] # update segments seg_dict = amp_char_specs['layout_params']['seg_dict'] for key in ('in', 'load', 'tail'): seg_dict[key] = result['seg_' + key] with open_file(amp_char_specs_out_fname, 'w') as f: yaml.dump(amp_char_specs, f) return result
def __init__(self, prj, spec_file): # type: (Optional[BagProject], str) -> None self.prj = prj self._specs = None if os.path.isfile(spec_file): self._specs = read_yaml(spec_file) root_dir = os.path.abspath(self._specs['root_dir']) save_spec_file = os.path.join(root_dir, 'specs.yaml') elif os.path.isdir(spec_file): root_dir = os.path.abspath(spec_file) save_spec_file = spec_file = os.path.join(root_dir, 'specs.yaml') self._specs = read_yaml(spec_file) else: raise ValueError( '%s is neither data directory or specification file.' % spec_file) self._swp_var_list = tuple(sorted(self._specs['sweep_params'].keys())) # save root_dir as absolute path, in this way everything will still work # if the user start python from a different directory. self._specs['root_dir'] = root_dir os.makedirs(root_dir, exist_ok=True) with open_file(save_spec_file, 'w') as f: yaml.dump(self._specs, f)
async def async_measure_performance(self, prj, load_from_file=False): # type: (BagProject, bool) -> Dict[str, Any] """A coroutine that performs measurement. The measurement is done like a FSM. On each iteration, depending on the current state, it creates a new testbench (or reuse an existing one) and simulate it. It then post-process the simulation data to determine the next FSM state, or if the measurement is done. Parameters ---------- prj : BagProject the BagProject instance. load_from_file : bool If True, then load existing simulation data instead of running actual simulation. Returns ------- output : Dict[str, Any] the last dictionary returned by process_output(). """ cur_state = self.get_initial_state() prev_output = None done = False while not done: # create and setup testbench tb_name, tb_type, tb_specs, tb_sch_params = self.get_testbench_info(cur_state, prev_output) tb_package = tb_specs['tb_package'] tb_cls_name = tb_specs['tb_class'] tb_module = importlib.import_module(tb_package) tb_cls = getattr(tb_module, tb_cls_name) raw_data_fname = os.path.join(self.data_dir, '%s.hdf5' % cur_state) tb_manager = tb_cls(raw_data_fname, tb_name, self.impl_lib, tb_specs, self.sim_view_list, self.env_list) if load_from_file: print('Measurement %s in state %s, ' 'load sim data from file.' % (self.meas_name, cur_state)) if os.path.isfile(raw_data_fname): cur_results = load_sim_file(raw_data_fname) else: print('Cannot find data file, simulating...') cur_results = await tb_manager.setup_and_simulate(prj, tb_sch_params) else: cur_results = await tb_manager.setup_and_simulate(prj, tb_sch_params) # process and save simulation data print('Measurement %s in state %s, ' 'processing data from %s' % (self.meas_name, cur_state, tb_name)) done, next_state, prev_output = self.process_output(cur_state, cur_results, tb_manager) with open_file(os.path.join(self.data_dir, '%s.yaml' % cur_state), 'w') as f: yaml.dump(prev_output, f) cur_state = next_state return prev_output
def evaluate(self, design_list): # type: (List[Design]) -> List swp_spec_file_list = [] sweep_params_update = deepcopy(self.ver_specs['sweep_params']) # del template['sweep_params']['swp_spec_file'] for dsn_num, design in enumerate(design_list): # 1. translate each list to a dict with layout_params and measurement_params indication # 2. write those dictionaries in the corresponding param.yaml and update self.ver_specs specs = deepcopy(self.ver_specs) layout_update = specs['layout_params'] measurement_update = specs['measurements'][0] params_dict = dict(zip(self.params_vec.keys(), design)) # imposing the constraint of layout generator self.impose_constraints(params_dict) # TODO: Still cannot handle multiple measurement manager units for key, value_idx in params_dict.items(): next_key = self.decend(key) if next_key in self.param_choices_layout.keys(): self.update_with_unmerged_key(layout_update, next_key, self.params_vec[key][value_idx]) elif next_key in self.param_choices_measurement.keys(): self.update_with_unmerged_key(measurement_update, next_key, self.params_vec[key][value_idx]) specs['sweep_params']['swp_spec_file'] = ['params_'+str(design.id)] swp_spec_file_list.append('params_'+str(design.id)) fname = os.path.join(self.swp_spec_dir, 'params_'+str(design.id)+'.yaml') with open_file(fname, 'w') as f: yaml.dump(specs, f) sweep_params_update['swp_spec_file'] = swp_spec_file_list self.ver_specs['sweep_params'].update(sweep_params_update) results = self.generate_and_sim() return self.process_results(results)
def _setup_pwl_input(cls, values, tper, tr, tran_fname): # type: (List[float], float) -> None tvec, yvec = dig_to_pwl(values, tper, tr, td=0.0) tran_fname = os.path.abspath(tran_fname) stimuli_dir = os.path.dirname(tran_fname) os.makedirs(stimuli_dir, exist_ok=True) with open_file(tran_fname, 'w') as f: for t, y in zip(tvec, yvec): f.write('%.8f %.8f\n' % (t, y))
def generate_and_sim(self): """ phase 1 of evaluation is generation of layout, schematic, LVS and RCX If any of LVS or RCX fail results_ph1 will contain Exceptions for the corresponding instance We proceed to phase 2 only if phase 1 was successful. phase 2 is running the simulation with post extracted netlist view Then we aggregate the results of phase 1 and phase 2 in a single list, in the same order that designs were ordered, if phase 1 was failed the corresponding entry will contain a Phase1Error exception """ results = [] with open_file(self.top_level_main_file, 'w') as f: yaml.dump(self.ver_specs, f) sim = DeepCKTDesignManager(self.bprj, self.top_level_main_file) if self.temp_db is None: self.temp_db = sim.make_tdb() sim.set_tdb(self.temp_db) results_ph1 = sim.characterize_designs(generate=True, measure=False, load_from_file=False) # hacky: do parallel measurements, you should not sweep anything other than 'swp_spec_file' in sweep_params # the new yaml files themselves should not include any sweep_param start = time.time() impl_lib = self.ver_specs['impl_lib'] coro_list = [] file_list = self.ver_specs['sweep_params']['swp_spec_file'] for ph1_iter_index, combo_list in enumerate( sim.get_combinations_iter()): dsn_name = sim.get_design_name(combo_list) specs_fname = os.path.join(self.gen_yamls_dir, file_list[ph1_iter_index] + '.yaml') if isinstance(results_ph1[ph1_iter_index], Exception): continue coro_list.append( self.async_characterization(impl_lib, dsn_name, specs_fname)) results_ph2 = batch_async_task(coro_list) print("sim time: {}".format(time.time() - start)) # this part returns the correct order of results if some of the instances failed phase1 of evaluation ph2_iter_index = 0 for ph1_iter_index, combo_list in enumerate( sim.get_combinations_iter()): if isinstance(results_ph1[ph1_iter_index], Exception): results.append(Phase1Error) else: results.append(results_ph2[ph2_iter_index]) ph2_iter_index += 1 # pprint.pprint(results) return results
def process_ibias_data(self, write=True): # type: () -> None tb_type = 'tb_ibias' tb_specs = self.specs[tb_type] dsn_name_base = self.specs['dsn_name_base'] root_dir = self.specs['root_dir'] vgs_file = self.specs['vgs_file'] layout_params = self.specs['layout_params'] fg = layout_params['fg'] ibias_min_fg = tb_specs['ibias_min_fg'] ibias_max_fg = tb_specs['ibias_max_fg'] vgs_res = tb_specs['vgs_resolution'] ans = {} for val_list in self.get_combinations_iter(): # invert PMOS ibias sign is_nmos = self.is_nmos(val_list) ibias_sgn = 1.0 if is_nmos else -1.0 results = self.get_sim_results(tb_type, val_list) # assume first sweep parameter is corner, second sweep parameter is vgs corner_idx = results['sweep_params']['ibias'].index('corner') vgs = results['vgs'] ibias = results['ibias'] * ibias_sgn # type: np.ndarray wv_max = Waveform(vgs, np.amax(ibias, corner_idx), 1e-6, order=2) wv_min = Waveform(vgs, np.amin(ibias, corner_idx), 1e-6, order=2) vgs1 = wv_max.get_crossing(ibias_min_fg * fg) if vgs1 is None: vgs1 = vgs[0] if is_nmos else vgs[-1] vgs2 = wv_min.get_crossing(ibias_max_fg * fg) if vgs2 is None: vgs2 = vgs[-1] if is_nmos else vgs[0] if is_nmos: vgs_min, vgs_max = vgs1, vgs2 else: vgs_min, vgs_max = vgs2, vgs1 vgs_min = math.floor(vgs_min / vgs_res) * vgs_res vgs_max = math.ceil(vgs_max / vgs_res) * vgs_res dsn_name = self.get_instance_name(dsn_name_base, val_list) print('%s: vgs = [%.4g, %.4g]' % (dsn_name, vgs_min, vgs_max)) ans[dsn_name] = [vgs_min, vgs_max] if write: vgs_file = os.path.join(root_dir, vgs_file) with open_file(vgs_file, 'w') as f: yaml.dump(ans, f)
def generate_and_sim(prj, generate=True): ver_specs_fname = 'specs_verification/opamp_two_stage_1e8.yaml' sim_specs_fname = 'specs_verification/opamp_two_stage_1e8_sim.yaml' ver_specs = read_yaml(ver_specs_fname) ver_specs['measurements'][0]['find_cfb'] = False with open_file(sim_specs_fname, 'w') as f: yaml.dump(ver_specs, f) sim = DesignManager(prj, sim_specs_fname) sim.characterize_designs(generate=generate, measure=True, load_from_file=False) dsn_name = list(sim.get_dsn_name_iter())[0] summary = sim.get_result(dsn_name)['opamp_ac'] print('result:') pprint.pprint(summary)
def get_result(self, dsn_name): # type: (str) -> Dict[str, Any] """Returns the measurement result summary dictionary. Parameters ---------- dsn_name : str the design name. Returns ------- result : Dict[str, Any] the result dictionary. """ fname = os.path.join(self._root_dir, dsn_name, self.specs['summary_fname']) with open_file(fname, 'r') as f: summary = yaml.load(f) return summary
def get_state_output(self, state): # type: (str) -> Dict[str, Any] """Get the post-processed output of the given state.""" with open_file(os.path.join(self.data_dir, '%s.yaml' % state), 'r') as f: return yaml.load(f)
def design_close_loop(prj, funity_min_first=None, max_iter=100): interp_method = 'spline' nch_conf_list = [ 'data/nch_w4_stack/specs.yaml', ] pch_conf_list = [ 'data/pch_w4_stack/specs.yaml', ] amp_specs_fname = 'specs_design/opamp_two_stage_1e8.yaml' ver_specs_fname = 'specs_verification/opamp_two_stage_1e8.yaml' iter_cnt = 0 f_unit_min_sim = -1 k_max = 2.0 k_min = 1.1 print('create transistor database') nch_db = MOSDBDiscrete(nch_conf_list, interp_method=interp_method) pch_db = MOSDBDiscrete(pch_conf_list, interp_method=interp_method) top_specs = read_yaml(amp_specs_fname) funity_dsn_targ = funity_targ = top_specs['dsn_specs']['f_unit'] sim, dsn_info = None, None summary = None while f_unit_min_sim < funity_targ and iter_cnt < max_iter: print('Iteration %d, f_unit_dsn_targ = %.4g' % (iter_cnt, funity_dsn_targ)) top_specs['dsn_specs']['f_unit'] = funity_dsn_targ if dsn_info is not None: top_specs['dsn_specs']['i1_min_size'] = dsn_info['i1_size'] if funity_min_first is not None and iter_cnt == 0: generate = False f_unit_min_dsn = funity_min_first else: generate = True dsn = design(top_specs, nch_db, pch_db) dsn_info = dsn.get_dsn_info() f_unit_min_dsn = min(dsn_info['f_unit']) ver_specs = dsn.get_specs_verification(top_specs) with open_file(ver_specs_fname, 'w') as f: yaml.dump(ver_specs, f) sim = DesignManager(prj, ver_specs_fname) sim.characterize_designs(generate=generate, measure=True, load_from_file=False) dsn_name = list(sim.get_dsn_name_iter())[0] summary = sim.get_result(dsn_name)['opamp_ac'] funity_list = summary['funity'] print('Iteration %d, result:' % iter_cnt) pprint.pprint(summary) f_unit_min_sim = min(funity_list) k = funity_targ / f_unit_min_sim k_real = max(k_min, min(k, k_max)) print('k = %.4g, k_real = %.4g' % (k, k_real)) funity_dsn_targ = f_unit_min_dsn * k_real iter_cnt += 1 print('close loop design done. Final result:') pprint.pprint(summary) return dsn_info