diff --git a/pycov/class_index.html b/pycov/class_index.html
index 990e8676..76d1e485 100644
--- a/pycov/class_index.html
+++ b/pycov/class_index.html
@@ -11,7 +11,7 @@
Coverage report:
- 61%
+ 85%
coverage.py v7.6.10, - created at 2025-01-16 16:34 +0000 + created at 2025-01-16 21:25 +0000
coverage.py v7.6.10, - created at 2025-01-16 16:34 +0000 + created at 2025-01-16 21:25 +0000
coverage.py v7.6.10, - created at 2025-01-16 16:34 +0000 + created at 2025-01-16 21:25 +0000
coverage.py v7.6.10, - created at 2025-01-16 16:34 +0000 + created at 2025-01-16 21:25 +0000
91 rule()
93 if proc.returncode == 0:
-94 info(f"klayout LVS succeeded after {'%.4g' % duration}s")
+94 info(f"klayout LVS succeeded after {'%.4g' % duration}s")
95 else:
-96 warning(f"klayout LVS failed with status code {proc.returncode} after {'%.4g' % duration}s, "
+96 warning(f"klayout LVS failed with status code {proc.returncode} after {'%.4g' % duration}s, "
97 f"see log file: {log_path}")
diff --git a/pycov/z_04b8b9b3cc5f2e8a_lvsdb_extractor_py.html b/pycov/z_04b8b9b3cc5f2e8a_lvsdb_extractor_py.html index 2c0622ed..66d4ef04 100644 --- a/pycov/z_04b8b9b3cc5f2e8a_lvsdb_extractor_py.html +++ b/pycov/z_04b8b9b3cc5f2e8a_lvsdb_extractor_py.html @@ -2,7 +2,7 @@ -@@ -65,7 +65,7 @@
222 return nonempty_layers, unnamed_layers
224 def top_cell_bbox(self) -> kdb.Box:
-225 b1: kdb.Box = self.target_layout.top_cell().bbox()
-226 b2: kdb.Box = self.lvsdb.internal_layout().top_cell().bbox()
-227 if b1.area() > b2.area():
+225 b1: kdb.Box = self.target_layout.top_cell().bbox()
+226 b2: kdb.Box = self.lvsdb.internal_layout().top_cell().bbox()
+227 if b1.area() > b2.area():
228 return b1
229 else:
-230 return b2
+230 return b2
232 def shapes_of_net(self, gds_pair: GDSPair, net: kdb.Net) -> Optional[kdb.Region]:
-233 lyr = self.extracted_layers.get(gds_pair, None)
-234 if not lyr:
-235 return None
+233 lyr = self.extracted_layers.get(gds_pair, None)
+234 if not lyr:
+235 return None
237 shapes: kdb.Region
-239 match len(lyr.source_layers):
-240 case 0:
+239 match len(lyr.source_layers):
+240 case 0:
241 raise AssertionError('Internal error: Empty list of source_layers')
-242 case 1:
-243 shapes = self.lvsdb.shapes_of_net(net, lyr.source_layers[0].region, True)
+242 case 1:
+243 shapes = self.lvsdb.shapes_of_net(net, lyr.source_layers[0].region, True)
244 case _:
245 shapes = kdb.Region()
246 for sl in lyr.source_layers:
247 shapes += self.lvsdb.shapes_of_net(net, sl.region, True)
248 # shapes.merge()
-250 return shapes
+250 return shapes
252 def shapes_of_layer(self, gds_pair: GDSPair) -> Optional[kdb.Region]:
-253 lyr = self.extracted_layers.get(gds_pair, None)
-254 if not lyr:
-255 return None
+253 lyr = self.extracted_layers.get(gds_pair, None)
+254 if not lyr:
+255 return None
257 shapes: kdb.Region
-259 match len(lyr.source_layers):
-260 case 0:
+259 match len(lyr.source_layers):
+260 case 0:
261 raise AssertionError('Internal error: Empty list of source_layers')
-262 case 1:
-263 shapes = lyr.source_layers[0].region
+262 case 1:
+263 shapes = lyr.source_layers[0].region
264 case _:
265 shapes = kdb.Region()
266 for sl in lyr.source_layers:
267 shapes += sl.region
268 # shapes.merge()
-270 return shapes
+270 return shapes
@@ -361,7 +361,7 @@@@ -65,7 +65,7 @@
35 def write_csv(netlist: kdb.Netlist,
36 top_cell_name: str,
37 output_path: str):
-38 with open(output_path, 'w') as f:
-39 f.write('Device;Net1;Net2;Capacitance [fF]\n')
+38 with open(output_path, 'w') as f:
+39 f.write('Device;Net1;Net2;Capacitance [fF]\n')
-41 top_circuit: kdb.Circuit = netlist.circuit_by_name(top_cell_name)
+41 top_circuit: kdb.Circuit = netlist.circuit_by_name(top_cell_name)
43 # NOTE: only caps for now
-44 for d in top_circuit.each_device():
+44 for d in top_circuit.each_device():
45 # https://www.klayout.de/doc-qt5/code/class_Device.html
-46 dc = d.device_class()
-47 if isinstance(dc, kdb.DeviceClassCapacitor):
-48 dn = d.expanded_name() or d.name
-49 if dc.name != 'PEX_CAP':
+46 dc = d.device_class()
+47 if isinstance(dc, kdb.DeviceClassCapacitor):
+48 dn = d.expanded_name() or d.name
+49 if dc.name != 'PEX_CAP':
50 info(f"Ignoring device {dn}")
51 continue
-52 param_defs = dc.parameter_definitions()
-53 params = {p.name: d.parameter(p.id()) for p in param_defs}
+52 param_defs = dc.parameter_definitions()
+53 params = {p.name: d.parameter(p.id()) for p in param_defs}
54 d: kdb.Device
-55 net1 = d.net_for_terminal('A')
-56 net2 = d.net_for_terminal('B')
-57 cap = params['C']
-58 cap_femto = round(cap * 1e15, 2)
-59 f.write(f"{dn};{net1.name};{net2.name};{cap_femto}\n")
+55 net1 = d.net_for_terminal('A')
+56 net2 = d.net_for_terminal('B')
+57 cap = params['C']
+58 cap_femto = round(cap * 1e15, 2)
+59 f.write(f"{dn};{net1.name};{net2.name};{cap_femto}\n")
diff --git a/pycov/z_04b8b9b3cc5f2e8a_netlist_expander_py.html b/pycov/z_04b8b9b3cc5f2e8a_netlist_expander_py.html index 55832347..78ff895a 100644 --- a/pycov/z_04b8b9b3cc5f2e8a_netlist_expander_py.html +++ b/pycov/z_04b8b9b3cc5f2e8a_netlist_expander_py.html @@ -65,7 +65,7 @@@@ -65,7 +65,7 @@
55 elif isinstance(dc, kdb.DeviceClassResistor):
56 # TODO
-57 pass
+57 pass
59 for d in devices_to_remove:
60 info(f"Removed device {d.name} {d.parameter('C')}")
@@ -152,7 +152,7 @@@@ -65,7 +65,7 @@
94 '--gds', gds_path,
95 '--out_dir', output_dir_path,
96 '--2.5D', 'y'])
-97 assert cli.rcx25_extraction_results is not None
-98 assert len(cli.rcx25_extraction_results.cell_extraction_results) == 1 # assume single cell test
-99 results = list(cli.rcx25_extraction_results.cell_extraction_results.values())[0]
-100 assert results.cell_name == path_components[-1][:-len('.gds.gz')]
-101 return results, cli.rcx25_extracted_csv_path, preview_png_path
+97 assert cli.rcx25_extraction_results is not None
+98 assert len(cli.rcx25_extraction_results.cell_extraction_results) == 1 # assume single cell test
+99 results = list(cli.rcx25_extraction_results.cell_extraction_results.values())[0]
+100 assert results.cell_name == path_components[-1][:-len('.gds.gz')]
+101 return results, cli.rcx25_extracted_csv_path, preview_png_path
104def assert_expected_matches_obtained(*path_components,
105 expected_csv_content: str) -> CellExtractionResults:
106 result, csv, preview_png = _run_rcx25d_single_cell(*path_components)
-107 allure.attach.file(csv, name='pex_obtained.csv', attachment_type=allure.attachment_type.CSV)
-108 allure.attach.file(preview_png, name='📸 layout_preview.png', attachment_type=allure.attachment_type.PNG)
-109 expected_csv = csv_diff.load_csv(io.StringIO(expected_csv_content), key='Device')
-110 with open(csv, 'r') as f:
-111 obtained_csv = csv_diff.load_csv(f, key='Device')
-112 diff = csv_diff.compare(expected_csv, obtained_csv, show_unchanged=False)
-113 human_diff = csv_diff.human_text(
+107 allure.attach.file(csv, name='pex_obtained.csv', attachment_type=allure.attachment_type.CSV)
+108 allure.attach.file(preview_png, name='📸 layout_preview.png', attachment_type=allure.attachment_type.PNG)
+109 expected_csv = csv_diff.load_csv(io.StringIO(expected_csv_content), key='Device')
+110 with open(csv, 'r') as f:
+111 obtained_csv = csv_diff.load_csv(f, key='Device')
+112 diff = csv_diff.compare(expected_csv, obtained_csv, show_unchanged=False)
+113 human_diff = csv_diff.human_text(
114 diff, current=obtained_csv, extras=(('Net1','{Net1}'),('Net2','{Net2}'))
115 )
-116 allure.attach(expected_csv_content, name='pex_expected.csv', attachment_type=allure.attachment_type.CSV)
-117 allure.attach(json.dumps(diff, sort_keys=True, indent=' ').encode("utf8"),
+116 allure.attach(expected_csv_content, name='pex_expected.csv', attachment_type=allure.attachment_type.CSV)
+117 allure.attach(json.dumps(diff, sort_keys=True, indent=' ').encode("utf8"),
118 name='pex_diff.json', attachment_type=allure.attachment_type.JSON)
-119 allure.attach(human_diff.encode("utf8"), name='‼️ pex_diff.txt', attachment_type=allure.attachment_type.TEXT)
+119 allure.attach(human_diff.encode("utf8"), name='‼️ pex_diff.txt', attachment_type=allure.attachment_type.TEXT)
120 # assert diff['added'] == []
121 # assert diff['removed'] == []
122 # assert diff['changed'] == []
123 # assert diff['columns_added'] == []
124 # assert diff['columns_removed'] == []
-125 assert human_diff == '', 'Diff detected'
-126 return result
+125 assert human_diff == '', 'Diff detected'
+126 return result
128@allure.parent_suite(parent_suite)
129@allure.tag(*tags)
@@ -414,7 +414,7 @@@@ -65,7 +65,7 @@
93 '--gds', gds_path,
94 '--out_dir', output_dir_path,
95 '--fastercap', 'y'])
-96 assert cli.fastercap_extracted_csv_path is not None
-97 return cli.fastercap_extracted_csv_path, preview_png_path
+96 assert cli.fastercap_extracted_csv_path is not None
+97 return cli.fastercap_extracted_csv_path, preview_png_path
100def assert_expected_matches_obtained(*path_components,
101 expected_csv_content: str):
102 csv, preview_png = _extract_single_cell(*path_components)
-103 allure.attach.file(csv, name='pex_obtained.csv', attachment_type=allure.attachment_type.CSV)
-104 allure.attach.file(preview_png, name='📸 layout_preview.png', attachment_type=allure.attachment_type.PNG)
-105 expected_csv = csv_diff.load_csv(io.StringIO(expected_csv_content), key='Device')
-106 with open(csv, 'r') as f:
-107 obtained_csv = csv_diff.load_csv(f, key='Device')
-108 diff = csv_diff.compare(expected_csv, obtained_csv, show_unchanged=False)
-109 human_diff = csv_diff.human_text(
+103 allure.attach.file(csv, name='pex_obtained.csv', attachment_type=allure.attachment_type.CSV)
+104 allure.attach.file(preview_png, name='📸 layout_preview.png', attachment_type=allure.attachment_type.PNG)
+105 expected_csv = csv_diff.load_csv(io.StringIO(expected_csv_content), key='Device')
+106 with open(csv, 'r') as f:
+107 obtained_csv = csv_diff.load_csv(f, key='Device')
+108 diff = csv_diff.compare(expected_csv, obtained_csv, show_unchanged=False)
+109 human_diff = csv_diff.human_text(
110 diff, current=obtained_csv, extras=(('Net1','{Net1}'),('Net2','{Net2}'))
111 )
-112 allure.attach(expected_csv_content, name='pex_expected.csv', attachment_type=allure.attachment_type.CSV)
-113 allure.attach(json.dumps(diff, sort_keys=True, indent=' ').encode("utf8"),
+112 allure.attach(expected_csv_content, name='pex_expected.csv', attachment_type=allure.attachment_type.CSV)
+113 allure.attach(json.dumps(diff, sort_keys=True, indent=' ').encode("utf8"),
114 name='pex_diff.json', attachment_type=allure.attachment_type.JSON)
-115 allure.attach(human_diff.encode("utf8"), name='‼️ pex_diff.txt', attachment_type=allure.attachment_type.TEXT)
+115 allure.attach(human_diff.encode("utf8"), name='‼️ pex_diff.txt', attachment_type=allure.attachment_type.TEXT)
116 # assert diff['added'] == []
117 # assert diff['removed'] == []
118 # assert diff['changed'] == []
119 # assert diff['columns_added'] == []
120 # assert diff['columns_removed'] == []
-121 assert human_diff == '', 'Diff detected'
+121 assert human_diff == '', 'Diff detected'
124@allure.parent_suite(parent_suite)
@@ -223,7 +223,7 @@@@ -65,7 +65,7 @@
84 net_outside: NetName
86 def __repr__(self) -> str:
-87 return f"{self.layer_inside}({self.net_inside})-"\
+87 return f"{self.layer_inside}({self.net_inside})-"\
88 f"{self.layer_outside}({self.net_outside})"
@@ -176,7 +176,7 @@94 cap_value: float # femto farad
96 def __str__(self) -> str:
-97 return f"(Side Overlap): {self.key} = {round(self.cap_value, 6)}fF"
+97 return f"(Side Overlap): {self.key} = {round(self.cap_value, 6)}fF"
100@dataclass(frozen=True)
@@ -189,10 +189,10 @@108 # NOTE: we norm net names alphabetically
109 def normed(self) -> NetCoupleKey:
-110 if self.net1 < self.net2:
-111 return self
+110 if self.net1 < self.net2:
+111 return self
112 else:
-113 return NetCoupleKey(self.net2, self.net1)
+113 return NetCoupleKey(self.net2, self.net1)
116@dataclass
@@ -201,11 +201,11 @@120 @classmethod
121 def merged(cls, summaries: List[ExtractionSummary]) -> ExtractionSummary:
-122 merged_capacitances = defaultdict(float)
-123 for s in summaries:
-124 for couple_key, cap in s.capacitances.items():
-125 merged_capacitances[couple_key.normed()] += cap
-126 return ExtractionSummary(merged_capacitances)
+122 merged_capacitances = defaultdict(float)
+123 for s in summaries:
+124 for couple_key, cap in s.capacitances.items():
+125 merged_capacitances[couple_key.normed()] += cap
+126 return ExtractionSummary(merged_capacitances)
129@dataclass
@@ -217,22 +217,22 @@135 sideoverlap_table: Dict[SideOverlapKey, SideOverlapCap] = field(default_factory=dict)
137 def summarize(self) -> ExtractionSummary:
-138 overlap_summary = ExtractionSummary({
+138 overlap_summary = ExtractionSummary({
139 NetCoupleKey(key.net_top, key.net_bot): cap.cap_value
140 for key, cap in self.overlap_coupling.items()
141 })
-143 sidewall_summary = ExtractionSummary({
+143 sidewall_summary = ExtractionSummary({
144 NetCoupleKey(key.net1, key.net2): cap.cap_value
145 for key, cap in self.sidewall_table.items()
146 })
-148 sideoverlap_summary = ExtractionSummary({
+148 sideoverlap_summary = ExtractionSummary({
149 NetCoupleKey(key.net_inside, key.net_outside): cap.cap_value
150 for key, cap in self.sideoverlap_table.items()
151 })
-153 return ExtractionSummary.merged([
+153 return ExtractionSummary.merged([
154 overlap_summary, sidewall_summary, sideoverlap_summary
155 ])
@@ -242,8 +242,8 @@160 cell_extraction_results: Dict[CellName, CellExtractionResults] = field(default_factory=dict)
162 def summarize(self) -> ExtractionSummary:
-163 subsummaries = [s.summarize() for s in self.cell_extraction_results.values()]
-164 return ExtractionSummary.merged(subsummaries)
+163 subsummaries = [s.summarize() for s in self.cell_extraction_results.values()]
+164 return ExtractionSummary.merged(subsummaries)
diff --git a/pycov/z_4810bfbf6fbd806c_extractor_py.html b/pycov/z_4810bfbf6fbd806c_extractor_py.html index 2d309c8d..c40b566e 100644 --- a/pycov/z_4810bfbf6fbd806c_extractor_py.html +++ b/pycov/z_4810bfbf6fbd806c_extractor_py.html @@ -2,7 +2,7 @@ -@@ -65,7 +65,7 @@
53 pex_context: KLayoutExtractionContext,
54 tech_info: TechInfo,
55 report_path: str):
-56 self.pex_context = pex_context
-57 self.tech_info = tech_info
-58 self.report_path = report_path
+56 self.pex_context = pex_context
+57 self.tech_info = tech_info
+58 self.report_path = report_path
60 def gds_pair(self, layer_name) -> Optional[GDSPair]:
-61 gds_pair = self.tech_info.gds_pair_for_computed_layer_name.get(layer_name, None)
-62 if not gds_pair:
-63 gds_pair = self.tech_info.gds_pair_for_layer_name.get(layer_name, None)
-64 if not gds_pair:
+61 gds_pair = self.tech_info.gds_pair_for_computed_layer_name.get(layer_name, None)
+62 if not gds_pair:
+63 gds_pair = self.tech_info.gds_pair_for_layer_name.get(layer_name, None)
+64 if not gds_pair:
65 warning(f"Can't find GDS pair for layer {layer_name}")
66 return None
-67 return gds_pair
+67 return gds_pair
69 def shapes_of_net(self, layer_name: str, net: kdb.Net) -> Optional[kdb.Region]:
-70 gds_pair = self.gds_pair(layer_name=layer_name)
-71 if not gds_pair:
+70 gds_pair = self.gds_pair(layer_name=layer_name)
+71 if not gds_pair:
72 return None
-74 shapes = self.pex_context.shapes_of_net(gds_pair=gds_pair, net=net)
-75 if not shapes:
-76 debug(f"Nothing extracted for layer {layer_name}")
-77 return shapes
+74 shapes = self.pex_context.shapes_of_net(gds_pair=gds_pair, net=net)
+75 if not shapes:
+76 debug(f"Nothing extracted for layer {layer_name}")
+77 return shapes
79 def shapes_of_layer(self, layer_name: str) -> Optional[kdb.Region]:
-80 gds_pair = self.gds_pair(layer_name=layer_name)
-81 if not gds_pair:
+80 gds_pair = self.gds_pair(layer_name=layer_name)
+81 if not gds_pair:
82 return None
-84 shapes = self.pex_context.shapes_of_layer(gds_pair=gds_pair)
-85 if not shapes:
-86 debug(f"Nothing extracted for layer {layer_name}")
-87 return shapes
+84 shapes = self.pex_context.shapes_of_layer(gds_pair=gds_pair)
+85 if not shapes:
+86 debug(f"Nothing extracted for layer {layer_name}")
+87 return shapes
89 def extract(self) -> ExtractionResults:
-90 extraction_results = ExtractionResults()
+90 extraction_results = ExtractionResults()
92 # TODO: for now, we always flatten and have only 1 cell
-93 cell_name = self.pex_context.top_cell.name
-94 report = rdb.ReportDatabase(f"PEX {cell_name}")
-95 cell_extraction_result = self.extract_cell(cell_name=cell_name, report=report)
-96 extraction_results.cell_extraction_results[cell_name] = cell_extraction_result
+93 cell_name = self.pex_context.top_cell.name
+94 report = rdb.ReportDatabase(f"PEX {cell_name}")
+95 cell_extraction_result = self.extract_cell(cell_name=cell_name, report=report)
+96 extraction_results.cell_extraction_results[cell_name] = cell_extraction_result
-98 report.save(self.report_path)
+98 report.save(self.report_path)
-100 return extraction_results
+100 return extraction_results
102 def extract_cell(self,
103 cell_name: CellName,
104 report: rdb.ReportDatabase) -> CellExtractionResults:
-105 lvsdb = self.pex_context.lvsdb
-106 netlist: kdb.Netlist = lvsdb.netlist()
-107 dbu = self.pex_context.dbu
+105 lvsdb = self.pex_context.lvsdb
+106 netlist: kdb.Netlist = lvsdb.netlist()
+107 dbu = self.pex_context.dbu
-109 extraction_results = CellExtractionResults(cell_name=cell_name)
+109 extraction_results = CellExtractionResults(cell_name=cell_name)
-111 rdb_cell = report.create_cell(cell_name)
-112 rdb_cat_common = report.create_category("Common")
-113 rdb_cat_sidewall_old = report.create_category("Sidewall (legacy space_check)")
-114 rdb_cat_sidewall = report.create_category("Sidewall (EdgeNeighborhoodVisitor)")
-115 rdb_cat_overlap = report.create_category("Overlap")
-116 rdb_cat_fringe = report.create_category("Fringe / Side Overlap")
+111 rdb_cell = report.create_cell(cell_name)
+112 rdb_cat_common = report.create_category("Common")
+113 rdb_cat_sidewall_old = report.create_category("Sidewall (legacy space_check)")
+114 rdb_cat_sidewall = report.create_category("Sidewall (EdgeNeighborhoodVisitor)")
+115 rdb_cat_overlap = report.create_category("Overlap")
+116 rdb_cat_fringe = report.create_category("Fringe / Side Overlap")
-118 def rdb_output(parent_category: rdb.RdbCategory,
+118 def rdb_output(parent_category: rdb.RdbCategory,
119 category_name: str,
120 shapes: kdb.Shapes | kdb.Region | List[kdb.Edge]):
-121 rdb_cat = report.create_category(parent_category, category_name)
-122 report.create_items(rdb_cell.rdb_id(), ## TODO: if later hierarchical mode is introduced
+121 rdb_cat = report.create_category(parent_category, category_name)
+122 report.create_items(rdb_cell.rdb_id(), ## TODO: if later hierarchical mode is introduced
123 rdb_cat.rdb_id(),
124 kdb.CplxTrans(mag=dbu),
125 shapes)
-127 circuit = netlist.circuit_by_name(self.pex_context.top_cell.name)
+127 circuit = netlist.circuit_by_name(self.pex_context.top_cell.name)
128 # https://www.klayout.de/doc-qt5/code/class_Circuit.html
-129 if not circuit:
+129 if not circuit:
130 circuits = [c.name for c in netlist.each_circuit()]
131 raise Exception(f"Expected circuit called {self.pex_context.top_cell.name} in extracted netlist, "
132 f"only available circuits are: {circuits}")
134 #----------------------------------------------------------------------------------------
-135 layer2net2regions = defaultdict(lambda: defaultdict(kdb.Region))
-136 net2layer2regions = defaultdict(lambda: defaultdict(kdb.Region))
-137 layer_by_name: Dict[LayerName, process_stack_pb2.ProcessStackInfo.LayerInfo] = {}
+135 layer2net2regions = defaultdict(lambda: defaultdict(kdb.Region))
+136 net2layer2regions = defaultdict(lambda: defaultdict(kdb.Region))
+137 layer_by_name: Dict[LayerName, process_stack_pb2.ProcessStackInfo.LayerInfo] = {}
-139 layer_regions_by_name: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region)
-140 all_region = kdb.Region()
-141 regions_below_layer: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region)
-142 regions_below_and_including_layer: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region)
-143 all_layer_names: List[LayerName] = []
-144 layer_names_below: Dict[LayerName, List[LayerName]] = {}
-145 shielding_layer_names: Dict[Tuple[LayerName, LayerName], List[LayerName]] = defaultdict(list)
-146 previous_layer_name: Optional[str] = None
+139 layer_regions_by_name: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region)
+140 all_region = kdb.Region()
+141 regions_below_layer: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region)
+142 regions_below_and_including_layer: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region)
+143 all_layer_names: List[LayerName] = []
+144 layer_names_below: Dict[LayerName, List[LayerName]] = {}
+145 shielding_layer_names: Dict[Tuple[LayerName, LayerName], List[LayerName]] = defaultdict(list)
+146 previous_layer_name: Optional[str] = None
-148 substrate_region = kdb.Region()
-149 substrate_region.insert(self.pex_context.top_cell_bbox().enlarged(8.0 / dbu)) # 8 µm halo
-150 substrate_layer_name = self.tech_info.internal_substrate_layer_name
-151 layer_names_below[substrate_layer_name] = []
-152 all_layer_names.append(substrate_layer_name)
-153 layer2net2regions[substrate_layer_name][substrate_layer_name] = substrate_region
-154 net2layer2regions[substrate_layer_name][substrate_layer_name] = substrate_region
-155 layer_regions_by_name[substrate_layer_name] = substrate_region
+148 substrate_region = kdb.Region()
+149 substrate_region.insert(self.pex_context.top_cell_bbox().enlarged(8.0 / dbu)) # 8 µm halo
+150 substrate_layer_name = self.tech_info.internal_substrate_layer_name
+151 layer_names_below[substrate_layer_name] = []
+152 all_layer_names.append(substrate_layer_name)
+153 layer2net2regions[substrate_layer_name][substrate_layer_name] = substrate_region
+154 net2layer2regions[substrate_layer_name][substrate_layer_name] = substrate_region
+155 layer_regions_by_name[substrate_layer_name] = substrate_region
156 # NOTE: substrate not needed for
157 # - all_region
158 # - regions_below_layer
159 # - regions_below_and_including_layer
-161 for metal_layer in self.tech_info.process_metal_layers:
-162 layer_name = metal_layer.name
-163 gds_pair = self.gds_pair(layer_name)
-164 canonical_layer_name = self.tech_info.canonical_layer_name_by_gds_pair[gds_pair]
+161 for metal_layer in self.tech_info.process_metal_layers:
+162 layer_name = metal_layer.name
+163 gds_pair = self.gds_pair(layer_name)
+164 canonical_layer_name = self.tech_info.canonical_layer_name_by_gds_pair[gds_pair]
-166 all_layer_shapes = self.shapes_of_layer(layer_name) or kdb.Region()
-167 layer_regions_by_name[canonical_layer_name] += all_layer_shapes
+166 all_layer_shapes = self.shapes_of_layer(layer_name) or kdb.Region()
+167 layer_regions_by_name[canonical_layer_name] += all_layer_shapes
168 # NOTE: multiple LVS layers can be mapped to the same canonical name
-169 if previous_layer_name != canonical_layer_name:
-170 regions_below_layer[canonical_layer_name] += all_region
-171 layer_names_below[canonical_layer_name] = list(all_layer_names)
-172 for ln in all_layer_names:
-173 lp = (canonical_layer_name, ln)
-174 shielding_layer_names[lp] = [l for l in all_layer_names
+169 if previous_layer_name != canonical_layer_name:
+170 regions_below_layer[canonical_layer_name] += all_region
+171 layer_names_below[canonical_layer_name] = list(all_layer_names)
+172 for ln in all_layer_names:
+173 lp = (canonical_layer_name, ln)
+174 shielding_layer_names[lp] = [l for l in all_layer_names
175 if l != ln and l not in layer_names_below[ln]]
-176 shielding_layer_names[ln, canonical_layer_name] = shielding_layer_names[lp]
-177 all_layer_names.append(canonical_layer_name)
-178 all_region += all_layer_shapes
-179 regions_below_and_including_layer[canonical_layer_name] += all_region
+176 shielding_layer_names[ln, canonical_layer_name] = shielding_layer_names[lp]
+177 all_layer_names.append(canonical_layer_name)
+178 all_region += all_layer_shapes
+179 regions_below_and_including_layer[canonical_layer_name] += all_region
-181 previous_layer_name = canonical_layer_name
+181 previous_layer_name = canonical_layer_name
-183 for net in circuit.each_net():
-184 net_name = net.expanded_name()
+183 for net in circuit.each_net():
+184 net_name = net.expanded_name()
-186 shapes = self.shapes_of_net(layer_name=layer_name, net=net)
-187 if shapes:
-188 layer2net2regions[canonical_layer_name][net_name] += shapes
-189 net2layer2regions[net_name][canonical_layer_name] += shapes
-190 layer_by_name[canonical_layer_name] = metal_layer
+186 shapes = self.shapes_of_net(layer_name=layer_name, net=net)
+187 if shapes:
+188 layer2net2regions[canonical_layer_name][net_name] += shapes
+189 net2layer2regions[net_name][canonical_layer_name] += shapes
+190 layer_by_name[canonical_layer_name] = metal_layer
-192 shielded_regions_between_layers: Dict[Tuple[LayerName, LayerName], kdb.Region] = {}
-193 for top_layer_name in layer2net2regions.keys():
-194 for bot_layer_name in reversed(layer_names_below[top_layer_name]):
-195 shielded_region = kdb.Region()
-196 shielding_layers = shielding_layer_names.get((top_layer_name, bot_layer_name), None)
-197 if shielding_layers:
-198 for sl in shielding_layers:
-199 shielded_region += layer_regions_by_name[sl]
-200 shielded_region.merge()
-201 shielded_regions_between_layers[(top_layer_name, bot_layer_name)] = shielded_region
-202 shielded_regions_between_layers[(bot_layer_name, top_layer_name)] = shielded_region
-203 if shielded_region:
-204 rdb_output(rdb_cat_common, f"Shielded ({top_layer_name}-{bot_layer_name})", shielded_region)
+192 shielded_regions_between_layers: Dict[Tuple[LayerName, LayerName], kdb.Region] = {}
+193 for top_layer_name in layer2net2regions.keys():
+194 for bot_layer_name in reversed(layer_names_below[top_layer_name]):
+195 shielded_region = kdb.Region()
+196 shielding_layers = shielding_layer_names.get((top_layer_name, bot_layer_name), None)
+197 if shielding_layers:
+198 for sl in shielding_layers:
+199 shielded_region += layer_regions_by_name[sl]
+200 shielded_region.merge()
+201 shielded_regions_between_layers[(top_layer_name, bot_layer_name)] = shielded_region
+202 shielded_regions_between_layers[(bot_layer_name, top_layer_name)] = shielded_region
+203 if shielded_region:
+204 rdb_output(rdb_cat_common, f"Shielded ({top_layer_name}-{bot_layer_name})", shielded_region)
206 #----------------------------------------------------------------------------------------
-208 side_halo_um = self.tech_info.tech.process_parasitics.side_halo
-209 side_halo_dbu = int(side_halo_um / dbu) + 1 # add 1 nm to halo
+208 side_halo_um = self.tech_info.tech.process_parasitics.side_halo
+209 side_halo_dbu = int(side_halo_um / dbu) + 1 # add 1 nm to halo
-211 space_markers_by_layer_name: Dict[LayerName, kdb.Region] = {}
-212 rdb_cat_space_markers = report.create_category(rdb_cat_sidewall_old, "All Space Markers")
+211 space_markers_by_layer_name: Dict[LayerName, kdb.Region] = {}
+212 rdb_cat_space_markers = report.create_category(rdb_cat_sidewall_old, "All Space Markers")
-214 for layer_name in layer2net2regions.keys():
-215 if layer_name == substrate_layer_name:
-216 continue
+214 for layer_name in layer2net2regions.keys():
+215 if layer_name == substrate_layer_name:
+216 continue
-218 space_markers = layer_regions_by_name[layer_name].space_check(
+218 space_markers = layer_regions_by_name[layer_name].space_check(
219 d=side_halo_dbu, # min space in um
220 whole_edges=True, # whole edges
221 metrics=kdb.Metrics.Projection, # metrics
@@ -311,140 +311,140 @@229 property_constraint=kdb.Region.IgnoreProperties, # property_constraint
230 zero_distance_mode=kdb.Region.IncludeZeroDistanceWhenTouching # zero distance mode
231 )
-232 space_markers_by_layer_name[layer_name] = space_markers
-233 rdb_output(rdb_cat_space_markers, f"layer={layer_name}", space_markers)
+232 space_markers_by_layer_name[layer_name] = space_markers
+233 rdb_output(rdb_cat_space_markers, f"layer={layer_name}", space_markers)
235 #
236 # (1) OVERLAP CAPACITANCE
237 #
-238 for top_layer_name in layer2net2regions.keys():
-239 if top_layer_name == substrate_layer_name:
-240 continue
+238 for top_layer_name in layer2net2regions.keys():
+239 if top_layer_name == substrate_layer_name:
+240 continue
-242 top_net2regions = layer2net2regions.get(top_layer_name, None)
-243 if not top_net2regions:
+242 top_net2regions = layer2net2regions.get(top_layer_name, None)
+243 if not top_net2regions:
244 continue
-246 top_overlap_specs = self.tech_info.overlap_cap_by_layer_names.get(top_layer_name, None)
-247 if not top_overlap_specs:
+246 top_overlap_specs = self.tech_info.overlap_cap_by_layer_names.get(top_layer_name, None)
+247 if not top_overlap_specs:
248 warning(f"No overlap cap specified for layer top={top_layer_name}")
249 continue
-251 rdb_cat_top_layer = report.create_category(rdb_cat_overlap, f"top_layer={top_layer_name}")
+251 rdb_cat_top_layer = report.create_category(rdb_cat_overlap, f"top_layer={top_layer_name}")
-253 shapes_top_layer = layer_regions_by_name[top_layer_name]
+253 shapes_top_layer = layer_regions_by_name[top_layer_name]
-255 for bot_layer_name in reversed(layer_names_below[top_layer_name]):
-256 bot_net2regions = layer2net2regions.get(bot_layer_name, None)
-257 if not bot_net2regions:
-258 continue
+255 for bot_layer_name in reversed(layer_names_below[top_layer_name]):
+256 bot_net2regions = layer2net2regions.get(bot_layer_name, None)
+257 if not bot_net2regions:
+258 continue
-260 overlap_cap_spec = top_overlap_specs.get(bot_layer_name, None)
-261 if not overlap_cap_spec:
+260 overlap_cap_spec = top_overlap_specs.get(bot_layer_name, None)
+261 if not overlap_cap_spec:
262 warning(f"No overlap cap specified for layer top={top_layer_name}/bottom={bot_layer_name}")
263 continue
-265 rdb_cat_bot_layer = report.create_category(rdb_cat_top_layer, f"bot_layer={bot_layer_name}")
+265 rdb_cat_bot_layer = report.create_category(rdb_cat_top_layer, f"bot_layer={bot_layer_name}")
-267 shielded_region = shielded_regions_between_layers[(top_layer_name, bot_layer_name)].and_(shapes_top_layer)
-268 rdb_output(rdb_cat_bot_layer, "Shielded Between Layers Region", shielded_region)
+267 shielded_region = shielded_regions_between_layers[(top_layer_name, bot_layer_name)].and_(shapes_top_layer)
+268 rdb_output(rdb_cat_bot_layer, "Shielded Between Layers Region", shielded_region)
-270 for net_top in top_net2regions.keys():
-271 shapes_top_net: kdb.Region = top_net2regions[net_top].dup()
+270 for net_top in top_net2regions.keys():
+271 shapes_top_net: kdb.Region = top_net2regions[net_top].dup()
-273 for net_bot in bot_net2regions.keys():
-274 if net_top == net_bot:
+273 for net_bot in bot_net2regions.keys():
+274 if net_top == net_bot:
275 continue
-277 shapes_bot_net: kdb.Region = bot_net2regions[net_bot]
+277 shapes_bot_net: kdb.Region = bot_net2regions[net_bot]
-279 overlapping_shapes = shapes_top_net.__and__(shapes_bot_net)
-280 if overlapping_shapes:
-281 rdb_cat_nets = report.create_category(rdb_cat_bot_layer, f"{net_top} – {net_bot}")
-282 rdb_output(rdb_cat_nets, "Overlapping Shapes", overlapping_shapes)
+279 overlapping_shapes = shapes_top_net.__and__(shapes_bot_net)
+280 if overlapping_shapes:
+281 rdb_cat_nets = report.create_category(rdb_cat_bot_layer, f"{net_top} – {net_bot}")
+282 rdb_output(rdb_cat_nets, "Overlapping Shapes", overlapping_shapes)
-284 shielded_net_shapes = overlapping_shapes.__and__(shielded_region)
-285 rdb_output(rdb_cat_nets, "Shielded Shapes", shielded_net_shapes)
+284 shielded_net_shapes = overlapping_shapes.__and__(shielded_region)
+285 rdb_output(rdb_cat_nets, "Shielded Shapes", shielded_net_shapes)
-287 unshielded_net_shapes = overlapping_shapes - shielded_net_shapes
-288 rdb_output(rdb_cat_nets, "Unshielded Shapes", unshielded_net_shapes)
+287 unshielded_net_shapes = overlapping_shapes - shielded_net_shapes
+288 rdb_output(rdb_cat_nets, "Unshielded Shapes", unshielded_net_shapes)
-290 area_um2 = overlapping_shapes.area() * dbu**2
-291 shielded_area_um2 = shielded_net_shapes.area() * dbu**2
-292 unshielded_area_um2 = area_um2 - shielded_area_um2
-293 cap_femto = unshielded_area_um2 * overlap_cap_spec.capacitance / 1000.0
-294 shielded_cap_femto = shielded_area_um2 * overlap_cap_spec.capacitance / 1000.0
-295 info(f"(Overlap): {top_layer_name}({net_top})-{bot_layer_name}({net_bot}): "
+290 area_um2 = overlapping_shapes.area() * dbu**2
+291 shielded_area_um2 = shielded_net_shapes.area() * dbu**2
+292 unshielded_area_um2 = area_um2 - shielded_area_um2
+293 cap_femto = unshielded_area_um2 * overlap_cap_spec.capacitance / 1000.0
+294 shielded_cap_femto = shielded_area_um2 * overlap_cap_spec.capacitance / 1000.0
+295 info(f"(Overlap): {top_layer_name}({net_top})-{bot_layer_name}({net_bot}): "
296 f"Unshielded area: {unshielded_area_um2} µm^2, "
297 f"cap: {round(cap_femto, 2)} fF")
-298 if cap_femto > 0.0:
-299 ovk = OverlapKey(layer_top=top_layer_name,
+298 if cap_femto > 0.0:
+299 ovk = OverlapKey(layer_top=top_layer_name,
300 net_top=net_top,
301 layer_bot=bot_layer_name,
302 net_bot=net_bot)
-303 cap = OverlapCap(key=ovk,
+303 cap = OverlapCap(key=ovk,
304 cap_value=cap_femto,
305 shielded_area=shielded_area_um2,
306 unshielded_area=unshielded_area_um2,
307 tech_spec=overlap_cap_spec)
-308 report.create_category( # used as info text
+308 report.create_category( # used as info text
309 rdb_cat_nets,
310 f"{round(cap_femto, 3)} fF "
311 f"({round(shielded_cap_femto, 3)} fF shielded "
312 f"of total {round(cap_femto+shielded_cap_femto, 3)} fF)"
313 )
-314 extraction_results.overlap_coupling[ovk] = cap
+314 extraction_results.overlap_coupling[ovk] = cap
316 # (2) SIDEWALL CAPACITANCE
317 #
-318 for layer_name in layer2net2regions.keys():
-319 if layer_name == substrate_layer_name:
-320 continue
+318 for layer_name in layer2net2regions.keys():
+319 if layer_name == substrate_layer_name:
+320 continue
-322 sidewall_cap_spec = self.tech_info.sidewall_cap_by_layer_name.get(layer_name, None)
-323 if not sidewall_cap_spec:
+322 sidewall_cap_spec = self.tech_info.sidewall_cap_by_layer_name.get(layer_name, None)
+323 if not sidewall_cap_spec:
324 warning(f"No sidewall cap specified for layer {layer_name}")
325 continue
-327 net2regions = layer2net2regions.get(layer_name, None)
-328 if not net2regions:
+327 net2regions = layer2net2regions.get(layer_name, None)
+328 if not net2regions:
329 continue
-331 rdb_cat_sw_layer = report.create_category(rdb_cat_sidewall_old, f"layer={layer_name}")
+331 rdb_cat_sw_layer = report.create_category(rdb_cat_sidewall_old, f"layer={layer_name}")
-333 space_markers = space_markers_by_layer_name[layer_name]
+333 space_markers = space_markers_by_layer_name[layer_name]
-335 for i, net1 in enumerate(net2regions.keys()):
-336 for j, net2 in enumerate(net2regions.keys()):
-337 if i < j:
+335 for i, net1 in enumerate(net2regions.keys()):
+336 for j, net2 in enumerate(net2regions.keys()):
+337 if i < j:
339 # info(f"Sidewall on {layer_name}: Nets {net1} <-> {net2}")
-340 shapes1: kdb.Region = net2regions[net1]
-341 shapes2: kdb.Region = net2regions[net2]
+340 shapes1: kdb.Region = net2regions[net1]
+341 shapes2: kdb.Region = net2regions[net2]
-343 markers_net1 = space_markers.interacting(shapes1)
-344 sidewall_edge_pairs = markers_net1.interacting(shapes2)
+343 markers_net1 = space_markers.interacting(shapes1)
+344 sidewall_edge_pairs = markers_net1.interacting(shapes2)
-346 if not sidewall_edge_pairs:
-347 continue
+346 if not sidewall_edge_pairs:
+347 continue
-349 rdb_cat_sw_nets = report.create_category(rdb_cat_sidewall_old, f"{net1} - {net2}")
-350 rdb_output(rdb_cat_sw_nets, f"Shapes {net1}", shapes1)
-351 rdb_output(rdb_cat_sw_nets, f"Shapes {net2}", shapes2)
-352 rdb_output(rdb_cat_sw_nets, f"Markers interacting {net1}", markers_net1)
-353 rdb_output(rdb_cat_sw_nets, f"Markers interacting {net1}-{net2}", sidewall_edge_pairs)
+349 rdb_cat_sw_nets = report.create_category(rdb_cat_sidewall_old, f"{net1} - {net2}")
+350 rdb_output(rdb_cat_sw_nets, f"Shapes {net1}", shapes1)
+351 rdb_output(rdb_cat_sw_nets, f"Shapes {net2}", shapes2)
+352 rdb_output(rdb_cat_sw_nets, f"Markers interacting {net1}", markers_net1)
+353 rdb_output(rdb_cat_sw_nets, f"Markers interacting {net1}-{net2}", sidewall_edge_pairs)
-355 for idx, pair in enumerate(sidewall_edge_pairs):
-356 edge1: kdb.Edge = pair.first
-357 edge2: kdb.Edge = pair.second
+355 for idx, pair in enumerate(sidewall_edge_pairs):
+356 edge1: kdb.Edge = pair.first
+357 edge2: kdb.Edge = pair.second
359 # TODO: support non-parallel situations
360 # avg_length = (edge1.length() + edge2.length()) / 2.0
361 # avg_distance = (pair.polygon(0).perimeter() - edge1.length() - edge2.length()) / 2.0
-362 avg_length = min(edge1.length(), edge2.length())
-363 avg_distance = pair.distance()
+362 avg_length = min(edge1.length(), edge2.length())
+363 avg_distance = pair.distance()
-365 debug(f"Edge pair distance {avg_distance}, symmetric? {pair.symmetric}, "
+365 debug(f"Edge pair distance {avg_distance}, symmetric? {pair.symmetric}, "
366 f"perimeter {pair.perimeter()}, parallel? {edge1.is_parallel(edge2)}")
368 # (3) SIDEWALL CAPACITANCE
@@ -452,72 +452,72 @@370 # C = Csidewall * l * t / s
371 # C = Csidewall * l / s
-373 length_um = avg_length * dbu
-374 distance_um = avg_distance * dbu
+373 length_um = avg_length * dbu
+374 distance_um = avg_distance * dbu
376 # NOTE: this is automatically bidirectional,
377 # whereas MAGIC counts 2 sidewall contributions (one for each side of the cap)
-378 cap_femto = (length_um * sidewall_cap_spec.capacitance) / \
+378 cap_femto = (length_um * sidewall_cap_spec.capacitance) / \
379 (distance_um + sidewall_cap_spec.offset) / 1000.0
-381 rdb_output(rdb_cat_sw_nets, f"Edge Pair {idx}: {round(cap_femto, 3)} fF", pair)
+381 rdb_output(rdb_cat_sw_nets, f"Edge Pair {idx}: {round(cap_femto, 3)} fF", pair)
-383 info(f"(Sidewall) layer {layer_name}: Nets {net1} <-> {net2}: {round(cap_femto, 5)} fF")
+383 info(f"(Sidewall) layer {layer_name}: Nets {net1} <-> {net2}: {round(cap_femto, 5)} fF")
-385 swk = SidewallKey(layer=layer_name, net1=net1, net2=net2)
-386 sw_cap = SidewallCap(key=swk,
+385 swk = SidewallKey(layer=layer_name, net1=net1, net2=net2)
+386 sw_cap = SidewallCap(key=swk,
387 cap_value=cap_femto,
388 distance=distance_um,
389 length=length_um,
390 tech_spec=sidewall_cap_spec)
-391 extraction_results.sidewall_table[swk] = sw_cap
+391 extraction_results.sidewall_table[swk] = sw_cap
393 #
394 # (3) FRINGE / SIDE OVERLAP CAPACITANCE
395 #
-397 class FringeEdgeNeighborhoodVisitor(kdb.EdgeNeighborhoodVisitor):
-398 def __init__(self,
+397 class FringeEdgeNeighborhoodVisitor(kdb.EdgeNeighborhoodVisitor):
+398 def __init__(self,
399 inside_layer_name: str,
400 inside_net_name: str,
401 outside_layer_name: str,
402 child_names: List[str],
403 tech_info: TechInfo,
404 report_category: rdb.RdbCategory):
-405 self.inside_layer_name = inside_layer_name
-406 self.inside_net_name = inside_net_name
-407 self.outside_layer_name = outside_layer_name
-408 self.child_names = child_names
+405 self.inside_layer_name = inside_layer_name
+406 self.inside_net_name = inside_net_name
+407 self.outside_layer_name = outside_layer_name
+408 self.child_names = child_names
409 # NOTE: child_names[0] is the inside net (foreign)
410 # child_names[1] is the shielded net (between layers)
411 # child_names[2:] are the outside nets
-412 self.tech_info = tech_info
-413 self.report_category = report_category
+412 self.tech_info = tech_info
+413 self.report_category = report_category
415 # NOTE: overlap_cap_by_layer_names is top/bot (dict is not symmetric)
-416 self.overlap_cap_spec = tech_info.overlap_cap_by_layer_names[inside_layer_name].get(outside_layer_name, None)
-417 if not self.overlap_cap_spec:
-418 self.overlap_cap_spec = tech_info.overlap_cap_by_layer_names[outside_layer_name][inside_layer_name]
+416 self.overlap_cap_spec = tech_info.overlap_cap_by_layer_names[inside_layer_name].get(outside_layer_name, None)
+417 if not self.overlap_cap_spec:
+418 self.overlap_cap_spec = tech_info.overlap_cap_by_layer_names[outside_layer_name][inside_layer_name]
-420 self.substrate_cap_spec = tech_info.substrate_cap_by_layer_name[inside_layer_name]
-421 self.sideoverlap_cap_spec = tech_info.side_overlap_cap_by_layer_names[inside_layer_name][outside_layer_name]
+420 self.substrate_cap_spec = tech_info.substrate_cap_by_layer_name[inside_layer_name]
+421 self.sideoverlap_cap_spec = tech_info.side_overlap_cap_by_layer_names[inside_layer_name][outside_layer_name]
-423 self.sidewall_cap_spec = tech_info.sidewall_cap_by_layer_name[inside_layer_name]
+423 self.sidewall_cap_spec = tech_info.sidewall_cap_by_layer_name[inside_layer_name]
-425 self.category_name_counter: Dict[str, int] = defaultdict(int)
-426 self.sidewall_counter = 0
+425 self.category_name_counter: Dict[str, int] = defaultdict(int)
+426 self.sidewall_counter = 0
-428 def begin_polygon(self,
+428 def begin_polygon(self,
429 layout: kdb.Layout,
430 cell: kdb.Cell,
431 polygon: kdb.Polygon):
-432 debug(f"----------------------------------------")
-433 debug(f"Polygon: {polygon}")
+432 debug(f"----------------------------------------")
+433 debug(f"Polygon: {polygon}")
-435 def end_polygon(self):
-436 debug(f"End of polygon")
+435 def end_polygon(self):
+436 debug(f"End of polygon")
-438 def on_edge(self,
+438 def on_edge(self,
439 layout: kdb.Layout,
440 cell: kdb.Cell,
441 edge: kdb.Edge,
@@ -530,196 +530,196 @@449 # TODO: consider z-shielding!
-451 debug(f"inside_layer={self.inside_layer_name}, "
+451 debug(f"inside_layer={self.inside_layer_name}, "
452 f"inside_net={self.inside_net_name}, "
453 f"outside_layer={self.outside_layer_name}, "
454 f"edge = {edge}")
-456 rdb_inside_layer = report.create_category(rdb_cat_sidewall, f"layer={self.inside_layer_name}")
-457 rdb_sidewall_inside_net = report.create_category(rdb_inside_layer, f"inside={self.inside_net_name}")
+456 rdb_inside_layer = report.create_category(rdb_cat_sidewall, f"layer={self.inside_layer_name}")
+457 rdb_sidewall_inside_net = report.create_category(rdb_inside_layer, f"inside={self.inside_net_name}")
-459 for (x1, x2), polygons_by_net in neighborhood:
-460 if not polygons_by_net:
+459 for (x1, x2), polygons_by_net in neighborhood:
+460 if not polygons_by_net:
461 continue
-463 edge_interval_length = x2 - x1
-464 edge_interval_length_um = edge_interval_length * dbu
+463 edge_interval_length = x2 - x1
+464 edge_interval_length_um = edge_interval_length * dbu
-466 edge_interval_original = (self.to_original_trans(edge) *
+466 edge_interval_original = (self.to_original_trans(edge) *
467 kdb.Edge(kdb.Point(x1, 0), kdb.Point(x2, 0)))
-468 transformed_category_name = f"Edge interval {(x1, x2)}"
-469 self.category_name_counter[transformed_category_name] += 1
-470 rdb_cat_edge_interval = report.create_category(
+468 transformed_category_name = f"Edge interval {(x1, x2)}"
+469 self.category_name_counter[transformed_category_name] += 1
+470 rdb_cat_edge_interval = report.create_category(
471 self.report_category,
472 f"{transformed_category_name} ({self.category_name_counter[transformed_category_name]})"
473 )
-474 rdb_output(rdb_cat_edge_interval, f"Original Section {edge_interval_original}", edge_interval_original)
+474 rdb_output(rdb_cat_edge_interval, f"Original Section {edge_interval_original}", edge_interval_original)
-476 polygons_on_same_layer = polygons_by_net.get(1, None)
-477 shielded_region_lateral = kdb.Region()
-478 if polygons_on_same_layer:
-479 shielded_region_lateral.insert(polygons_on_same_layer)
-480 rdb_output(rdb_cat_edge_interval, 'Laterally nearby shapes',
+476 polygons_on_same_layer = polygons_by_net.get(1, None)
+477 shielded_region_lateral = kdb.Region()
+478 if polygons_on_same_layer:
+479 shielded_region_lateral.insert(polygons_on_same_layer)
+480 rdb_output(rdb_cat_edge_interval, 'Laterally nearby shapes',
481 kdb.Region([self.to_original_trans(edge) * p for p in shielded_region_lateral]))
483 # NOTE: first lateral nearby shape blocks everything beyond (like sidewall situation) up to halo
-484 def distance_near(p: kdb.Polygon) -> float:
-485 bbox: kdb.Box = p.bbox()
+484 def distance_near(p: kdb.Polygon) -> float:
+485 bbox: kdb.Box = p.bbox()
-487 if not p.is_box():
+487 if not p.is_box():
488 warning(f"Side overlap, outside polygon {p} is not a box. "
489 f"Currently, only boxes are supported, will be using bounding box {bbox}")
490 ## distance_near = (bbox.p1.y + bbox.p2.y) / 2.0
-491 distance_near = min(bbox.p1.y, bbox.p2.y)
-492 if distance_near < 0:
+491 distance_near = min(bbox.p1.y, bbox.p2.y)
+492 if distance_near < 0:
493 distance_near = 0
-494 return distance_near
+494 return distance_near
-496 nearest_lateral_shape = (math.inf, polygons_on_same_layer[0])
-497 for p in polygons_on_same_layer:
-498 dnear = distance_near(p)
-499 if dnear < nearest_lateral_shape[0]:
-500 nearest_lateral_shape = (dnear, p)
+496 nearest_lateral_shape = (math.inf, polygons_on_same_layer[0])
+497 for p in polygons_on_same_layer:
+498 dnear = distance_near(p)
+499 if dnear < nearest_lateral_shape[0]:
+500 nearest_lateral_shape = (dnear, p)
-502 rdb_output(rdb_cat_edge_interval, 'Closest nearby shape',
+502 rdb_output(rdb_cat_edge_interval, 'Closest nearby shape',
503 kdb.Region(self.to_original_trans(edge) * nearest_lateral_shape[1]))
505 # NOTE: this method is always called for a single nearest edge (line), so the
506 # polygons have 4 points.
507 # Polygons points are sorted clockwise, so the edge
508 # that goes from right-to-left is the nearest edge
-509 nearby_opposing_edge = [e for e in nearest_lateral_shape[1].each_edge() if e.d().x < 0][-1]
-510 nearby_opposing_edge_trans = self.to_original_trans(edge) * nearby_opposing_edge
+509 nearby_opposing_edge = [e for e in nearest_lateral_shape[1].each_edge() if e.d().x < 0][-1]
+510 nearby_opposing_edge_trans = self.to_original_trans(edge) * nearby_opposing_edge
-512 opposing_net = '<unknown>'
+512 opposing_net = '<unknown>'
513 # find the opposing net
-514 for other_net, region in layer2net2regions[self.inside_layer_name].items():
-515 if other_net == self.inside_net_name:
-516 continue
-517 if region.interacting(nearby_opposing_edge_trans).count() >= 1:
+514 for other_net, region in layer2net2regions[self.inside_layer_name].items():
+515 if other_net == self.inside_net_name:
+516 continue
+517 if region.interacting(nearby_opposing_edge_trans).count() >= 1:
518 # we found the other net!
-519 opposing_net = other_net
-520 break
+519 opposing_net = other_net
+520 break
-522 rdb_output(rdb_cat_edge_interval,
+522 rdb_output(rdb_cat_edge_interval,
523 f"Closest nearby edge (net {opposing_net})", [nearby_opposing_edge_trans])
-525 sidewall_edge_pair = [nearby_opposing_edge_trans, edge_interval_original]
-526 distance_um = nearest_lateral_shape[0] * dbu
-527 sidewall_cap_femto = (edge_interval_length_um * self.sidewall_cap_spec.capacitance) / \
+525 sidewall_edge_pair = [nearby_opposing_edge_trans, edge_interval_original]
+526 distance_um = nearest_lateral_shape[0] * dbu
+527 sidewall_cap_femto = (edge_interval_length_um * self.sidewall_cap_spec.capacitance) / \
528 (distance_um + self.sidewall_cap_spec.offset) / 1000.0 / 2.0
-530 rdb_sidewall_outside_net = report.create_category(rdb_sidewall_inside_net,
+530 rdb_sidewall_outside_net = report.create_category(rdb_sidewall_inside_net,
531 f"outside={opposing_net}")
-532 self.sidewall_counter += 1
-533 rdb_output(rdb_sidewall_outside_net,
+532 self.sidewall_counter += 1
+533 rdb_output(rdb_sidewall_outside_net,
534 f"#{self.sidewall_counter}: "
535 f"len {round(edge_interval_length_um, 3)} µm, distance {round(distance_um, 3)} µm, "
536 f"{round(sidewall_cap_femto, 3)} fF",
537 sidewall_edge_pair)
-539 nearby_shield = kdb.Polygon([nearby_opposing_edge.p1,
+539 nearby_shield = kdb.Polygon([nearby_opposing_edge.p1,
540 nearby_opposing_edge.p2,
541 kdb.Point(x1, side_halo_dbu),
542 kdb.Point(x2, side_halo_dbu)])
-544 rdb_output(rdb_cat_edge_interval, 'Nearby shield',
+544 rdb_output(rdb_cat_edge_interval, 'Nearby shield',
545 kdb.Region(self.to_original_trans(edge) * nearby_shield))
-547 shielded_region_between = kdb.Region()
-548 shielded_polygons = polygons_by_net.get(2, None) # shielded from layers between
-549 if shielded_polygons:
-550 shielded_region_between.insert(shielded_polygons)
+547 shielded_region_between = kdb.Region()
+548 shielded_polygons = polygons_by_net.get(2, None) # shielded from layers between
+549 if shielded_polygons:
+550 shielded_region_between.insert(shielded_polygons)
-552 for net_index, polygons in polygons_by_net.items():
-553 if net_index == 0: # laterally shielded
+552 for net_index, polygons in polygons_by_net.items():
+553 if net_index == 0: # laterally shielded
554 continue
-555 elif net_index == 1: # ignore "shielded"
-556 continue
+555 elif net_index == 1: # ignore "shielded"
+556 continue
-558 if not polygons:
+558 if not polygons:
559 continue
-561 unshielded_region: kdb.Region = kdb.Region(polygons) - shielded_region_between
-562 if not unshielded_region:
-563 continue
+561 unshielded_region: kdb.Region = kdb.Region(polygons) - shielded_region_between
+562 if not unshielded_region:
+563 continue
-565 net_name = self.child_names[net_index]
-566 rdb_cat_outside_net = report.create_category(rdb_cat_edge_interval,
+565 net_name = self.child_names[net_index]
+566 rdb_cat_outside_net = report.create_category(rdb_cat_edge_interval,
567 f"outside_net={net_name}")
-569 rdb_output(rdb_cat_outside_net, 'Unshielded',
+569 rdb_output(rdb_cat_outside_net, 'Unshielded',
570 kdb.Region([self.to_original_trans(edge) * p for p in unshielded_region]))
-572 for p in unshielded_region:
-573 bbox: kdb.Box = p.bbox()
+572 for p in unshielded_region:
+573 bbox: kdb.Box = p.bbox()
-575 if not p.is_box():
+575 if not p.is_box():
576 warning(f"Side overlap, outside polygon {p} is not a box. "
577 f"Currently, only boxes are supported, will be using bounding box {bbox}")
-578 distance_near = bbox.p1.y #+ 1
-579 if distance_near < 0:
-580 distance_near = 0
-581 distance_far = bbox.p2.y #- 2
-582 if distance_far < 0:
+578 distance_near = bbox.p1.y #+ 1
+579 if distance_near < 0:
+580 distance_near = 0
+581 distance_far = bbox.p2.y #- 2
+582 if distance_far < 0:
583 distance_far = 0
-584 try:
-585 assert distance_near >= 0
-586 assert distance_far >= distance_near
+584 try:
+585 assert distance_near >= 0
+586 assert distance_far >= distance_near
587 except AssertionError:
588 print()
589 raise
-591 if distance_far == distance_near:
-592 continue
+591 if distance_far == distance_near:
+592 continue
-594 distance_near_um = distance_near * dbu
-595 distance_far_um = distance_far * dbu
+594 distance_near_um = distance_near * dbu
+595 distance_far_um = distance_far * dbu
597 # NOTE: overlap scaling is 1/50 (see MAGIC ExtTech)
-598 alpha_scale_factor = 0.02 * 0.01 * 0.5 * 200.0
-599 alpha_c = self.overlap_cap_spec.capacitance * alpha_scale_factor
+598 alpha_scale_factor = 0.02 * 0.01 * 0.5 * 200.0
+599 alpha_c = self.overlap_cap_spec.capacitance * alpha_scale_factor
601 # see Magic ExtCouple.c L1164
-602 cnear = (2.0 / math.pi) * math.atan(alpha_c * distance_near_um)
-603 cfar = (2.0 / math.pi) * math.atan(alpha_c * distance_far_um)
+602 cnear = (2.0 / math.pi) * math.atan(alpha_c * distance_near_um)
+603 cfar = (2.0 / math.pi) * math.atan(alpha_c * distance_far_um)
605 # "cfrac" is the fractional portion of the fringe cap seen
606 # by tile tp along its length. This is independent of the
607 # portion of the boundary length that tile tp occupies.
-608 cfrac = cfar - cnear
+608 cfrac = cfar - cnear
610 # The fringe portion extracted from the substrate will be
611 # different than the portion added to the coupling layer.
612 sfrac: float
614 # see Magic ExtCouple.c L1198
-615 alpha_s = self.substrate_cap_spec.area_capacitance / alpha_scale_factor
-616 if alpha_s != alpha_c:
-617 snear = (2.0 / math.pi) * math.atan(alpha_s * distance_near_um)
-618 sfar = (2.0 / math.pi) * math.atan(alpha_s * distance_far_um)
-619 sfrac = sfar - snear
+615 alpha_s = self.substrate_cap_spec.area_capacitance / alpha_scale_factor
+616 if alpha_s != alpha_c:
+617 snear = (2.0 / math.pi) * math.atan(alpha_s * distance_near_um)
+618 sfar = (2.0 / math.pi) * math.atan(alpha_s * distance_far_um)
+619 sfrac = sfar - snear
620 else:
621 sfrac = cfrac
-623 if outside_layer_name == substrate_layer_name:
-624 cfrac = sfrac
+623 if outside_layer_name == substrate_layer_name:
+624 cfrac = sfrac
-626 cap_femto = (cfrac * edge_interval_length_um *
+626 cap_femto = (cfrac * edge_interval_length_um *
627 self.sideoverlap_cap_spec.capacitance / 1000.0)
-628 if cap_femto > 0.0:
-629 report.create_category(rdb_cat_outside_net, f"{round(cap_femto, 3)} fF") # used as info text
+628 if cap_femto > 0.0:
+629 report.create_category(rdb_cat_outside_net, f"{round(cap_femto, 3)} fF") # used as info text
-631 sok = SideOverlapKey(layer_inside=self.inside_layer_name,
+631 sok = SideOverlapKey(layer_inside=self.inside_layer_name,
632 net_inside=self.inside_net_name,
633 layer_outside=self.outside_layer_name,
634 net_outside=net_name)
-635 sov = extraction_results.sideoverlap_table.get(sok, None)
-636 if sov:
-637 sov.cap_value += cap_femto
+635 sov = extraction_results.sideoverlap_table.get(sok, None)
+636 if sov:
+637 sov.cap_value += cap_femto
638 else:
-639 sov = SideOverlapCap(key=sok, cap_value=cap_femto)
-640 extraction_results.sideoverlap_table[sok] = sov
+639 sov = SideOverlapCap(key=sok, cap_value=cap_femto)
+640 extraction_results.sideoverlap_table[sok] = sov
642 # efflength = (cfrac - sov.so_coupfrac) * (double) length;
643 # cap += e->ec_cap * efflength;
@@ -733,63 +733,63 @@652 # TODO: fringe portion extracted from substrate
-654 for inside_layer_name in layer2net2regions.keys():
-655 if inside_layer_name == substrate_layer_name:
-656 continue
+654 for inside_layer_name in layer2net2regions.keys():
+655 if inside_layer_name == substrate_layer_name:
+656 continue
-658 inside_net2regions = layer2net2regions.get(inside_layer_name, None)
-659 if not inside_net2regions:
+658 inside_net2regions = layer2net2regions.get(inside_layer_name, None)
+659 if not inside_net2regions:
660 continue
-662 inside_fringe_specs = self.tech_info.side_overlap_cap_by_layer_names.get(inside_layer_name, None)
-663 if not inside_fringe_specs:
+662 inside_fringe_specs = self.tech_info.side_overlap_cap_by_layer_names.get(inside_layer_name, None)
+663 if not inside_fringe_specs:
664 warning(f"No fringe / side overlap cap specified for layer inside={inside_layer_name}")
665 continue
-667 shapes_inside_layer = layer_regions_by_name[inside_layer_name]
-668 fringe_halo_inside = shapes_inside_layer.sized(side_halo_dbu) - shapes_inside_layer
+667 shapes_inside_layer = layer_regions_by_name[inside_layer_name]
+668 fringe_halo_inside = shapes_inside_layer.sized(side_halo_dbu) - shapes_inside_layer
-670 rdb_cat_inside_layer = report.create_category(rdb_cat_fringe, f"inside_layer={inside_layer_name}")
-671 rdb_output(rdb_cat_inside_layer, "fringe_halo_inside", fringe_halo_inside)
+670 rdb_cat_inside_layer = report.create_category(rdb_cat_fringe, f"inside_layer={inside_layer_name}")
+671 rdb_output(rdb_cat_inside_layer, "fringe_halo_inside", fringe_halo_inside)
673 # Side Overlap: metal <-> metal (additionally, substrate)
-674 for outside_layer_name in layer2net2regions.keys():
-675 if inside_layer_name == outside_layer_name:
-676 continue
+674 for outside_layer_name in layer2net2regions.keys():
+675 if inside_layer_name == outside_layer_name:
+676 continue
-678 outside_net2regions = layer2net2regions.get(outside_layer_name, None)
-679 if not outside_net2regions:
+678 outside_net2regions = layer2net2regions.get(outside_layer_name, None)
+679 if not outside_net2regions:
680 continue
-682 cap_spec = inside_fringe_specs.get(outside_layer_name, None)
-683 if not cap_spec:
+682 cap_spec = inside_fringe_specs.get(outside_layer_name, None)
+683 if not cap_spec:
684 warning(f"No side overlap cap specified for "
685 f"layer inside={inside_layer_name}/outside={outside_layer_name}")
686 continue
-688 shapes_outside_layer = layer_regions_by_name[outside_layer_name]
-689 if not shapes_outside_layer:
+688 shapes_outside_layer = layer_regions_by_name[outside_layer_name]
+689 if not shapes_outside_layer:
690 continue
-692 shapes_outside_layer_within_halo = shapes_outside_layer.__and__(fringe_halo_inside)
-693 if not shapes_outside_layer_within_halo:
-694 continue
+692 shapes_outside_layer_within_halo = shapes_outside_layer.__and__(fringe_halo_inside)
+693 if not shapes_outside_layer_within_halo:
+694 continue
-696 rdb_cat_outside_layer = report.create_category(rdb_cat_inside_layer,
+696 rdb_cat_outside_layer = report.create_category(rdb_cat_inside_layer,
697 f"outside_layer={outside_layer_name}")
-699 shielded_regions_between = shielded_regions_between_layers[(inside_layer_name, outside_layer_name)]
-700 rdb_output(rdb_cat_outside_layer, 'Shielded between layers', shielded_regions_between)
+699 shielded_regions_between = shielded_regions_between_layers[(inside_layer_name, outside_layer_name)]
+700 rdb_output(rdb_cat_outside_layer, 'Shielded between layers', shielded_regions_between)
-702 for net_inside in inside_net2regions.keys():
-703 shapes_inside_net: kdb.Region = inside_net2regions[net_inside]
-704 if not shapes_inside_net:
+702 for net_inside in inside_net2regions.keys():
+703 shapes_inside_net: kdb.Region = inside_net2regions[net_inside]
+704 if not shapes_inside_net:
705 continue
-707 rdb_cat_inside_net = report.create_category(rdb_cat_outside_layer,
+707 rdb_cat_inside_net = report.create_category(rdb_cat_outside_layer,
708 f"inside_net={net_inside}")
-710 visitor = FringeEdgeNeighborhoodVisitor(
+710 visitor = FringeEdgeNeighborhoodVisitor(
711 inside_layer_name=inside_layer_name,
712 inside_net_name=net_inside,
713 outside_layer_name=outside_layer_name,
@@ -799,16 +799,16 @@717 report_category=rdb_cat_inside_net
718 )
-720 nearby_shapes = shapes_inside_layer - shapes_inside_net
+720 nearby_shapes = shapes_inside_layer - shapes_inside_net
721 # children = [kdb.CompoundRegionOperationNode.new_secondary(shapes_inside_net),
-722 children = [kdb.CompoundRegionOperationNode.new_foreign(),
+722 children = [kdb.CompoundRegionOperationNode.new_foreign(),
723 kdb.CompoundRegionOperationNode.new_secondary(nearby_shapes),
724 kdb.CompoundRegionOperationNode.new_secondary(shielded_regions_between)] + \
725 [kdb.CompoundRegionOperationNode.new_secondary(region)
726 for net, region in list(outside_net2regions.items())
727 if net != net_inside]
-729 node = kdb.CompoundRegionOperationNode.new_edge_neighborhood(
+729 node = kdb.CompoundRegionOperationNode.new_edge_neighborhood(
730 children,
731 visitor,
732 0, # bext
@@ -817,12 +817,12 @@735 side_halo_dbu # dout
736 )
-738 shapes_inside_net.complex_op(node)
+738 shapes_inside_net.complex_op(node)
-740 for so in extraction_results.sideoverlap_table.values():
-741 info(so)
+740 for so in extraction_results.sideoverlap_table.values():
+741 info(so)
-743 return extraction_results
+743 return extraction_results
diff --git a/pycov/z_5f30060c77e65d78_lvs_runner_test_py.html b/pycov/z_5f30060c77e65d78_lvs_runner_test_py.html index 384e4461..c147f95d 100644 --- a/pycov/z_5f30060c77e65d78_lvs_runner_test_py.html +++ b/pycov/z_5f30060c77e65d78_lvs_runner_test_py.html @@ -65,7 +65,7 @@@@ -65,7 +65,7 @@
62 return [c for c in choices if c in self.included]
64 def is_included(self, choice: str) -> bool:
-65 if self.has_none:
+65 if self.has_none:
66 return choice in self.included
-67 if self.has_all:
-68 return choice not in self.excluded
+67 if self.has_all:
+68 return choice not in self.excluded
69 return False
@@ -65,7 +65,7 @@
56 k_void: float = 3.5,
57 delaunay_amax: float = 0.0,
58 delaunay_b: float = 1.0):
-59 self.pex_context = pex_context
-60 self.tech_info = tech_info
-61 self.k_void = k_void
-62 self.delaunay_amax = delaunay_amax
-63 self.delaunay_b = delaunay_b
+59 self.pex_context = pex_context
+60 self.tech_info = tech_info
+61 self.k_void = k_void
+62 self.delaunay_amax = delaunay_amax
+63 self.delaunay_b = delaunay_b
65 @cached_property
66 def dbu(self) -> float:
-67 return self.pex_context.dbu
+67 return self.pex_context.dbu
69 def gds_pair(self, layer_name) -> Optional[GDSPair]:
-70 gds_pair = self.tech_info.gds_pair_for_computed_layer_name.get(layer_name, None)
-71 if not gds_pair:
-72 gds_pair = self.tech_info.gds_pair_for_layer_name.get(layer_name, None)
-73 if not gds_pair:
+70 gds_pair = self.tech_info.gds_pair_for_computed_layer_name.get(layer_name, None)
+71 if not gds_pair:
+72 gds_pair = self.tech_info.gds_pair_for_layer_name.get(layer_name, None)
+73 if not gds_pair:
74 warning(f"Can't find GDS pair for layer {layer_name}")
75 return None
-76 return gds_pair
+76 return gds_pair
78 def shapes_of_net(self, layer_name: str, net: kdb.Net) -> Optional[kdb.Region]:
-79 gds_pair = self.gds_pair(layer_name=layer_name)
-80 if not gds_pair:
+79 gds_pair = self.gds_pair(layer_name=layer_name)
+80 if not gds_pair:
81 return None
-83 shapes = self.pex_context.shapes_of_net(gds_pair=gds_pair, net=net)
-84 if not shapes:
-85 debug(f"Nothing extracted for layer {layer_name}")
-86 return shapes
+83 shapes = self.pex_context.shapes_of_net(gds_pair=gds_pair, net=net)
+84 if not shapes:
+85 debug(f"Nothing extracted for layer {layer_name}")
+86 return shapes
88 def shapes_of_layer(self, layer_name: str) -> Optional[kdb.Region]:
-89 gds_pair = self.gds_pair(layer_name=layer_name)
-90 if not gds_pair:
+89 gds_pair = self.gds_pair(layer_name=layer_name)
+90 if not gds_pair:
91 return None
-93 shapes = self.pex_context.shapes_of_layer(gds_pair=gds_pair)
-94 if not shapes:
-95 debug(f"Nothing extracted for layer {layer_name}")
-96 return shapes
+93 shapes = self.pex_context.shapes_of_layer(gds_pair=gds_pair)
+94 if not shapes:
+95 debug(f"Nothing extracted for layer {layer_name}")
+96 return shapes
98 def top_cell_bbox(self) -> kdb.Box:
-99 return self.pex_context.top_cell_bbox()
+99 return self.pex_context.top_cell_bbox()
101 def build(self) -> FasterCapModelGenerator:
-102 lvsdb = self.pex_context.lvsdb
-103 netlist: kdb.Netlist = lvsdb.netlist()
+102 lvsdb = self.pex_context.lvsdb
+103 netlist: kdb.Netlist = lvsdb.netlist()
-105 def format_terminal(t: kdb.NetTerminalRef) -> str:
+105 def format_terminal(t: kdb.NetTerminalRef) -> str:
106 td = t.terminal_def()
107 d = t.device()
108 return f"{d.expanded_name()}/{td.name}/{td.description}"
-110 model_builder = FasterCapModelBuilder(
+110 model_builder = FasterCapModelBuilder(
111 dbu=self.dbu,
112 k_void=self.k_void,
113 delaunay_amax=self.delaunay_amax, # test/compare with smaller, e.g. 0.05 => more triangles
114 delaunay_b=self.delaunay_b # test/compare with 1.0 => more triangles at edges
115 )
-117 fox_layer = self.tech_info.field_oxide_layer
+117 fox_layer = self.tech_info.field_oxide_layer
-119 model_builder.add_material(name=fox_layer.name, k=fox_layer.field_oxide_layer.dielectric_k)
-120 for diel_name, diel_k in self.tech_info.dielectric_by_name.items():
-121 model_builder.add_material(name=diel_name, k=diel_k)
+119 model_builder.add_material(name=fox_layer.name, k=fox_layer.field_oxide_layer.dielectric_k)
+120 for diel_name, diel_k in self.tech_info.dielectric_by_name.items():
+121 model_builder.add_material(name=diel_name, k=diel_k)
-123 circuit = netlist.circuit_by_name(self.pex_context.top_cell.name)
+123 circuit = netlist.circuit_by_name(self.pex_context.top_cell.name)
124 # https://www.klayout.de/doc-qt5/code/class_Circuit.html
-125 if not circuit:
+125 if not circuit:
126 circuits = [c.name for c in netlist.each_circuit()]
127 raise Exception(f"Expected circuit called {self.pex_context.top_cell.name} in extracted netlist, "
128 f"only available circuits are: {circuits}")
-130 diffusion_regions: List[kdb.Region] = []
+130 diffusion_regions: List[kdb.Region] = []
-132 for net in circuit.each_net():
+132 for net in circuit.each_net():
133 # https://www.klayout.de/doc-qt5/code/class_Net.html
-134 debug(f"Net name={net.name}, expanded_name={net.expanded_name()}, pin_count={net.pin_count()}, "
+134 debug(f"Net name={net.name}, expanded_name={net.expanded_name()}, pin_count={net.pin_count()}, "
135 f"is_floating={net.is_floating()}, is_passive={net.is_passive()}, "
136 f"terminals={list(map(lambda t: format_terminal(t), net.each_terminal()))}")
-138 net_name = net.expanded_name()
+138 net_name = net.expanded_name()
-140 for metal_layer in self.tech_info.process_metal_layers:
-141 metal_layer_name = metal_layer.name
-142 metal_layer = metal_layer.metal_layer
+140 for metal_layer in self.tech_info.process_metal_layers:
+141 metal_layer_name = metal_layer.name
+142 metal_layer = metal_layer.metal_layer
-144 metal_z_bottom = metal_layer.height
-145 metal_z_top = metal_z_bottom + metal_layer.thickness
+144 metal_z_bottom = metal_layer.height
+145 metal_z_top = metal_z_bottom + metal_layer.thickness
-147 shapes = self.shapes_of_net(layer_name=metal_layer_name, net=net)
-148 if shapes:
-149 if shapes.count() >= 1:
-150 info(f"Conductor {net_name}, metal {metal_layer_name}, "
+147 shapes = self.shapes_of_net(layer_name=metal_layer_name, net=net)
+148 if shapes:
+149 if shapes.count() >= 1:
+150 info(f"Conductor {net_name}, metal {metal_layer_name}, "
151 f"z={metal_layer.height}, height={metal_layer.thickness}")
-152 model_builder.add_conductor(net_name=net_name,
+152 model_builder.add_conductor(net_name=net_name,
153 layer=shapes,
154 z=metal_layer.height,
155 height=metal_layer.thickness)
-157 if metal_layer.HasField('contact_above'):
-158 contact = metal_layer.contact_above
-159 shapes = self.shapes_of_net(layer_name=contact.name, net=net)
-160 if shapes and not shapes.is_empty():
+157 if metal_layer.HasField('contact_above'):
+158 contact = metal_layer.contact_above
+159 shapes = self.shapes_of_net(layer_name=contact.name, net=net)
+160 if shapes and not shapes.is_empty():
161 info(f"Conductor {net_name}, via {contact.name}, "
162 f"z={metal_z_top}, height={contact.thickness}")
163 model_builder.add_conductor(net_name=net_name,
@@ -255,11 +255,11 @@173 # TODO: add stuff
175 # DIFF / TAP
-176 for diffusion_layer in self.tech_info.process_diffusion_layers:
-177 diffusion_layer_name = diffusion_layer.name
-178 diffusion_layer = diffusion_layer.diffusion_layer
-179 shapes = self.shapes_of_net(layer_name=diffusion_layer_name, net=net)
-180 if shapes and not shapes.is_empty():
+176 for diffusion_layer in self.tech_info.process_diffusion_layers:
+177 diffusion_layer_name = diffusion_layer.name
+178 diffusion_layer = diffusion_layer.diffusion_layer
+179 shapes = self.shapes_of_net(layer_name=diffusion_layer_name, net=net)
+180 if shapes and not shapes.is_empty():
181 diffusion_regions.append(shapes)
182 info(f"Diffusion {net_name}, layer {diffusion_layer_name}, "
183 f"z={0}, height={0.1}")
@@ -268,9 +268,9 @@186 z=0, # TODO
187 height=0.1) # TODO: diffusion_layer.height
-189 contact = diffusion_layer.contact_above
-190 shapes = self.shapes_of_net(layer_name=contact.name, net=net)
-191 if shapes and not shapes.is_empty():
+189 contact = diffusion_layer.contact_above
+190 shapes = self.shapes_of_net(layer_name=contact.name, net=net)
+191 if shapes and not shapes.is_empty():
192 info(f"Diffusion {net_name}, contact {contact.name}, "
193 f"z={0}, height={contact.thickness}")
194 model_builder.add_conductor(net_name=net_name,
@@ -278,24 +278,24 @@196 z=0.0,
197 height=contact.thickness)
-199 enlarged_top_cell_bbox = self.top_cell_bbox().enlarged(math.floor(8 / self.dbu)) # 8µm fringe halo
+199 enlarged_top_cell_bbox = self.top_cell_bbox().enlarged(math.floor(8 / self.dbu)) # 8µm fringe halo
201 #
202 # global substrate block below everything. independent of nets!
203 #
-205 substrate_layer = self.tech_info.process_substrate_layer.substrate_layer
-206 substrate_region = kdb.Region()
+205 substrate_layer = self.tech_info.process_substrate_layer.substrate_layer
+206 substrate_region = kdb.Region()
-208 substrate_block = enlarged_top_cell_bbox.dup()
-209 substrate_region.insert(substrate_block)
+208 substrate_block = enlarged_top_cell_bbox.dup()
+209 substrate_region.insert(substrate_block)
-211 diffusion_margin = math.floor(1 / self.dbu) # 1 µm
-212 for d in diffusion_regions:
+211 diffusion_margin = math.floor(1 / self.dbu) # 1 µm
+212 for d in diffusion_regions:
213 substrate_region -= d.sized(diffusion_margin)
-214 info(f"Substrate VSUBS, "
+214 info(f"Substrate VSUBS, "
215 f"z={0 - substrate_layer.height - substrate_layer.thickness}, height={substrate_layer.thickness}")
-216 model_builder.add_conductor(net_name="VSUBS",
+216 model_builder.add_conductor(net_name="VSUBS",
217 layer=substrate_region,
218 z=0 - substrate_layer.height - substrate_layer.thickness,
219 height=substrate_layer.thickness)
@@ -304,49 +304,49 @@222 # add dielectrics
223 #
-225 fox_region = kdb.Region()
-226 fox_block = enlarged_top_cell_bbox.dup()
-227 fox_region.insert(fox_block)
+225 fox_region = kdb.Region()
+226 fox_block = enlarged_top_cell_bbox.dup()
+227 fox_region.insert(fox_block)
229 # field oxide goes from substrate/diff/well up to below the gate-poly
-230 gate_poly_height = self.tech_info.gate_poly_layer.metal_layer.height
-231 fox_z = 0
-232 fox_height = gate_poly_height - fox_z
-233 info(f"Simple dielectric (field oxide) {fox_layer.name}: "
+230 gate_poly_height = self.tech_info.gate_poly_layer.metal_layer.height
+231 fox_z = 0
+232 fox_height = gate_poly_height - fox_z
+233 info(f"Simple dielectric (field oxide) {fox_layer.name}: "
234 f"z={fox_z}, height={fox_height}")
-235 model_builder.add_dielectric(material_name=fox_layer.name,
+235 model_builder.add_dielectric(material_name=fox_layer.name,
236 layer=fox_region,
237 z=fox_z,
238 height=fox_height)
-240 for metal_layer in self.tech_info.process_metal_layers:
-241 metal_layer_name = metal_layer.name
-242 metal_layer = metal_layer.metal_layer
+240 for metal_layer in self.tech_info.process_metal_layers:
+241 metal_layer_name = metal_layer.name
+242 metal_layer = metal_layer.metal_layer
-244 metal_z_bottom = metal_layer.height
+244 metal_z_bottom = metal_layer.height
-246 extracted_shapes = self.shapes_of_layer(layer_name=metal_layer_name)
+246 extracted_shapes = self.shapes_of_layer(layer_name=metal_layer_name)
-248 sidewall_region: Optional[kdb.Region] = None
-249 sidewall_height = 0
+248 sidewall_region: Optional[kdb.Region] = None
+249 sidewall_height = 0
-251 no_metal_region: Optional[kdb.Region] = None
-252 no_metal_height = 0
+251 no_metal_region: Optional[kdb.Region] = None
+252 no_metal_height = 0
254 #
255 # add sidewall dielectrics
256 #
-257 if extracted_shapes:
-258 sidewall_height = 0
-259 sidewall_region = extracted_shapes
-260 sidewallee = metal_layer_name
+257 if extracted_shapes:
+258 sidewall_height = 0
+259 sidewall_region = extracted_shapes
+260 sidewallee = metal_layer_name
-262 while True:
-263 sidewall = self.tech_info.sidewall_dielectric_layer(sidewallee)
-264 if not sidewall:
-265 break
-266 match sidewall.layer_type:
-267 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
+262 while True:
+263 sidewall = self.tech_info.sidewall_dielectric_layer(sidewallee)
+264 if not sidewall:
+265 break
+266 match sidewall.layer_type:
+267 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
268 d = math.floor(sidewall.sidewall_dielectric_layer.width_outside_sidewall / self.dbu)
269 sidewall_region = sidewall_region.sized(d)
270 h_delta = sidewall.sidewall_dielectric_layer.height_above_metal or metal_layer.thickness
@@ -359,67 +359,67 @@277 z=metal_layer.height,
278 height=sidewall_height)
-280 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC:
-281 conf_diel = sidewall.conformal_dielectric_layer
-282 d = math.floor(conf_diel.thickness_sidewall / self.dbu)
-283 sidewall_region = sidewall_region.sized(d)
-284 h_delta = metal_layer.thickness + conf_diel.thickness_over_metal
-285 sidewall_height += h_delta
-286 info(f"Conformal dielectric (sidewall) {sidewall.name}: "
+280 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC:
+281 conf_diel = sidewall.conformal_dielectric_layer
+282 d = math.floor(conf_diel.thickness_sidewall / self.dbu)
+283 sidewall_region = sidewall_region.sized(d)
+284 h_delta = metal_layer.thickness + conf_diel.thickness_over_metal
+285 sidewall_height += h_delta
+286 info(f"Conformal dielectric (sidewall) {sidewall.name}: "
287 f"z={metal_layer.height}, height={sidewall_height}")
-288 model_builder.add_dielectric(material_name=sidewall.name,
+288 model_builder.add_dielectric(material_name=sidewall.name,
289 layer=sidewall_region,
290 z=metal_layer.height,
291 height=sidewall_height)
-292 if conf_diel.thickness_where_no_metal > 0.0:
-293 no_metal_block = enlarged_top_cell_bbox.dup()
-294 no_metal_region = kdb.Region()
-295 no_metal_region.insert(no_metal_block)
-296 no_metal_region -= sidewall_region
-297 no_metal_height = conf_diel.thickness_where_no_metal
-298 info(f"Conformal dielectric (where no metal) {sidewall.name}: "
+292 if conf_diel.thickness_where_no_metal > 0.0:
+293 no_metal_block = enlarged_top_cell_bbox.dup()
+294 no_metal_region = kdb.Region()
+295 no_metal_region.insert(no_metal_block)
+296 no_metal_region -= sidewall_region
+297 no_metal_height = conf_diel.thickness_where_no_metal
+298 info(f"Conformal dielectric (where no metal) {sidewall.name}: "
299 f"z={metal_layer.height}, height={no_metal_height}")
-300 model_builder.add_dielectric(material_name=sidewall.name,
+300 model_builder.add_dielectric(material_name=sidewall.name,
301 layer=no_metal_region,
302 z=metal_layer.height,
303 height=no_metal_height)
-305 sidewallee = sidewall.name
+305 sidewallee = sidewall.name
307 #
308 # add simple dielectric
309 #
-310 simple_dielectric, diel_height = self.tech_info.simple_dielectric_above_metal(metal_layer_name)
-311 if simple_dielectric:
-312 diel_block = enlarged_top_cell_bbox.dup()
-313 diel_region = kdb.Region()
-314 diel_region.insert(diel_block)
-315 if sidewall_region:
-316 assert sidewall_height >= 0.0
-317 diel_region -= sidewall_region
-318 info(f"Simple dielectric (sidewall) {simple_dielectric.name}: "
+310 simple_dielectric, diel_height = self.tech_info.simple_dielectric_above_metal(metal_layer_name)
+311 if simple_dielectric:
+312 diel_block = enlarged_top_cell_bbox.dup()
+313 diel_region = kdb.Region()
+314 diel_region.insert(diel_block)
+315 if sidewall_region:
+316 assert sidewall_height >= 0.0
+317 diel_region -= sidewall_region
+318 info(f"Simple dielectric (sidewall) {simple_dielectric.name}: "
319 f"z={metal_z_bottom + sidewall_height}, height={diel_height - sidewall_height}")
-320 model_builder.add_dielectric(material_name=simple_dielectric.name,
+320 model_builder.add_dielectric(material_name=simple_dielectric.name,
321 layer=sidewall_region,
322 z=metal_z_bottom + sidewall_height,
323 height=diel_height - sidewall_height)
-324 if no_metal_region:
-325 info(f"Simple dielectric (no metal) {simple_dielectric.name}: "
+324 if no_metal_region:
+325 info(f"Simple dielectric (no metal) {simple_dielectric.name}: "
326 f"z={metal_z_bottom + no_metal_height}, height={diel_height - no_metal_height}")
-327 model_builder.add_dielectric(material_name=simple_dielectric.name,
+327 model_builder.add_dielectric(material_name=simple_dielectric.name,
328 layer=diel_region,
329 z=metal_z_bottom + no_metal_height,
330 height=diel_height - no_metal_height)
331 else:
-332 info(f"Simple dielectric {simple_dielectric.name}: "
+332 info(f"Simple dielectric {simple_dielectric.name}: "
333 f"z={metal_z_bottom}, height={diel_height}")
-334 model_builder.add_dielectric(material_name=simple_dielectric.name,
+334 model_builder.add_dielectric(material_name=simple_dielectric.name,
335 layer=diel_region,
336 z=metal_z_bottom,
337 height=diel_height)
-339 gen = model_builder.generate()
-340 return gen
+339 gen = model_builder.generate()
+340 return gen
diff --git a/pycov/z_875d58d22143ae30_fastercap_model_generator_py.html b/pycov/z_875d58d22143ae30_fastercap_model_generator_py.html index ace38328..47ffaf3b 100644 --- a/pycov/z_875d58d22143ae30_fastercap_model_generator_py.html +++ b/pycov/z_875d58d22143ae30_fastercap_model_generator_py.html @@ -2,7 +2,7 @@ -@@ -65,7 +65,7 @@
478 if lout:
479 d: kdb.Region = lout & lin
480 if not d.is_empty():
-481 self.generate_hdiel(below=mno, above=mni, layer=d)
+481 self.generate_hdiel(below=mno, above=mni, layer=d)
482 lin -= lout
483 if not lin.is_empty():
484 self.generate_hdiel(below=None, above=mni, layer=lin)
@@ -584,10 +584,10 @@502 for mno in self.materials.keys():
503 lout = dout.get(f"-{mno}", None)
504 if lout:
-505 d = lout & lin
-506 if not d.is_empty():
-507 self.generate_hcond_in(net_name=nn, below=mno, layer=d)
-508 lin -= lout
+505 d = lout & lin
+506 if not d.is_empty():
+507 self.generate_hcond_in(net_name=nn, below=mno, layer=d)
+508 lin -= lout
509 if not lin.is_empty():
510 self.generate_hcond_in(net_name=nn, below=None, layer=lin)
@@ -604,7 +604,7 @@522 self.generate_hcond_out(net_name=nn, above=mni, layer=d)
523 lout -= lin
524 if not lout.is_empty():
-525 self.generate_hcond_out(net_name=nn, above=None, layer=lout)
+525 self.generate_hcond_out(net_name=nn, above=None, layer=lout)
527 def next_z(self, z: float):
528 debug(f"Next layer {z}")
@@ -1060,7 +1060,7 @@978 def _write_as_stl(file_name: str,
979 tris: List[Triangle]):
980 if len(tris) == 0:
-981 return
+981 return
983 subproc(file_name)
984 with open(file_name, "w") as f:
@@ -1139,7 +1139,7 @@@@ -65,7 +65,7 @@
45 auto_preconditioner: bool,
46 galerkin_scheme: bool,
47 jacobi_preconditioner: bool):
-48 args = [
+48 args = [
49 exe_path,
50 '-b', # console mode, without GUI
51 '-i', # Dump detailed time and memory information
@@ -137,48 +137,48 @@55 f"-m{mesh_refinement_value}", # Mesh relative refinement value
56 ]
-58 if ooc_condition is not None:
-59 args += [f"-f{ooc_condition}"]
+58 if ooc_condition is not None:
+59 args += [f"-f{ooc_condition}"]
-61 if auto_preconditioner:
-62 args += ['-ap']
+61 if auto_preconditioner:
+62 args += ['-ap']
-64 if galerkin_scheme:
+64 if galerkin_scheme:
65 args += ['-g']
-67 if jacobi_preconditioner:
+67 if jacobi_preconditioner:
68 args += ['-pj']
-70 args += [
+70 args += [
71 lst_file_path
72 ]
-73 info(f"Calling FasterCap")
-74 subproc(f"{' '.join(args)}, output file: {log_path}")
+73 info(f"Calling FasterCap")
+74 subproc(f"{' '.join(args)}, output file: {log_path}")
-76 rule('FasterCap Output')
-77 start = time.time()
+76 rule('FasterCap Output')
+77 start = time.time()
-79 proc = subprocess.Popen(args,
+79 proc = subprocess.Popen(args,
80 stdin=subprocess.DEVNULL,
81 stdout=subprocess.PIPE,
82 stderr=subprocess.STDOUT,
83 universal_newlines=True,
84 text=True)
-85 with open(log_path, 'w') as f:
-86 while True:
-87 line = proc.stdout.readline()
-88 if not line:
-89 break
-90 subproc(line[:-1]) # remove newline
-91 f.writelines([line])
-92 proc.wait()
+85 with open(log_path, 'w') as f:
+86 while True:
+87 line = proc.stdout.readline()
+88 if not line:
+89 break
+90 subproc(line[:-1]) # remove newline
+91 f.writelines([line])
+92 proc.wait()
-94 duration = time.time() - start
+94 duration = time.time() - start
-96 rule()
+96 rule()
-98 if proc.returncode == 0:
-99 info(f"FasterCap succeeded after {'%.4g' % duration}s")
+98 if proc.returncode == 0:
+99 info(f"FasterCap succeeded after {'%.4g' % duration}s")
100 else:
101 raise Exception(f"FasterCap failed with status code {proc.returncode} after {'%.4g' % duration}s, "
102 f"see log file: {log_path}")
@@ -218,7 +218,7 @@@@ -65,7 +65,7 @@
97 @cached_property
98 def config(self) -> PDKConfig:
-99 # NOTE: installation paths of resources in the distribution wheel differes from source repo
+99 # NOTE: installation paths of resources in the distribution wheel differs from source repo
100 base_dir = os.path.dirname(os.path.realpath(__file__))
-101 tech_pb_json_dir = os.path.join(os.path.dirname(base_dir), 'klayout_pex_protobuf')
- -103 match self:
-104 case PDK.IHP_SG13G2:
-105 return PDKConfig(
-106 name=self,
-107 pex_lvs_script_path=os.path.join(base_dir, 'pdk', self, 'libs.tech', 'kpex', 'sg130g2.lvs'),
-108 tech_pb_json_path=os.path.join(tech_pb_json_dir, f"{self}_tech.pb.json")
-109 )
-110 case PDK.SKY130A:
-111 return PDKConfig(
-112 name=self,
-113 pex_lvs_script_path=os.path.join(base_dir, 'pdk', self, 'libs.tech', 'kpex', 'sky130.lvs'),
-114 tech_pb_json_path=os.path.join(tech_pb_json_dir, f"{self}_tech.pb.json")
-115 )
- - -118class KpexCLI:
-119 @staticmethod
-120 def parse_args(arg_list: List[str] = None) -> argparse.Namespace:
-121 # epilog = f"See '{PROGRAM_NAME} <subcommand> -h' for help on subcommand"
-122 epilog = """
-123| Variable | Example | Description |
-124| -------- | -------------------- | --------------------------------------- |
-125| PDKPATH | (e.g. $HOME/.volare) | Optional (required for default magicrc) |
-126| PDK | (e.g. sky130A) | Optional (required for default magicrc) |
-127"""
-128 epilog_md = rich.console.Group(
-129 rich.text.Text('Environmental variables:', style='argparse.groups'),
-130 rich.markdown.Markdown(epilog, style='argparse.text')
-131 )
-132 main_parser = argparse.ArgumentParser(description=f"{PROGRAM_NAME}: "
-133 f"KLayout-integrated Parasitic Extraction Tool",
-134 epilog=epilog_md,
-135 add_help=False,
-136 formatter_class=RichHelpFormatter)
- -138 group_special = main_parser.add_argument_group("Special options")
-139 group_special.add_argument("--help", "-h", action='help', help="show this help message and exit")
-140 group_special.add_argument("--version", "-v", action='version', version=f'{PROGRAM_NAME} {__version__}')
-141 group_special.add_argument("--log_level", dest='log_level', default='subprocess',
-142 help=render_enum_help(topic='log_level', enum_cls=LogLevel))
-143 group_special.add_argument("--threads", dest='num_threads', type=int,
-144 default=os.cpu_count() * 4,
-145 help="number of threads (e.g. for FasterCap) (default is %(default)s)")
-146 group_special.add_argument('--klayout', dest='klayout_exe_path', default='klayout',
-147 help="Path to klayout executable (default is '%(default)s')")
- -149 group_pex = main_parser.add_argument_group("Parasitic Extraction Setup")
-150 group_pex.add_argument("--pdk", dest="pdk", required=True, type=PDK,
-151 help=render_enum_help(topic='pdk', enum_cls=PDK))
- -153 group_pex.add_argument("--out_dir", "-o", dest="output_dir_base_path", default="output",
-154 help="Output directory path (default is '%(default)s')")
- -156 group_pex_input = main_parser.add_argument_group("Parasitic Extraction Input",
-157 description="Either LVS is run, or an existing LVSDB is used")
-158 group_pex_input.add_argument("--gds", "-g", dest="gds_path", help="GDS path (for LVS)")
-159 group_pex_input.add_argument("--schematic", "-s", dest="schematic_path",
-160 help="Schematic SPICE netlist path (for LVS)")
-161 group_pex_input.add_argument("--lvsdb", "-l", dest="lvsdb_path", help="KLayout LVSDB path (bypass LVS)")
-162 group_pex_input.add_argument("--cell", "-c", dest="cell_name", default=None,
-163 help="Cell (default is the top cell)")
- -165 group_pex_input.add_argument("--cache-lvs", dest="cache_lvs",
-166 type=true_or_false, default=True,
-167 help="Used cached LVSDB (for given input GDS) (default is %(default)s)")
-168 group_pex_input.add_argument("--cache-dir", dest="cache_dir_path", default=None,
-169 help="Path for cached LVSDB (default is .kpex_cache within --out_dir)")
- -171 group_pex_options = main_parser.add_argument_group("Parasitic Extraction Options")
-172 group_pex_options.add_argument("--blackbox", dest="blackbox_devices",
-173 type=true_or_false, default=False, # TODO: in the future this should be True by default
-174 help="Blackbox devices like MIM/MOM caps, as they are handled by SPICE models "
-175 "(default is %(default)s for testing now)")
-176 group_pex_options.add_argument("--fastercap", dest="run_fastercap",
-177 type=true_or_false, default=False,
-178 help="Run FasterCap engine (default is %(default)s)")
-179 group_pex_options.add_argument("--fastcap", dest="run_fastcap",
-180 type=true_or_false, default=False,
-181 help="Run FastCap2 engine (default is %(default)s)")
-182 group_pex_options.add_argument("--magic", dest="run_magic",
-183 type=true_or_false, default=False,
-184 help="Run MAGIC engine (default is %(default)s)")
-185 group_pex_options.add_argument("--2.5D", dest="run_2_5D",
-186 type=true_or_false, default=False,
-187 help="Run 2.5D analytical engine (default is %(default)s)")
- -189 group_fastercap = main_parser.add_argument_group("FasterCap options")
-190 group_fastercap.add_argument("--k_void", "-k", dest="k_void",
-191 type=float, default=3.9,
-192 help="Dielectric constant of void (default is %(default)s)")
-193 group_fastercap.add_argument("--delaunay_amax", "-a", dest="delaunay_amax",
-194 type=float, default=50,
-195 help="Delaunay triangulation maximum area (default is %(default)s)")
-196 group_fastercap.add_argument("--delaunay_b", "-b", dest="delaunay_b",
-197 type=float, default=0.5,
-198 help="Delaunay triangulation b (default is %(default)s)")
-199 group_fastercap.add_argument("--geo_check", dest="geometry_check",
-200 type=true_or_false, default=False,
-201 help=f"Validate geometries before passing to FasterCap "
-202 f"(default is False)")
-203 group_fastercap.add_argument("--diel", dest="dielectric_filter",
-204 type=str, default="all",
-205 help=f"Comma separated list of dielectric filter patterns. "
-206 f"Allowed patterns are: (none, all, -dielname1, +dielname2) "
-207 f"(default is %(default)s)")
- -209 group_fastercap.add_argument("--tolerance", dest="fastercap_tolerance",
-210 type=float, default=0.05,
-211 help="FasterCap -aX error tolerance (default is %(default)s)")
-212 group_fastercap.add_argument("--d_coeff", dest="fastercap_d_coeff",
-213 type=float, default=0.5,
-214 help=f"FasterCap -d direct potential interaction coefficient to mesh refinement "
-215 f"(default is %(default)s)")
-216 group_fastercap.add_argument("--mesh", dest="fastercap_mesh_refinement_value",
-217 type=float, default=0.5,
-218 help="FasterCap -m Mesh relative refinement value (default is %(default)s)")
-219 group_fastercap.add_argument("--ooc", dest="fastercap_ooc_condition",
-220 type=float, default=2,
-221 help="FasterCap -f out-of-core free memory to link memory condition "
-222 "(0 = don't go OOC, default is %(default)s)")
-223 group_fastercap.add_argument("--auto_precond", dest="fastercap_auto_preconditioner",
-224 type=true_or_false, default=True,
-225 help=f"FasterCap -ap Automatic preconditioner usage (default is %(default)s)")
-226 group_fastercap.add_argument("--galerkin", dest="fastercap_galerkin_scheme",
-227 action='store_true', default=False,
-228 help=f"FasterCap -g Use Galerkin scheme (default is %(default)s)")
-229 group_fastercap.add_argument("--jacobi", dest="fastercap_jacobi_preconditioner",
-230 action='store_true', default=False,
-231 help="FasterCap -pj Use Jacobi preconditioner (default is %(default)s)")
- -233 PDKPATH = os.environ.get('PDKPATH', None)
-234 default_magicrc_path = \
-235 None if PDKPATH is None \
-236 else os.path.abspath(f"{PDKPATH}/libs.tech/magic/{os.environ['PDK']}.magicrc")
-237 group_magic = main_parser.add_argument_group("MAGIC options")
-238 group_magic.add_argument('--magicrc', dest='magicrc_path', default=default_magicrc_path,
-239 help=f"Path to magicrc configuration file (default is '%(default)s')")
-240 group_magic.add_argument("--magic_mode", dest='magic_pex_mode', default='CC',
-241 help=render_enum_help(topic='log_level', enum_cls=MagicPEXMode))
-242 group_magic.add_argument("--magic_cthresh", dest="magic_cthresh",
-243 type=float, default=0.01,
-244 help="Threshold for ignored parasitic capacitances (default is %(default)s)")
-245 group_magic.add_argument("--magic_rthresh", dest="magic_rthresh",
-246 type=float, default=100.0,
-247 help="Threshold for ignored parasitic resistances (default is %(default)s)")
-248 group_magic.add_argument("--magic_halo", dest="magic_halo",
-249 type=float, default=None,
-250 help="Custom sidewall halo distance in µm "
-251 "(MAGIC command: extract halo <value>) (default is no custom halo)")
-252 group_magic.add_argument('--magic_exe', dest='magic_exe_path', default='magic',
-253 help="Path to magic executable (default is '%(default)s')")
- -255 if arg_list is None:
-256 arg_list = sys.argv[1:]
-257 args = main_parser.parse_args(arg_list)
-258 return args
+ +102 if os.path.isdir(os.path.join(base_dir, '..', '.git')): # in source repo
+103 base_dir = os.path.dirname(base_dir)
+104 tech_pb_json_dir = os.path.join(base_dir, 'klayout_pex_protobuf')
+105 else: # site-packages/klayout_pex -> site-packages/klayout_pex_protobuf
+106 tech_pb_json_dir = os.path.join(os.path.dirname(base_dir), 'klayout_pex_protobuf')
+ +108 match self:
+109 case PDK.IHP_SG13G2:
+110 return PDKConfig(
+111 name=self,
+112 pex_lvs_script_path=os.path.join(base_dir, 'pdk', self, 'libs.tech', 'kpex', 'sg130g2.lvs'),
+113 tech_pb_json_path=os.path.join(tech_pb_json_dir, f"{self}_tech.pb.json")
+114 )
+115 case PDK.SKY130A:
+116 return PDKConfig(
+117 name=self,
+118 pex_lvs_script_path=os.path.join(base_dir, 'pdk', self, 'libs.tech', 'kpex', 'sky130.lvs'),
+119 tech_pb_json_path=os.path.join(tech_pb_json_dir, f"{self}_tech.pb.json")
+120 )
+ + +123class KpexCLI:
+124 @staticmethod
+125 def parse_args(arg_list: List[str] = None) -> argparse.Namespace:
+126 # epilog = f"See '{PROGRAM_NAME} <subcommand> -h' for help on subcommand"
+127 epilog = """
+128| Variable | Example | Description |
+129| -------- | -------------------- | --------------------------------------- |
+130| PDKPATH | (e.g. $HOME/.volare) | Optional (required for default magicrc) |
+131| PDK | (e.g. sky130A) | Optional (required for default magicrc) |
+132"""
+133 epilog_md = rich.console.Group(
+134 rich.text.Text('Environmental variables:', style='argparse.groups'),
+135 rich.markdown.Markdown(epilog, style='argparse.text')
+136 )
+137 main_parser = argparse.ArgumentParser(description=f"{PROGRAM_NAME}: "
+138 f"KLayout-integrated Parasitic Extraction Tool",
+139 epilog=epilog_md,
+140 add_help=False,
+141 formatter_class=RichHelpFormatter)
+ +143 group_special = main_parser.add_argument_group("Special options")
+144 group_special.add_argument("--help", "-h", action='help', help="show this help message and exit")
+145 group_special.add_argument("--version", "-v", action='version', version=f'{PROGRAM_NAME} {__version__}')
+146 group_special.add_argument("--log_level", dest='log_level', default='subprocess',
+147 help=render_enum_help(topic='log_level', enum_cls=LogLevel))
+148 group_special.add_argument("--threads", dest='num_threads', type=int,
+149 default=os.cpu_count() * 4,
+150 help="number of threads (e.g. for FasterCap) (default is %(default)s)")
+151 group_special.add_argument('--klayout', dest='klayout_exe_path', default='klayout',
+152 help="Path to klayout executable (default is '%(default)s')")
+ +154 group_pex = main_parser.add_argument_group("Parasitic Extraction Setup")
+155 group_pex.add_argument("--pdk", dest="pdk", required=True, type=PDK,
+156 help=render_enum_help(topic='pdk', enum_cls=PDK))
+ +158 group_pex.add_argument("--out_dir", "-o", dest="output_dir_base_path", default="output",
+159 help="Output directory path (default is '%(default)s')")
+ +161 group_pex_input = main_parser.add_argument_group("Parasitic Extraction Input",
+162 description="Either LVS is run, or an existing LVSDB is used")
+163 group_pex_input.add_argument("--gds", "-g", dest="gds_path", help="GDS path (for LVS)")
+164 group_pex_input.add_argument("--schematic", "-s", dest="schematic_path",
+165 help="Schematic SPICE netlist path (for LVS)")
+166 group_pex_input.add_argument("--lvsdb", "-l", dest="lvsdb_path", help="KLayout LVSDB path (bypass LVS)")
+167 group_pex_input.add_argument("--cell", "-c", dest="cell_name", default=None,
+168 help="Cell (default is the top cell)")
+ +170 group_pex_input.add_argument("--cache-lvs", dest="cache_lvs",
+171 type=true_or_false, default=True,
+172 help="Used cached LVSDB (for given input GDS) (default is %(default)s)")
+173 group_pex_input.add_argument("--cache-dir", dest="cache_dir_path", default=None,
+174 help="Path for cached LVSDB (default is .kpex_cache within --out_dir)")
+ +176 group_pex_options = main_parser.add_argument_group("Parasitic Extraction Options")
+177 group_pex_options.add_argument("--blackbox", dest="blackbox_devices",
+178 type=true_or_false, default=False, # TODO: in the future this should be True by default
+179 help="Blackbox devices like MIM/MOM caps, as they are handled by SPICE models "
+180 "(default is %(default)s for testing now)")
+181 group_pex_options.add_argument("--fastercap", dest="run_fastercap",
+182 type=true_or_false, default=False,
+183 help="Run FasterCap engine (default is %(default)s)")
+184 group_pex_options.add_argument("--fastcap", dest="run_fastcap",
+185 type=true_or_false, default=False,
+186 help="Run FastCap2 engine (default is %(default)s)")
+187 group_pex_options.add_argument("--magic", dest="run_magic",
+188 type=true_or_false, default=False,
+189 help="Run MAGIC engine (default is %(default)s)")
+190 group_pex_options.add_argument("--2.5D", dest="run_2_5D",
+191 type=true_or_false, default=False,
+192 help="Run 2.5D analytical engine (default is %(default)s)")
+ +194 group_fastercap = main_parser.add_argument_group("FasterCap options")
+195 group_fastercap.add_argument("--k_void", "-k", dest="k_void",
+196 type=float, default=3.9,
+197 help="Dielectric constant of void (default is %(default)s)")
+198 group_fastercap.add_argument("--delaunay_amax", "-a", dest="delaunay_amax",
+199 type=float, default=50,
+200 help="Delaunay triangulation maximum area (default is %(default)s)")
+201 group_fastercap.add_argument("--delaunay_b", "-b", dest="delaunay_b",
+202 type=float, default=0.5,
+203 help="Delaunay triangulation b (default is %(default)s)")
+204 group_fastercap.add_argument("--geo_check", dest="geometry_check",
+205 type=true_or_false, default=False,
+206 help=f"Validate geometries before passing to FasterCap "
+207 f"(default is False)")
+208 group_fastercap.add_argument("--diel", dest="dielectric_filter",
+209 type=str, default="all",
+210 help=f"Comma separated list of dielectric filter patterns. "
+211 f"Allowed patterns are: (none, all, -dielname1, +dielname2) "
+212 f"(default is %(default)s)")
+ +214 group_fastercap.add_argument("--tolerance", dest="fastercap_tolerance",
+215 type=float, default=0.05,
+216 help="FasterCap -aX error tolerance (default is %(default)s)")
+217 group_fastercap.add_argument("--d_coeff", dest="fastercap_d_coeff",
+218 type=float, default=0.5,
+219 help=f"FasterCap -d direct potential interaction coefficient to mesh refinement "
+220 f"(default is %(default)s)")
+221 group_fastercap.add_argument("--mesh", dest="fastercap_mesh_refinement_value",
+222 type=float, default=0.5,
+223 help="FasterCap -m Mesh relative refinement value (default is %(default)s)")
+224 group_fastercap.add_argument("--ooc", dest="fastercap_ooc_condition",
+225 type=float, default=2,
+226 help="FasterCap -f out-of-core free memory to link memory condition "
+227 "(0 = don't go OOC, default is %(default)s)")
+228 group_fastercap.add_argument("--auto_precond", dest="fastercap_auto_preconditioner",
+229 type=true_or_false, default=True,
+230 help=f"FasterCap -ap Automatic preconditioner usage (default is %(default)s)")
+231 group_fastercap.add_argument("--galerkin", dest="fastercap_galerkin_scheme",
+232 action='store_true', default=False,
+233 help=f"FasterCap -g Use Galerkin scheme (default is %(default)s)")
+234 group_fastercap.add_argument("--jacobi", dest="fastercap_jacobi_preconditioner",
+235 action='store_true', default=False,
+236 help="FasterCap -pj Use Jacobi preconditioner (default is %(default)s)")
+ +238 PDKPATH = os.environ.get('PDKPATH', None)
+239 default_magicrc_path = \
+240 None if PDKPATH is None \
+241 else os.path.abspath(f"{PDKPATH}/libs.tech/magic/{os.environ['PDK']}.magicrc")
+242 group_magic = main_parser.add_argument_group("MAGIC options")
+243 group_magic.add_argument('--magicrc', dest='magicrc_path', default=default_magicrc_path,
+244 help=f"Path to magicrc configuration file (default is '%(default)s')")
+245 group_magic.add_argument("--magic_mode", dest='magic_pex_mode', default='CC',
+246 help=render_enum_help(topic='log_level', enum_cls=MagicPEXMode))
+247 group_magic.add_argument("--magic_cthresh", dest="magic_cthresh",
+248 type=float, default=0.01,
+249 help="Threshold for ignored parasitic capacitances (default is %(default)s)")
+250 group_magic.add_argument("--magic_rthresh", dest="magic_rthresh",
+251 type=float, default=100.0,
+252 help="Threshold for ignored parasitic resistances (default is %(default)s)")
+253 group_magic.add_argument("--magic_halo", dest="magic_halo",
+254 type=float, default=None,
+255 help="Custom sidewall halo distance in µm "
+256 "(MAGIC command: extract halo <value>) (default is no custom halo)")
+257 group_magic.add_argument('--magic_exe', dest='magic_exe_path', default='magic',
+258 help="Path to magic executable (default is '%(default)s')")
-260 @staticmethod
-261 def validate_args(args: argparse.Namespace):
-262 found_errors = False
- -264 pdk_config: PDKConfig = args.pdk.config
-265 args.tech_pbjson_path = pdk_config.tech_pb_json_path
-266 args.lvs_script_path = pdk_config.pex_lvs_script_path
- -268 if not os.path.isfile(args.klayout_exe_path):
-269 path = shutil.which(args.klayout_exe_path)
-270 if not path:
-271 error(f"Can't locate KLayout executable at {args.klayout_exe_path}")
-272 found_errors = True
- -274 if not os.path.isfile(args.tech_pbjson_path):
-275 error(f"Can't read technology file at path {args.tech_pbjson_path}")
-276 found_errors = True
- -278 rule('Input Layout')
- -280 # input mode: LVS or existing LVSDB?
-281 if args.gds_path:
-282 info(f"GDS input file passed, running in LVS mode")
-283 args.input_mode = InputMode.GDS
-284 if not os.path.isfile(args.gds_path):
-285 error(f"Can't read GDS file (LVS input) at path {args.gds_path}")
-286 found_errors = True
-287 else:
-288 args.layout = kdb.Layout()
-289 args.layout.read(args.gds_path)
- -291 top_cells = args.layout.top_cells()
- -293 if args.cell_name: # explicit user-specified cell name
-294 args.effective_cell_name = args.cell_name
- -296 found_cell: Optional[kdb.Cell] = None
-297 for cell in args.layout.cells('*'):
-298 if cell.name == args.effective_cell_name:
-299 found_cell = cell
-300 break
-301 if not found_cell:
-302 error(f"Could not find cell {args.cell_name} in GDS {args.gds_path}")
-303 found_errors = True
+260 if arg_list is None:
+261 arg_list = sys.argv[1:]
+262 args = main_parser.parse_args(arg_list)
+263 return args
+ +265 @staticmethod
+266 def validate_args(args: argparse.Namespace):
+267 found_errors = False
+ +269 pdk_config: PDKConfig = args.pdk.config
+270 args.tech_pbjson_path = pdk_config.tech_pb_json_path
+271 args.lvs_script_path = pdk_config.pex_lvs_script_path
+ +273 if not os.path.isfile(args.klayout_exe_path):
+274 path = shutil.which(args.klayout_exe_path)
+275 if not path:
+276 error(f"Can't locate KLayout executable at {args.klayout_exe_path}")
+277 found_errors = True
+ +279 if not os.path.isfile(args.tech_pbjson_path):
+280 error(f"Can't read technology file at path {args.tech_pbjson_path}")
+281 found_errors = True
+ +283 if not os.path.isfile(args.lvs_script_path):
+284 error(f"Can't locate LVS script path at {args.lvs_script_path}")
+285 found_errors = True
+ +287 rule('Input Layout')
+ +289 # input mode: LVS or existing LVSDB?
+290 if args.gds_path:
+291 info(f"GDS input file passed, running in LVS mode")
+292 args.input_mode = InputMode.GDS
+293 if not os.path.isfile(args.gds_path):
+294 error(f"Can't read GDS file (LVS input) at path {args.gds_path}")
+295 found_errors = True
+296 else:
+297 args.layout = kdb.Layout()
+298 args.layout.read(args.gds_path)
+ +300 top_cells = args.layout.top_cells()
+ +302 if args.cell_name: # explicit user-specified cell name
+303 args.effective_cell_name = args.cell_name
-305 is_only_top_cell = len(top_cells) == 1 and top_cells[0].name == args.cell_name
-306 if is_only_top_cell:
-307 info(f"Found cell {args.cell_name} in GDS {args.gds_path} (only top cell)")
-308 else: # there are other cells => extract the top cell to a tmp layout
-309 args.effective_gds_path = os.path.join(args.output_dir_path, f"{args.cell_name}_exported.gds.gz")
-310 info(f"Found cell {args.cell_name} in GDS {args.gds_path}, "
-311 f"but it is not the only top cell, "
-312 f"so layout is exported to: {args.effective_gds_path}")
+305 found_cell: Optional[kdb.Cell] = None
+306 for cell in args.layout.cells('*'):
+307 if cell.name == args.effective_cell_name:
+308 found_cell = cell
+309 break
+310 if not found_cell:
+311 error(f"Could not find cell {args.cell_name} in GDS {args.gds_path}")
+312 found_errors = True
-314 found_cell.write(args.effective_gds_path)
-315 else: # find top cell
-316 if len(top_cells) == 1:
-317 args.effective_cell_name = top_cells[0].name
-318 info(f"No explicit top cell specified, using top cell '{args.effective_cell_name}'")
-319 else:
-320 args.effective_cell_name = 'TOP'
-321 error(f"Could not determine the default top cell in GDS {args.gds_path}, "
-322 f"there are multiple: {', '.join([c.name for c in top_cells])}. "
-323 f"Use --cell to specify the cell")
-324 found_errors = True
- -326 args.effective_gds_path = args.gds_path
-327 else:
-328 info(f"LVSDB input file passed, bypassing LVS")
-329 args.input_mode = InputMode.LVSDB
-330 if not hasattr(args, 'lvsdb_path'):
-331 error(f"LVSDB input path not specified (argument --lvsdb)")
-332 found_errors = True
-333 elif not os.path.isfile(args.lvsdb_path):
-334 error(f"Can't read KLayout LVSDB file at path {args.lvsdb_path}")
-335 found_errors = True
-336 else:
-337 lvsdb = kdb.LayoutVsSchematic()
-338 lvsdb.read(args.lvsdb_path)
-339 top_cell: kdb.Cell = lvsdb.internal_top_cell()
-340 args.effective_cell_name = top_cell.name
- -342 def input_file_stem(path: str):
-343 # could be *.gds, or *.gds.gz, so remove all extensions
-344 return os.path.basename(path).split(sep='.')[0]
- -346 if hasattr(args, 'effective_cell_name'):
-347 run_dir_id: str
-348 match args.input_mode:
-349 case InputMode.GDS:
-350 run_dir_id = f"{input_file_stem(args.gds_path)}__{args.effective_cell_name}"
-351 case InputMode.LVSDB:
-352 run_dir_id = f"{input_file_stem(args.lvsdb_path)}__{args.effective_cell_name}"
-353 case _:
-354 raise NotImplementedError(f"Unknown input mode {args.input_mode}")
- -356 args.output_dir_path = os.path.join(args.output_dir_base_path, run_dir_id)
-357 os.makedirs(args.output_dir_path, exist_ok=True)
-358 if args.input_mode == InputMode.GDS:
-359 if args.schematic_path:
-360 args.effective_schematic_path = args.schematic_path
-361 if not os.path.isfile(args.schematic_path):
-362 error(f"Can't read schematic (LVS input) at path {args.schematic_path}")
-363 found_errors = True
-364 else:
-365 info(f"LVS input schematic not specified (argument --schematic), using dummy schematic")
-366 args.effective_schematic_path = os.path.join(args.output_dir_path,
-367 f"{args.effective_cell_name}_dummy_schematic.spice")
-368 with open(args.effective_schematic_path, 'w') as f:
-369 f.writelines([
-370 f".subckt {args.effective_cell_name} VDD VSS",
-371 '.ends',
-372 '.end'
-373 ])
- -375 try:
-376 args.log_level = LogLevel[args.log_level.upper()]
-377 except KeyError:
-378 error(f"Requested log level {args.log_level.lower()} does not exist, "
-379 f"{render_enum_help(topic='log_level', enum_cls=LogLevel, print_default=False)}")
-380 found_errors = True
- -382 try:
-383 pattern_string: str = args.dielectric_filter
-384 args.dielectric_filter = MultipleChoicePattern(pattern=pattern_string)
-385 except ValueError as e:
-386 error("Failed to parse --diel arg", e)
-387 found_errors = True
- -389 # at least one engine must be activated
+314 is_only_top_cell = len(top_cells) == 1 and top_cells[0].name == args.cell_name
+315 if is_only_top_cell:
+316 info(f"Found cell {args.cell_name} in GDS {args.gds_path} (only top cell)")
+317 else: # there are other cells => extract the top cell to a tmp layout
+318 args.effective_gds_path = os.path.join(args.output_dir_path, f"{args.cell_name}_exported.gds.gz")
+319 info(f"Found cell {args.cell_name} in GDS {args.gds_path}, "
+320 f"but it is not the only top cell, "
+321 f"so layout is exported to: {args.effective_gds_path}")
+ +323 found_cell.write(args.effective_gds_path)
+324 else: # find top cell
+325 if len(top_cells) == 1:
+326 args.effective_cell_name = top_cells[0].name
+327 info(f"No explicit top cell specified, using top cell '{args.effective_cell_name}'")
+328 else:
+329 args.effective_cell_name = 'TOP'
+330 error(f"Could not determine the default top cell in GDS {args.gds_path}, "
+331 f"there are multiple: {', '.join([c.name for c in top_cells])}. "
+332 f"Use --cell to specify the cell")
+333 found_errors = True
+ +335 args.effective_gds_path = args.gds_path
+336 else:
+337 info(f"LVSDB input file passed, bypassing LVS")
+338 args.input_mode = InputMode.LVSDB
+339 if not hasattr(args, 'lvsdb_path'):
+340 error(f"LVSDB input path not specified (argument --lvsdb)")
+341 found_errors = True
+342 elif not os.path.isfile(args.lvsdb_path):
+343 error(f"Can't read KLayout LVSDB file at path {args.lvsdb_path}")
+344 found_errors = True
+345 else:
+346 lvsdb = kdb.LayoutVsSchematic()
+347 lvsdb.read(args.lvsdb_path)
+348 top_cell: kdb.Cell = lvsdb.internal_top_cell()
+349 args.effective_cell_name = top_cell.name
+ +351 def input_file_stem(path: str):
+352 # could be *.gds, or *.gds.gz, so remove all extensions
+353 return os.path.basename(path).split(sep='.')[0]
+ +355 if hasattr(args, 'effective_cell_name'):
+356 run_dir_id: str
+357 match args.input_mode:
+358 case InputMode.GDS:
+359 run_dir_id = f"{input_file_stem(args.gds_path)}__{args.effective_cell_name}"
+360 case InputMode.LVSDB:
+361 run_dir_id = f"{input_file_stem(args.lvsdb_path)}__{args.effective_cell_name}"
+362 case _:
+363 raise NotImplementedError(f"Unknown input mode {args.input_mode}")
+ +365 args.output_dir_path = os.path.join(args.output_dir_base_path, run_dir_id)
+366 os.makedirs(args.output_dir_path, exist_ok=True)
+367 if args.input_mode == InputMode.GDS:
+368 if args.schematic_path:
+369 args.effective_schematic_path = args.schematic_path
+370 if not os.path.isfile(args.schematic_path):
+371 error(f"Can't read schematic (LVS input) at path {args.schematic_path}")
+372 found_errors = True
+373 else:
+374 info(f"LVS input schematic not specified (argument --schematic), using dummy schematic")
+375 args.effective_schematic_path = os.path.join(args.output_dir_path,
+376 f"{args.effective_cell_name}_dummy_schematic.spice")
+377 with open(args.effective_schematic_path, 'w') as f:
+378 f.writelines([
+379 f".subckt {args.effective_cell_name} VDD VSS",
+380 '.ends',
+381 '.end'
+382 ])
+ +384 try:
+385 args.log_level = LogLevel[args.log_level.upper()]
+386 except KeyError:
+387 error(f"Requested log level {args.log_level.lower()} does not exist, "
+388 f"{render_enum_help(topic='log_level', enum_cls=LogLevel, print_default=False)}")
+389 found_errors = True
-391 if not (args.run_magic or args.run_fastcap or args.run_fastercap or args.run_2_5D):
-392 error("No PEX engines activated")
-393 engine_help = """
-394| Argument | Description |
-395| -------------- | --------------------------------------- |
-396| --fastercap y | Run kpex/FasterCap engine |
-397| --2.5D y | Run kpex/2.5D engine |
-398| --magic y | Run MAGIC engine |
-399"""
-400 subproc(f"\nPlease activate one or more engines using the arguments:\n{engine_help}")
-401 found_errors = True
- -403 if args.cache_dir_path is None:
-404 args.cache_dir_path = os.path.join(args.output_dir_base_path, '.kpex_cache')
- -406 if found_errors:
-407 raise ArgumentValidationError("Argument validation failed")
- -409 def build_fastercap_input(self,
-410 args: argparse.Namespace,
-411 pex_context: KLayoutExtractionContext,
-412 tech_info: TechInfo) -> str:
-413 rule('Process stackup')
-414 fastercap_input_builder = FasterCapInputBuilder(pex_context=pex_context,
-415 tech_info=tech_info,
-416 k_void=args.k_void,
-417 delaunay_amax=args.delaunay_amax,
-418 delaunay_b=args.delaunay_b)
-419 gen: FasterCapModelGenerator = fastercap_input_builder.build()
- -421 rule('FasterCap Input File Generation')
-422 faster_cap_input_dir_path = os.path.join(args.output_dir_path, 'FasterCap_Input_Files')
-423 os.makedirs(faster_cap_input_dir_path, exist_ok=True)
- -425 lst_file = gen.write_fastcap(output_dir_path=faster_cap_input_dir_path, prefix='FasterCap_Input_')
- -427 rule('STL File Generation')
-428 geometry_dir_path = os.path.join(args.output_dir_path, 'Geometries')
-429 os.makedirs(geometry_dir_path, exist_ok=True)
-430 gen.dump_stl(output_dir_path=geometry_dir_path, prefix='')
- -432 if args.geometry_check:
-433 rule('Geometry Validation')
-434 gen.check()
+391 try:
+392 pattern_string: str = args.dielectric_filter
+393 args.dielectric_filter = MultipleChoicePattern(pattern=pattern_string)
+394 except ValueError as e:
+395 error("Failed to parse --diel arg", e)
+396 found_errors = True
+ +398 # at least one engine must be activated
+ +400 if not (args.run_magic or args.run_fastcap or args.run_fastercap or args.run_2_5D):
+401 error("No PEX engines activated")
+402 engine_help = """
+403| Argument | Description |
+404| -------------- | --------------------------------------- |
+405| --fastercap y | Run kpex/FasterCap engine |
+406| --2.5D y | Run kpex/2.5D engine |
+407| --magic y | Run MAGIC engine |
+408"""
+409 subproc(f"\nPlease activate one or more engines using the arguments:\n{engine_help}")
+410 found_errors = True
+ +412 if args.cache_dir_path is None:
+413 args.cache_dir_path = os.path.join(args.output_dir_base_path, '.kpex_cache')
+ +415 if found_errors:
+416 raise ArgumentValidationError("Argument validation failed")
+ +418 def build_fastercap_input(self,
+419 args: argparse.Namespace,
+420 pex_context: KLayoutExtractionContext,
+421 tech_info: TechInfo) -> str:
+422 rule('Process stackup')
+423 fastercap_input_builder = FasterCapInputBuilder(pex_context=pex_context,
+424 tech_info=tech_info,
+425 k_void=args.k_void,
+426 delaunay_amax=args.delaunay_amax,
+427 delaunay_b=args.delaunay_b)
+428 gen: FasterCapModelGenerator = fastercap_input_builder.build()
+ +430 rule('FasterCap Input File Generation')
+431 faster_cap_input_dir_path = os.path.join(args.output_dir_path, 'FasterCap_Input_Files')
+432 os.makedirs(faster_cap_input_dir_path, exist_ok=True)
+ +434 lst_file = gen.write_fastcap(output_dir_path=faster_cap_input_dir_path, prefix='FasterCap_Input_')
-436 return lst_file
- - -439 def run_fastercap_extraction(self,
-440 args: argparse.Namespace,
-441 pex_context: KLayoutExtractionContext,
-442 lst_file: str):
-443 rule('FasterCap Execution')
-444 info(f"Configure number of OpenMP threads (environmental variable OMP_NUM_THREADS) as {args.num_threads}")
-445 os.environ['OMP_NUM_THREADS'] = f"{args.num_threads}"
+436 rule('STL File Generation')
+437 geometry_dir_path = os.path.join(args.output_dir_path, 'Geometries')
+438 os.makedirs(geometry_dir_path, exist_ok=True)
+439 gen.dump_stl(output_dir_path=geometry_dir_path, prefix='')
+ +441 if args.geometry_check:
+442 rule('Geometry Validation')
+443 gen.check()
+ +445 return lst_file
-447 exe_path = "FasterCap"
-448 log_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FasterCap_Output.txt")
-449 raw_csv_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FasterCap_Result_Matrix_Raw.csv")
-450 avg_csv_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FasterCap_Result_Matrix_Avg.csv")
-451 expanded_netlist_path = os.path.join(args.output_dir_path,
-452 f"{args.effective_cell_name}_FasterCap_Expanded_Netlist.cir")
-453 expanded_netlist_csv_path = os.path.join(args.output_dir_path,
-454 f"{args.effective_cell_name}_FasterCap_Expanded_Netlist.csv")
-455 reduced_netlist_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FasterCap_Reduced_Netlist.cir")
- -457 run_fastercap(exe_path=exe_path,
-458 lst_file_path=lst_file,
-459 log_path=log_path,
-460 tolerance=args.fastercap_tolerance,
-461 d_coeff=args.fastercap_d_coeff,
-462 mesh_refinement_value=args.fastercap_mesh_refinement_value,
-463 ooc_condition=args.fastercap_ooc_condition,
-464 auto_preconditioner=args.fastercap_auto_preconditioner,
-465 galerkin_scheme=args.fastercap_galerkin_scheme,
-466 jacobi_preconditioner=args.fastercap_jacobi_preconditioner)
- -468 cap_matrix = fastercap_parse_capacitance_matrix(log_path)
-469 cap_matrix.write_csv(raw_csv_path)
- -471 cap_matrix = cap_matrix.averaged_off_diagonals()
-472 cap_matrix.write_csv(avg_csv_path)
- -474 netlist_expander = NetlistExpander()
-475 expanded_netlist = netlist_expander.expand(
-476 extracted_netlist=pex_context.lvsdb.netlist(),
-477 top_cell_name=pex_context.top_cell.name,
-478 cap_matrix=cap_matrix,
-479 blackbox_devices=args.blackbox_devices
-480 )
- -482 # create a nice CSV for reports, useful for spreadsheets
-483 netlist_csv_writer = NetlistCSVWriter()
-484 netlist_csv_writer.write_csv(netlist=expanded_netlist,
-485 top_cell_name=pex_context.top_cell.name,
-486 output_path=expanded_netlist_csv_path)
- -488 rule("Extended netlist (CSV format):")
-489 with open(expanded_netlist_csv_path, 'r') as f:
-490 for line in f.readlines():
-491 subproc(line[:-1]) # abusing subproc, simply want verbatim
-492 rule()
- -494 info(f"Wrote expanded netlist CSV to: {expanded_netlist_csv_path}")
- -496 spice_writer = kdb.NetlistSpiceWriter()
-497 spice_writer.use_net_names = True
-498 spice_writer.with_comments = False
-499 expanded_netlist.write(expanded_netlist_path, spice_writer)
-500 info(f"Wrote expanded netlist to: {expanded_netlist_path}")
- -502 netlist_reducer = NetlistReducer()
-503 reduced_netlist = netlist_reducer.reduce(netlist=expanded_netlist,
-504 top_cell_name=pex_context.top_cell.name)
-505 reduced_netlist.write(reduced_netlist_path, spice_writer)
-506 info(f"Wrote reduced netlist to: {reduced_netlist_path}")
- -508 self._fastercap_extracted_csv_path = expanded_netlist_csv_path
- -510 def run_magic_extraction(self,
-511 args: argparse.Namespace):
-512 if args.input_mode != InputMode.GDS:
-513 error(f"MAGIC engine only works with GDS input mode"
-514 f" (currently {args.input_mode})")
-515 return
+ +448 def run_fastercap_extraction(self,
+449 args: argparse.Namespace,
+450 pex_context: KLayoutExtractionContext,
+451 lst_file: str):
+452 rule('FasterCap Execution')
+453 info(f"Configure number of OpenMP threads (environmental variable OMP_NUM_THREADS) as {args.num_threads}")
+454 os.environ['OMP_NUM_THREADS'] = f"{args.num_threads}"
+ +456 exe_path = "FasterCap"
+457 log_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FasterCap_Output.txt")
+458 raw_csv_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FasterCap_Result_Matrix_Raw.csv")
+459 avg_csv_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FasterCap_Result_Matrix_Avg.csv")
+460 expanded_netlist_path = os.path.join(args.output_dir_path,
+461 f"{args.effective_cell_name}_FasterCap_Expanded_Netlist.cir")
+462 expanded_netlist_csv_path = os.path.join(args.output_dir_path,
+463 f"{args.effective_cell_name}_FasterCap_Expanded_Netlist.csv")
+464 reduced_netlist_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FasterCap_Reduced_Netlist.cir")
+ +466 run_fastercap(exe_path=exe_path,
+467 lst_file_path=lst_file,
+468 log_path=log_path,
+469 tolerance=args.fastercap_tolerance,
+470 d_coeff=args.fastercap_d_coeff,
+471 mesh_refinement_value=args.fastercap_mesh_refinement_value,
+472 ooc_condition=args.fastercap_ooc_condition,
+473 auto_preconditioner=args.fastercap_auto_preconditioner,
+474 galerkin_scheme=args.fastercap_galerkin_scheme,
+475 jacobi_preconditioner=args.fastercap_jacobi_preconditioner)
+ +477 cap_matrix = fastercap_parse_capacitance_matrix(log_path)
+478 cap_matrix.write_csv(raw_csv_path)
+ +480 cap_matrix = cap_matrix.averaged_off_diagonals()
+481 cap_matrix.write_csv(avg_csv_path)
+ +483 netlist_expander = NetlistExpander()
+484 expanded_netlist = netlist_expander.expand(
+485 extracted_netlist=pex_context.lvsdb.netlist(),
+486 top_cell_name=pex_context.top_cell.name,
+487 cap_matrix=cap_matrix,
+488 blackbox_devices=args.blackbox_devices
+489 )
+ +491 # create a nice CSV for reports, useful for spreadsheets
+492 netlist_csv_writer = NetlistCSVWriter()
+493 netlist_csv_writer.write_csv(netlist=expanded_netlist,
+494 top_cell_name=pex_context.top_cell.name,
+495 output_path=expanded_netlist_csv_path)
+ +497 rule("Extended netlist (CSV format):")
+498 with open(expanded_netlist_csv_path, 'r') as f:
+499 for line in f.readlines():
+500 subproc(line[:-1]) # abusing subproc, simply want verbatim
+501 rule()
+ +503 info(f"Wrote expanded netlist CSV to: {expanded_netlist_csv_path}")
+ +505 spice_writer = kdb.NetlistSpiceWriter()
+506 spice_writer.use_net_names = True
+507 spice_writer.with_comments = False
+508 expanded_netlist.write(expanded_netlist_path, spice_writer)
+509 info(f"Wrote expanded netlist to: {expanded_netlist_path}")
+ +511 netlist_reducer = NetlistReducer()
+512 reduced_netlist = netlist_reducer.reduce(netlist=expanded_netlist,
+513 top_cell_name=pex_context.top_cell.name)
+514 reduced_netlist.write(reduced_netlist_path, spice_writer)
+515 info(f"Wrote reduced netlist to: {reduced_netlist_path}")
-517 magic_run_dir = os.path.join(args.output_dir_path, f"magic_{args.magic_pex_mode}")
-518 magic_log_path = os.path.join(magic_run_dir, f"{args.effective_cell_name}_MAGIC_CC_Output.txt")
-519 magic_script_path = os.path.join(magic_run_dir, f"{args.effective_cell_name}_MAGIC_CC_Script.tcl")
- -521 output_netlist_path = f"{magic_run_dir}/{args.effective_cell_name}.pex.spice"
- -523 os.makedirs(magic_run_dir, exist_ok=True)
- -525 prepare_magic_script(gds_path=args.effective_gds_path,
-526 cell_name=args.effective_cell_name,
-527 run_dir_path=magic_run_dir,
-528 script_path=magic_script_path,
-529 output_netlist_path=output_netlist_path,
-530 pex_mode=args.magic_pex_mode,
-531 c_threshold=args.magic_cthresh,
-532 r_threshold=args.magic_rthresh,
-533 halo=args.magic_halo)
- -535 run_magic(exe_path=args.magic_exe_path,
-536 magicrc_path=args.magicrc_path,
-537 script_path=magic_script_path,
-538 log_path=magic_log_path)
- -540 subproc(f"SPICE netlist saved at: {output_netlist_path}")
-541 rule("MAGIC PEX SPICE netlist")
-542 with open(output_netlist_path, 'r') as f:
-543 subproc(f.read())
-544 rule()
- -546 def run_fastcap_extraction(self,
-547 args: argparse.Namespace,
-548 pex_context: KLayoutExtractionContext,
-549 lst_file: str):
-550 rule('FastCap2 Execution')
-551 exe_path = "fastcap"
-552 log_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FastCap2_Output.txt")
-553 raw_csv_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FastCap2_Result_Matrix_Raw.csv")
-554 avg_csv_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FastCap2_Result_Matrix_Avg.csv")
-555 expanded_netlist_path = os.path.join(args.output_dir_path,
-556 f"{args.effective_cell_name}_FastCap2_Expanded_Netlist.cir")
-557 reduced_netlist_path = os.path.join(args.output_dir_path,
-558 f"{args.effective_cell_name}_FastCap2_Reduced_Netlist.cir")
- -560 run_fastcap(exe_path=exe_path,
-561 lst_file_path=lst_file,
-562 log_path=log_path)
- -564 cap_matrix = fastcap_parse_capacitance_matrix(log_path)
-565 cap_matrix.write_csv(raw_csv_path)
- -567 cap_matrix = cap_matrix.averaged_off_diagonals()
-568 cap_matrix.write_csv(avg_csv_path)
- -570 netlist_expander = NetlistExpander()
-571 expanded_netlist = netlist_expander.expand(
-572 extracted_netlist=pex_context.lvsdb.netlist(),
-573 top_cell_name=pex_context.top_cell.name,
-574 cap_matrix=cap_matrix,
-575 blackbox_devices=args.blackbox_devices
-576 )
- -578 spice_writer = kdb.NetlistSpiceWriter()
-579 spice_writer.use_net_names = True
-580 spice_writer.with_comments = False
-581 expanded_netlist.write(expanded_netlist_path, spice_writer)
-582 info(f"Wrote expanded netlist to: {expanded_netlist_path}")
- -584 netlist_reducer = NetlistReducer()
-585 reduced_netlist = netlist_reducer.reduce(netlist=expanded_netlist,
-586 top_cell_name=pex_context.top_cell.name)
-587 reduced_netlist.write(reduced_netlist_path, spice_writer)
-588 info(f"Wrote reduced netlist to: {reduced_netlist_path}")
- -590 def run_kpex_2_5d_engine(self,
-591 args: argparse.Namespace,
-592 pex_context: KLayoutExtractionContext,
-593 tech_info: TechInfo,
-594 report_path: str,
-595 netlist_csv_path: str):
-596 extractor = RCExtractor(pex_context=pex_context,
-597 tech_info=tech_info,
-598 report_path=report_path)
-599 extraction_results = extractor.extract()
- -601 with open(netlist_csv_path, 'w') as f:
-602 f.write('Device;Net1;Net2;Capacitance [fF]\n')
-603 # f.write('Device;Net1;Net2;Capacitance [F];Capacitance [fF]\n')
-604 summary = extraction_results.summarize()
-605 for idx, (key, cap_value) in enumerate(summary.capacitances.items()):
-606 # f.write(f"C{idx + 1};{key.net1};{key.net2};{cap_value / 1e15};{round(cap_value, 3)}\n")
-607 f.write(f"C{idx + 1};{key.net1};{key.net2};{round(cap_value, 3)}\n")
- -609 rule("kpex/2.5D extracted netlist (CSV format):")
-610 with open(netlist_csv_path, 'r') as f:
-611 for line in f.readlines():
-612 subproc(line[:-1]) # abusing subproc, simply want verbatim
- -614 rule("Extracted netlist CSV")
-615 subproc(f"{netlist_csv_path}")
- +517 self._fastercap_extracted_csv_path = expanded_netlist_csv_path
+ +519 def run_magic_extraction(self,
+520 args: argparse.Namespace):
+521 if args.input_mode != InputMode.GDS:
+522 error(f"MAGIC engine only works with GDS input mode"
+523 f" (currently {args.input_mode})")
+524 return
+ +526 magic_run_dir = os.path.join(args.output_dir_path, f"magic_{args.magic_pex_mode}")
+527 magic_log_path = os.path.join(magic_run_dir, f"{args.effective_cell_name}_MAGIC_CC_Output.txt")
+528 magic_script_path = os.path.join(magic_run_dir, f"{args.effective_cell_name}_MAGIC_CC_Script.tcl")
+ +530 output_netlist_path = f"{magic_run_dir}/{args.effective_cell_name}.pex.spice"
+ +532 os.makedirs(magic_run_dir, exist_ok=True)
+ +534 prepare_magic_script(gds_path=args.effective_gds_path,
+535 cell_name=args.effective_cell_name,
+536 run_dir_path=magic_run_dir,
+537 script_path=magic_script_path,
+538 output_netlist_path=output_netlist_path,
+539 pex_mode=args.magic_pex_mode,
+540 c_threshold=args.magic_cthresh,
+541 r_threshold=args.magic_rthresh,
+542 halo=args.magic_halo)
+ +544 run_magic(exe_path=args.magic_exe_path,
+545 magicrc_path=args.magicrc_path,
+546 script_path=magic_script_path,
+547 log_path=magic_log_path)
+ +549 subproc(f"SPICE netlist saved at: {output_netlist_path}")
+550 rule("MAGIC PEX SPICE netlist")
+551 with open(output_netlist_path, 'r') as f:
+552 subproc(f.read())
+553 rule()
+ +555 def run_fastcap_extraction(self,
+556 args: argparse.Namespace,
+557 pex_context: KLayoutExtractionContext,
+558 lst_file: str):
+559 rule('FastCap2 Execution')
+560 exe_path = "fastcap"
+561 log_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FastCap2_Output.txt")
+562 raw_csv_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FastCap2_Result_Matrix_Raw.csv")
+563 avg_csv_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_FastCap2_Result_Matrix_Avg.csv")
+564 expanded_netlist_path = os.path.join(args.output_dir_path,
+565 f"{args.effective_cell_name}_FastCap2_Expanded_Netlist.cir")
+566 reduced_netlist_path = os.path.join(args.output_dir_path,
+567 f"{args.effective_cell_name}_FastCap2_Reduced_Netlist.cir")
+ +569 run_fastcap(exe_path=exe_path,
+570 lst_file_path=lst_file,
+571 log_path=log_path)
+ +573 cap_matrix = fastcap_parse_capacitance_matrix(log_path)
+574 cap_matrix.write_csv(raw_csv_path)
+ +576 cap_matrix = cap_matrix.averaged_off_diagonals()
+577 cap_matrix.write_csv(avg_csv_path)
+ +579 netlist_expander = NetlistExpander()
+580 expanded_netlist = netlist_expander.expand(
+581 extracted_netlist=pex_context.lvsdb.netlist(),
+582 top_cell_name=pex_context.top_cell.name,
+583 cap_matrix=cap_matrix,
+584 blackbox_devices=args.blackbox_devices
+585 )
+ +587 spice_writer = kdb.NetlistSpiceWriter()
+588 spice_writer.use_net_names = True
+589 spice_writer.with_comments = False
+590 expanded_netlist.write(expanded_netlist_path, spice_writer)
+591 info(f"Wrote expanded netlist to: {expanded_netlist_path}")
+ +593 netlist_reducer = NetlistReducer()
+594 reduced_netlist = netlist_reducer.reduce(netlist=expanded_netlist,
+595 top_cell_name=pex_context.top_cell.name)
+596 reduced_netlist.write(reduced_netlist_path, spice_writer)
+597 info(f"Wrote reduced netlist to: {reduced_netlist_path}")
+ +599 def run_kpex_2_5d_engine(self,
+600 args: argparse.Namespace,
+601 pex_context: KLayoutExtractionContext,
+602 tech_info: TechInfo,
+603 report_path: str,
+604 netlist_csv_path: str):
+605 extractor = RCExtractor(pex_context=pex_context,
+606 tech_info=tech_info,
+607 report_path=report_path)
+608 extraction_results = extractor.extract()
+ +610 with open(netlist_csv_path, 'w') as f:
+611 f.write('Device;Net1;Net2;Capacitance [fF]\n')
+612 # f.write('Device;Net1;Net2;Capacitance [F];Capacitance [fF]\n')
+613 summary = extraction_results.summarize()
+614 for idx, (key, cap_value) in enumerate(summary.capacitances.items()):
+615 # f.write(f"C{idx + 1};{key.net1};{key.net2};{cap_value / 1e15};{round(cap_value, 3)}\n")
+616 f.write(f"C{idx + 1};{key.net1};{key.net2};{round(cap_value, 3)}\n")
-618 # NOTE: there was a KLayout bug that some of the categories were lost,
-619 # so that the marker browser could not load the report file
-620 try:
-621 report = rdb.ReportDatabase('')
-622 report.load(report_path) # try loading rdb
-623 except Exception as e:
-624 rule("Repair broken marker DB")
-625 warning(f"Detected KLayout bug: RDB can't be loaded due to exception {e}")
-626 repair_rdb(report_path)
- -628 return extraction_results
- -630 def setup_logging(self, args: argparse.Namespace):
-631 def register_log_file_handler(log_path: str,
-632 formatter: Optional[logging.Formatter]) -> logging.Handler:
-633 handler = logging.FileHandler(log_path)
-634 handler.setLevel(LogLevel.SUBPROCESS)
-635 if formatter:
-636 handler.setFormatter(formatter)
-637 register_additional_handler(handler)
-638 return handler
- -640 def reregister_log_file_handler(handler: logging.Handler,
-641 log_path: str,
-642 formatter: Optional[logging.Formatter]):
-643 deregister_additional_handler(handler)
-644 handler.flush()
-645 handler.close()
-646 os.makedirs(args.output_dir_path, exist_ok=True)
-647 new_path = os.path.join(args.output_dir_path, os.path.basename(log_path))
-648 if os.path.exists(new_path):
-649 ctime = os.path.getctime(new_path)
-650 dt = datetime.fromtimestamp(ctime)
-651 timestamp = dt.strftime('%Y-%m-%d_%H-%M-%S')
-652 backup_path = f"{new_path[:-4]}_{timestamp}.bak.log"
-653 shutil.move(new_path, backup_path)
-654 log_path = shutil.move(log_path, new_path)
-655 register_log_file_handler(log_path, formatter)
- -657 # setup preliminary logger
-658 cli_log_path_plain = os.path.join(args.output_dir_base_path, f"kpex_plain.log")
-659 cli_log_path_formatted = os.path.join(args.output_dir_base_path, f"kpex.log")
-660 formatter = logging.Formatter('[%(asctime)s] [%(levelname)s] %(message)s')
-661 file_handler_plain = register_log_file_handler(cli_log_path_plain, None)
-662 file_handler_formatted = register_log_file_handler(cli_log_path_formatted, formatter)
-663 try:
-664 self.validate_args(args)
-665 except ArgumentValidationError:
-666 if hasattr(args, 'output_dir_path'):
-667 reregister_log_file_handler(file_handler_plain, cli_log_path_plain, None)
-668 reregister_log_file_handler(file_handler_formatted, cli_log_path_formatted, formatter)
-669 sys.exit(1)
-670 reregister_log_file_handler(file_handler_plain, cli_log_path_plain, None)
-671 reregister_log_file_handler(file_handler_formatted, cli_log_path_formatted, formatter)
- -673 set_log_level(args.log_level)
- -675 @staticmethod
-676 def modification_date(filename: str) -> datetime:
-677 t = os.path.getmtime(filename)
-678 return datetime.fromtimestamp(t)
- -680 def create_lvsdb(self, args: argparse.Namespace) -> kdb.LayoutVsSchematic:
-681 lvsdb = kdb.LayoutVsSchematic()
- -683 match args.input_mode:
-684 case InputMode.LVSDB:
-685 lvsdb.read(args.lvsdb_path)
-686 case InputMode.GDS:
-687 lvs_log_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_lvs.log")
-688 lvsdb_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}.lvsdb.gz")
-689 lvsdb_cache_path = os.path.join(args.cache_dir_path, args.pdk,
-690 os.path.splitroot(os.path.abspath(args.gds_path))[-1],
-691 f"{args.effective_cell_name}.lvsdb.gz")
- -693 lvs_needed = True
- -695 if args.cache_lvs:
-696 if not os.path.exists(lvsdb_cache_path):
-697 info(f"Cache miss: extracted LVSDB does not exist")
-698 subproc(lvsdb_cache_path)
-699 elif self.modification_date(lvsdb_cache_path) <= self.modification_date(args.gds_path):
-700 info(f"Cache miss: extracted LVSDB is older than the input GDS")
-701 subproc(lvsdb_cache_path)
-702 else:
-703 warning(f"Cache hit: Reusing cached LVSDB")
-704 subproc(lvsdb_cache_path)
-705 lvs_needed = False
- -707 if lvs_needed:
-708 lvs_runner = LVSRunner()
-709 lvs_runner.run_klayout_lvs(exe_path=args.klayout_exe_path,
-710 lvs_script=args.lvs_script_path,
-711 gds_path=args.effective_gds_path,
-712 schematic_path=args.effective_schematic_path,
-713 log_path=lvs_log_path,
-714 lvsdb_path=lvsdb_path)
-715 if args.cache_lvs:
-716 cache_dir_path = os.path.dirname(lvsdb_cache_path)
-717 if not os.path.exists(cache_dir_path):
-718 os.makedirs(cache_dir_path, exist_ok=True)
-719 shutil.copy(lvsdb_path, lvsdb_cache_path)
- -721 lvsdb.read(lvsdb_path)
-722 return lvsdb
- -724 def main(self, argv: List[str]):
-725 if '-v' not in argv and \
-726 '--version' not in argv and \
-727 '-h' not in argv and \
-728 '--help' not in argv:
-729 rule('Command line arguments')
-730 subproc(' '.join(map(shlex.quote, sys.argv)))
- -732 args = self.parse_args(argv[1:])
- -734 os.makedirs(args.output_dir_base_path, exist_ok=True)
-735 self.setup_logging(args)
- -737 tech_info = TechInfo.from_json(args.tech_pbjson_path,
-738 dielectric_filter=args.dielectric_filter)
- -740 if args.run_magic:
-741 rule('MAGIC')
-742 self.run_magic_extraction(args)
- -744 # no need to run LVS etc if only running magic engine
-745 if not (args.run_fastcap or args.run_fastercap or args.run_2_5D):
-746 return
- -748 rule('Prepare LVSDB')
-749 lvsdb = self.create_lvsdb(args)
- -751 pex_context = KLayoutExtractionContext.prepare_extraction(top_cell=args.effective_cell_name,
-752 lvsdb=lvsdb,
-753 tech=tech_info,
-754 blackbox_devices=args.blackbox_devices)
-755 rule('Non-empty layers in LVS database')
-756 for gds_pair, layer_info in pex_context.extracted_layers.items():
-757 names = [l.lvs_layer_name for l in layer_info.source_layers]
-758 info(f"{gds_pair} -> ({' '.join(names)})")
+618 rule("kpex/2.5D extracted netlist (CSV format):")
+619 with open(netlist_csv_path, 'r') as f:
+620 for line in f.readlines():
+621 subproc(line[:-1]) # abusing subproc, simply want verbatim
+ +623 rule("Extracted netlist CSV")
+624 subproc(f"{netlist_csv_path}")
+ + +627 # NOTE: there was a KLayout bug that some of the categories were lost,
+628 # so that the marker browser could not load the report file
+629 try:
+630 report = rdb.ReportDatabase('')
+631 report.load(report_path) # try loading rdb
+632 except Exception as e:
+633 rule("Repair broken marker DB")
+634 warning(f"Detected KLayout bug: RDB can't be loaded due to exception {e}")
+635 repair_rdb(report_path)
+ +637 return extraction_results
+ +639 def setup_logging(self, args: argparse.Namespace):
+640 def register_log_file_handler(log_path: str,
+641 formatter: Optional[logging.Formatter]) -> logging.Handler:
+642 handler = logging.FileHandler(log_path)
+643 handler.setLevel(LogLevel.SUBPROCESS)
+644 if formatter:
+645 handler.setFormatter(formatter)
+646 register_additional_handler(handler)
+647 return handler
+ +649 def reregister_log_file_handler(handler: logging.Handler,
+650 log_path: str,
+651 formatter: Optional[logging.Formatter]):
+652 deregister_additional_handler(handler)
+653 handler.flush()
+654 handler.close()
+655 os.makedirs(args.output_dir_path, exist_ok=True)
+656 new_path = os.path.join(args.output_dir_path, os.path.basename(log_path))
+657 if os.path.exists(new_path):
+658 ctime = os.path.getctime(new_path)
+659 dt = datetime.fromtimestamp(ctime)
+660 timestamp = dt.strftime('%Y-%m-%d_%H-%M-%S')
+661 backup_path = f"{new_path[:-4]}_{timestamp}.bak.log"
+662 shutil.move(new_path, backup_path)
+663 log_path = shutil.move(log_path, new_path)
+664 register_log_file_handler(log_path, formatter)
+ +666 # setup preliminary logger
+667 cli_log_path_plain = os.path.join(args.output_dir_base_path, f"kpex_plain.log")
+668 cli_log_path_formatted = os.path.join(args.output_dir_base_path, f"kpex.log")
+669 formatter = logging.Formatter('[%(asctime)s] [%(levelname)s] %(message)s')
+670 file_handler_plain = register_log_file_handler(cli_log_path_plain, None)
+671 file_handler_formatted = register_log_file_handler(cli_log_path_formatted, formatter)
+672 try:
+673 self.validate_args(args)
+674 except ArgumentValidationError:
+675 if hasattr(args, 'output_dir_path'):
+676 reregister_log_file_handler(file_handler_plain, cli_log_path_plain, None)
+677 reregister_log_file_handler(file_handler_formatted, cli_log_path_formatted, formatter)
+678 sys.exit(1)
+679 reregister_log_file_handler(file_handler_plain, cli_log_path_plain, None)
+680 reregister_log_file_handler(file_handler_formatted, cli_log_path_formatted, formatter)
+ +682 set_log_level(args.log_level)
+ +684 @staticmethod
+685 def modification_date(filename: str) -> datetime:
+686 t = os.path.getmtime(filename)
+687 return datetime.fromtimestamp(t)
+ +689 def create_lvsdb(self, args: argparse.Namespace) -> kdb.LayoutVsSchematic:
+690 lvsdb = kdb.LayoutVsSchematic()
+ +692 match args.input_mode:
+693 case InputMode.LVSDB:
+694 lvsdb.read(args.lvsdb_path)
+695 case InputMode.GDS:
+696 lvs_log_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_lvs.log")
+697 lvsdb_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}.lvsdb.gz")
+698 lvsdb_cache_path = os.path.join(args.cache_dir_path, args.pdk,
+699 os.path.splitroot(os.path.abspath(args.gds_path))[-1],
+700 f"{args.effective_cell_name}.lvsdb.gz")
+ +702 lvs_needed = True
+ +704 if args.cache_lvs:
+705 if not os.path.exists(lvsdb_cache_path):
+706 info(f"Cache miss: extracted LVSDB does not exist")
+707 subproc(lvsdb_cache_path)
+708 elif self.modification_date(lvsdb_cache_path) <= self.modification_date(args.gds_path):
+709 info(f"Cache miss: extracted LVSDB is older than the input GDS")
+710 subproc(lvsdb_cache_path)
+711 else:
+712 warning(f"Cache hit: Reusing cached LVSDB")
+713 subproc(lvsdb_cache_path)
+714 lvs_needed = False
+ +716 if lvs_needed:
+717 lvs_runner = LVSRunner()
+718 lvs_runner.run_klayout_lvs(exe_path=args.klayout_exe_path,
+719 lvs_script=args.lvs_script_path,
+720 gds_path=args.effective_gds_path,
+721 schematic_path=args.effective_schematic_path,
+722 log_path=lvs_log_path,
+723 lvsdb_path=lvsdb_path)
+724 if args.cache_lvs:
+725 cache_dir_path = os.path.dirname(lvsdb_cache_path)
+726 if not os.path.exists(cache_dir_path):
+727 os.makedirs(cache_dir_path, exist_ok=True)
+728 shutil.copy(lvsdb_path, lvsdb_cache_path)
+ +730 lvsdb.read(lvsdb_path)
+731 return lvsdb
+ +733 def main(self, argv: List[str]):
+734 if '-v' not in argv and \
+735 '--version' not in argv and \
+736 '-h' not in argv and \
+737 '--help' not in argv:
+738 rule('Command line arguments')
+739 subproc(' '.join(map(shlex.quote, sys.argv)))
+ +741 args = self.parse_args(argv[1:])
+ +743 os.makedirs(args.output_dir_base_path, exist_ok=True)
+744 self.setup_logging(args)
+ +746 tech_info = TechInfo.from_json(args.tech_pbjson_path,
+747 dielectric_filter=args.dielectric_filter)
+ +749 if args.run_magic:
+750 rule('MAGIC')
+751 self.run_magic_extraction(args)
+ +753 # no need to run LVS etc if only running magic engine
+754 if not (args.run_fastcap or args.run_fastercap or args.run_2_5D):
+755 return
+ +757 rule('Prepare LVSDB')
+758 lvsdb = self.create_lvsdb(args)
-760 gds_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_l2n_extracted.gds.gz")
-761 pex_context.target_layout.write(gds_path)
- -763 gds_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_l2n_internal.gds.gz")
-764 pex_context.lvsdb.internal_layout().write(gds_path)
- -766 def dump_layers(cell: str,
-767 layers: List[KLayoutExtractedLayerInfo],
-768 layout_dump_path: str):
-769 layout = kdb.Layout()
-770 layout.dbu = lvsdb.internal_layout().dbu
+760 pex_context = KLayoutExtractionContext.prepare_extraction(top_cell=args.effective_cell_name,
+761 lvsdb=lvsdb,
+762 tech=tech_info,
+763 blackbox_devices=args.blackbox_devices)
+764 rule('Non-empty layers in LVS database')
+765 for gds_pair, layer_info in pex_context.extracted_layers.items():
+766 names = [l.lvs_layer_name for l in layer_info.source_layers]
+767 info(f"{gds_pair} -> ({' '.join(names)})")
+ +769 gds_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_l2n_extracted.gds.gz")
+770 pex_context.target_layout.write(gds_path)
-772 top_cell = layout.create_cell(cell)
-773 for ulyr in layers:
-774 li = kdb.LayerInfo(*ulyr.gds_pair)
-775 li.name = ulyr.lvs_layer_name
-776 layer = layout.insert_layer(li)
-777 layout.insert(top_cell.cell_index(), layer, ulyr.region.dup())
- -779 layout.write(layout_dump_path)
+772 gds_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_l2n_internal.gds.gz")
+773 pex_context.lvsdb.internal_layout().write(gds_path)
+ +775 def dump_layers(cell: str,
+776 layers: List[KLayoutExtractedLayerInfo],
+777 layout_dump_path: str):
+778 layout = kdb.Layout()
+779 layout.dbu = lvsdb.internal_layout().dbu
-781 if len(pex_context.unnamed_layers) >= 1:
-782 layout_dump_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_unnamed_LVS_layers.gds.gz")
-783 dump_layers(cell=args.effective_cell_name,
-784 layers=pex_context.unnamed_layers,
-785 layout_dump_path=layout_dump_path)
- -787 if len(pex_context.extracted_layers) >= 1:
-788 layout_dump_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_nonempty_LVS_layers.gds.gz")
-789 nonempty_layers = [l \
-790 for layers in pex_context.extracted_layers.values() \
-791 for l in layers.source_layers]
-792 dump_layers(cell=args.effective_cell_name,
-793 layers=nonempty_layers,
+781 top_cell = layout.create_cell(cell)
+782 for ulyr in layers:
+783 li = kdb.LayerInfo(*ulyr.gds_pair)
+784 li.name = ulyr.lvs_layer_name
+785 layer = layout.insert_layer(li)
+786 layout.insert(top_cell.cell_index(), layer, ulyr.region.dup())
+ +788 layout.write(layout_dump_path)
+ +790 if len(pex_context.unnamed_layers) >= 1:
+791 layout_dump_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_unnamed_LVS_layers.gds.gz")
+792 dump_layers(cell=args.effective_cell_name,
+793 layers=pex_context.unnamed_layers,
794 layout_dump_path=layout_dump_path)
-795 else:
-796 error("No extracted layers found")
-797 sys.exit(1)
- -799 if args.run_fastcap or args.run_fastercap:
-800 lst_file = self.build_fastercap_input(args=args,
-801 pex_context=pex_context,
-802 tech_info=tech_info)
-803 if args.run_fastercap:
-804 self.run_fastercap_extraction(args=args,
-805 pex_context=pex_context,
-806 lst_file=lst_file)
-807 if args.run_fastcap:
-808 self.run_fastcap_extraction(args=args,
-809 pex_context=pex_context,
-810 lst_file=lst_file)
- -812 if args.run_2_5D:
-813 rule("kpex/2.5D PEX Engine")
-814 report_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_k25d_pex_report.rdb.gz")
-815 netlist_csv_path = os.path.abspath(os.path.join(args.output_dir_path, f"{args.effective_cell_name}_k25d_pex_netlist.csv"))
- -817 self._rcx25_extraction_results = self.run_kpex_2_5d_engine( # NOTE: store for test case
-818 args=args,
-819 pex_context=pex_context,
-820 tech_info=tech_info,
-821 report_path=report_path,
-822 netlist_csv_path=netlist_csv_path
-823 )
- -825 self._rcx25_extracted_csv_path = netlist_csv_path
- -827 @property
-828 def rcx25_extraction_results(self) -> ExtractionResults:
-829 if not hasattr(self, '_rcx25_extraction_results'):
-830 raise Exception('rcx25_extraction_results is not initialized, was run_kpex_2_5d_engine called?')
-831 return self._rcx25_extraction_results
- -833 @property
-834 def rcx25_extracted_csv_path(self) -> str:
-835 if not hasattr(self, '_rcx25_extracted_csv_path'):
-836 raise Exception('rcx25_extracted_csv_path is not initialized, was run_kpex_2_5d_engine called?')
-837 return self._rcx25_extracted_csv_path
- -839 @property
-840 def fastercap_extracted_csv_path(self) -> str:
-841 if not hasattr(self, '_fastercap_extracted_csv_path'):
-842 raise Exception('fastercap_extracted_csv_path is not initialized, was run_fastercap_extraction called?')
-843 return self._fastercap_extracted_csv_path
- - -846if __name__ == "__main__":
-847 cli = KpexCLI()
-848 cli.main(sys.argv)
+ +796 if len(pex_context.extracted_layers) >= 1:
+797 layout_dump_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_nonempty_LVS_layers.gds.gz")
+798 nonempty_layers = [l \
+799 for layers in pex_context.extracted_layers.values() \
+800 for l in layers.source_layers]
+801 dump_layers(cell=args.effective_cell_name,
+802 layers=nonempty_layers,
+803 layout_dump_path=layout_dump_path)
+804 else:
+805 error("No extracted layers found")
+806 sys.exit(1)
+ +808 if args.run_fastcap or args.run_fastercap:
+809 lst_file = self.build_fastercap_input(args=args,
+810 pex_context=pex_context,
+811 tech_info=tech_info)
+812 if args.run_fastercap:
+813 self.run_fastercap_extraction(args=args,
+814 pex_context=pex_context,
+815 lst_file=lst_file)
+816 if args.run_fastcap:
+817 self.run_fastcap_extraction(args=args,
+818 pex_context=pex_context,
+819 lst_file=lst_file)
+ +821 if args.run_2_5D:
+822 rule("kpex/2.5D PEX Engine")
+823 report_path = os.path.join(args.output_dir_path, f"{args.effective_cell_name}_k25d_pex_report.rdb.gz")
+824 netlist_csv_path = os.path.abspath(os.path.join(args.output_dir_path, f"{args.effective_cell_name}_k25d_pex_netlist.csv"))
+ +826 self._rcx25_extraction_results = self.run_kpex_2_5d_engine( # NOTE: store for test case
+827 args=args,
+828 pex_context=pex_context,
+829 tech_info=tech_info,
+830 report_path=report_path,
+831 netlist_csv_path=netlist_csv_path
+832 )
+ +834 self._rcx25_extracted_csv_path = netlist_csv_path
+ +836 @property
+837 def rcx25_extraction_results(self) -> ExtractionResults:
+838 if not hasattr(self, '_rcx25_extraction_results'):
+839 raise Exception('rcx25_extraction_results is not initialized, was run_kpex_2_5d_engine called?')
+840 return self._rcx25_extraction_results
+ +842 @property
+843 def rcx25_extracted_csv_path(self) -> str:
+844 if not hasattr(self, '_rcx25_extracted_csv_path'):
+845 raise Exception('rcx25_extracted_csv_path is not initialized, was run_kpex_2_5d_engine called?')
+846 return self._rcx25_extracted_csv_path
+ +848 @property
+849 def fastercap_extracted_csv_path(self) -> str:
+850 if not hasattr(self, '_fastercap_extracted_csv_path'):
+851 raise Exception('fastercap_extracted_csv_path is not initialized, was run_fastercap_extraction called?')
+852 return self._fastercap_extracted_csv_path
+ + +855if __name__ == "__main__":
+856 cli = KpexCLI()
+857 cli.main(sys.argv)
diff --git a/pycov/z_9c05d59a441cce45_pdk_config_py.html b/pycov/z_9c05d59a441cce45_pdk_config_py.html index fe2c4bf2..806ae99a 100644 --- a/pycov/z_9c05d59a441cce45_pdk_config_py.html +++ b/pycov/z_9c05d59a441cce45_pdk_config_py.html @@ -65,7 +65,7 @@@@ -65,7 +65,7 @@
78 @cached_property
79 def canonical_layer_name_by_gds_pair(self) -> Dict[GDSPair, str]:
-80 return {
+80 return {
81 (lyr.layer_info.gds_layer, lyr.layer_info.gds_datatype): lyr.original_layer_name
82 for lyr in self.tech.lvs_computed_layers
83 }
@@ -170,7 +170,7 @@89 @cached_property
90 def gds_pair_for_layer_name(self) -> Dict[str, GDSPair]:
-91 return {lyr.name: (lyr.gds_layer, lyr.gds_datatype) for lyr in self.tech.layers}
+91 return {lyr.name: (lyr.gds_layer, lyr.gds_datatype) for lyr in self.tech.layers}
93 @cached_property
94 def layer_info_by_gds_pair(self) -> Dict[GDSPair, tech_pb2.LayerInfo]:
@@ -189,103 +189,103 @@108 @cached_property
109 def process_substrate_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo:
-110 return list(
+110 return list(
111 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SUBSTRATE,
112 self.tech.process_stack.layers)
113 )[0]
115 @cached_property
116 def process_diffusion_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]:
-117 return list(
+117 return list(
118 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_DIFFUSION,
119 self.tech.process_stack.layers)
120 )
122 @cached_property
123 def gate_poly_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo:
-124 return self.process_metal_layers[0]
+124 return self.process_metal_layers[0]
126 @cached_property
127 def field_oxide_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo:
-128 return list(
+128 return list(
129 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_FIELD_OXIDE,
130 self.tech.process_stack.layers)
131 )[0]
133 @cached_property
134 def process_metal_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]:
-135 return list(
+135 return list(
136 filter(lambda lyr: lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL,
137 self.tech.process_stack.layers)
138 )
140 @cached_property
141 def filtered_dielectric_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]:
-142 layers = []
-143 for pl in self.tech.process_stack.layers:
-144 match pl.layer_type:
-145 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC | \
+142 layers = []
+143 for pl in self.tech.process_stack.layers:
+144 match pl.layer_type:
+145 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC | \
146 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC | \
147 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
-148 if self.dielectric_filter.is_included(pl.name):
-149 layers.append(pl)
-150 return layers
+148 if self.dielectric_filter.is_included(pl.name):
+149 layers.append(pl)
+150 return layers
152 @cached_property
153 def dielectric_by_name(self) -> Dict[str, float]:
-154 diel_by_name = {}
-155 for pl in self.filtered_dielectric_layers:
-156 match pl.layer_type:
-157 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC:
-158 diel_by_name[pl.name] = pl.simple_dielectric_layer.dielectric_k
-159 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC:
-160 diel_by_name[pl.name] = pl.conformal_dielectric_layer.dielectric_k
-161 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
-162 diel_by_name[pl.name] = pl.sidewall_dielectric_layer.dielectric_k
-163 return diel_by_name
+154 diel_by_name = {}
+155 for pl in self.filtered_dielectric_layers:
+156 match pl.layer_type:
+157 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC:
+158 diel_by_name[pl.name] = pl.simple_dielectric_layer.dielectric_k
+159 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC:
+160 diel_by_name[pl.name] = pl.conformal_dielectric_layer.dielectric_k
+161 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
+162 diel_by_name[pl.name] = pl.sidewall_dielectric_layer.dielectric_k
+163 return diel_by_name
165 def sidewall_dielectric_layer(self, layer_name: str) -> Optional[process_stack_pb2.ProcessStackInfo.LayerInfo]:
-166 found_layers: List[process_stack_pb2.ProcessStackInfo.LayerInfo] = []
-167 for lyr in self.filtered_dielectric_layers:
-168 match lyr.layer_type:
-169 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
-170 if lyr.sidewall_dielectric_layer.reference == layer_name:
+166 found_layers: List[process_stack_pb2.ProcessStackInfo.LayerInfo] = []
+167 for lyr in self.filtered_dielectric_layers:
+168 match lyr.layer_type:
+169 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
+170 if lyr.sidewall_dielectric_layer.reference == layer_name:
171 found_layers.append(lyr)
-172 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC:
-173 if lyr.conformal_dielectric_layer.reference == layer_name:
-174 found_layers.append(lyr)
-175 case _:
-176 continue
+172 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC:
+173 if lyr.conformal_dielectric_layer.reference == layer_name:
+174 found_layers.append(lyr)
+175 case _:
+176 continue
-178 if len(found_layers) == 0:
-179 return None
-180 if len(found_layers) >= 2:
+178 if len(found_layers) == 0:
+179 return None
+180 if len(found_layers) >= 2:
181 raise Exception(f"found multiple sidewall dielectric layers for {layer_name}")
-182 return found_layers[0]
+182 return found_layers[0]
184 def simple_dielectric_above_metal(self, layer_name: str) -> Tuple[Optional[process_stack_pb2.ProcessStackInfo.LayerInfo], float]:
185 """
186 Returns a tuple of the dielectric layer and it's (maximum) height.
187 Maximum would be the case where no metal and other dielectrics are present.
188 """
-189 found_layer: Optional[process_stack_pb2.ProcessStackInfo.LayerInfo] = None
-190 diel_lyr: Optional[process_stack_pb2.ProcessStackInfo.LayerInfo] = None
-191 for lyr in self.tech.process_stack.layers:
-192 if lyr.name == layer_name:
-193 found_layer = lyr
-194 elif found_layer:
-195 if not diel_lyr and lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC:
-196 if not self.dielectric_filter.is_included(lyr.name):
+189 found_layer: Optional[process_stack_pb2.ProcessStackInfo.LayerInfo] = None
+190 diel_lyr: Optional[process_stack_pb2.ProcessStackInfo.LayerInfo] = None
+191 for lyr in self.tech.process_stack.layers:
+192 if lyr.name == layer_name:
+193 found_layer = lyr
+194 elif found_layer:
+195 if not diel_lyr and lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC:
+196 if not self.dielectric_filter.is_included(lyr.name):
197 return None, 0.0
-198 diel_lyr = lyr
+198 diel_lyr = lyr
199 # search for next metal or end of stack
-200 if lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL:
-201 return diel_lyr, lyr.metal_layer.height - found_layer.metal_layer.height
-202 return diel_lyr, 5.0 # air TODO
+200 if lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL:
+201 return diel_lyr, lyr.metal_layer.height - found_layer.metal_layer.height
+202 return diel_lyr, 5.0 # air TODO
204 @cached_property
205 def substrate_cap_by_layer_name(self) -> Dict[str, process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance]:
-206 return {sc.layer_name: sc for sc in self.tech.process_parasitics.capacitance.substrates}
+206 return {sc.layer_name: sc for sc in self.tech.process_parasitics.capacitance.substrates}
208 @cached_property
209 def overlap_cap_by_layer_names(self) -> Dict[str, Dict[str, process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance]]:
@@ -293,21 +293,21 @@211 usage: dict[top_layer_name][bottom_layer_name]
212 """
-214 def convert_substrate_to_overlap_cap(sc: process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance) \
+214 def convert_substrate_to_overlap_cap(sc: process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance) \
215 -> process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance:
-216 oc = process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance()
-217 oc.top_layer_name = sc.layer_name
-218 oc.bottom_layer_name = self.internal_substrate_layer_name
-219 oc.capacitance = sc.area_capacitance
-220 return oc
+216 oc = process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance()
+217 oc.top_layer_name = sc.layer_name
+218 oc.bottom_layer_name = self.internal_substrate_layer_name
+219 oc.capacitance = sc.area_capacitance
+220 return oc
-222 d = {
+222 d = {
223 ln: {
224 self.internal_substrate_layer_name: convert_substrate_to_overlap_cap(sc)
225 } for ln, sc in self.substrate_cap_by_layer_name.items()
226 }
-228 d2 = {
+228 d2 = {
229 oc.top_layer_name: {
230 oc_bot.bottom_layer_name: oc_bot
231 for oc_bot in self.tech.process_parasitics.capacitance.overlaps if oc_bot.top_layer_name == oc.top_layer_name
@@ -315,22 +315,22 @@233 for oc in self.tech.process_parasitics.capacitance.overlaps
234 }
-236 for k1, ve in d2.items():
-237 for k2, v in ve.items():
-238 if k1 not in d:
-239 d[k1] = {k2: v}
+236 for k1, ve in d2.items():
+237 for k2, v in ve.items():
+238 if k1 not in d:
+239 d[k1] = {k2: v}
240 else:
-241 d[k1][k2] = v
-242 return d
+241 d[k1][k2] = v
+242 return d
244 @cached_property
245 def sidewall_cap_by_layer_name(self) -> Dict[str, process_parasitics_pb2.CapacitanceInfo.SidewallCapacitance]:
-246 return {sc.layer_name: sc for sc in self.tech.process_parasitics.capacitance.sidewalls}
+246 return {sc.layer_name: sc for sc in self.tech.process_parasitics.capacitance.sidewalls}
248 @classmethod
249 @property
250 def internal_substrate_layer_name(cls) -> str:
-251 return 'VSUBS'
+251 return 'VSUBS'
253 @cached_property
254 def side_overlap_cap_by_layer_names(self) -> Dict[str, Dict[str, process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance]]:
@@ -338,21 +338,21 @@256 usage: dict[in_layer_name][out_layer_name]
257 """
-259 def convert_substrate_to_side_overlap_cap(sc: process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance) \
+259 def convert_substrate_to_side_overlap_cap(sc: process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance) \
260 -> process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance:
-261 soc = process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance()
-262 soc.in_layer_name = sc.layer_name
-263 soc.out_layer_name = self.internal_substrate_layer_name
-264 soc.capacitance = sc.perimeter_capacitance
-265 return soc
+261 soc = process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance()
+262 soc.in_layer_name = sc.layer_name
+263 soc.out_layer_name = self.internal_substrate_layer_name
+264 soc.capacitance = sc.perimeter_capacitance
+265 return soc
-267 d = {
+267 d = {
268 ln: {
269 self.internal_substrate_layer_name: convert_substrate_to_side_overlap_cap(sc)
270 } for ln, sc in self.substrate_cap_by_layer_name.items()
271 }
-273 d2 = {
+273 d2 = {
274 oc.in_layer_name: {
275 oc_bot.out_layer_name: oc_bot
276 for oc_bot in self.tech.process_parasitics.capacitance.sideoverlaps if oc_bot.in_layer_name == oc.in_layer_name
@@ -360,11 +360,11 @@278 for oc in self.tech.process_parasitics.capacitance.sideoverlaps
279 }
-281 for k1, ve in d2.items():
-282 for k2, v in ve.items():
-283 d[k1][k2] = v
+281 for k1, ve in d2.items():
+282 for k2, v in ve.items():
+283 d[k1][k2] = v
-285 return d
+285 return d
diff --git a/pycov/z_9c05d59a441cce45_version_py.html b/pycov/z_9c05d59a441cce45_version_py.html index ab5807b5..c267af0a 100644 --- a/pycov/z_9c05d59a441cce45_version_py.html +++ b/pycov/z_9c05d59a441cce45_version_py.html @@ -65,7 +65,7 @@21# SPDX-License-Identifier: GPL-3.0-or-later
22# --------------------------------------------------------------------------------
23#
-24__version__ = "0.1.8"
+24__version__ = "0.1.12"
diff --git a/pycov/z_a44f0ac069e85531___init___py.html b/pycov/z_a44f0ac069e85531___init___py.html index 86a945e8..f817cc5f 100644 --- a/pycov/z_a44f0ac069e85531___init___py.html +++ b/pycov/z_a44f0ac069e85531___init___py.html @@ -65,7 +65,7 @@