Files

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)