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 all 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
2 changes: 1 addition & 1 deletion .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
run: |
eval "$(micromamba shell hook --shell=bash)"
micromamba activate
micromamba install -c conda-forge pymeep=*=mpi_mpich_* nlopt -y
micromamba install -c conda-forge pymeep=*=mpi_mpich_* nlopt hdl21 -y
make dev test-data
make docs
- name: Upload artifact
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
fail-fast: false
max-parallel: 12
matrix:
plugin: [femwell, gmsh, meow, sax, tidy3d, klayout, vlsir]
plugin: [femwell, gmsh, meow, sax, tidy3d, klayout, vlsir, hdl21]

name: Test ${{ matrix.plugin }}
steps:
Expand Down
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",
]
293 changes: 293 additions & 0 deletions gplugins/hdl21/netlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
"""
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 = connection["target"][0]

# Handle different structures of target
if "sig" in target:
target_signal = target["sig"]
elif "slice" in target:
target_signal = target["slice"][0]["signal"]
else:
# Log a warning or provide a default value for unknown structures
target_signal = "unknown_signal"
print(
f"Warning: Unknown target structure in connection for instance '{instance_name}', port '{portname}'"
)

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", []):
target = connection["target"][0]

# Handle different structures of target
if "sig" in target:
signal = target["sig"]
elif "slice" in target:
signal = target["slice"][0]["signal"]
else:
signal = None
print(
f"Warning: Unknown target structure for instance '{instance['name']}', port '{connection['portname']}'"
)

if signal == 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", []):
target = connection["target"][0]

if "sig" in target:
signal = target["sig"]
elif "slice" in target:
signal = target["slice"][0]["signal"]
else:
signal = None
print(
f"Warning: Unknown target structure for instance '{instance['name']}', port '{connection['portname']}'"
)

if signal == 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", []):
target = connection["target"][0]

# Handle different structures of target
if "sig" in target:
signal = target["sig"]
elif "slice" in target:
signal = target["slice"][0]["signal"]
else:
signal = None
print(
f"Warning: Unknown target structure in connection for instance '{instance_name}', port '{connection['portname']}'"
)

if signal == 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 = connection["target"][0]

if "sig" in target:
target_signal = target["sig"]
elif "slice" in target:
target_signal = target["slice"][0]["signal"]
else:
# Handle cases where 'target' does not have 'sig' or 'slice'
target_signal = "unknown_signal"

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)
Loading
Loading