TCAD guide¶
gsim.tcad solves Poisson and electron/hole drift-diffusion on a 2D cross-section of a doped silicon device with
DEVSIM. Each bias point returns the carrier concentrations, the terminal currents and the
small-signal capacitance and admittance of the swept contact. The cross-section is meshed by the same native-2D pipeline
as the Palace mode solver, so the charge solve and the RF and optical solves see the same mesh.
Use Simulation to configure and solve, and BiasSweepResult to
read the result. The PN junction notebook walks through a complete C(V) extraction.
Install¶
DEVSIM is optional. Install the tcad extra:
It brings devsim and scipy-openblas32, which gives DEVSIM a BLAS/LAPACK library on Linux, Windows and macOS. On
macOS it needs macOS 15 or later on Apple silicon, the only macOS wheel DEVSIM ships. import gsim.tcad works without
DEVSIM; solve() and sweep() raise an error naming the extra.
Configure and solve¶
The simulation is set up by physical concern, like gsim.fdtd.Simulation:
from gsim import tcad
sim = tcad.Simulation()
sim.geometry(component, stack=stack)
sim.domain(plane="x=0", window=(-25.0, -15.0), margin_x=3.0, margin_y=3.0, z_above=2.0, z_below=1.0)
sim.materials(temperature_k=300.0, mobility=tcad.MobilityModel.masetti_silicon())
sim.doping.add(tcad.StepDoping(region="p_rib", dopant_type="acceptor", concentration_cm3=1e18))
sim.doping.add(tcad.StepDoping(region="n_rib", dopant_type="donor", concentration_cm3=1e18))
sim.contacts.add(name="anode", semiconductor="p_rib", electrode="metal_p")
sim.contacts.add(name="cathode", semiconductor="n_rib", electrode="metal_n")
sim.interfaces.add(name="junction", region_a="p_rib", region_b="n_rib")
sim.solver(mesh_preset="coarse", refined_mesh_size_um=0.05, relative_error=1e-8)
sim.mesh(output_dir="./tcad")
point = sim.solve(1.0, contact="cathode") # BiasPoint
sweep = sim.sweep([0.0, 0.5, 1.0, 2.0], contact="cathode") # BiasSweepResult
A positive bias on the n-side contact reverse-biases the junction. The other contacts are at 0 V: one swept by an
earlier solve is ramped back first. Between bias points the swept contact is ramped in solver.bias_step_v steps, and
a bias must be a finite voltage.
Calling a concern applies several settings at once and validates them together; a failed call changes nothing. Single
attributes can also be assigned, sim.solver.relative_error = 1e-8, and a whole concern replaced by a model or a mapping,
sim.materials = {"temperature_k": 350.0}. The constructor takes the same concerns as keyword arguments:
sim = tcad.Simulation(
domain={"plane": "x=0", "window": (-25.0, -15.0)},
doping=[tcad.StepDoping(region="p_rib", dopant_type="acceptor", concentration_cm3=1e18)],
contacts=[{"name": "anode", "semiconductor": "p_rib", "electrode": "metal_p"}],
solver={"mesh_preset": "coarse"},
)
Concerns¶
Lengths are in µm, concentrations in cm⁻³, voltages in V, frequencies in Hz and temperatures in K. Field names carry their unit.
geometry(component, *, stack=None): the gdsfactory component and its layer stack, aLayerStack, a stack YAML path, orNonefor the active PDK.domain(...):plane("x=<um>"or"y=<um>");windowandwindow_z,(min, max)clips of the domain in µm; the airboxmargin_x,margin_y,z_above,z_belowin µm.materials(...):temperature_k(300 K);semiconductor, the DEVSIM material of the doped Regions ("Silicon");mobility, aMobilityModel(Masetti silicon);insulators, Region to static relative permittivity.doping: the doping profiles, withadd(profile),remove(profile),clear(), iteration andlen().contacts:add(name=, semiconductor=, electrode=),remove(name),sim.contacts[name], iteration.interfaces:add(name=, region_a=, region_b=),remove(name),sim.interfaces[name], iteration.solver(...): mesh resolution (mesh_preset,refined_mesh_size_um,max_mesh_size_um,refinement_boxes), Newton convergence (absolute_error,relative_error,max_iterations,bias_step_v,extended_precision), the small-signal frequencies (small_signal_freq_hz,junction_freq_hz) andclaim_devsim_namespace.
Doping¶
A profile names the mesh Region it applies to and its dopant type. StepDoping is uniform inside an optional box,
GaussianDoping and ImplantDoping are analytic implants, TableDoping interpolates a sampled profile (a SIMS depth
profile, a process simulator's output) and CallableDoping evaluates a function of (x, y) in µm. Profiles on the same
Region add up; donors and acceptors compensate in the net doping and add up as scatterers in the mobility.
Contacts, interfaces and insulators¶
Every Region of the device takes one role, and the declarations state it:
- A doped Region carries at least one doping profile. It holds the potential and both carrier densities.
- A Contact is where an electrode meets a doped Region: an ohmic contact whose bias the solve sets.
semiconductoris the doped Region it injects into;electrodeis the Region on the other side, the metal or any Region that is neither doped nor insulating. - An Interface is where two doped Regions meet. Potential and carriers are continuous across it; it carries no bias. Declare one between every pair of touching doped Regions: two touching doped Regions without one are refused, since no carrier would cross between them.
- An insulator is a Region in
materials.insulators. It joins the electrostatic solve with its own permittivity and holds no carriers. Without any, Poisson is solved in the doped silicon alone and its boundary is a zero-normal-field wall; with the oxide declared, the field may fringe around the junction through it. The interfaces an insulator shares with its neighbours are bound automatically.
A Region is doped or insulating, never both. A Contact against a declared insulator is refused: one curve cannot be both a Contact and a semiconductor-insulator interface.
Each Contact and Interface becomes a line group of that name on the mesh, so contact and interface names are unique across both.
Mesh, then solve¶
mesh(output_dir=...) writes the native-2D mesh in µm and its copy in cm that DEVSIM loads, and returns a
TCADMesh with both paths and the mesh's physical groups. output_dir is required on the first
call and remembered afterwards. Resolution comes from the solver concern.
The DEVSIM device is built on the first solve, and mesh() is never called implicitly. What a later change invalidates
depends on the concern:
- Mesh inputs:
geometry,domain,contacts,interfaces, and the solver's mesh fields.mesh()records them. A solve after any of them changed raisesValueError("... changed since mesh(); call mesh() again"). - Device inputs:
materials,doping, and the other solver fields. A change drops the DEVSIM device and its bias state; the next solve builds it again on the existing mesh.
Results¶
A DEVSIM 2D device is 1 cm deep, so extensive quantities are per cm of device length; each has a per-metre counterpart.
sweep.voltages # V
sweep.capacitance_f_per_cm # small-signal C(V)
sweep.capacitance_f_per_m
sweep.admittance_s_per_cm # complex admittance at solver.junction_freq_hz
sweep.junction_branch() # series-RC fit: r_s_ohm_m, c_j_f_per_m
point = sweep.points[-1]
point.currents_a_per_cm # DC terminal current per contact
point.carriers.electrons_cm3, point.carriers.holes_cm3 # per mesh node
point.carriers.x_um, point.carriers.y_um # in-plane and vertical node coordinates
The capacitance comes from an AC solve at the quasi-static small_signal_freq_hz. The admittance comes from a second AC
solve at junction_freq_hz, high enough that the capacitive current dominates the junction's DC leakage and far below
the branch's own RC rolloff, so junction_branch() reads the series resistance of the doped slab and the junction
capacitance a loaded-line RF model needs.
tcad.compare_capacitance evaluates the depletion-approximation capacitance (Sze) at the same biases and reports the
relative deviation; tcad.estimate_depletion_width_um reads the depletion width off a 1D cut of the carrier map. The
comparison uses the solve's own silicon constants (below) for whatever the PNJunctionConfig leaves at its defaults.
Silicon constants¶
The solve takes DEVSIM's silicon parameters, with one change: the intrinsic carrier density follows the temperature.
- Permittivity: static relative permittivity 11.1, DEVSIM's value
(
gsim.tcad.silicon.SILICON_RELATIVE_PERMITTIVITY). The RF and optical stacks use 11.9 for silicon. - Intrinsic density:
n_i(T) = n_i(300 K) (T/300)^1.5 exp(E_g(300)/2kT_300 - E_g(T)/2kT), with Varshni's band gap and DEVSIM'sn_i(300 K) = 1e10cm⁻³ (gsim.tcad.silicon.intrinsic_density_cm3). A 300 K solve is DEVSIM's own; away from 300 K the built-in voltage falls with temperature as it should. - Temperature:
materials.temperature_ksets the thermal voltage, the intrinsic density and the SRH trap densities. The Masetti mobility is its 300 K fit at every temperature.
Limits¶
- DEVSIM keeps meshes, devices and its circuit in one process-wide namespace. By default each device setup claims it
(
solver.claim_devsim_namespace), so one charge-transport device is live at a time. - The physics is DEVSIM's Scharfetter-Gummel drift-diffusion for silicon, with Shockley-Read-Hall recombination and the
doping-dependent mobility of
materials.mobility. There is no field-dependent mobility (velocity saturation), no impact ionisation and no quantum correction. - Contacts, interfaces and insulators are declared, never inferred from the layer stack.