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

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 * 

27 

28import klayout.db as kdb 

29 

30from ..log import ( 

31 info 

32) 

33 

34# see https://yosyshq.readthedocs.io/projects/yosys/en/latest/cmd/write_json.html 

35 

36 

37class NetlistJSONWriter: 

38 def __init__(self): 

39 self.next_id = 0 

40 self.id_for_net: Dict[str, int] = {} 

41 

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 

50 

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 } 

62 

63 return port_dict 

64 

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') 

72 

73 cells_dict = {} 

74 

75 for sc in circuit.each_subcircuit(): 

76 subcircuit: kdb.Circuit = sc.circuit_ref() 

77 

78 port_directions = {} 

79 connections = {} 

80 

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)] 

88 

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 } 

97 

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}") 

136 

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 } 

156 

157 return cells_dict 

158 

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 

171 

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)