diff --git a/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionSimulationTests.cs b/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionSimulationTests.cs new file mode 100644 index 00000000..1a360cfe --- /dev/null +++ b/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionSimulationTests.cs @@ -0,0 +1,398 @@ +using System; +using Xunit; + +namespace SpiceSharpParser.IntegrationTests.Components +{ + /// + /// Integration tests with simulations for model selection based on L and W parameters. + /// These tests verify that the correct model is selected by running actual circuit simulations. + /// + public class ModelDimensionSimulationTests : BaseTests + { + #region Resistor Simulation Tests + + [Fact] + public void ResistorModelSelectionAffectsSimulationResults() + { + // Model with RSH=100 ohm/square, L/W range for small resistors + // Model with RSH=1000 ohm/square, L/W range for large resistors + // R = RSH * L / W + var netlist = GetSpiceSharpModel( + "Resistor model selection affects resistance value", + "V1 IN 0 10", + "R1 IN 0 RMOD L=1u W=1u", + ".model RMOD.0 R RSH=100 lmin=0.1u lmax=5u", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=1u, W=1u -> should use RMOD.0 (RSH=100) -> R = 100 * 1 / 1 = 100 ohms + // Expected current: 10V / 100 ohm = 0.1 A + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(0.1, Math.Abs(current1)), $"R1 current expected ~0.1A, got {current1}"); + } + + [Fact] + public void ResistorWidthParameterAffectsModelSelection() + { + var netlist = GetSpiceSharpModel( + "Resistor width affects model selection", + "V1 IN 0 5", + "R1 IN 0 RMOD L=2u W=2u", + ".model RMOD.0 R RSH=50 wmin=1u wmax=10u", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=2u, W=2u -> RMOD.0 (RSH=50) -> R = 50 * 2 / 2 = 50 ohms -> I = 5V / 50 = 0.1 A + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(0.1, Math.Abs(current1)), $"R1 current expected ~0.1A, got {current1}"); + } + + [Fact] + public void ResistorFallsBackToDefaultModelSimulation() + { + var netlist = GetSpiceSharpModel( + "Resistor falls back to default model when dimensions don't match", + "V1 IN 0 10", + "R1 IN 0 RMOD L=0.5u W=1u", + ".model RMOD.0 R RSH=100 lmin=1u lmax=10u", + ".model RMOD R RSH=500", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=0.5u (< lmin=1u) -> should use RMOD (default, RSH=500) -> R = 500 * 0.5 / 1 = 250 ohms + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(10.0 / 250.0, Math.Abs(current1)), $"R1 current expected ~0.04A, got {current1}"); + } + + [Fact] + public void ResistorWithBothLAndWConstraintsSimulation() + { + var netlist = GetSpiceSharpModel( + "Resistor with both L and W constraints", + "V1 IN 0 12", + "R1 IN 0 RMOD L=1u W=2u", + ".model RMOD.0 R RSH=60 lmin=0.5u lmax=5u wmin=1u wmax=10u", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=1u, W=2u -> RMOD.0 (RSH=60) -> R = 60 * 1 / 2 = 30 ohms -> I = 12V / 30 = 0.4 A + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(0.4, Math.Abs(current1)), $"R1 current expected ~0.4A, got {current1}"); + } + + #endregion + + #region Capacitor Simulation Tests + + [Fact] + public void CapacitorModelSelectionAffectsSimulationResults() + { + // Capacitance C = CJ * L * W (approximately for semiconductor capacitors) + var netlist = GetSpiceSharpModel( + "Capacitor model selection affects capacitance value", + "V1 IN 0 PULSE(0 5 0 1n 1n 10n 20n)", + "R1 IN OUT1 1k", + "C1 OUT1 0 CMOD L=2u W=2u", + ".model CMOD.0 C CJ=1e-6 lmin=0.5u lmax=5u wmin=0.5u wmax=5u", + ".TRAN 1n 30n", + ".SAVE V(OUT1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports1 = RunTransientSimulation(netlist, "V(OUT1)"); + Assert.NotNull(exports1); + Assert.True(exports1.Length > 0); + } + + [Fact] + public void CapacitorWidthParameterAffectsModelSelection() + { + var netlist = GetSpiceSharpModel( + "Capacitor width affects model selection", + "V1 IN 0 PULSE(0 10 0 1n 1n 20n 40n)", + "R1 IN OUT1 1k", + "C1 OUT1 0 CMOD L=1u W=3u", + ".model CMOD.0 C CJ=5e-7 wmin=1u wmax=10u", + ".TRAN 1n 40n", + ".SAVE V(OUT1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports1 = RunTransientSimulation(netlist, "V(OUT1)"); + Assert.NotNull(exports1); + Assert.True(exports1.Length > 0); + } + + [Fact] + public void CapacitorFallsBackToDefaultModelSimulation() + { + var netlist = GetSpiceSharpModel( + "Capacitor falls back to default model", + "V1 IN 0 PULSE(0 5 0 1n 1n 10n 20n)", + "R1 IN OUT1 1k", + "C1 OUT1 0 CMOD L=0.3u W=1u", + ".model CMOD.0 C CJ=1e-6 lmin=1u lmax=3u", + ".model CMOD C CJ=5e-7", + ".TRAN 1n 30n", + ".SAVE V(OUT1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports1 = RunTransientSimulation(netlist, "V(OUT1)"); + Assert.NotNull(exports1); + Assert.True(exports1.Length > 0); + } + + #endregion + + #region Inductor Simulation Tests + + [Fact] + public void InductorWithLengthAndWidthParameters() + { + // Basic test to verify inductors accept L and W parameters + var netlist = GetSpiceSharpModel( + "Inductor with L and W parameters", + "V1 IN 0 PULSE(0 1 0 1n 1n 10n 20n)", + "R1 IN OUT 10", + "L1 OUT 0 1u", + ".TRAN 1n 30n", + ".SAVE V(OUT) I(L1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports = RunTransientSimulation(netlist, "I(L1)"); + Assert.NotNull(exports); + Assert.True(exports.Length > 0); + } + + [Fact] + public void InductorBasicBehavior() + { + // Test basic RL circuit behavior + var netlist = GetSpiceSharpModel( + "RL circuit transient response", + "V1 IN 0 PULSE(0 10 0 1n 1n 50n 100n)", + "R1 IN OUT 100", + "L1 OUT 0 1u", + ".TRAN 0.5n 60n", + ".SAVE I(L1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports = RunTransientSimulation(netlist, "I(L1)"); + + // Current through inductor should gradually rise (not instantaneous) + // At t=0, current should be ~0 + Assert.True(Math.Abs(exports[0].Item2) < 0.01, $"Initial current should be ~0, got {exports[0].Item2}"); + + // Current should increase over time + var index30n = 60; // Approximate index for 30ns (0.5ns steps) + if (index30n < exports.Length) + { + Assert.True(exports[index30n].Item2 > exports[10].Item2, + $"Current should increase: I(t=5ns)={exports[10].Item2}, I(t=30ns)={exports[index30n].Item2}"); + } + } + + [Fact] + public void InductorComparisonDifferentValues() + { + // Compare two inductors with different inductance values + var netlist = GetSpiceSharpModel( + "Compare inductors with different values", + "V1 IN 0 PULSE(0 10 0 1n 1n 50n 100n)", + "R1 IN OUT1 100", + "L1 OUT1 0 1u", + "R2 IN OUT2 100", + "L2 OUT2 0 10u", + ".TRAN 0.5n 60n", + ".SAVE I(L1) I(L2)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports1 = RunTransientSimulation(netlist, "I(L1)"); + var exports2 = RunTransientSimulation(netlist, "I(L2)"); + + // Smaller inductance (L1=1u) should reach steady state faster than larger (L2=10u) + var index20n = 40; // Approximate index for 20ns + if (index20n < exports1.Length && index20n < exports2.Length) + { + Assert.True(exports1[index20n].Item2 > exports2[index20n].Item2, + $"Smaller inductor should have higher current earlier: I(L1)={exports1[index20n].Item2}, I(L2)={exports2[index20n].Item2}"); + } + } + + #endregion + + #region Combined Component Tests + + [Fact] + public void RLCCircuitWithDimensionBasedModels() + { + var netlist = GetSpiceSharpModel( + "RLC circuit with dimension-based component models", + "V1 IN 0 PULSE(0 5 0 1n 1n 20n 40n)", + "R1 IN N1 RMOD L=2u W=1u", + "L1 N1 N2 10u", + "C1 N2 0 1p", + ".model RMOD.0 R RSH=50 lmin=1u lmax=10u", + ".TRAN 0.5n 50n", + ".SAVE V(N2)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports = RunTransientSimulation(netlist, "V(N2)"); + + // Verify simulation runs and produces results + Assert.NotNull(exports); + Assert.True(exports.Length > 0); + + // Verify circuit responds to input (voltage should change from 0) + var maxVoltage = 0.0; + foreach (var v in exports) + { + if (Math.Abs(v.Item2) > maxVoltage) + maxVoltage = Math.Abs(v.Item2); + } + Assert.True(maxVoltage > 0.1, $"Circuit should respond to input, max voltage: {maxVoltage}"); + } + + [Fact] + public void MultipleResistorsWithDifferentModelSelection() + { + var netlist = GetSpiceSharpModel( + "Voltage divider with different resistor models", + "V1 IN 0 10", + "R1 IN MID RMOD L=1u W=1u", + "R2 MID 0 RMOD L=10u W=1u", + ".model RMOD.0 R RSH=100 lmin=0.5u lmax=5u", + ".model RMOD.1 R RSH=1000 lmin=5u lmax=50u", + ".OP", + ".SAVE V(MID)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=1u, W=1u -> RMOD.0 (RSH=100) -> R1 = 100 * 1 / 1 = 100 ohms + // R2: L=10u, W=1u -> RMOD.1 (RSH=1000) -> R2 = 1000 * 10 / 1 = 10000 ohms + // Voltage divider: V(MID) = 10V * R2 / (R1 + R2) = 10 * 10000 / 10100 ≈ 9.9 V + + var voltage = RunOpSimulation(netlist, "V(MID)"); + var expected = 9.9; + var tolerance = 0.1; + Assert.True(Math.Abs(expected - voltage) < tolerance, $"V(MID) expected ~{expected}V, got {voltage}"); + } + + [Fact] + public void CapacitorChargeDischargeWithModelSelection() + { + var netlist = GetSpiceSharpModel( + "Capacitor charge/discharge with model selection", + "V1 IN 0 PULSE(0 10 0 1n 1n 50n 100n)", + "R1 IN OUT 1k", + "C1 OUT 0 CMOD L=3u W=3u", + ".model CMOD.0 C CJ=1e-6 lmin=1u lmax=10u wmin=1u wmax=10u", + ".TRAN 1n 80n", + ".SAVE V(OUT)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports = RunTransientSimulation(netlist, "V(OUT)"); + + // Verify charging behavior + Assert.True(exports[0].Item2 < 1.0, "Initial voltage should be low"); + + // Find peak during pulse + var peakVoltage = 0.0; + for (int i = 10; i < Math.Min(50, exports.Length); i++) + { + if (exports[i].Item2 > peakVoltage) + peakVoltage = exports[i].Item2; + } + + Assert.True(peakVoltage > 5.0, $"Capacitor should charge significantly, peak: {peakVoltage}V"); + } + + #endregion + + #region Edge Case Simulation Tests + + [Fact] + public void ResistorWithLminBoundaryCondition() + { + var netlist = GetSpiceSharpModel( + "Resistor at lmin boundary", + "V1 IN 0 10", + "R1 IN 0 RMOD L=1.01u W=1u", + ".model RMOD.0 R RSH=100 lmin=1u", + ".model RMOD R RSH=200", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=1.01u (>= lmin) -> should use RMOD.0 (RSH=100) -> R = 100 * 1.01 / 1 = 101 ohms + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(10.0 / 101.0, Math.Abs(current1)), $"R1 current expected ~0.099A, got {current1}"); + } + + [Fact] + public void ResistorWithLmaxBoundaryCondition() + { + var netlist = GetSpiceSharpModel( + "Resistor at lmax boundary", + "V1 IN 0 10", + "R1 IN 0 RMOD L=9.99u W=1u", + ".model RMOD.0 R RSH=100 lmax=10u", + ".model RMOD R RSH=200", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=9.99u (<= lmax) -> should use RMOD.0 (RSH=100) -> R = 100 * 9.99 / 1 = 999 ohms + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(10.0 / 999.0, Math.Abs(current1)), $"R1 current expected ~0.01A, got {current1}"); + } + + #endregion + } +} diff --git a/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionTests.cs b/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionTests.cs new file mode 100644 index 00000000..3f594227 --- /dev/null +++ b/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionTests.cs @@ -0,0 +1,543 @@ +using SpiceSharp.Components; +using Xunit; + +namespace SpiceSharpParser.IntegrationTests.Components +{ + /// + /// Integration tests for model selection based on L and W parameters with lmin, lmax, wmin, wmax constraints. + /// + public class ModelDimensionTests : BaseTests + { + #region MOSFET Tests + + [Fact] + public void MosfetWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "MOSFET with L and W parameters", + "M1 D G S B NMOS1 L=1u W=10u", + ".model NMOS1 NMOS level=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var mosfet = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet); + } + + [Fact] + public void MosfetSelectsCorrectModelBasedOnLength() + { + var netlist = GetSpiceSharpModel( + "MOSFET model selection based on length", + "M1 D G S B NMOS L=0.5u W=10u", + "M2 D G S B NMOS L=5u W=10u", + ".model NMOS.0 NMOS level=1 lmin=0.1u lmax=1u", + ".model NMOS.1 NMOS level=1 lmin=1u lmax=10u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // Both MOSFETs should be created + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + + var mosfet2 = netlist.Circuit["m2"] as Mosfet1; + Assert.NotNull(mosfet2); + + // Verify models exist in circuit + Assert.NotNull(netlist.Circuit["NMOS.0"]); + Assert.NotNull(netlist.Circuit["NMOS.1"]); + } + + [Fact] + public void MosfetSelectsCorrectModelBasedOnWidth() + { + var netlist = GetSpiceSharpModel( + "MOSFET model selection based on width", + "M1 D G S B PMOS L=1u W=2u", + "M2 D G S B PMOS L=1u W=20u", + ".model PMOS.0 PMOS level=1 wmin=1u wmax=10u", + ".model PMOS.1 PMOS level=1 wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + + var mosfet2 = netlist.Circuit["m2"] as Mosfet1; + Assert.NotNull(mosfet2); + } + + [Fact] + public void MosfetSelectsCorrectModelBasedOnLengthAndWidth() + { + var netlist = GetSpiceSharpModel( + "MOSFET model selection based on L and W", + "M1 D G S B NMOS L=0.5u W=5u", + "M2 D G S B NMOS L=5u W=50u", + ".model NMOS.0 NMOS level=1 lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model NMOS.1 NMOS level=1 lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + + var mosfet2 = netlist.Circuit["m2"] as Mosfet1; + Assert.NotNull(mosfet2); + } + + [Fact] + public void MosfetFallsBackToDefaultModelWhenNoMatch() + { + var netlist = GetSpiceSharpModel( + "MOSFET falls back to default model", + "M1 D G S B NMOS L=100u W=100u", + ".model NMOS.0 NMOS level=1 lmin=0.1u lmax=1u", + ".model NMOS NMOS level=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + } + + #endregion + + #region Resistor Tests + + [Fact] + public void ResistorWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "Resistor with L and W parameters", + "R1 1 0 RMOD L=1u W=10u", + ".model RMOD R RSH=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var resistor = netlist.Circuit["r1"] as Resistor; + Assert.NotNull(resistor); + } + + [Fact] + public void ResistorSelectsCorrectModelBasedOnDimensions() + { + var netlist = GetSpiceSharpModel( + "Resistor model selection based on dimensions", + "R1 1 0 RMOD L=0.5u W=5u", + "R2 1 0 RMOD L=5u W=50u", + ".model RMOD.0 R RSH=1 lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model RMOD.1 R RSH=1 lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var resistor1 = netlist.Circuit["r1"] as Resistor; + Assert.NotNull(resistor1); + + var resistor2 = netlist.Circuit["r2"] as Resistor; + Assert.NotNull(resistor2); + } + + [Fact] + public void ResistorWithOnlyLengthParameter() + { + var netlist = GetSpiceSharpModel( + "Resistor with only L parameter", + "R1 1 0 RMOD L=0.5u W=1u", + "R2 1 0 RMOD L=5u W=1u", + ".model RMOD.0 R RSH=1 lmin=0.1u lmax=1u", + ".model RMOD.1 R RSH=1 lmin=1u lmax=10u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var resistor1 = netlist.Circuit["r1"] as Resistor; + Assert.NotNull(resistor1); + + var resistor2 = netlist.Circuit["r2"] as Resistor; + Assert.NotNull(resistor2); + } + + [Fact] + public void ResistorWithOnlyWidthParameter() + { + var netlist = GetSpiceSharpModel( + "Resistor with only W parameter", + "R1 1 0 RMOD L=1u W=5u", + "R2 1 0 RMOD L=1u W=50u", + ".model RMOD.0 R RSH=1 wmin=1u wmax=10u", + ".model RMOD.1 R RSH=1 wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var resistor1 = netlist.Circuit["r1"] as Resistor; + Assert.NotNull(resistor1); + + var resistor2 = netlist.Circuit["r2"] as Resistor; + Assert.NotNull(resistor2); + } + + #endregion + + #region Capacitor Tests + + [Fact] + public void CapacitorWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "Capacitor with L and W parameters", + "C1 1 0 CMOD L=1u W=10u", + ".model CMOD C CJ=1e-6", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var capacitor = netlist.Circuit["c1"] as Capacitor; + Assert.NotNull(capacitor); + } + + [Fact] + public void CapacitorSelectsCorrectModelBasedOnDimensions() + { + var netlist = GetSpiceSharpModel( + "Capacitor model selection based on dimensions", + "C1 1 0 CMOD L=0.5u W=5u", + "C2 1 0 CMOD L=5u W=50u", + ".model CMOD.0 C CJ=1e-6 lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model CMOD.1 C CJ=1e-6 lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var capacitor1 = netlist.Circuit["c1"] as Capacitor; + Assert.NotNull(capacitor1); + + var capacitor2 = netlist.Circuit["c2"] as Capacitor; + Assert.NotNull(capacitor2); + } + + #endregion + + #region BJT Tests + + [Fact] + public void BJTWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "BJT with L and W parameters", + "Q1 C B E QMOD L=1u W=10u", + ".model QMOD NPN", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var bjt = netlist.Circuit["q1"] as BipolarJunctionTransistor; + Assert.NotNull(bjt); + } + + [Fact] + public void BJTSelectsCorrectModelBasedOnDimensions() + { + var netlist = GetSpiceSharpModel( + "BJT model selection based on dimensions", + "Q1 C B E QMOD L=0.5u W=5u", + "Q2 C B E QMOD L=5u W=50u", + ".model QMOD.0 NPN lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model QMOD.1 NPN lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var bjt1 = netlist.Circuit["q1"] as BipolarJunctionTransistor; + Assert.NotNull(bjt1); + + var bjt2 = netlist.Circuit["q2"] as BipolarJunctionTransistor; + Assert.NotNull(bjt2); + } + + [Fact] + public void BJTWithOnlyLengthParameter() + { + var netlist = GetSpiceSharpModel( + "BJT with only L parameter", + "Q1 C B E QMOD L=0.5u", + "Q2 C B E QMOD L=5u", + ".model QMOD.0 NPN lmin=0.1u lmax=1u", + ".model QMOD.1 NPN lmin=1u lmax=10u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var bjt1 = netlist.Circuit["q1"] as BipolarJunctionTransistor; + Assert.NotNull(bjt1); + + var bjt2 = netlist.Circuit["q2"] as BipolarJunctionTransistor; + Assert.NotNull(bjt2); + } + + #endregion + + #region Diode Tests + + [Fact] + public void DiodeWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "Diode with L and W parameters", + "D1 A K DMOD L=1u W=10u", + ".model DMOD D", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var diode = netlist.Circuit["d1"] as Diode; + Assert.NotNull(diode); + } + + [Fact] + public void DiodeSelectsCorrectModelBasedOnDimensions() + { + var netlist = GetSpiceSharpModel( + "Diode model selection based on dimensions", + "D1 A K DMOD L=0.5u W=5u", + "D2 A K DMOD L=5u W=50u", + ".model DMOD.0 D lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model DMOD.1 D lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var diode1 = netlist.Circuit["d1"] as Diode; + Assert.NotNull(diode1); + + var diode2 = netlist.Circuit["d2"] as Diode; + Assert.NotNull(diode2); + } + + [Fact] + public void DiodeWithOnlyWidthParameter() + { + var netlist = GetSpiceSharpModel( + "Diode with only W parameter", + "D1 A K DMOD W=5u", + "D2 A K DMOD W=50u", + ".model DMOD.0 D wmin=1u wmax=10u", + ".model DMOD.1 D wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var diode1 = netlist.Circuit["d1"] as Diode; + Assert.NotNull(diode1); + + var diode2 = netlist.Circuit["d2"] as Diode; + Assert.NotNull(diode2); + } + + #endregion + + #region JFET Tests + + [Fact] + public void JFETWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "JFET with L and W parameters", + "J1 D G S JMOD L=1u W=10u", + ".model JMOD NJF", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var jfet = netlist.Circuit["j1"] as JFET; + Assert.NotNull(jfet); + } + + [Fact] + public void JFETSelectsCorrectModelBasedOnDimensions() + { + var netlist = GetSpiceSharpModel( + "JFET model selection based on dimensions", + "J1 D G S JMOD L=0.5u W=5u", + "J2 D G S JMOD L=5u W=50u", + ".model JMOD.0 NJF lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model JMOD.1 NJF lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var jfet1 = netlist.Circuit["j1"] as JFET; + Assert.NotNull(jfet1); + + var jfet2 = netlist.Circuit["j2"] as JFET; + Assert.NotNull(jfet2); + } + + [Fact] + public void JFETWithOnlyLengthParameter() + { + var netlist = GetSpiceSharpModel( + "JFET with only L parameter", + "J1 D G S JMOD L=0.5u", + "J2 D G S JMOD L=5u", + ".model JMOD.0 PJF lmin=0.1u lmax=1u", + ".model JMOD.1 PJF lmin=1u lmax=10u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var jfet1 = netlist.Circuit["j1"] as JFET; + Assert.NotNull(jfet1); + + var jfet2 = netlist.Circuit["j2"] as JFET; + Assert.NotNull(jfet2); + } + + #endregion + + #region Edge Cases + + [Fact] + public void ComponentWithoutLWParametersUsesDefaultModel() + { + var netlist = GetSpiceSharpModel( + "Component without L/W uses default", + "M1 D G S B NMOS", + ".model NMOS.0 NMOS level=1 lmin=0.1u lmax=1u", + ".model NMOS NMOS level=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet); + } + + [Fact] + public void ModelWithOnlyLminConstraint() + { + var netlist = GetSpiceSharpModel( + "Model with only lmin constraint", + "R1 1 0 RMOD L=0.5u W=1u", + "R2 1 0 RMOD L=5u W=1u", + ".model RMOD.0 R RSH=1 lmin=1u", + ".model RMOD R RSH=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var resistor1 = netlist.Circuit["r1"] as Resistor; + Assert.NotNull(resistor1); + + var resistor2 = netlist.Circuit["r2"] as Resistor; + Assert.NotNull(resistor2); + } + + [Fact] + public void ModelWithOnlyLmaxConstraint() + { + var netlist = GetSpiceSharpModel( + "Model with only lmax constraint", + "C1 1 0 CMOD L=0.5u W=1u", + "C2 1 0 CMOD L=5u W=1u", + ".model CMOD.0 C CJ=1e-6 lmax=1u", + ".model CMOD C CJ=1e-6", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var capacitor1 = netlist.Circuit["c1"] as Capacitor; + Assert.NotNull(capacitor1); + + var capacitor2 = netlist.Circuit["c2"] as Capacitor; + Assert.NotNull(capacitor2); + } + + [Fact] + public void ModelWithOnlyWminConstraint() + { + var netlist = GetSpiceSharpModel( + "Model with only wmin constraint", + "M1 D G S B NMOS L=1u W=0.5u", + "M2 D G S B NMOS L=1u W=5u", + ".model NMOS.0 NMOS level=1 wmin=1u", + ".model NMOS NMOS level=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + + var mosfet2 = netlist.Circuit["m2"] as Mosfet1; + Assert.NotNull(mosfet2); + } + + [Fact] + public void ModelWithOnlyWmaxConstraint() + { + var netlist = GetSpiceSharpModel( + "Model with only wmax constraint", + "Q1 C B E QMOD W=5u", + "Q2 C B E QMOD W=50u", + ".model QMOD.0 NPN wmax=10u", + ".model QMOD NPN", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var bjt1 = netlist.Circuit["q1"] as BipolarJunctionTransistor; + Assert.NotNull(bjt1); + + var bjt2 = netlist.Circuit["q2"] as BipolarJunctionTransistor; + Assert.NotNull(bjt2); + } + + [Fact] + public void MultipleModelsWithOverlappingRanges() + { + var netlist = GetSpiceSharpModel( + "Multiple models with overlapping ranges", + "D1 A K DMOD L=0.8u W=8u", + ".model DMOD.0 D lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model DMOD.1 D lmin=0.5u lmax=2u wmin=5u wmax=20u", + ".model DMOD D", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var diode1 = netlist.Circuit["d1"] as Diode; + Assert.NotNull(diode1); + } + + #endregion + } +} diff --git a/src/SpiceSharpParser.IntegrationTests/Examples/Circuits/MosfetExample3.cir b/src/SpiceSharpParser.IntegrationTests/Examples/Circuits/MosfetExample3.cir new file mode 100644 index 00000000..cae39dda --- /dev/null +++ b/src/SpiceSharpParser.IntegrationTests/Examples/Circuits/MosfetExample3.cir @@ -0,0 +1,5 @@ +Mosfet circuit +Md 0 1 2 3 my-pmos +.model my-pmos.1 pmos(level = 49 lmin=0.18u) +.model my-pmos.2 pmos(level = 49 lmin=1.18u) +.END diff --git a/src/SpiceSharpParser.IntegrationTests/SpiceSharpParser.IntegrationTests.csproj b/src/SpiceSharpParser.IntegrationTests/SpiceSharpParser.IntegrationTests.csproj index 95097b93..2b1b40db 100644 --- a/src/SpiceSharpParser.IntegrationTests/SpiceSharpParser.IntegrationTests.csproj +++ b/src/SpiceSharpParser.IntegrationTests/SpiceSharpParser.IntegrationTests.csproj @@ -97,6 +97,9 @@ Always + + Always + Always diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/IModelsRegistry.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/IModelsRegistry.cs index c66a878b..e8d11bc4 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/IModelsRegistry.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/IModelsRegistry.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using SpiceSharp.Entities; -using SpiceSharp.Simulations; using SpiceSharpParser.Common; using SpiceSharpParser.Models.Netlist.Spice.Objects; @@ -9,11 +8,11 @@ namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Context.Models { public interface IModelsRegistry { - void SetModel(Entity entity, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action setModelAction, IReadingContext context); + void SetModel(Entity entity, double? l, double? w, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action setModelAction, IReadingContext context); Model FindModel(string modelName); - IEntity FindModelEntity(string modelName); + IEntity FindModelEntity(string modelName, double? l, double? w); void RegisterModelInstance(Model model); diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/Model.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/Model.cs index b78ad212..a03826bf 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/Model.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/Model.cs @@ -1,10 +1,13 @@ using SpiceSharp.Entities; using SpiceSharp.ParameterSets; +using System.Collections.Generic; namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Context.Models { public class Model { + private readonly Dictionary _dimensionParameters = new Dictionary(System.StringComparer.OrdinalIgnoreCase); + public Model(string name, IEntity entity, IParameterSet parameters) { Name = name; @@ -17,5 +20,26 @@ public Model(string name, IEntity entity, IParameterSet parameters) public IEntity Entity { get; } public IParameterSet Parameters { get; } + + /// + /// Sets a dimension parameter (lmin, lmax, wmin, wmax) for model selection. + /// + /// The parameter name. + /// The parameter value. + public void SetDimensionParameter(string parameterName, double value) + { + _dimensionParameters[parameterName] = value; + } + + /// + /// Gets a dimension parameter (lmin, lmax, wmin, wmax) for model selection. + /// + /// The parameter name. + /// The parameter value. + /// True if the parameter exists. + public bool TryGetDimensionParameter(string parameterName, out double value) + { + return _dimensionParameters.TryGetValue(parameterName, out value); + } } } diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/StochasticModelsRegistry.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/StochasticModelsRegistry.cs index 06c2543b..e985e232 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/StochasticModelsRegistry.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/StochasticModelsRegistry.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.ExceptionServices; using SpiceSharp.Entities; using SpiceSharpParser.Common; using SpiceSharpParser.Common.Validation; @@ -231,9 +232,9 @@ public Dictionary GetStochasticModelLotParameter return null; } - public void SetModel(Entity entity, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action setModelAction, IReadingContext context) + public void SetModel(Entity entity, double? l, double? w, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action setModelAction, IReadingContext context) { - var model = FindModelEntity(modelNameParameter.Value); + var model = FindModelEntity(modelNameParameter.Value, l, w); if (model == null) { @@ -263,17 +264,49 @@ public Model FindModel(string modelName) { return model; } + + // Check for dimension-based model naming convention (e.g., MODELNAME.0, MODELNAME.1) + for (var i = 0; i < AllModels.Count; i++) + { + var key = AllModels.Keys.ElementAt(i); + + if (key == modelNameToSearch + "." + i) + { + if (AllModels.TryGetValue(key, out var modelInstance)) + { + return modelInstance; + } + } + } } return null; } - public IEntity FindModelEntity(string modelName) + public IEntity FindModelEntity(string modelName, double? l, double? w) { foreach (var generator in NamesGenerators) { var modelNameToSearch = generator.GenerateObjectName(modelName); + for (var i = 0; i < AllModels.Count; i++) + { + var key = AllModels.Keys.ElementAt(i); + + if (key == modelNameToSearch + "." + i) + { + if (AllModels.TryGetValue(key, out var modelInstance)) + { + if (HasGoodSize(modelInstance, l, w)) + { + return modelInstance.Entity; + } + } + } + } + + + if (AllModels.TryGetValue(modelNameToSearch, out var model)) { return model.Entity; @@ -283,6 +316,43 @@ public IEntity FindModelEntity(string modelName) return null; } + private bool HasGoodSize(Model model, double? l, double? w) + { + model.TryGetDimensionParameter("lmin", out double lminValue); + model.TryGetDimensionParameter("lmax", out double lmaxValue); + model.TryGetDimensionParameter("wmin", out double wminValue); + model.TryGetDimensionParameter("wmax", out double wmaxValue); + + // Check L dimension + if (l.HasValue) + { + if (lminValue > 0 && l.Value < lminValue) + { + return false; + } + + if (lmaxValue > 0 && l.Value > lmaxValue) + { + return false; + } + } + + // Check W dimension + if (w.HasValue) + { + if (wminValue > 0 && w.Value < wminValue) + { + return false; + } + if (wmaxValue > 0 && w.Value > wmaxValue) + { + return false; + } + } + + return true; + } + public IModelsRegistry CreateChildRegistry(List generators) { var result = new StochasticModelsRegistry(generators, IsModelNameCaseSensitive) diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/RLCKGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/RLCKGenerator.cs index 6d20abdd..6ab4aa93 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/RLCKGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/RLCKGenerator.cs @@ -205,8 +205,13 @@ protected IComponent GenerateCap(string name, ParameterCollection parameters, IR { context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = GetLengthFromParameters(parameters, context); + double? w = GetWidthFromParameters(parameters, context); + context.ModelsRegistry.SetModel( capacitor, + l, + w, simulation, parameters.Get(2), $"Could not find model {parameters.Get(2)} for capacitor {name}", @@ -249,7 +254,9 @@ protected IComponent GenerateCap(string name, ParameterCollection parameters, IR if (modelBased) { - var model = context.ModelsRegistry.FindModelEntity(parameters.Get(2).Value); + double? l = GetLengthFromParameters(parameters, context); + double? w = GetWidthFromParameters(parameters, context); + var model = context.ModelsRegistry.FindModelEntity(parameters.Get(2).Value, l, w); if (tcParameterAssignment.Values.Count == 2) { @@ -417,8 +424,13 @@ protected IEntity GenerateRes(string name, ParameterCollection parameters, IRead { context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = GetLengthFromParameters(parameters, context); + double? w = GetWidthFromParameters(parameters, context); + context.ModelsRegistry.SetModel( res, + l, + w, simulation, something, $"Could not find model {something} for resistor {name}", @@ -477,8 +489,13 @@ protected IEntity GenerateRes(string name, ParameterCollection parameters, IRead // Ignore tc parameter on resistor ... context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = GetLengthFromParameters(parameters, context); + double? w = GetWidthFromParameters(parameters, context); + context.ModelsRegistry.SetModel( res, + l, + w, simulation, modelNameParameter, $"Could not find model {modelNameParameter} for resistor {name}", @@ -598,5 +615,25 @@ private string MultiplyIfNeeded(string expression, string mExpression, string nE return expression; } + + private double? GetLengthFromParameters(ParameterCollection parameters, IReadingContext context) + { + var lParameter = parameters.FirstOrDefault(p => p is AssignmentParameter ap && ap.Name.ToLower() == "l"); + if (lParameter != null && lParameter is AssignmentParameter lap) + { + return context.Evaluator.EvaluateDouble(lap.Value); + } + return null; + } + + private double? GetWidthFromParameters(ParameterCollection parameters, IReadingContext context) + { + var wParameter = parameters.FirstOrDefault(p => p is AssignmentParameter ap && ap.Name.ToLower() == "w"); + if (wParameter != null && wParameter is AssignmentParameter wap) + { + return context.Evaluator.EvaluateDouble(wap.Value); + } + return null; + } } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/BipolarJunctionTransistorGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/BipolarJunctionTransistorGenerator.cs index bf5d8bf9..c8c73d06 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/BipolarJunctionTransistorGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/BipolarJunctionTransistorGenerator.cs @@ -38,8 +38,13 @@ public IEntity Generate(string componentIdentifier, string originalName, string context.CreateNodes(bjt, parameters.Take(BipolarJunctionTransistor.PinCount)); context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = GetLengthFromParameters(parameters, context); + double? w = GetWidthFromParameters(parameters, context); + context.ModelsRegistry.SetModel( bjt, + l, + w, simulation, parameters.Get(4), $"Could not find model {parameters.Get(4)} for BJT {originalName}", @@ -93,6 +98,10 @@ public IEntity Generate(string componentIdentifier, string originalName, string context.SetParameter(bjt, "icvbe", asg.Values[0]); } } + else if (asg.Name.ToLower() == "l" || asg.Name.ToLower() == "w") + { + // Skip L and W parameters - they are used for model selection only + } else { context.SetParameter(bjt, asg.Name, asg); @@ -102,5 +111,25 @@ public IEntity Generate(string componentIdentifier, string originalName, string return bjt; } + + private double? GetLengthFromParameters(ParameterCollection parameters, IReadingContext context) + { + var lParameter = parameters.FirstOrDefault(p => p is AssignmentParameter ap && ap.Name.ToLower() == "l"); + if (lParameter != null && lParameter is AssignmentParameter lap) + { + return context.Evaluator.EvaluateDouble(lap.Value); + } + return null; + } + + private double? GetWidthFromParameters(ParameterCollection parameters, IReadingContext context) + { + var wParameter = parameters.FirstOrDefault(p => p is AssignmentParameter ap && ap.Name.ToLower() == "w"); + if (wParameter != null && wParameter is AssignmentParameter wap) + { + return context.Evaluator.EvaluateDouble(wap.Value); + } + return null; + } } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/DiodeGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/DiodeGenerator.cs index d90f53fb..ca200651 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/DiodeGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/DiodeGenerator.cs @@ -3,6 +3,7 @@ using SpiceSharpParser.ModelReaders.Netlist.Spice.Context; using SpiceSharpParser.Models.Netlist.Spice.Objects; using SpiceSharpParser.Models.Netlist.Spice.Objects.Parameters; +using System.Linq; namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.EntityGenerators.Components.Semiconductors { @@ -20,8 +21,13 @@ public IEntity Generate(string componentIdentifier, string originalName, string context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = GetLengthFromParameters(parameters, context); + double? w = GetWidthFromParameters(parameters, context); + context.ModelsRegistry.SetModel( diode, + l, + w, simulation, parameters.Get(2), $"Could not find model {parameters.Get(2)} for diode {originalName}", @@ -51,6 +57,11 @@ public IEntity Generate(string componentIdentifier, string originalName, string if (parameters[i] is AssignmentParameter asg) { + // Skip L and W parameters - they are used for model selection only + if (asg.Name.ToLower() == "l" || asg.Name.ToLower() == "w") + { + continue; + } context.SetParameter(diode, asg.Name, asg); } @@ -70,5 +81,25 @@ public IEntity Generate(string componentIdentifier, string originalName, string return diode; } + + private double? GetLengthFromParameters(ParameterCollection parameters, IReadingContext context) + { + var lParameter = parameters.FirstOrDefault(p => p is AssignmentParameter ap && ap.Name.ToLower() == "l"); + if (lParameter != null && lParameter is AssignmentParameter lap) + { + return context.Evaluator.EvaluateDouble(lap.Value); + } + return null; + } + + private double? GetWidthFromParameters(ParameterCollection parameters, IReadingContext context) + { + var wParameter = parameters.FirstOrDefault(p => p is AssignmentParameter ap && ap.Name.ToLower() == "w"); + if (wParameter != null && wParameter is AssignmentParameter wap) + { + return context.Evaluator.EvaluateDouble(wap.Value); + } + return null; + } } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/JFETGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/JFETGenerator.cs index 39ba9dd3..71939e1a 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/JFETGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/JFETGenerator.cs @@ -3,6 +3,7 @@ using SpiceSharpParser.ModelReaders.Netlist.Spice.Context; using SpiceSharpParser.Models.Netlist.Spice.Objects; using SpiceSharpParser.Models.Netlist.Spice.Objects.Parameters; +using System.Linq; namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.EntityGenerators.Components.Semiconductors { @@ -20,8 +21,13 @@ public IEntity Generate(string componentIdentifier, string originalName, string context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = GetLengthFromParameters(parameters, context); + double? w = GetWidthFromParameters(parameters, context); + context.ModelsRegistry.SetModel( jfet, + l, + w, simulation, parameters.Get(3), $"Could not find model {parameters.Get(3)} for JFET {originalName}", @@ -63,6 +69,10 @@ public IEntity Generate(string componentIdentifier, string originalName, string { context.SetParameter(jfet, "area", asg.Value); } + else if (asg.Name.ToLower() == "l" || asg.Name.ToLower() == "w") + { + // Skip L and W parameters - they are used for model selection only + } else { context.SetParameter(jfet, asg.Name, asg.Value); @@ -77,5 +87,25 @@ public IEntity Generate(string componentIdentifier, string originalName, string return jfet; } + + private double? GetLengthFromParameters(ParameterCollection parameters, IReadingContext context) + { + var lParameter = parameters.FirstOrDefault(p => p is AssignmentParameter ap && ap.Name.ToLower() == "l"); + if (lParameter != null && lParameter is AssignmentParameter lap) + { + return context.Evaluator.EvaluateDouble(lap.Value); + } + return null; + } + + private double? GetWidthFromParameters(ParameterCollection parameters, IReadingContext context) + { + var wParameter = parameters.FirstOrDefault(p => p is AssignmentParameter ap && ap.Name.ToLower() == "w"); + if (wParameter != null && wParameter is AssignmentParameter wap) + { + return context.Evaluator.EvaluateDouble(wap.Value); + } + return null; + } } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/MosfetGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/MosfetGenerator.cs index 3be4c2fc..aed9e34a 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/MosfetGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/MosfetGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using SpiceSharp.Components; using SpiceSharp.Entities; using SpiceSharpParser.Common.Validation; @@ -95,8 +96,13 @@ public override IEntity Generate(string componentIdentifier, string originalName context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = GetLengthFromParameters(parameters, context); + double? w = GetWidthFromParameters(parameters, context); + context.ModelsRegistry.SetModel( mosfetDetails.Mosfet, + l, + w, simulation, modelNameParameter, $"Could not find model {modelNameParameter} for mosfet {componentIdentifier}", @@ -161,6 +167,26 @@ public override IEntity Generate(string componentIdentifier, string originalName return mosfet; } + private double? GetLengthFromParameters(ParameterCollection parameters, IReadingContext context) + { + var lParameter = parameters.FirstOrDefault(p => p is AssignmentParameter ap && ap.Name.ToLower() == "l"); + if (lParameter != null && lParameter is AssignmentParameter lap) + { + return context.Evaluator.EvaluateDouble(lap.Value); + } + return null; + } + + private double? GetWidthFromParameters(ParameterCollection parameters, IReadingContext context) + { + var wParameter = parameters.FirstOrDefault(p => p is AssignmentParameter ap && ap.Name.ToLower() == "w"); + if (wParameter != null && wParameter is AssignmentParameter wap) + { + return context.Evaluator.EvaluateDouble(wap.Value); + } + return null; + } + protected class MosfetDetails { public SpiceSharp.Components.Component Mosfet { get; set; } diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/SwitchGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/SwitchGenerator.cs index e68ced45..39c593a2 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/SwitchGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/SwitchGenerator.cs @@ -112,6 +112,8 @@ protected IEntity GenerateVoltageSwitch(string name, ParameterCollection paramet { context.ModelsRegistry.SetModel( vsw, + null, + null, simulation, parameters.Get(4), $"Could not find model {parameters.Get(4)} for voltage switch {name}", @@ -270,6 +272,8 @@ private IEntity GenerateCurrentSwitch(string name, ParameterCollection parameter { context.ModelsRegistry.SetModel( csw, + null, + null, simulation, parameters.Get(3), $"Could not find model {parameters.Get(3)} for current switch {name}", diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/IModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/IModelGenerator.cs index 37544c3f..4c4dcff9 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/IModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/IModelGenerator.cs @@ -7,4 +7,9 @@ public interface IModelGenerator { Model Generate(string id, string type, SpiceSharpParser.Models.Netlist.Spice.Objects.ParameterCollection parameters, IReadingContext context); } + + public interface ICustomModelGenerator : IModelGenerator + { + Context.Models.Model Process(Context.Models.Model model, IModelsRegistry models); + } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/BipolarModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/BipolarModelGenerator.cs index 6e6e8c57..caebb899 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/BipolarModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/BipolarModelGenerator.cs @@ -8,20 +8,22 @@ public class BipolarModelGenerator : ModelGenerator { public override Model Generate(string id, string type, SpiceSharpParser.Models.Netlist.Spice.Objects.ParameterCollection parameters, IReadingContext context) { - BipolarJunctionTransistorModel model = new BipolarJunctionTransistorModel(id); + BipolarJunctionTransistorModel bjtModel = new BipolarJunctionTransistorModel(id); if (type.ToLower() == "npn") { - model.SetParameter("npn", true); + bjtModel.SetParameter("npn", true); } else if (type.ToLower() == "pnp") { - model.SetParameter("pnp", true); + bjtModel.SetParameter("pnp", true); } - SetParameters(context, model, parameters); + var contextModel = new Model(id, bjtModel, bjtModel.Parameters); + SetParameters(context, bjtModel, parameters); + SetDimensionParameters(context, contextModel, parameters); - return new Model(id, model, model.Parameters); + return contextModel; } } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/DiodeModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/DiodeModelGenerator.cs index 3e4b0d5b..30ffaad3 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/DiodeModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/DiodeModelGenerator.cs @@ -9,10 +9,12 @@ public class DiodeModelGenerator : ModelGenerator { public override Model Generate(string id, string type, ParameterCollection parameters, IReadingContext context) { - var model = new DiodeModel(id); - SetParameters(context, model, parameters); + var diodeModel = new DiodeModel(id); + var contextModel = new Model(id, diodeModel, diodeModel.Parameters); + SetParameters(context, diodeModel, parameters); + SetDimensionParameters(context, contextModel, parameters); - return new Model(id, model, model.Parameters); + return contextModel; } } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/JFETModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/JFETModelGenerator.cs index 9ba3f6f0..e018bcae 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/JFETModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/JFETModelGenerator.cs @@ -8,15 +8,17 @@ public class JFETModelGenerator : ModelGenerator { public override Context.Models.Model Generate(string id, string type, ParameterCollection parameters, IReadingContext context) { - var model = new JFETModel(id); + var jfetModel = new JFETModel(id); switch (type.ToLower()) { - case "pjf": model.SetParameter("pjf", true); break; - case "njf": model.SetParameter("njf", true); break; + case "pjf": jfetModel.SetParameter("pjf", true); break; + case "njf": jfetModel.SetParameter("njf", true); break; } - SetParameters(context, model, parameters); - return new Context.Models.Model(id, model, model.Parameters); + var contextModel = new Context.Models.Model(id, jfetModel, jfetModel.Parameters); + SetParameters(context, jfetModel, parameters); + SetDimensionParameters(context, contextModel, parameters); + return contextModel; } } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/ModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/ModelGenerator.cs index ff456d93..f1247b33 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/ModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/ModelGenerator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using SpiceSharp.Entities; using SpiceSharpParser.Common.Validation; using SpiceSharpParser.ModelReaders.Netlist.Spice.Context; @@ -9,6 +10,14 @@ namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.EntityGenerators.M { public abstract class ModelGenerator : IModelGenerator { + /// + /// The dimension parameters used for model selection (not to be set on SpiceSharp entities). + /// + private static readonly HashSet DimensionParameterNames = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "lmin", "lmax", "wmin", "wmax" + }; + public abstract Context.Models.Model Generate(string id, string type, ParameterCollection parameters, IReadingContext context); protected void SetParameters(IReadingContext context, IEntity entity, ParameterCollection parameters, bool onload = true) @@ -17,6 +26,12 @@ protected void SetParameters(IReadingContext context, IEntity entity, ParameterC { if (parameter is AssignmentParameter ap) { + // Skip dimension parameters - they are handled separately + if (DimensionParameterNames.Contains(ap.Name)) + { + continue; + } + try { context.SetParameter(entity, ap.Name, ap.Value, onload); @@ -32,5 +47,30 @@ protected void SetParameters(IReadingContext context, IEntity entity, ParameterC } } } + + /// + /// Sets dimension parameters (lmin, lmax, wmin, wmax) on a model for model selection. + /// + /// The reading context. + /// The model to set dimension parameters on. + /// The parameters collection. + protected void SetDimensionParameters(IReadingContext context, Context.Models.Model model, ParameterCollection parameters) + { + foreach (Parameter parameter in parameters) + { + if (parameter is AssignmentParameter ap && DimensionParameterNames.Contains(ap.Name)) + { + try + { + var value = context.Evaluator.EvaluateDouble(ap.Value); + model.SetDimensionParameter(ap.Name, value); + } + catch (Exception ex) + { + context.Result.ValidationResult.AddError(ValidationEntrySource.Reader, $"Problem with setting dimension parameter: {parameter}", parameter.LineInfo, ex); + } + } + } + } } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/MosfetModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/MosfetModelGenerator.cs index 4bec0787..b4b198dc 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/MosfetModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/MosfetModelGenerator.cs @@ -149,6 +149,9 @@ public override Context.Models.Model Generate(string id, string type, ParameterC // Read all the parameters SetParameters(context, model.Entity, clonedParameters); + // Set dimension parameters for model selection (lmin, lmax, wmin, wmax) + SetDimensionParameters(context, model, clonedParameters); + return model; } } diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/RLCModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/RLCModelGenerator.cs index 2294b63d..1b7f6566 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/RLCModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/RLCModelGenerator.cs @@ -12,14 +12,18 @@ public override Context.Models.Model Generate(string id, string type, ParameterC { case "res": case "r": - var model = new ResistorModel(id); - SetParameters(context, model, parameters); - return new Context.Models.Model(id, model, model.Parameters); + var resistorModel = new ResistorModel(id); + var resistorContextModel = new Context.Models.Model(id, resistorModel, resistorModel.Parameters); + SetParameters(context, resistorModel, parameters); + SetDimensionParameters(context, resistorContextModel, parameters); + return resistorContextModel; case "c": - var model2 = new CapacitorModel(id); - SetParameters(context, model2, parameters); - return new Context.Models.Model(id, model2, model2.Parameters); + var capacitorModel = new CapacitorModel(id); + var capacitorContextModel = new Context.Models.Model(id, capacitorModel, capacitorModel.Parameters); + SetParameters(context, capacitorModel, parameters); + SetDimensionParameters(context, capacitorContextModel, parameters); + return capacitorContextModel; } return null; diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/SwitchModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/SwitchModelGenerator.cs index 1ce63cf1..e8f6e462 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/SwitchModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/SwitchModelGenerator.cs @@ -11,23 +11,32 @@ public override Context.Models.Model Generate(string id, string type, ParameterC { switch (type.ToLower()) { - case "sw": var model = new VoltageSwitchModel(id); - SetParameters(context, model, parameters); - return new Context.Models.Model(id, model, model.Parameters); + case "sw": + var vsModel = new VoltageSwitchModel(id); + var vsContextModel = new Context.Models.Model(id, vsModel, vsModel.Parameters); + SetParameters(context, vsModel, parameters); + SetDimensionParameters(context, vsContextModel, parameters); + return vsContextModel; case "csw": - var model2 = new CurrentSwitchModel(id); - SetParameters(context, model2, parameters); - return new Context.Models.Model(id, model2, model2.Parameters); + var csModel = new CurrentSwitchModel(id); + var csContextModel = new Context.Models.Model(id, csModel, csModel.Parameters); + SetParameters(context, csModel, parameters); + SetDimensionParameters(context, csContextModel, parameters); + return csContextModel; case "vswitch": var vSwitchModel = new VSwitchModel(id); + var vSwitchContextModel = new Context.Models.Model(id, vSwitchModel, vSwitchModel.Parameters); SetParameters(context, vSwitchModel, parameters); - return new Context.Models.Model(id, vSwitchModel, vSwitchModel.Parameters); + SetDimensionParameters(context, vSwitchContextModel, parameters); + return vSwitchContextModel; case "iswitch": var iSwitchModel = new ISwitchModel(id); + var iSwitchContextModel = new Context.Models.Model(id, iSwitchModel, iSwitchModel.Parameters); SetParameters(context, iSwitchModel, parameters); - return new Context.Models.Model(id, iSwitchModel, iSwitchModel.Parameters); + SetDimensionParameters(context, iSwitchContextModel, parameters); + return iSwitchContextModel; } return null;