745 lines
33 KiB
Python
745 lines
33 KiB
Python
import gmsh
|
|
import numpy as np
|
|
import os
|
|
import sys
|
|
DEV_DIR = os.environ.get("DEV_DIR", "devices/Triac_rp")
|
|
sys.path.insert(0, os.path.abspath(DEV_DIR))
|
|
from device_config import *
|
|
|
|
def create_ldmos_mesh(mesh_out="device_2d.msh", bgmesh_pos="device_bgmesh.pos"):
|
|
gmsh.initialize()
|
|
gmsh.model.add("device_2d")
|
|
occ = gmsh.model.occ
|
|
|
|
def add_polygon_surface(occ_kernel, points):
|
|
pt_tags = []
|
|
for x, y in points:
|
|
pt_tags.append(occ_kernel.addPoint(x, y, 0))
|
|
line_tags = []
|
|
n = len(pt_tags)
|
|
for i in range(n):
|
|
pt1 = pt_tags[i]
|
|
pt2 = pt_tags[(i + 1) % n]
|
|
line_tags.append(occ_kernel.addLine(pt1, pt2))
|
|
loop_tag = occ_kernel.addCurveLoop(line_tags)
|
|
surf_tag = occ_kernel.addPlaneSurface([loop_tag])
|
|
return surf_tag, line_tags
|
|
|
|
# 1. Create Silicon substrate
|
|
silicon_base = occ.addRectangle(-W_DEVICE, 0.0, 0.0, 2 * W_DEVICE, H_SI)
|
|
|
|
# Recessed parts of oxide
|
|
p_rec_r_pts = [
|
|
(X_FOX_START - L_BEAK, 0.0),
|
|
(X_FOX_START, 0.5 * TH_FOX),
|
|
(X_FOX_END, 0.5 * TH_FOX),
|
|
(X_CON_START, 0.0)
|
|
]
|
|
rec_r_surf, _ = add_polygon_surface(occ, p_rec_r_pts)
|
|
|
|
p_rec_l_pts = [
|
|
(-(X_FOX_START - L_BEAK), 0.0),
|
|
(-X_FOX_START, 0.5 * TH_FOX),
|
|
(-X_FOX_END, 0.5 * TH_FOX),
|
|
(-X_CON_START, 0.0)
|
|
]
|
|
rec_l_surf, _ = add_polygon_surface(occ, p_rec_l_pts)
|
|
|
|
silicon_cut, _ = occ.cut([(2, silicon_base)], [(2, rec_r_surf), (2, rec_l_surf)])
|
|
silicon_tag = silicon_cut[0][1]
|
|
|
|
# 2. Create Oxide layer
|
|
oxide_pts = [
|
|
(-X_CON_START, 0.0), # Contact beak outer tip (left)
|
|
(-X_FOX_END, -0.5 * TH_FOX), # FOX outer edge top (left)
|
|
(-X_FOX_START, -0.5 * TH_FOX), # FOX inner edge top (left)
|
|
(-(X_FOX_START - L_BEAK), -TH_GOX), # Gate oxide edge top (left)
|
|
(X_FOX_START - L_BEAK, -TH_GOX), # Gate oxide edge top (right)
|
|
(X_FOX_START, -0.5 * TH_FOX), # FOX inner edge top (right)
|
|
(X_FOX_END, -0.5 * TH_FOX), # FOX outer edge top (right)
|
|
(X_CON_START, 0.0), # Contact beak outer tip (right)
|
|
(X_FOX_END, 0.5 * TH_FOX), # FOX outer edge bottom (right)
|
|
(X_FOX_START, 0.5 * TH_FOX), # FOX inner edge bottom (right)
|
|
(X_FOX_START - L_BEAK, 0.0), # Gate oxide edge bottom (right)
|
|
(-(X_FOX_START - L_BEAK), 0.0), # Gate oxide edge bottom (left)
|
|
(-X_FOX_START, 0.5 * TH_FOX), # FOX inner edge bottom (left)
|
|
(-X_FOX_END, 0.5 * TH_FOX) # FOX outer edge bottom (left)
|
|
]
|
|
oxide_surf, _ = add_polygon_surface(occ, oxide_pts)
|
|
|
|
# 3. Create Gate shape (for cutout)
|
|
gate_pts = [
|
|
(-X_GATE_END, -H_GATE),
|
|
(X_GATE_END, -H_GATE),
|
|
(X_GATE_END, -0.5 * TH_FOX),
|
|
(X_FOX_START, -0.5 * TH_FOX),
|
|
(X_FOX_START - L_BEAK, -TH_GOX),
|
|
(-(X_FOX_START - L_BEAK), -TH_GOX),
|
|
(-X_FOX_START, -0.5 * TH_FOX),
|
|
(-X_GATE_END, -0.5 * TH_FOX)
|
|
]
|
|
gate_surf, _ = add_polygon_surface(occ, gate_pts)
|
|
|
|
# 4. Create Contact & Metal shapes (for cutout)
|
|
# Right Electrode (contR)
|
|
cont_r_pts = [
|
|
(X_MET_START, -H_CON - H_MET),
|
|
(X_MET_END, -H_CON - H_MET),
|
|
(X_MET_END, 0.0),
|
|
(X_CON_START, 0.0),
|
|
(X_CON_START, -H_CON),
|
|
(X_MET_START, -H_CON)
|
|
]
|
|
cont_r_surf, _ = add_polygon_surface(occ, cont_r_pts)
|
|
|
|
# Left Electrode (contL)
|
|
cont_l_pts = [
|
|
(-X_MET_START, -H_CON - H_MET),
|
|
(-X_MET_START, -H_CON),
|
|
(-X_CON_START, -H_CON),
|
|
(-X_CON_START, 0.0),
|
|
(-X_MET_END, 0.0),
|
|
(-X_MET_END, -H_CON - H_MET)
|
|
]
|
|
cont_l_surf, _ = add_polygon_surface(occ, cont_l_pts)
|
|
|
|
# 5. Create Molding layer
|
|
molding_base = occ.addRectangle(-W_DEVICE, -H_MOLD, 0.0, 2 * W_DEVICE, H_MOLD + H_SI)
|
|
|
|
molding_cut, _ = occ.cut([(2, molding_base)],
|
|
[(2, silicon_tag), (2, oxide_surf), (2, gate_surf), (2, cont_l_surf), (2, cont_r_surf)],
|
|
removeObject=True, removeTool=False)
|
|
molding_tag = molding_cut[0][1]
|
|
|
|
# Fragment everything to be conformal
|
|
out, out_map = occ.fragment([(2, silicon_tag), (2, oxide_surf), (2, molding_tag)], [])
|
|
|
|
# Remove temporary gate and contact surfaces from OpenCASCADE recursively to delete their boundary curves
|
|
occ.remove([(2, gate_surf), (2, cont_l_surf), (2, cont_r_surf)], recursive=True)
|
|
occ.synchronize()
|
|
|
|
# Define physical groups for regions
|
|
silicon_tags = []
|
|
oxide_tags = []
|
|
molding_tags = []
|
|
eps = 0.05 * um
|
|
|
|
for ent in gmsh.model.getEntities(dim=2):
|
|
tag = ent[1]
|
|
mass_center = occ.getCenterOfMass(2, tag)
|
|
x_c, y_c = mass_center[0], mass_center[1]
|
|
|
|
if y_c >= 0.1 * um:
|
|
silicon_tags.append(tag)
|
|
elif y_c >= -0.3 * um:
|
|
oxide_tags.append(tag)
|
|
else:
|
|
molding_tags.append(tag)
|
|
|
|
gmsh.model.addPhysicalGroup(2, silicon_tags, tag=1, name="Silicon")
|
|
gmsh.model.addPhysicalGroup(2, oxide_tags, tag=2, name="Oxide")
|
|
gmsh.model.addPhysicalGroup(2, molding_tags, tag=3, name="Molding")
|
|
|
|
# Grouping 1D curves for boundary conditions
|
|
substrate_bottom_si_curves = []
|
|
gate_ox_curves = []
|
|
gate_mold_curves = []
|
|
contr_si_curves = []
|
|
contr_ox_curves = []
|
|
contr_mold_curves = []
|
|
contl_si_curves = []
|
|
contl_ox_curves = []
|
|
contl_mold_curves = []
|
|
silicon_oxide_interface_curves = []
|
|
ox_mold_interface_curves = []
|
|
molding_top_curves = []
|
|
|
|
def is_on_gate_bottom(xc, yc):
|
|
ax = abs(xc)
|
|
x_gox_end = X_FOX_START - L_BEAK
|
|
if ax <= x_gox_end + eps:
|
|
return abs(yc - (-TH_GOX)) < eps
|
|
elif ax <= X_FOX_START + eps:
|
|
y_expected = -TH_GOX + (ax - x_gox_end) / L_BEAK * (-0.5 * TH_FOX + TH_GOX)
|
|
return abs(yc - y_expected) < eps
|
|
elif ax <= X_GATE_END + eps:
|
|
return abs(yc - (-0.5 * TH_FOX)) < eps
|
|
return False
|
|
|
|
def is_on_si_ox_interface(xc, yc):
|
|
ax = abs(xc)
|
|
x_gox_end = X_FOX_START - L_BEAK
|
|
if ax <= x_gox_end + eps:
|
|
return abs(yc - 0.0) < eps
|
|
elif ax <= X_FOX_START + eps:
|
|
y_expected = 0.0 + (ax - x_gox_end) / L_BEAK * (0.5 * TH_FOX)
|
|
return abs(yc - y_expected) < eps
|
|
elif ax <= X_FOX_END + eps:
|
|
return abs(yc - (0.5 * TH_FOX)) < eps
|
|
elif ax <= X_CON_START + eps:
|
|
y_expected = 0.5 * TH_FOX - (ax - X_FOX_END) / L_BEAK * (0.5 * TH_FOX)
|
|
return abs(yc - y_expected) < eps
|
|
return False
|
|
|
|
def is_on_ox_mold_interface(xc, yc):
|
|
ax = abs(xc)
|
|
if ax < X_GATE_END - eps:
|
|
return False
|
|
if ax <= X_FOX_END + eps:
|
|
return abs(yc - (-0.5 * TH_FOX)) < eps
|
|
elif ax <= X_CON_START + eps:
|
|
y_expected = -0.5 * TH_FOX + (ax - X_FOX_END) / L_BEAK * (0.5 * TH_FOX)
|
|
return abs(yc - y_expected) < eps
|
|
return False
|
|
|
|
curves = gmsh.model.getEntities(dim=1)
|
|
for c in curves:
|
|
c_tag = c[1]
|
|
xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.getBoundingBox(1, c_tag)
|
|
xc = (xmin + xmax) / 2.0
|
|
yc = (ymin + ymax) / 2.0
|
|
|
|
# Substrate Bottom: Y = H_SI
|
|
if abs(ymin - H_SI) < eps and abs(ymax - H_SI) < eps:
|
|
substrate_bottom_si_curves.append(c_tag)
|
|
continue
|
|
|
|
# Molding Top: Y = -H_MOLD
|
|
if abs(ymin - (-H_MOLD)) < eps and abs(ymax - (-H_MOLD)) < eps:
|
|
molding_top_curves.append(c_tag)
|
|
continue
|
|
|
|
# Gate top & sides
|
|
# Gate top
|
|
if abs(ymin - (-H_GATE)) < eps and abs(ymax - (-H_GATE)) < eps and abs(xc) <= X_GATE_END + eps:
|
|
gate_mold_curves.append(c_tag)
|
|
continue
|
|
# Gate sides
|
|
if abs(abs(xc) - X_GATE_END) < eps and ymin >= -H_GATE - eps and ymax <= -0.5 * TH_FOX + eps:
|
|
gate_mold_curves.append(c_tag)
|
|
continue
|
|
|
|
# Gate bottom
|
|
if is_on_gate_bottom(xc, yc):
|
|
gate_ox_curves.append(c_tag)
|
|
continue
|
|
|
|
# contR boundaries
|
|
# Silicon interface
|
|
if abs(ymin) < eps and abs(ymax) < eps and xmin >= X_CON_START - eps and xmax <= X_CON_END + eps:
|
|
contr_si_curves.append(c_tag)
|
|
continue
|
|
# Oxide interface
|
|
if abs(xc - X_FOX_END) < eps and ymin >= -0.5 * TH_FOX - eps and ymax <= eps:
|
|
contr_ox_curves.append(c_tag)
|
|
continue
|
|
# Molding interface
|
|
# inner contact wall
|
|
if abs(xc - X_CON_START) < eps and ymin >= -H_CON - eps and ymax <= 0.0 + eps:
|
|
contr_mold_curves.append(c_tag)
|
|
continue
|
|
# metal bottom
|
|
if abs(ymin - (-H_CON)) < eps and abs(ymax - (-H_CON)) < eps and xmin >= X_MET_START - eps and xmax <= X_CON_START + eps:
|
|
contr_mold_curves.append(c_tag)
|
|
continue
|
|
# metal inner wall
|
|
if abs(xc - X_MET_START) < eps and ymin >= -H_CON - H_MET - eps and ymax <= -H_CON + eps:
|
|
contr_mold_curves.append(c_tag)
|
|
continue
|
|
# contL boundaries
|
|
# Silicon interface
|
|
if abs(ymin) < eps and abs(ymax) < eps and xmin >= -X_CON_END - eps and xmax <= -X_CON_START + eps:
|
|
contl_si_curves.append(c_tag)
|
|
continue
|
|
# Oxide interface
|
|
if abs(xc - (-X_FOX_END)) < eps and ymin >= -0.5 * TH_FOX - eps and ymax <= eps:
|
|
contl_ox_curves.append(c_tag)
|
|
continue
|
|
# Molding interface
|
|
# inner contact wall
|
|
if abs(xc - (-X_CON_START)) < eps and ymin >= -H_CON - eps and ymax <= 0.0 + eps:
|
|
contl_mold_curves.append(c_tag)
|
|
continue
|
|
# metal bottom
|
|
if abs(ymin - (-H_CON)) < eps and abs(ymax - (-H_CON)) < eps and xmin >= -X_CON_START - eps and xmax <= -X_MET_START + eps:
|
|
contl_mold_curves.append(c_tag)
|
|
continue
|
|
# metal inner wall
|
|
if abs(xc - (-X_MET_START)) < eps and ymin >= -H_CON - H_MET - eps and ymax <= -H_CON + eps:
|
|
contl_mold_curves.append(c_tag)
|
|
continue
|
|
|
|
# Silicon-Oxide Interface
|
|
if is_on_si_ox_interface(xc, yc):
|
|
silicon_oxide_interface_curves.append(c_tag)
|
|
continue
|
|
|
|
# Oxide-Molding Interface (exposed top surface of oxide)
|
|
if is_on_ox_mold_interface(xc, yc):
|
|
ox_mold_interface_curves.append(c_tag)
|
|
continue
|
|
|
|
# Add physical groups for 1D curves
|
|
if gate_ox_curves:
|
|
gmsh.model.addPhysicalGroup(1, gate_ox_curves, name="gate_Ox")
|
|
if gate_mold_curves:
|
|
gmsh.model.addPhysicalGroup(1, gate_mold_curves, name="gate_Mold")
|
|
|
|
if contr_si_curves:
|
|
gmsh.model.addPhysicalGroup(1, contr_si_curves, name="contR_Si")
|
|
if contr_ox_curves:
|
|
gmsh.model.addPhysicalGroup(1, contr_ox_curves, name="contR_Ox")
|
|
if contr_mold_curves:
|
|
gmsh.model.addPhysicalGroup(1, contr_mold_curves, name="contR_Mold")
|
|
|
|
if contl_si_curves:
|
|
gmsh.model.addPhysicalGroup(1, contl_si_curves, name="contL_Si")
|
|
if contl_ox_curves:
|
|
gmsh.model.addPhysicalGroup(1, contl_ox_curves, name="contL_Ox")
|
|
if contl_mold_curves:
|
|
gmsh.model.addPhysicalGroup(1, contl_mold_curves, name="contL_Mold")
|
|
|
|
if silicon_oxide_interface_curves:
|
|
gmsh.model.addPhysicalGroup(1, silicon_oxide_interface_curves, name="Si_Ox_Interface")
|
|
if ox_mold_interface_curves:
|
|
gmsh.model.addPhysicalGroup(1, ox_mold_interface_curves, name="Ox_Mold_Interface")
|
|
if substrate_bottom_si_curves:
|
|
gmsh.model.addPhysicalGroup(1, substrate_bottom_si_curves, name="Substrate_Bottom")
|
|
if molding_top_curves:
|
|
gmsh.model.addPhysicalGroup(1, molding_top_curves, name="Molding_Top")
|
|
|
|
# 1. Silicon Sizing Field: refined at interface, growing linearly to 0.2 um at 1 um depth, then to 0.5 um in bulk
|
|
gmsh.model.mesh.field.add("MathEval", 10)
|
|
x_gox_end = X_FOX_START - L_BEAK
|
|
lc_surf = f"(0.005*{um} + 0.045*{um}*(1.0 - exp(-max(0.0, abs(x) - {x_gox_end}) / (0.2*{um}))))"
|
|
gmsh.model.mesh.field.setString(10, "F", f"{lc_surf} + (0.2*{um} - {lc_surf})*min(1.0, y/(1.0*{um})) + (0.3*{um})*min(1.0, max(0.0, y - 1.0*{um})/(1.0*{um}))")
|
|
restrict_si = gmsh.model.mesh.field.add("Restrict")
|
|
gmsh.model.mesh.field.setNumbers(restrict_si, "SurfacesList", silicon_tags)
|
|
si_boundary = gmsh.model.getBoundary([(2, t) for t in silicon_tags], combined=True, oriented=False, recursive=False)
|
|
gmsh.model.mesh.field.setNumbers(restrict_si, "CurvesList", [c[1] for c in si_boundary if c[0] == 1])
|
|
gmsh.model.mesh.field.setNumber(restrict_si, "IField", 10)
|
|
|
|
# 2. Oxide Sizing Field: Gate area (|x| <= X_FOX_START) is 0.05 um, relaxing to 0.2 um at FOX
|
|
gmsh.model.mesh.field.add("MathEval", 11)
|
|
gmsh.model.mesh.field.setString(11, "F", f"0.05 * {um} + 0.15 * {um} * (1.0 - exp(-max(0.0, abs(x) - {X_FOX_START}) / (0.5 * {um})))")
|
|
restrict_ox = gmsh.model.mesh.field.add("Restrict")
|
|
gmsh.model.mesh.field.setNumbers(restrict_ox, "SurfacesList", oxide_tags)
|
|
ox_boundary = gmsh.model.getBoundary([(2, t) for t in oxide_tags], combined=True, oriented=False, recursive=False)
|
|
gmsh.model.mesh.field.setNumbers(restrict_ox, "CurvesList", [c[1] for c in ox_boundary if c[0] == 1])
|
|
gmsh.model.mesh.field.setNumber(restrict_ox, "IField", 11)
|
|
|
|
# 3. Molding/ILD Sizing Field: Uniform 0.6 um
|
|
gmsh.model.mesh.field.add("MathEval", 12)
|
|
gmsh.model.mesh.field.setString(12, "F", f"0.6 * {um}")
|
|
restrict_mold = gmsh.model.mesh.field.add("Restrict")
|
|
gmsh.model.mesh.field.setNumbers(restrict_mold, "SurfacesList", molding_tags)
|
|
mold_boundary = gmsh.model.getBoundary([(2, t) for t in molding_tags], combined=True, oriented=False, recursive=False)
|
|
gmsh.model.mesh.field.setNumbers(restrict_mold, "CurvesList", [c[1] for c in mold_boundary if c[0] == 1])
|
|
gmsh.model.mesh.field.setNumber(restrict_mold, "IField", 12)
|
|
|
|
# Combine all restricted fields using Min
|
|
min_field = gmsh.model.mesh.field.add("Min")
|
|
gmsh.model.mesh.field.setNumbers(min_field, "FieldsList", [restrict_si, restrict_ox, restrict_mold])
|
|
|
|
if bgmesh_pos and os.path.exists(bgmesh_pos):
|
|
gmsh.merge(bgmesh_pos)
|
|
bgm_field = gmsh.model.mesh.field.add("PostView")
|
|
gmsh.model.mesh.field.setNumber(bgm_field, "ViewIndex", 0)
|
|
|
|
comb_field = gmsh.model.mesh.field.add("Min")
|
|
gmsh.model.mesh.field.setNumbers(comb_field, "FieldsList", [min_field, bgm_field])
|
|
gmsh.model.mesh.field.setAsBackgroundMesh(comb_field)
|
|
else:
|
|
gmsh.model.mesh.field.setAsBackgroundMesh(min_field)
|
|
|
|
# Set colors of surfaces for better contrast (GUI visualization)
|
|
# Silicon: Steel Blue (70, 130, 180)
|
|
# Oxide: Coral Orange (255, 127, 80)
|
|
# Molding/ILD: Dark Sea Green (143, 188, 143)
|
|
if silicon_tags:
|
|
gmsh.model.setColor([(2, tag) for tag in silicon_tags], 70, 130, 180)
|
|
if oxide_tags:
|
|
gmsh.model.setColor([(2, tag) for tag in oxide_tags], 255, 127, 80)
|
|
if molding_tags:
|
|
gmsh.model.setColor([(2, tag) for tag in molding_tags], 143, 188, 143)
|
|
|
|
# Color all 1D curves to dark grey (50, 50, 50) to make boundaries highly visible
|
|
curves = gmsh.model.getEntities(dim=1)
|
|
if curves:
|
|
gmsh.model.setColor([(1, c[1]) for c in curves], 50, 50, 50)
|
|
|
|
# Force MSH 2.2 output format
|
|
gmsh.option.setNumber("Mesh.MshFileVersion", 2.2)
|
|
|
|
# Configure high contrast visualization options in case of GUI launch
|
|
gmsh.option.setNumber("Mesh.ColorCarousel", 0) # Use entity colors instead of dimension colors
|
|
gmsh.option.setNumber("Mesh.SurfaceFaces", 1) # Fill mesh elements in 2D
|
|
gmsh.option.setNumber("Mesh.SurfaceEdges", 1) # Draw mesh boundaries/lines
|
|
# Set background color to dark grey (30, 30, 30) for maximum line contrast
|
|
gmsh.option.setColor("General.Background", 30, 30, 30)
|
|
gmsh.option.setColor("General.BackgroundGradient", 30, 30, 30)
|
|
gmsh.option.setColor("General.Foreground", 255, 255, 255) # White text
|
|
|
|
# Set global size limits
|
|
gmsh.option.setNumber("Mesh.MeshSizeMin", 0.005 * um)
|
|
gmsh.option.setNumber("Mesh.MeshSizeMax", 20.0 * um)
|
|
|
|
gmsh.model.mesh.generate(2)
|
|
gmsh.write(mesh_out)
|
|
gmsh.finalize()
|
|
print("LDMOS Mesh generation complete! Saved as device_2d.msh.")
|
|
|
|
def create_triac_mesh(y_box_max=12.0*um, y_medium_max=20.0*um, mesh_out="device_2d.msh", bgmesh_pos="device_bgmesh.pos"):
|
|
gmsh.initialize()
|
|
gmsh.model.add("device_2d")
|
|
|
|
# Use OpenCASCADE kernel
|
|
occ = gmsh.model.occ
|
|
|
|
# 1. Create Silicon substrate: Y in [0, H_SI]
|
|
silicon = occ.addRectangle(-W_DEVICE, 0, 0, 2 * W_DEVICE, H_SI)
|
|
|
|
# 2. Create Oxide layer: Y in [-T_OX, 0]
|
|
oxide_base = occ.addRectangle(-W_DEVICE, -T_OX, 0, 2 * W_DEVICE, T_OX)
|
|
|
|
# Helper to create via rectangles (metal openings)
|
|
def create_vias(occ_kernel):
|
|
mring_l = occ_kernel.addRectangle(-W_DEVICE, -T_OX, 0, (W_DEVICE - MRING_X1), T_OX)
|
|
mt2_v1 = occ_kernel.addRectangle(-VIA_P13_X - 0.5 * VIA_WIDTH, -T_OX, 0, VIA_WIDTH, T_OX)
|
|
mt2_v3 = occ_kernel.addRectangle(-VIA_P11_X - 0.5 * VIA_WIDTH, -T_OX, 0, VIA_WIDTH, T_OX)
|
|
mt1_v1 = occ_kernel.addRectangle(VIA_P11_X - 0.5 * VIA_WIDTH, -T_OX, 0, VIA_WIDTH, T_OX)
|
|
mt1_v3 = occ_kernel.addRectangle(VIA_P13_X - 0.5 * VIA_WIDTH, -T_OX, 0, VIA_WIDTH, T_OX)
|
|
mring_r = occ_kernel.addRectangle(MRING_X1, -T_OX, 0, (W_DEVICE - MRING_X1), T_OX)
|
|
return [(2, mring_l), (2, mt2_v1), (2, mt2_v3), (2, mt1_v1), (2, mt1_v3), (2, mring_r)]
|
|
|
|
# 3. Subtract vias from oxide to create oxide regions
|
|
vias_for_oxide = create_vias(occ)
|
|
oxide_cut_list, _ = occ.cut([(2, oxide_base)], vias_for_oxide)
|
|
|
|
# 4. Create Molding layer that covers the entire simulation domain:
|
|
# X in [-W_SIM, W_SIM], Y in [-T_OX - H_MOLD, H_SI]
|
|
molding_base = occ.addRectangle(-W_SIM, -T_OX - H_MOLD, 0, 2 * W_SIM, H_SI + T_OX + H_MOLD)
|
|
|
|
# Subtract vias from molding_base to ensure vias are not filled with molding compound
|
|
vias_for_mold = create_vias(occ)
|
|
molding_cut_list, _ = occ.cut([(2, molding_base)], vias_for_mold)
|
|
|
|
# Add dummy points at Y=0 to force fragmentation of Silicon surface for P12 virtual contacts
|
|
p1 = occ.addPoint(-P12_X2, 0, 0)
|
|
p2 = occ.addPoint(-P12_X1, 0, 0)
|
|
p3 = occ.addPoint(P12_X1, 0, 0)
|
|
p4 = occ.addPoint(P12_X2, 0, 0)
|
|
dummy_points = [(0, p1), (0, p2), (0, p3), (0, p4)]
|
|
|
|
# Add dummy points at Y=-T_OX to force fragmentation of oxide-molding interface for field plates
|
|
fp_points = []
|
|
fp_x_list = [
|
|
-MT1_FP2_X2, -MT1_FP2_X1,
|
|
-MT1_FP1_X2, -MT1_FP1_X1,
|
|
MT1_FP1_X1, MT1_FP1_X2,
|
|
MT1_FP2_X1, MT1_FP2_X2
|
|
]
|
|
for i, x_val in enumerate(fp_x_list):
|
|
pt = occ.addPoint(x_val, -T_OX, 0)
|
|
fp_points.append((0, pt))
|
|
|
|
# Now fragment the silicon substrate, the remaining oxide, and the remaining molding layer, along with dummy points and field plate points
|
|
out, out_map = occ.fragment([(2, silicon)] + dummy_points + fp_points, oxide_cut_list + molding_cut_list)
|
|
|
|
occ.synchronize()
|
|
|
|
# Define physical groups for regions
|
|
silicon_tags = []
|
|
oxide_tags = []
|
|
molding_tags = []
|
|
for ent in gmsh.model.getEntities(dim=2):
|
|
tag = ent[1]
|
|
mass_center = occ.getCenterOfMass(2, tag)
|
|
x_c, y_c = mass_center[0], mass_center[1]
|
|
|
|
# Check if it is inside Silicon die boundaries
|
|
if y_c >= -1e-8 and abs(x_c) <= W_DEVICE + 1e-8:
|
|
silicon_tags.append(tag)
|
|
# Check if it is inside Oxide layer boundaries
|
|
elif y_c < -1e-8 and y_c >= -T_OX - 1e-8 and abs(x_c) <= W_DEVICE + 1e-8:
|
|
oxide_tags.append(tag)
|
|
# Otherwise it is molding compound (top or sides)
|
|
else:
|
|
molding_tags.append(tag)
|
|
|
|
gmsh.model.addPhysicalGroup(2, silicon_tags, tag=1, name="Silicon")
|
|
gmsh.model.addPhysicalGroup(2, oxide_tags, tag=2, name="Oxide")
|
|
gmsh.model.addPhysicalGroup(2, molding_tags, tag=3, name="Molding")
|
|
|
|
# Bounding box epsilon
|
|
eps = 0.01 * um
|
|
|
|
mt1_si_curves = []
|
|
mt2_si_curves = []
|
|
p12_l_si_curves = []
|
|
p12_r_si_curves = []
|
|
mring_l_si_curves = []
|
|
mring_r_si_curves = []
|
|
|
|
# Contacts for Oxide
|
|
mt1_ox_curves = []
|
|
mt2_ox_curves = []
|
|
mring_l_ox_curves = []
|
|
mring_r_ox_curves = []
|
|
|
|
# Contacts for Molding
|
|
mt1_mold_curves = []
|
|
mt2_mold_curves = []
|
|
mring_l_mold_curves = []
|
|
mring_r_mold_curves = []
|
|
|
|
silicon_oxide_interface_curves = []
|
|
|
|
substrate_bottom_si_curves = []
|
|
substrate_bottom_mold_curves = []
|
|
|
|
silicon_molding_side_curves = []
|
|
|
|
ox_mold_interface_curves = []
|
|
molding_top_curves = []
|
|
|
|
def is_in_via_opening(xmin, xmax):
|
|
via_ranges = [
|
|
(-VIA_P13_X - 0.5 * VIA_WIDTH, -VIA_P13_X + 0.5 * VIA_WIDTH),
|
|
(-VIA_P11_X - 0.5 * VIA_WIDTH, -VIA_P11_X + 0.5 * VIA_WIDTH),
|
|
(VIA_P11_X - 0.5 * VIA_WIDTH, VIA_P11_X + 0.5 * VIA_WIDTH),
|
|
(VIA_P13_X - 0.5 * VIA_WIDTH, VIA_P13_X + 0.5 * VIA_WIDTH)
|
|
]
|
|
for vl, vh in via_ranges:
|
|
if xmin >= vl - eps and xmax <= vh + eps:
|
|
return True
|
|
return False
|
|
|
|
curves = gmsh.model.getEntities(dim=1)
|
|
for c in curves:
|
|
c_tag = c[1]
|
|
xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.getBoundingBox(1, c_tag)
|
|
|
|
# Check if it lies on the substrate bottom boundary Y = H_SI
|
|
if abs(ymin - H_SI) < eps and abs(ymax - H_SI) < eps:
|
|
if abs(xmin) <= W_DEVICE + eps and abs(xmax) <= W_DEVICE + eps:
|
|
substrate_bottom_si_curves.append(c_tag)
|
|
else:
|
|
substrate_bottom_mold_curves.append(c_tag)
|
|
continue
|
|
|
|
# Check if it lies at Y = 0 (Silicon-Oxide interface or contacts at Y=0)
|
|
if abs(ymin) < eps and abs(ymax) < eps:
|
|
# MT2 Left Via contact
|
|
if xmin >= (-VIA_P13_X - 0.5*VIA_WIDTH) - eps and xmax <= (-VIA_P13_X + 0.5*VIA_WIDTH) + eps:
|
|
mt2_si_curves.append(c_tag)
|
|
# MT2 Right Via contact (p11_left)
|
|
elif xmin >= (-VIA_P11_X - 0.5*VIA_WIDTH) - eps and xmax <= (-VIA_P11_X + 0.5*VIA_WIDTH) + eps:
|
|
mt2_si_curves.append(c_tag)
|
|
# MT1 Left Via contact (p11_right)
|
|
elif xmin >= (VIA_P11_X - 0.5*VIA_WIDTH) - eps and xmax <= (VIA_P11_X + 0.5*VIA_WIDTH) + eps:
|
|
mt1_si_curves.append(c_tag)
|
|
# MT1 Right Via contact (p13_right N+)
|
|
elif xmin >= (VIA_P13_X - 0.5*VIA_WIDTH) - eps and xmax <= (VIA_P13_X + 0.5*VIA_WIDTH) + eps:
|
|
mt1_si_curves.append(c_tag)
|
|
# P12 Left virtual contact (connected to MT2)
|
|
elif xmin >= -P12_X2 - eps and xmax <= -P12_X1 + eps:
|
|
p12_l_si_curves.append(c_tag)
|
|
# P12 Right virtual contact (connected to MT1)
|
|
elif xmin >= P12_X1 - eps and xmax <= P12_X2 + eps:
|
|
p12_r_si_curves.append(c_tag)
|
|
# MRING Left contact at Y=0
|
|
elif xmin >= -W_DEVICE - eps and xmax <= -MRING_X1 + eps:
|
|
mring_l_si_curves.append(c_tag)
|
|
# MRING Right contact at Y=0
|
|
elif xmin >= MRING_X1 - eps and xmax <= W_DEVICE + eps:
|
|
mring_r_si_curves.append(c_tag)
|
|
else:
|
|
silicon_oxide_interface_curves.append(c_tag)
|
|
continue
|
|
|
|
# Check if it lies on the top boundary of Molding: Y = -T_OX - H_MOLD
|
|
if abs(ymin - (-T_OX - H_MOLD)) < eps and abs(ymax - (-T_OX - H_MOLD)) < eps:
|
|
molding_top_curves.append(c_tag)
|
|
continue
|
|
|
|
# Check if it lies at Y = -T_OX (oxide-molding interface and field plates)
|
|
if abs(ymin + T_OX) < eps and abs(ymax + T_OX) < eps:
|
|
# MT2 field plates: [-MT1_FP2_X2, -MT1_FP2_X1] and [-MT1_FP1_X2, -MT1_FP1_X1]
|
|
if (xmin >= -MT1_FP2_X2 - eps and xmax <= -MT1_FP2_X1 + eps) or \
|
|
(xmin >= -MT1_FP1_X2 - eps and xmax <= -MT1_FP1_X1 + eps):
|
|
mt2_mold_curves.append(c_tag)
|
|
if not is_in_via_opening(xmin, xmax):
|
|
mt2_ox_curves.append(c_tag)
|
|
# MT1 field plates: [MT1_FP1_X1, MT1_FP1_X2] and [MT1_FP2_X1, MT1_FP2_X2]
|
|
elif (xmin >= MT1_FP1_X1 - eps and xmax <= MT1_FP1_X2 + eps) or \
|
|
(xmin >= MT1_FP2_X1 - eps and xmax <= MT1_FP2_X2 + eps):
|
|
mt1_mold_curves.append(c_tag)
|
|
if not is_in_via_opening(xmin, xmax):
|
|
mt1_ox_curves.append(c_tag)
|
|
# MRING Left top: [-W_DEVICE, -MRING_X1]
|
|
elif xmin >= -W_DEVICE - eps and xmax <= -MRING_X1 + eps:
|
|
mring_l_mold_curves.append(c_tag)
|
|
# MRING Right top: [MRING_X1, W_DEVICE]
|
|
elif xmin >= MRING_X1 - eps and xmax <= W_DEVICE + eps:
|
|
mring_r_mold_curves.append(c_tag)
|
|
else:
|
|
ox_mold_interface_curves.append(c_tag)
|
|
continue
|
|
|
|
# Check for vertical curves: abs(xmin - xmax) < eps
|
|
if abs(xmin - xmax) < eps:
|
|
x_coord = (xmin + xmax) / 2.0
|
|
|
|
# Check for Silicon-Molding side boundaries: at X = +-W_DEVICE and Y in [0, H_SI]
|
|
if (abs(x_coord - W_DEVICE) < eps or abs(x_coord - (-W_DEVICE)) < eps) and ymin >= -eps and ymax <= H_SI + eps:
|
|
silicon_molding_side_curves.append(c_tag)
|
|
continue
|
|
|
|
# Check for vertical sidewalls of the vias (which are metal-oxide interfaces)
|
|
# These are vertical lines between Y = -T_OX and Y = 0
|
|
if ymin >= -T_OX - eps and ymax <= eps:
|
|
# Vias for MT2
|
|
if (abs(x_coord - (-VIA_P13_X - 0.5*VIA_WIDTH)) < eps or abs(x_coord - (-VIA_P13_X + 0.5*VIA_WIDTH)) < eps or
|
|
abs(x_coord - (-VIA_P11_X - 0.5*VIA_WIDTH)) < eps or abs(x_coord - (-VIA_P11_X + 0.5*VIA_WIDTH)) < eps):
|
|
mt2_ox_curves.append(c_tag)
|
|
# Vias for MT1
|
|
elif (abs(x_coord - (VIA_P11_X - 0.5*VIA_WIDTH)) < eps or abs(x_coord - (VIA_P11_X + 0.5*VIA_WIDTH)) < eps or
|
|
abs(x_coord - (VIA_P13_X - 0.5*VIA_WIDTH)) < eps or abs(x_coord - (VIA_P13_X + 0.5*VIA_WIDTH)) < eps):
|
|
mt1_ox_curves.append(c_tag)
|
|
# Vias/sidewalls for MRING (Oxide-MRING interface)
|
|
elif abs(x_coord - (-MRING_X1)) < eps:
|
|
mring_l_ox_curves.append(c_tag)
|
|
elif abs(x_coord - (MRING_X1)) < eps:
|
|
mring_r_ox_curves.append(c_tag)
|
|
# Outer side of MRING touching Molding (at X = +-W_DEVICE, Y in [-T_OX, 0])
|
|
elif abs(x_coord - (-W_DEVICE)) < eps:
|
|
mring_l_mold_curves.append(c_tag)
|
|
elif abs(x_coord - (W_DEVICE)) < eps:
|
|
mring_r_mold_curves.append(c_tag)
|
|
|
|
# Register the physical groups for boundaries
|
|
if mt1_si_curves:
|
|
gmsh.model.addPhysicalGroup(1, mt1_si_curves, name="MT1_Si")
|
|
if mt2_si_curves:
|
|
gmsh.model.addPhysicalGroup(1, mt2_si_curves, name="MT2_Si")
|
|
if p12_l_si_curves:
|
|
gmsh.model.addPhysicalGroup(1, p12_l_si_curves, name="MT2_P12_Si")
|
|
if p12_r_si_curves:
|
|
gmsh.model.addPhysicalGroup(1, p12_r_si_curves, name="MT1_P12_Si")
|
|
if mring_l_si_curves:
|
|
gmsh.model.addPhysicalGroup(1, mring_l_si_curves, name="MRING_L_Si")
|
|
if mring_r_si_curves:
|
|
gmsh.model.addPhysicalGroup(1, mring_r_si_curves, name="MRING_R_Si")
|
|
|
|
if mt1_ox_curves:
|
|
gmsh.model.addPhysicalGroup(1, mt1_ox_curves, name="MT1_Ox")
|
|
if mt1_mold_curves:
|
|
gmsh.model.addPhysicalGroup(1, mt1_mold_curves, name="MT1_Mold")
|
|
|
|
if mt2_ox_curves:
|
|
gmsh.model.addPhysicalGroup(1, mt2_ox_curves, name="MT2_Ox")
|
|
if mt2_mold_curves:
|
|
gmsh.model.addPhysicalGroup(1, mt2_mold_curves, name="MT2_Mold")
|
|
|
|
if mring_l_ox_curves:
|
|
gmsh.model.addPhysicalGroup(1, mring_l_ox_curves, name="MRING_L_Ox")
|
|
if mring_l_mold_curves:
|
|
gmsh.model.addPhysicalGroup(1, mring_l_mold_curves, name="MRING_L_Mold")
|
|
|
|
if mring_r_ox_curves:
|
|
gmsh.model.addPhysicalGroup(1, mring_r_ox_curves, name="MRING_R_Ox")
|
|
if mring_r_mold_curves:
|
|
gmsh.model.addPhysicalGroup(1, mring_r_mold_curves, name="MRING_R_Mold")
|
|
|
|
if silicon_oxide_interface_curves:
|
|
gmsh.model.addPhysicalGroup(1, silicon_oxide_interface_curves, name="Si_Ox_Interface")
|
|
if substrate_bottom_si_curves:
|
|
gmsh.model.addPhysicalGroup(1, substrate_bottom_si_curves, name="Substrate_Bottom")
|
|
if substrate_bottom_mold_curves:
|
|
gmsh.model.addPhysicalGroup(1, substrate_bottom_mold_curves, name="Substrate_Bottom_Mold")
|
|
|
|
if silicon_molding_side_curves:
|
|
gmsh.model.addPhysicalGroup(1, silicon_molding_side_curves, name="Si_Mold_Interface")
|
|
|
|
if ox_mold_interface_curves:
|
|
gmsh.model.addPhysicalGroup(1, ox_mold_interface_curves, name="Ox_Mold_Interface")
|
|
if molding_top_curves:
|
|
gmsh.model.addPhysicalGroup(1, molding_top_curves, name="Molding_Top")
|
|
|
|
# Set mesh size field for high resolution near all interfaces and electrode edges
|
|
gmsh.model.mesh.field.add("Distance", 1)
|
|
target_curves = (silicon_oxide_interface_curves + mt1_si_curves + mt2_si_curves +
|
|
ox_mold_interface_curves + mt1_ox_curves + mt2_ox_curves +
|
|
p12_l_si_curves + p12_r_si_curves +
|
|
mring_l_si_curves + mring_r_si_curves +
|
|
mring_l_ox_curves + mring_r_ox_curves +
|
|
mring_l_mold_curves + mring_r_mold_curves)
|
|
gmsh.model.mesh.field.setNumbers(1, "CurvesList", target_curves)
|
|
|
|
gmsh.model.mesh.field.add("Threshold", 2)
|
|
gmsh.model.mesh.field.setNumber(2, "IField", 1)
|
|
gmsh.model.mesh.field.setNumber(2, "LcMin", 0.15 * um) # 0.15 um near interfaces
|
|
gmsh.model.mesh.field.setNumber(2, "LcMax", 20.0 * um) #放寬至 20 um
|
|
gmsh.model.mesh.field.setNumber(2, "DistMin", 0.15 * um) # Concentrated near interfaces
|
|
gmsh.model.mesh.field.setNumber(2, "DistMax", 1.0 * um) # Coarsen rapidly at 1.0 um
|
|
|
|
# MathEval field for logarithmic transition from 1.0 um (0-10 um depth) -> 2.0 um (10-80 um depth) -> 20.0 um (80-110 um depth)
|
|
gmsh.model.mesh.field.add("MathEval", 3)
|
|
u1 = f"min(1.0, max(0.0, (y - 10.0 * {um}) / (70.0 * {um})))"
|
|
u2 = f"max(0.0, (y - 80.0 * {um}) / (30.0 * {um}))"
|
|
formula = f"1.0 * {um} * 2^({u1}) * 10^({u2})"
|
|
gmsh.model.mesh.field.setString(3, "F", formula)
|
|
|
|
# Combine threshold field and MathEval field using Min field
|
|
gmsh.model.mesh.field.add("Min", 4)
|
|
gmsh.model.mesh.field.setNumbers(4, "FieldsList", [2, 3])
|
|
|
|
# Restrict the combined field to only Silicon and Oxide regions
|
|
restrict_field = gmsh.model.mesh.field.add("Restrict")
|
|
gmsh.model.mesh.field.setNumbers(restrict_field, "SurfacesList", silicon_tags + oxide_tags)
|
|
gmsh.model.mesh.field.setNumber(restrict_field, "IField", 4)
|
|
|
|
# If background mesh file exists, merge it and combine with restricted field using Min field
|
|
if bgmesh_pos and os.path.exists(bgmesh_pos):
|
|
gmsh.merge(bgmesh_pos)
|
|
bgm_field = gmsh.model.mesh.field.add("PostView")
|
|
gmsh.model.mesh.field.setNumber(bgm_field, "ViewIndex", 0)
|
|
|
|
# Restrict the bgm_field to Silicon and Oxide only
|
|
restrict_bgm = gmsh.model.mesh.field.add("Restrict")
|
|
gmsh.model.mesh.field.setNumbers(restrict_bgm, "SurfacesList", silicon_tags + oxide_tags)
|
|
gmsh.model.mesh.field.setNumber(restrict_bgm, "IField", bgm_field)
|
|
|
|
min_field = gmsh.model.mesh.field.add("Min")
|
|
gmsh.model.mesh.field.setNumbers(min_field, "FieldsList", [restrict_field, restrict_bgm])
|
|
gmsh.model.mesh.field.setAsBackgroundMesh(min_field)
|
|
print("Successfully merged and combined background mesh with restricted field using Min field.")
|
|
else:
|
|
gmsh.model.mesh.field.setAsBackgroundMesh(restrict_field)
|
|
print("Set restricted field as background mesh.")
|
|
|
|
# Force MSH 2.2 output format and set global size limits and gradation
|
|
gmsh.option.setNumber("Mesh.MshFileVersion", 2.2)
|
|
gmsh.option.setNumber("Mesh.MeshSizeMin", 0.15 * um)
|
|
gmsh.option.setNumber("Mesh.MeshSizeMax", 20.0 * um)
|
|
|
|
# Generate 2D mesh
|
|
gmsh.model.mesh.generate(2)
|
|
gmsh.write(mesh_out)
|
|
gmsh.finalize()
|
|
print("Mesh generation complete! Saved as device_2d.msh.")
|
|
|
|
def create_mesh(y_box_max=12.0*um, y_medium_max=20.0*um, mesh_out="device_2d.msh", bgmesh_pos="device_bgmesh.pos"):
|
|
DEV_DIR = os.environ.get("DEV_DIR", "devices/Triac_rp")
|
|
if "LDMOS" in DEV_DIR:
|
|
create_ldmos_mesh(mesh_out=mesh_out, bgmesh_pos=bgmesh_pos)
|
|
else:
|
|
create_triac_mesh(y_box_max, y_medium_max, mesh_out, bgmesh_pos)
|
|
|
|
if __name__ == "__main__":
|
|
DEV_DIR = os.environ.get("DEV_DIR", "devices/Triac_rp")
|
|
mesh_out = os.path.join(DEV_DIR, "device_2d.msh")
|
|
bgmesh_pos = os.path.join(DEV_DIR, "device_bgmesh.pos")
|
|
create_mesh(mesh_out=mesh_out, bgmesh_pos=bgmesh_pos)
|