Questions on the transient diode simulation

Hello Juan, thank you for your continued updates for DEVSIM. Recently, I am interested in transient device simulation. I want to use DEVSIM to study the transient characteristic of field effect transistors like MOS. I am trying to learn the given examples like tran_diode.py, but I have some questions:

  1. what is the connection between the “voltage” , “acreal” and “acimag” for the voltage source?What is the expression for a voltage source?:
devsim.circuit_element(
    name="V1", n1=GetContactBiasName("top"), n2=0, value=voltage, acreal=1.0, acimag=0.0
)
  1. In tran_diode.py, I set a small time step to capture the transient current changes following an applied voltage. However, with a very small time step, the initial output current becomes unusually large and appears dependent on the time step size. Additionally, I am not observing the expected transient change in the current. For example, when time_step=1e-9, this issue is particularly noticeable.:
    image
    And smaller time_step=1e-10:
    image
    The code is below:
import devsim
from devsim.python_packages.simple_physics import GetContactBiasName
import diode_common
import csv


def print_circuit_solution():
    for node in devsim.get_circuit_node_list():
        r = devsim.get_circuit_node_value(solution="dcop", node=node)
        print("%s\t%1.15e" % (node, r))


device = "MyDevice"
region = "MyRegion"

# Set extended parameters
devsim.set_parameter(name="extended_solver", value=True)
devsim.set_parameter(name="extended_model", value=True)
devsim.set_parameter(name="extended_equation", value=True)

# This requires a circuit element to integrated current
voltage = 0.0
devsim.circuit_element(
    name="V1", n1=GetContactBiasName("top"), n2=0, value=voltage, acreal=1.0, acimag=0.0
)

diode_common.CreateMesh2(device=device, region=region)
diode_common.SetParameters(device=device, region=region)

# Übergeben der Werte an SetNetDoping
diode_common.SetNetDoping(device=device, region=region)

diode_common.InitialSolution(device, region, circuit_contacts="top")

# Initial DC solution
devsim.solve(type="dc", absolute_error=1.0, relative_error=1e-12, maximum_iterations=30)

diode_common.DriftDiffusionInitialSolution(device, region, circuit_contacts=["top"])

devsim.solve(
    type="transient_dc", absolute_error=1.0, relative_error=1e-14, maximum_iterations=30
)

print_circuit_solution()

devsim.circuit_alter(name="V1", value=0.7)

time_step = 1e-10
total_time = 1e-9
current_time = 0

while current_time < total_time:
    devsim.solve(
        type="transient_bdf1",
        absolute_error=1e10,
        relative_error=1e-10,
        maximum_iterations=30,
        tdelta=time_step,
        charge_error=1,
    )

    print_circuit_solution()
    current_time += time_step

    current_V1I = devsim.get_circuit_node_value(solution="dcop", node="V1.I")
    with open("current_vs_time.csv", mode='a', newline='') as file:
        writer = csv.writer(file)
        writer.writerow([current_time, current_V1I])  

    print(current_time)

And I find that the current reaches stability quickly and seems more dependent on the number of iterations than on time itself. Why?

  1. I’m looking to apply a periodically changing signal, like a simple square wave, to the device to simulate high-frequency operation. Do you have any advice on how to set up this kind of signal in DEVSIM?
    Thank you very much for any suggestions and guidance!

The acreal and acimag options are for small signal simulation, and are not used for dc or transient simulation. For transient simulation you need to adjust the voltage. Please see:

testing/transient_circ.py
testing/transient_circ2.py
testing/transient_circ3.py
testing/transient_rc.py

for additional transient examples.

The current is proportional to the size of the voltage step for any capacitance in the device.;

i = \frac{C \partial v}{\partial t} = C \frac{v\left( t + \Delta t\right) - v\left(t\right)}{\Delta t}

so the bigger the voltage step, the more current. Please note that C is not constant for a diode.

The voltage source alteration and time stepping is controlled by the user. Proper time step selection is needed.

A sine wave, with at several time steps per period is probably the best way to start. For a square wave, with a large ramp. I would suggest several time steps going from low to high to low bias.

For this voltage change operation:

devsim.circuit_alter(name="V1", value=0.7)

Does this voltage change occur only in the first time step? Therefore, the smaller the time step I set, the larger the first output current will be. In subsequent time steps, the voltage does not change (if I don’t make another alteration)?

The initial condition is from:

devsim.solve(
    type="transient_dc", absolute_error=1.0, relative_error=1e-14, maximum_iterations=3
)

This would set the voltage for the next step:

devsim.circuit_alter(name="V1", value=0.7)

Then a statement like this would increase the voltage by 0.7 V over tdelta seconds

devsim.solve(
    type="transient_bdf1",
    absolute_error=1.0,
    relative_error=1e-14,
    maximum_iterations=3,
    tdelta=1e-3,
    charge_error=1e-2,
)

Hello, Juan. Thank you for your kind explanation. I am currently attempting to simulate the MOSFET example in a transient state. However, no matter how fast the pulse is, I am unable to observe the current spike caused by parasitic capacitance. Instead, the transient current behaves almost the same as the steady-state current for the same voltage:


The code is as followed:

import devsim
import csv
from devsim.python_packages.simple_physics import (
    GetContactBiasName,
    SetOxideParameters,
    SetSiliconParameters,
    CreateSiliconPotentialOnly,
    CreateSiliconPotentialOnlyContact,
    CreateSiliconDriftDiffusion,
    CreateSiliconDriftDiffusionAtContact,
    CreateOxidePotentialOnly,
    CreateSiliconOxideInterface,
)
from devsim.python_packages.ramp import rampbias, printAllCurrents
from devsim import (
    element_from_edge_model,
    get_contact_list,
    get_region_list,
    node_model,
    set_node_values,
    set_parameter,
    solve,
    write_devices,
)
from devsim.python_packages.model_create import CreateSolution


import gmsh_mos2d_create  # noqa

device = "mos2d"
silicon_regions = ("gate", "bulk")
oxide_regions = ("oxide",)
regions = ("gate", "bulk", "oxide")
interfaces = ("bulk_oxide", "gate_oxide")

for i in regions:
    CreateSolution(device, i, "Potential")

for i in silicon_regions:
    SetSiliconParameters(device, i, 300)
    CreateSiliconPotentialOnly(device, i)

for i in oxide_regions:
    SetOxideParameters(device, i, 300)
    CreateOxidePotentialOnly(device, i, "log_damp")

### Set up contacts
# contacts = get_contact_list(device=device)

for i in ("gate","body","source"):
    tmp = get_region_list(device=device, contact=i)
    r = tmp[0]
    print("%s %s" % (r, i))
    CreateSiliconPotentialOnlyContact(device, r, i)
    set_parameter(device=device, name=GetContactBiasName(i), value=0.0)


voltage = 0.0
devsim.circuit_element(
    name="V1", n1=GetContactBiasName("drain"), n2=0, value=voltage, acreal=0.0, acimag=0.0
)

CreateSiliconPotentialOnlyContact(device=device,region="bulk",contact="drain",is_circuit="true")


for i in interfaces:
    CreateSiliconOxideInterface(device, i)

solve(type="dc", absolute_error=1.0e-13, relative_error=1e-12, maximum_iterations=30)
solve(type="dc", absolute_error=1.0e-13, relative_error=1e-12, maximum_iterations=30)

print("potential solution successful")

for i in silicon_regions:
    CreateSolution(device, i, "Electrons")
    CreateSolution(device, i, "Holes")
    set_node_values(
        device=device, region=i, name="Electrons", init_from="IntrinsicElectrons"
    )
    set_node_values(device=device, region=i, name="Holes", init_from="IntrinsicHoles")
    CreateSiliconDriftDiffusion(device, i, "mu_n", "mu_p")

for c in ("gate","body","source"):
    tmp = get_region_list(device=device, contact=c)
    r = tmp[0]
    CreateSiliconDriftDiffusionAtContact(device, r, c)

CreateSiliconDriftDiffusionAtContact(device=device,region="bulk",contact="drain",is_circuit="true")


solve(type="transient_dc", absolute_error=1.0e30, relative_error=1e-5, maximum_iterations=30)

print("ini solution successful")


current_time=0

with open("t_I_V_Transient.csv", mode='w', newline='') as file:
    writer = csv.writer(file)
    writer.writerow(["Time (s)", "Voltage (V)", "Current (A)"])

def change_voltage(start_v, end_v, duration, steps, voltage_source_name="V1", current_time=0):
    time_step = duration / steps
    voltage_step = (end_v - start_v) / steps
    
    for i in range(steps):
        new_voltage = start_v + i * voltage_step
        devsim.circuit_alter(name=voltage_source_name, value=new_voltage)

        devsim.solve(
            type="transient_bdf1",
            absolute_error=1e30,
            relative_error=1e-5,
            maximum_iterations=30,
            tdelta=time_step,
            charge_error=1e10,
        )
        
        current_time += time_step
        voltage = devsim.get_circuit_node_value(solution="dcop", node="drain_bias")
        current = devsim.get_circuit_node_value(solution="dcop", node="V1.I")

        with open("t_I_V_Transient.csv", mode='a', newline='') as file:
            writer = csv.writer(file)
            writer.writerow([current_time, voltage, current])
    
    return current_time  

current_time = change_voltage(0.0, 8.0, duration=1e-9, steps=800,voltage_source_name="V1",current_time=current_time)
current_time = change_voltage(8.0, 8.0, duration=1e-7, steps=100,voltage_source_name="V1",current_time=current_time)

write_devices(file="gmsh_mos2d_dd.dat", type="tecplot")

Could you help me identify the issue? Thank you for your valuable time!

Hi @ghost

Looking at your script, is it possible to keep the 1e-9 step for all of your time steps, until you debug the transient behavior. This may help resolve any short time phenomena

Perhaps, also look at changing taun and taup if you are interested in capturing any charge storage in the depletion regions.

Please let me know if this helps.

if you share the script for plotting the csv file I will experiment with it a little this weekend.

Hi Juan, sorry about the earlier plot being done in Origin. I’ve updated the code and used Matplotlib to generate the transient 𝑡−𝐼 curve:

# Copyright 2013 DEVSIM LLC
#
# SPDX-License-Identifier: Apache-2.0
import devsim
import csv
import matplotlib.pyplot as plt
from devsim.python_packages.simple_physics import (
    GetContactBiasName,
    SetOxideParameters,
    SetSiliconParameters,
    CreateSiliconPotentialOnly,
    CreateSiliconPotentialOnlyContact,
    CreateSiliconDriftDiffusion,
    CreateSiliconDriftDiffusionAtContact,
    CreateOxidePotentialOnly,
    CreateSiliconOxideInterface,
)
from devsim.python_packages.ramp import rampbias, printAllCurrents
from devsim import (
    element_from_edge_model,
    get_contact_list,
    get_region_list,
    node_model,
    set_node_values,
    set_parameter,
    solve,
    write_devices,
)
from devsim.python_packages.model_create import CreateSolution


import gmsh_mos2d_create  # noqa

device = "mos2d"
silicon_regions = ("gate", "bulk")
oxide_regions = ("oxide",)
regions = ("gate", "bulk", "oxide")
interfaces = ("bulk_oxide", "gate_oxide")

for i in regions:
    CreateSolution(device, i, "Potential")

for i in silicon_regions:
    SetSiliconParameters(device, i, 300)
    CreateSiliconPotentialOnly(device, i)

for i in oxide_regions:
    SetOxideParameters(device, i, 300)
    CreateOxidePotentialOnly(device, i, "log_damp")

### Set up contacts
# contacts = get_contact_list(device=device)

for i in ("gate","body","source"):
    tmp = get_region_list(device=device, contact=i)
    r = tmp[0]
    print("%s %s" % (r, i))
    CreateSiliconPotentialOnlyContact(device, r, i)
    set_parameter(device=device, name=GetContactBiasName(i), value=0.0)


voltage = 0.0
devsim.circuit_element(
    name="V1", n1=GetContactBiasName("drain"), n2=0, value=voltage, acreal=0.0, acimag=0.0
)

CreateSiliconPotentialOnlyContact(device=device,region="bulk",contact="drain",is_circuit="true")


for i in interfaces:
    CreateSiliconOxideInterface(device, i)

solve(type="dc", absolute_error=1.0e-13, relative_error=1e-12, maximum_iterations=30)
solve(type="dc", absolute_error=1.0e-13, relative_error=1e-12, maximum_iterations=30)

print("potential solution successful")

for i in silicon_regions:
    CreateSolution(device, i, "Electrons")
    CreateSolution(device, i, "Holes")
    set_node_values(
        device=device, region=i, name="Electrons", init_from="IntrinsicElectrons"
    )
    set_node_values(device=device, region=i, name="Holes", init_from="IntrinsicHoles")
    CreateSiliconDriftDiffusion(device, i, "mu_n", "mu_p")

for c in ("gate","body","source"):
    tmp = get_region_list(device=device, contact=c)
    r = tmp[0]
    CreateSiliconDriftDiffusionAtContact(device, r, c)

CreateSiliconDriftDiffusionAtContact(device=device,region="bulk",contact="drain",is_circuit="true")


solve(type="transient_dc", absolute_error=1.0e30, relative_error=1e-5, maximum_iterations=30)

print("ini solution successful")


current_time=0
simulation_data = []

def change_voltage(start_v, end_v, duration, steps, voltage_source_name="V1", current_time=0):
    time_step = duration / steps
    voltage_step = (end_v - start_v) / steps
    
    for i in range(steps):
        new_voltage = start_v + i * voltage_step
        devsim.circuit_alter(name=voltage_source_name, value=new_voltage)

        devsim.solve(
            type="transient_bdf1",
            absolute_error=1e30,
            relative_error=1e-5,
            maximum_iterations=30,
            tdelta=time_step,
            charge_error=1e10,
        )
        
        current_time += time_step
        voltage = devsim.get_circuit_node_value(solution="dcop", node="drain_bias")
        current = devsim.get_circuit_node_value(solution="dcop", node="V1.I")

        simulation_data.append((current_time, voltage, current))
        with open("t_I_V_Transient.csv", mode='a', newline='') as file:
            writer = csv.writer(file)
            writer.writerow([current_time, voltage, current])
    
    return current_time  

with open("t_I_V_Transient.csv", mode='w', newline='') as file:
    writer = csv.writer(file)
    writer.writerow(["Time (s)", "Voltage (V)", "Current (A)"])

current_time = change_voltage(0.0, 8.0, duration=1e-9, steps=800, voltage_source_name="V1", current_time=current_time)
current_time = change_voltage(8.0, 8.0, duration=1e-9, steps=100, voltage_source_name="V1", current_time=current_time)

def plot_t_I_curve(data):
    time = [entry[0] for entry in data]
    current = [entry[2] for entry in data]

    plt.figure(figsize=(8, 6))
    plt.plot(time, current, label="Current (I)", marker='o', markersize=2, linestyle='-')
    plt.xlabel("Time (s)")
    plt.ylabel("Current (A)")
    plt.title("Time vs Current")
    plt.grid(True)
    plt.legend()
    plt.savefig("t_I_curve.png", dpi=300)
    plt.show()

# Plot using the in-memory simulation_data
plot_t_I_curve(simulation_data)

write_devices(file="gmsh_mos2d_dd.dat", type="tecplot")


If there is parasitic capacitance , the current at the location marked by the red circle in the figure above should initially be significantly larger than the steady-state value observed later?

Hi @ghost

Hope you are well. Your intuition about the behavior is better than mine. It really looks to be dependent on time step taken upon reaching the 1 nS mark.

current_time = change_voltage(0.0, 8.0, duration=1e-9, steps=800, voltage_source_name="V1", current_time=current_time)
current_time = change_voltage(8.0, 8.0, duration=1e-14, steps=1000, voltage_source_name="V1", current_time=current_time)
current_time = change_voltage(8.0, 8.0, duration=1e-12, steps=100, voltage_source_name="V1", current_time=current_time)

so time step variation may be needed to capture the dynamics with a reasonable simulation time.


Hello Juan,thank you for your help. The switching characteristic time should be on the nanosecond scale. I guess the parasitic capacitance of the simulated device is very small, which makes the transient characteristics more noticeable only during extremely fast voltage changes.

1 Like