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
« 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
27import klayout.db as kdb
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
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])
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
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')
83 def net_of(device_name: str, terminal: str) -> str:
84 return circuit.device_by_name(device_name).net_for_terminal(terminal).name
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)