From a5a7bf1e548c53543d53a19f395a5208a708939e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Go=C5=82=C4=99biowski?= Date: Tue, 20 Jan 2026 20:08:41 +0100 Subject: [PATCH 1/2] Prototype --- .../Examples/Circuits/MosfetExample3.cir | 5 + .../Examples/Extensions/Aggregate.cs | 48 +++++++ .../Extensions/ComplexMosfetModelGenerator.cs | 128 ++++++++++++++++++ .../Extensions/CustomMosfetModelTest.cs | 30 ++++ .../SpiceSharpParser.IntegrationTests.csproj | 3 + .../EntityGenerators/IModelGenerator.cs | 5 + .../StochasticModelsGenerator.cs | 14 +- 7 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 src/SpiceSharpParser.IntegrationTests/Examples/Circuits/MosfetExample3.cir create mode 100644 src/SpiceSharpParser.IntegrationTests/Examples/Extensions/Aggregate.cs create mode 100644 src/SpiceSharpParser.IntegrationTests/Examples/Extensions/ComplexMosfetModelGenerator.cs 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/Examples/Extensions/Aggregate.cs b/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/Aggregate.cs new file mode 100644 index 00000000..71dab453 --- /dev/null +++ b/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/Aggregate.cs @@ -0,0 +1,48 @@ +using SpiceSharp.Components; +using SpiceSharp.Entities; +using SpiceSharp.Simulations; +using System.Collections.Generic; +using System.Xml.Linq; + +public class BSIM3AggregateModel : Entity +{ + /// + /// Gets the models in the aggregate model based on sizes. + /// + public HashSet Models { get; } = []; + + /// + /// Creates an aggregate model. + /// + /// The name. + public BSIM3AggregateModel(string name) + : base(name) + { + } + + /// + /// Creates an aggregate model based on sizes. + /// + /// + /// + public BSIM3AggregateModel(string name, IEnumerable models) + : base(name) + { + foreach (var model in models) + Models.Add(model); + } + + public override void CreateBehaviors(ISimulation simulation) + { + throw new System.NotImplementedException(); + } + + /// + public override IEntity Clone() + { + var n = new BSIM3AggregateModel(Name); + foreach (var model in Models) + n.Models.Add((BSIM3Model)model.Clone()); + return n; + } +} \ No newline at end of file diff --git a/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/ComplexMosfetModelGenerator.cs b/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/ComplexMosfetModelGenerator.cs new file mode 100644 index 00000000..5d97976b --- /dev/null +++ b/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/ComplexMosfetModelGenerator.cs @@ -0,0 +1,128 @@ +using SpiceSharp.Components; +using SpiceSharpParser.Common.Validation; +using SpiceSharpParser.ModelReaders.Netlist.Spice.Context; +using SpiceSharpParser.ModelReaders.Netlist.Spice.Context.Models; +using SpiceSharpParser.Models.Netlist.Spice.Objects; +using SpiceSharpParser.Models.Netlist.Spice.Objects.Parameters; +using System; + +namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.EntityGenerators.Models +{ + public class ComplexMosfetModelGenerator : MosfetModelGenerator, ICustomModelGenerator + { + public ComplexMosfetModelGenerator() + { + } + + public Context.Models.Model Process(Context.Models.Model model, IModelsRegistry models) + { + + if (model.Entity is BSIM3Model bsim3Model) + { + + if (model.Name.Contains(".")) + { + var aggregateName = bsim3Model.Name.Substring(0, bsim3Model.Name.IndexOf('.')); + var aggregate = models.FindModel(aggregateName); + if (aggregate != null) + { + var aggrategeEntity = aggregate.Entity as BSIM3AggregateModel; + aggrategeEntity.Models.Add(bsim3Model); + + return aggregate; + } + else + { + var aggrategeEntity = new BSIM3AggregateModel(aggregateName); + aggrategeEntity.Models.Add(bsim3Model); + + return new Context.Models.Model(aggregateName, aggrategeEntity, null); + } + + } + + } + return model; + } + + public override void AddGenericLevel(int level) + { + base.AddGenericLevel(level); + } + + public override void AddLevel(int level) + { + Levels[level] = (name, type, _) => + { + var mosfet = (TModel)Activator.CreateInstance(typeof(TModel), name); + switch (type.ToLower()) + { + case "nmos": mosfet.SetParameter("nmos", true); break; + case "pmos": mosfet.SetParameter("pmos", true); break; + } + + return new Context.Models.Model(name, mosfet, mosfet.Parameters); + }; + } + + + public override Context.Models.Model Generate(string id, string type, ParameterCollection parameters, IReadingContext context) + { + var clonedParameters = (ParameterCollection)parameters.Clone(); + + int level = 1; + string version = null; + int lindex = -1, vindex = -1; + for (int i = 0; i < clonedParameters.Count; i++) + { + if (clonedParameters[i] is AssignmentParameter ap) + { + if (ap.Name.ToLower() == "level") + { + lindex = i; + level = (int)Math.Round(context.Evaluator.EvaluateDouble(ap.Value)); + } + + if (ap.Name.ToLower() == "version") + { + vindex = i; + version = ap.Value.ToLower(); + } + + if (vindex >= 0 && lindex >= 0) + { + break; + } + } + } + + if (lindex >= 0) + { + clonedParameters.RemoveAt(lindex); + } + + if (vindex >= 0) + { + clonedParameters.RemoveAt(vindex < lindex ? vindex : vindex - 1); + } + + // Generate the model + Context.Models.Model model; + if (Levels.ContainsKey(level)) + { + model = Levels[level].Invoke(id, type, version); + } + else + { + context.Result.ValidationResult.AddError(ValidationEntrySource.Reader, $"Unknown mosfet model level {level}", parameters.LineInfo); + return null; + } + + // Read all the parameters + SetParameters(context, model.Entity, clonedParameters); + + return model; + } + + } +} \ No newline at end of file diff --git a/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/CustomMosfetModelTest.cs b/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/CustomMosfetModelTest.cs index ed0a7c82..6204284c 100644 --- a/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/CustomMosfetModelTest.cs +++ b/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/CustomMosfetModelTest.cs @@ -80,6 +80,36 @@ public void When_BSIM1_Used_NoExceptions() spiceSharpReader.Settings.Mappings.Components.Map("M", mosfetGenerator); + var spiceSharpModel = spiceSharpReader.Read(parseResult.FinalModel); + + Assert.False(spiceSharpModel.ValidationResult.HasError); + Assert.False(spiceSharpModel.ValidationResult.HasWarning); + } + + [Fact] + public void When_BSIM3_Used_NoExceptions() + { + // Create a model from text file + string path = Path.Combine(Directory.GetCurrentDirectory(), "Examples/Circuits/MosfetExample3.cir"); + var netlistContent = File.ReadAllText(path); + var parser = new SpiceNetlistParser(); + parser.Settings.Lexing.HasTitle = true; + var parseResult = parser.ParseNetlist(netlistContent); + + // Convert to Spice# + var spiceSharpReader = new SpiceSharpReader(); + spiceSharpReader.Settings.CaseSensitivity.IsModelTypeCaseSensitive = false; + + // custom mosfet models + var modelGenerator = new ComplexMosfetModelGenerator(); + modelGenerator.AddGenericLevel(49); + + spiceSharpReader.Settings.Mappings.Models.Map(new[] { "PMOS", "NMOS" }, modelGenerator); + var mosfetGenerator = new MosfetGenerator(); + mosfetGenerator.AddMosfet(); + spiceSharpReader.Settings.Mappings.Components.Map("M", mosfetGenerator); + + var spiceSharpModel = spiceSharpReader.Read(parseResult.FinalModel); Assert.False(spiceSharpModel.ValidationResult.HasError); 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/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/StochasticModelsGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/StochasticModelsGenerator.cs index dbea6bfe..5d1af38a 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/StochasticModelsGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/StochasticModelsGenerator.cs @@ -82,8 +82,18 @@ public Context.Models.Model GenerateModel(IModelGenerator modelGenerator, string RegisterDevAndLotModels(parameters, stochasticModelRegistry, model, (modelId) => { var stochasticCandidate = modelGenerator.Generate(modelId, type, filteredParameters, context); - context.ModelsRegistry.RegisterModelInstance(stochasticCandidate); - return stochasticCandidate; + + if (modelGenerator is ICustomModelGenerator custom) + { + var model = custom.Process(stochasticCandidate, context.ModelsRegistry); + context.ModelsRegistry.RegisterModelInstance(model); + return model; + } + else + { + context.ModelsRegistry.RegisterModelInstance(stochasticCandidate); + return stochasticCandidate; + } }); return model; } From 54c20542a229f241bc35e3bf13b31c3f597f0aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Go=C5=82=C4=99biowski?= Date: Tue, 20 Jan 2026 20:09:47 +0100 Subject: [PATCH 2/2] Fix --- .../Extensions/ComplexMosfetModelGenerator.cs | 85 ------------------- 1 file changed, 85 deletions(-) diff --git a/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/ComplexMosfetModelGenerator.cs b/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/ComplexMosfetModelGenerator.cs index 5d97976b..57ea5929 100644 --- a/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/ComplexMosfetModelGenerator.cs +++ b/src/SpiceSharpParser.IntegrationTests/Examples/Extensions/ComplexMosfetModelGenerator.cs @@ -1,10 +1,5 @@ using SpiceSharp.Components; -using SpiceSharpParser.Common.Validation; -using SpiceSharpParser.ModelReaders.Netlist.Spice.Context; using SpiceSharpParser.ModelReaders.Netlist.Spice.Context.Models; -using SpiceSharpParser.Models.Netlist.Spice.Objects; -using SpiceSharpParser.Models.Netlist.Spice.Objects.Parameters; -using System; namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.EntityGenerators.Models { @@ -44,85 +39,5 @@ public Context.Models.Model Process(Context.Models.Model model, IModelsRegistry } return model; } - - public override void AddGenericLevel(int level) - { - base.AddGenericLevel(level); - } - - public override void AddLevel(int level) - { - Levels[level] = (name, type, _) => - { - var mosfet = (TModel)Activator.CreateInstance(typeof(TModel), name); - switch (type.ToLower()) - { - case "nmos": mosfet.SetParameter("nmos", true); break; - case "pmos": mosfet.SetParameter("pmos", true); break; - } - - return new Context.Models.Model(name, mosfet, mosfet.Parameters); - }; - } - - - public override Context.Models.Model Generate(string id, string type, ParameterCollection parameters, IReadingContext context) - { - var clonedParameters = (ParameterCollection)parameters.Clone(); - - int level = 1; - string version = null; - int lindex = -1, vindex = -1; - for (int i = 0; i < clonedParameters.Count; i++) - { - if (clonedParameters[i] is AssignmentParameter ap) - { - if (ap.Name.ToLower() == "level") - { - lindex = i; - level = (int)Math.Round(context.Evaluator.EvaluateDouble(ap.Value)); - } - - if (ap.Name.ToLower() == "version") - { - vindex = i; - version = ap.Value.ToLower(); - } - - if (vindex >= 0 && lindex >= 0) - { - break; - } - } - } - - if (lindex >= 0) - { - clonedParameters.RemoveAt(lindex); - } - - if (vindex >= 0) - { - clonedParameters.RemoveAt(vindex < lindex ? vindex : vindex - 1); - } - - // Generate the model - Context.Models.Model model; - if (Levels.ContainsKey(level)) - { - model = Levels[level].Invoke(id, type, version); - } - else - { - context.Result.ValidationResult.AddError(ValidationEntrySource.Reader, $"Unknown mosfet model level {level}", parameters.LineInfo); - return null; - } - - // Read all the parameters - SetParameters(context, model.Entity, clonedParameters); - - return model; - } - } } \ No newline at end of file