444 lines
18 KiB
Python
444 lines
18 KiB
Python
from typing import Any, Optional
|
|
import nazca as nd
|
|
import numpy as np
|
|
|
|
from ..structures 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)
|
|
|
|
nd.Pin(name='a1').put(0,0,180)
|
|
nd.Pin(name='b1').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)
|
|
nd.Pin('a1',io=0,width=self.area[0]).put(self.area[0]/2,0,0)
|
|
nd.Pin('b1',io=1,width=self.area[1]).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)
|
|
|
|
nd.Pin(name='b1').put(R*np.cos(theta_stop/180*pi),R*np.sin(theta_stop/180*pi),90+theta_stop)
|
|
nd.Pin(name='a1').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
|
|
|