From 278ef8edc1555aa27f0f5f8c611b2005c9bcd2f6 Mon Sep 17 00:00:00 2001 From: mole99 Date: Tue, 8 Oct 2024 15:44:49 +0200 Subject: [PATCH] netlist importer: add support for subckts --- .../sky130/pymacros/sky130_import_netlist.lym | 4 +- tech/sky130/pymacros/sky130_pcells.lym | 4 +- tech/sky130/python/import_netlist/__init__.py | 1 + .../python/import_netlist/import_netlist.py | 202 +++++++++++++ .../import_netlist/sky130_pcell_templates.py | 277 ++++++++++++++++++ tech/sky130/python/sky130_import_netlist.py | 246 ---------------- 6 files changed, 484 insertions(+), 250 deletions(-) create mode 100644 tech/sky130/python/import_netlist/__init__.py create mode 100644 tech/sky130/python/import_netlist/import_netlist.py create mode 100644 tech/sky130/python/import_netlist/sky130_pcell_templates.py delete mode 100644 tech/sky130/python/sky130_import_netlist.py diff --git a/tech/sky130/pymacros/sky130_import_netlist.lym b/tech/sky130/pymacros/sky130_import_netlist.lym index 2ee3bba9..249d0689 100644 --- a/tech/sky130/pymacros/sky130_import_netlist.lym +++ b/tech/sky130/pymacros/sky130_import_netlist.lym @@ -40,8 +40,8 @@ lib_path = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "pytho if not lib_path in sys.path: sys.path.insert(0, lib_path) -import sky130_import_netlist -sky130_import_netlist.sky130_import_netlist() +import import_netlist +import_netlist.sky130_import_netlist() diff --git a/tech/sky130/pymacros/sky130_pcells.lym b/tech/sky130/pymacros/sky130_pcells.lym index 7f2f90f1..641d18da 100644 --- a/tech/sky130/pymacros/sky130_pcells.lym +++ b/tech/sky130/pymacros/sky130_pcells.lym @@ -38,7 +38,7 @@ import os try: import gdsfactory except: - print('Info: To enable the Sky130 PCells you need to install gdsfactory in your system-wide Python installation.') + print('Info: To enable the sky130 PCells you need to install gdsfactory in your system-wide Python installation.') print(f'Python sys.path: {sys.path}') sys.exit(0) @@ -52,7 +52,7 @@ from cells import sky130 # Instantiate and register the library sky130() -print("Loaded Sky130 PCells.") +print("Loaded sky130 PCells.") diff --git a/tech/sky130/python/import_netlist/__init__.py b/tech/sky130/python/import_netlist/__init__.py new file mode 100644 index 00000000..5cdb524d --- /dev/null +++ b/tech/sky130/python/import_netlist/__init__.py @@ -0,0 +1 @@ +from .import_netlist import sky130_import_netlist diff --git a/tech/sky130/python/import_netlist/import_netlist.py b/tech/sky130/python/import_netlist/import_netlist.py new file mode 100644 index 00000000..422cb2d1 --- /dev/null +++ b/tech/sky130/python/import_netlist/import_netlist.py @@ -0,0 +1,202 @@ +# Copyright 2024 Efabless Corporation +# +# 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. + +import os +import sys +import pya + +from .sky130_pcell_templates import templates + +def create_pcell_instance(pcell_name='CIRCLE', lib_name='Basic', params={}, pos=pya.Trans.R0): + """ + Create a new instance of a PCell + and return its width and height + """ + + print(f"Creating PCell '{pcell_name}' with parameters:") + + for key, value in params.items(): + print(f' - {key}: {value}') + + # Get PCell Library + lib = pya.Library.library_by_name(lib_name) + + if not lib: + print(f'Error: Library not found {lib_name}') + return (0, 0) + + # The PCell Declaration. This one will create PCell variants. + pcell_decl = lib.layout().pcell_declaration(pcell_name) + + if not pcell_decl: + print(f'Error: Pcell not found {pcell_name}') + return (0, 0) + + # Get the active layout + cellview = pya.CellView().active() + layout = cellview.layout() + if layout == None: + print(f'Error: Couldn\'t get active layout.') + return + + # Get the top cell. Assuming only one top cell exists + top_cell = layout.top_cell() + + # Add a PCell variant + pcell_var = layout.add_pcell_variant(lib, pcell_decl.id(), params) + + bbox = layout.cell(pcell_var).bbox() + + # Add an offset to the position to account for the origin + offset = pya.Trans(pos, x=-bbox.left, y=-bbox.bottom) + + width = bbox.width() + height = bbox.height() + + # Insert instance + top_cell.insert(pya.CellInstArray(pcell_var, offset)) + + return (width, height) + +current_x = 0 +spacing = 100 + +def create_subckt_instance(name, subckt_definitions): + global current_x + global spacing + + print(name) + + for pcell_inst in subckt_definitions[name]['pcells']: + (width, height) = create_pcell_instance( + pcell_inst['pcell_name'], + pcell_inst['pcell_library'], + pcell_inst['params'], + pya.Trans(current_x, 0) + ) + current_x += width + spacing + + for subckt_inst in subckt_definitions[name]['subckts']: + if not subckt_inst in subckt_definitions: + print(f'Error: Unknown subckt {subckt_inst}') + else: + create_subckt_instance(subckt_inst, subckt_definitions) + +def sky130_import_netlist(): + + # Get the schematic netlist + netlist_path = pya.FileDialog.ask_open_file_name("Choose the schematic netlist", '.', "SPICE (*.spice)") + + print(f'Info: The netlist importer is still experimental.') + print(f'Please report issues to: https://github.com/efabless/sky130_klayout_pdk/issues') + + # Check whether file exists + if not netlist_path or not os.path.isfile(netlist_path): + print(f'Error: {netlist_path} is not a file!') + sys.exit(0) + + # Parse the spice netlist + with open(netlist_path, 'r') as netlist_file: + netlist_content = netlist_file.read() + + # Continue lines starting with '+' + netlist_content = netlist_content.replace('\n+', '') + + # Split lines + lines = netlist_content.split('\n') + + subckt_definitions = {'root': {'subckts': [], 'pcells': [], 'references': 0}} + active_subckt = None + + # Scan for subckts + for line in lines: + # Ignore comments + if line.startswith('*'): + continue + + # Find start of subckt definitions + if line.startswith('.subckt') or line.startswith('.SUBCKT'): + active_subckt = line.split(' ')[1] + subckt_definitions[active_subckt] = {'subckts': [], 'pcells': [], 'references': 0} + + print(subckt_definitions) + + for line in lines: + # Ignore comments + if line.startswith('*'): + continue + + # Find start of subckt definitions + if line.startswith('.subckt') or line.startswith('.SUBCKT'): + active_subckt = line.split(' ')[1] + + if not active_subckt in subckt_definitions: + print(f'Error: Unknown subckt {active_subckt}') + + # Find end of subckt definitions + if line.startswith('.ends') or line.startswith('.ENDS'): + active_subckt = None + + # Subckt instantiation + if line.startswith('x') or line.startswith('X'): + subckt = line.split(' ')[-1] + subckt_definitions[active_subckt]['subckts'].append(subckt) + + if not subckt in subckt_definitions: + print(f'Error: Unknown subckt {active_subckt}') + else: + subckt_definitions[subckt]['references'] += 1 + + #print(f"Searching for match with '{line}'!") + for template in templates: + + match = template['regex'].match(line) + if match: + params = template['default_params'] + + # Parse parameters + for param in template['params']: + #print(f"Parsing parameter {param['name']}") + if param["type"] == "string": + params[param['name']] = match.group(param['name']) + if param["type"] == "int": + params[param['name']] = int(match.group(param['name'])) + if param["type"] == "float": + params[param['name']] = float(match.group(param['name'])) + + # Multiplicity 'm' + m = 1 + if 'm' in params: + m = params.pop('m') + + # Divide 'w' by 'nf' to get individual finger width + if 'nf' in params and params['nf'] > 1: + if 'w' in params: + params['w'] /= params['nf'] + + #print(f'Instantiating Pcell with: {params}') + + for _ in range(m): + subckt_definitions[active_subckt]['pcells'].append({ + 'pcell_name': template['pcell_name'], + 'pcell_library': template['pcell_library'], + 'params': params.copy(), + }) + + print(subckt_definitions) + + # Instanciate all top-level subckts + for name in subckt_definitions.keys(): + if subckt_definitions[name]['references'] == 0: + create_subckt_instance(name, subckt_definitions) diff --git a/tech/sky130/python/import_netlist/sky130_pcell_templates.py b/tech/sky130/python/import_netlist/sky130_pcell_templates.py new file mode 100644 index 00000000..9d4f5a72 --- /dev/null +++ b/tech/sky130/python/import_netlist/sky130_pcell_templates.py @@ -0,0 +1,277 @@ +# Copyright 2024 Efabless Corporation +# +# 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. + +import re + +templates = [ + { + # This regex searches for one of the available nfets with the parameters L, W, nf, m in any order + 'regex' : re.compile(r'^.*(?Psky130_fd_pr__nfet_(?:01v8|01v8_lvt|01v8_hvt|g5v0d10v5))(?=.*L=(?P\d+(\.\d+)?))(?=.*W=(?P\d+(\.\d+)?))(?=.*nf=(?P\d+))(?=.*m=(?P\d+)).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'nfet', + 'params' : [ + { + 'name' : 'type', + 'type' : 'string', + }, + { + 'name' : 'l', + 'type' : 'float', + }, + { + 'name' : 'w', + 'type' : 'float', + }, + { + 'name' : 'nf', + 'type' : 'int', + }, + { + 'name' : 'm', + 'type' : 'int', + } + ], + 'default_params' : { + 'bulk' : 'guard ring', # 'bulk tie', 'None' + 'gate_con_pos' : 'top', # 'bottom', 'alternating' + 'sd_con_col' : 1, + 'inter_sd_l' : 0.5, + 'nf' : 1, + 'grw' : 0.17 + } + }, + { + 'regex' : re.compile(r'^.*(?Psky130_fd_pr__pfet_(?:01v8|01v8_lvt|01v8_hvt|g5v0d10v5))(?=.*L=(?P\d+(\.\d+)?))(?=.*W=(?P\d+(\.\d+)?))(?=.*nf=(?P\d+))(?=.*m=(?P\d+)).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'pfet', + 'params' : [ + { + 'name' : 'type', + 'type' : 'string', + }, + { + 'name' : 'l', + 'type' : 'float', + }, + { + 'name' : 'w', + 'type' : 'float', + }, + { + 'name' : 'nf', + 'type' : 'int', + }, + { + 'name' : 'm', + 'type' : 'int', + } + ], + 'default_params' : { + 'bulk' : 'guard ring', # 'bulk tie', 'None' + 'gate_con_pos' : 'top', # 'bottom', 'alternating' + 'sd_con_col' : 1, + 'inter_sd_l' : 0.5, + 'nf' : 1, + 'grw' : 0.17 + } + }, + { + 'regex' : re.compile(r'^.*(?Psky130_fd_pr__res_(?:x)?high_po_(?:0p35|0p69|1p41|2p85|5p73))(?=.*L=(?P\d+(\.\d+)?))(?=.*m=(?P\d+)).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'res_poly', + 'params' : [ + { + 'name' : 'type', + 'type' : 'string', + }, + { + 'name' : 'len', + 'type' : 'float', + }, + { + 'name' : 'm', + 'type' : 'int', + } + ], + 'default_params' : { + 'gr' : False # guard ring + } + }, + { + 'regex' : re.compile(r'^.*sky130_fd_pr__cap_mim_m3_1(?=.*L=(?P\d+(\.\d+)?))(?=.*W=(?P\d+(\.\d+)?))(?=.*m=(?P\d+)).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'mim_cap', + 'params' : [ + { + 'name' : 'l', + 'type' : 'float', + }, + { + 'name' : 'w', + 'type' : 'float', + }, + { + 'name' : 'm', + 'type' : 'int', + } + ], + 'default_params' : { + 'type' : 'sky130_fd_pr__model__cap_mim', + } + }, + { + 'regex' : re.compile(r'^.*sky130_fd_pr__cap_mim_m3_2(?=.*W=(?P\d+(\.\d+)?))(?=.*L=(?P\d+(\.\d+)?))(?=.*m=(?P\d+)).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'mim_cap', + 'params' : [ + { + 'name' : 'l', + 'type' : 'float', + }, + { + 'name' : 'w', + 'type' : 'float', + }, + { + 'name' : 'm', + 'type' : 'int', + } + ], + 'default_params' : { + 'type' : 'sky130_fd_pr__model__cap_mim_m4', + } + }, + { + 'regex' : re.compile(r'^.*(?Psky130_fd_pr__photodiode).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'photodiode', + 'params' : [ + { + 'name' : 'Type', + 'type' : 'string', + }, + ], + 'default_params' : { + } + }, + { + 'regex' : re.compile(r'^.*(?Psky130_fd_pr__res_generic_(?:l1|m1|m2|m3|m4|m5))(?=.*W=(?P\d+(\.\d+)?))(?=.*L=(?P\d+(\.\d+)?))(?=.*m=(?P\d+)).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'res_metal', + 'params' : [ + { + 'name' : 'type', + 'type' : 'string', + }, + { + 'name' : 'len', + 'type' : 'float', + }, + { + 'name' : 'w', + 'type' : 'float', + }, + { + 'name' : 'm', + 'type' : 'int', + } + ], + 'default_params' : { + 'gr': True, + } + }, + { + 'regex' : re.compile(r'^.*sky130_fd_pr__npn_05v5_w1p00l2p00(?=.*m=(?P\d+)).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'npn_bjt', + 'params' : [ + { + 'name' : 'm', + 'type' : 'int', + } + ], + 'default_params' : { + 'type': 'sky130_fd_pr__npn_05v5_W1p00L1p00', + } + }, + { + 'regex' : re.compile(r'^.*sky130_fd_pr__pnp_05v5_W0p68L0p68(?=.*m=(?P\d+)).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'pnp_bjt', + 'params' : [ + { + 'name' : 'm', + 'type' : 'int', + } + ], + 'default_params' : { + 'Type': 'sky130_fd_pr__pnp_05v5_W0p68L0p68', + } + }, + { + 'regex' : re.compile(r'^.*(?Psky130_fd_pr__diode_pw2nd_(?:05v5|05v5_lvt|05v5_nvt|11v0)).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'n_diode', + 'params' : [ + { + 'name' : 'type', + 'type' : 'string', + }, + ], + 'default_params' : { + } + }, + { + 'regex' : re.compile(r'^.*(?Psky130_fd_pr__cap_var_(?:lvt|hvt))(?=.*W=(?P\d+(\.\d+)?))(?=.*L=(?P\d+(\.\d+)?))(?=.*m=(?P\d+)).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'cap_var', + 'params' : [ + { + 'name' : 'type', + 'type' : 'string', + }, + { + 'name' : 'l', + 'type' : 'float', + }, + { + 'name' : 'w', + 'type' : 'float', + }, + { + 'name' : 'm', + 'type' : 'int', + } + ], + 'default_params' : { + } + }, + { + 'regex' : re.compile(r'^.*(?Psky130_fd_pr__cap_vpp_[^\s]*)(?=.*m=(?P\d+)).*$'), + 'pcell_library': 'skywater130', + 'pcell_name' : 'cap_vpp', + 'params' : [ + { + 'name' : 'Type', + 'type' : 'string', + }, + { + 'name' : 'm', + 'type' : 'int', + } + ], + 'default_params' : { + } + }, +] diff --git a/tech/sky130/python/sky130_import_netlist.py b/tech/sky130/python/sky130_import_netlist.py deleted file mode 100644 index 27621af8..00000000 --- a/tech/sky130/python/sky130_import_netlist.py +++ /dev/null @@ -1,246 +0,0 @@ -# Copyright 2024 Efabless Corporation -# -# 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. - -import os -import sys -import re -import pya - -def createPCellInstance(pcell_name='CIRCLE', lib_name='Basic', params={}, pos=pya.Trans.R0): - """ - Create a new instance of a PCell - and return its width and height - """ - - print(f"Creating PCell '{pcell_name}' with parameters:") - - for key, value in params.items(): - print(f' - {key}: {value}') - - # Get PCell Library - lib = pya.Library.library_by_name(lib_name) - - if not lib: - print(f'Error: Library not found {lib_name}') - return (0, 0) - - # The PCell Declaration. This one will create PCell variants. - pcell_decl = lib.layout().pcell_declaration(pcell_name) - - if not pcell_decl: - print(f'Error: Pcell not found {pcell_name}') - return (0, 0) - - layoutview = pya.LayoutView().current() - - cellview = pya.CellView().active() - view = cellview.view() - layout = cellview.layout() - - # Get the top cell. Assuming only one top cell exists - top_cell = layout.top_cell() - - # Add a PCell variant - pcell_var = layout.add_pcell_variant(lib, pcell_decl.id(), params) - - width = layout.cell(pcell_var).bbox().width() - height = layout.cell(pcell_var).bbox().height() - - # Insert instance - top_cell.insert(pya.CellInstArray(pcell_var, pos)) - - return (width, height) - -def sky130_import_netlist(): - - # Get the schematic netlist - netlist_path = pya.FileDialog.ask_open_file_name("Choose the schematic netlist", '.', "SPICE (*.spice)") - - print(f'Info: The netlist importer is still experimental and does not yet support ".subckt" statements.') - print(f'Please report issues to: https://github.com/efabless/sky130_klayout_pdk/issues') - - # Check whether file exists - if not netlist_path or not os.path.isfile(netlist_path): - print(f'Error: {netlist_path} is not a file!') - sys.exit(0) - - # Get PCell Library - lib = pya.Library.library_by_name('skywater130') - - if lib == None: - print(f'Error: Couldn\'t get library "skywater130"') - return - - layoutview = pya.LayoutView().current() - - if layoutview == None: - print(f'Error: Couldn\'t get current layout view') - return - - cellview = pya.CellView().active() - view = cellview.view() - layout = cellview.layout() - - # Get the top cell. Assuming only one top cell exists - top_cell = layout.top_cell() - - # Here we store the variants for later instantiation - variants = {} - - templates = [ - { - # This regex searches for one of the available nfets with the parameters L, W, nf, m in any order - 'regex' : re.compile(r'^.*(?Psky130_fd_pr__nfet_(?:01v8|01v8_lvt|01v8_hvt|g5v0d10v5))(?=.*L=(?P\d+(\.\d+)?))(?=.*W=(?P\d+(\.\d+)?))(?=.*nf=(?P\d+))(?=.*m=(?P\d+)).*$'), - 'pcell_name' : 'nfet', - 'params' : [ - { - 'name' : 'type', - 'type' : 'string', - }, - { - 'name' : 'l', - 'type' : 'float', - }, - { - 'name' : 'w', - 'type' : 'float', - }, - { - 'name' : 'nf', - 'type' : 'int', - }, - { - 'name' : 'm', - 'type' : 'int', - } - ], - 'default_params' : { - 'bulk' : 'guard ring', # 'bulk tie', 'None' - 'gate_con_pos' : 'top', # 'bottom', 'alternating' - 'sd_con_col' : 1, - 'inter_sd_l' : 0.5, - 'nf' : 1, - 'grw' : 0.17 - } - }, - { - 'regex' : re.compile(r'^.*(?Psky130_fd_pr__pfet_(?:01v8|01v8_lvt|01v8_hvt|g5v0d10v5))(?=.*L=(?P\d+(\.\d+)?))(?=.*W=(?P\d+(\.\d+)?))(?=.*nf=(?P\d+))(?=.*m=(?P\d+)).*$'), - 'pcell_name' : 'pfet', - 'params' : [ - { - 'name' : 'type', - 'type' : 'string', - }, - { - 'name' : 'l', - 'type' : 'float', - }, - { - 'name' : 'w', - 'type' : 'float', - }, - { - 'name' : 'nf', - 'type' : 'int', - }, - { - 'name' : 'm', - 'type' : 'int', - } - ], - 'default_params' : { - 'bulk' : 'guard ring', # 'bulk tie', 'None' - 'gate_con_pos' : 'top', # 'bottom', 'alternating' - 'sd_con_col' : 1, - 'inter_sd_l' : 0.5, - 'nf' : 1, - 'grw' : 0.17 - } - }, - { - 'regex' : re.compile(r'^.*(?Psky130_fd_pr__res_(?:x)?high_po_(?:0p35|0p69|1p41|2p85|5p73))(?=.*L=(?P\d+(\.\d+)?))(?=.*m=(?P\d+)).*$'), - 'pcell_name' : 'res_poly', - 'params' : [ - { - 'name' : 'type', - 'type' : 'string', - }, - { - 'name' : 'len', - 'type' : 'float', - }, - { - 'name' : 'm', - 'type' : 'int', - } - ], - 'default_params' : { - 'gr' : False # guard ring - } - }, - ] - - current_x = 0 - spacing = 100 - - # Parse the spice netlist - with open(netlist_path, 'r') as netlist_file: - - line = netlist_file.readline() - - for next_line in netlist_file.readlines(): - #print(f'line {line}', end="") - - # This line is a continuation of the previous line - if next_line.startswith('+'): - line = line.rstrip('\n') + next_line[1:] - #print(line) - - # Got a complete line - else: - #print(f"Searching for match with '{line}'!") - for template in templates: - - match = template['regex'].match(line) - if match: - params = template['default_params'] - - for param in template['params']: - #print(f"Parsing parameter {param['name']}") - - if param["type"] == "string": - params[param['name']] = match.group(param['name']) - if param["type"] == "int": - params[param['name']] = int(match.group(param['name'])) - if param["type"] == "float": - params[param['name']] = float(match.group(param['name'])) - - # Multiplicity 'm' - m = 1 - if 'm' in params: - m = params.pop('m') - - # Divide 'w' by 'nf' to get individual finger width - if 'nf' in params and params['nf'] > 1: - if 'w' in params: - params['w'] /= params['nf'] - - #print(f'Instantiating Pcell with: {params}') - - for _ in range(m): - (width, height) = createPCellInstance(template['pcell_name'], 'skywater130', params, pya.Trans(current_x, 0)) - current_x += width + spacing - - # Assign the next line - line = next_line