# 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 module implements all CAD database manipulations using skill commands.
"""
from typing import Sequence, List, Dict, Optional, Any, Tuple
import shutil
from pathlib import Path
from ..io.common import fix_string
from ..io.file import open_temp, read_yaml, write_file
from ..io.string import read_yaml_str, to_yaml_str
from ..layout.routing.grid import RoutingGrid
from .database import DbAccess
from .zmqwrapper import ZMQDealer
[docs]def _dict_to_pcell_params(table: Dict[str, Any]) -> List[Any]:
"""Convert given parameter dictionary to pcell parameter list format.
Parameters
----------
table : Dict[str, Any]
the parameter dictionary.
Returns
-------
param_list : List[Any]
the Pcell parameter list
"""
param_list = []
for key, val in table.items():
# python 2/3 compatibility: convert raw bytes to string.
val = fix_string(val)
if isinstance(val, float):
param_list.append([key, "float", val])
elif isinstance(val, str):
# unicode string
param_list.append([key, "string", val])
elif isinstance(val, int):
param_list.append([key, "int", val])
elif isinstance(val, bool):
param_list.append([key, "bool", val])
else:
raise Exception('Unsupported parameter %s with type: %s' % (key, type(val)))
return param_list
[docs]def to_skill_list_str(pylist: List[str]) -> str:
"""Convert given python list to a skill list string.
Parameters
----------
pylist : List[str]
a list of string.
Returns
-------
ans : str
a string representation of the equivalent skill list.
"""
content = ' '.join((f'"{val}"' for val in pylist))
return f"'( {content} )"
[docs]def dict_to_item_list(table: Dict[str, Any]) -> List[List[str]]:
"""Given a Python dictionary, convert to sorted item list.
Parameters
----------
table : Dict[str, Any]
a Python dictionary where the keys are strings.
Returns
-------
assoc_list : List[List[str]]
the sorted item list representation of the given dictionary.
"""
return [[key, table[key]] for key in sorted(table.keys())]
[docs]class SkillInterface(DbAccess):
"""Skill interface between bag and Virtuoso.
This class sends all bag's database and simulation operations to
an external Virtuoso process, then get the result from it.
"""
def __init__(self, dealer: ZMQDealer, tmp_dir: str, db_config: Dict[str, Any],
lib_defs_file: str) -> None:
DbAccess.__init__(self, dealer, tmp_dir, db_config, lib_defs_file)
self.exc_libs = set(db_config['schematic']['exclude_libraries'])
# BAG_prim is always excluded
self.exc_libs.add('BAG_prim')
[docs] def get_exit_object(self) -> Any:
return {'type': 'exit'}
[docs] def get_cells_in_library(self, lib_name: str) -> List[str]:
cmd = 'get_cells_in_library_file( "%s" {cell_file} )' % lib_name
return self._eval_skill(cmd, out_file='cell_file').split()
[docs] def create_library(self, lib_name: str, lib_path: str = '') -> None:
lib_path = lib_path or self.default_lib_path
tech_lib = self.db_config['schematic']['tech_lib']
self._eval_skill(
'create_or_erase_library("%s" "%s" "%s" nil)' % (lib_name, tech_lib, lib_path))
[docs] def create_implementation(self, lib_name: str, template_list: Sequence[Any], change_list: Sequence[Any],
lib_path: str = '') -> None:
lib_path = lib_path or self.default_lib_path
tech_lib = self.db_config['schematic']['tech_lib']
copy = "'t"
in_files = {'template_list': template_list,
'change_list': change_list}
sympin = to_skill_list_str(self.db_config['schematic']['sympin'])
ipin = to_skill_list_str(self.db_config['schematic']['ipin'])
opin = to_skill_list_str(self.db_config['schematic']['opin'])
iopin = to_skill_list_str(self.db_config['schematic']['iopin'])
simulators = to_skill_list_str(self.db_config['schematic']['simulators'])
cmd = ('create_concrete_schematic( "%s" "%s" "%s" {template_list} '
'{change_list} %s %s %s %s %s %s)' % (lib_name, tech_lib, lib_path,
sympin, ipin, opin, iopin, simulators, copy))
self._eval_skill(cmd, input_files=in_files)
[docs] def get_testbench_info(self, tb_lib: str, tb_cell: str) -> Tuple[List[str], List[str], Dict[str, str],
Dict[str, str]]:
cmd = 'get_testbench_info("{tb_lib}" "{tb_cell}" {result_file})'
cmd = cmd.format(tb_lib=tb_lib,
tb_cell=tb_cell,
result_file='{result_file}')
output = read_yaml(self._eval_skill(cmd, out_file='result_file'))
return output['enabled_corners'], output['corners'], output['parameters'], output['outputs']
[docs] def update_testbench(self, lib: str, cell: str, parameters: Dict[str, str], sim_envs: List[str],
config_rules: List[List[str]], env_parameters: List[List[Tuple[str, str]]]) -> None:
cmd = 'modify_testbench("%s" "%s" {conf_rules} ' \
'{run_opts} {sim_envs} {params} {env_params})' % (lib, cell)
in_files = {'conf_rules': config_rules,
'run_opts': [],
'sim_envs': sim_envs,
'params': list(parameters.items()),
'env_params': list(zip(sim_envs, env_parameters)),
}
self._eval_skill(cmd, input_files=in_files)
[docs] def instantiate_layout_pcell(self, lib_name: str, cell_name: str, view_name: str, inst_lib: str, inst_cell: str,
params: Dict[str, Any], pin_mapping: Dict[str, str]) -> None:
# create library in case it doesn't exist
self.create_library(lib_name)
# convert parameter dictionary to pcell params list format
param_list = _dict_to_pcell_params(params)
cmd = ('create_layout_with_pcell( "%s" "%s" "%s" "%s" "%s"'
'{params} {pin_mapping} )' % (lib_name, cell_name,
view_name, inst_lib, inst_cell))
in_files = {'params': param_list, 'pin_mapping': list(pin_mapping.items())}
self._eval_skill(cmd, input_files=in_files)
[docs] def create_schematics(self, lib_name: str, sch_view: str, sym_view: str,
content_list: Sequence[Any], lib_path: str = '') -> None:
template_list, change_list = [], []
for content in content_list:
if content is not None:
impl_cell, (master_lib, master_cell, pin_map, inst_map, new_pins, port_order) = content
# add to template list
template_list.append([master_lib, master_cell, impl_cell])
# construct change object
change = dict(
name=impl_cell,
pin_map=dict_to_item_list(pin_map),
inst_list=format_inst_map(inst_map),
new_pins=new_pins,
port_order=port_order,
)
change_list.append(change)
self.create_implementation(lib_name, template_list, change_list, lib_path=lib_path)
[docs] def create_layouts(self, lib_name: str, view: str, content_list: Sequence[Any]) -> None:
raise NotImplementedError
[docs] def close_all_cellviews(self) -> None:
if self.has_bag_server:
self._eval_skill('close_all_cellviews()')
[docs] def instantiate_layout(self, lib_name: str, content_list: Sequence[Any], lib_path: str = '', view: str = 'layout'
) -> None:
raise ValueError('Deprecated: BAG should create gds which is then imported into Virtuoso')
# # create library in case it doesn't exist
# self.create_library(lib_name)
#
# # convert parameter dictionary to pcell params list format
# new_layout_list = []
# for info_list in content_list:
# new_inst_list = []
# for inst in info_list[1]:
# if 'params' in inst:
# inst = inst.copy()
# inst['params'] = _dict_to_pcell_params(inst['params'])
# new_inst_list.append(inst)
#
# new_info_list = info_list[:]
# new_info_list[1] = new_inst_list
# new_layout_list.append(new_info_list)
#
# tech_lib = self.db_config['schematic']['tech_lib']
# cmd = 'create_layout( "%s" "%s" "%s" {layout_list} )' % (lib_name, view, tech_lib)
# in_files = {'layout_list': new_layout_list}
# self._eval_skill(cmd, input_files=in_files)
[docs] def release_write_locks(self, lib_name: str, cell_view_list: Sequence[Tuple[str, str]]) -> None:
cmd = 'release_write_locks( "%s" {cell_view_list} )' % lib_name
in_files = {'cell_view_list': cell_view_list}
self._eval_skill(cmd, input_files=in_files)
[docs] def refresh_cellviews(self, lib_name: str, cell_view_list: Sequence[Tuple[str, str]]) -> None:
cmd = 'refresh_cellviews( "%s" {cell_view_list} )' % lib_name
in_files = {'cell_view_list': cell_view_list}
self._eval_skill(cmd, input_files=in_files)
[docs] def create_schematic_from_netlist(self, netlist: str, lib_name: str, cell_name: str, sch_view: str = '',
**kwargs: 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.
"""
calview_config = self.db_config.get('calibreview', None)
use_calibreview = self.db_config.get('use_calibreview', True)
if calview_config is not None and use_calibreview:
# create calibre view from extraction netlist
cell_map = calview_config['cell_map']
sch_view = sch_view or calview_config['view_name']
# create calibre view config file
tmp_params = dict(
netlist_file=netlist,
lib_name=lib_name,
cell_name=cell_name,
calibre_cellmap=cell_map,
view_name=sch_view,
)
content = self.render_file_template('calibreview_setup.txt', tmp_params)
with open_temp(prefix='calview', dir=self.tmp_dir, delete=False) as f:
fname = f.name
f.write(content)
# delete old calibre view
cmd = 'delete_cellview( "%s" "%s" "%s" )' % (lib_name, cell_name, sch_view)
self._eval_skill(cmd)
# make extracted schematic
cmd = 'mgc_rve_load_setup_file( "%s" )' % fname
self._eval_skill(cmd)
else:
# get netlists to copy
netlist_dir = Path(netlist).parent
netlist_files = self.checker.get_rcx_netlists(lib_name, cell_name)
if not netlist_files:
# some error checking. Shouldn't be needed but just in case
raise ValueError('RCX did not generate any netlists')
# copy netlists to a "netlist" subfolder in the CAD database
cell_dir = self.get_cell_directory(lib_name, cell_name)
targ_dir = cell_dir / 'netlist'
targ_dir.mkdir(exist_ok=True)
for fname in netlist_files:
shutil.copy(netlist_dir / fname, targ_dir)
# create symbolic link as aliases
symlink = targ_dir / 'netlist'
symlink.unlink(missing_ok=True)
symlink.symlink_to(netlist_files[0])
[docs] def get_cell_directory(self, lib_name: str, cell_name: str) -> Path:
"""Returns the directory name of the given cell.
Parameters
----------
lib_name : str
library name.
cell_name : str
cell name.
Returns
-------
cell_dir : Path
path to the cell directory.
"""
lib_dir = to_yaml_str(read_yaml_str(self._eval_skill(f'get_lib_directory( "{lib_name}" )')))
if not lib_dir:
raise ValueError(f'Library {lib_name} not found.')
return Path(lib_dir) / cell_name
[docs] def create_verilog_view(self, verilog_file: str, lib_name: str, cell_name: str, **kwargs: Any) -> None:
# delete old verilog view
cmd = 'delete_cellview( "%s" "%s" "verilog" )' % (lib_name, cell_name)
self._eval_skill(cmd)
cmd = 'schInstallHDL("%s" "%s" "verilog" "%s" t)' % (lib_name, cell_name, verilog_file)
self._eval_skill(cmd)
[docs] def import_sch_cellview(self, lib_name: str, cell_name: str, view_name: str) -> None:
if lib_name not in self.lib_path_map:
self.add_sch_library(lib_name)
if lib_name == 'BAG_prim':
# reading BAG primitives library, don't need to parse YAML files,
# just get the cell list
cell_list = [(lib_name, cell_name)]
else:
# read schematic information
cell_list = []
self._import_design(lib_name, cell_name, view_name, cell_list)
# create python templates
self._create_sch_templates(cell_list)
[docs] def import_design_library(self, lib_name: str, view_name: str) -> None:
if lib_name not in self.lib_path_map:
self.add_sch_library(lib_name)
if lib_name == 'BAG_prim':
# reading BAG primitives library, don't need to parse YAML files,
# just get the cell list
cell_list = [(lib_name, cell) for cell in self.get_cells_in_library(lib_name)]
else:
# read schematic information
cell_list = []
for cell_name in self.get_cells_in_library(lib_name):
self._import_design(lib_name, cell_name, view_name, cell_list)
# create python templates
self._create_sch_templates(cell_list)
[docs] def import_gds_file(self, gds_fname: str, lib_name: str, layer_map: str, obj_map: str,
grid: RoutingGrid) -> None:
raise NotImplementedError('Use import_layout() instead.')
[docs] def _import_design(self, lib_name: str, cell_name: str, view_name: str, cell_list: List[Tuple[str, str]]) -> None:
"""Recursive helper for import_design_library and import_sch_cellview.
"""
# check if we already imported this schematic
key = (lib_name, cell_name)
if key in cell_list:
return
cell_list.append(key)
root_path = Path(self.lib_path_map[lib_name])
yaml_file = root_path / 'netlist_info' / f'{cell_name}.yaml'
yaml_sym_file = root_path / 'netlist_info' / f'{cell_name}.symbol.yaml'
if not yaml_file.parent.exists():
yaml_file.parent.mkdir(exist_ok=True)
write_file(root_path / '__init__.py', '\n', mkdir=False)
# update netlist file
for view_name, _yaml in [('schematic', yaml_file), ('symbol', yaml_sym_file)]:
content = self.parse_schematic_template(lib_name, cell_name, view_name)
sch_info = read_yaml_str(content)
if sch_info:
try:
write_file(_yaml, content)
except IOError:
print(f'Warning: cannot write to {_yaml}.')
if view_name == 'schematic':
# recursively import all children
for inst_name, inst_attrs in sch_info['instances'].items():
inst_lib_name = inst_attrs['lib_name']
if inst_lib_name not in self.exc_libs:
inst_cell_name = inst_attrs['cell_name']
if (inst_lib_name, inst_cell_name) not in cell_list:
self._import_design(inst_lib_name, inst_cell_name, view_name, cell_list)
[docs] def parse_schematic_template(self, lib_name: str, cell_name: str, view_name: str) -> str:
"""Parse the given schematic template.
Parameters
----------
lib_name : str
name of the library.
cell_name : str
name of the cell.
view_name : str
name of the view.
Returns
-------
template : str
the content of the netlist structure file.
"""
cmd = 'parse_cad_sch( "%s" "%s" "%s" {netlist_info} )' % (lib_name, cell_name, view_name)
return self._eval_skill(cmd, out_file='netlist_info')
[docs] def write_tech_info(self, tech_name: str, out_yaml: str) -> None:
# get tech file
cmd = f'techid=techGetTechFile(ddGetObj("{tech_name}"))'
self._eval_skill(cmd)
# get via definitions
cmd = 'let((vialist) foreach(via techid->viaDefs vialist=cons(list(via->name via->layer1Num via->layer2Num' \
' via->params) vialist)) vialist)'
via_str = self._eval_skill(cmd)[3:-2]
via_dict = {}
via_id_dict = {}
drawing_purp = 4294967295
for sub_str in via_str.split(') ("'):
_name, _num_b, _num_t, _id = sub_str.split(' ', 4)[:4]
_name = _name[:-1]
_id = _id[2:-1]
# Remove extraneous (blank) _id
if not _id:
continue
via_dict[_name] = [(int(_num_b), drawing_purp), (int(_num_t), drawing_purp)]
if _id in via_id_dict:
via_id_dict[_id].append(_name)
else:
via_id_dict[_id] = [_name]
# the _id will be present in the next layers list, so we hve to get its number during the next loop
# get layers and derived layers
cmd = 'let((laylist) foreach(layer techid->layers laylist=cons(list(layer->name layer->number) ' \
'laylist)) laylist)'
lay_str = self._eval_skill(cmd)[2:-2]
cmd = 'let((laylist) foreach(layer techid->derivedLayers laylist=cons(list(layer->name layer->number) ' \
'laylist)) laylist)'
dlay_str = self._eval_skill(cmd)[2:-2]
# Check if we have any derived layers
all_lay_str = lay_str
if dlay_str:
all_lay_str += ') (' + dlay_str # Trick to include derived layres
lay_dict = {}
via_keys = list(via_id_dict.keys())
for sub_str in all_lay_str.split(') ('):
_name, _num = sub_str.split(' ')
_name = _name[1:-1]
_num = int(_num)
lay_dict[_num] = _name # indexing by number so that the dict can be sorted before printing
if _name in via_keys:
for _via in via_id_dict[_name]:
via_dict[_via].insert(1, (_num, drawing_purp))
via_keys.remove(_name)
# get purposes
cmd = 'let((purplist) foreach(purp techid->purposeDefs purplist=cons(list(purp->name purp->number) ' \
'purplist)) purplist)'
purp_str = self._eval_skill(cmd)[2:-2]
purp_dict = {}
for sub_str in purp_str.split(') ('):
_name, _num = sub_str.split(' ')
_num = int(_num)
if _num < 0:
_num += drawing_purp + 1
purp_dict[_num] = _name[1:-1] # indexing by number so that the dict can be sorted before printing
# write to yaml
with open(out_yaml, 'w') as file:
file.write('layer:\n')
for _num, _name in sorted(lay_dict.items()):
file.write(f' {_name}: {_num}\n')
file.write('purpose:\n')
for _num, _name in sorted(purp_dict.items()):
file.write(f' {_name}: {_num}\n')
file.write('via_layers:\n')
for _name, _list in via_dict.items():
file.write(f' {_name}:\n')
for _tuple in _list:
file.write(f' - [{_tuple[0]}, {_tuple[1]}]\n')