Source code for bag.core

# SPDX-License-Identifier: BSD-3-Clause AND Apache-2.0
# Copyright 2018 Regents of the University of California
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Copyright 2019 Blue Cheetah Analog Design Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""This is the core bag module.
"""
from __future__ import annotations

from typing import (
    TYPE_CHECKING, Dict, Any, Tuple, Optional, Type, Sequence, Union, Mapping, cast, List
)

import os
import shutil
import pprint
from pathlib import Path
import time

from pybag.enum import DesignOutput, SupplyWrapMode, LogLevel
from pybag.core import PySchCellViewInfo

from .io.file import write_yaml, read_yaml
from .interface import ZMQDealer
from .interface.lef import LEFInterface
from .design.netlist import add_mismatch_offsets
from .design.database import ModuleDB
from .design.module import Module
from .layout.routing.grid import RoutingGrid
from .layout.template import TemplateDB, TemplateBase
from .layout.tech import TechInfo
from .concurrent.core import batch_async_task
from .env import (
    get_port_number, get_bag_config, get_bag_work_dir, create_routing_grid, get_bag_tmp_dir,
    get_gds_layer_map, get_gds_object_map
)
from .util.importlib import import_class
from .simulation.data import netlist_info_from_dict
from .simulation.hdf5 import load_sim_data_hdf5
from .simulation.core import TestbenchManager
from .simulation.core import MeasurementManager as MeasurementManagerOld
from .simulation.measure import MeasurementManager
from .simulation.cache import SimulationDB, DesignDB

if TYPE_CHECKING:
    from .simulation.base import SimAccess, EmSimAccess


[docs]class BagProject: """The main bag controller class. This class mainly stores all the user configurations, and issue high level bag commands. Attributes ---------- bag_config : Dict[str, Any] the BAG configuration parameters dictionary. """ def __init__(self) -> None: self.bag_config = get_bag_config() bag_tmp_dir = get_bag_tmp_dir() bag_work_dir = get_bag_work_dir() # get port files port, msg = get_port_number(bag_config=self.bag_config) if msg: print(f'*WARNING* {msg}. Operating without Virtuoso.') # create ZMQDealer object dealer_kwargs = {} dealer_kwargs.update(self.bag_config['socket']) del dealer_kwargs['port_file'] # create TechInfo instance self._grid = create_routing_grid() if port >= 0: # make DbAccess instance. dealer = ZMQDealer(port, **dealer_kwargs) else: dealer = None # create database interface object try: lib_defs_file = os.path.join(bag_work_dir, self.bag_config['lib_defs']) except ValueError: lib_defs_file = '' db_cls = cast(Type['DbAccess'], import_class(self.bag_config['database']['class'])) self.impl_db = db_cls(dealer, bag_tmp_dir, self.bag_config['database'], lib_defs_file) self._default_lib_path = self.impl_db.default_lib_path # make SimAccess instance. sim_cls = cast(Type['SimAccess'], import_class(self.bag_config['simulation']['class'])) self._sim = sim_cls(bag_tmp_dir, self.bag_config['simulation']) # make EmSimAccess instance if it is defined in bag_config.yaml if 'em_simulation' in self.bag_config: em_sim_cls = cast(Type['EmSimAccess'], import_class(self.bag_config['em_simulation']['class'])) self._em_sim = em_sim_cls(bag_tmp_dir, self.bag_config['em_simulation']) else: self._em_sim = None # make LEFInterface instance self._lef: Optional[LEFInterface] = None lef_config = self.bag_config.get('lef', None) if lef_config is not None: lef_cls = cast(Type[LEFInterface], import_class(lef_config['class'])) self._lef = lef_cls(lef_config) @property
[docs] def tech_info(self) -> TechInfo: """TechInfo: the TechInfo object.""" return self._grid.tech_info
@property
[docs] def grid(self) -> RoutingGrid: """RoutingGrid: the global routing grid object.""" return self._grid
@property
[docs] def default_lib_path(self) -> str: return self._default_lib_path
@property
[docs] def sim_access(self) -> SimAccess: return self._sim
@property
[docs] def em_sim_access(self) -> Optional[EmSimAccess]: return self._em_sim
[docs] def close_bag_server(self) -> None: """Close the BAG database server.""" self.impl_db.close() self.impl_db = None
[docs] def import_sch_cellview(self, lib_name: str, cell_name: str, view_name: str = 'schematic') -> None: """Import the given schematic and symbol template into Python. This import process is done recursively. Parameters ---------- lib_name : str library name. cell_name : str cell name. view_name : str view name. """ self.impl_db.import_sch_cellview(lib_name, cell_name, view_name)
[docs] def import_design_library(self, lib_name, view_name='schematic'): # type: (str, str) -> None """Import all design templates in the given library from CAD database. Parameters ---------- lib_name : str name of the library. view_name : str the view name to import from the library. """ self.impl_db.import_design_library(lib_name, view_name)
[docs] def import_gds_file(self, gds_fname: str, lib_name: str) -> None: lay_map = get_gds_layer_map() obj_map = get_gds_object_map() self.impl_db.create_library(lib_name) self.impl_db.import_gds_file(gds_fname, lib_name, lay_map, obj_map, self.grid)
[docs] def get_cells_in_library(self, lib_name): # type: (str) -> Sequence[str] """Get a list of cells in the given library. Returns an empty list if the given library does not exist. Parameters ---------- lib_name : str the library name. Returns ------- cell_list : Sequence[str] a list of cells in the library """ return self.impl_db.get_cells_in_library(lib_name)
[docs] def make_template_db(self, log_file: str, impl_lib: str, **kwargs: Any) -> TemplateDB: """Create and return a new TemplateDB instance. Parameters ---------- log_file: str the log file path. impl_lib : str the library name to put generated layouts in. **kwargs : Any optional TemplateDB parameters. """ return TemplateDB(self.grid, impl_lib, log_file, prj=self, **kwargs)
[docs] def make_module_db(self, log_file: str, impl_lib: str, **kwargs: Any) -> ModuleDB: """Create and return a new ModuleDB instance. Parameters ---------- log_file: str the log file path. impl_lib : str the library name to put generated layouts in. **kwargs : Any optional ModuleDB parameters. """ return ModuleDB(self.tech_info, impl_lib, log_file, prj=self, **kwargs)
[docs] def make_dsn_db(self, root_dir: Path, log_file: str, impl_lib: str, sch_db: Optional[ModuleDB] = None, lay_db: Optional[TemplateDB] = None, **kwargs: Any) -> DesignDB: if sch_db is None: sch_db = self.make_module_db(log_file, impl_lib) if lay_db is None: lay_db = self.make_template_db(log_file, impl_lib) dsn_db = DesignDB(root_dir, log_file, self.impl_db, self.sim_access.netlist_type, sch_db, lay_db, **kwargs) return dsn_db
[docs] def make_sim_db(self, dsn_dir: Path, log_file: str, impl_lib: str, dsn_options: Optional[Mapping[str, Any]] = None, **kwargs: Any) -> SimulationDB: if dsn_options is None: dsn_options = {} dsn_db = self.make_dsn_db(dsn_dir, log_file, impl_lib, **dsn_options) sim_db = SimulationDB(log_file, dsn_db, **kwargs) return sim_db
[docs] def get_root_path(self, root_dir: Union[str, Path], use_sim_path: bool = True) -> Path: if isinstance(root_dir, str): root_path = Path(root_dir) else: root_path = root_dir if not root_path.is_absolute() and use_sim_path: root_path = self._sim._dir_path / root_path return root_path.resolve()
@staticmethod
[docs] def get_dut_class_info(specs: Mapping[str, Any]) -> Tuple[bool, Optional[Type[TemplateBase]], Optional[Type[Module]]]: """Returns information about the DUT generator class. Parameters ---------- specs : Param The generator specs. Returns ------- is_lay : bool True if the DUT generator is a layout generator, False if schematic generator. lay_cls : Optional[Type[TemplateBase]] The DUT layout generator class, if applicable. If schematic generator, then evaluates to None. sch_cls : Optional[Type[Module]] The DUT schematic generator class. If sch_class is not specified in specs, then evaluates to None. """ dut_str: Union[str, Type[TemplateBase], Type[Module]] = specs.get('dut_class') or specs.get('lay_class', '') sch_str: Union[str, Type[Module]] = specs.get('sch_class', '') dut_cls = import_class(dut_str) if issubclass(dut_cls, TemplateBase): lay_cls = dut_cls is_lay = True elif issubclass(dut_cls, Module): lay_cls = None sch_str = dut_cls is_lay = False else: raise ValueError(f"Invalid generator class {dut_cls.get_qualified_name()}") if isinstance(sch_str, str): # no schematic class from layout, try get it from string sch_cls = cast(Type[Module], import_class(sch_str)) if sch_str else None else: sch_cls = sch_str return is_lay, lay_cls, sch_cls
[docs] def generate_cell(self, specs: Dict[str, Any], raw: bool = False, gen_lay: bool = True, gen_sch: bool = True, run_drc: bool = False, run_lvs: bool = False, run_rcx: bool = False, lay_db: Optional[TemplateDB] = None, sch_db: Optional[ModuleDB] = None, gen_lef: bool = False, cv_info_out: Optional[List[PySchCellViewInfo]] = None, sim_netlist: bool = False, flat: bool = False, gen_hier: bool = False, gen_model: bool = False, mismatch: bool = False, gen_shell: bool = False, export_lay: bool = False, gen_netlist: bool = False) -> str: """Generate layout/schematic of a given cell from specification file. Parameters ---------- specs : Dict[str, Any] the specification dictionary. Some non-obvious parameters: params : Dict[str, Any] If layout generator is given, this is the layout parameters. Otherwise this is the schematic parameters. netlist_file : str If not empty, we will create a netlist file with this name (even if raw = False). if sim_netlist is True, this will be a simulation netlist. model_file : str the behavioral model filename if gen_model = True. gds_file : str override the default GDS layout file name. Note that specifying this entry does not mean a GDS file will be created, you must set raw = True or gen_gds = True. rcx_params : Optional[Dict[str, Any]] Extra rcx parameters that users can override. raw : bool True to generate GDS and netlist files instead of OA cellviews. gen_lay : bool True to generate layout. gen_sch : bool True to generate schematics. run_drc : bool True to run DRC. run_lvs : bool True to run LVS. run_rcx : bool True to run RCX. lay_db : Optional[TemplateDB] the layout database. sch_db : Optional[ModuleDB] the schematic database. gen_lef : bool True to create LEF file. cv_info_out : Optional[List[PySchCellViewInfo]] = None If given cellview information objects will be appended to this list. sim_netlist : bool True to return a simulation netlist. flat : bool True to generate flat netlist. gen_hier: bool True to write the system verilog modeling hierarchy in a yaml file. gen_model: bool True to generate behavioral models mismatch : bool True to add mismatch voltages gen_shell: bool True to generate verilog shell file. export_lay: bool True to export layout file even in non-raw mode. gen_netlist : bool True to generate netlist even in non-raw mode. Returns ------- rcx_netlist : str the extraction netlist. Empty on error or if extraction is not run. """ root_dir: Union[str, Path] = specs.get('root_dir', '') has_lay, lay_cls, sch_cls = self.get_dut_class_info(specs) impl_lib: str = specs['impl_lib'] impl_cell: str = specs['impl_cell'] params: Optional[Mapping[str, Any]] = specs.get('params', None) netlist_file_override: str = specs.get('netlist_file', '') model_file: str = specs.get('model_file', '') yaml_file: str = specs.get('yaml_file', '') layout_file_override: str = specs.get('layout_file', '') leaves: Optional[Mapping[str, List[str]]] = specs.get('leaf_cells', None) mod_type_str: str = specs.get('model_type', 'SYSVERILOG') default_model_view: str = specs.get('default_model_view', '') hierarchy_file: str = specs.get('hierarchy_file', '') model_params: Mapping[str, Any] = specs.get('model_params', {}) rcx_params: Optional[Mapping[str, Any]] = specs.get('rcx_params', None) sup_wrap_mode: str = specs.get('model_supply_wrap_mode', 'NONE') lef_config: Mapping[str, Any] = specs.get('lef_config', {}) name_prefix: str = specs.get('name_prefix', '') name_suffix: str = specs.get('name_suffix', '') exact_cell_names_list: List[str] = specs.get('exact_cell_names', []) square_bracket: bool = specs.get('square_bracket', False) lay_type_specs: Union[str, List[str]] = specs.get('layout_type', 'GDS') mod_type: DesignOutput = DesignOutput[mod_type_str] sup_wrap_type: SupplyWrapMode = SupplyWrapMode[sup_wrap_mode] exact_cell_names = set(exact_cell_names_list) if isinstance(lay_type_specs, str): lay_type_list: List[DesignOutput] = [DesignOutput[lay_type_specs]] else: lay_type_list: List[DesignOutput] = [DesignOutput[v] for v in lay_type_specs] root_path = self.get_root_path(root_dir, use_sim_path=False) log_file = str(root_path / 'gen.log') gen_lay = gen_lay and has_lay gen_model = gen_model and model_params run_drc = run_drc and gen_lay verilog_shell_path = root_path / f'{impl_cell}_shell.v' if gen_lef or gen_shell else None layout_ext = lay_type_list[0].extension layout_file = '' lef_options = {} if layout_file_override and Path(layout_file_override).suffix[1:] != layout_ext: raise ValueError('Conflict between layout file type and layout file name.') if has_lay: if lay_db is None: lay_db = self.make_template_db(log_file, impl_lib, name_prefix=name_prefix, name_suffix=name_suffix) print('computing layout...') lay_master: TemplateBase = lay_db.new_template(lay_cls, params=params) lay_master.get_lef_options(lef_options, lef_config) # try getting schematic class from instance, if possible sch_cls = lay_master.get_schematic_class_inst() or sch_cls dut_list = [(lay_master, impl_cell)] print('computation done.') if gen_lay: print('creating layout...') t0 = time.perf_counter() # always create gds first layout_file = (layout_file_override or str(root_path / f'{impl_cell}.{layout_ext}')) lay_db.batch_layout(dut_list, output=lay_type_list[0], fname=layout_file, exact_cell_names=exact_cell_names, square_bracket=square_bracket) for out_type in lay_type_list[1:]: cur_file = str(root_path / f'{impl_cell}.{out_type.extension}') lay_db.batch_layout(dut_list, output=out_type, fname=cur_file, exact_cell_names=exact_cell_names, square_bracket=square_bracket) if not raw: # import the gds into Virtuoso self.import_layout(layout_file, impl_lib, f'{name_prefix}{impl_cell}{name_suffix}') # lay_db.batch_layout(dut_list, output=DesignOutput.LAYOUT, # exact_cell_names=exact_cell_names) t1 = time.perf_counter() print(f'layout done: time taken = {t1 - t0}') sch_params = lay_master.sch_params else: sch_params = params # Layout is already created as gds first, so re-exporting is not necessary. # if export_lay and not raw: # print('exporting layout') # layout_file = (layout_file_override or # str(root_path / f'{impl_cell}.{layout_ext}')) # export_params = dict(square_bracket=square_bracket, # output_type=lay_type_list[0]) # self.impl_db.export_layout(impl_lib, impl_cell, layout_file, # params=export_params) # for out_type in lay_type_list[1:]: # export_params['output_type'] = out_type # cur_file = str(root_path / f'{impl_cell}.{out_type.extension}') # self.impl_db.export_layout(impl_lib, impl_cell, cur_file, # params=export_params) has_sch = sch_cls is not None run_lvs = (run_lvs or run_rcx) and gen_lay and has_sch run_rcx = run_rcx and gen_lay and has_sch gen_sch = (gen_sch or gen_hier or gen_model or run_lvs or run_rcx) and has_sch flat = flat or (mismatch and not run_rcx) final_netlist = '' final_netlist_type = DesignOutput.CDL lvs_netlist = '' netlist_file = netlist_file_override if (gen_netlist or raw) and not netlist_file: if sim_netlist: ext = self._sim.netlist_type.extension else: ext = DesignOutput.CDL.extension netlist_file = str(root_path / f'{impl_cell}.{ext}') if gen_sch: if sch_db is None: sch_db = self.make_module_db(log_file, impl_lib, name_prefix=name_prefix, name_suffix=name_suffix) print('computing schematic...') sch_master: Module = sch_db.new_master(sch_cls, params=sch_params) sch_master.get_lef_options(lef_options, lef_config) dut_list = [(sch_master, impl_cell)] print('computation done.') if not raw: print('creating schematic...') t0 = time.perf_counter() sch_db.batch_schematic(dut_list, exact_cell_names=exact_cell_names) t1 = time.perf_counter() print(f'schematic done: time taken = {t1 - t0}') if yaml_file: sch_db.batch_schematic(dut_list, output=DesignOutput.YAML, fname=yaml_file, exact_cell_names=exact_cell_names) if netlist_file: print('creating netlist...') t0 = time.perf_counter() final_netlist = netlist_file if sim_netlist: final_netlist_type = self._sim.netlist_type if run_lvs: lvs_netlist = str(root_path / f'{impl_cell}.{DesignOutput.CDL.extension}') sch_db.batch_schematic(dut_list, output=DesignOutput.CDL, fname=lvs_netlist, cv_info_out=cv_info_out, flat=flat, exact_cell_names=exact_cell_names, square_bracket=square_bracket) sch_db.batch_schematic(dut_list, output=final_netlist_type, fname=netlist_file, cv_info_out=cv_info_out, flat=flat, exact_cell_names=exact_cell_names) else: sch_db.batch_schematic(dut_list, output=final_netlist_type, fname=netlist_file, cv_info_out=cv_info_out, flat=flat, exact_cell_names=exact_cell_names) else: final_netlist_type = DesignOutput.CDL lvs_netlist = netlist_file sch_db.batch_schematic(dut_list, output=final_netlist_type, fname=netlist_file, cv_info_out=cv_info_out, flat=flat, exact_cell_names=exact_cell_names, square_bracket=square_bracket) t1 = time.perf_counter() print(f'netlisting done: time taken = {t1 - t0}') if verilog_shell_path is not None: sch_db.batch_schematic(dut_list, output=DesignOutput.VERILOG, shell=True, fname=str(verilog_shell_path), exact_cell_names=exact_cell_names) print(f'verilog shell file created at {verilog_shell_path}') if gen_hier: print('creating hierarchy...') if not hierarchy_file: hierarchy_file = str(root_path / 'hierarchy.yaml') write_yaml(hierarchy_file, sch_master.get_instance_hierarchy(mod_type, leaves, default_model_view)) print(f'hierarchy done. File is {hierarchy_file}') if gen_model: if not model_file: model_file = str(root_path / f'{impl_cell}.{mod_type.extension}') print('creating behavioral model...') sch_db.batch_model([(sch_master, impl_cell, model_params)], output=mod_type, fname=model_file, supply_wrap_mode=sup_wrap_type, exact_cell_names=exact_cell_names) print(f'behavioral model done. File is {model_file}') elif netlist_file: if sim_netlist: raise ValueError('Cannot generate simulation netlist from custom cellview') print('exporting netlist') self.impl_db.export_schematic(impl_lib, impl_cell, netlist_file) if impl_cell in exact_cell_names: gen_cell_name = impl_cell else: gen_cell_name = name_prefix + impl_cell + name_suffix if run_drc: print('running DRC...') drc_passed, drc_log = self.run_drc(impl_lib, gen_cell_name, layout=layout_file) if drc_passed: print('DRC passed!') else: print(f'DRC failed... log file: {drc_log}') lvs_passed = False if run_lvs: print('running LVS...') lvs_passed, lvs_log = self.run_lvs(impl_lib, gen_cell_name, run_rcx=run_rcx, layout=layout_file, netlist=lvs_netlist) if lvs_passed: print('LVS passed!') else: raise ValueError(f'LVS failed... log file: {lvs_log}') if lvs_passed and run_rcx: print('running RCX...') final_netlist, rcx_log = self.run_rcx(impl_lib, gen_cell_name, params=rcx_params, layout=layout_file, netlist=lvs_netlist) final_netlist_type = DesignOutput.CDL if final_netlist: print('RCX passed!') if not raw: root_path.mkdir(parents=True, exist_ok=True) if isinstance(final_netlist, list): for f in final_netlist: to_file = str(root_path / Path(f).name) shutil.copy(f, to_file) final_netlist = to_file else: to_file = str(root_path / Path(final_netlist).name) shutil.copy(final_netlist, to_file) final_netlist = to_file else: raise ValueError(f'RCX failed... log file: {rcx_log}') if gen_lef: if not verilog_shell_path.is_file(): raise ValueError(f'Missing verilog shell file: {verilog_shell_path}') lef_options = lef_config.get('lef_options_override', lef_options) print('generating LEF...') lef_path = root_path / f'{impl_cell}.lef' success = self.generate_lef(impl_lib, impl_cell, verilog_shell_path, lef_path, root_path, lef_options) if success: print(f'LEF generation done, file at {lef_path}') else: raise ValueError('LEF generation failed... ' f'check log files in run directory: {root_path}') if mismatch: add_mismatch_offsets(final_netlist, final_netlist, final_netlist_type) return final_netlist
[docs] def extract_cell(self, lib_name: str, cell_name: str, extract_type: Optional[str], extract_corner: Optional[str] ) -> None: print('running LVS...') lvs_passed, lvs_log = self.run_lvs(lib_name, cell_name, run_rcx=True) if lvs_passed: print('LVS passed!') print('running RCX...') rcx_params = {} _suf = '' if extract_type: rcx_params['extract_type'] = extract_type _suf += f'_{extract_type}' if extract_corner: rcx_params['extract_corner'] = extract_corner _suf += f'_{extract_corner}' final_netlist, rcx_log = self.run_rcx(lib_name, cell_name, params=rcx_params) if final_netlist: print('RCX passed!') root_path = self.get_root_path(Path(f'gen_outputs/{lib_name}/{cell_name}'), use_sim_path=False) root_path.mkdir(parents=True, exist_ok=True) if isinstance(final_netlist, list): for f in final_netlist: # there may be multiple suffixes _list = [str(root_path / Path(f).stem), _suf] + Path(f).suffixes to_file = ''.join(_list) shutil.copy(f, to_file) final_netlist = to_file else: _list = [str(root_path / Path(final_netlist).stem), _suf] + Path(final_netlist).suffixes to_file = ''.join(_list) shutil.copy(final_netlist, to_file) final_netlist = to_file print(f'Extracted netlist is {final_netlist}') else: raise ValueError(f'RCX failed... log file: {rcx_log}') else: raise ValueError(f'LVS failed... log file: {lvs_log}')
[docs] def replace_dut_in_wrapper(self, params: Mapping[str, Any], dut_lib: str, dut_cell: str) -> Mapping[str, Any]: # helper function that replaces dut_lib and dut_cell in the wrapper recursively in # dut_params ans = {k: v for k, v in params.items()} dut_params: Optional[Mapping[str, Any]] = params.get('dut_params', None) if dut_params is None: ans['dut_lib'] = dut_lib ans['dut_cell'] = dut_cell else: ans['dut_params'] = self.replace_dut_in_wrapper(dut_params, dut_lib, dut_cell) return ans
[docs] def simulate_cell(self, specs: Dict[str, Any], extract: bool = True, gen_tb: bool = True, simulate: bool = True, mismatch: bool = False, raw: bool = True, lay_db: Optional[TemplateDB] = None, sch_db: Optional[ModuleDB] = None, ) -> str: """Generate and simulate a single design. This method only works for simulating a single cell (or a wrapper around a single cell). If you need to simulate multiple designs together, use simulate_config(). Parameters ---------- specs : Dict[str, Any] the specification dictionary. Important entries are: use_netlist : str If specified, use this netlist file as the DUT netlist, and only generate the testbench and simulation netlists. If specified but the netlist does not exist, or the PySchCellViewInfo yaml file does not exist in the same directory, we will still generate the DUT, but the resulting netlist/PySchCellViewInfo object will be saved to this location extract : bool True to generate extracted netlist. gen_tb : bool True to generate the DUT/testbench/simulation netlists. If False, we will simply grab the final simulation netlist and simulate it. This means you can quickly simulate a previously generated netlist with manual modifications. simulate : bool True to run simulation. If False, we will only generate the netlists. mismatch: bool If True mismatch voltage sources are added to the netlist and simulation is done with those in place raw: bool True to generate GDS and netlist files instead of OA cellviews. lay_db : Optional[TemplateDB] the layout database. sch_db : Optional[ModuleDB] the schematic database. Returns ------- sim_result : str simulation result file name. """ gen_specs_file: str = specs.get('gen_specs_file', '') if gen_specs_file: gen_specs: Mapping[str, Any] = read_yaml(gen_specs_file) else: gen_specs = specs root_dir: Union[str, Path] = gen_specs['root_dir'] impl_lib: str = gen_specs['impl_lib'] impl_cell: str = gen_specs['impl_cell'] use_netlist: str = specs.get('use_netlist', '') precision: int = specs.get('precision', 6) tb_params: Dict[str, Any] = specs.get('tb_params', {}).copy() wrapper_lib: str = specs.get('wrapper_lib', '') if wrapper_lib: wrapper_cell: str = specs['wrapper_cell'] wrapper_params: Mapping[str, Any] = specs['wrapper_params'] wrapper_params = self.replace_dut_in_wrapper(wrapper_params, impl_lib, impl_cell) tb_params['dut_params'] = wrapper_params tb_params['dut_lib'] = wrapper_lib tb_params['dut_cell'] = wrapper_cell else: tb_params['dut_lib'] = impl_lib tb_params['dut_cell'] = impl_cell root_path = self.get_root_path(root_dir, use_sim_path=True) log_file = str(root_path / 'sim.log') netlist_type = self._sim.netlist_type tb_netlist_path = root_path / f'tb.{netlist_type.extension}' sim_netlist_path = root_path / f'sim.{netlist_type.extension}' root_path.mkdir(parents=True, exist_ok=True) if gen_tb: if not impl_cell: raise ValueError('impl_cell is empty.') if sch_db is None: sch_db = self.make_module_db(log_file, impl_lib) if use_netlist: use_netlist_path = Path(use_netlist) netlist_dir: Path = use_netlist_path.parent cv_info_path = netlist_dir / (use_netlist_path.stem + '.cvinfo.yaml') if use_netlist_path.is_file(): if cv_info_path.is_file(): # both files exist, load from file cvinfo = PySchCellViewInfo(str(cv_info_path)) cv_info_list = [cvinfo] else: # no cv_info, still need to generate cv_info_list = [] else: # need to save netlist and cv_info_list cv_info_list = [] else: # no need to save use_netlist_path = None cv_info_path = None cv_info_list = [] has_netlist = use_netlist_path is not None and use_netlist_path.is_file() extract = extract and not has_netlist if not cv_info_list: gen_netlist = self.generate_cell(gen_specs, raw=raw, gen_lay=extract, gen_sch=True, run_lvs=extract, run_rcx=extract, gen_netlist=True, sim_netlist=True, sch_db=sch_db, lay_db=lay_db, cv_info_out=cv_info_list, mismatch=mismatch) if use_netlist_path is None: use_netlist_path = Path(gen_netlist) else: # save netlist and cvinfo use_netlist_path.parent.mkdir(parents=True, exist_ok=True) if not use_netlist_path.is_file(): shutil.copy(gen_netlist, str(use_netlist_path)) for cv_info in reversed(cv_info_list): print(cv_info.lib_name, cv_info.cell_name) if cv_info.lib_name == impl_lib and cv_info.cell_name == impl_cell: cv_info.to_file(str(cv_info_path)) break tbm_str: Union[str, Type[TestbenchManager]] = specs.get('tbm_class', '') if isinstance(tbm_str, str): if tbm_str: tbm_cls = cast(Type[TestbenchManager], import_class(tbm_str)) else: tbm_cls = None else: tbm_cls = tbm_str if tbm_cls is not None: # setup testbench using TestbenchManager tbm_specs: Dict[str, Any] = specs['tbm_specs'] sim_envs: List[str] = tbm_specs['sim_envs'] tbm = tbm_cls(self._sim, root_path, 'tb_sim', impl_lib, tbm_specs, [], sim_envs, precision=precision) tbm.setup(sch_db, tb_params, cv_info_list, [use_netlist_path], gen_sch=not raw) else: # setup testbench using spec file tb_lib: str = specs['tb_lib'] tb_cell: str = specs['tb_cell'] sim_info_dict: Dict[str, Any] = specs['sim_info'] impl_cell_tb = f'{tb_cell.upper()}_{impl_cell}' if impl_cell else tb_cell.upper() tb_cls = sch_db.get_schematic_class(tb_lib, tb_cell) # noinspection PyTypeChecker tb_master = sch_db.new_master(tb_cls, params=tb_params) dut_list = [(tb_master, impl_cell_tb)] fname = '' if use_netlist_path is None else str(use_netlist_path) sch_db.batch_schematic(dut_list, output=netlist_type, top_subckt=False, fname=str(tb_netlist_path), cv_info_list=cv_info_list, cv_netlist_list=[fname]) if not raw: sch_db.batch_schematic(dut_list, output=DesignOutput.SCHEMATIC) sim_info = netlist_info_from_dict(sim_info_dict) self._sim.create_netlist(sim_netlist_path, tb_netlist_path, sim_info, precision) tbm = None else: tbm = None sim_result = '' if simulate: if not tb_netlist_path.is_file(): raise ValueError(f'Cannot find testbench netlist: {tb_netlist_path}') if not sim_netlist_path.is_file(): raise ValueError(f'Cannot find simulation netlist: {sim_netlist_path}') sim_tag = 'sim' print(f'simulation netlist: {sim_netlist_path}') self._sim.run_simulation(sim_netlist_path, sim_tag) print(f'Finished simulating {sim_netlist_path}') sim_path = self._sim.get_sim_file(sim_netlist_path.parent, sim_tag) sim_result = str(sim_path) print(f'Simulation result in {sim_result}') if tbm is not None and specs.get('tbm_print', False): tbm.print_results(load_sim_data_hdf5(sim_path)) return sim_result
[docs] def measure_cell(self, specs: Mapping[str, Any], extract: bool = False, force_sim: bool = False, force_extract: bool = False, gen_cell: bool = False, gen_cell_dut: bool = False, gen_cell_tb: bool = False, fake: bool = False, log_level: LogLevel = LogLevel.DEBUG) -> None: meas_str: Union[str, Type[MeasurementManager]] = specs['meas_class'] meas_name: str = specs['meas_name'] meas_params: Dict[str, Any] = specs['meas_params'] precision: int = specs.get('precision', 6) rcx_params: Optional[Mapping[str, Any]] = specs.get('rcx_params', None) gen_cell_dut |= gen_cell gen_cell_tb |= gen_cell # DUT no_dut = False gen_specs_file: str = specs.get('gen_specs_file', '') if gen_specs_file: gen_specs: Mapping[str, Any] = read_yaml(gen_specs_file) params_key = 'params' else: gen_specs = specs params_key = 'dut_params' static_info: str = specs.get('static_info', '') if static_info: use_netlist: Optional[str] = specs.get('use_netlist') if use_netlist is None and not extract: raise ValueError('Since DUT is static, please either provide a schematic or extracted netlist, or ' 'use "-x" to extract the DUT from the Virtuoso library.') specs_list = [dict( static_info=static_info, use_netlist=use_netlist, )] else: if 'dut_class' not in gen_specs and 'lay_class' not in gen_specs: no_dut = True specs_list = [] else: specs_list = [dict( impl_cell=gen_specs['impl_cell'], dut_cls=gen_specs.get('dut_class') or gen_specs['lay_class'], dut_params=gen_specs[params_key], extract=extract, export_lay=gen_cell_dut & extract, name_prefix=gen_specs.get('name_prefix', ''), name_suffix=gen_specs.get('name_suffix', ''), )] impl_lib: str = gen_specs['impl_lib'] root_dir: Union[str, Path] = gen_specs['root_dir'] root_path = self.get_root_path(root_dir, use_sim_path=True) # harnesses harness_specs_list: Sequence[Mapping[str, Any]] = specs.get('harness_specs_list', []) for _specs in harness_specs_list: _extract: bool = _specs.get('extract', extract) _gen_specs_file: str = _specs.get('gen_specs_file', '') if _gen_specs_file: _gen_specs: Mapping[str, Any] = read_yaml(_gen_specs_file) _params_key = 'params' else: _gen_specs = _specs _params_key = 'dut_params' _static_info: str = _specs.get('static_info', '') if _static_info: _use_netlist: Optional[str] = _specs.get('use_netlist') if _use_netlist is None and not _extract: print('Since harness is static, and a schematic or extracted netlist is not provided, ' 'the harness will be extracted from the Virtuoso library.') specs_list.append(dict( static_info=_static_info, use_netlist=_use_netlist, extract=_use_netlist is None, )) else: specs_list.append(dict( impl_cell=_gen_specs['impl_cell'], dut_cls=_gen_specs.get('dut_class') or _gen_specs['lay_class'], dut_params=_gen_specs[_params_key], extract=_extract, export_lay=gen_cell_dut & _extract, name_prefix=_gen_specs.get('name_prefix', ''), name_suffix=_gen_specs.get('name_suffix', ''), )) meas_rel_dir: str = specs.get('meas_rel_dir', '') meas_cls = cast(Type[MeasurementManager], import_class(meas_str)) if meas_rel_dir: meas_path = root_path / meas_rel_dir else: meas_path = root_path dsn_options = dict( extract=extract, force_extract=force_extract, gen_sch_dut=gen_cell_dut, gen_sch_tb=gen_cell_tb, log_level=log_level, ) log_file = str(meas_path / 'meas.log') sim_db: SimulationDB = self.make_sim_db(root_path / 'dsn', log_file, impl_lib, dsn_options=dsn_options, force_sim=force_sim, precision=precision, log_level=log_level) if specs_list: coro = sim_db.async_batch_design(specs_list, rcx_params) inst_list = batch_async_task([coro])[0] else: inst_list = [] meas_params['fake'] = fake mm = sim_db.make_mm(meas_cls, meas_params) if no_dut: dut = None harnesses = inst_list else: dut = inst_list[0] if len(inst_list) > 1: harnesses = inst_list[1:] else: harnesses = [] result = sim_db.simulate_mm_obj(meas_name, meas_path / meas_name, dut, mm, harnesses) pprint.pprint(result.data)
[docs] def measure_cell_old(self, specs: Dict[str, Any], gen_dut: bool = True, load_from_file: bool = False, extract: bool = True, mismatch: bool = False, sch_db: Optional[ModuleDB] = None, cv_info_list: Optional[List[PySchCellViewInfo]] = None, ) -> Dict[str, Any]: """Generate and simulate a single design. This method only works for simulating a single cell (or a wrapper around a single cell). If you need to simulate multiple designs together, use simulate_config(). Parameters ---------- specs : Dict[str, Any] the specification dictionary. Important entries are: use_netlist : str If specified, use this netlist file as the DUT netlist, and only generate the testbench and simulation netlists. gen_dut : bool True to generate DUT. load_from_file : bool True to load from file. extract : bool True to run extracted simulation. mismatch: bool If True mismatch voltage sources are added to the netlist and simulation is done with those in place sch_db : Optional[ModuleDB] the schematic database. cv_info_list: Optional[List[PySchCellViewInfo]] Optional cellview information objects. Returns ------- meas_result : Dict[str, Any] measurement results dictionary. """ root_dir: str = specs['root_dir'] impl_lib: str = specs['impl_lib'] impl_cell: str = specs['impl_cell'] precision: int = specs.get('precision', 6) use_netlist: str = specs.get('use_netlist', None) mm_name: str = specs['meas_name'] mm_str: Union[str, Type[MeasurementManagerOld]] = specs['meas_class'] mm_specs: Dict[str, Any] = specs['meas_specs'] gen_dut = (gen_dut or not load_from_file) and not use_netlist root_path = self.get_root_path(root_dir, use_sim_path=True) root_path.mkdir(parents=True, exist_ok=True) wrapper_lookup = {'': impl_cell} if cv_info_list is None: cv_info_list = [] if gen_dut: if sch_db is None: sch_db = self.make_module_db(impl_lib) netlist = Path(self.generate_cell(specs, raw=True, gen_lay=extract, gen_sch=True, run_lvs=extract, run_rcx=extract, sim_netlist=True, sch_db=sch_db, cv_info_out=cv_info_list, mismatch=mismatch)) else: netlist = use_netlist mm_cls = cast(Type[MeasurementManagerOld], import_class(mm_str)) sim_envs: List[str] = mm_specs['sim_envs'] mm = mm_cls(self._sim, root_path, mm_name, impl_lib, mm_specs, wrapper_lookup, [], sim_envs, precision) result = mm.measure_performance(sch_db, cv_info_list, netlist, load_from_file=load_from_file, gen_sch=False) return result
[docs] def run_em_cell(self, specs: Mapping[str, Any], force_sim: bool = False, log_level: LogLevel = LogLevel.DEBUG) -> None: gds_file: str = specs.get('gds_file', '') impl_cell: str = specs['impl_cell'] root_dir: Union[str, Path] = specs['root_dir'] root_path = self.get_root_path(root_dir, use_sim_path=True) if gds_file: export_lay = False gds_path = Path(gds_file) impl_lib = '' else: export_lay = True gds_path = root_path / f'{impl_cell}.gds' impl_lib: str = specs['impl_lib'] params: Mapping[str, Any] = specs['params'] process_output: bool = specs.get('process_output', True) # TODO: caching # run EM simulation if it is defined in bag_config.yaml if self._em_sim: if export_lay: self.export_layout(impl_lib, impl_cell, str(gds_path)) if force_sim: self._em_sim.run_simulation(impl_cell, gds_path, params, root_path) else: print('Skipping EM simulation and using old results. Use "--force_sim" to force EM simulation.') if process_output: self._em_sim.process_output(impl_cell, params, root_path) else: raise NotImplementedError('EM simulation is not set up in bag_config.yaml.')
[docs] def create_library(self, lib_name, lib_path=''): # type: (str, str) -> None """Create a new library if one does not exist yet. Parameters ---------- lib_name : str the library name. lib_path : str directory to create the library in. If Empty, use default location. """ return self.impl_db.create_library(lib_name, lib_path=lib_path)
[docs] def instantiate_schematic(self, lib_name, content_list, lib_path=''): # type: (str, Sequence[Any], str) -> None """Create the given schematic contents in CAD database. NOTE: this is BAG's internal method. To create schematics, call batch_schematic() instead. Parameters ---------- lib_name : str name of the new library to put the schematic instances. content_list : Sequence[Any] list of schematics to create. lib_path : str the path to create the library in. If empty, use default location. """ self.impl_db.instantiate_schematic(lib_name, content_list, lib_path=lib_path)
[docs] def instantiate_layout_pcell(self, lib_name, cell_name, inst_lib, inst_cell, params, pin_mapping=None, view_name='layout'): # type: (str, str, str, str, Dict[str, Any], Optional[Dict[str, str]], str) -> None """Create a layout cell with a single pcell instance. Parameters ---------- lib_name : str layout library name. cell_name : str layout cell name. inst_lib : str pcell library name. inst_cell : str pcell cell name. params : Dict[str, Any] the parameter dictionary. pin_mapping: Optional[Dict[str, str]] the pin renaming dictionary. view_name : str layout view name, default is "layout". """ pin_mapping = pin_mapping or {} self.impl_db.instantiate_layout_pcell(lib_name, cell_name, view_name, inst_lib, inst_cell, params, pin_mapping)
[docs] def instantiate_layout(self, lib_name, content_list, lib_path='', view='layout'): # type: (str, Sequence[Any], str, str) -> None """Create a batch of layouts. Parameters ---------- lib_name : str layout library name. content_list : Sequence[Any] list of layouts to create lib_path : str the path to create the library in. If empty, use default location. view : str layout view name. """ self.impl_db.instantiate_layout(lib_name, content_list, lib_path=lib_path, view=view)
[docs] def release_write_locks(self, lib_name, cell_view_list): # type: (str, Sequence[Tuple[str, str]]) -> None """Release write locks from all the given cells. Parameters ---------- lib_name : str the library name. cell_view_list : Sequence[Tuple[str, str]] list of cell/view name tuples. """ self.impl_db.release_write_locks(lib_name, cell_view_list)
[docs] def refresh_cellviews(self, lib_name, cell_view_list): # type: (str, Sequence[Tuple[str, str]]) -> None """Refresh the given cellviews in the database. Parameters ---------- lib_name : str the library name. cell_view_list : Sequence[Tuple[str, str]] list of cell/view name tuples. """ self.impl_db.refresh_cellviews(lib_name, cell_view_list)
[docs] def perform_checks_on_cell(self, lib_name, cell_name, view_name): # type: (str, str, str) -> None """Perform checks on the given cell. Parameters ---------- lib_name : str the library name. cell_name : str the cell name. view_name : str the view name. """ self.impl_db.perform_checks_on_cell(lib_name, cell_name, view_name)
[docs] def run_drc(self, lib_name: str, cell_name: str, **kwargs: Any) -> Tuple[bool, str]: """Run DRC on the given cell. Parameters ---------- lib_name : str library name. cell_name : str cell_name **kwargs : optional keyword arguments. See DbAccess class for details. Returns ------- value : bool True if DRC succeeds. log_fname : str name of the DRC log file. """ return self.impl_db.run_drc(lib_name, cell_name, **kwargs)
[docs] def run_lvs(self, lib_name: str, cell_name: str, **kwargs: Any) -> Tuple[bool, str]: """Run LVS on the given cell. Parameters ---------- lib_name : str library name. cell_name : str cell_name **kwargs : optional keyword arguments. See DbAccess class for details. Returns ------- value : bool True if LVS succeeds log_fname : str name of the LVS log file. """ return self.impl_db.run_lvs(lib_name, cell_name, **kwargs)
[docs] def run_rcx(self, lib_name: str, cell_name: str, params: Optional[Mapping[str, Any]] = None, **kwargs: Any) -> Tuple[str, str]: """run RC extraction on the given cell. Parameters ---------- lib_name : str library name. cell_name : str cell name. params : Optional[Dict[str, Any]] optional RCX parameter values. **kwargs : optional keyword arguments. See DbAccess class for details. Returns ------- netlist : str The RCX netlist file name. empty if RCX failed. log_fname : str RCX log file name. """ return self.impl_db.run_rcx(lib_name, cell_name, params=params, **kwargs)
[docs] def generate_lef(self, impl_lib: str, impl_cell: str, verilog_path: Path, lef_path: Path, run_path: Path, options: Dict[str, Any]) -> bool: if self._lef is None: raise ValueError('LEF generation interface not defined in bag_config.yaml') else: return self._lef.generate_lef(impl_lib, impl_cell, verilog_path, lef_path, run_path, **options)
[docs] def import_layout(self, in_file: str, lib_name: str, cell_name: str, **kwargs: Any) -> str: """import layout. Parameters ---------- in_file : str input file name. lib_name : str library name. cell_name : str cell name. **kwargs : Any optional keyword arguments. See Checker class for details. Returns ------- log_fname : str log file name. Empty if task cancelled. """ return self.impl_db.import_layout(in_file, lib_name, cell_name, **kwargs)
[docs] def export_layout(self, lib_name: str, cell_name: str, out_file: str, **kwargs: Any) -> str: """export layout. Parameters ---------- lib_name : str library name. cell_name : str cell name. out_file : str output file name. **kwargs : Any optional keyword arguments. See Checker class for details. Returns ------- log_fname : str log file name. Empty if task cancelled. """ return self.impl_db.export_layout(lib_name, cell_name, out_file, **kwargs)
[docs] def batch_export_layout(self, info_list): # type: (Sequence[Tuple[Any, ...]]) -> Optional[Sequence[str]] """Export layout of all given cells Parameters ---------- info_list: list of cell information. Each element is a tuple of: lib_name : str library name. cell_name : str cell name. out_file : str layout output file name. view_name : str layout view name. Optional. params : Optional[Dict[str, Any]] optional export parameter values. Returns ------- results : Optional[Sequence[str]] If task is cancelled, return None. Otherwise, this is a list of log file names. """ coro_list = [self.impl_db.async_export_layout(*info) for info in info_list] temp_results = batch_async_task(coro_list) if temp_results is None: return None return ['' if isinstance(val, Exception) else val for val in temp_results]
[docs] async def async_run_lvs(self, lib_name: str, cell_name: str, **kwargs: Any) -> Tuple[bool, str]: """A coroutine for running LVS. Parameters ---------- lib_name : str library name. cell_name : str cell_name **kwargs : Any optional keyword arguments. See Checker class for details. LVS parameters should be specified as lvs_params. Returns ------- value : bool True if LVS succeeds log_fname : str name of the LVS log file. """ return await self.impl_db.async_run_lvs(lib_name, cell_name, **kwargs)
[docs] async def async_run_rcx(self, lib_name: str, cell_name: str, params: Optional[Dict[str, Any]] = None) -> Tuple[str, str]: """A coroutine for running RCX. Parameters ---------- lib_name : str library name. cell_name : str cell name. params : Optional[Dict[str, Any]] optional RCX parameter values. Returns ------- netlist : str The RCX netlist file name. empty if RCX failed. log_fname : str RCX log file name. """ return await self.impl_db.async_run_rcx(lib_name, cell_name, params=params)
[docs] def create_schematic_from_netlist(self, netlist, lib_name, cell_name, sch_view=None, **kwargs): # type: (str, str, str, Optional[str], **Any) -> None """Create a schematic from a netlist. This is mainly used to create extracted schematic from an extracted netlist. Parameters ---------- netlist : str the netlist file name. lib_name : str library name. cell_name : str cell_name sch_view : Optional[str] schematic view name. The default value is implemendation dependent. **kwargs : Any additional implementation-dependent arguments. """ return self.impl_db.create_schematic_from_netlist(netlist, lib_name, cell_name, sch_view=sch_view, **kwargs)
[docs] def create_verilog_view(self, verilog_file, lib_name, cell_name, **kwargs): # type: (str, str, str, **Any) -> None """Create a verilog view for mix-signal simulation. Parameters ---------- verilog_file : str the verilog file name. lib_name : str library name. cell_name : str cell name. **kwargs : Any additional implementation-dependent arguments. """ verilog_file = os.path.abspath(verilog_file) if not os.path.isfile(verilog_file): raise ValueError('%s is not a file.' % verilog_file) return self.impl_db.create_verilog_view(verilog_file, lib_name, cell_name, **kwargs)
[docs] def exclude_model(self, lib_name: str, cell_name: str) -> bool: """True to exclude the given schematic generator when generating behavioral models.""" return self.impl_db.exclude_model(lib_name, cell_name)