44b41698e8
- gui1d: - Created interactive 1D Diode simulator dashboard (gui1d/app.py, solve_1d.py). - Redesigned doping process step editor, layout, and centered legend plots. - Implemented state caching for I-V sweeps and grid spacing optimization. - Doubled voltage step resolution: 0.05V step for V < 1V, and V/20 step for V >= 1V. - physics (avalanche bug fix): - Fixed charge sign in Hole Continuity Equation for avalanche generation. - Created AvalancheGeneration_p in physics/new_physics.py to correctly act as a hole source. - Resolved physical breakdown current polarity and negative current leakage at high reverse bias.
509 lines
21 KiB
Python
509 lines
21 KiB
Python
# gui1d/solve_1d.py
|
|
import sys
|
|
import os
|
|
import numpy as np
|
|
import math
|
|
|
|
# Ensure root directory is in the path to import physics and config modules
|
|
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
if ROOT_DIR not in sys.path:
|
|
sys.path.append(ROOT_DIR)
|
|
|
|
import devsim
|
|
from physics.model_create import CreateSolution
|
|
from physics.new_physics import (
|
|
CreateSiliconPotentialOnly,
|
|
CreateSiliconPotentialOnlyContact,
|
|
CreateAroraMobilityLF,
|
|
CreateHFMobility,
|
|
CreateSiliconDriftDiffusion,
|
|
CreateSiliconDriftDiffusionContact,
|
|
CreateAvalancheGeneration
|
|
)
|
|
|
|
# Vectorized complementary error function
|
|
erfc_vec = np.vectorize(math.erfc)
|
|
|
|
# --- Process Database ---
|
|
# Implant Range & Straggle in Silicon (Energy in keV, Rp and dRp in microns)
|
|
IMPLANT_DB = {
|
|
'Boron': {
|
|
'energy': np.array([10.0, 20.0, 30.0, 50.0, 80.0, 100.0, 150.0, 200.0]),
|
|
'Rp': np.array([0.04, 0.07, 0.10, 0.16, 0.24, 0.30, 0.42, 0.55]),
|
|
'dRp': np.array([0.015, 0.025, 0.035, 0.050, 0.063, 0.070, 0.085, 0.100])
|
|
},
|
|
'Phosphorus': {
|
|
'energy': np.array([10.0, 20.0, 30.0, 50.0, 80.0, 100.0, 150.0, 200.0]),
|
|
'Rp': np.array([0.015, 0.028, 0.040, 0.065, 0.100, 0.120, 0.180, 0.240]),
|
|
'dRp': np.array([0.007, 0.012, 0.016, 0.025, 0.038, 0.045, 0.060, 0.075])
|
|
},
|
|
'Arsenic': {
|
|
'energy': np.array([10.0, 20.0, 30.0, 50.0, 80.0, 100.0, 150.0, 200.0]),
|
|
'Rp': np.array([0.010, 0.018, 0.025, 0.038, 0.058, 0.070, 0.100, 0.130]),
|
|
'dRp': np.array([0.004, 0.007, 0.009, 0.014, 0.020, 0.025, 0.035, 0.045])
|
|
}
|
|
}
|
|
|
|
# Diffusion Arrhenius parameters in Silicon (D0 in cm^2/s, Ea in eV)
|
|
DIFFUSION_DB = {
|
|
'Boron': {'D0': 1.0, 'Ea': 3.46},
|
|
'Phosphorus': {'D0': 10.5, 'Ea': 3.69},
|
|
'Arsenic': {'D0': 0.32, 'Ea': 3.56}
|
|
}
|
|
|
|
|
|
def get_implant_params(dopant, energy_kev):
|
|
"""Interpolate Rp and dRp from the database for a given energy in keV."""
|
|
db = IMPLANT_DB.get(dopant, IMPLANT_DB['Boron'])
|
|
e = np.clip(energy_kev, db['energy'][0], db['energy'][-1])
|
|
Rp = np.interp(e, db['energy'], db['Rp'])
|
|
dRp = np.interp(e, db['energy'], db['dRp'])
|
|
return Rp, dRp
|
|
|
|
|
|
def get_diffusion_coefficient(dopant, temp_c):
|
|
"""Calculate diffusion coefficient D in cm^2/s at temperature in °C."""
|
|
params = DIFFUSION_DB.get(dopant, DIFFUSION_DB['Boron'])
|
|
T_kelvin = temp_c + 273.15
|
|
k_B = 8.6173e-5 # eV/K
|
|
D = params['D0'] * np.exp(-params['Ea'] / (k_B * T_kelvin))
|
|
return D
|
|
|
|
|
|
def calc_donors_acceptors(x_um, process_steps, substrate_type, substrate_doping, length):
|
|
"""
|
|
Calculate the separate Donors and Acceptors concentration arrays (cm^-3)
|
|
across a position array x_um (in microns), and also return individual step profiles.
|
|
"""
|
|
donors = np.zeros_like(x_um)
|
|
acceptors = np.zeros_like(x_um)
|
|
step_profiles = []
|
|
|
|
# Initialize substrate baseline
|
|
if substrate_type == 'n':
|
|
donors += substrate_doping
|
|
else:
|
|
acceptors += substrate_doping
|
|
|
|
# Add background contact doping to ensure Ohmic contact at bottom (x = L)
|
|
# Contact doping thickness is 0.5 um, peak is 1e19
|
|
contact_profile = 1e19 * np.exp(-((length - x_um) / 0.5) ** 2)
|
|
if substrate_type == 'n':
|
|
donors += contact_profile
|
|
else:
|
|
acceptors += contact_profile
|
|
|
|
# We also keep Step 0 Substrate profile in step_profiles
|
|
step_profiles.append({
|
|
'name': 'Substrate (Step 0)',
|
|
'type': substrate_type,
|
|
'profile': np.full_like(x_um, substrate_doping)
|
|
})
|
|
|
|
# Compute profiles for each process step
|
|
num_steps = len(process_steps)
|
|
for i, step in enumerate(process_steps):
|
|
if not step.get('enabled', True):
|
|
continue
|
|
|
|
dopant = step.get('dopant', 'Boron')
|
|
type_ = step.get('type', 'p')
|
|
method = step.get('method', 'Implant')
|
|
surface = step.get('surface', 'Top')
|
|
|
|
# Calculate cumulative Dt (in cm^2) for this step's dopant
|
|
Dt_total_cm2 = 0.0
|
|
for j in range(i, num_steps):
|
|
sub_step = process_steps[j]
|
|
if not sub_step.get('enabled', True):
|
|
continue
|
|
t_temp = sub_step.get('temp', 25.0)
|
|
t_time = sub_step.get('time', 0.0) # in minutes
|
|
if t_time > 0 and t_temp > 100.0:
|
|
D_temp = get_diffusion_coefficient(dopant, t_temp)
|
|
Dt_total_cm2 += D_temp * (t_time * 60.0)
|
|
|
|
# Convert Dt to microns^2
|
|
Dt_total_um2 = Dt_total_cm2 * 1e8
|
|
|
|
# Calculate profile
|
|
if method == 'Implant':
|
|
energy = step.get('energy', 80.0)
|
|
dose = step.get('dose', 1e12)
|
|
Rp, dRp = get_implant_params(dopant, energy)
|
|
dRp_eff = np.sqrt(dRp ** 2 + 2 * Dt_total_um2)
|
|
peak_conc = dose / (np.sqrt(2 * np.pi) * (dRp_eff * 1e-4)) # dose in cm^-2, dRp in cm
|
|
|
|
if surface == 'Top':
|
|
prof = peak_conc * (np.exp(-((x_um - Rp) / (np.sqrt(2) * dRp_eff)) ** 2) +
|
|
np.exp(-((x_um + Rp) / (np.sqrt(2) * dRp_eff)) ** 2))
|
|
else:
|
|
prof = peak_conc * (np.exp(-((x_um - (length - Rp)) / (np.sqrt(2) * dRp_eff)) ** 2) +
|
|
np.exp(-((x_um - (length + Rp)) / (np.sqrt(2) * dRp_eff)) ** 2))
|
|
|
|
else: # Constant Source Predeposition
|
|
cs = step.get('cs', 1e19)
|
|
if Dt_total_um2 <= 0.0:
|
|
Dt_total_um2 = 1e-10
|
|
|
|
if surface == 'Top':
|
|
prof = cs * erfc_vec(x_um / (2 * np.sqrt(Dt_total_um2)))
|
|
else:
|
|
prof = cs * erfc_vec((length - x_um) / (2 * np.sqrt(Dt_total_um2)))
|
|
|
|
# Add to Net Donors/Acceptors arrays
|
|
if type_ == 'n':
|
|
donors += prof
|
|
else:
|
|
acceptors += prof
|
|
|
|
# Save this step's final diffused profile
|
|
step_profiles.append({
|
|
'name': f"Step {i+1}: {dopant} {method}",
|
|
'type': type_,
|
|
'profile': prof
|
|
})
|
|
|
|
return donors, acceptors, step_profiles
|
|
|
|
|
|
def find_junction_depths(process_steps, substrate_type, substrate_doping, length):
|
|
"""Locate depth coordinates in microns where the net doping crosses zero."""
|
|
x_test = np.linspace(0, length, 2000)
|
|
donors, acceptors, _ = calc_donors_acceptors(x_test, process_steps, substrate_type, substrate_doping, length)
|
|
doping_test = donors - acceptors
|
|
sign_doping = np.sign(doping_test)
|
|
crossings = np.where(sign_doping[:-1] != sign_doping[1:])[0]
|
|
junctions = []
|
|
for idx in crossings:
|
|
x1, x2 = x_test[idx], x_test[idx+1]
|
|
y1, y2 = doping_test[idx], doping_test[idx+1]
|
|
if abs(y2 - y1) > 1e-20:
|
|
x_cross = x1 - y1 * (x2 - x1) / (y2 - y1)
|
|
junctions.append(x_cross)
|
|
return junctions
|
|
|
|
|
|
def build_and_solve_1d(
|
|
bias_target,
|
|
substrate_type='n',
|
|
substrate_doping=1e14,
|
|
length=30.0,
|
|
process_steps=[],
|
|
enable_avalanche=False,
|
|
area_cm2=1.0
|
|
):
|
|
"""
|
|
Builds a 1D Diode mesh, sets up doping, solves from 0V equilibrium
|
|
to bias_target using a rapid micro-sweep, and returns physical profiles.
|
|
"""
|
|
# 1. Reset DEVSIM state to prevent name collisions
|
|
devsim.reset_devsim()
|
|
|
|
um = 1e-4 # 1 micron = 1e-4 cm
|
|
|
|
device = "device1d"
|
|
region = "Silicon"
|
|
mesh_name = "mesh1d"
|
|
|
|
# 2. Build 1D adaptive mesh
|
|
# Find junctions to refine the mesh around them
|
|
junctions = find_junction_depths(process_steps, substrate_type, substrate_doping, length)
|
|
|
|
# Define control points (x, target spacing)
|
|
control_points = [(0.0, 0.05)]
|
|
for j in junctions:
|
|
control_points.append((j, 0.02)) # Fine mesh around junctions
|
|
control_points.append((length, 0.5)) # Coarser mesh near bottom
|
|
|
|
# Sort control points
|
|
control_points.sort(key=lambda x: x[0])
|
|
|
|
# Construct node list
|
|
all_x = [0.0]
|
|
for idx in range(len(control_points) - 1):
|
|
x_start, sp_start = control_points[idx]
|
|
x_end, sp_end = control_points[idx+1]
|
|
seg_len = x_end - x_start
|
|
if seg_len <= 0:
|
|
continue
|
|
|
|
curr_x = x_start
|
|
while curr_x < x_end:
|
|
t = (curr_x - x_start) / seg_len
|
|
spacing = sp_start + t * (sp_end - sp_start)
|
|
spacing = max(0.01, min(2.0, spacing))
|
|
curr_x += spacing
|
|
if curr_x < x_end - 0.005:
|
|
all_x.append(curr_x)
|
|
all_x.append(x_end)
|
|
|
|
all_x = np.unique(np.array(all_x))
|
|
|
|
# Create mesh in DEVSIM
|
|
devsim.create_1d_mesh(mesh=mesh_name)
|
|
for i, x_val in enumerate(all_x):
|
|
tag = ""
|
|
if i == 0:
|
|
tag = "top"
|
|
elif i == len(all_x) - 1:
|
|
tag = "bottom"
|
|
|
|
if i == 0:
|
|
ps = all_x[1] - all_x[0]
|
|
elif i == len(all_x) - 1:
|
|
ps = all_x[-1] - all_x[-2]
|
|
else:
|
|
ps = min(all_x[i] - all_x[i-1], all_x[i+1] - all_x[i])
|
|
|
|
devsim.add_1d_mesh_line(mesh=mesh_name, pos=x_val * um, ps=ps * um, tag=tag)
|
|
|
|
devsim.add_1d_contact(mesh=mesh_name, name="top", tag="top", material="metal")
|
|
devsim.add_1d_contact(mesh=mesh_name, name="bottom", tag="bottom", material="metal")
|
|
devsim.add_1d_region(mesh=mesh_name, region=region, tag1="top", tag2="bottom", material="Silicon")
|
|
devsim.finalize_mesh(mesh=mesh_name)
|
|
|
|
devsim.create_device(mesh=mesh_name, device=device)
|
|
|
|
# 3. Setup Doping Profile models in DEVSIM
|
|
# Calculate separate donors and acceptors on the grid
|
|
donors_array, acceptors_array, step_profiles = calc_donors_acceptors(all_x, process_steps, substrate_type, substrate_doping, length)
|
|
|
|
# Set Donors directly
|
|
devsim.node_solution(device=device, region=region, name="Donors_data")
|
|
devsim.set_node_values(device=device, region=region, name="Donors_data", values=donors_array)
|
|
devsim.node_model(device=device, region=region, name="Donors", equation="Donors_data")
|
|
|
|
# Set Acceptors directly
|
|
devsim.node_solution(device=device, region=region, name="Acceptors_data")
|
|
devsim.set_node_values(device=device, region=region, name="Acceptors_data", values=acceptors_array)
|
|
devsim.node_model(device=device, region=region, name="Acceptors", equation="Acceptors_data")
|
|
|
|
# Define NetDoping model
|
|
devsim.node_model(device=device, region=region, name="NetDoping", equation="Donors - Acceptors")
|
|
devsim.node_model(device=device, region=region, name="x_um", equation=f"x / {um}")
|
|
|
|
devsim.set_parameter(device=device, name="top_bias", value=0.0)
|
|
devsim.set_parameter(device=device, name="bottom_bias", value=0.0)
|
|
|
|
# 4. Setup Potential-only Equilibrium equations
|
|
CreateSolution(device, region, "Potential")
|
|
CreateSiliconPotentialOnly(device, region)
|
|
CreateSiliconPotentialOnlyContact(device, region, "top")
|
|
CreateSiliconPotentialOnlyContact(device, region, "bottom")
|
|
|
|
# Solve thermal equilibrium (0V)
|
|
devsim.solve(type="dc", absolute_error=1.0, relative_error=1e-10, maximum_iterations=100)
|
|
|
|
# 5. Set up Drift-Diffusion physical models
|
|
CreateSolution(device, region, "Electrons")
|
|
CreateSolution(device, region, "Holes")
|
|
devsim.set_node_values(device=device, region=region, name="Electrons", init_from="IntrinsicElectrons")
|
|
devsim.set_node_values(device=device, region=region, name="Holes", init_from="IntrinsicHoles")
|
|
|
|
# Redefine IntrinsicElectrons, IntrinsicHoles to avoid overflow/underflow
|
|
devsim.node_model(device=device, region=region, name="IntrinsicElectrons", equation="Electrons")
|
|
devsim.node_model(device=device, region=region, name="IntrinsicElectrons:Potential", equation="0")
|
|
devsim.node_model(device=device, region=region, name="IntrinsicElectrons:Electrons", equation="1")
|
|
devsim.node_model(device=device, region=region, name="IntrinsicElectrons:Holes", equation="0")
|
|
|
|
devsim.node_model(device=device, region=region, name="IntrinsicHoles", equation="Holes")
|
|
devsim.node_model(device=device, region=region, name="IntrinsicHoles:Potential", equation="0")
|
|
devsim.node_model(device=device, region=region, name="IntrinsicHoles:Electrons", equation="0")
|
|
devsim.node_model(device=device, region=region, name="IntrinsicHoles:Holes", equation="1")
|
|
|
|
arora_opts = CreateAroraMobilityLF(device, region)
|
|
hf_opts = CreateHFMobility(device, region, **arora_opts)
|
|
CreateSiliconDriftDiffusion(device, region, **hf_opts)
|
|
CreateSiliconDriftDiffusionContact(device, region, "top", hf_opts['Jn'], hf_opts['Jp'])
|
|
CreateSiliconDriftDiffusionContact(device, region, "bottom", hf_opts['Jn'], hf_opts['Jp'])
|
|
|
|
# Override equations
|
|
if enable_avalanche:
|
|
CreateAvalancheGeneration(device, region, hf_opts['Jn'], hf_opts['Jp'])
|
|
devsim.equation(device=device, region=region, name="ElectronContinuityEquation", variable_name="Electrons",
|
|
time_node_model="NCharge", edge_model=hf_opts['Jn'], edge_volume_model="AvalancheGeneration",
|
|
variable_update="positive", node_model="ElectronGeneration", min_error=1e5)
|
|
devsim.equation(device=device, region=region, name="HoleContinuityEquation", variable_name="Holes",
|
|
time_node_model="PCharge", edge_model=hf_opts['Jp'], edge_volume_model="AvalancheGeneration_p",
|
|
variable_update="positive", node_model="HoleGeneration", min_error=1e5)
|
|
else:
|
|
devsim.equation(device=device, region=region, name="ElectronContinuityEquation", variable_name="Electrons",
|
|
time_node_model="NCharge", edge_model=hf_opts['Jn'],
|
|
variable_update="positive", node_model="ElectronGeneration", min_error=1e5)
|
|
devsim.equation(device=device, region=region, name="HoleContinuityEquation", variable_name="Holes",
|
|
time_node_model="PCharge", edge_model=hf_opts['Jp'],
|
|
variable_update="positive", node_model="HoleGeneration", min_error=1e5)
|
|
|
|
devsim.equation(device=device, region=region, name="PotentialEquation", variable_name="Potential",
|
|
node_model="PotentialNodeCharge", edge_model="DField", variable_update="default", min_error=1e-3)
|
|
|
|
# Solve 0V Drift-Diffusion
|
|
devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-8, charge_error=1e12, maximum_iterations=100)
|
|
|
|
# Calculate current density at 0V
|
|
jn_edge_0 = np.array(devsim.get_edge_model_values(device=device, region=region, name=hf_opts['Jn']))
|
|
jp_edge_0 = np.array(devsim.get_edge_model_values(device=device, region=region, name=hf_opts['Jp']))
|
|
jtot_avg_0 = -np.mean(jn_edge_0 + jp_edge_0) # Current flows from right to left
|
|
|
|
v_history = [0.0]
|
|
j_history = [jtot_avg_0]
|
|
|
|
# 6. Adaptive Geometric/Log Micro-Sweep execution to reach bias_target
|
|
v_current = 0.0
|
|
v_target = bias_target
|
|
adaptive_mult = 1.0
|
|
|
|
while abs(v_current) < abs(v_target):
|
|
# Calculate base step size based on current voltage
|
|
abs_v = abs(v_current)
|
|
if abs_v < 1.0:
|
|
step_size = 0.05
|
|
else:
|
|
step_size = abs_v / 20.0
|
|
|
|
# For avalanche sweep, if current starts to rise, limit step size to 2V
|
|
if enable_avalanche and len(j_history) > 0 and j_history[-1] > 1e-4:
|
|
step_size = min(step_size, 2.0)
|
|
|
|
# Apply adaptive multiplier (from convergence failure backtracking)
|
|
step_size *= adaptive_mult
|
|
|
|
# Halt if step size becomes non-physically tiny
|
|
if step_size < 0.005:
|
|
print(f"Step size too small ({step_size:.5f} V) at V = {v_current} V, stopping sweep.")
|
|
break
|
|
|
|
# Determine next voltage
|
|
v_next = v_current + np.sign(v_target) * step_size
|
|
if abs(v_next) > abs(v_target):
|
|
v_next = v_target
|
|
|
|
devsim.set_parameter(device=device, name="top_bias", value=0.0) # Left is reference 0V
|
|
devsim.set_parameter(device=device, name="bottom_bias", value=v_next) # Right is biased
|
|
|
|
try:
|
|
devsim.solve(type="dc", absolute_error=1e10, relative_error=1e-5, charge_error=1e12, maximum_iterations=40)
|
|
v_current = v_next
|
|
|
|
# Record current (flowing from right to left, which is -J)
|
|
jn_edge = np.array(devsim.get_edge_model_values(device=device, region=region, name=hf_opts['Jn']))
|
|
jp_edge = np.array(devsim.get_edge_model_values(device=device, region=region, name=hf_opts['Jp']))
|
|
j_val = -np.mean(jn_edge + jp_edge)
|
|
|
|
v_history.append(v_current)
|
|
j_history.append(j_val)
|
|
|
|
# Check 1mA current limit
|
|
current_ma = j_val * area_cm2 * 1000.0
|
|
if current_ma > 1.0:
|
|
print(f"Current limit 1mA reached at V = {v_current} V ({current_ma:.2f} mA). Stopping sweep.")
|
|
break
|
|
|
|
# If successful, recover the multiplier towards 1.0
|
|
adaptive_mult = min(1.0, adaptive_mult * 1.5)
|
|
|
|
except Exception as e:
|
|
print(f"Sweep convergence failure at V = {v_next} V. Backtracking with smaller step size...")
|
|
adaptive_mult /= 4.0
|
|
# Note: v_current is not updated, so the loop retries from same position with smaller step_size
|
|
|
|
# 7. Collect output profiles
|
|
x_cm = np.array(devsim.get_node_model_values(device=device, region=region, name="x"))
|
|
x_plot = x_cm / um # Convert back to um for plotting
|
|
|
|
potential = np.array(devsim.get_node_model_values(device=device, region=region, name="Potential"))
|
|
electrons = np.array(devsim.get_node_model_values(device=device, region=region, name="Electrons"))
|
|
holes = np.array(devsim.get_node_model_values(device=device, region=region, name="Holes"))
|
|
doping = np.array(devsim.get_node_model_values(device=device, region=region, name="NetDoping"))
|
|
|
|
efn = np.array(devsim.get_node_model_values(device=device, region=region, name="EFN"))
|
|
efp = np.array(devsim.get_node_model_values(device=device, region=region, name="EFP"))
|
|
ec = np.array(devsim.get_node_model_values(device=device, region=region, name="EC"))
|
|
ev = np.array(devsim.get_node_model_values(device=device, region=region, name="EV"))
|
|
|
|
efield_edge = np.array(devsim.get_edge_model_values(device=device, region=region, name="EField"))
|
|
x_edge_mid = (x_plot[:-1] + x_plot[1:]) / 2.0
|
|
|
|
jn_edge = np.array(devsim.get_edge_model_values(device=device, region=region, name=hf_opts['Jn']))
|
|
jp_edge = np.array(devsim.get_edge_model_values(device=device, region=region, name=hf_opts['Jp']))
|
|
jtot_edge = jn_edge + jp_edge
|
|
|
|
g_av_edge = np.zeros_like(efield_edge)
|
|
if enable_avalanche:
|
|
try:
|
|
g_av_edge = np.array(devsim.get_edge_model_values(device=device, region=region, name="AvalancheGeneration"))
|
|
q = 1.6e-19
|
|
g_av_edge = np.abs(g_av_edge) / q
|
|
except Exception:
|
|
pass
|
|
|
|
# Final current density from right to left
|
|
j_avg_rl = -np.mean(jtot_edge)
|
|
peak_efield = np.max(np.abs(efield_edge))
|
|
|
|
is_depleted = np.abs(electrons - holes) < 0.1 * np.abs(doping)
|
|
dep_edges = x_plot[is_depleted]
|
|
if len(dep_edges) > 0:
|
|
w_left = np.min(dep_edges)
|
|
w_right = np.max(dep_edges)
|
|
depletion_width = w_right - w_left
|
|
else:
|
|
w_left, w_right, depletion_width = 0.0, 0.0, 0.0
|
|
|
|
return {
|
|
"v_solved": v_current,
|
|
"x": x_plot,
|
|
"potential": potential,
|
|
"electrons": electrons,
|
|
"holes": holes,
|
|
"doping": doping,
|
|
"efn": efn,
|
|
"efp": efp,
|
|
"ec": ec,
|
|
"ev": ev,
|
|
"x_edge": x_edge_mid,
|
|
"efield": np.abs(efield_edge),
|
|
"jn": jn_edge,
|
|
"jp": jp_edge,
|
|
"jtot": jtot_edge,
|
|
"g_av": g_av_edge,
|
|
"current_density": j_avg_rl,
|
|
"peak_field": peak_efield,
|
|
"depletion_width": depletion_width,
|
|
"depletion_left": w_left,
|
|
"depletion_right": w_right,
|
|
# Sweep history
|
|
"v_history": v_history,
|
|
"j_history": j_history,
|
|
"step_profiles": step_profiles
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import pickle
|
|
import sys
|
|
|
|
if len(sys.argv) < 3:
|
|
print("Usage: solve_1d.py <input_pickle_path> <output_pickle_path>")
|
|
sys.exit(1)
|
|
|
|
input_path = sys.argv[1]
|
|
output_path = sys.argv[2]
|
|
|
|
with open(input_path, 'rb') as f:
|
|
args = pickle.load(f)
|
|
|
|
result = build_and_solve_1d(
|
|
bias_target=args['bias_target'],
|
|
substrate_type=args['substrate_type'],
|
|
substrate_doping=args['substrate_doping'],
|
|
length=args['length'],
|
|
process_steps=args['process_steps'],
|
|
enable_avalanche=args['enable_avalanche'],
|
|
area_cm2=args['area_cm2']
|
|
)
|
|
|
|
with open(output_path, 'wb') as f:
|
|
pickle.dump(result, f)
|