Coverage for tests/rcx25/rcx25_netlist_expander_terminals_test.py: 100%

51 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-02 23:32 +0000

1# 

2# -------------------------------------------------------------------------------- 

3# SPDX-FileCopyrightText: 2024-2026 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/iic-jku/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# 

24import allure 

25import unittest 

26 

27import klayout.db as kdb 

28 

29from klayout_pex.device_models import DeviceModels 

30from klayout_pex.rcx25.extraction_results import CellExtractionResults, ExtractionResults 

31from klayout_pex.rcx25.netlist_expander import RCX25NetlistExpander 

32import klayout_pex_protobuf.kpex.r.r_network_pb2 as r_network_pb2 

33import klayout_pex_protobuf.kpex.tech.device_models_pb2 as device_models_pb2 

34 

35 

36@allure.parent_suite("Unit Tests") 

37@allure.tag("RCX25", "Netlist Expansion") 

38class Test(unittest.TestCase): 

39 def test_device_terminals_are_connected_to_their_nodes(self): 

40 # 2 nmos with drain on the unlabelled net $2, and gate on the pin G 

41 netlist = kdb.Netlist() 

42 circuit = kdb.Circuit() 

43 circuit.name = 'chip' 

44 netlist.add(circuit) 

45 nmos = kdb.DeviceClassMOS4Transistor() 

46 nmos.name = 'nmos' 

47 netlist.add(nmos) 

48 nets = {name: circuit.create_net(name) for name in ('G', '$2', 'S', 'B')} 

49 m1 = circuit.create_device(nmos, 'M1') 

50 m2 = circuit.create_device(nmos, 'M2') 

51 for m in (m1, m2): 

52 for terminal, net in (('S', 'S'), ('G', 'G'), ('D', '$2'), ('B', 'B')): 

53 m.connect_terminal(terminal, nets[net]) 

54 

55 K = r_network_pb2.RNode.Kind 

56 results = CellExtractionResults(cell_name='chip') 

57 for net_name, nodes, elements in ( 

58 ('$2', [(1, K.KIND_DEVICE_TERMINAL, 'P0.12', m1.id(), 'D'), 

59 (2, K.KIND_WIRE_JUNCTION, '$0.12', None, None), 

60 (3, K.KIND_DEVICE_TERMINAL, 'P1.12', m2.id(), 'D')], 

61 [(1, 2, 10.0), (2, 3, 20.0)]), 

62 ('G', [(1, K.KIND_PIN, 'G', None, None), 

63 (2, K.KIND_DEVICE_TERMINAL, 'P0.16', m1.id(), 'G')], 

64 [(1, 2, 100.0)])): 

65 network = results.r_extraction_result.networks.add(net_name=net_name) 

66 for node_id, kind, node_name, device_id, terminal in nodes: 

67 node = network.nodes.add(node_id=node_id, node_kind=kind, node_name=node_name) 

68 if kind != K.KIND_PIN: 

69 node.net_name = f"{net_name}.{node_name}" 

70 if device_id is not None: 

71 node.device_terminal.device_id = device_id 

72 node.device_terminal.terminal_id = nmos.terminal_id(terminal) 

73 for node_a, node_b, resistance in elements: 

74 element = network.elements.add(resistance=resistance) 

75 element.node_a.node_id = node_a 

76 element.node_b.node_id = node_b 

77 

78 expanded = RCX25NetlistExpander.expand(netlist, 'chip', ExtractionResults({'chip': results}), 

79 blackbox_devices=True, 

80 device_models=DeviceModels(device_models_pb2.DeviceModelsInfo())) 

81 circuit = expanded.circuit_by_name('chip') 

82 

83 def net_of(device_name: str, terminal: str) -> str: 

84 return circuit.device_by_name(device_name).net_for_terminal(terminal).name 

85 

86 # NOTE: M1's drain is the first device terminal of $2, so it carries the net's name 

87 self.assertEqual('$2', net_of('M1', 'D')) 

88 self.assertEqual('$2.P1.12', net_of('M2', 'D')) 

89 self.assertEqual('G.P0.16', net_of('M1', 'G')) 

90 self.assertEqual('G', net_of('M2', 'G')) # no port, stays on the net 

91 self.assertEqual('B', net_of('M1', 'B')) # no resistor network 

92 resistors = {frozenset((d.net_for_terminal('A').name, d.net_for_terminal('B').name)): d.parameter('R') 

93 for d in circuit.each_device() if d.device_class().name == 'PEX_RES'} 

94 self.assertEqual({frozenset(('$2', '$2.$0.12')): 10.0, 

95 frozenset(('$2.$0.12', '$2.P1.12')): 20.0, 

96 frozenset(('G', 'G.P0.16')): 100.0}, resistors)