Source code for bag.data.lark

# SPDX-License-Identifier: Apache-2.0
# 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.

from typing import Optional, Union, List, Any, Dict
from pathlib import Path
from dataclasses import dataclass

from lark.lark import Lark
from lark.visitors import Transformer
from lark.tree import pydot__tree_to_png

from ..util.search import BinaryIterator
from ..io.file import open_file
from ..io.string import wrap_string
from ..design.netlist import read_spectre_cdl_unwrap

[docs]grammar_cdl = """ start: headers subckts+ headers: HEADER* subckts.2: ".SUBCKT" CELL PORTS+ NEWLINE instances* ".ENDS" instances: DEV PORTS* "/"* PORTS PARAMS* NEWLINE HEADER: ("*." | ".") ("_" | LETTER | NUMBER)+ (("=" | " = " | WS)? ("_" | LETTER | NUMBER)+)? CELL: ("_" | LETTER | NUMBER)+ PORTS: ("_" | LETTER | NUMBER)+ DEV: ("_" | LETTER | NUMBER | "@" | "/")+ PARAMS: ("_" | LETTER | NUMBER)+ "=" PAR_VAL SC_NUM: (NUMBER | ".")+ "e-" NUMBER PAR_VAL: (("_" | LETTER | NUMBER)+ | SC_NUM | " * " | "*")+ %import common.ESCAPED_STRING -> STRING %import common.LETTER %import common.SIGNED_NUMBER -> NUMBER %import common.WS %import common.NEWLINE %ignore WS """
[docs]grammar_scs = r""" start: headers subckts+ headers: HEADER* subckts: "subckt" CELL PORTS+ NEWLINE instances* "ends" CELL instances: DEV PORTS* PARAMS* NEWLINE HEADER: "include " PATH | "simulator lang=spectre" CELL: ("_" | LETTER | NUMBER)+ PORTS: ("_" | LETTER | NUMBER)+ DEV: ("_" | LETTER | NUMBER | "@" | "/")+ PARAMS: ("_" | LETTER | NUMBER)+ "=" PAR_VAL SC_NUM: (NUMBER | ".")+ "e-" NUMBER PAR_VAL: (("_" | LETTER | NUMBER)+ | SC_NUM | " * " | "*")+ PATH: /"[\w\.\/]+"/ %import common.ESCAPED_STRING -> STRING %import common.LETTER %import common.SIGNED_NUMBER -> NUMBER %import common.WS %import common.NEWLINE %ignore WS """
@dataclass
[docs]class Instance:
[docs] inst_name: str
[docs] ports: List[str]
[docs] params: List[str]
[docs] prim: Optional[str] = None
[docs] is_transistor: bool = False
[docs] is_BAG_prim: bool = False
[docs] netlist_str: str = ''
def __init__(self, items: List[Any]): self.params = [] self.ports = [] for item in items: if item.type == 'DEV': self.inst_name = item.value elif item.type == 'PARAMS': self.params.append(item.value) elif item.type == 'PORTS': if item.value.startswith(('nmos', 'pmos')): self.prim = item.value self.is_transistor = True self.is_BAG_prim = True # TODO: add conditions to check for transistor in extracted netlist else: self.ports.append(item.value) if self.prim is None: self.prim = self.ports.pop()
[docs] def netlist(self, used_names: List[str], offset_map: Dict[str, str], scs: bool, last: bool ) -> str: if self.is_transistor: if self.is_BAG_prim: body, drain, gate, source = self.ports else: drain, gate, source, body = self.ports # 1. modify gate connection of device new_gate = f'new___{gate}_{self.inst_name.replace("/", "_").replace("@", "_")}' if \ last else gate if self.is_BAG_prim: new_ports = [body, drain, new_gate, source] else: new_ports = [drain, new_gate, source, body] self.netlist_str = wrap_string([self.inst_name] + new_ports + [self.prim] + self.params) if last: # 2. add voltage source base_name, sep, index = self.inst_name.partition('@') if base_name in offset_map.keys(): # different finger of same transistor offset_v = offset_map[base_name] else: # create unique name offset_v = f'v__{base_name.replace("/", "_")}' if offset_v in used_names: # not unique; find unique by Binary Iteration bin_iter = BinaryIterator(1, None) while bin_iter.has_next(): new_offset_v = f'{offset_v}_{bin_iter.get_next()}' if new_offset_v in used_names: bin_iter.up() else: bin_iter.save_info(new_offset_v) bin_iter.down() offset_v = f'{offset_v}_{bin_iter.get_last_save_info()}' used_names.append(offset_v) offset_map[base_name] = offset_v vdc_name = f'V{offset_v}{sep}{index}' if scs: str_list = [vdc_name, new_gate, gate, 'vsource', 'type=dc', f'dc={offset_v}'] else: str_list = [vdc_name, new_gate, gate, offset_v] self.netlist_str += wrap_string(str_list) else: tmp_list = [self.inst_name] tmp_list.extend(self.ports) tmp_list.append(self.prim) tmp_list.extend(self.params) self.netlist_str = wrap_string(tmp_list) return self.netlist_str
@dataclass
[docs]class SubCKT:
[docs] subckt_name: str
[docs] ports: List[str]
[docs] instances: List[Instance]
[docs] netlist_str: str = ''
[docs] last: bool = False
def __init__(self, items: List[Any]): self.ports = [] self.instances = [] for item in items: if isinstance(item, Instance): self.instances.append(item) elif item.type == 'CELL': self.subckt_name = item.value elif item.type == 'PORTS': self.ports.append(item.value)
[docs] def netlist(self, used_names: List[str], offset_map: Dict[str, str], scs: bool) -> str: # Construct sub-circuit netlist # 1. begin sub-circuit str_list = ['subckt' if scs else '.SUBCKT', self.subckt_name] + self.ports self.netlist_str = wrap_string(str_list) # 2. write instances for inst in self.instances: net = inst.netlist(used_names=used_names, offset_map=offset_map, scs=scs, last=self.last) self.netlist_str += net # 3. end str_list = ['ends', self.subckt_name] if scs else ['.ENDS'] self.netlist_str += wrap_string(str_list) return self.netlist_str + '\n'
@dataclass
[docs]class CktTransformer(Transformer): @classmethod
[docs] def instances(cls, items): return Instance(items)
@classmethod
[docs] def subckts(cls, items): return SubCKT(items)
@classmethod
[docs] def headers(cls, items): return Header(items)
[docs]def add_mismatch_offsets(netlist_in: Union[Path, str], netlist_out: Optional[Union[Path, str]] = None, debug: bool = False, ) -> None: if isinstance(netlist_in, str): netlist_in = Path(netlist_in) if netlist_in.suffix in ['.cdl', '.sp', '.spf']: parser = Lark(grammar_cdl, parser='lalr') scs = False elif netlist_in.suffix in ['.scs', '.net']: parser = Lark(grammar_scs, parser='lalr') scs = True else: raise ValueError(f'Unknown netlist suffix={netlist_in.suffix}. Use ".cdl" or ".scs".') lines = read_spectre_cdl_unwrap(netlist_in) lines[-1] += '\n' tree = parser.parse('\n'.join(lines)) if debug: pydot__tree_to_png(tree, "test0.png") obj_list = CktTransformer().transform(tree).children obj_list[-1].last = True if netlist_out is None: netlist_out: Path = netlist_in.with_name(netlist_in.stem + 'out') if isinstance(netlist_out, str): netlist_out: Path = Path(netlist_out) full_netlist = '' used_names = [] offset_map = {} for obj in obj_list: full_netlist += obj.netlist(used_names, offset_map, scs) for key, val in offset_map.items(): print(f'{val}: 0.0') with open_file(netlist_out, 'w') as f: f.write(full_netlist)