272 lines
9.5 KiB
Python
272 lines
9.5 KiB
Python
# device_pcad_config.py
|
|
# Process-Step Oriented 2D Doping Configuration File for LDMOS
|
|
|
|
import os
|
|
import sys
|
|
import numpy as np
|
|
import math
|
|
|
|
# Add workspace directory to path for physics import
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
from physics.pcad_physics import get_implant_params, get_diffusion_coefficient
|
|
|
|
# All units in cm (1 um = 1e-4 cm)
|
|
um = 1e-4
|
|
|
|
from device_config import (
|
|
W_DEVICE, H_SI, N_SUB, SIM_NAME,
|
|
N_CON_PEAK, N_CON_DEPTH, N_CON_HDDIFF
|
|
)
|
|
|
|
# Define mask openings (Horizontal coordinate ranges, positive X-axis. Mirroring is handled dynamically)
|
|
masks = {
|
|
'pbl_mask': [(0.0 * um, 1.5 * um)], # Deep buried P-layer (mirrors to -2 ~ 2 um)
|
|
'ndrift_mask': [(1.5 * um, 5.5 * um)], # N-drift for supporting high voltage (mirrors to 3 ~ 9.5 um)
|
|
'nchannel_mask': [(0.5 * um, 5 * um)], # Light threshold adjustment channel layer (mirrors to 0.5 ~ 8.5 um)
|
|
}
|
|
|
|
# Define process steps for the 2D doping profile
|
|
process_steps = [
|
|
# Step 0: Substrate Wafer
|
|
{
|
|
'enabled': True,
|
|
'name': 'Substrate',
|
|
'method': 'Substrate',
|
|
'type': 'p',
|
|
'doping': N_SUB,
|
|
},
|
|
# Step 1: PBL (Deep P-buried Layer)
|
|
{
|
|
'enabled': True,
|
|
'name': 'PBL',
|
|
'method': 'Implant_and_Diffuse',
|
|
'type': 'p',
|
|
'dopant': 'Boron',
|
|
'energy': 800.0, # High energy for deep penetration
|
|
'dose': 1.0e13, # typical deep implant dose (cm^-2)
|
|
'mask': 'pbl_mask',
|
|
'temp': 1000.0, # High temp for deep diffusion drive-in 1150
|
|
'time': 20.0, # Long diffusion time 240
|
|
'lateral_ratio': 0.8,
|
|
},
|
|
# Step 2: N-Drift (Drift region supporting ~60V)
|
|
{
|
|
'enabled': True,
|
|
'name': 'NDrift',
|
|
'method': 'Implant_and_Diffuse',
|
|
'type': 'n',
|
|
'dopant': 'Phosphorus',
|
|
'energy': 300.0, # FOX 讓深度變深
|
|
'dose': 5.0e12, # typical drift implant dose (cm^-2)
|
|
'mask': 'ndrift_mask',
|
|
'temp': 1050.0,
|
|
'time': 120.0,
|
|
'lateral_ratio': 0.8,
|
|
},
|
|
# Step 3: N-Channel (Light threshold adjust / channel adjust)
|
|
{
|
|
'enabled': True,
|
|
'name': 'NChannel',
|
|
'method': 'Implant_and_Diffuse',
|
|
'type': 'n',
|
|
'dopant': 'Phosphorus',
|
|
'energy': 150.0,
|
|
'dose': 8.0e11, # light dose for channel adjustment
|
|
'mask': 'nchannel_mask',
|
|
'temp': 1000.0,
|
|
'time': 45.0,
|
|
'lateral_ratio': 0.6,
|
|
}
|
|
]
|
|
|
|
def resolve_steps():
|
|
"""
|
|
Resolves the physical process steps into analytical model parameters
|
|
(peak, vdiff, hdiff, x_ranges) for profile evaluation.
|
|
"""
|
|
resolved = []
|
|
|
|
# 1. Resolve substrate first
|
|
sub_step = [s for s in process_steps if s['method'] == 'Substrate'][0]
|
|
if sub_step.get('enabled', True):
|
|
resolved.append({
|
|
'name': sub_step.get('name', 'Substrate'),
|
|
'method': 'Substrate',
|
|
'type': sub_step['type'],
|
|
'doping': sub_step['doping']
|
|
})
|
|
|
|
# 2. Resolve implant/diffusion steps
|
|
for step in process_steps:
|
|
if step['method'] == 'Substrate' or not step.get('enabled', True):
|
|
continue
|
|
|
|
name = step.get('name')
|
|
type_ = step['type']
|
|
dopant = step['dopant']
|
|
|
|
# Look up mask coordinate ranges
|
|
mask_name = step['mask']
|
|
x_ranges = masks.get(mask_name, [])
|
|
|
|
# Get implant properties
|
|
energy = step.get('energy', 80.0)
|
|
dose = step.get('dose', 1.0e12)
|
|
Rp, dRp = get_implant_params(dopant, energy)
|
|
|
|
# Get thermal diffusion properties
|
|
temp = step.get('temp', 1000.0)
|
|
time_min = step.get('time', 30.0)
|
|
|
|
D = get_diffusion_coefficient(dopant, temp)
|
|
time_sec = time_min * 60.0
|
|
Dt = D * time_sec
|
|
|
|
# Standard deviation incorporating both implant straggle and thermal budget diffusion
|
|
vdiff = math.sqrt(2.0 * (dRp ** 2) + 4.0 * Dt)
|
|
|
|
# Horizontal standard deviation based on lateral ratio
|
|
lateral_ratio = step.get('lateral_ratio', 0.8)
|
|
hdiff = vdiff * lateral_ratio
|
|
|
|
# Calculate peak concentration matching the integrated dose for Gaussian profile:
|
|
peak = dose / (vdiff * math.sqrt(math.pi))
|
|
|
|
resolved.append({
|
|
'name': name,
|
|
'method': 'Implant_and_Diffuse',
|
|
'type': type_,
|
|
'peak': peak,
|
|
'vdiff': vdiff,
|
|
'hdiff': hdiff,
|
|
'Rp': Rp,
|
|
'x_ranges': x_ranges
|
|
})
|
|
|
|
return resolved
|
|
|
|
def apply_pcad_doping_2d(device, region="Silicon"):
|
|
"""
|
|
Parses the process steps list and registers the corresponding
|
|
2D analytical doping models in DEVSIM.
|
|
"""
|
|
import devsim
|
|
|
|
donor_terms = []
|
|
acceptor_terms = []
|
|
|
|
steps = resolve_steps()
|
|
|
|
# 1. Setup Substrate baseline first
|
|
sub_step = [s for s in steps if s['method'] == 'Substrate'][0]
|
|
sub_doping = sub_step['doping']
|
|
sub_type = sub_step['type']
|
|
|
|
devsim.node_model(device=device, region=region, name="nD_sub", equation=f"{sub_doping}")
|
|
if sub_type == 'n':
|
|
donor_terms.append("nD_sub")
|
|
else:
|
|
acceptor_terms.append("nD_sub")
|
|
|
|
# 2. Setup Fixed Contact N+ doping from device_config (does not change with PCAD steps)
|
|
expr_ncon_r = f"erfc(y / {N_CON_DEPTH}) * 0.5 * (erf((x - (8.5 * {um})) / {N_CON_HDDIFF}) - erf((x - (9.5 * {um})) / {N_CON_HDDIFF}))"
|
|
expr_ncon_l = f"erfc(y / {N_CON_DEPTH}) * 0.5 * (erf((x - (-9.5 * {um})) / {N_CON_HDDIFF}) - erf((x - (-8.5 * {um})) / {N_CON_HDDIFF}))"
|
|
devsim.node_model(device=device, region=region, name="nD_contact", equation=f"{N_CON_PEAK} * ({expr_ncon_r} + {expr_ncon_l})")
|
|
donor_terms.append("nD_contact")
|
|
|
|
# 3. Iterate and build expressions for Implant/Diffusion steps
|
|
for step in steps:
|
|
if step['method'] == 'Substrate':
|
|
continue
|
|
|
|
name = step['name']
|
|
type_ = step['type']
|
|
peak = step['peak']
|
|
x_ranges = step['x_ranges']
|
|
vdiff = step['vdiff']
|
|
hdiff = step['hdiff']
|
|
Rp = step['Rp']
|
|
|
|
# Construct analytical 2D Gaussian expression for each window range, including mirroring
|
|
expr_terms = []
|
|
for x1, x2 in x_ranges:
|
|
y_profile = f"(exp(-pow((y - {Rp}) / {vdiff}, 2)) + exp(-pow((y + {Rp}) / {vdiff}, 2)))"
|
|
# Right side (positive X)
|
|
expr_terms.append(f"{y_profile} * 0.5 * (erf((x - ({x1})) / {hdiff}) - erf((x - ({x2})) / {hdiff}))")
|
|
# Left side (negative X, mirrored)
|
|
expr_terms.append(f"{y_profile} * 0.5 * (erf((x - ({-x2})) / {hdiff}) - erf((x - ({-x1})) / {hdiff}))")
|
|
|
|
# Combine terms and multiply by peak concentration
|
|
combined_expr = f"{peak} * (" + " + ".join(expr_terms) + ")"
|
|
|
|
model_name = f"nA_{name}" if type_ == 'p' else f"nD_{name}"
|
|
devsim.node_model(device=device, region=region, name=model_name, equation=combined_expr)
|
|
|
|
if type_ == 'n':
|
|
donor_terms.append(model_name)
|
|
else:
|
|
acceptor_terms.append(model_name)
|
|
|
|
# 4. Combine separate models into global Donors and Acceptors fields in DEVSIM
|
|
donor_equation = " + ".join(donor_terms)
|
|
acceptor_equation = "1e10 + " + " + ".join(acceptor_terms)
|
|
|
|
devsim.node_model(device=device, region=region, name="Donors", equation=donor_equation)
|
|
devsim.node_model(device=device, region=region, name="Acceptors", equation=acceptor_equation)
|
|
devsim.node_model(device=device, region=region, name="NetDoping", equation="Donors - Acceptors")
|
|
devsim.node_model(device=device, region=region, name="LogNetDoping", equation="asinh(NetDoping / 2.0) / log(10.0)")
|
|
|
|
def get_pcad_doping_val_2d(x, y):
|
|
"""
|
|
Evaluates the 2D process-step doping profile analytically on numpy arrays x and y.
|
|
"""
|
|
erf_vec = np.vectorize(math.erf)
|
|
erfc_vec = np.vectorize(math.erfc)
|
|
|
|
donors = np.zeros_like(x, dtype=float)
|
|
acceptors = np.zeros_like(x, dtype=float)
|
|
|
|
steps = resolve_steps()
|
|
|
|
# 1. Setup Substrate baseline first
|
|
sub_step = [s for s in steps if s['method'] == 'Substrate'][0]
|
|
sub_doping = sub_step['doping']
|
|
sub_type = sub_step['type']
|
|
if sub_type == 'n':
|
|
donors += sub_doping
|
|
else:
|
|
acceptors += sub_doping
|
|
|
|
# 2. Add Fixed Contact N+ doping
|
|
expr_ncon_r = erfc_vec(y / N_CON_DEPTH) * 0.5 * (erf_vec((x - 8.5 * um) / N_CON_HDDIFF) - erf_vec((x - 9.5 * um) / N_CON_HDDIFF))
|
|
expr_ncon_l = erfc_vec(y / N_CON_DEPTH) * 0.5 * (erf_vec((x - (-9.5 * um)) / N_CON_HDDIFF) - erf_vec((x - (-8.5 * um)) / N_CON_HDDIFF))
|
|
donors += N_CON_PEAK * (expr_ncon_r + expr_ncon_l)
|
|
|
|
# 3. Iterate and build expressions for Implant/Diffusion steps
|
|
for step in steps:
|
|
if step['method'] == 'Substrate':
|
|
continue
|
|
|
|
type_ = step['type']
|
|
peak = step['peak']
|
|
x_ranges = step['x_ranges']
|
|
vdiff = step['vdiff']
|
|
hdiff = step['hdiff']
|
|
|
|
prof = np.zeros_like(x, dtype=float)
|
|
for x1, x2 in x_ranges:
|
|
# Right side
|
|
prof += erfc_vec(y / vdiff) * 0.5 * (erf_vec((x - x1) / hdiff) - erf_vec((x - x2) / hdiff))
|
|
# Left side (mirrored)
|
|
prof += erfc_vec(y / vdiff) * 0.5 * (erf_vec((x - (-x2)) / hdiff) - erf_vec((x - (-x1)) / hdiff))
|
|
|
|
if type_ == 'n':
|
|
donors += peak * prof
|
|
else:
|
|
acceptors += peak * prof
|
|
|
|
# Acceptor base offset 1e10
|
|
acceptors += 1e10
|
|
|
|
return donors - acceptors
|