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