gdsfill 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gdsfill-0.1.0/PKG-INFO +13 -0
- gdsfill-0.1.0/README.rst +98 -0
- gdsfill-0.1.0/gdsfill/__init__.py +0 -0
- gdsfill-0.1.0/gdsfill/gdsfill.py +211 -0
- gdsfill-0.1.0/gdsfill/library/__init__.py +0 -0
- gdsfill-0.1.0/gdsfill/library/common.py +245 -0
- gdsfill-0.1.0/gdsfill/library/fill.py +68 -0
- gdsfill-0.1.0/gdsfill/library/filler/__init__.py +0 -0
- gdsfill-0.1.0/gdsfill/library/filler/helper.py +313 -0
- gdsfill-0.1.0/gdsfill/library/filler/overlap.py +201 -0
- gdsfill-0.1.0/gdsfill/library/filler/square.py +219 -0
- gdsfill-0.1.0/gdsfill/library/filler/track.py +159 -0
- gdsfill-0.1.0/gdsfill/library/klayout.py +155 -0
- gdsfill-0.1.0/gdsfill.egg-info/PKG-INFO +13 -0
- gdsfill-0.1.0/gdsfill.egg-info/SOURCES.txt +19 -0
- gdsfill-0.1.0/gdsfill.egg-info/dependency_links.txt +1 -0
- gdsfill-0.1.0/gdsfill.egg-info/entry_points.txt +2 -0
- gdsfill-0.1.0/gdsfill.egg-info/requires.txt +3 -0
- gdsfill-0.1.0/gdsfill.egg-info/top_level.txt +1 -0
- gdsfill-0.1.0/pyproject.toml +27 -0
- gdsfill-0.1.0/setup.cfg +4 -0
gdsfill-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gdsfill
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python project using pyyaml, packaging, and gdstk.
|
|
5
|
+
Author-email: Your Name <you@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: homepage, https://example.com
|
|
8
|
+
Project-URL: repository, https://github.com/yourusername/my-python-project
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: pyyaml
|
|
12
|
+
Requires-Dist: packaging
|
|
13
|
+
Requires-Dist: gdstk
|
gdsfill-0.1.0/README.rst
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
gdsfill
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
**gdsfill** is an open-source tool for inserting dummy metal fill into semiconductor layouts.
|
|
5
|
+
It helps designers meet density requirements and prepare GDSII layouts for manufacturing by analyzing, erasing, and generating dummy fill patterns across multiple layers.
|
|
6
|
+
The tool is designed to integrate easily into existing design flows and ensures reproducible, automated preparation of layouts before tape-out.
|
|
7
|
+
|
|
8
|
+
This project is still under development. Please report any issues you encounter and always verify your layout before tape-out deadlines to prevent submission failures.
|
|
9
|
+
|
|
10
|
+
Installation
|
|
11
|
+
############
|
|
12
|
+
|
|
13
|
+
**gdsfill** can be installed as a Python package. We recommend using a virtual environment to keep dependencies isolated.
|
|
14
|
+
|
|
15
|
+
.. code-block:: text
|
|
16
|
+
|
|
17
|
+
$ python3 -m venv venv
|
|
18
|
+
$ source venv/bin/activate
|
|
19
|
+
(venv) $ pip install --updage pip
|
|
20
|
+
(venv) $ pip install gdsfill
|
|
21
|
+
|
|
22
|
+
Density
|
|
23
|
+
#######
|
|
24
|
+
|
|
25
|
+
This command calculates the utilization per layer and prints the values.
|
|
26
|
+
It is useful to check layer density before and after running the fill process:
|
|
27
|
+
|
|
28
|
+
.. code-block:: text
|
|
29
|
+
|
|
30
|
+
gdsfill density <my-layout.gds>
|
|
31
|
+
|
|
32
|
+
Erase
|
|
33
|
+
#####
|
|
34
|
+
|
|
35
|
+
If a layout already contains dummy fill, or if previous fills should be removed, this command erases all dummy metal fill from a layout:
|
|
36
|
+
|
|
37
|
+
.. code-block:: text
|
|
38
|
+
|
|
39
|
+
gdsfill erase <my-layout.gds>
|
|
40
|
+
|
|
41
|
+
Fill
|
|
42
|
+
####
|
|
43
|
+
|
|
44
|
+
To insert dummy metal fill into all supported layers of a layout, run:
|
|
45
|
+
|
|
46
|
+
.. code-block:: text
|
|
47
|
+
|
|
48
|
+
gdsfill fill <my-layout.gds>
|
|
49
|
+
|
|
50
|
+
Some algorithms, such as the track filler, require information about the chip core region. You can provide the lower-left and upper-right coordinates as floating-point values:
|
|
51
|
+
|
|
52
|
+
.. code-block:: text
|
|
53
|
+
|
|
54
|
+
gdsfill fill <my-layout.gds> --core-size llx lly urx ury
|
|
55
|
+
|
|
56
|
+
By default, **gdsfill** creates a temporary directory for intermediate data.
|
|
57
|
+
Use ``--keep-data`` to retain all generated files in a directory called ``gdsfill-tmp``:
|
|
58
|
+
|
|
59
|
+
.. code-block:: text
|
|
60
|
+
|
|
61
|
+
gdsfill fill <my-layout.gds> --keep-data
|
|
62
|
+
|
|
63
|
+
If you only want to simulate the process without modifying the layout file, use ``--dry-run``:
|
|
64
|
+
|
|
65
|
+
.. code-block:: text
|
|
66
|
+
|
|
67
|
+
gdsfill fill <my-layout.gds> --dry-run
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
Custom Configuration
|
|
71
|
+
####################
|
|
72
|
+
|
|
73
|
+
By default, **gdsfill** inserts dummy metal fill into each layer using predefined parameters.
|
|
74
|
+
To apply different parameters or restrict fill to specific layers, you can create a custom configuration file.
|
|
75
|
+
|
|
76
|
+
The following example config inserts fill only into **TopMetal1** and **TopMetal2**:
|
|
77
|
+
|
|
78
|
+
.. code-block:: yaml
|
|
79
|
+
|
|
80
|
+
PDK: ihp-sg13g2
|
|
81
|
+
layers:
|
|
82
|
+
TopMetal1:
|
|
83
|
+
algorithm: Square
|
|
84
|
+
density: 60
|
|
85
|
+
deviation: 1
|
|
86
|
+
TopMetal2:
|
|
87
|
+
algorithm: Square
|
|
88
|
+
density: 60
|
|
89
|
+
deviation: 1
|
|
90
|
+
|
|
91
|
+
.. note::
|
|
92
|
+
Example config files are available in ``gdsfill/configs``.
|
|
93
|
+
|
|
94
|
+
To use a custom config file, pass it with ``--config-file``:
|
|
95
|
+
|
|
96
|
+
.. code-block:: text
|
|
97
|
+
|
|
98
|
+
gdsfill fill <my-layout.gds> --config-file <my-config-file.yaml>
|
|
File without changes
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for GDSII dummy fill operations.
|
|
3
|
+
|
|
4
|
+
This module provides subcommands to:
|
|
5
|
+
- Insert dummy fill into a layout (`fill`)
|
|
6
|
+
- Erase dummy fill from a layout (`erase`)
|
|
7
|
+
- Calculate per-layer density (`density`)
|
|
8
|
+
|
|
9
|
+
Dummy fill is generated tile-by-tile using processes defined
|
|
10
|
+
in the PDK, and relies on Klayout for GDSII operations.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import tempfile
|
|
15
|
+
import importlib
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from multiprocessing import Process
|
|
18
|
+
|
|
19
|
+
from gdsfill.library.klayout import (
|
|
20
|
+
get_version,
|
|
21
|
+
export_layer,
|
|
22
|
+
print_density,
|
|
23
|
+
erase_fill,
|
|
24
|
+
merge_tile
|
|
25
|
+
)
|
|
26
|
+
from gdsfill.library.common import (
|
|
27
|
+
PdkInformation,
|
|
28
|
+
Tile,
|
|
29
|
+
open_yaml
|
|
30
|
+
)
|
|
31
|
+
from gdsfill.library.fill import fill_layer
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# pylint: disable=too-many-locals, too-many-arguments, too-many-positional-arguments
|
|
35
|
+
def _fill_layer(layer, pdk, inputfile, tmpdirname, dry_run, core_size=None):
|
|
36
|
+
"""
|
|
37
|
+
Run the fill pipeline for a single layer.
|
|
38
|
+
|
|
39
|
+
Steps:
|
|
40
|
+
1. Export layer geometry into tiles.
|
|
41
|
+
2. Modify tiles using the process-specific `prepare_tile`.
|
|
42
|
+
3. Apply dummy fill to each tile with the selected algorithms.
|
|
43
|
+
4. Optionally merge tiles back into a single GDS file.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
layer (str): Target layer name.
|
|
47
|
+
pdk (PdkInformation): PDK instance with layer rules.
|
|
48
|
+
inputfile (Path): Path to the input GDS file.
|
|
49
|
+
tmpdirname (Path | str): Temporary directory for intermediate data.
|
|
50
|
+
dry_run (bool): If True, skip merging filled tiles.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
None
|
|
54
|
+
"""
|
|
55
|
+
print(f"--- Layer {layer} ---")
|
|
56
|
+
|
|
57
|
+
output_path = Path(tmpdirname) / layer
|
|
58
|
+
for stage in ('raw', 'modified', 'filled'):
|
|
59
|
+
(output_path / stage).mkdir(parents=True, exist_ok=True)
|
|
60
|
+
|
|
61
|
+
export_layer(pdk, inputfile, output_path, layer, core_size)
|
|
62
|
+
tiles = open_yaml(output_path / "tiles.yaml")
|
|
63
|
+
|
|
64
|
+
prepare_module = importlib.import_module(f'gdsfill.{pdk.get_name()}.prepare')
|
|
65
|
+
procs_modify = []
|
|
66
|
+
for tile in tiles['tiles'].keys():
|
|
67
|
+
raw_tile = output_path / "raw" / f"tile_{tile}.gds"
|
|
68
|
+
proc = Process(target=prepare_module.prepare_tile, args=(pdk, raw_tile, layer))
|
|
69
|
+
procs_modify.append(proc)
|
|
70
|
+
proc.start()
|
|
71
|
+
for proc in procs_modify:
|
|
72
|
+
proc.join()
|
|
73
|
+
|
|
74
|
+
procs_fill = []
|
|
75
|
+
for tile, values in tiles['tiles'].items():
|
|
76
|
+
file = output_path / "modified" / f"tile_{tile}.gds"
|
|
77
|
+
proc = Process(target=fill_layer,
|
|
78
|
+
args=(pdk, file, layer, tiles, Tile(values['x'], values['y'])))
|
|
79
|
+
procs_fill.append(proc)
|
|
80
|
+
proc.start()
|
|
81
|
+
for proc in procs_fill:
|
|
82
|
+
proc.join()
|
|
83
|
+
|
|
84
|
+
if dry_run:
|
|
85
|
+
print("Skip merging filled tiles because --dry-run was passed.")
|
|
86
|
+
else:
|
|
87
|
+
merge_tile(pdk, inputfile, output_path / "filled", output_path / "tiles.yaml")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def func_fill(args, pdk):
|
|
91
|
+
"""
|
|
92
|
+
Subcommand: Insert dummy fill into each layer of a GDS file.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
args (Namespace): Parsed CLI arguments.
|
|
96
|
+
pdk (PdkInformation): PDK instance with layer rules.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
None
|
|
100
|
+
"""
|
|
101
|
+
if args.keep_data:
|
|
102
|
+
tmpdirname = Path.cwd() / "gdsfill-tmp"
|
|
103
|
+
print(f"Data are stored in {tmpdirname}")
|
|
104
|
+
for layer, _ in pdk.get_layers():
|
|
105
|
+
_fill_layer(layer, pdk, args.input, tmpdirname, args.dry_run, args.core_size)
|
|
106
|
+
else:
|
|
107
|
+
with tempfile.TemporaryDirectory(prefix='gdsfill-') as tmpdirname:
|
|
108
|
+
for layer, _ in pdk.get_layers():
|
|
109
|
+
_fill_layer(layer, pdk, args.input, tmpdirname, args.dry_run, args.core_size)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def func_density(args, pdk):
|
|
113
|
+
"""
|
|
114
|
+
Subcommand: Calculate density for each layer of a GDS file.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
args (Namespace): Parsed CLI arguments.
|
|
118
|
+
pdk (PdkInformation): PDK instance with layer rules.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
None
|
|
122
|
+
"""
|
|
123
|
+
print_density(pdk, args.input)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def func_erase(args, pdk):
|
|
127
|
+
"""
|
|
128
|
+
Subcommand: Erase all dummy fill from a GDS file.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
args (Namespace): Parsed CLI arguments.
|
|
132
|
+
pdk (PdkInformation): PDK instance with layer rules.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
None
|
|
136
|
+
"""
|
|
137
|
+
erase_fill(pdk, args.input)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def is_valid_file(value: str):
|
|
141
|
+
"""
|
|
142
|
+
Argparse validator: ensure argument points to an existing file.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
value (str): Path to the file.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Path: Validated Path object.
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
argparse.ArgumentTypeError: If the path does not exist or is not a file.
|
|
152
|
+
"""
|
|
153
|
+
file_ = Path(value)
|
|
154
|
+
if file_.is_file():
|
|
155
|
+
return file_
|
|
156
|
+
raise argparse.ArgumentTypeError(f"File {value} doesn't exist!")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def arguments():
|
|
160
|
+
"""
|
|
161
|
+
Define CLI arguments and subcommands.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
argparse.Namespace: Parsed command-line arguments.
|
|
165
|
+
"""
|
|
166
|
+
parser = argparse.ArgumentParser()
|
|
167
|
+
parser.add_argument("--process", default="ihp-sg13g2")
|
|
168
|
+
subparsers = parser.add_subparsers(help='subcommand help')
|
|
169
|
+
fill = subparsers.add_parser('fill', help='Fill chip with dummy metal')
|
|
170
|
+
fill.add_argument("input", type=is_valid_file)
|
|
171
|
+
fill.add_argument('--keep-data', action=argparse.BooleanOptionalAction)
|
|
172
|
+
fill.add_argument('--dry-run', action=argparse.BooleanOptionalAction)
|
|
173
|
+
fill.add_argument('--config-file', type=is_valid_file)
|
|
174
|
+
fill.add_argument('--core-size', type=float, nargs=4, metavar=('llx', 'lly', 'urx', 'ury'),
|
|
175
|
+
help="lower left (x, y) and upper right (x, y) points of the chip core.")
|
|
176
|
+
fill.set_defaults(func=func_fill)
|
|
177
|
+
|
|
178
|
+
erase = subparsers.add_parser('erase', help='Erase dummy metal from chip')
|
|
179
|
+
erase.add_argument("input", type=is_valid_file)
|
|
180
|
+
erase.set_defaults(func=func_erase)
|
|
181
|
+
|
|
182
|
+
density = subparsers.add_parser('density', help='Calculate density for each layer')
|
|
183
|
+
density.add_argument("input", type=is_valid_file)
|
|
184
|
+
density.set_defaults(func=func_density)
|
|
185
|
+
|
|
186
|
+
return parser.parse_args()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def main():
|
|
190
|
+
"""
|
|
191
|
+
Entry point for the gdsfill CLI.
|
|
192
|
+
|
|
193
|
+
Parses arguments, checks Klayout version compatibility,
|
|
194
|
+
and dispatches to the selected subcommand.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
int: Exit code (0 on success, nonzero on error).
|
|
198
|
+
"""
|
|
199
|
+
args = arguments()
|
|
200
|
+
pdk = PdkInformation(args.process, args.config_file if 'config_file' in args else None)
|
|
201
|
+
klayout_version = get_version()
|
|
202
|
+
if klayout_version < pdk.get_minimum_klayout_version():
|
|
203
|
+
print(f"Please install Klayout {pdk.get_minimum_klayout_version()}")
|
|
204
|
+
return 1
|
|
205
|
+
|
|
206
|
+
args.func(args, pdk)
|
|
207
|
+
return 0
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
if __name__ == '__main__':
|
|
211
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Common utilities for handling PDK configuration and tile metadata.
|
|
3
|
+
|
|
4
|
+
This module provides:
|
|
5
|
+
- `PdkInformation`: an interface for accessing process design kit (PDK)
|
|
6
|
+
configuration, constants, and layer-specific parameters.
|
|
7
|
+
- `Tile`: a dataclass representing the position of a tile in the layout.
|
|
8
|
+
- Utility functions for validating input files and loading YAML
|
|
9
|
+
configuration files.
|
|
10
|
+
|
|
11
|
+
The functions and classes in this module are used across the GDS fill
|
|
12
|
+
workflow for tasks such as density calculation, layer export, and tile
|
|
13
|
+
processing.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
import yaml
|
|
19
|
+
from packaging.version import Version
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PdkInformation:
|
|
23
|
+
"""
|
|
24
|
+
Accessor for PDK configuration and constants.
|
|
25
|
+
|
|
26
|
+
Loads a process-specific configuration (`configs/`) and
|
|
27
|
+
constants (`constants.yaml`) from the PDK directory and provides
|
|
28
|
+
convenient methods to query layer settings, algorithms, density
|
|
29
|
+
requirements, and rules for dummy fill.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
data (dict): Contents of the process config file.
|
|
33
|
+
constants (dict): Contents of the process `constants.yaml`.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, process, config_file):
|
|
37
|
+
"""
|
|
38
|
+
Initialize a PDK information object.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
process (str): Name of the process directory (e.g., "ihp-sg13g2").
|
|
42
|
+
config_file (Path or str): Optional path to a custom config file.
|
|
43
|
+
If None, defaults to `configs/<process>.yaml`.
|
|
44
|
+
"""
|
|
45
|
+
script = Path(__file__).parent.parent.resolve()
|
|
46
|
+
self.data = open_yaml(config_file if config_file else script / f"configs/{process}.yaml")
|
|
47
|
+
self.constants = open_yaml(script / process / "constants.yaml")
|
|
48
|
+
|
|
49
|
+
def get_minimum_klayout_version(self):
|
|
50
|
+
"""
|
|
51
|
+
Get the minimum required KLayout version for this PDK.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Version: Required version parsed as a `packaging.version.Version`.
|
|
55
|
+
"""
|
|
56
|
+
return Version(self.constants['minimum_klayout'])
|
|
57
|
+
|
|
58
|
+
def get_name(self):
|
|
59
|
+
"""
|
|
60
|
+
Get the name of the PDK.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
str: Name of the process design kit.
|
|
64
|
+
"""
|
|
65
|
+
return self.data['PDK']
|
|
66
|
+
|
|
67
|
+
def get_layers(self):
|
|
68
|
+
"""
|
|
69
|
+
Get all configured layers for this PDK.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Iterable[Tuple[str, dict]]: List of (layer_name, properties).
|
|
73
|
+
"""
|
|
74
|
+
return self.data['layers'].items()
|
|
75
|
+
|
|
76
|
+
def get_layer(self, layer: str) -> dict:
|
|
77
|
+
"""
|
|
78
|
+
Get configuration details for a specific layer.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
layer (str): Layer name.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
dict: Layer configuration dictionary.
|
|
85
|
+
"""
|
|
86
|
+
return self.data['layers'][layer]
|
|
87
|
+
|
|
88
|
+
def get_layer_tile_width(self, layer: str) -> int:
|
|
89
|
+
"""
|
|
90
|
+
Get the tile width for a given layer.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
layer (str): Layer name.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
int: Tile width in database units.
|
|
97
|
+
"""
|
|
98
|
+
if 'tile_width' in self.get_layer(layer):
|
|
99
|
+
return int(self.get_layer(layer)['tile_width'])
|
|
100
|
+
return int(self.constants['tile_width'])
|
|
101
|
+
|
|
102
|
+
def get_layer_algorithm(self, layer: str) -> list:
|
|
103
|
+
"""
|
|
104
|
+
Get the fill algorithm(s) for a given layer.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
layer (str): Layer name.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
List[str]: List of algorithm names.
|
|
111
|
+
"""
|
|
112
|
+
algo = self.get_layer(layer)['algorithm']
|
|
113
|
+
if isinstance(algo, str):
|
|
114
|
+
return [algo]
|
|
115
|
+
return algo
|
|
116
|
+
|
|
117
|
+
def get_layer_density(self, layer: str) -> float:
|
|
118
|
+
"""
|
|
119
|
+
Get the target metal density for a layer.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
layer (str): Layer name.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
float: Target density percentage.
|
|
126
|
+
"""
|
|
127
|
+
return self.get_layer(layer)['density']
|
|
128
|
+
|
|
129
|
+
def get_layer_deviation(self, layer: str) -> float:
|
|
130
|
+
"""
|
|
131
|
+
Get the allowed density deviation for a layer.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
layer (str): Layer name.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
float: Allowed deviation percentage.
|
|
138
|
+
"""
|
|
139
|
+
return self.get_layer(layer)['deviation']
|
|
140
|
+
|
|
141
|
+
def get_layer_index(self, layer: str) -> int:
|
|
142
|
+
"""
|
|
143
|
+
Get the numeric index for a given layer.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
layer (str): Layer name.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
int: Layer index.
|
|
150
|
+
"""
|
|
151
|
+
return self.constants['layers'][layer]['index']
|
|
152
|
+
|
|
153
|
+
def get_layer_fill_datatype(self, layer: str) -> int:
|
|
154
|
+
"""
|
|
155
|
+
Get the GDS datatype used for fill shapes of a layer.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
layer (str): Layer name.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
int: Fill datatype.
|
|
162
|
+
"""
|
|
163
|
+
return self.constants['layers'][layer]['fill']
|
|
164
|
+
|
|
165
|
+
def get_layer_max_depth(self, layer: str) -> int:
|
|
166
|
+
"""
|
|
167
|
+
Get the maximum recursion depth for fill placement.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
layer (str): Layer name.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
int: Maximum recursion depth.
|
|
174
|
+
"""
|
|
175
|
+
return self.constants['layers'][layer]['max_depth']
|
|
176
|
+
|
|
177
|
+
def has_fill_algorithm(self, layer: str, algorithm: str) -> bool:
|
|
178
|
+
"""
|
|
179
|
+
Check if a layer supports a given fill algorithm.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
layer (str): Layer name.
|
|
183
|
+
algorithm (str): Algorithm identifier.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
bool: True if supported, False otherwise.
|
|
187
|
+
"""
|
|
188
|
+
return self.constants['layers'][layer].get(algorithm) is not None
|
|
189
|
+
|
|
190
|
+
def get_fill_rules(self, layer: str, algorithm: str) -> dict:
|
|
191
|
+
"""
|
|
192
|
+
Get the rule set for a given layer and fill algorithm.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
layer (str): Layer name.
|
|
196
|
+
algorithm (str): Algorithm identifier.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
dict: Dictionary of fill rules (e.g., min/max size, spacing).
|
|
200
|
+
"""
|
|
201
|
+
return self.constants['layers'][layer][algorithm]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass
|
|
205
|
+
class Tile:
|
|
206
|
+
"""
|
|
207
|
+
Representation of a tile’s position in the layout.
|
|
208
|
+
|
|
209
|
+
Attributes:
|
|
210
|
+
x (int): X-coordinate of the tile.
|
|
211
|
+
y (int): Y-coordinate of the tile.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
x: int
|
|
215
|
+
y: int
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def inputfile_exists(inputfile: str):
|
|
219
|
+
"""
|
|
220
|
+
Check if a given file path exists.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
inputfile (str): Path to the file.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
bool: True if the file exists, False otherwise.
|
|
227
|
+
"""
|
|
228
|
+
file_ = Path(inputfile)
|
|
229
|
+
return file_.exists()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def open_yaml(yamlfile: Path):
|
|
233
|
+
"""
|
|
234
|
+
Load the contents of a YAML file.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
yamlfile (Path): Path to the YAML file.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
dict or bool: Parsed YAML content, or False if the file does not exist.
|
|
241
|
+
"""
|
|
242
|
+
if not yamlfile.exists():
|
|
243
|
+
return False
|
|
244
|
+
content = Path(yamlfile).read_text(encoding='utf-8')
|
|
245
|
+
return yaml.safe_load(content)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Layer filler driver.
|
|
3
|
+
|
|
4
|
+
Coordinates filler insertion by selecting the appropriate algorithm
|
|
5
|
+
and applying it to a given tile and layer.
|
|
6
|
+
"""
|
|
7
|
+
import gdstk
|
|
8
|
+
|
|
9
|
+
from gdsfill.library.filler.helper import calculate_density
|
|
10
|
+
from gdsfill.library.filler.overlap import fill_overlap
|
|
11
|
+
from gdsfill.library.filler.square import fill_square
|
|
12
|
+
from gdsfill.library.filler.track import fill_track
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
ALGOS = {
|
|
16
|
+
'Overlap': fill_overlap,
|
|
17
|
+
'Square': fill_square,
|
|
18
|
+
'Track': fill_track,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def fill_layer(pdk, inputfile, layer, tiles, tile):
|
|
23
|
+
"""
|
|
24
|
+
Fill a layout layer to meet density requirements.
|
|
25
|
+
|
|
26
|
+
Reads a GDS file, checks density, and applies one or more filler
|
|
27
|
+
algorithms (square or track) as defined in the PDK.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
pdk (object): Provides layer rules and supported algorithms.
|
|
31
|
+
inputfile (Path | str): Path to the input GDS file.
|
|
32
|
+
layer (str): Target layer name.
|
|
33
|
+
tiles (dict): Tiling information for the layout.
|
|
34
|
+
tile (object): Current tile instance.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
bool: True if successful, False if algorithm unsupported.
|
|
38
|
+
"""
|
|
39
|
+
library = gdstk.read_gds(inputfile, unit=1e-6)
|
|
40
|
+
annotated_cell = library.top_level()[0]
|
|
41
|
+
|
|
42
|
+
metal_density = calculate_density(annotated_cell)
|
|
43
|
+
desired_density = pdk.get_layer_density(layer)
|
|
44
|
+
fill_algos = pdk.get_layer_algorithm(layer)
|
|
45
|
+
|
|
46
|
+
print(f"Filling Tile {tile.x}x{tile.y}")
|
|
47
|
+
print(f"Metal density: {metal_density} %")
|
|
48
|
+
print(f"Desired density: {desired_density} % with {pdk.get_layer_deviation(layer)} % deviation")
|
|
49
|
+
print(f"Fill algorithm: {', '.join(fill_algos)}")
|
|
50
|
+
|
|
51
|
+
for fill_algo in fill_algos:
|
|
52
|
+
if fill_algo not in ALGOS:
|
|
53
|
+
print(f"Unknown fill algorithm {fill_algo} for layer {layer}")
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
for fill_algo in fill_algos:
|
|
57
|
+
if not pdk.has_fill_algorithm(layer, fill_algo):
|
|
58
|
+
print(f"Unsupported fill algorithm {fill_algo} for layer {layer}")
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
fill_lib = gdstk.Library("fill")
|
|
62
|
+
if metal_density < desired_density:
|
|
63
|
+
for fill_algo in fill_algos:
|
|
64
|
+
fill_cell = ALGOS[fill_algo](pdk, layer, tiles, tile, annotated_cell)
|
|
65
|
+
fill_lib.add(fill_cell)
|
|
66
|
+
|
|
67
|
+
fill_lib.write_gds(str(inputfile).replace('modified', 'filled'))
|
|
68
|
+
return True
|
|
File without changes
|