Chris Parmer — home

MOSFET I-V curves

Device Characterization — SPICE Level-1 model, computed

Example from the compendium of canonical charts

Device Characterization — MOSFET I-V curves (SPICE Level-1 model, computed)

Python Code

"""Device Characterization — MOSFET I-V curves (SPICE Level-1 model, computed)."""


import numpy as np
import plotly.graph_objects as go


def generate():
    # SPICE Level-1 MOSFET parameters
    k   = 1e-3   # process gain (A/V²)
    Vth = 1.0    # threshold voltage (V)
    lam = 0.02   # channel-length modulation (V⁻¹)

    VGS_vals = [1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0]
    VDS = np.linspace(0, 5, 200)

    colors = [
        "#845EEE", "#55B685", "#E9A23B", "#DA5597", "#52B3D0",
        "#a07fd6", "#3d9c6e", "#c87c22",
    ]

    fig = go.Figure()

    for idx, vgs in enumerate(VGS_vals):
        ID = np.zeros_like(VDS)
        vov = vgs - Vth  # overdrive voltage

        if vov <= 0:
            # Cutoff: ID = 0 everywhere
            pass
        else:
            for j, vds in enumerate(VDS):
                if vds < vov:
                    # Triode (linear) region
                    ID[j] = k * (vov * vds - vds**2 / 2)
                else:
                    # Saturation region
                    ID[j] = (k / 2) * vov**2 * (1 + lam * vds)

        # Convert A → mA
        ID_mA = ID * 1e3

        fig.add_trace(go.Scatter(
            x=VDS,
            y=ID_mA,
            mode="lines",
            name=f"V<sub>GS</sub> = {vgs:.1f} V",
            line=dict(color=colors[idx % len(colors)], width=2),
            hovertemplate=f"VGS={vgs:.1f}V<br>VDS=%{{x:.2f}}V<br>ID=%{{y:.2f}}mA<extra></extra>",
        ))

    # Boundary between triode and saturation: VDS = VGS - Vth
    vgs_arr = np.array(VGS_vals)
    vds_sat = vgs_arr - Vth
    id_sat_mA = (k / 2) * (vgs_arr - Vth)**2 * 1e3

    # Only show for VGS > Vth
    mask = vgs_arr > Vth
    fig.add_trace(go.Scatter(
        x=vds_sat[mask],
        y=id_sat_mA[mask],
        mode="lines",
        line=dict(color="gray", width=1.5, dash="dot"),
        name="Saturation boundary",
        hovertemplate="VDS<sub>sat</sub>=%{x:.2f}V<br>ID=%{y:.2f}mA<extra></extra>",
    ))

    # Annotations for regions
    fig.add_annotation(
        x=0.5, y=6.5,
        text="Triode",
        showarrow=False,
        font=dict(size=11, color="gray"),
    )
    fig.add_annotation(
        x=3.0, y=6.5,
        text="Saturation",
        showarrow=False,
        font=dict(size=11, color="gray"),
    )

    fig.update_layout(
        xaxis=dict(title="V<sub>DS</sub> (V)", range=[0, 5]),
        yaxis=dict(title="I<sub>D</sub> (mA)", range=[0, None]),
        legend=dict(
            x=1.02, y=1.0,
            xanchor="left",
            font=dict(size=10),
        ),
        margin=dict(t=20, b=60, l=70, r=140),
    )

    return fig


if __name__ == "__main__":
    generate()

Made with Plotly