Skip to content

Add MosfetModel class with lambda (channel-length modulation)#313

Open
esaruoho wants to merge 4 commits intopfalstad:devfrom
esaruoho:v3-mosfet-model
Open

Add MosfetModel class with lambda (channel-length modulation)#313
esaruoho wants to merge 4 commits intopfalstad:devfrom
esaruoho:v3-mosfet-model

Conversation

@esaruoho
Copy link
Copy Markdown

@esaruoho esaruoho commented Mar 8, 2026

Summary

  • Adds MosfetModel class following the DiodeModel/TransistorModel pattern — named, reusable MOSFET parameter sets stored in a static HashMap
  • Model parameters: threshold voltage (Vt), beta, lambda (channel-length modulation, 1/V), gate-source capacitance (Cgs), gate-drain capacitance (Cgd)
  • Lambda is applied in both saturation and linear (triode) regions, matching SPICE Level 1:
    • Saturation: Ids = 0.5*β*(Vgs-Vt)²*(1+λ*Vds), with proper gm and Gds derivatives
    • Linear: Ids = β*((Vgs-Vt)*Vds - Vds²/2)*(1+λ*Vds), with full product-rule Gds derivative
  • MosfetElm edit dialog shows model selector with Create/Edit buttons; old circuits without FLAG_MODEL load inline vt/beta as before (full backward compatibility)
  • JfetElm is unaffected — overrides needsModel() to return false (1-line change)
  • Gate capacitance fields (Cgs/Cgd) are stored in the model but companion-model simulation is deferred to a follow-up PR

Changes

File Change
MosfetModel.java New — model class with HashMap registry, text dump (type 36), XML serialization (tag "mm"), Editable interface
EditMosfetModelDialog.java New — model editor dialog (follows EditTransistorModelDialog pattern)
MosfetElm.java Model reference (modelName/model), FLAG_MODEL (128) for backward compat, setup(), dump()/dumpXml()/undumpXml() with model support, lambda in calculate() (both linear and saturation regions), model selector in edit dialog
JfetElm.java needsModel() returns false (1 line)
CircuitLoader.java Register type code 36 → MosfetModel.undumpModel()
XMLDeserializer.java Register tag "mm" → MosfetModel.undumpModelXml()
CirSim.java Add mosfetModelEditDialog static field, MosfetModel.clearDumpedFlags()
CommandManager.java MosfetModel.clearDumpedFlags() in copyOfSelectedElms()
SimulationManager.java MosfetModel.clearDumpedFlags() in getCircuitAsComposite()

Context

This addresses Paul's feedback on PR #266 and #240: "Between this and the lambda change, I think we need mosfet models." The approach follows the established model pattern exactly — DiodeModel for diodes, TransistorModel for BJTs, now MosfetModel for MOSFETs.

The linear-region lambda improvement was informed by independent work from Jonathan Klamroth (@jonnykl), who implemented channel-length modulation in their fork. Their approach applied (1+λ*Vds) in the linear region as well, matching SPICE Level 1. We adopted this improvement with corrected linear-region gm (including the (1+λ*Vds) factor, which their version omitted) and the Gds convergence floor preserved.

Based on v3-dev branch.

Test plan

  • Create new n-MOSFET and p-MOSFET — should use "default" model
  • Open edit dialog — model selector with "default" selected, Create/Edit buttons
  • Create new model with non-zero lambda — verify saturation Ids increases with Vds
  • Verify linear-region behavior with lambda — Ids should also show Vds dependence before saturation
  • Load old circuit files (no FLAG_MODEL) — should read inline vt/beta, no model
  • Save and reload circuit with model — model persists via FLAG_MODEL
  • Create JFET — should show old-style Threshold/Beta fields, no model selector
  • Verify XML save/load round-trip preserves model reference
  • With lambda=0, verify behavior is identical to original (no regression)

🤖 Generated with Claude Code

@pfalstad
Copy link
Copy Markdown
Owner

pfalstad commented Mar 9, 2026

If you load an existing circuit with a MOSFET (example: Circuits->MOSFETs->n-MOSFET) and right click the MOSFET you get an exception.

If you check and then uncheck "Simulate Body Diode" you get the edit model dialog...?

I assume you have another PR coming soon with Cgs/Cgd support so I'd probably wait for that... Otherwise remove Cgs/Cgd from the model details.

@esaruoho
Copy link
Copy Markdown
Author

esaruoho commented Mar 9, 2026

Fixed both bugs and added Cgs/Cgd companion model simulation in the latest push:

Bug fixes:

  • Old circuits without FLAG_MODEL now get "default" model assigned (matching TransistorElm pattern) — this prevents the NPE on right-click and fixes the body diode toggle issue (both caused by null model)

Cgs/Cgd support:

  • Trapezoidal companion model (same approach as BJT junction caps)
  • startIteration() computes geq/ceq from model's capGS/capGD
  • Stamps go in calculate() alongside the FET equations
  • stepFinished() saves cap voltage/current state
  • Gate node marked nonlinear when caps are present
  • getConnection() returns true when caps bridge gate to S/D

The saturable inductor (dL/dt) is already up as PR #308.

esaruoho added a commit to esaruoho/circuitjs1 that referenced this pull request Apr 15, 2026
…nstant model

Replaces the draw-call-counter scope fade with a wall-clock-based
exponential decay. The user setting now means a literal trail
persistence in milliseconds, not an opaque alpha number.

Per @pfalstad on PR pfalstad#240:
- "vary alpha instead of the number of draw calls between fade outs"
- "should look the same regardless of the simulation speed setting"
- "It still seems too persistent even at the lowest setting"

Implementation:

  alpha = 1 - exp(-elapsed_ms / trailPersistence)

This is the analytic solution for an exponential decay with time
constant `trailPersistence`. Same fade rate at 30fps or 144fps,
because elapsed wall-clock time appears in the exponent. The user
sees and sets the time constant directly (default 200 ms).

Range 0..2000 ms. Setting trailPersistence = 0 maps to alpha = 1
(instant erase, no trail at all) so "lowest setting" really is no
trail, addressing the "still too persistent" complaint.

Persisted per-scope as XML attribute "tp"; omitted when at the
default to keep file size small.

The MOSFET lambda part of the original PR pfalstad#240 has been superseded
by PR pfalstad#313 (MosfetModel class), so this PR is now scope-fade-only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@esaruoho esaruoho changed the base branch from v3-dev to dev April 15, 2026 09:50
esaruoho added a commit to esaruoho/circuitjs1 that referenced this pull request Apr 15, 2026
…nstant model

Replaces the draw-call-counter scope fade with a wall-clock-based
exponential decay. The user setting now means a literal trail
persistence in milliseconds, not an opaque alpha number.

Per @pfalstad on PR pfalstad#240:
- "vary alpha instead of the number of draw calls between fade outs"
- "should look the same regardless of the simulation speed setting"
- "It still seems too persistent even at the lowest setting"

Implementation:

  alpha = 1 - exp(-elapsed_ms / trailPersistence)

This is the analytic solution for an exponential decay with time
constant `trailPersistence`. Same fade rate at 30fps or 144fps,
because elapsed wall-clock time appears in the exponent. The user
sees and sets the time constant directly (default 200 ms).

Range 0..2000 ms. Setting trailPersistence = 0 maps to alpha = 1
(instant erase, no trail at all) so "lowest setting" really is no
trail, addressing the "still too persistent" complaint.

Persisted per-scope as XML attribute "tp"; omitted when at the
default to keep file size small.

The MOSFET lambda part of the original PR pfalstad#240 has been superseded
by PR pfalstad#313 (MosfetModel class), so this PR is now scope-fade-only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
esaruoho and others added 4 commits April 15, 2026 13:34
Introduces named, reusable MOSFET models following the DiodeModel/
TransistorModel pattern. Models store threshold voltage (Vt), beta,
lambda (channel-length modulation), and gate capacitance fields
(Cgs/Cgd, for future use).

Key changes:
- MosfetModel.java: new model class with HashMap registry, text/XML
  serialization (type 36, tag "mm"), edit dialog support
- EditMosfetModelDialog.java: model editor dialog
- MosfetElm.java: model reference with FLAG_MODEL backward compat,
  model selector in edit dialog, lambda in saturation calculation
  (ids = 0.5*beta*(Vgs-Vt)^2*(1+lambda*Vds))
- JfetElm.java: needsModel() returns false (JFETs unaffected)
- Registration in CircuitLoader, XMLDeserializer, CirSim,
  CommandManager, SimulationManager

Old circuits without FLAG_MODEL load vt/beta inline as before.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bug fixes:
- Old circuits without FLAG_MODEL now get default model assigned
  (matching TransistorElm pattern), preventing NPE on right-click
- Body diode toggle no longer causes dialog issues with null model

Gate capacitance (Cgs/Cgd) simulation:
- Trapezoidal companion model stamps in doStep via calculate()
- startIteration() computes geq/ceq from model capGS/capGD
- stepFinished() saves cap voltage/current state
- Gate node stamped as nonlinear when caps are present
- getConnection() returns true when caps bridge gate to S/D
- Info display shows Cgs/Cgd values when enabled

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The companion model stamps correct Cgs/Cgd values into the matrix,
but getCurrentIntoNode() was returning 0 for the gate node and
omitting cap currents from source/drain. This caused incorrect
current display (same bug pattern as DiodeElm and TransistorElm).

Gate current = -(Igs + Igd), source gets +Igs, drain gets +Igd,
maintaining KCL compliance across all terminals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ids, gm, and Gds in the linear region now include the (1+lambda*Vds)
factor, matching the SPICE Level 1 MOSFET model. When lambda=0 (the
default), all formulas collapse to the originals with no behavior change.

Linear-region lambda was informed by independent work from Jonathan
Klamroth (@jonnykl) in their fork. Our version additionally includes
the lambda factor in linear-region gm, which their implementation omitted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants