Files
mxpic_forge/mxpic/routing/routing.py
T
2026-06-06 16:43:26 +08:00

1054 lines
47 KiB
Python

import nazca as nd
import numpy as np
import nazca.trace as trace
import nazca.cfg as cfg
from nazca.netlist import interconnect_logger
import math as m
import nazca.cp as cp
from ..components.geometry import *
from ..components.geometry import _my_polygon
# from .dev_passive import *
def ic_exception(msg=''):
"""Raise interconnect exception if conditions apply."""
if cfg.interconnect_raise:
if cfg.interconnect_msgnum is None\
or cfg.interconnect_msgcnt == cfg.interconnect_msgnum:
raise Exception(msg)
'''
Maxwell's lib of routing for both electrics and photonics
Based on Nazca.Interconnect
'''
class Route(nd.interconnects.Interconnect):
def __init__(self, radius=None, width=None, angle=90, Ltp_mm = 10, width2_mm=1.0, MM_route = False,
xs=None, layer=None, adapt_width=False, adapt_xs=False,
instantiate=False, pinstyle=None, offset=None, varname=None, doc='', PCB=False, modes=None,sharp_patch=True):
self.PCB= PCB
self.sharp_patch = sharp_patch
self.Ltp_mm = Ltp_mm
if (width2_mm is None):
self.width2_mm = width
else :
self.width2_mm = width2_mm
self.MM_route = MM_route
if (self.PCB==True):
self.MM_route = False ## Override the multimode routing
super().__init__(radius=radius, width=width, angle=angle,
xs=xs, layer=layer, adapt_width=adapt_width, adapt_xs=adapt_xs,
instantiate=instantiate, pinstyle=pinstyle, offset=offset, varname=varname, doc=doc, PCB=PCB, modes=modes)
def connPatch(self,):
pass
def rt_bend (self,width=3,xs='strip',angle=90,layer=None,pin=None):
trace.trace_start()
pin = self._getpinout(pin)
with nd.Cell(instantiate=False) as ICcell:
if (layer==None):
for layers,growx,growy,acc in nd.layeriter(xs=xs):
(a1,b1), (a2,b2),c1,c2 = growx
nd.strt(length=width*(a1-a2)+(b1-b2),width=width*(a1-a2)+(b1-b2),layer=layers).put(-(width*(a1-a2)+(b1-b2))/2,0,0)
nd.Pin('a0',io=1,xs=xs,width=pin.width).put(0,0,0)
nd.Pin('b0',io=0,xs=xs,width=pin.width).put(0,0,angle)
else :
nd.strt(length=width,width=width,layer=layer).put(-width/2,0,0)
nd.Pin('a0',io=1,xs=xs,width=pin.width).put(0)
nd.Pin('b0',io=0,xs=xs,width=pin.width).put(angle)
trace.trace_stop()
ICcell.length_geo = trace.trace_length()
cfg.cp=pin
return ICcell
def strt_mm(self,pin=None,width2=None,Ltp=None,width1=None,Lstart=0.5,length=None,xs=None,arrow=False):
pin = self._getpinout(pin)
xs = self._getxs(pin, xs)
width1 = self._getwidth(pin, width1, xs)
pinflip = not nd.get_xsection(xs).symmetry
if (Ltp==None):
Ltp = self.Ltp_mm
if (width2==None):
width2 = self.width2_mm
if pin is None:
pin = cp.here()
if (width2==None):
width2 = self._getwidth(pin, width2, xs)
if (length>(Ltp*2+Lstart*2)):
Lmm = length-Ltp*2-Lstart*2
else :
Lmm = 0
trace.trace_start()
with nd.Cell(instantiate=False) as ICcell:
if (Lmm==0):
tr = self.strt(length=length,width=width1).put(0)
nd.Pin('a0', io=0, width=width1, xs=xs).put(tr.pin['a0'])
nd.Pin('b0', io=1, width=width1, xs=xs).put(tr.pin['b0'])
else:
tr = self.strt(length=Lstart,width=width1).put(0)
self.taper(width1=width1,length=Ltp,width2=width2).put()
if (Lmm==None):
Lmm=0
self.strt(length=Lmm,width=width2).put()
self.taper(width1=width2,length=Ltp,width2=width1).put()
tl = self.strt(length=Lstart,width=width1).put()
patch = self.strt(length=0.1,width=width1).put(tr.pin['b0'].move(-0.05,0,0))
patch = self.strt(length=0.1,width=width1).put(tl.pin['a0'].move(-0.05,0,0))
nd.Pin('a0', io=0, width=width1, xs=xs).put(tr.pin['a0'])
nd.Pin('b0', io=1, width=width1, xs=xs).put(tl.pin['b0'])
if arrow:
self.arrow.put(ICcell.pin['a0'])
self.arrow.put(ICcell.pin['b0'], flip=pinflip)
trace.trace_stop()
ICcell.length_geo = trace.trace_length()
cfg.cp=pin
return ICcell
""" Multimode swithing """
def line_mm(self,length=None,width=None,xs=None,width_mm=None,Ltp=None,):
if (self.MM_route):
return self.strt_mm(length=length,width1=width,xs=xs,width2=width_mm,Ltp=Ltp)
else:
return self.line(length,width,xs)
""" Multimode waveguide inside strt_p2p """
def strt_mm_p2p(self,pin1=None, pin2=None,width2=None,Ltp=None,width1=None,Lstart=0.5,length=None,xs=None,arrow=False,name=None):
parse, (length, b) = self._strt_p2p_solve(pin1=pin1, pin2=pin2,
width=width1, xs=xs)
pin1, pin2, xs, width, _, radius1, radius2 = parse
if (Ltp==None):
Ltp = self.Ltp_mm
if (width2==None):
width2 = self.width2_mm
if (Ltp*2 + Lstart*2 >= length):
return self.strt_p2p(pin1=pin1,pin2=pin2,width=width,xs=xs,name=None,arrow=arrow)
else :
if name is None:
name = 'ic_strt_p2p'
with nd.Cell('{}_{}'.format(name, xs), instantiate=self.instantiate, cnt=True) as ICcell:
ICcell.group = 'interconnect'
trace.trace_start()
s1 = self.line(length=Lstart, width=width, xs=xs).put(0,0,b)
p1 = nd.Pin('a0', io=0, width=width, xs=xs).put(s1.pin['a0'].rot(-b))
s1 = self._taper(length=Ltp,width1=width,width2=width2, xs=xs).put()
e1 = self.line(length=length-Ltp*2-Lstart*2, width=width2, xs=xs).put()
d1 = self._taper(length=Ltp,width1=width2,width2=width, xs=xs).put()
d1 = self.line(length=Lstart, width=width, xs=xs).put()
p2 = nd.Pin('b0', io=1, width=width, xs=xs).put(d1.pin['b0'].rot(-b-pin1.a+pin2.a+180))
if arrow:
self.arrow.put(ICcell.pin['a0'])
self.arrow.put(ICcell.pin['b0'])
trace.trace_stop()
ICcell.length_geo = trace.trace_length()
ICcell.pin2 = pin2
# connect_opt needed because the extra rot in p1 and p2 does hide the line optical connection:
nd.connect_path(p1, p2, trace.trace_length())
cfg.cp = pin1
return ICcell
def bend_p2p(self,pin1=None, pin2=None, radius=None, width=None, xs=None, name=None, arrow=False, width_mm=None,Ltp=None,sharp_path=True):
'''
Another package of IC.strt_bend_strt_p2p, which gives the possiblity to attach pins with right angle
'''
return self.strt_bend_strt_p2p_mine(pin1=pin1, pin2=pin2, radius=radius, width=width, xs=xs, name=name, arrow=arrow,
width_mm=width_mm,Ltp=Ltp)
def ubend_route(self, pin=None, offset=20.0, radius=None, width=None, xs=None,
length=0, name=None, arrow=False, balance=0, end_angle=False,
width_mm=None,Ltp=None):
if pin is None:
pin = cp.here()
pin2 = pin.move(0, offset, 0)
return self.ubend_p2p(pin1=pin, pin2=pin2, radius=radius, width=width, xs=xs,
length=length, name=name, arrow=arrow, balance=balance, end_angle=end_angle,width_mm=width_mm,Ltp=Ltp)
def sbend_route(self, radius=None, width=None, pin=None, xs=None, offset=20,
Ltot=None, length1=0, length2=0, name=None, arrow=False, Amax=90.0,original_function=False):
pin = self._getpinout(pin)
xs = self._getxs(pin, xs)
width = self._getwidth(pin, width, xs)
radius = self._getradius(pin, radius, xs)
if pin is None:
pin = cp.here()
## Revised bt HGL 2023.1.4
if offset < 2*radius :
offset_length = 2*np.sqrt(np.power(radius,2)-np.power(radius-np.abs(offset/2),2))
else :
offset_length = 2*radius
pin2 = pin.move(length1+length2+offset_length+1, offset, 180) ## revise in 2022.12.11
return self.sbend_p2p(pin1=pin, pin2=pin2, radius=radius, width=width, xs=xs,
Lstart=length1, name=name, arrow=arrow, BendEndFlag=1,original_function=True)
def bend_strt_bend_p2p_mine(self, pin1=None, pin2=None, radius=None,
radius1=None, radius2=None, width=None, xs=None,
length1=0, length2=0,
ictype='shortest', name=None, arrow=False):
"""Generate a point-to-point bend-straight-bend interconnect.
Args:
pin1 (Node | Instance | tuple(x, y, a)): start pin (default=cp)
pin2 (Node | Instance | tuple(x, y, a)): end pin
radius1 (float): optional first bend radius in um
radius2 (float): optional second bend radius im um
width (float): optional waveguide width in um
xs (str): optional xsection
ictype (str): interconnection type (default='shortest')
options: 'shortest', 'll', 'lr', 'rl', rr', 'all'
name (str): optional new name for the component
arrow (bool): draw connection arrows (default=True)
Returns:
Cell: bend_strt_bend element
Example:
Create and place a bend-straight-bend guide to connect two specific points::
import nazca as nd
from nazca.interconnects import Interconnect
ic = Interconnect(width=2.0, radius=10.0)
guide = ic.bend_strt_bend_p2p(pin1=(0, 0, 0), pin2=(40, 20, 90))
guide.put()
nd.export_plt()
"""
if radius is not None:
if radius1 is None:
radius1 = radius
if radius2 is None:
radius2 = radius
parse, curves = self._bend_strt_bend_p2p_solve(pin1, pin2,
xs=xs, width=width, radius1=radius1, radius2=radius2,
length1=length1, length2=length2, ictype=ictype)
pin1, pin2, xs, width, _, radius1, radius2 = parse
pinflip = not nd.get_xsection(xs).symmetry
instantiate = self.instantiate
if ictype == 'all':
instantiate = True
variations = curves['variations']
else:
variations = [curves]
cells = []
if not curves['found']:
interconnect_logger(curves['message'], 'error')
return self.strt_p2p(pin1, pin2, xs='error')
else:
for curve in variations:
par = curve['solution']
shape = curve['type']
param = curve['solution']
Ltot = param['Ltot']
L = param['L']
b = param['angle1']
e = param['angle2']
if name is None:
name = 'ic_bend_strt_bend_p2p'
with nd.Cell("{}_{}".format(name, shape),
instantiate=instantiate, cnt=True) as ICcell:
ICcell.group = 'interconnect'
trace.trace_start()
if length1 > 0:
# if (self.MM_route):
# e1 = self.strt_mm(length=length1, width1=width, xs=xs).put()
# else:
# e1 = self.line(length1, width, xs=xs).put()
e1 = self.line_mm(length=length1, width1=width, xs=xs).put()
self._circle_(radius1, angle=m.degrees(b), width=width, xs=xs,sharp_patch=self.sharp_patch).put()
# print(m.degrees())
else:
e1 = self._circle_(radius1, angle=m.degrees(b), width=width, xs=xs,sharp_patch=self.sharp_patch).put()
# if (self.MM_route):
# self.strt_mm(length=L, width1=width, xs=xs).put()
# else :
# self.line(L, width, xs).put()
self.line_mm(length=L, width=width, xs=xs).put()
self._circle_(radius2, angle=m.degrees(e), width=width, xs=xs,sharp_patch=self.sharp_patch).put()
if length2 > 0:
# if (self.MM_route):
# self.strt_mm(length=length2, width1=width, xs=xs).put()
# else:
# self.line(length2, width, xs).put()
self.line_mm(length=length2, width=width, xs=xs).put()
nd.Pin('a0', io=0, width=width, xs=xs).put(e1.pin['a0'])
nd.Pin('b0', io=1, width=width, xs=xs).put()
if arrow:
self.arrow.put(ICcell.pin['a0'])
self.arrow.put(ICcell.pin['b0'], flip=pinflip)
trace.trace_stop()
ICcell.length_geo = trace.trace_length()
ICcell.pin2 = pin2
cfg.cp = pin1
cells.append(ICcell)
if not cells:
msg = "No solution in bend_strt_bend_p2p."
interconnect_logger(msg, 'warning')
ic_exception(msg)
elif len(cells) == 1:
return cells[0]
elif len(cells) > 1:
with nd.Cell('{}_{}'.format(name, xs), cnt=True) as ICgroup:
ICcell.group = 'interconnect'
trace.trace_start()
for cell in cells:
cell.put(180)
trace.trace_stop()
ICgroup.length_geo = trace.trace_length()
cfg.cp = pin1
return ICgroup
def bend_route(self, radius=None, angle=None, width=None, pin=None, xs=None,
length1=0.1, length2=0.1, name=None, arrow=False, offset=None,sharp_patch=True,original_function=False):
if (original_function or self.PCB):
return self.bend(radius=radius, angle=angle, width=width, pin=pin, xs=xs,
length1=length1, length2=length2, name=name, arrow=arrow, offset=offset)
else:
return self.bend_mine(radius=radius, angle=angle, width=width, pin=pin, xs=xs,
length1=length1, length2=length2, name=name, arrow=arrow, offset=offset)
def bend_route_p2p(self, pin1=None, pin2=None, radius=None, width=None,
xs=None, name=None, arrow=False,original_function=False):
if (original_function or self.PCB):
return self.strt_bend_strt_p2p(pin1=pin1, pin2=pin2, radius=radius, width=width,
xs=xs, name=name, arrow=arrow)
else:
return self.strt_bend_strt_p2p_mine(pin1=pin1, pin2=pin2, radius=radius, width=width,
xs=xs, name=name, arrow=arrow)
def _circle_(self,radius = 10, width = 0.45, angle=360, n_points = 65,xs=None,layer=None,sharp_patch=True, theta_start=None,theta_stop=None):
with nd.Cell(instantiate=False) as C:
if (angle is not None):
theta_start = 0
theta_stop = angle
else :
angle = theta_stop - theta_start
""" """
bend_dir = np.sign(angle)
for layers,growx,growy,acc in nd.layeriter(xs=xs):
(a1,b1), (a2,b2),c1,c2 = growx
theta = np.linspace(theta_start,theta_stop,n_points)
theta = theta/180*np.pi
vtx_outer_x = np.sin(bend_dir*theta)*(radius+width*a1 + b1)
vtx_outer_y =-bend_dir*np.cos(theta)*(radius+width*a1 + b1)
vtx_outer = np.c_[vtx_outer_x,vtx_outer_y]
if (radius+width*a2+b1>0.0000001 or np.abs(angle-0)<360):
vtx_inner_x = np.sin(bend_dir*theta)*(radius+width*a2 + b2)
vtx_inner_y = -bend_dir*np.cos(theta)*(radius+width*a2 + b2)
vtx_inner = np.c_[np.flip(vtx_inner_x),np.flip(vtx_inner_y)]
vtx = np.r_[vtx_outer,vtx_inner]
else :
vtx = vtx_outer
if (sharp_patch==True and b2!=0 and b1!=0):
""" Creating rectangle for sharp-patching """
L_patch = max([max(vtx_outer_x),max(vtx_inner_x)])-min([min(vtx_outer_x),min(vtx_inner_x)])
X_patch = 1/2*(max([max(vtx_outer_x),max(vtx_inner_x)])+min([min(vtx_outer_x),min(vtx_inner_x)]))
W_patch = (max([max(vtx_outer_y),max(vtx_inner_y)])-min([min(vtx_outer_y),min(vtx_inner_y)]))
Y_patch = 1/2*(max([max(vtx_outer_y),max(vtx_inner_y)])+min([min(vtx_outer_y),min(vtx_inner_y)]))
nd.strt(length=L_patch,width=W_patch,layer=layers).put(X_patch-L_patch/2,Y_patch,0)
_my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0)
nd.Pin(name='a0',width=width,xs=xs).put(radius*np.sin(bend_dir*theta_start/180*np.pi),-bend_dir*radius*np.cos(theta_start/180*np.pi),theta_start+180)
nd.Pin(name='b0',width=width,xs=xs).put(radius*np.sin(bend_dir*theta_stop/180*np.pi) ,-bend_dir*radius*np.cos(theta_stop/180*np.pi) ,theta_stop)
# if (angle>0):
# nd.Pin(name='a0',width=width,xs=xs).put(radius*np.cos(0/180*np.pi),radius*np.sin(0/180*np.pi),0-90)
# nd.Pin(name='b0',width=width,xs=xs).put(radius*np.cos(angle/180*np.pi),radius*np.sin(angle/180*np.pi),angle+90)
# else :
# nd.Pin(name='a0',width=width,xs=xs).put(radius*np.cos(0/180*np.pi),radius*np.sin(0/180*np.pi), 90)
# nd.Pin(name='b0',width=width,xs=xs).put(radius*np.cos(angle/180*np.pi),radius*np.sin(angle/180*np.pi),270+angle)
return C
def bend_mine(self, radius=None, angle=None, width=None, pin=None, xs=None,
length1=0, length2=0, name=None, arrow=False, offset=None,sharp_patch=True):
pin = self._getpinout(pin)
xs = self._getxs(pin, xs)
width = self._getwidth(pin, width, xs)
radius = self._getradius(pin, radius, xs)
pinflip = not nd.get_xsection(xs).symmetry
if offset is None:
offset = self.offset
if angle is None:
angle = self.angle
if name is None:
name = 'ic_bend'
with nd.Cell('{}_{}'.format(name, xs), instantiate=self.instantiate, cnt=True) as ICcell:
ICcell.group = 'interconnect'
trace.trace_start()
if length1 > 0:
# if (self.MM_route):
# e1 = self.strt_mm(length=length1, width1=width, xs=xs).put(0)
# else:
# e1 = self.line(length=length1, width=width, xs=xs).put(0)
e1 = self.line_mm(length=length1, width=width, xs=xs).put(0)
e2 = self._circle_(radius=radius, width=width, angle=angle, xs=xs,sharp_patch=self.sharp_patch).put()
else:
e1 =self._circle_(radius=radius, width=width, angle=angle, xs=xs,sharp_patch=self.sharp_patch).put()
e2 = e1
if length2 > 0:
# if (self.MM_route):
# e2 = self.strt_mm(length=length2, width1=width, xs=xs).put()
# else:
# e2 = self.line(length=length2, width=width, xs=xs).put()
e2 = self.line_mm(length=length2, width=width, xs=xs).put()
nd.Pin('b0', io=1, width=width, xs=xs, pin=e2.pin['b0']).put()
nd.Pin('a0', io=0, width=width, xs=xs, pin=e1.pin['a0']).put()
if arrow:
self.arrow.put(ICcell.pin['a0'])
self.arrow.put(ICcell.pin['b0'], flip=pinflip)
trace.trace_stop()
ICcell.length_geo = trace.trace_length()
if pin is not None:
cfg.cp = pin
return ICcell
def tube_mine(self, geo, showpins=False, name=None, xs=None, arrow=False,Ltp=None,width_mm=None,sharp_patch=None):
"""draw interconnect based on symbols.
Returns:
Cell: Tube element based on the provided elements
"""
if (sharp_patch is None):
sharp_patch = self.sharp_patch
pinflip = not nd.get_xsection(xs).symmetry
if name is None:
name = 'ic_tube'
with nd.Cell(name, instantiate=self.instantiate, cnt=True) as ICcell:
ICcell.group = 'interconnect'
trace.trace_start()
el_list=[]
new_geo=[]
for i, tube in enumerate(geo):
if tube[0] == 's':
if abs(tube[1][0])<1.e-5:
continue
if (self.MM_route):
el_list.append(self.strt_mm(length=tube[1][0], width1=tube[1][1], arrow=False,Ltp=Ltp,width2=width_mm).put())
else:
el_list.append(self.strt(length=tube[1][0], width=tube[1][1], arrow=False).put())
new_geo.append(tube)
elif tube[0] == 'b':
if abs(tube[1][0])<1.e-5:
continue
if (len(el_list)>0):
angle_pre = el_list[-1].pin['b0'].a
else:
angle_pre = 0
el_list.append(self._circle_(theta_start=angle_pre,theta_stop=angle_pre + tube[1][0], angle=None,
radius=tube[1][1], width=tube[1][2],xs=xs,sharp_patch=sharp_patch).put())
# el_list.append(self.bend(angle=tube[1][0],radius=tube[1][1], width=tube[1][2]).put())
# el_list.append(self._circle_(theta_start=0,theta_stop=tube[1][0], angle=None,
# radius=tube[1][1], width=tube[1][2],xs=xs,sharp_patch=self.sharp_patch).put())
# el_list.append(self._circle_(angle=tube[1][0], radius=tube[1][1], width=tube[1][2],xs=xs,sharp_patch=self.sharp_patch).put())
new_geo.append(tube)
else:
cfg.interconnect_errcnt += 1
msg = "IC-{} Tube element not recognized {} in cell '{}'".\
format(cfg.interconnect_errcnt, tube, cfg.cells[-1].cell_name)
interconnect_logger(msg, 'error')
ic_exception(msg)
if el_list:
nd.Pin('a0', io=0, width=el_list[0].pin['a0'].width, xs=xs).put(el_list[0].pin['a0'])
nd.Pin('b0', io=1, width=el_list[-1].pin['b0'].width, xs=xs).put(el_list[-1].pin['b0'])
else: # nothing to place. TODO: Handle in cell.put(), where empty cells can be skipped, e.g. with cell.void = True
nd.Pin('a0', io=0, width=None, xs=xs).put(0, 0, 180)
nd.Pin('b0', io=1, width=None, xs=xs).put(0, 0, 0)
trace.trace_stop()
ICcell.length_geo = trace.trace_length()
if arrow:
self.arrow.put(ICcell.pin['a0'])
self.arrow.put(ICcell.pin['b0'], flip=pinflip)
return ICcell
def strt_bend_strt_p2p_mine(self, pin1=None, pin2=None, radius=None,
radius1=None, radius2=None, width=None, xs=None,
length1=0, length2=0,
ictype='shortest', name=None, arrow=False,
width_mm=None,Ltp =None):
parse, params = self._strt_bend_strt_p2p_solve(pin1=pin1, pin2=pin2,
xs=xs, width=width, radius=radius)
pin1, pin2, xs, width, _, radius1, radius2 = parse
pinflip = not nd.get_xsection(xs).symmetry
if params['found']:
solution = params['solution']
L1 = solution['length1']
L2 = solution['length2']
da = solution['da']
if name is None:
name = 'ic_strt_bend_strt_p2p'
cfg.cp = pin1
if (self.PCB==True):
C = self.tube(geo=params['geo'], name='{}_{}'.format(name, xs), xs=xs, arrow=arrow)
else:
C = self.tube_mine(geo=params['geo'], name='{}_{}'.format(name, xs), xs=xs, arrow=arrow,width_mm=width_mm,Ltp=Ltp)
C.pin2 = pin2
return C
else:
interconnect_logger(params['message'], 'warning')
ic_exception(params['message'])
# goto bend_strt_bend:
return self.bend_strt_bend_p2p_mine(pin1, pin2, radius1=radius,
radius2=radius, width=width, xs=xs, arrow=arrow, name=name)
## HGL's sbend ###############################################################################################
def sbend_p2p_mine(self, pin1=None, pin2=None, width=None, radius=None, Lstart=0, doFirst=1, arrow=False,
width_mm=None,Ltp=None,sharp_patch=False):
"""
Create pin-to-pin s-bend interconnect. This code is made by myself to avoid the possible gap between two bend
The direction of the end pin is ignored and is assumed to be parallel with pin1.
Args:
pin1 (Node | Instance | tuple(x, y, a)): start pin (default=cp)
pin2 (Node | Instance | tuple(x, y, a)): end pin
width (float): width of the interconnect in um
radius (float): bend radius of the interconnect in um
xs (str): optional xsection of sbend
doFirst (int): (default=1)
Lstart (float): straight waveguide length at beginning (positive
value) or end (negative value) of sbend
arrow (bool): draw connection arrows (default=True)
Returns:
Cell: sbend element
Example:
Create and place a sbend to connect two specific points::
import nazca as nd
from nazca.interconnects import Interconnect
ic = Interconnect(width=2.0, radius=10.0)
guide = ic.sbend_p2p(pin1=(0), pin2=(40, 20))
guide.put()
nd.export_plt()
"""
# if pin1.width != pin1.width:
# print("--Be carefull that width of two ends of this sbend does not equal to each other.--")
res = 0.2
parse = self._p2p_parse(pin1, pin2)
pin1, pin2, _, _, _, _, _ = parse
if width is None:
width = self.width
if radius is None:
radius = self.radius
## Add code to solve the problem when (pin1.a-pin2.a)!=180
angle_offset = 180-abs(pin1.a-pin2.a)
if abs(angle_offset)>0.01:
bend = self.bend(radius=radius,width=width,angle=-angle_offset*np.sign(pin1.a-pin2.a),xs=self.xs).put(pin2)
pin2 = bend.pin['b0']
## Do sbend connecting
a = pin1.a
a = a*np.pi/180
lateral_length = (pin2.x-pin1.x)*np.cos(a) + (pin2.y-pin1.y)*np.sin(a)
offset_length = -(pin2.x-pin1.x)*np.sin(a) + (pin2.y-pin1.y)*np.cos(a)
cp.goto_pin(pin1)
with nd.Cell(name="my_sbend", instantiate=False, cnt=True) as ICcell:
## Add Lstart strt
strt = nd.strt(length=Lstart, width=width, xs=self.xs).put(0, 0)
lateral_length = lateral_length - Lstart
## Add sbend
if abs(offset_length) >= 2*radius:
bend1 = nd.bend(radius=radius, width=width, xs=self.xs, angle=90*np.sign(offset_length)).put()
nd.strt(length=abs(offset_length)-2*radius, width=width, xs=self.xs).put()
bend2 = nd.bend(radius=radius, width=width, xs=self.xs, angle=-90*np.sign(offset_length)).put()
nd.strt(length=lateral_length-2*radius, width=width, xs=self.xs).put()
# Add patch
for lay, grow, acc, polyline in nd.layeriter(xs=self.xs):
(a1, b1), (a2, b2), c1, c2 = grow
if b1!=0 and b2!=0:
if offset_length>0:
patch_poly = [
(0,width*a2+b2),
(abs(radius)-(width*a2+b2),width*a2+b2),
(abs(radius)-(width*a2+b2),abs(radius)),
(0,abs(radius))
]
nd.Polygon(points=patch_poly,layer=lay).put(bend1.pin['a0'].move(0,0,180))
patch_poly = [
(0,-(width*a1+b1)),
(abs(radius)+(width*a1+b1),-(width*a1+b1)),
(abs(radius)+(width*a1+b1),abs(radius)),
(0,abs(radius))
]
nd.Polygon(points=patch_poly,layer=lay).put(bend2.pin['b0'].move(0,0,180))
elif offset_length<0:
patch_poly = [
(0,(width*a1+b1)),
(abs(radius)+(width*a1+b1),(width*a1+b1)),
(abs(radius)+(width*a1+b1),-abs(radius)),
(0,-abs(radius))
]
nd.Polygon(points=patch_poly,layer=lay).put(bend1.pin['a0'].move(0,0,180))
patch_poly = [
(0,-(width*a2+b2)),
(abs(radius)-(width*a2+b2),-(width*a2+b2)),
(abs(radius)-(width*a2+b2),-abs(radius)),
(0,-abs(radius))
]
nd.Polygon(points=patch_poly,layer=lay).put(bend2.pin['b0'].move(0,0,180))
if lateral_length-2*radius < 0:
print("--Be carefull that the length of last strt in strt-sbend-strt is negative.--")
elif offset_length == 0:
nd.strt(length=lateral_length, width=width, xs=self.xs).put()
elif abs(offset_length) < 2*radius:
# Sbend parameter
sbend_length = 2 * np.sqrt(np.power(radius,2) - np.power(radius-abs(offset_length)/2,2))
theta_range = np.arccos((radius-abs(offset_length)/2) / radius)
theta_step = res / radius
theta_list = np.arange(0, theta_range, theta_step)
# Center of two bend
center1 = {"x":0, "y":radius*np.sign(offset_length)}
center2 = {"x":sbend_length, "y":offset_length-radius*np.sign(offset_length)}
for lay, grow, acc, polyline in nd.layeriter(xs=self.xs):
(a1, b1), (a2, b2), c1, c2 = grow
Ro = radius + width * a1 + b1
Ri = radius + width * a2 + b2
p1 = [(center1["x"] + Ri*np.sin(theta),
center1["y"] - np.sign(offset_length)*Ri*np.cos(theta))
for theta in theta_list]
p2 = [(center2['x'] - Ro*np.sin(theta),
center2['y'] + np.sign(offset_length)*Ro*np.cos(theta))
for theta in reversed(theta_list)]
p3 = [(center2['x'] - Ri*np.sin(theta),
center2['y'] + np.sign(offset_length)*Ri*np.cos(theta))
for theta in theta_list]
p4 = [(center1["x"] + Ro*np.sin(theta),
center1["y"] - np.sign(offset_length)*Ro*np.cos(theta))
for theta in reversed(theta_list)]
sbend_poly = p1 + p2 + p3 + p4
nd.Polygon(points=sbend_poly, layer=lay).put()
if b1!=0 and b2!=0:
if offset_length>0:
patch_poly = [
(0,width*a2+b2), (sbend_length,width*a2+b2),
(sbend_length,width*a1+b1+offset_length),
(0,width*a1+b1+offset_length)
]
nd.Polygon(points=patch_poly,layer=lay).put(strt.pin['b0'])
elif offset_length<0:
patch_poly = [
(0,width*a1+b1), (sbend_length,width*a1+b1),
(sbend_length,width*a2+b2+offset_length),
(0,width*a2+b2+offset_length)
]
nd.Polygon(points=patch_poly,layer=lay).put(strt.pin['b0'])
nd.strt(length=lateral_length-sbend_length, width=width, xs=self.xs).put(Lstart+sbend_length, offset_length)
nd.Pin(name='a0').put(0, 0, 180)
nd.Pin(name='b0').put(lateral_length+Lstart, offset_length, 0)
if arrow:
self.arrow.put(ICcell.pin['a0'])
self.arrow.put(ICcell.pin['b0'])
return ICcell
def sbend_p2p(self, pin1=None, pin2=None, width=None, radius=None, Amax=90,
xs=None, doStrFirst=1, Lstart=0, BendEndFlag=1, ref=None,
name=None, arrow=False, bsb=True, sharp_patch=None,
width_mm=None,Ltp=None):
# if (original_function or self.PCB==True or self.sharp_patch==False):
if (sharp_patch is None):
sharp_patch = self.sharp_patch
""" Note : This part is directly copy from sbend_p2p in nazca.Interconnect """
parse, params = self._sbend_p2p_solve(
pin1=pin1, pin2=pin2, width=width, radius=radius, length1=Lstart,
doStrFirst=doStrFirst, ref=ref, Amax=Amax)
pin1, pin2, xs, width, _, radius1, radius2 = parse
pinflip = not nd.get_xsection(xs).symmetry
if not params['found'] or abs(params['solution']['angle']) < 0*m.pi:
interconnect_logger(params['message'], 'info')
#TODO: alterative back up if La is so long that Lb is negative?
if bsb:
msg = "replacing sbend with bend_strt_bend."
interconnect_logger(msg, 'info')
return self.bend_strt_bend_p2p(
pin1=pin1, pin2=pin2, radius=radius, width=width, xs=xs,
name=name, arrow=arrow, width_mm=width_mm, Ltp=Ltp)
else:
if (self.MM_route and self.PCB is False):
return self.strt_mm_p2p(pin1=pin1,pin2=pin2,xs='error',arrow=arrow,width2=width_mm,Ltp=Ltp)
else:
return self.strt_p2p(pin1, pin2, xs='error', arrow=arrow)
else:
if name is None:
name = 'ic_sbend'
cfg.cp = pin1
if (self.MM_route==False or self.PCB==True or sharp_patch==False):
C = self.tube(geo=params['geo'], name='{}_{}'.format(name, xs), xs=xs, arrow=arrow)
else:
C = self.tube_mine(geo=params['geo'], name='{}_{}'.format(name, xs), xs=xs, arrow=arrow, width_mm=width_mm, Ltp=Ltp,sharp_patch=sharp_patch)
C.pin2 = pin2
return C
""" Original Function ends """
# else :
# return self.sbend_p2p_mine(pin1=pin1, pin2=pin2, width=width, radius=radius, Lstart=Lstart, doFirst=doStrFirst, arrow=arrow)
def bend_strt_bend_p2p(self, pin1=None, pin2=None, radius=None,
radius1=None, radius2=None, width=None, xs=None,
length1=0, length2=0,
ictype='shortest', name=None, arrow=True,
width_mm=None,Ltp=None,
):
"""Generate a point-to-point bend-straight-bend interconnect.
Args:
pin1 (Node | Instance | tuple(x, y, a)): start pin (default=cp)
pin2 (Node | Instance | tuple(x, y, a)): end pin
radius1 (float): optional first bend radius in um
radius2 (float): optional second bend radius im um
width (float): optional waveguide width in um
xs (str): optional xsection
ictype (str): interconnection type (default='shortest')
options: 'shortest', 'll', 'lr', 'rl', rr', 'all'
name (str): optional new name for the component
arrow (bool): draw connection arrows (default=True)
Returns:
Cell: bend_strt_bend element
Example:
Create and place a bend-straight-bend guide to connect two specific points::
import nazca as nd
from nazca.interconnects import Interconnect
ic = Interconnect(width=2.0, radius=10.0)
guide = ic.bend_strt_bend_p2p(pin1=(0, 0, 0), pin2=(40, 20, 90))
guide.put()
nd.export_plt()
"""
if radius is not None:
if radius1 is None:
radius1 = radius
if radius2 is None:
radius2 = radius
parse, curves = self._bend_strt_bend_p2p_solve(pin1, pin2,
xs=xs, width=width, radius1=radius1, radius2=radius2,
length1=length1, length2=length2, ictype=ictype)
pin1, pin2, xs, width, _, radius1, radius2 = parse
pinflip = not nd.get_xsection(xs).symmetry
instantiate = self.instantiate
if ictype == 'all':
instantiate = True
variations = curves['variations']
else:
variations = [curves]
cells = []
if not curves['found']:
interconnect_logger(curves['message'], 'error')
return self.strt_p2p(pin1, pin2, xs='error')
else:
for curve in variations:
par = curve['solution']
shape = curve['type']
param = curve['solution']
Ltot = param['Ltot']
L = param['L']
b = param['angle1']
e = param['angle2']
if name is None:
name = 'ic_bend_strt_bend_p2p'
with nd.Cell("{}_{}".format(name, shape),
instantiate=instantiate, cnt=True) as ICcell:
ICcell.group = 'interconnect'
trace.trace_start()
if length1 > 0:
e1 = self.line_mm(length=length1, width=width, xs=xs, width_mm=width_mm, Ltp=Ltp).put()
self._circle_(radius=radius1, angle=m.degrees(b), width=width, xs=xs).put()
else:
e1 = self._circle_(radius=radius1, angle=m.degrees(b), width=width, xs=xs).put()
self.line_mm(length=L, width=width, xs=xs, width_mm=width_mm, Ltp=Ltp).put()
self._circle_(radius=radius2, angle=m.degrees(e), width=width, xs=xs).put()
if length2 > 0:
self.line_mm(length=length2, width=width, xs=xs, width_mm=width_mm, Ltp=Ltp).put()
nd.Pin('a0', io=0, width=width, xs=xs).put(e1.pin['a0'])
nd.Pin('b0', io=1, width=width, xs=xs).put()
if arrow:
self.arrow.put(ICcell.pin['a0'])
self.arrow.put(ICcell.pin['b0'], flip=pinflip)
trace.trace_stop()
ICcell.length_geo = trace.trace_length()
ICcell.pin2 = pin2
cfg.cp = pin1
cells.append(ICcell)
if not cells:
msg = "No solution in bend_strt_bend_p2p."
interconnect_logger(msg, 'warning')
ic_exception(msg)
elif len(cells) == 1:
return cells[0]
elif len(cells) > 1:
with nd.Cell('{}_{}'.format(name, xs), cnt=True) as ICgroup:
ICcell.group = 'interconnect'
trace.trace_start()
for cell in cells:
cell.put(180)
trace.trace_stop()
ICgroup.length_geo = trace.trace_length()
cfg.cp = pin1
return ICgroup
""" Functions Reconstructed with <arrow = False> default """
def ubend_p2p(self, pin1=None, pin2=None, radius=None, width=None, xs=None,
length=0.1, name=None, arrow=False, balance=0, end_angle=False,original_function=False,Ltp=None,width_mm=None):
parse, params = self._ubend_p2p_solve(
pin1,
pin2,
length=length,
width=width,
radius=radius,
balance=balance,
end_angle=end_angle
)
pin1, pin2, xs, width, _, radius1, radius2 = parse
if name is None:
name = 'ic_ubend_p2p'
cfg.cp = pin1
if (original_function or self.PCB):
C = self.tube(geo=params['geo'], name='{}_{}'.format(name, xs), xs=xs, arrow=arrow)
C.pin2 = pin2
else:
C = self.tube_mine(geo=params['geo'], name='{}_{}'.format(name, xs), xs=xs, arrow=arrow,Ltp=Ltp,width_mm=width_mm)
C.pin2 = pin2
return C
def strt(self, length=None, width=None, pin=None, xs=None, edge1=None,
edge2=None, edgepoints=50, name=None, arrow=False, gridpatch=None):
pin = self._getpinout(pin)
xs = self._getxs(pin, xs)
width = self._getwidth(pin, width, xs)
pinflip = not nd.get_xsection(xs).symmetry
if gridpatch is None:
gridpatch = self.gridpatch
if length is None:
length = self.length
if name is None:
name = 'ic_strt'
with nd.Cell('{}_{}'.format(name, xs), instantiate=self.instantiate, cnt=True) as ICcell:
ICcell.group = 'interconnect'
trace.trace_start()
e1 = self.line(length=length, width=width, xs=xs, edge1=edge1,
edge2=edge2, edgepoints=edgepoints, gridpatch=gridpatch).put(0)
nd.Pin('a0', io=0, width=width, xs=xs).put(e1.pin['a0'])
nd.Pin('b0', io=1, width=width, xs=xs).put()
if arrow:
self.arrow.put(ICcell.pin['a0'])
self.arrow.put(ICcell.pin['b0'], flip=pinflip)
trace.trace_stop()
ICcell.length_geo = trace.trace_length()
if pin is not None:
cfg.cp = pin
return ICcell
def taper(self, length=None, width1=None, width2=None, shift=0, xs=None, pin=None,
name=None, patch=False, arrow=False):
pin = self._getpinout(pin)
xs = self._getxs(pin, xs)
width1 = self._getwidth(pin, width1, xs, adapt=False)
width2 = self._getwidth(pin, width2, xs, adapt=False)
pinflip = not nd.get_xsection(xs).symmetry
if length is None:
length = self.length
with nd.Cell('taper_{}'.format(xs), instantiate=self.instantiate, cnt=True) as ICcell:
ICcell.group = 'interconnect'
trace.trace_start()
e1 = self._taper(length=length, width1=width1, width2=width2,
shift=shift, xs=xs).put(0)
nd.Pin('a0', io=0, width=width1, xs=xs).put(e1.pin['a0'])
nd.Pin('b0', io=1, width=width2, xs=xs).put()
if arrow:
self.arrow.put(ICcell.pin['a0'])
self.arrow.put(ICcell.pin['b0'], flip=pinflip)
trace.trace_stop()
ICcell.length_geo = trace.trace_length()
if patch :
width_max = np.max(np.array([width1,width2]))
for lay, grow, acc, polyline in nd.layeriter(xs=self.xs):
if "CLD" in lay :
(a1, b1), (a2, b2), c1, c2 = grow
nd.strt(length=length,width=width_max*(a1-a2)+b1-b2,layer=lay).put(0,0,0)
if pin is not None:
cfg.cp = pin
return ICcell
def taper_p2p(self, pin1=None, pin2=None, width1=None, width2=None, xs=None, name=None, arrow=False ):
save_adapt = self.adapt_width
self.adapt_width = True # Adapt taper width to what it connects to.
parse = self._p2p_parse(
pin1=pin1,
pin2=pin2,
width1=width1,
width2=width2,
xs=xs
)
pin1, pin2, xs, width1, width2, radius1, radius2 = parse
length, shift, _ = nd.diff(pin1, pin2)
if name is None:
name = 'ic_taper_p2p'
with nd.Cell(f'{name}_{xs}', instantiate=self.instantiate, cnt=True) as ICcell:
ICcell.group = 'interconnect'
trace.trace_start()
e1 = self._taper(
length=length,
width1=width1,
width2=width2,
shift=shift,
xs=xs
).put(0, 0, pin1.xya()[2])
p1 = nd.Pin('a0', io=0, width=width1, xs=xs).put(e1.pin['a0'])
p2 = nd.Pin('b0', io=1, width=width2, xs=xs).put(e1.pin['b0'])
if arrow:
self.arrow.put(ICcell.pin['a0'])
self.arrow.put(ICcell.pin['b0'])
trace.trace_stop()
ICcell.length_geo = trace.trace_length()
ICcell.pin2 = pin2
# connect_opt needed because the extra rot in p1 and p2 does hide the line
# optical connection:
nd.connect_path(p1, p2, trace.trace_length())
cfg.cp = pin1
self.adapt_width = save_adapt # restore previous value
return ICcell
def strt_p2p(self, pin1=None, pin2=None, width=None, xs=None, name=None, arrow=False ):
parse, (length, b) = self._strt_p2p_solve(pin1=pin1, pin2=pin2,
width=width, xs=xs)
pin1, pin2, xs, width, _, radius1, radius2 = parse
if name is None:
name = 'ic_strt_p2p'
with nd.Cell('{}_{}'.format(name, xs), instantiate=self.instantiate, cnt=True) as ICcell:
ICcell.group = 'interconnect'
trace.trace_start()
e1 = self.line(length=length, width=width, xs=xs).put(0, 0, b)
p1 = nd.Pin('a0', io=0, width=width, xs=xs).put(e1.pin['a0'].rot(-b))
p2 = nd.Pin('b0', io=1, width=width, xs=xs).put(e1.pin['b0'].rot(-b-pin1.a+pin2.a+180))
if arrow:
self.arrow.put(ICcell.pin['a0'])
self.arrow.put(ICcell.pin['b0'])
trace.trace_stop()
ICcell.length_geo = trace.trace_length()
ICcell.pin2 = pin2
# connect_opt needed because the extra rot in p1 and p2 does hide the line optical connection:
nd.connect_path(p1, p2, trace.trace_length())
cfg.cp = pin1
return ICcell