Coverage for klayout_pex/netlistsvg/netlist_json.py: 99%
77 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-08 14:04 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-08 14:04 +0000
1#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024 Martin Jan Köhler and Harald Pretl
4# Johannes Kepler University, Institute for Integrated Circuits.
5#
6# This file is part of KPEX
7# (see https://github.com/martinjankoehler/klayout-pex).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21# SPDX-License-Identifier: GPL-3.0-or-later
22# --------------------------------------------------------------------------------
23#
24from __future__ import annotations
25import json
26from typing import *
28import klayout.db as kdb
30from ..log import (
31 info
32)
34# see https://yosyshq.readthedocs.io/projects/yosys/en/latest/cmd/write_json.html
37class NetlistJSONWriter:
38 def __init__(self):
39 self.next_id = 0
40 self.id_for_net: Dict[str, int] = {}
42 def _get_or_create_id(self, net: kdb.Net) -> int:
43 net_name = net.expanded_name() or net.name
44 i = self.id_for_net.get(net_name, None)
45 if i is None:
46 i = self.next_id
47 self.next_id += 1
48 self.id_for_net[net_name] = i
49 return i
51 def dict_for_ports(self, circuit: kdb.Circuit) -> Dict[str, Any]:
52 port_dict = {}
53 for pin in circuit.each_pin():
54 pin: kdb.Pin
55 name = pin.name()
56 net = circuit.net_for_pin(pin)
57 bit = self._get_or_create_id(net)
58 port_dict[name] = {
59 'direction': 'input',
60 'bits': [bit]
61 }
63 return port_dict
65 def dict_for_cells(self, circuit: kdb.Circuit) -> Dict[str, Any]:
66 gnd_nodes: Dict[str, List[int]] = {}
67 vdd_nodes: Dict[str, List[int]] = {}
68 vss_nodes: Dict[str, List[int]] = {}
69 gnd_aliases = ('GND', 'DGND', 'AGND', 'VGND', 'GND1', 'GND2', 'VSUBS')
70 vdd_aliases = ('VDD', 'VCC', 'VPWR')
71 vss_aliases = ('VSS', 'VEE')
73 cells_dict = {}
75 for sc in circuit.each_subcircuit():
76 subcircuit: kdb.Circuit = sc.circuit_ref()
78 port_directions = {}
79 connections = {}
81 for pin in subcircuit.each_pin():
82 pin: kdb.Pin
83 net = sc.net_for_pin(pin.id())
84 pin_name = net.expanded_name()
85 pin_text = f"{pin.id()}={pin_name}"
86 port_directions[pin_text] = 'input'
87 connections[pin_text] = [self._get_or_create_id(net)]
89 cells_dict[f"{subcircuit.name}{subcircuit.cell_index}"] = {
90 'hide_name': 1,
91 'type': f"${subcircuit.name}",
92 'port_directions': port_directions,
93 'connections': connections,
94 'attributes': {
95 }
96 }
98 for d in circuit.each_device():
99 d: kdb.Device
100 # https://www.klayout.de/doc-qt5/code/class_Device.html
101 dc = d.device_class()
102 dn = d.expanded_name() or d.name
103 param_defs = dc.parameter_definitions()
104 params = {p.name: d.parameter(p.id()) for p in param_defs}
105 if isinstance(dc, kdb.DeviceClassCapacitor):
106 net1: kdb.Net = d.net_for_terminal('A')
107 net2: kdb.Net = d.net_for_terminal('B')
108 cap = params['C']
109 cap_femto = round(cap * 1e15, 3)
110 cells_dict[f"C{dn}"] = {
111 'type': 'c_v',
112 'connections': {
113 'A': [self._get_or_create_id(net1)],
114 'B': [self._get_or_create_id(net2)],
115 },
116 'attributes': {
117 'value': f"{cap_femto}f"
118 }
119 }
120 elif isinstance(dc, kdb.DeviceClassResistor):
121 net1: kdb.Net = d.net_for_terminal('A')
122 net2: kdb.Net = d.net_for_terminal('B')
123 ohm = params['R']
124 cells_dict[f"R{dn}"] = {
125 'type': 'r_v',
126 'connections': {
127 'A': [self._get_or_create_id(net1)],
128 'B': [self._get_or_create_id(net2)],
129 },
130 'attributes': {
131 'value': f"{round(ohm, 3)}"
132 }
133 }
134 else:
135 raise NotImplementedError(f"Not yet implemented: {dc}")
137 gnd_counter = 0
138 for gnd_name in ('VSUBS', 'GND'):
139 gnd_id = self.id_for_net.get(gnd_name, None)
140 if gnd_id is None:
141 continue
142 device_name = f"gnd{'' if gnd_counter == 0 else gnd_counter}"
143 gnd_counter += 1
144 cells_dict[device_name] = {
145 'type': 'gnd',
146 'port_directions': {
147 'A': 'input'
148 },
149 'connections': {
150 'A': [gnd_id],
151 },
152 'attributes': {
153 'value': gnd_name
154 }
155 }
157 return cells_dict
159 def netlist_json_dict(self,
160 netlist: kdb.Netlist,
161 top_circuit: kdb.Circuit) -> Dict[str, Any]:
162 json_dict = {
163 'modules': {
164 top_circuit.name: {
165 'ports': self.dict_for_ports(top_circuit),
166 'cells': self.dict_for_cells(top_circuit)
167 }
168 }
169 }
170 return json_dict
172 def write_json(self,
173 netlist: kdb.Netlist,
174 top_circuit: kdb.Circuit,
175 output_path: str):
176 with open(output_path, 'w', encoding='utf-8') as f:
177 d = self.netlist_json_dict(netlist=netlist, top_circuit=top_circuit)
178 json.dump(d, f, indent=4)