Skip to content

Commit 9c4971c

Browse files
esaruohoclaude
andcommitted
Add MOSFET channel-length modulation and scope trail persistence
- Add lambda parameter to MosfetElm for channel-length modulation (#155). In saturation: Ids = 0.5*beta*(Vgs-Vt)^2*(1+lambda*Vds), Gds = 0.5*beta*(Vgs-Vt)^2*lambda. Default 0 (ideal). - Expose lambda in JfetElm edit dialog (n < 3 threshold). - Add configurable XY trail persistence slider in scope dialog (#157). trailLen 0-49 = varying fade speed, 50 = infinite (no fade). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7755622 commit 9c4971c

4 files changed

Lines changed: 55 additions & 22 deletions

File tree

src/com/lushprojects/circuitjs1/client/JfetElm.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ void getInfo(String arr[]) {
137137
getFetInfo(arr, "JFET");
138138
}
139139
public EditInfo getEditInfo(int n) {
140-
if (n < 2)
140+
if (n < 3)
141141
return super.getEditInfo(n);
142142
return null;
143143
}

src/com/lushprojects/circuitjs1/client/MosfetElm.java

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class MosfetElm extends CircuitElm {
3939
double vt;
4040
// beta = 1/(RdsON*(Vgs-Vt))
4141
double beta;
42+
// channel-length modulation parameter (1/V), 0 = ideal
43+
double lambda;
4244
static int globalFlags;
4345
Diode diodeB1, diodeB2;
4446
double diodeCurrent1, diodeCurrent2, bodyCurrent;
@@ -67,6 +69,7 @@ public MosfetElm(int xa, int ya, int xb, int yb, int f,
6769
try {
6870
vt = new Double(st.nextToken()).doubleValue();
6971
beta = new Double(st.nextToken()).doubleValue();
72+
lambda = new Double(st.nextToken()).doubleValue();
7073
} catch (Exception e) {}
7174
globalFlags = flags & (FLAGS_GLOBAL);
7275
allocNodes(); // make sure volts[] has the right number of elements when hasBodyTerminal() is true
@@ -105,7 +108,7 @@ void reset() {
105108
volts[bodyTerminal] = 0;
106109
}
107110
String dump() {
108-
return super.dump() + " " + vt + " " + beta;
111+
return super.dump() + " " + vt + " " + beta + " " + lambda;
109112
}
110113

111114
void dumpXml(Document doc, Element elem) {
@@ -415,11 +418,14 @@ void calculate(boolean finished) {
415418
Gds = beta*(vgs-vds-vt);
416419
mode = 1;
417420
} else {
418-
// saturation; Gds = 0
419-
gm = beta*(vgs-vt);
421+
// saturation; Gds from channel-length modulation
422+
double vgs_vt = vgs-vt;
423+
gm = beta*vgs_vt*(1+lambda*vds);
424+
ids = .5*beta*vgs_vt*vgs_vt*(1+lambda*vds);
425+
Gds = .5*beta*vgs_vt*vgs_vt*lambda;
420426
// use very small Gds to avoid nonconvergence
421-
Gds = 1e-8;
422-
ids = .5*beta*(vgs-vt)*(vgs-vt) + (vds-(vgs-vt))*Gds;
427+
if (Gds < 1e-8)
428+
Gds = 1e-8;
423429
mode = 2;
424430
}
425431

@@ -457,7 +463,9 @@ void calculate(boolean finished) {
457463
void getFetInfo(String arr[], String n) {
458464
arr[0] = Locale.LS(((pnp == -1) ? "p-" : "n-") + n);
459465
arr[0] += " (Vt=" + getVoltageText(pnp*vt);
460-
arr[0] += ", \u03b2=" + beta + ")";
466+
arr[0] += ", \u03b2=" + beta;
467+
if (lambda > 0) arr[0] += ", \u03bb=" + lambda;
468+
arr[0] += ")";
461469
arr[1] = ((pnp == 1) ? "Ids = " : "Isd = ") + getCurrentText(ids);
462470
arr[2] = "Vgs = " + getVoltageText(volts[0]-volts[pnp == -1 ? 2 : 1]);
463471
arr[3] = ((pnp == 1) ? "Vds = " : "Vsd = ") + getVoltageText(volts[2]-volts[1]);
@@ -485,27 +493,29 @@ public EditInfo getEditInfo(int n) {
485493
return new EditInfo("Threshold Voltage", pnp*vt, .01, 5);
486494
if (n == 1)
487495
return new EditInfo(EditInfo.makeLink("mosfet-beta.html", "Beta"), beta, .01, 5);
488-
if (n == 2) {
496+
if (n == 2)
497+
return new EditInfo("Channel-Length Modulation (1/V)", lambda, 0, 0).setDimensionless();
498+
if (n == 3) {
489499
EditInfo ei = new EditInfo("", 0, -1, -1);
490500
ei.checkbox = new Checkbox("Show Bulk", showBulk());
491501
return ei;
492502
}
493-
if (n == 3) {
503+
if (n == 4) {
494504
EditInfo ei = new EditInfo("", 0, -1, -1);
495505
ei.checkbox = new Checkbox("Swap D/S", (flags & FLAG_FLIP) != 0);
496506
return ei;
497507
}
498-
if (n == 4 && !showBulk()) {
508+
if (n == 5 && !showBulk()) {
499509
EditInfo ei = new EditInfo("", 0, -1, -1);
500510
ei.checkbox = new Checkbox("Digital Symbol", drawDigital());
501511
return ei;
502512
}
503-
if (n == 4 && showBulk()) {
513+
if (n == 5 && showBulk()) {
504514
EditInfo ei = new EditInfo("", 0, -1, -1);
505515
ei.checkbox = new Checkbox("Simulate Body Diode", (flags & FLAG_BODY_DIODE) != 0);
506516
return ei;
507517
}
508-
if (n == 5 && doBodyDiode()) {
518+
if (n == 6 && doBodyDiode()) {
509519
EditInfo ei = new EditInfo("", 0, -1, -1);
510520
ei.checkbox = new Checkbox("Body Terminal", (flags & FLAG_BODY_TERMINAL) != 0);
511521
return ei;
@@ -517,28 +527,30 @@ public void setEditValue(int n, EditInfo ei) {
517527
if (n == 0)
518528
vt = pnp*ei.value;
519529
if (n == 1 && ei.value > 0)
520-
beta = lastBeta = ei.value;
521-
if (n == 2) {
530+
beta = lastBeta = ei.value;
531+
if (n == 2 && ei.value >= 0)
532+
lambda = ei.value;
533+
if (n == 3) {
522534
globalFlags = (!ei.checkbox.getState()) ? (globalFlags|FLAG_HIDE_BULK) :
523535
(globalFlags & ~(FLAG_HIDE_BULK|FLAG_DIGITAL));
524536
// setPoints();
525537
ei.newDialog = true;
526538
}
527-
if (n == 3) {
539+
if (n == 4) {
528540
flags = (ei.checkbox.getState()) ? (flags | FLAG_FLIP) :
529541
(flags & ~FLAG_FLIP);
530542
// setPoints();
531543
}
532-
if (n == 4 && !showBulk()) {
544+
if (n == 5 && !showBulk()) {
533545
globalFlags = (ei.checkbox.getState()) ? (globalFlags|FLAG_DIGITAL) :
534546
(globalFlags & ~FLAG_DIGITAL);
535547
// setPoints();
536548
}
537-
if (n == 4 && showBulk()) {
549+
if (n == 5 && showBulk()) {
538550
flags = ei.changeFlag(flags, FLAG_BODY_DIODE);
539551
ei.newDialog = true;
540552
}
541-
if (n == 5) {
553+
if (n == 6) {
542554
flags = ei.changeFlag(flags, FLAG_BODY_TERMINAL);
543555
}
544556

src/com/lushprojects/circuitjs1/client/Scope.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ class Scope {
241241
Canvas imageCanvas;
242242
Context2d imageContext;
243243
int alphaCounter =0;
244+
int trailLen = 2; // controls XY trail fade speed (higher = longer persistence)
244245
// scopeTimeStep to check if sim timestep has changed from previous value when redrawing
245246
double scopeTimeStep;
246247
double scale[]; // Max value to scale the display to show - indexed for each value of UNITS - e.g. UNITS_V, UNITS_A etc.
@@ -805,8 +806,8 @@ void draw2d(Graphics g) {
805806
g.clipRect(0, 0, rect.width, rect.height);
806807

807808
alphaCounter++;
808-
809-
if (alphaCounter>2) {
809+
810+
if (trailLen < 50 && alphaCounter > trailLen) {
810811
// fade out plot
811812
alphaCounter=0;
812813
imageContext.setGlobalAlpha(0.01);

src/com/lushprojects/circuitjs1/client/ScopePropertiesDialog.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ public class ScopePropertiesDialog extends Dialog implements ValueChangeHandler<
5858
CheckBox elmInfoBox;
5959
TextBox labelTextBox, manualScaleTextBox, divisionsTextBox;
6060
Button applyButton, scaleUpButton, scaleDownButton;
61-
Scrollbar speedBar,positionBar;
61+
Scrollbar speedBar, positionBar, trailBar;
6262
Scope scope;
6363
Grid grid, vScaleGrid, hScaleGrid;
6464
int nx, ny;
65-
Label scopeSpeedLabel, manualScaleLabel,vScaleList, manualScaleId, positionLabel, divisionsLabel;
65+
Label scopeSpeedLabel, manualScaleLabel,vScaleList, manualScaleId, positionLabel, divisionsLabel, trailLabel;
6666
expandingLabel vScaleLabel, hScaleLabel;
6767
Vector <Button> chanButtons = new Vector <Button>();
6868
int plotSelection = 0;
@@ -424,6 +424,19 @@ public void execute() {
424424
viBox.addValueChangeHandler(this);
425425
addItemToGrid(grid, xyBox = new ScopeCheckBox(Locale.LS("Plot X/Y"), "plotxy"));
426426
xyBox.addValueChangeHandler(this);
427+
Grid trailGrid = new Grid(1, 3);
428+
trailGrid.setWidget(0, 0, new Label(Locale.LS("Trail Persistence")));
429+
trailBar = new Scrollbar(Scrollbar.HORIZONTAL, scope.trailLen, 1, 0, 51, new Command() {
430+
public void execute() {
431+
scope.trailLen = trailBar.getValue();
432+
setTrailLabel();
433+
}
434+
});
435+
trailGrid.setWidget(0, 1, trailBar);
436+
trailLabel = new Label("");
437+
trailGrid.setWidget(0, 2, trailLabel);
438+
fp.add(trailGrid);
439+
setTrailLabel();
427440
if (transistor) {
428441
addItemToGrid(grid, vceIcBox = new ScopeCheckBox(Locale.LS("Show Vce vs Ic"), "showvcevsic"));
429442
vceIcBox.addValueChangeHandler(this);
@@ -529,6 +542,13 @@ void updateRowVisibility() {
529542
void setScopeSpeedLabel() {
530543
scopeSpeedLabel.setText(CircuitElm.getUnitText(scope.calcGridStepX(), "s")+"/div");
531544
}
545+
546+
void setTrailLabel() {
547+
if (scope.trailLen >= 50)
548+
trailLabel.setText("max");
549+
else
550+
trailLabel.setText("" + scope.trailLen);
551+
}
532552

533553
void addItemToGrid(Grid g, FocusWidget scb) {
534554
g.setWidget(ny, nx, scb);

0 commit comments

Comments
 (0)