| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # |
| # Copyright 2019-2021 SkyWater PDK Authors |
| # |
| # 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 |
| # |
| # https://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 code is *alternatively* available under a BSD-3-Clause license, see |
| # details in the README.md at the top level and the license text at |
| # https://github.com/google/skywater-pdk-libs-sky130_bag3_pr/blob/master/LICENSE.alternative |
| # |
| # SPDX-License-Identifier: BSD-3-Clause OR Apache 2.0 |
| |
| |
| from typing import Tuple, Optional, Mapping |
| |
| from pybag.core import BBox |
| |
| from bag.util.immutable import Param |
| from bag.layout.tech import TechInfo |
| from bag.layout.template import TemplateBase |
| |
| from xbase.layout.enum import DeviceType |
| |
| from . import config as _config |
| from . import config_fname as _config_fname |
| from .mos.tech import MOSTechSkywater130 |
| from .mim.tech import MIMTechSkywater130 |
| # from .fill.tech import FillTechSkywater130 |
| from .res.tech import ResTechSkywater130 |
| |
| |
| class TechInfoSkywater130(TechInfo): |
| def __init__(self, process_params): |
| TechInfo.__init__(self, process_params, _config, _config_fname) |
| |
| self.register_device_tech('mos', MOSTechSkywater130) |
| self.register_device_tech('mim', MIMTechSkywater130) |
| self.register_device_tech('res', ResTechSkywater130) |
| # self.register_device_tech('fill', FillTechSkywater130) |
| |
| def get_margin(self, is_vertical: bool, edge1: Param, edge2: Optional[Param]) -> int: |
| if edge2 is None: |
| dev_type = edge1['dev_type'] |
| if dev_type is DeviceType.MOS: |
| margins: Mapping[str, int] = edge1['margins'] |
| table: Mapping[str, Tuple[int, int]] = self.config['margins'] |
| max_sp = 0 |
| for name, val in margins.items(): |
| sp_tot = -(-table[name][is_vertical] // 2) |
| max_sp = max(sp_tot - val, max_sp) |
| return max_sp |
| else: |
| # TODO: implement this |
| raise ValueError('Not implemented yet, see developer.') |
| else: |
| # TODO: implement this |
| raise ValueError('Not implemented yet, see developer.') |
| |
| def add_cell_boundary(self, template: TemplateBase, box: BBox) -> None: |
| if box.is_physical(): |
| pt_list = [(box.xl, box.yl), (box.xl, box.yh), (box.xh, box.yh), (box.xh, box.yl)] |
| # template.add_boundary(BoundaryType.PR, pt_list) |
| |
| def draw_device_blockage(self, template: TemplateBase) -> None: |
| pass |
| |
| def get_metal_em_specs(self, layer: str, purpose: str, w: int, length: int = -1, |
| vertical: bool = False, dc_temp: int = -1000, rms_dt: int = -1000 |
| ) -> Tuple[float, float, float]: |
| idc = self._get_metal_idc(layer, purpose, w, length, vertical, dc_temp) |
| irms = self._get_metal_irms(layer, purpose, w, rms_dt) |
| ipeak = float('inf') |
| return idc, irms, ipeak |
| |
| def get_via_em_specs(self, layer_dir: int, layer: str, purpose: str, adj_layer: str, |
| adj_purpose: str, cut_w: int, cut_h: int, m_w: int = -1, m_l: int = -1, |
| adj_m_w: int = -1, adj_m_l: int = -1, array: bool = False, |
| dc_temp: int = -1000, rms_dt: int = -1000) -> Tuple[float, float, float]: |
| def_purpose = self.default_purpose |
| purpose = purpose or def_purpose |
| adj_purpose = adj_purpose or def_purpose |
| |
| lay_vec = [None, None] |
| dim_vec = [None, None] |
| |
| lay_vec[layer_dir] = (layer, purpose) |
| lay_vec[1 - layer_dir] = (adj_layer, adj_purpose) |
| dim_vec[layer_dir] = (m_w, m_l) |
| dim_vec[1 - layer_dir] = (adj_m_w, adj_m_l) |
| |
| idc = self._get_via_idc(lay_vec[0], lay_vec[1], cut_w, cut_h, dim_vec[0], dim_vec[1], |
| array, dc_temp) |
| # via do not have AC current specs |
| irms = float('inf') |
| ipeak = float('inf') |
| return idc, irms, ipeak |
| |
| def get_res_em_specs(self, res_type: str, w: int, *, length: int = -1, |
| dc_temp: int = -1000, rms_dt: int = -1000) -> Tuple[float, float, float]: |
| dc_temp = self.get_dc_temp(dc_temp) |
| rms_dt = self.get_rms_dt(rms_dt) |
| |
| idc_scale = self.get_idc_scale_factor('', '', dc_temp, is_res=True) |
| idc = 1.0e-3 * w * idc_scale |
| |
| irms = 1e-3 * (0.02 * rms_dt * w * (w + 0.5)) ** 0.5 |
| |
| ipeak = 5e-3 * 2 * w |
| return idc, irms, ipeak |
| |
| # noinspection PyUnusedLocal,PyMethodMayBeStatic |
| def _get_metal_idc_factor(self, layer: str, purpose: str, w: int, length: int): |
| return 1 |
| |
| def _get_metal_idc(self, layer: str, purpose: str, w: int, length: int, |
| vertical: bool, dc_temp: int) -> float: |
| if vertical: |
| raise NotImplementedError('Vertical DC current not supported yet') |
| |
| inorm, woff = 1.0, 0.0 |
| idc = inorm * self._get_metal_idc_factor(layer, purpose, w, length) * (w - woff) |
| return self.get_idc_scale_factor(layer, purpose, self.get_dc_temp(dc_temp)) * idc * 1e-3 |
| |
| # noinspection PyUnusedLocal |
| def _get_metal_irms(self, layer: str, purpose: str, w: int, rms_dt: int): |
| b = 0.0443 |
| k, wo, a = 6.0, 0.0, 0.2 |
| |
| irms_ma = (k * self.get_rms_dt(rms_dt) * (w - wo)**2 * (w - wo + a) / (w - wo + b))**0.5 |
| return irms_ma * 1e-3 |
| |
| # noinspection PyUnusedLocal |
| def _get_via_idc(self, bot_lp: Tuple[str, str], top_lp: Tuple[str, str], cut_w: int, |
| cut_h: int, bot_dim: Tuple[int, int], top_dim: Tuple[int, int], |
| array: bool, dc_temp: int) -> float: |
| if bot_dim[0] > 0: |
| bf = self._get_metal_idc_factor(bot_lp[0], bot_lp[1], bot_dim[0], bot_dim[1]) |
| else: |
| bf = 1.0 |
| |
| if top_dim[0] > 0: |
| tf = self._get_metal_idc_factor(top_lp[0], top_lp[1], top_dim[0], top_dim[1]) |
| else: |
| tf = 1.0 |
| |
| factor = min(bf, tf) |
| via_id = self.config['via_id'][(bot_lp, top_lp)] |
| |
| if via_id in ('M1_LiPo', 'M1_LiAct', 'M2_M1', 'M3_M2', 'M4_M3'): |
| if cut_w == cut_h == 64: |
| idc = 0.1 |
| elif cut_w != cut_h: |
| idc = 0.2 |
| else: |
| # we do not support 2X square via, as it has large |
| # spacing rule to square/rectangle vias. |
| raise ValueError('Unsupported via w/h: ({},{})'.format(cut_w, cut_h)) |
| else: |
| idc = 0.4 |
| |
| temp = self.get_dc_temp(dc_temp) |
| return factor * self.get_idc_scale_factor(bot_lp[0], bot_lp[1], temp) * idc * 1e-3 |