Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sky130nm inverter schematic-driven-layout design & hdl21 integration #356

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ cython_debug/
.DS_Store
*Thumbs.db


# Possible generated files
*.cmd
*.tcl
Expand All @@ -180,3 +181,5 @@ cython_debug/
*.BAK
*.sav
*.plt
.virtual_documents
.idea
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gplugins_spice
19 changes: 19 additions & 0 deletions gplugins/hdl21/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from .netlist import (
ParsedProtoVLSIR,
generate_raw_netlist_dict_from_module,
generate_raw_yaml_from_module,
)
from .sky130 import (
filter_port,
find_most_relevant_gds,
hdl21_module_to_schematic_editor,
)

__all__ = [
"filter_port",
"find_most_relevant_gds",
"hdl21_module_to_schematic_editor",
"generate_raw_yaml_from_module",
"generate_raw_netlist_dict_from_module",
"ParsedProtoVLSIR",
]
232 changes: 232 additions & 0 deletions gplugins/hdl21/netlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
"""
This module provides functions to generate a raw netlist semi-compatible with gdsfactory from a hdl21 module object.
"""

import hdl21 as h
import yaml

ParsedProtoVLSIR = dict


def _parse_module_to_proto_dict(module: h.module) -> ParsedProtoVLSIR:
"""
Parse a hdl21 module object into a dictionary with the same structure as the proto VLSIR format.
"""

def parse_value(lines, index):
value = {}
while index < len(lines):
line = lines[index].strip()
if line == "}":
return value, index
elif line.endswith("{"):
key = line[:-1].strip()
sub_value, new_index = parse_value(lines, index + 1)
if key not in value:
value[key] = []
value[key].append(sub_value)
index = new_index
else:
key, val = line.split(":", 1)
value[key.strip()] = val.strip().strip('"')
index += 1
return value, index

raw_proto_str = str(h.to_proto(module))
lines = raw_proto_str.split("\n")
result = {}
index = 0
while index < len(lines):
line = lines[index].strip()
if line.endswith("{"):
key = line[:-1].strip()
sub_value, new_index = parse_value(lines, index + 1)
if key not in result:
result[key] = []
result[key].append(sub_value)
index = new_index
else:
index += 1

return result


def _parse_connections(proto_dict: ParsedProtoVLSIR) -> dict:
"""
Extract the connections from the proto_dict and return a dictionary with the connections.
"""
connections = {}

# Extract the instances and their connections
for module in proto_dict.get("modules", []):
for instance in module.get("instances", []):
instance_name = instance["name"]
for connection in instance.get("connections", []):
portname = connection["portname"]
target_signal = connection["target"][0]["sig"]
connection_key = f"{instance_name},{portname}"
# Find the target instance and port
target_instance_port = _find_target_instance_port(
proto_dict, target_signal, instance_name
)
if target_instance_port:
connections[connection_key] = target_instance_port

return connections


def _find_target_instance_port(
proto_dict: ParsedProtoVLSIR, target_signal, current_instance_name
):
"""
Find the target instance and port of the target signal in the proto_dict.
"""
# Search in the same module
for module in proto_dict.get("modules", []):
for instance in module.get("instances", []):
if instance["name"] == current_instance_name:
continue
for connection in instance.get("connections", []):
if connection["target"][0]["sig"] == target_signal:
return f"{instance['name']},{connection['portname']}"
# Search in external modules
for ext_module in proto_dict.get("ext_modules", []):
for port in ext_module.get("ports", []):
if port["signal"] == target_signal:
for instance in module.get("instances", []):
if instance["name"] == current_instance_name:
continue
for connection in instance.get("connections", []):
if connection["target"][0]["sig"] == target_signal:
return f"{instance['name']},{connection['portname']}"

return None


def _generate_top_level_connections(proto_dict: ParsedProtoVLSIR):
"""
Generate the top-level connections from the proto_dict.
"""
top_level_connections = {}

# Iterate over the top-level module ports
for module in proto_dict.get("modules", []):
for port in module.get("ports", []):
port_signal = port["signal"]
connection = _find_port_connection(proto_dict, port_signal)
if connection:
top_level_connections[port_signal] = connection

return top_level_connections


def _find_port_connection(proto_dict: ParsedProtoVLSIR, port_signal):
"""
Find the connection of the port signal in the proto_dict.
"""
# Search within the module instances
for module in proto_dict.get("modules", []):
for instance in module.get("instances", []):
instance_name = instance["name"]
for connection in instance.get("connections", []):
if connection["target"][0]["sig"] == port_signal:
return f"{instance_name},{connection['portname']}"
return None


def _extract_instance_parameters(proto_dict: ParsedProtoVLSIR):
"""
Extract the instance parameters from the proto_dict.
"""
instance_parameters = {}

for module in proto_dict.get("modules", []):
for instance in module.get("instances", []):
instance_name = instance["name"]
instance_info = {
"component": _extract_component_name(instance),
"info": {},
"settings": {},
}

# Extract parameters into the settings
for parameter in instance.get("parameters", []):
param_name = parameter["name"]
param_value = _extract_parameter_value(parameter["value"])
instance_info["settings"][param_name] = param_value

# Extract connections and add to settings
instance_info["settings"]["ports"] = {}
for connection in instance.get("connections", []):
portname = connection["portname"]
target_signal = connection["target"][0]["sig"]
instance_info["settings"]["ports"][portname] = target_signal

instance_parameters[instance_name] = instance_info

return instance_parameters


def _extract_component_name(instance):
"""
Extract the component name from the instance.
"""
external_modules = instance.get("module", [])
if external_modules:
name = external_modules[0].get("external", [{}])[0].get("name", "")
return f"{name}"
return "unknown_component"


def _extract_parameter_value(value):
"""
Extract the parameter value from the value dictionary.
"""
if value and "literal" in value[0]:
return value[0]["literal"]
elif value and "prefixed" in value[0]:
prefix = value[0]["prefixed"][0].get("prefix", "")
int64_value = value[0]["prefixed"][0].get("int64_value", "")
return f"{prefix}_{int64_value}"
return None


def _generate_raw_netlist_dict_from_proto_dict(proto_dict: ParsedProtoVLSIR):
"""
Generate a raw netlist dictionary from the proto_dict.
"""
raw_netlist_dict = {"name": "", "instances": {}, "connections": {}, "ports": {}}

# Extract the top-level module name
if proto_dict.get("modules"):
raw_netlist_dict["name"] = proto_dict["modules"][0].get("name", "")

# Generate instances information
raw_netlist_dict["instances"] = _extract_instance_parameters(proto_dict)

# Generate connections
raw_netlist_dict["connections"] = _parse_connections(proto_dict)

# Generate top-level connections
raw_netlist_dict["ports"] = _generate_top_level_connections(proto_dict)

return raw_netlist_dict


def generate_raw_netlist_dict_from_module(module: h.module):
"""
Generate a raw netlist dictionary from a hdl21 module object.
This just gives us a raw structure of the hdl21 modules, we cannot use this json equivalently to a gdsfactory netlist.
"""
proto_dict = _parse_module_to_proto_dict(module)
return _generate_raw_netlist_dict_from_proto_dict(proto_dict)


def generate_raw_yaml_from_module(module: h.module):
"""
Generate a raw netlist yaml from a hdl21 module object which could be manually edited for specific instances
related to the corresponding SPICE.
"""

raw_netlist = generate_raw_netlist_dict_from_module(module)
return yaml.dump(raw_netlist, default_flow_style=False)
95 changes: 95 additions & 0 deletions gplugins/hdl21/sky130.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from collections.abc import Callable
from difflib import get_close_matches

import hdl21 as h
import sky130

from ..schematic_editor import SchematicEditor
from .netlist import (
_generate_raw_netlist_dict_from_proto_dict,
_parse_module_to_proto_dict,
)

custom_mapping_dict = {
"sky130_fd_pr__nfet_01v8": "sky130_fd_pr__rf_nfet_01v8_aM02W1p65L0p15",
"sky130_fd_pr__pfet_01v8": "sky130_fd_pr__rf_pfet_01v8_mcM04W3p00L0p15",
}


def find_most_relevant_gds(
component_name, component_dict=sky130.cells, custom_mapping=None
):
if custom_mapping is None:
custom_mapping = custom_mapping_dict

if component_name in custom_mapping.keys():
print(f"Mapping for {component_name}: {custom_mapping[component_name]}")
return custom_mapping[component_name]

all_components = [
name for name in component_dict.keys() if "rf_test_coil" not in name
]
closest_matches = get_close_matches(component_name, all_components, n=1, cutoff=0.1)
print(f"Closest matches for {component_name}: {closest_matches}")
return closest_matches[0] if closest_matches else component_name


def filter_port(port):
"""
Filter the port name to match spice declaration to gds port name, specifically focused on the SKY130nm technology.
"""
if port == "d":
return "DRAIN"
elif port == "g":
return "GATE"
elif port == "s":
return "SOURCE"
else:
return port


def hdl21_module_to_schematic_editor(
module: h.module,
yaml_schematic_file_name: str,
spice_gds_mapping_method: Callable | None = find_most_relevant_gds,
port_filter_method: Callable = filter_port,
) -> SchematicEditor:
"""
Constructs a SchematicEditor instance from a hdl21 module object.

Args:
module (h.module): The hdl21 module object.
yaml_schematic_file_name (str): The yaml schematic file name.
spice_gds_mapping_method (Callable): The method to map the spice instance name to the component name.
port_filter_method (Callable): The method to filter the port name.
"""
proto_dict = _parse_module_to_proto_dict(module)
raw_netlist_dict = _generate_raw_netlist_dict_from_proto_dict(proto_dict)

# This just gives us a raw structure of the hdl21 modules.
se = SchematicEditor(yaml_schematic_file_name)

for instance_name_i, instance_i in raw_netlist_dict["instances"].items():
# Maps the spice instance name to the component name.
# TODO implement setting mapping and custom name mapping
if spice_gds_mapping_method is None:
gds_component_name_i = instance_i["component"]
else:
gds_component_name_i = spice_gds_mapping_method(
instance_i["component"], sky130.cells
)
se.add_instance(
instance_name=instance_name_i,
component=sky130.cells[gds_component_name_i](),
)

for connection_source_i, connection_target_i in raw_netlist_dict[
"connections"
].items():
source_instance, source_port = connection_source_i.split(",")
target_instance, target_port = connection_target_i.split(",")
source_port = port_filter_method(source_port)
target_port = port_filter_method(target_port)
se.add_net(source_instance, source_port, target_instance, target_port)

return se
17 changes: 10 additions & 7 deletions gplugins/schematic_editor/schematic_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,16 +327,19 @@ def update_settings(
instance_name=instance, settings=settings, old_settings=old_settings
)

def add_net(self, inst1, port1, inst2, port2):
def add_net(self, inst1, port1, inst2, port2, allow_multiple: bool = False) -> None:
p1 = f"{inst1},{port1}"
p2 = f"{inst2},{port2}"
if p1 in self._connected_ports:
if self._connected_ports[p1] == p2:
return
current_port = self._connected_ports[p1]
raise ValueError(
f"{p1} is already connected to {current_port}. Can't connect to {p2}"
)
if allow_multiple:
pass
else:
if self._connected_ports[p1] == p2:
return
current_port = self._connected_ports[p1]
raise ValueError(
f"{p1} is already connected to {current_port}. Can't connect to {p2}"
)
self._connected_ports[p1] = p2
self._connected_ports[p2] = p1
old_nets = self._schematic.nets.copy()
Expand Down
Loading
Loading