Files
mxpic_forge/mxpic/cores/pinJsonFileGen.py
T
2026-06-04 23:21:39 +08:00

472 lines
16 KiB
Python

import time
import nazca as nd
import json,yaml
import os,sys
import gdstk
from pathlib import Path
import cairosvg
import math
# ==============================================================================
# ANSI Escape Codes for Console Logging
# ==============================================================================
RED = "\033[1;31m"
GRE = "\033[1;32m"
YEL = "\033[1;33m"
BLU = "\033[1;34m"
PUR = "\033[1;35m"
CRY = "\033[1;36m"
ENC = "\033[0m"
def encode_string(s, alphabet="1234567890ABCDEFGHIJKLMNOPGRSTUVWXYZ"):
base = len(alphabet)
num = int.from_bytes(s.encode(), 'big')
result = ""
while num > 0:
num, rem = divmod(num, base)
result = alphabet[rem] + result
return result
def decode_string(encoded, alphabet):
base = len(alphabet)
num = 0
for c in encoded:
num = num * base + alphabet.index(c)
length = (num.bit_length() + 7) // 8
return num.to_bytes(length, 'big').decode()
def mx_pdk_gen(fname:str,cell,fpath=None,info=None,layer_exclude=None):
""" Disposing fname and fpath """
if (fpath[-1] != "\\"):
fpath = fpath + "\\"
if (fname.endswith(".gds")):
fname = fname.removesuffix(".gds")
# ## 1. adding information to the ports ##
# with nd.Cell(instantiate=False) as cell_with_info:
# inst = cell.put(0,0,0)
# for pinName,Pin in cell_with_info.ic_pins():
# fdict = {}
# fdict["x"] = Pin.x
# fdict["y"] = Pin.y
# fdict["a"] = Pin.a
# fdict["width"] = Pin.width
# fdict["name"] = pinName
# nd.put_parameters(parameters=fdict,pin=Pin)
# nd.put_stub(pinsize=4)
## 2. export gds files ##
nd.export_gds(filename=fpath+fname+".gds",topcells=cell)
## 3. creating other releated files including .yml, _BB.gds and .png files ##
pinFileGen(fname=fname,cell=cell,fpath=fpath,info=info, layer_exclude=layer_exclude)
def pinFileGen(fname, cell, fpath=None, info=None, layer_exclude=None):
fdict = {}
data_yml = {}
timeCurr = time.strftime("%Y%m%d-%H%M%S")
fdict["time"] = timeCurr
data_yml["ports"] = {}
for pinName, Pin in cell.ic_pins():
fdict[pinName] = {
"x": Pin.x,
"y": Pin.y,
"a": Pin.a,
"width": Pin.width
}
pin_type = "None" if Pin.type is None else Pin.type
pin_descript = "None"
if (Pin.type is not None ):
pin_descript = pin_type.split(":")[1]
pin_type = pin_type.split(":")[0]
data_yml["ports"][pinName] = {
"x": round(float(Pin.x), 3),
"y": round(float(Pin.y), 3),
"a": round(float(Pin.a), 3),
"width": "None" if Pin.width is None else round(float(Pin.width), 3),
"type": pin_type,
"description": pin_descript,
}
# Safely handle directory and paths using pathlib
output_dir = Path(fpath) if fpath else Path.cwd()
output_dir.mkdir(parents=True, exist_ok=True)
base_path = output_dir / fname
# File targets
orig_gds_path = base_path.with_suffix(".gds")
bb_gds_path = output_dir / f"{fname}_BB.gds"
json_path = base_path.with_suffix(".json")
yml_path = base_path.with_suffix(".yml")
orig_png_path = base_path.with_suffix(".svg")
bb_png_path = output_dir / f"{fname}_BB.svg"
file_size_kb = 0.0
box_sz = [0.0, 0.0]
## Generation of black box cell
try:
if orig_gds_path.exists():
file_size_bytes = os.path.getsize(orig_gds_path)
file_size_kb = round(float(file_size_bytes / 1024), 2)
# Your existing black box generator
box_sz = generate_bb_and_svgs(gds_name=str(orig_gds_path), pin_info=data_yml["ports"], fpath=str(output_dir),layer_exclude=layer_exclude)
# box_sz = BB_gds_gen(gds_name=str(orig_gds_path), pin_info=data_yml["ports"], fpath=str(output_dir))
print(f"{GRE} Black box GDS cell successfully generated for {YEL}{fname}{ENC}")
# Trigger PNG Generation for both Original and Black-Box
# if orig_gds_path.exists():
# render_gds_to_svg(orig_gds_path, orig_png_path)
# if bb_gds_path.exists():
# render_gds_to_svg(bb_gds_path, bb_png_path)
except Exception as e:
print(f"{RED} Error in Black box or PNG generation of {fname} in path {YEL}{output_dir}{ENC}. Details: {e}")
## Creating .json and .yml files
try:
with open(json_path, 'w') as f:
json.dump(fdict, f, indent=4)
if info is not None:
data_yml = {**info, **data_yml}
data_yml["time"] = timeCurr
if box_sz is not None and len(box_sz) >= 2:
data_yml["box_size"] = [round(float(box_sz[0]), 3), round(float(box_sz[1]), 3)]
else:
data_yml["box_size"] = [0.0, 0.0]
data_yml["file_size"] = f"{file_size_kb} KB"
with open(yml_path, mode="w") as Fyml:
yaml.dump(data=data_yml, stream=Fyml, sort_keys=False)
print(f"{GRE} Pin file successfully generated in {YEL}{yml_path.name}{ENC}")
except Exception as e:
print(f"{RED} Error in configuration file generation of {fname} in path {YEL}{output_dir}{ENC}. Details: {e}")
def cellLoad(gdsName,path = "",cellName=None,pinFileName = None,cellresued=None):
if (gdsName[-4:] == ".gds"):
fname = gdsName[0:-4]
else:
fname = gdsName
gdsName = gdsName + ".gds"
if (cellName is None):
cellName = fname
""" Older json pin file recording system """
if (pinFileName is None):
if (os.path.isfile(path + fname + ".yml")):
print(f"{path +fname}.yml file exist")
pinFileName = fname + ".yml"
elif (os.path.isfile(path + fname + ".json")):
print(f"{path +fname}.json file exist")
pinFileName = fname + ".json"
else:
raise Exception(f"No meta para file found for {path + fname}")
if (pinFileName.find(".yml") != -1):
with open(file=path + pinFileName,mode="r") as Fpara:
paraList = yaml.load(Fpara,Loader=yaml.FullLoader)
pin_val = paraList["ports"]
elif (pinFileName.find(".json") != -1):
with open(file=path + pinFileName,mode="r") as Fpara:
pin_val = json.load(Fpara)
""" Loading GDS """
inst = nd.load_gds(filename=path+gdsName,cellname=cellName,newcellname=cellName,cellsreused=cellresued)
for keys in pin_val:
if (keys == "time"):
continue
inst._put_pin(name=keys,width=pin_val[keys]["width"],connect=(pin_val[keys]["x"],pin_val[keys]["y"],pin_val[keys]["a"]))
return inst
def render_gds_to_png(gds_path: Path, png_path: Path):
"""
Renders a PNG from a GDS file, handling massive chip dimensions
by enforcing a strict pixel limit to prevent Cairo memory crashes.
"""
try:
# 1. Read the saved GDS file
lib = gdstk.read_gds(str(gds_path))
top_cells = lib.top_level()
if not top_cells:
raise ValueError(f"No top-level cell found in {gds_path.name}")
top_cell = top_cells[0]
# 2. Define a temporary SVG path
svg_path = gds_path.with_suffix(".svg")
# 3. Export to SVG
# CRITICAL FIX: Add scaling=1.0. gdstk defaults to 10, which artificially
# inflates the base SVG document size before Cairo even touches it.
top_cell.write_svg(str(svg_path), background="white", scaling=1.0)
# 4. Convert the SVG to a high-res PNG
# CRITICAL FIX: Replace `scale=2.0` with `output_width=2048`.
# Cairo will now force the width to 2048px and auto-calculate the height,
# ensuring the image is always high-res but never exceeds memory limits.
cairosvg.svg2png(
url=str(svg_path),
write_to=str(png_path),
output_width=2048 # You can adjust this for more/less resolution
)
# 5. Clean up the temporary SVG file
if svg_path.exists():
svg_path.unlink()
print(f"{GRE} Image successfully generated: {YEL}{png_path.name}{ENC}")
except Exception as e:
print(f"{RED} Failed to generate PNG for {gds_path.name}: {e}{ENC}")
def render_gds_to_svg(gds_path: Path, svg_path: Path):
"""
Renders an SVG from a GDS file.
SVGs are vector-based, allowing infinite zoom for inspecting fine PIC details
and completely avoiding memory limits associated with pixel-based PNGs.
"""
try:
# 1. Read the saved GDS file
lib = gdstk.read_gds(str(gds_path))
top_cells = lib.top_level()
if not top_cells:
raise ValueError(f"No top-level cell found in {gds_path.name}")
top_cell = top_cells[0]
# 2. Export directly to SVG
# We remove scaling=1.0 because web browsers handle standard SVG coordinate
# scaling natively without any memory penalty.
top_cell.write_svg(str(svg_path), background="white")
print(f"{GRE} Image successfully generated: {YEL}{svg_path.name}{ENC}")
except Exception as e:
print(f"{RED} Failed to generate SVG for {gds_path.name}: {e}{ENC}")
def BB_gds_gen(gds_name, pin_info,fpath = None, BB_Layer=1200, pin_rec_layer=1205,pin_text_layer=1001):
# 1. Load the existing GDS file
library = gdstk.read_gds(gds_name)
top_cell = library.top_level()[0]
# 2. Calculate the Bounding Box (Extents)
# Returns [(min_x, min_y), (max_x, max_y)]
bbox = top_cell.bounding_box()
(min_x, min_y), (max_x, max_y) = bbox
# Create the new black box cell
bb_cell = gdstk.Cell(f"{top_cell.name}_BB")
output_filename = f"{top_cell.name}_BB"
# 3. Create the boundary box on Layer 1001
# We create a rectangle using the min/max coordinates
rect = gdstk.rectangle((min_x, min_y), (max_x, max_y), layer=BB_Layer, datatype=0)
bb_cell.add(rect)
# 3.1 Return of the box size
box_sz = [abs(max_x-min_x),abs(max_y-min_y)]
# 5. Place Pins on Layer 1015
# Assuming YAML structure: pins: [{name: "P1", x: 10, y: 20, a: 0, width: 2}, ...]
for pin in pin_info:
# Rounding to your preferred 0.001 resolution
px = round(pin_info[pin]['x'], 3)
py = round(pin_info[pin]['y'], 3)
pa = pin_info[pin]['a']
pw = pin_info[pin]['width']
if (pw == "None"):
pw = 0.5
# Create a small square box centered at the pin location
pin_box = gdstk.rectangle(
(px - pw/2, py - pw/2),
(px + pw/2, py + pw/2),
layer=pin_rec_layer
)
# Rotate the pin box if necessary
pin_box.rotate(pa * (3.14159 / 180), center=(px, py))
bb_cell.add(pin_box)
# Add Label/Text for the pin
text = gdstk.Label(
text = pin,
origin = (px, py),
magnification=3, # Scale text relative to pin width
layer=pin_text_layer
)
bb_cell.add(text)
# 6. Save the new Library
new_lib = gdstk.Library()
new_lib.add(bb_cell)
if (fpath[-1] != "\\"):
fpath = fpath + "\\"
new_lib.write_gds(fpath + output_filename+".gds")
return box_sz
def generate_bb_and_svgs(gds_name, pin_info, fpath=None, BB_Layer=1200, pin_rec_layer=1205, pin_text_layer=1001, layer_exclude: list = None):
"""
Reads an original GDS, generates a Black Box GDS, and outputs two SVGs.
layer_exclude can contain integers (e.g., 5) to exclude all datatypes on that layer,
or tuples (e.g., (23, 0)) to exclude specific layer/datatype pairs.
"""
if layer_exclude is None:
layer_exclude = []
try:
gds_path = Path(gds_name)
output_dir = Path(fpath) if fpath else gds_path.parent
output_dir.mkdir(parents=True, exist_ok=True)
base_name = gds_path.stem
bb_gds_path = output_dir / f"{base_name}_BB.gds"
orig_svg_path = output_dir / f"{base_name}.svg"
bb_svg_path = output_dir / f"{base_name}_BB.svg"
library = gdstk.read_gds(str(gds_path))
# ---------------------------------------------------------
# NEW: Smart Exclusion Logic for (Layer, Datatype)
# ---------------------------------------------------------
def is_excluded(layer, datatype):
for item in layer_exclude:
# Check if the user passed a (layer, datatype) tuple
if isinstance(item, (tuple, list)) and len(item) == 2:
if layer == item[0] and datatype == item[1]:
return True
# Check if the user passed just a base layer integer
elif layer == item:
return True
return False
if layer_exclude:
for cell in library.cells:
elements_to_remove = []
# Check Polygons (.datatype)
for p in cell.polygons:
if is_excluded(p.layer, p.datatype):
elements_to_remove.append(p)
# Check Paths (paths can have multiple parallel traces, so we check all pairs)
for p in cell.paths:
if any(is_excluded(l, d) for l, d in zip(p.layers, p.datatypes)):
elements_to_remove.append(p)
# Check Labels (.texttype is the GDS equivalent of datatype for text)
for lbl in cell.labels:
if is_excluded(lbl.layer, lbl.texttype):
elements_to_remove.append(lbl)
if elements_to_remove:
cell.remove(*elements_to_remove)
# ---------------------------------------------------------
top_cells = library.top_level()
if not top_cells:
raise ValueError(f"No top-level cell found in {gds_path.name}")
top_cell = top_cells[0]
bbox = top_cell.bounding_box()
if bbox is None:
min_x, min_y, max_x, max_y = 0.0, 0.0, 0.0, 0.0
else:
(min_x, min_y), (max_x, max_y) = bbox
box_sz = [abs(max_x - min_x), abs(max_y - min_y)]
bb_cell = gdstk.Cell(top_cell.name)
rect = gdstk.rectangle((min_x, min_y), (max_x, max_y), layer=BB_Layer, datatype=0)
bb_cell.add(rect)
for pin_name, pin_data in pin_info.items():
px = round(pin_data['x'], 3)
py = round(pin_data['y'], 3)
pa = pin_data['a']
pw = pin_data.get('width', 0.5)
if pw == "None" or pw is None:
pw = 0.5
box_min = (px - pw/2, py - pw/2)
box_max = (px + pw/2, py + pw/2)
orig_pin_box = gdstk.rectangle(box_min, box_max, layer=pin_rec_layer)
orig_pin_box.rotate(pa * (math.pi / 180), center=(px, py))
orig_text = gdstk.Label(
text = pin_name, origin = (px, py), magnification=1, layer=pin_text_layer
)
top_cell.add(orig_pin_box)
top_cell.add(orig_text)
bb_pin_box = gdstk.rectangle(box_min, box_max, layer=pin_rec_layer)
bb_pin_box.rotate(pa * (math.pi / 180), center=(px, py))
bb_text = gdstk.Label(
text = pin_name, origin = (px, py), magnification=1, layer=pin_text_layer
)
bb_cell.add(bb_pin_box)
bb_cell.add(bb_text)
new_lib = gdstk.Library()
new_lib.add(bb_cell)
new_lib.write_gds(str(bb_gds_path))
print(f"Black Box GDS saved: {bb_gds_path.name}")
top_cell.write_svg(str(orig_svg_path), background="white")
print(f"Original SVG generated: {orig_svg_path.name}")
bb_cell.write_svg(str(bb_svg_path), background="white")
print(f"Black Box SVG generated: {bb_svg_path.name}")
return box_sz
except Exception as e:
print(f"Error generating BB and SVGs for {gds_name}: {e}")
return [0.0, 0.0]