Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
241fefd
Add reference BJT models from Klayout
LuighiV Aug 28, 2025
44d3163
Add support for BJT in the pdk
LuighiV Aug 28, 2025
0d777c0
Add functions to generate BJT model programatically
LuighiV Aug 28, 2025
30dbffe
Add notebook for BJT model testing
LuighiV Aug 28, 2025
4c9f002
Add support for bjt to ip130
LuighiV Aug 28, 2025
6206951
Add npn and pnp models. Add routing
LuighiV Sep 2, 2025
f184e01
Update jupyter notebooks used to test bjt models
LuighiV Sep 2, 2025
03f698c
Functions to create custom patterns with primitives
LuighiV Sep 5, 2025
469b72e
Create custom patterns for bjts
LuighiV Sep 5, 2025
7965ccf
Add notebook to test custom pattern generation of bjt
LuighiV Sep 5, 2025
adfefa9
Small fix to correct emitter reference
LuighiV Sep 7, 2025
453cc02
Small fix for 1 row array generation
LuighiV Sep 8, 2025
fd54d52
Add bc_shared option for routing which is common in bandgap applications
LuighiV Sep 8, 2025
f6807e9
Update notebook using bc_shared option
LuighiV Sep 8, 2025
c8cd32f
Add custom patterning to fet
LuighiV Sep 5, 2025
d4b685b
Add notebook to test custom patterns with fet
LuighiV Sep 5, 2025
d4bf048
Fix generation when there is just 1 row
LuighiV Sep 8, 2025
fbd9f2c
Add gate_shared and src_shared options for the routing
LuighiV Sep 9, 2025
c7ead1e
Add tests for shared gate and source
LuighiV Sep 9, 2025
fdbb672
Add support for multidomain rules
LuighiV Sep 7, 2025
53d9038
Add support for 5V domain to fet pcell
LuighiV Sep 7, 2025
682a756
Add testing notebook for 5V domain
LuighiV Sep 7, 2025
4ee6399
Minor fix 5V support for 2 dim array
LuighiV Sep 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
100 changes: 98 additions & 2 deletions src/glayout/pdk/gf180_mapped/gf180_grules.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from ..mappedpdk import MappedPDK

grulesobj = dict()
for glayer in MappedPDK.valid_glayers:
grulesobj[glayer] = dict((x, None) for x in MappedPDK.valid_glayers)
for glayer in MappedPDK.valid_glayers["3p3"]:
grulesobj[glayer] = dict((x, None) for x in MappedPDK.valid_glayers["3p3"])

grulesobj["dnwell"]["dnwell"] = {'min_width': 1.7, 'min_separation': 5.42}
grulesobj["dnwell"]["pwell"] = {'min_enclosure': 2.5}
Expand Down Expand Up @@ -366,3 +366,99 @@
grulesobj["capmet"]["met5"] = {}
grulesobj["capmet"]["capmet"] = {'capmettop': (42, 0), 'capmetbottom': (36, 0), 'min_separation': 1.2}

grulesobj_5p0 = dict()
for glayer in MappedPDK.valid_glayers["5p0"]:
grulesobj_5p0[glayer] = dict((x, None) for x in
MappedPDK.valid_glayers["5p0"])

# DN.1 DN2b
grulesobj_5p0["dnwell"]["dnwell"] = {'min_width': 1.7, 'min_separation': 5.42}
# DN.2a
grulesobj_5p0["dnwell"]["pwell"] = {'min_enclosure': 2.5}

# LVW.2a LVW.1
grulesobj_5p0["pwell"]["pwell"] = {'min_width': 0.74, 'min_separation': 1.7}

# NW.1a NW.2b
grulesobj_5p0["nwell"]["nwell"] = {'min_width': 0.86, 'min_separation': 1.7}
# NW.3 NW.5
grulesobj_5p0["dnwell"]["nwell"] = {'min_separation': 3.1, 'min_enclosure': 0.5}
# NW.4
grulesobj_5p0["pwell"]["nwell"] = {'min_separation': 0.0}


# DF.1a DF.3a
grulesobj_5p0["active_diff"]["active_diff"] = {'min_width': 0.3,
'min_separation': 0.36}
grulesobj_5p0["active_tap"]["active_tap"] = {'min_width': 0.3,
'min_separation': 0.36}
# DF.3a DF.13
grulesobj_5p0["active_diff"]["active_tap"] = {'min_separation': 0.36,
'max_separation': 15.0}
# DF.4b
grulesobj_5p0["dnwell"]["active_tap"] = {'min_enclosure': 0.66, 'min_separation': 2.5}
# DF.4c
grulesobj_5p0["nwell"]["active_diff"] = {'min_enclosure': 0.6}
# DF.4d
grulesobj_5p0["nwell"]["active_tap"] = {'min_enclosure': 0.16}
# DF.4e
grulesobj_5p0["dnwell"]["active_diff"] = {'min_enclosure': 1.1}
# DF.5
grulesobj_5p0["pwell"]["active_tap"] = {'min_enclosure': 0.16}
# DF.8, DF.7
grulesobj_5p0["pwell"]["active_diff"] = {'min_enclosure': 0.6}


# DV.1
grulesobj_5p0["dualgate"]["dnwell"] = {'min_enclosure': 0.5}
# DV.2 DV.5
grulesobj_5p0["dualgate"]["dualgate"] = {'min_width':0.7,'min_separation': 0.44}
# DV.3
grulesobj_5p0["dualgate"]["active_diff"] = {'min_separation': 0.24}
# DV.8
grulesobj_5p0["dualgate"]["poly"] = {"min_enclosure": 0.4}



# PL.2
grulesobj_5p0["poly"]["poly"] = {'min_width': 0.5}
# PL.3a PL.5b
grulesobj_5p0["active_diff"]["poly"] = {'overhang': 0.24, 'min_separation': 0.3}
# PL.5a
grulesobj_5p0["active_tap"]["poly"] = {'min_separation': 0.3}


# Rules bellow for difussions and contacts doesn't change for 5V in GF180
grulesobj_5p0["n+s/d"]["n+s/d"] = {'min_width': 0.4, 'min_separation': 0.4}
grulesobj_5p0["n+s/d"]["active_diff"] = {'min_enclosure': 0.23}
grulesobj_5p0["n+s/d"]["active_tap"] = {'min_enclosure': 0.16}

grulesobj_5p0["p+s/d"]["p+s/d"] = {'min_width': 0.4, 'min_separation': 0.4}
grulesobj_5p0["p+s/d"]["active_diff"] = {'min_enclosure': 0.23}
grulesobj_5p0["p+s/d"]["active_tap"] = {'min_enclosure': 0.16}

grulesobj_5p0["active_diff"]["mcon"] = {'min_enclosure': 0.07}
grulesobj_5p0["active_tap"]["mcon"] = {'min_enclosure': 0.07}
grulesobj_5p0["poly"]["mcon"] = {'min_enclosure': 0.07, 'min_separation': 0.17}
grulesobj_5p0["mcon"]["mcon"] = {'min_separation': 0.28, 'width': 0.22}

grulesobj_5p0["mcon"]["met1"] = {'min_enclosure': 0.12}
grulesobj_5p0["met1"]["met1"] = {'min_width': 0.23, 'min_separation': 0.3}
grulesobj_5p0["met1"]["via1"] = {'min_enclosure': 0.12}
grulesobj_5p0["via1"]["via1"] = {'width': 0.26, 'min_separation': 0.36}
grulesobj_5p0["via1"]["met2"] = {'min_enclosure': 0.12}
grulesobj_5p0["met2"]["met2"] = {'min_width': 0.28, 'min_separation': 0.3}
grulesobj_5p0["met2"]["via2"] = {'min_enclosure': 0.12}
grulesobj_5p0["met2"]["capmet"] = {'min_enclosure': 0.6}
grulesobj_5p0["via2"]["via2"] = {'width': 0.26, 'min_separation': 0.36}
grulesobj_5p0["via2"]["met3"] = {'min_enclosure': 0.12}
grulesobj_5p0["met3"]["met3"] = {'min_width': 0.28, 'min_separation': 0.3}
grulesobj_5p0["met3"]["via3"] = {'min_enclosure': 0.12}
grulesobj_5p0["via3"]["via3"] = {'width': 0.26, 'min_separation': 0.36}
grulesobj_5p0["via3"]["met4"] = {'min_enclosure': 0.12}
grulesobj_5p0["met4"]["met4"] = {'min_width': 0.28, 'min_separation': 0.3}
grulesobj_5p0["met4"]["via4"] = {'min_enclosure': 0.12}
grulesobj_5p0["via4"]["via4"] = {'width': 0.26, 'min_separation': 0.36}
grulesobj_5p0["via4"]["met5"] = {'min_enclosure': 0.12}
grulesobj_5p0["met5"]["met5"] = {'min_width': 0.28, 'min_separation': 0.3}
grulesobj_5p0["capmet"]["capmet"] = {'capmettop': (42, 0), 'capmetbottom': (36, 0), 'min_separation': 1.2}
37 changes: 36 additions & 1 deletion src/glayout/pdk/gf180_mapped/gf180_mapped.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
usage: from gf180_mapped import gf180_mapped_pdk
"""

from ..gf180_mapped.gf180_grules import grulesobj
from ..gf180_mapped.gf180_grules import grulesobj, grulesobj_5p0
from ..mappedpdk import MappedPDK, SetupPDKFiles
from pathlib import Path
import os
Expand Down Expand Up @@ -30,6 +30,12 @@
"lvpwell": (204, 0),
"dnwell": (12, 0),
"CAP_MK": (117, 5),
# BJT layers
"drc_bjt": (127, 5),
"lvs_bjt": (118, 5),
# 5V layers
"dualgate": (55,0),
"v5_xtor": (112,1),
# _Label Layer Definations
"metal5_label": (81,10),
"metal4_label": (46,10),
Expand Down Expand Up @@ -60,6 +66,12 @@
"pwell": "lvpwell",
"dnwell": "dnwell",
"capmet": "CAP_MK",
# bjt layer
"drc_bjt": "drc_bjt",
"lvs_bjt": "lvs_bjt",
# 5V layer
"dualgate": "dualgate",
"v5_xtor": "v5_xtor",
# _pin layer ampping
"met5_pin": "metal5_label",
"met4_pin": "metal4_label",
Expand All @@ -78,6 +90,25 @@
"active_diff_label": "comp_label",
}

# Add valid BJT sizes

gf180_valid_bjt_sizes = {
"npn" : [
(0.54, 2.0),
(0.54, 4.0),
(0.54, 8.0),
(0.54, 16.0),
(5.0, 5.0),
(10.0, 10.0),
],
"pnp" : [
(5.0, 0.42),
(5.0, 5.0),
(10.0, 0.42),
(10.0, 10.0),
],
}

# note for DRC, there is mim_option 'A'. This is the one configured for use

gf180_lydrc_file_path = Path(__file__).resolve().parent / "gf180mcu_drc.lydrc"
Expand Down Expand Up @@ -111,6 +142,10 @@
layers=LAYER,
pdk_files=pdk_files,
grules=grulesobj,
grules_3p3=grulesobj,
grules_5p0=grulesobj_5p0,
valid_bjt_sizes=gf180_valid_bjt_sizes,
domain="3p3"
)

# configure the grid size and other settings
Expand Down
8 changes: 5 additions & 3 deletions src/glayout/pdk/ihp130_mapped/ihp130_grules.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from ..mappedpdk import MappedPDK

grulesobj = dict()
for glayer in MappedPDK.valid_glayers:
grulesobj[glayer] = dict((x, None) for x in MappedPDK.valid_glayers)
for glayer in MappedPDK.valid_glayers["3p3"]:
grulesobj[glayer] = dict((x, None) for x in MappedPDK.valid_glayers["3p3"])

grulesobj["dnwell"]["dnwell"] = {"min_width": 3.0, "min_separation": 6.3}
grulesobj["dnwell"]["pwell"] = {"min_enclosure": 0.0}
Expand Down Expand Up @@ -368,4 +368,6 @@
grulesobj["capmet"]["met4"] = {}
grulesobj["capmet"]["via4"] = {}
grulesobj["capmet"]["met5"] = {}
grulesobj["capmet"]["capmet"] = {"capmettop": (71, 20), "capmetbottom": (70, 20), "min_separation": 1.2}
grulesobj["capmet"]["capmet"] = {"capmettop": (71, 20), "capmetbottom": (70, 20), "min_separation": 1.2}

grulesobj_5p0 = grulesobj.copy()
13 changes: 12 additions & 1 deletion src/glayout/pdk/ihp130_mapped/ihp130_mapped.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
usage: from ihp130_mapped import ihp130_mapped_pdk
"""

from ..ihp130_mapped.ihp130_grules import grulesobj
from ..ihp130_mapped.ihp130_grules import grulesobj, grulesobj_5p0
from ..mappedpdk import MappedPDK, SetupPDKFiles
from pathlib import Path
import os
Expand Down Expand Up @@ -99,6 +99,13 @@
"active_diff_label": "activ_pin",
}

# Add valid BJT sizes

ip130_valid_bjt_sizes = {
"npn" : [ ],
"pnp" : [ ],
}

ip130_lydrc_file_path = Path(__file__).resolve().parent / "ihp130_drc.lydrc"

if os.getenv('PDK_ROOT') is None:
Expand Down Expand Up @@ -139,6 +146,10 @@
layers=LAYER,
pdk_files=pdk_files,
grules=grulesobj,
grules_3p3=grulesobj,
grules_5p0=grulesobj_5p0,
valid_bjt_sizes=ip130_valid_bjt_sizes,
domain="3p3"
)

# Configure GDS write precision and cell decorator cache
Expand Down
50 changes: 46 additions & 4 deletions src/glayout/pdk/mappedpdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ class MappedPDK(Pdk):
has_required_glayers(list[str]) is used to verify all required generic layers are
present"""

valid_glayers: ClassVar[tuple[str]] = (
valid_glayers_3p3: tuple[str] = (
"dnwell",
"pwell",
"nwell",
Expand All @@ -259,6 +259,8 @@ class MappedPDK(Pdk):
"via4",
"met5",
"capmet",
"lvs_bjt",
"drc_bjt",
# _pin layers
"met5_pin",
"met4_pin",
Expand All @@ -281,6 +283,22 @@ class MappedPDK(Pdk):
#"pwell_label",
)

valid_glayers_5p0: list[str]=list(valid_glayers_3p3)
valid_glayers_5p0.extend([
"dualgate",
"v5_xtor"
])

valid_domains: ClassVar[tuple] = (
"3p3",
"5p0"
)

valid_glayers: ClassVar[dict[str,list[str]]] = {
"3p3": valid_glayers_3p3,
"5p0": valid_glayers_5p0,
}

models: dict = {
"nfet": "",
"pfet": "",
Expand All @@ -290,15 +308,29 @@ class MappedPDK(Pdk):
glayers: dict[StrictStr, Union[StrictStr, tuple[int,int]]]
# friendly way to implement a graph
grules: dict[StrictStr, dict[StrictStr, Optional[dict[StrictStr, Any]]]]
grules_3p3: dict[StrictStr, dict[StrictStr, Optional[dict[StrictStr, Any]]]]
grules_5p0: dict[StrictStr, dict[StrictStr, Optional[dict[StrictStr, Any]]]]

pdk_files: dict[StrictStr, Union[PathType, None]]

domain: str

valid_bjt_sizes: dict[StrictStr, list[tuple[float,float]]]

@validator("models")
def models_check(cls, models_obj: dict[StrictStr, StrictStr]):
for model in models_obj.keys():
if not model in ["nfet","pfet","mimcap"]:
raise ValueError(f"specify nfet, pfet, or mimcap models only")
return models_obj


@validator("domain")
def domain_check(cls, domain:str):
if domain not in cls.valid_domains:
raise ValueError("Domain set is not currently supported."
f"Available options: {cls.valid_domains}")

@validator("glayers")
def glayers_check_keys(cls, glayers_obj: dict[StrictStr, Union[StrictStr, tuple[int,int]]]):
"""force people to pick glayers from a finite set of string layers that you define
Expand All @@ -307,9 +339,10 @@ def glayers_check_keys(cls, glayers_obj: dict[StrictStr, Union[StrictStr, tuple[
for glayer, mapped_layer in glayers_obj.items():
if (not isinstance(glayer, str)) or (not isinstance(mapped_layer, Union[str, tuple])):
raise TypeError("glayers should be passed as dict[str, Union[StrictStr, tuple[int,int]]]")
if glayer not in cls.valid_glayers:
if glayer not in cls.valid_glayers["5p0"]:
raise ValueError(
"glayers keys must be one of generic layers listed in class variable valid_glayers"
" for selected domain."
)
return glayers_obj

Expand Down Expand Up @@ -989,18 +1022,27 @@ def get_glayer(self, layer: str) -> Layer:
else:
return self.get_layer(direct_mapping)


@validate_arguments
def set_domain(self, domain):
if domain not in MappedPDK.valid_domains:
raise ValueError("Not a supported domain"
f"Available options: {MappedPDK.valid_domains}")
self.domain = domain
self.grules = self.grules_3p3 if self.domain == "3p3" else self.grules_5p0

@validate_arguments
def get_grule(
self, glayer1: str, glayer2: Optional[str] = None, return_decimal = False
) -> dict[StrictStr, Union[float,Decimal]]:
"""Returns a dictionary describing the relationship between two layers
If one layer is specified, returns a dictionary with all intra layer rules"""
if glayer1 not in MappedPDK.valid_glayers:
if glayer1 not in MappedPDK.valid_glayers[self.domain]:
raise ValueError("get_grule, " + str(glayer1) + " not valid glayer")
# decide if two or one inputs and set rules_dict accordingly
rules_dict = None
if glayer2 is not None:
if glayer2 not in MappedPDK.valid_glayers:
if glayer2 not in MappedPDK.valid_glayers[self.domain]:
raise ValueError("get_grule, " + str(glayer2) + " not valid glayer")
rules_dict = self.grules.get(glayer1, dict()).get(glayer2)
if rules_dict is None or rules_dict == {}:
Expand Down
7 changes: 5 additions & 2 deletions src/glayout/pdk/sky130_mapped/sky130_grules.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from ..mappedpdk import MappedPDK

grulesobj = dict()
for glayer in MappedPDK.valid_glayers:
grulesobj[glayer] = dict((x, None) for x in MappedPDK.valid_glayers)
for glayer in MappedPDK.valid_glayers["3p3"]:
grulesobj[glayer] = dict((x, None) for x in MappedPDK.valid_glayers["3p3"])

grulesobj["dnwell"]["dnwell"] = {"min_width": 3.0, "min_separation": 6.3}
grulesobj["dnwell"]["pwell"] = {"min_enclosure": 0.0}
Expand Down Expand Up @@ -369,3 +369,6 @@
grulesobj["capmet"]["via4"] = {}
grulesobj["capmet"]["met5"] = {}
grulesobj["capmet"]["capmet"] = {"capmettop": (71, 20), "capmetbottom": (70, 20), "min_separation": 1.2}


grulesobj_5p0 = grulesobj.copy()
Loading