Files
mxpic_forge/mxpic/components/electronics/eic_units.py
T

468 lines
20 KiB
Python

from typing import Any, Optional
import nazca as nd
import numpy as np
from ..geometry import *
from ...technologies import *
from ..basic import __cell_arg__
class ISL:
def __init__ (self,xs: str,width: float,length: float,Lmax: int=10,spacing: float=11,sp_isl_metal: int=5,sp_isl_wg: int=5,rc_radius: int = 0,rc_points: int=64) -> None:
self.xs = xs
self.width = width
self.length = length
self.Lmax = Lmax
self.spacing = spacing
self.sp_isl_metal = sp_isl_metal ## this is the DRC of isolation xs to other xs
self.sp_isl_wg = sp_isl_wg ## this is the DRC of isolation xs to other xs
self.rc_radius = rc_radius ## this is the DRC of isolation xs to other xs
self.rc_points = rc_points ## this is the DRC of isolation xs to other xs
with nd.Cell(instantiate=False) as C:
for layers,growx,growy,acc in nd.layeriter(xs=xs):
(a1,b1), (a2,b2),c1,c2 = growx
if (b1==0 and b2==0):
if (Lmax!=None and spacing!=0):
X_isl = 0
while(1):
if ((length-X_isl)<Lmax and (length-X_isl)>0):
nd.strt(length=(length-X_isl),width=width,layer=layers).put(X_isl,0,0)
break
elif ((length-X_isl)<=0):
break
nd.strt(length=Lmax,width=width,layer=layers).put(X_isl,0,0)
X_isl = X_isl+Lmax+spacing
else :
if (rc_radius>0):
strt_round_courner(length=length*(a1-a2)+(b1-b2),width=width*(a1-a2)+(b1-b2),layer=layers,radius=rc_radius,n_points=rc_points).cell.put(0,0,0)
else:
nd.strt(length=length*(a1-a2)+(b1-b2),width=width*(a1-a2)+(b1-b2),layer=layers).put(-(b1-b2)/2,0,0)
else :
nd.strt(length=length*(a1-a2)+(b1-b2),width=width*(a1-a2)+(b1-b2),layer=layers).put(-(b1-b2)/2,0,0)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a1').put(0,0,180)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='opt_a1').put(0,0,180)
nd.Pin(name='ele_a1',type="electrical:").put(0,0,180)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b1').put(length,0,0)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='opt_b1').put(length,0,0)
nd.Pin(name='ele_b1',type="electrical:").put(length,0,0)
self.cell = C
""" Internal Logic revised """
class Vias:
def __init__ (self,xs:str,
area:'float|list',
sz:'float|list',
spacing:'float|list',
name : str = None,
xs_l1:str=None,
xs_l2:str=None,
# l1_tail:float=0,
# l2_tail:float=0,
instantiate: bool = False,
sp_via_xs: float = 0.2, ## spacing of via layer and others
via_cell: Any = None,
show_pins: bool=False) -> None:
"""_summary_
Args:
xs (str): via xsection, if None, no VIA holes will be added
area (float|list): the total size of the VIA area
sz (float|list): size of the VIA holes
spacing (float|list): edge spacing of the VIA holes
xs_l1 (str, optional): the xsection of the VIA connects
xs_l2 (str, optional): the xsection of the VIA connects
l1_tail (float, optional): the expansion length in xsection 1
l2_tail (float, optional): the expansion length in xsection 2
"""
# if (name!=None):
# self.instantiate = True
# else :
# self.instantiate = False
self.name = name
self.instantiate = instantiate
if (isinstance(sz,int) or isinstance(sz,float)):
sz = [sz,sz]
self.sz = sz
if (isinstance(spacing,int) or isinstance(spacing,float)):
spacing = [spacing,spacing]
self.spacing = spacing
if (isinstance(area,int) or isinstance(area,float)):
area = [area,area]
self.area = area
if(xs!=None):
try:
nd.get_xsection(xs)
except:
print("==eic::eic_units::via::xs_via::",xs," not defined in tapeout")
xs = None
self.xs = xs
self.sp_via_xs = sp_via_xs
if (isinstance(sp_via_xs,int) or isinstance(sp_via_xs,float)):
self.sp_via_edge = [sp_via_xs,sp_via_xs]
else:
self.sp_via_edge = sp_via_xs
szVia = np.zeros(np.shape(self.sz))
""" Revised in 2025.08.10 """
""" Previou self.szVia = self.sz """
""" Previous version will cause influenc to self.sz """
if (self.sz[0]==0 or self.sz==None or self.sz[1]==0):
szVia[0] = self.area[0]-self.sp_via_edge[0]*2
szVia[1] = self.area[1]-self.sp_via_edge[1]*2
else:
szVia[0] = self.sz[0]
szVia[1] = self.sz[1]
self.szVia = szVia
""" Initiating parameters """
self.xs_l1 = xs_l1
self.xs_l2 = xs_l2
self.instantiate = instantiate
## using below can avoid renaming cells
self.via = None
if (via_cell==None):
if (xs!=None and self.instantiate==True):
try :
self.via = nd.get_Cell(name=xs)
except:
with nd.Cell(name=xs,instantiate=instantiate) as viaCell:
if (self.xs!=None):
nd.strt(length=self.szVia[0],width=self.szVia[1],xs=xs).put(-self.szVia[0]/2,0,0)
self.via = viaCell
else :
with nd.Cell(instantiate=False) as viaCell:
if (self.xs!=None):
nd.strt(length=self.szVia[0],width=self.szVia[1],xs=xs).put(-self.szVia[0]/2,0,0)
self.via = viaCell
else :
self.via = via_cell
self.show_pins = show_pins
self.cell = self.generate_gds()
def generate_gds(self):
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
""" For AMF, only one via allowed """
via_period_x = self.szVia[0] + self.spacing[0]
num_x = int(np.floor((self.area[0]-self.sp_via_edge[0])/via_period_x))
via_period_y = self.szVia[1] + self.spacing[1]
num_y = int(np.floor((self.area[1]-self.sp_via_edge[1])/via_period_y))
### Cutting into small segments to place faster
n_unit = 15
ny_sects = num_y//n_unit
res_y = np.mod(num_y,n_unit)
nx_sects = num_x//n_unit
res_x = np.mod(num_x,n_unit)
# print(num_x,num_y,nx_sects,ny_sects)
offset_Y = res_y/2*via_period_y
offset_X = res_x/2*via_period_x
if (self.xs!=None):
if (self.instantiate):
""" This is used for IMEC and CUMEC with their via are small holes """
""" Adding vias by Big-Little segments """
if (nx_sects>0 and ny_sects>0):
try :
seg_full = nd.get_Cell(name=self.xs+'_'+str(n_unit)+'x'+str(n_unit))
except:
with nd.Cell(name=self.xs+'_'+str(n_unit)+'x'+str(n_unit),instantiate=self.instantiate) as seg_full:
for _idx_x_ in range(0,n_unit):
for _idx_y_ in range(0,n_unit):
self.via.put((_idx_x_-(n_unit-1)/2)*via_period_x,(_idx_y_-(n_unit-1)/2)*via_period_y,0)
for idx_x_seg in range(0,nx_sects):
for idx_y_seg in range(0,ny_sects):
seg_full.put((idx_x_seg-(nx_sects-1)/2)*via_period_x*n_unit -offset_X ,
(idx_y_seg-(ny_sects-1)/2)*via_period_y*n_unit -offset_Y,0)
if (nx_sects>0 and res_y>0):
try :
seg_x = nd.get_Cell(name=self.xs+'_'+str(n_unit)+'x'+str(res_y))
except:
with nd.Cell(name=self.xs+'_'+str(n_unit)+'x'+str(res_y),instantiate=self.instantiate) as seg_x:
for _idx_x_ in range(0,n_unit):
for _idx_y_ in range(0,res_y):
self.via.put((_idx_x_-(n_unit-1)/2)*via_period_x,(_idx_y_ - (res_y-1)/2)*via_period_y,0)
for idx_x_seg in range(0,nx_sects):
seg_x.put((idx_x_seg-(nx_sects-1)/2)*via_period_x*n_unit - offset_X,
(ny_sects)*via_period_y*n_unit/2,0)
if (ny_sects>0 and res_x>0):
try :
seg_y = nd.get_Cell(name=self.xs+'_'+str(res_x)+'x'+str(n_unit))
except:
with nd.Cell(name=self.xs+'_'+str(res_x)+'x'+str(n_unit),instantiate=self.instantiate) as seg_y:
for _idx_x_ in range(0,res_x):
for _idx_y_ in range(0,n_unit):
self.via.put((_idx_x_ - (res_x-1)/2)*via_period_x,(_idx_y_-(n_unit-1)/2)*via_period_y,0)
for idx_y_seg in range(0,ny_sects):
seg_y.put((nx_sects)*via_period_x*n_unit/2,
(idx_y_seg-(ny_sects-1)/2)*via_period_y*n_unit - offset_Y,0)
if (res_y>0 and res_x>0):
try :
res = nd.get_Cell(name=self.xs+'_'+str(res_x)+'x'+str(res_y))
except:
with nd.Cell(name=self.xs+'_'+str(res_x)+'x'+str(res_y),instantiate=self.instantiate) as res:
for _idx_x_ in range(0,res_x):
for _idx_y_ in range(0,res_y):
self.via.put((_idx_x_-(res_x-1)/2)*via_period_x,(_idx_y_ - (res_y-1)/2)*via_period_y,0)
res.put((nx_sects)*via_period_x*n_unit/2,
(ny_sects)*via_period_y*n_unit/2,0)
else:
self.via.put(0,0,0)
if (self.xs_l1!=None):
nd.strt(length=self.area[0],width=self.area[1],xs=self.xs_l1).put(-self.area[0]/2,0,0)
# if (l1_tail!=0):
# nd.strt(length=l1_tail+self.area[0]/2,width=self.area[1],xs=self.xs_l1).put(0,0,0)
if (self.xs_l2!=None):
nd.strt(length=self.area[0],width=self.area[1],xs=self.xs_l2).put(-self.area[0]/2,0,0)
# if (l2_tail!=0):
# nd.strt(length=l2_tail+self.area[0]/2,width=self.area[1],xs=self.xs_l2).put(0,0,180)
nd.Pin('a0',io=0,width=self.area[0]).put(0,0,0)
nd.Pin('b0',io=1,width=self.area[1]).put(-0,0,180)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin('a1',io=0,width=self.area[0]).put(self.area[0]/2,0,0)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin('opt_a1',io=0,width=self.area[0]).put(self.area[0]/2,0,0)
nd.Pin('ele_a1',io=0,width=self.area[0],type="electrical:").put(self.area[0]/2,0,0)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin('b1',io=1,width=self.area[1]).put(-self.area[0]/2,0,180)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin('opt_b1',io=1,width=self.area[1]).put(-self.area[0]/2,0,180)
nd.Pin('ele_b1',io=1,width=self.area[1],type="electrical:").put(-self.area[0]/2,0,180)
if (self.show_pins):
nd.put_stub()
return C
class PAD :
def __init__(
self,
name: Optional[str]=None,
xs_pad:str='pad',
length:float=80,
width:float=60,
edge:float=5,
show_pins: bool=False
) -> None:
self.name = name
if (name==None):
self.instantiate = False
else :
self.instantiate = True
self.xs_pad = xs_pad
self.length = length
self.width = width
self.edge = edge
self.show_pins = show_pins
self.cell = self.generate_gds()
def generate_gds(self) :
with nd.Cell(instantiate=self.instantiate,name=self.name) as ICell :
for layers,growx,growy,acc in nd.layeriter(xs=self.xs_pad):
(a1,b1), (a2,b2),c1,c2 = growx
_L_ = self.length*(a1-a2)+(b1-b2)
_W_ = self.width*(a1-a2)+(b1-b2)
nd.strt(length=_L_,width=_W_,layer=layers).put(0,-_L_/2,90)
nd.Pin(name='p1',width=self.width).put(0,0,90)
if (self.show_pins):
nd.put_stub(pinsize=3)
return ICell
class PADs :
def __init__(
self,
pad: Any,
num : int,
rows:int=1,
x_spacing:float=100,
y_spacing:float=120,
row_offset:float=50,
show_pins: bool=False,
cross_naming: bool = True,
absent: list = [],
) -> None:
self.num = num
## Revised by QY in 2023.1.3
## self.pad = pad
self.pad = __cell_arg__(arg=pad,arg_name="pad",func_name="mxpic::eic::PADs")
self.rows = rows
self.x_spacing = x_spacing
self.y_spacing = y_spacing
self.row_offset = row_offset
self.show_pins = show_pins
self.cross_naming = cross_naming
self.absent = absent
self.cell = self.generate_gds()
def generate_gds(self) :
with nd.Cell(instantiate=False) as ICell :
idx = np.linspace(0,self.num-1,self.num)
row_idx = np.linspace(0,self.rows-1,self.rows)
for _row_ in row_idx :
for _idx_ in idx :
if (self.cross_naming):
pin_idx = _idx_*self.rows+_row_+1
else:
pin_idx = _idx_+_row_*self.num+1
if (pin_idx in self.absent):
continue
instr = self.pad.put(
'p1',
_idx_*self.x_spacing+self.row_offset*_row_,
-_row_*self.y_spacing,
-90
)
instr.raise_pins(['p1'],['p'+str(int(pin_idx))])
# nd.Pin(
# name='p'+str(int(pin_idx)),
# width=
# ).put(
# _idx_*self.x_spacing+self.row_offset*_row_,
# -_row_*self.y_spacing,
# 90
# )
if self.show_pins :
nd.put_stub()
return ICell
class Vias_arc:
def __init__(self,
R: float=10,
w: float=3,
theta_start: float=0,
theta_stop: float = 90,
xs: str = 'via_s2m',
sz:'float|list'=0.5, ## if sz = 0, the area will be filled with layer via
spacing:'float|list'=0.8,
sp_via_xs:float=0.2,
xs_l1:str='sa',
xs_l2:str='metal',
res: float = 0.5, ## resolution
via_cell: Any = None,
show_pins: bool=False
) -> None:
if (isinstance(sz,int) or isinstance(sz,float)):
sz = [sz,sz]
if (isinstance(spacing,int) or isinstance(spacing,float)):
spacing = [spacing,spacing]
if (via_cell==None):
try :
via = nd.get_Cell(name=xs)
except:
with nd.Cell(name=xs) as via:
nd.strt(length=sz[0],width=sz[1],xs=xs).put(-sz[0]/2,0,0)
else :
via = via_cell
with nd.Cell(instantiate=False) as C:
L_arc = R*np.abs(theta_start-theta_stop)/180*pi ## length of arc in um
n_points = int(np.abs(np.round(L_arc/res))+1)
if (xs_l1!=None):
circle(radius=R,width=w,theta_start=theta_start,theta_stop=theta_stop,xs=xs_l1,res=res,
# n_points=n_points
).cell.put(0,0,0)
if (xs_l2!=None):
circle(radius=R,width=w,theta_start=theta_start,theta_stop=theta_stop,xs=xs_l2,res=res,).cell.put(0,0,0)
if (sz[0]>0 and sz[1]>0):
rows = int(np.floor((w-max(spacing))/(max(sz) +max(spacing)))) ## maximum rows in radius direction
for r_idx in range(0,rows):
R_cur = R - (r_idx-(rows-1)/2)*(max(sz) +max(spacing))
nums = abs(int(np.floor(((R_cur-max(spacing))*np.abs(theta_stop-theta_start)*pi/180)/(max(sz) +max(spacing)))))
# print("nums:: ",nums) 2022.11.27 DEBUG
angle = np.linspace(theta_start/180*pi+(max(spacing)/R_cur+max(sz)/R_cur),theta_stop/180*pi-(max(spacing)/R_cur+max(sz)/R_cur),nums)
for n_idx in range(0,nums):
x = R_cur * np.cos(angle[n_idx])
y = R_cur * np.sin(angle[n_idx])
via.put(x,y,0)
# nd.strt(width=sz[1],length=sz[0],xs=xs).put(x-sz[0]/2,y,0)
else :
R_cur = R
w_via = w-2*sp_via_xs
A_start = theta_start/180*pi+((np.max(sp_via_xs)+w_via/2)/R_cur)
A_stop = theta_stop/180*pi-((np.max(sp_via_xs)+w_via/2)/R_cur)
circle(radius=R_cur,width=w_via,theta_start=A_start/pi*180,theta_stop=A_stop/pi*180,xs=xs,res=res,
).cell.put(0,0,0)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b1').put(R*np.cos(theta_stop/180*pi),R*np.sin(theta_stop/180*pi),90+theta_stop)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='opt_b1').put(R*np.cos(theta_stop/180*pi),R*np.sin(theta_stop/180*pi),90+theta_stop)
nd.Pin(name='ele_b1',type="electrical:").put(R*np.cos(theta_stop/180*pi),R*np.sin(theta_stop/180*pi),90+theta_stop)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a1').put(R*np.cos(theta_start/180*pi),R*np.sin(theta_start/180*pi),-90+theta_start)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='opt_a1').put(R*np.cos(theta_start/180*pi),R*np.sin(theta_start/180*pi),-90+theta_start)
nd.Pin(name='ele_a1',type="electrical:").put(R*np.cos(theta_start/180*pi),R*np.sin(theta_start/180*pi),-90+theta_start)
A_mid = (theta_start+theta_stop)/2
nd.Pin(name='c1').put(R*np.cos(A_mid/180*pi),R*np.sin(A_mid/180*pi),A_mid)
if (show_pins):
nd.put_stub()
self.cell = C