#!/usr/bin/env python3 """ preview_doping_3d.py - Parameterized 3D geometry and doping setup for BJT. Generates a 3D mesh using Gmsh and exports a Tecplot file for ParaView preview. """ import sys import os import gmsh import numpy as np # Add virtual env path to ensure devsim is found sys.path.append(os.path.dirname(os.path.abspath(__file__))) from devsim import ( create_gmsh_mesh, add_gmsh_region, add_gmsh_contact, finalize_mesh, create_device, get_device_list, node_model, write_devices ) # ============================================================================= # 1. 幾何參數定義 (單位: cm, 1 um = 1e-4 cm) # ============================================================================= um = 1e-4 # 元件總尺寸 W_device = 50.0 * um # X 寬度 H_device = 25.0 * um # Y 深度 (朝基底方向) L_device = 10.0 * um # Z 長度 # 電極尺寸與位置 # Emitter (X: 20~30 um, Z: 2~8 um, Y: 0 表面) x_emit_center = 25.0 * um w_emit = 10.0 * um z_emit_center = 5.0 * um l_emit = 6.0 * um # Base (X: 40~45 um, Z: 2~8 um, Y: 0 表面) x_base_center = 42.5 * um w_base = 5.0 * um z_base_center = 5.0 * um l_base = 6.0 * um # 網格控制尺寸 (加密以獲得平滑的 3D 界面) mesh_size_min = 0.15 * um mesh_size_max = 0.8 * um # ============================================================================= # 2. 使用 Gmsh Python API 進行 3D 幾何與網格建模 # ============================================================================= print(">>> Step 1: Generating 3D geometry and mesh using Gmsh...") gmsh.initialize() gmsh.model.add("bjt_3d_device") # 設定網格大小限制與輸出格式為 MSH v2.2 gmsh.option.setNumber("Mesh.CharacteristicLengthMin", mesh_size_min) gmsh.option.setNumber("Mesh.CharacteristicLengthMax", mesh_size_max) gmsh.option.setNumber("Mesh.MshFileVersion", 2.2) # 建立矽區主體 (Box) silicon_vol = gmsh.model.occ.addBox(0, 0, 0, W_device, H_device, L_device) # 建立 Emitter 和 Base 的接觸面 (Rectangles, 位在 Y = 0 的上表面) # 由於 addRectangle 預設是在 X-Y 平面建立,我們需要將其旋轉 -90 度到 X-Z 平面 emitter_surf = gmsh.model.occ.addRectangle( x_emit_center - 0.5 * w_emit, 0, z_emit_center - 0.5 * l_emit, w_emit, l_emit ) base_surf = gmsh.model.occ.addRectangle( x_base_center - 0.5 * w_base, 0, z_base_center - 0.5 * l_base, w_base, l_base ) # 旋轉至 X-Z 平面 (繞起點,沿 X 軸方向向量 [1, 0, 0] 旋轉 pi/2) gmsh.model.occ.rotate([(2, emitter_surf)], x_emit_center - 0.5 * w_emit, 0, z_emit_center - 0.5 * l_emit, 1, 0, 0, np.pi/2) gmsh.model.occ.rotate([(2, base_surf)], x_base_center - 0.5 * w_base, 0, z_base_center - 0.5 * l_base, 1, 0, 0, np.pi/2) # 使用布林片段 (Boolean Fragment) 將電極表面縫合嵌入到矽主體的頂面 # 這會確保網格在此交界處的節點能完美對齊 out, out_map = gmsh.model.occ.fragment( [(3, silicon_vol)], [(2, emitter_surf), (2, base_surf)] ) gmsh.model.occ.synchronize() # 找出各個實體 (Entity) 的 Tag 來指定 Physical Groups (區域與電極) # 我們透過包圍盒 (Bounding Box) 來精確搜尋 # search format: xmin, ymin, zmin, xmax, ymax, zmax, dim # 1. 找出 Silicon 3D 體積 (dim=3) vol_entities = gmsh.model.getEntities(3) silicon_tags = [tag for dim, tag in vol_entities] gmsh.model.addPhysicalGroup(3, silicon_tags, tag=1, name="Silicon") # 2. 找出 Emitter 接觸面 (dim=2, 位在 Y = 0) eps = 0.1 * um emitter_entities = gmsh.model.getEntitiesInBoundingBox( x_emit_center - 0.5 * w_emit - eps, -eps, z_emit_center - 0.5 * l_emit - eps, x_emit_center + 0.5 * w_emit + eps, eps, z_emit_center + 0.5 * l_emit + eps, dim=2 ) emitter_tags = [tag for dim, tag in emitter_entities] gmsh.model.addPhysicalGroup(2, emitter_tags, tag=10, name="emitter") # 3. 找出 Base 接觸面 (dim=2, 位在 Y = 0) base_entities = gmsh.model.getEntitiesInBoundingBox( x_base_center - 0.5 * w_base - eps, -eps, z_base_center - 0.5 * l_base - eps, x_base_center + 0.5 * w_base + eps, eps, z_base_center + 0.5 * l_base + eps, dim=2 ) base_tags = [tag for dim, tag in base_entities] gmsh.model.addPhysicalGroup(2, base_tags, tag=11, name="base") # 4. 找出 Collector 接觸面 (整個底面, dim=2, 位在 Y = H_device) collector_entities = gmsh.model.getEntitiesInBoundingBox( -eps, H_device - eps, -eps, W_device + eps, H_device + eps, L_device + eps, dim=2 ) collector_tags = [tag for dim, tag in collector_entities] gmsh.model.addPhysicalGroup(2, collector_tags, tag=12, name="collector") # 生成 3D 網格 gmsh.model.mesh.generate(3) msh_filename = "bjt_3d.msh" gmsh.write(msh_filename) gmsh.finalize() print(f">>> Step 1 Completed: Mesh saved to {msh_filename}") # ============================================================================= # 3. 載入 DEVSIM 並設定 3D 參數化摻雜 (Doping Profile) # ============================================================================= print(">>> Step 2: Setting up 3D Doping Profiles in DEVSIM...") device = "bjt_3d_device" # 載入 Gmsh 產生的網格 create_gmsh_mesh(mesh=device, file=msh_filename) add_gmsh_region(mesh=device, gmsh_name="Silicon", region="Silicon", material="Silicon") for contact in ["collector", "emitter", "base"]: add_gmsh_contact(mesh=device, gmsh_name=contact, region="Silicon", name=contact, material="metal") finalize_mesh(mesh=device) create_device(mesh=device, device=device) # --- 定義 3D 擴散參數 (可自由調整數值進行參數化) --- # Emitter (N+) node_model(device=device, region="Silicon", name="emitter_doping", equation="1.0e19") node_model(device=device, region="Silicon", name="emitter_depth", equation="0.8e-4") # 0.8 um 結深 node_model(device=device, region="Silicon", name="emitter_vdiff", equation="0.2e-4") # Y 垂直擴散係數 node_model(device=device, region="Silicon", name="emitter_hdiff", equation="0.15e-4") # X 橫向擴散係數 node_model(device=device, region="Silicon", name="emitter_zdiff", equation="0.15e-4") # Z 橫向擴散係數 # Base (P) node_model(device=device, region="Silicon", name="base_doping", equation="1.0e17") node_model(device=device, region="Silicon", name="base_depth", equation="3.5e-4") # 3.5 um 結深 node_model(device=device, region="Silicon", name="base_vdiff", equation="0.8e-4") node_model(device=device, region="Silicon", name="base_hdiff", equation="0.6e-4") node_model(device=device, region="Silicon", name="base_zdiff", equation="0.6e-4") # Background Substrate (N-) node_model(device=device, region="Silicon", name="nsub_doping", equation="1.0e16") # --- 3D ERFC 摻雜分佈方程式 (X, Y, Z 三維空間分佈) --- # Emitter 3D 摻雜 (Y 往下擴散,X 與 Z 則是雙向橫向擴散) node_model(device=device, region="Silicon", name="nD_emit", equation=f""" emitter_doping * erfc((y - emitter_depth) / emitter_vdiff) * erfc(-(x + 0.5 * {w_emit} - {x_emit_center}) / emitter_hdiff) * erfc((x - 0.5 * {w_emit} - {x_emit_center}) / emitter_hdiff) * erfc(-(z + 0.5 * {l_emit} - {z_emit_center}) / emitter_zdiff) * erfc((z - 0.5 * {l_emit} - {z_emit_center}) / emitter_zdiff) """) # Base 3D 摻雜 node_model(device=device, region="Silicon", name="nA_base", equation=f""" base_doping * erfc((y - base_depth) / base_vdiff) * erfc(-(x + 0.5 * {w_base} - {x_base_center}) / base_hdiff) * erfc((x - 0.5 * {w_base} - {x_base_center}) / base_hdiff) * erfc(-(z + 0.5 * {l_base} - {z_base_center}) / base_zdiff) * erfc((z - 0.5 * {l_base} - {z_base_center}) / base_zdiff) """) # 合併總摻雜 (NetDoping) node_model(device=device, region="Silicon", name="Donors", equation="nsub_doping + nD_emit") node_model(device=device, region="Silicon", name="Acceptors", equation="1e10 + nA_base") node_model(device=device, region="Silicon", name="NetDoping", equation="Donors - Acceptors") # 建立 LogScale 變數,便於在 ParaView 中以對數範圍看濃度 (跨越 10^10 ~ 10^19) node_model(device=device, region="Silicon", name="LogNetDoping", equation="asinh(NetDoping/2)/log(10)") # ============================================================================= # 4. 輸出預覽檔案 (不進行求解) # ============================================================================= preview_filename = "doping_3d_preview.tec" print(f">>> Step 3: Exporting preview to {preview_filename}...") write_devices(file=preview_filename, type="tecplot") print(">>> Step 3 Completed! Ready for ParaView visualization.")