diff --git a/src/SpiceSharpParser.IntegrationTests/DotStatements/MeasTests.cs b/src/SpiceSharpParser.IntegrationTests/DotStatements/MeasTests.cs
index c8198a5f..1c82f2b9 100644
--- a/src/SpiceSharpParser.IntegrationTests/DotStatements/MeasTests.cs
+++ b/src/SpiceSharpParser.IntegrationTests/DotStatements/MeasTests.cs
@@ -2636,5 +2636,195 @@ public void FifteenMeasurementsStressTest()
model.Measurements["m5"][0].Value,
model.Measurements["m14"][0].Value));
}
+ // =====================================================================
+ // Group O: FIND ... AT= Measurements
+ // =====================================================================
+
+ [Fact]
+ public void FindAtConstantVoltage()
+ {
+ // Constant 5V source — FIND V(out) AT=any_time should return 5.0
+ var model = GetSpiceSharpModel(
+ "MEAS FIND AT Constant Voltage",
+ "V1 OUT 0 5.0",
+ "R1 OUT 0 1e3",
+ ".TRAN 1e-4 10e-3",
+ ".MEAS TRAN res1 FIND V(OUT) AT=5m",
+ ".END");
+
+ RunSimulations(model);
+
+ Assert.True(model.Measurements.ContainsKey("res1"));
+ var results = model.Measurements["res1"];
+ Assert.Single(results);
+ Assert.True(results[0].Success);
+ Assert.True(EqualsWithTol(5.0, results[0].Value));
+ }
+
+ [Fact]
+ public void FindAtRCCharging()
+ {
+ // RC circuit: V(out) = 10*(1-exp(-t/RC)), RC=10ms
+ // At t=10ms: V(out) = 10*(1-exp(-1)) ≈ 6.3212
+ double rc = 10e3 * 1e-6; // 10ms
+ double expected = 10.0 * (1.0 - Math.Exp(-10e-3 / rc));
+
+ var model = GetSpiceSharpModel(
+ "MEAS FIND AT RC Charging",
+ "V1 IN 0 10.0",
+ "R1 IN OUT 10e3",
+ "C1 OUT 0 1e-6",
+ ".IC V(OUT)=0.0",
+ ".TRAN 1e-5 50e-3",
+ ".MEAS TRAN res1 FIND V(OUT) AT=10m",
+ ".END");
+
+ RunSimulations(model);
+
+ Assert.True(model.Measurements.ContainsKey("res1"));
+ var results = model.Measurements["res1"];
+ Assert.Single(results);
+ Assert.True(results[0].Success);
+ Assert.True(EqualsWithTol(expected, results[0].Value));
+ }
+
+ // =====================================================================
+ // Group P: TD on WHEN Measurements
+ // =====================================================================
+
+ [Fact]
+ public void WhenWithTD()
+ {
+ // Oscillating signal: crosses 0V many times. With TD, skip early crossings.
+ // V(out) = sin(2*pi*1000*t) crosses 0 at t=0, 0.5ms, 1ms, 1.5ms, ...
+ // With TD=0.8ms, the first crossing after 0.8ms is at t=1ms
+ var model = GetSpiceSharpModel(
+ "MEAS WHEN with TD",
+ "V1 OUT 0 SIN(0 1 1e3)",
+ "R1 OUT 0 1e3",
+ ".TRAN 1e-6 5e-3",
+ ".MEAS TRAN res_notd WHEN V(OUT)=0 CROSS=1",
+ ".MEAS TRAN res_td WHEN V(OUT)=0 CROSS=1 TD=0.8m",
+ ".END");
+
+ RunSimulations(model);
+
+ Assert.True(model.Measurements.ContainsKey("res_notd"));
+ Assert.True(model.Measurements.ContainsKey("res_td"));
+ Assert.True(model.Measurements["res_notd"][0].Success);
+ Assert.True(model.Measurements["res_td"][0].Success);
+ // Without TD, first crossing is near t=0.5ms (first positive-to-negative)
+ // With TD=0.8ms, first crossing after 0.8ms should be at ~1ms
+ Assert.True(model.Measurements["res_td"][0].Value > 0.8e-3);
+ }
+
+ // =====================================================================
+ // Group Q: RISE/FALL/CROSS=LAST
+ // =====================================================================
+
+ [Fact]
+ public void WhenCrossLast()
+ {
+ // Oscillating signal: find the last crossing of 0V
+ var model = GetSpiceSharpModel(
+ "MEAS WHEN CROSS LAST",
+ "V1 OUT 0 SIN(0 1 1e3)",
+ "R1 OUT 0 1e3",
+ ".TRAN 1e-6 5e-3",
+ ".MEAS TRAN res_last WHEN V(OUT)=0 CROSS=LAST",
+ ".MEAS TRAN res_first WHEN V(OUT)=0 CROSS=1",
+ ".END");
+
+ RunSimulations(model);
+
+ Assert.True(model.Measurements.ContainsKey("res_last"));
+ Assert.True(model.Measurements.ContainsKey("res_first"));
+ Assert.True(model.Measurements["res_last"][0].Success);
+ Assert.True(model.Measurements["res_first"][0].Success);
+ // Last crossing should be much later than first
+ Assert.True(model.Measurements["res_last"][0].Value > model.Measurements["res_first"][0].Value);
+ // Last crossing should be near end of simulation (5ms)
+ Assert.True(model.Measurements["res_last"][0].Value > 4e-3);
+ }
+
+ [Fact]
+ public void FindWhenRiseLast()
+ {
+ // Find V(out) at the last rising crossing of 0V
+ var model = GetSpiceSharpModel(
+ "MEAS FIND WHEN RISE LAST",
+ "V1 OUT 0 SIN(0 1 1e3)",
+ "R1 OUT 0 1e3",
+ ".TRAN 1e-6 5e-3",
+ ".MEAS TRAN res1 FIND V(OUT) WHEN V(OUT)=0 RISE=LAST",
+ ".END");
+
+ RunSimulations(model);
+
+ Assert.True(model.Measurements.ContainsKey("res1"));
+ Assert.True(model.Measurements["res1"][0].Success);
+ // At a rising zero crossing, V(OUT) ≈ 0
+ Assert.True(Math.Abs(model.Measurements["res1"][0].Value) < 0.1);
+ }
+
+ [Fact]
+ public void TrigTargCrossLast()
+ {
+ // Use CROSS=LAST on TARG for TRIG/TARG measurement
+ var model = GetSpiceSharpModel(
+ "MEAS TRIG/TARG CROSS LAST",
+ "V1 OUT 0 SIN(0 1 1e3)",
+ "R1 OUT 0 1e3",
+ ".TRAN 1e-6 5e-3",
+ ".MEAS TRAN res1 TRIG V(OUT) VAL=0 CROSS=1 TARG V(OUT) VAL=0 CROSS=LAST",
+ ".END");
+
+ RunSimulations(model);
+
+ Assert.True(model.Measurements.ContainsKey("res1"));
+ Assert.True(model.Measurements["res1"][0].Success);
+ // Should be a large positive value (last crossing - first crossing)
+ Assert.True(model.Measurements["res1"][0].Value > 3e-3);
+ }
+
+ // =====================================================================
+ // Group R: DERIV with WHEN
+ // =====================================================================
+
+ [Fact]
+ public void DerivWithWhen()
+ {
+ // RC circuit: V(out) = 10*(1-exp(-t/RC))
+ // dV/dt = (10/RC)*exp(-t/RC)
+ // Use DERIV WHEN V(OUT)=5 to find the derivative at the point where V=5V
+ // V=5 when t = -RC*ln(0.5) = RC*ln(2) ≈ 6.93ms
+ // dV/dt at that point = (10/RC)*exp(-ln(2)) = (10/RC)*0.5 = 500 V/s
+ double rc = 10e3 * 1e-6; // 10ms
+
+ var model = GetSpiceSharpModel(
+ "MEAS DERIV with WHEN",
+ "V1 IN 0 10.0",
+ "R1 IN OUT 10e3",
+ "C1 OUT 0 1e-6",
+ ".IC V(OUT)=0.0",
+ ".TRAN 1e-5 50e-3",
+ ".MEAS TRAN res1 DERIV V(OUT) AT=6.93m",
+ ".MEAS TRAN res2 DERIV V(OUT) WHEN V(OUT)=5",
+ ".END");
+
+ RunSimulations(model);
+
+ Assert.True(model.Measurements.ContainsKey("res1"));
+ Assert.True(model.Measurements.ContainsKey("res2"));
+ Assert.True(model.Measurements["res1"][0].Success);
+ Assert.True(model.Measurements["res2"][0].Success);
+ // Both should give approximately the same derivative (around 500 V/s)
+ // Use a 5% tolerance since discrete derivative is approximate
+ double expected = (10.0 / rc) * 0.5; // 500 V/s
+ Assert.True(Math.Abs(model.Measurements["res1"][0].Value - expected) / expected < 0.05,
+ $"res1 derivative {model.Measurements["res1"][0].Value} not within 5% of {expected}");
+ Assert.True(Math.Abs(model.Measurements["res2"][0].Value - expected) / expected < 0.05,
+ $"res2 derivative {model.Measurements["res2"][0].Value} not within 5% of {expected}");
+ }
}
}
diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/MeasControl.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/MeasControl.cs
index 2ed0420e..957db796 100644
--- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/MeasControl.cs
+++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/MeasControl.cs
@@ -122,6 +122,12 @@ private void SetupMeasurement(MeasurementDefinition definition, ISimulationWithE
findExport = GenerateExport(definition.FindSignal, context, simulation);
}
+ // For DERIV with WHEN, collect the derivative signal separately
+ if (definition.Type == MeasType.Deriv && definition.WhenSignal != null && definition.Signal != null)
+ {
+ findExport = GenerateExport(definition.Signal, context, simulation);
+ }
+
// For TRIG/TARG, we might need separate exports for trigger and target signals
Export trigExport = null;
Export targExport = null;
@@ -222,13 +228,23 @@ private static Parameter GetPrimarySignalParameter(MeasurementDefinition definit
return definition.WhenSignal;
case MeasType.FindWhen:
return definition.WhenSignal;
+ case MeasType.FindAt:
+ return definition.FindSignal;
case MeasType.Min:
case MeasType.Max:
case MeasType.Avg:
case MeasType.Rms:
case MeasType.Pp:
case MeasType.Integ:
+ return definition.Signal;
case MeasType.Deriv:
+ // If DERIV has a WHEN condition, the primary signal is the WHEN signal
+ // and we collect the derivative signal separately via findExport
+ if (definition.WhenSignal != null)
+ {
+ return definition.WhenSignal;
+ }
+
return definition.Signal;
default:
return null;
@@ -459,15 +475,15 @@ private void ParseThresholdSection(
break;
case "RISE":
edge = EdgeType.Rise;
- edgeNumber = Math.Max(1, (int)context.Evaluator.EvaluateDouble(ap.Value));
+ edgeNumber = ParseEdgeNumber(ap.Value, context);
break;
case "FALL":
edge = EdgeType.Fall;
- edgeNumber = Math.Max(1, (int)context.Evaluator.EvaluateDouble(ap.Value));
+ edgeNumber = ParseEdgeNumber(ap.Value, context);
break;
case "CROSS":
edge = EdgeType.Cross;
- edgeNumber = Math.Max(1, (int)context.Evaluator.EvaluateDouble(ap.Value));
+ edgeNumber = ParseEdgeNumber(ap.Value, context);
break;
case "TD":
td = context.Evaluator.EvaluateDouble(ap.Value);
@@ -499,10 +515,11 @@ private MeasurementDefinition ParseWhen(MeasurementDefinition definition, List
specParams, IReadingContext context)
{
// .MEAS TRAN name FIND V(out) WHEN V(in)=0.5 [RISE|FALL|CROSS=]
- definition.Type = MeasType.FindWhen;
+ // .MEAS TRAN name FIND V(out) AT=5m
- // Find the WHEN keyword
+ // Find the WHEN keyword or AT= parameter
int whenIndex = -1;
+ int atIndex = -1;
for (int i = 1; i < specParams.Count; i++)
{
if (specParams[i].Value.ToUpper() == "WHEN")
@@ -510,18 +527,26 @@ private MeasurementDefinition ParseFindWhen(MeasurementDefinition definition, Li
whenIndex = i;
break;
}
+
+ if (specParams[i] is AssignmentParameter ap && ap.Name.ToUpper() == "AT")
+ {
+ atIndex = i;
+ break;
+ }
}
- if (whenIndex < 0)
+ if (whenIndex < 0 && atIndex < 0)
{
context.Result.ValidationResult.AddError(
ValidationEntrySource.Reader,
- ".MEAS FIND: Missing WHEN keyword");
+ ".MEAS FIND: Missing WHEN or AT= keyword");
return null;
}
- // The FIND signal is between FIND keyword and WHEN keyword
- for (int i = 1; i < whenIndex; i++)
+ int signalEnd = whenIndex >= 0 ? whenIndex : atIndex;
+
+ // The FIND signal is between FIND keyword and WHEN/AT keyword
+ for (int i = 1; i < signalEnd; i++)
{
if (specParams[i] is BracketParameter || specParams[i] is ReferenceParameter)
{
@@ -538,13 +563,24 @@ private MeasurementDefinition ParseFindWhen(MeasurementDefinition definition, Li
{
context.Result.ValidationResult.AddError(
ValidationEntrySource.Reader,
- ".MEAS FIND/WHEN: Missing signal between FIND and WHEN keywords");
+ ".MEAS FIND: Missing signal after FIND keyword");
return null;
}
- // Parse the WHEN condition
- var whenParams = specParams.GetRange(whenIndex, specParams.Count - whenIndex);
- ParseWhenCondition(whenParams, 1, definition, context);
+ if (atIndex >= 0)
+ {
+ // FIND ... AT= form
+ definition.Type = MeasType.FindAt;
+ var atParam = (AssignmentParameter)specParams[atIndex];
+ definition.At = context.Evaluator.EvaluateDouble(atParam.Value);
+ }
+ else
+ {
+ // FIND ... WHEN form
+ definition.Type = MeasType.FindWhen;
+ var whenParams = specParams.GetRange(whenIndex, specParams.Count - whenIndex);
+ ParseWhenCondition(whenParams, 1, definition, context);
+ }
return definition;
}
@@ -607,15 +643,18 @@ private void ParseWhenCondition(List specParams, int startIndex, Meas
break;
case "RISE":
definition.WhenEdge = EdgeType.Rise;
- definition.WhenEdgeNumber = Math.Max(1, (int)context.Evaluator.EvaluateDouble(edgeAp.Value));
+ definition.WhenEdgeNumber = ParseEdgeNumber(edgeAp.Value, context);
break;
case "FALL":
definition.WhenEdge = EdgeType.Fall;
- definition.WhenEdgeNumber = Math.Max(1, (int)context.Evaluator.EvaluateDouble(edgeAp.Value));
+ definition.WhenEdgeNumber = ParseEdgeNumber(edgeAp.Value, context);
break;
case "CROSS":
definition.WhenEdge = EdgeType.Cross;
- definition.WhenEdgeNumber = Math.Max(1, (int)context.Evaluator.EvaluateDouble(edgeAp.Value));
+ definition.WhenEdgeNumber = ParseEdgeNumber(edgeAp.Value, context);
+ break;
+ case "TD":
+ definition.WhenTd = context.Evaluator.EvaluateDouble(edgeAp.Value);
break;
case "FROM":
definition.From = context.Evaluator.EvaluateDouble(edgeAp.Value);
@@ -669,9 +708,23 @@ private MeasurementDefinition ParseStatistical(MeasurementDefinition definition,
private MeasurementDefinition ParseDeriv(MeasurementDefinition definition, List specParams, IReadingContext context)
{
// .MEAS TRAN name DERIV V(out) AT=
+ // .MEAS TRAN name DERIV V(out) WHEN V(in)=0.5
definition.Type = MeasType.Deriv;
+ // Check for WHEN keyword
+ int whenIndex = -1;
for (int i = 1; i < specParams.Count; i++)
+ {
+ if (specParams[i].Value.ToUpper() == "WHEN")
+ {
+ whenIndex = i;
+ break;
+ }
+ }
+
+ int parseEnd = whenIndex >= 0 ? whenIndex : specParams.Count;
+
+ for (int i = 1; i < parseEnd; i++)
{
var param = specParams[i];
if (param is AssignmentParameter ap)
@@ -703,17 +756,33 @@ private MeasurementDefinition ParseDeriv(MeasurementDefinition definition, List<
return null;
}
- if (!definition.At.HasValue)
+ if (whenIndex >= 0)
+ {
+ // DERIV ... WHEN form: find crossing point, then compute derivative there
+ var whenParams = specParams.GetRange(whenIndex, specParams.Count - whenIndex);
+ ParseWhenCondition(whenParams, 1, definition, context);
+ }
+ else if (!definition.At.HasValue)
{
context.Result.ValidationResult.AddError(
ValidationEntrySource.Reader,
- ".MEAS DERIV: Missing required AT= parameter");
+ ".MEAS DERIV: Missing required AT= or WHEN parameter");
return null;
}
return definition;
}
+ private static int ParseEdgeNumber(string value, IReadingContext context)
+ {
+ if (value.Equals("LAST", StringComparison.OrdinalIgnoreCase))
+ {
+ return EdgeConstants.Last;
+ }
+
+ return Math.Max(1, (int)context.Evaluator.EvaluateDouble(value));
+ }
+
private MeasurementDefinition ParseParam(MeasurementDefinition definition, List specParams)
{
// .MEAS TRAN name PARAM='expression' or PARAM {expression}
diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Measurements/MeasurementDefinition.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Measurements/MeasurementDefinition.cs
index 35f48b06..fd0f718d 100644
--- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Measurements/MeasurementDefinition.cs
+++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Measurements/MeasurementDefinition.cs
@@ -37,10 +37,22 @@ public enum MeasType
/// Compute the derivative of a signal at a point.
Deriv,
+ /// Find the value of a signal at a specific point (FIND ... AT=).
+ FindAt,
+
/// Compute a parameter expression from other measurement results.
Param,
}
+ ///
+ /// Sentinel value for RISE/FALL/CROSS=LAST.
+ ///
+ public static class EdgeConstants
+ {
+ /// Indicates the last matching edge should be used.
+ public const int Last = -1;
+ }
+
///
/// The type of edge to detect for threshold crossings.
///
@@ -119,9 +131,12 @@ public class MeasurementDefinition
/// Gets or sets the WHEN edge type.
public EdgeType WhenEdge { get; set; } = EdgeType.Cross;
- /// Gets or sets the WHEN edge number (1-based).
+ /// Gets or sets the WHEN edge number (1-based, or EdgeConstants.Last).
public int WhenEdgeNumber { get; set; } = 1;
+ /// Gets or sets the WHEN time delay offset.
+ public double? WhenTd { get; set; }
+
// --- FIND/WHEN properties ---
/// Gets or sets the FIND signal parameter (signal to evaluate at the WHEN crossing point).
diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Measurements/MeasurementEvaluator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Measurements/MeasurementEvaluator.cs
index fa8fa007..e07df009 100644
--- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Measurements/MeasurementEvaluator.cs
+++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Measurements/MeasurementEvaluator.cs
@@ -62,6 +62,8 @@ public MeasurementResult ComputeResult(string simulationName)
return ComputeWhen(simulationName);
case MeasType.FindWhen:
return ComputeFindWhen(simulationName);
+ case MeasType.FindAt:
+ return ComputeFindAt(simulationName);
case MeasType.Min:
return ComputeMin(simulationName);
case MeasType.Max:
@@ -93,7 +95,9 @@ public MeasurementResult ComputeResult(string simulationName)
double? fromX,
double? toX)
{
+ bool findLast = edgeNumber == EdgeConstants.Last;
int edgeCount = 0;
+ double? lastCrossing = null;
for (int i = 1; i < data.Count; i++)
{
@@ -152,21 +156,31 @@ public MeasurementResult ComputeResult(string simulationName)
}
edgeCount++;
- if (edgeCount == edgeNumber)
- {
- // Linear interpolation to find precise crossing point
- double dy = data[i].Y - data[i - 1].Y;
- if (Math.Abs(dy) < 1e-30)
- {
- return data[i].X;
- }
+ // Linear interpolation to find precise crossing point
+ double crossX;
+ double dy = data[i].Y - data[i - 1].Y;
+ if (Math.Abs(dy) < 1e-30)
+ {
+ crossX = data[i].X;
+ }
+ else
+ {
double fraction = (threshold - data[i - 1].Y) / dy;
- return data[i - 1].X + fraction * (data[i].X - data[i - 1].X);
+ crossX = data[i - 1].X + fraction * (data[i].X - data[i - 1].X);
+ }
+
+ if (findLast)
+ {
+ lastCrossing = crossX;
+ }
+ else if (edgeCount == edgeNumber)
+ {
+ return crossX;
}
}
- return null;
+ return findLast ? lastCrossing : null;
}
///
@@ -238,7 +252,7 @@ internal static double InterpolateY(List<(double X, double Y)> data, double targ
private MeasurementResult ComputeWhen(string simulationName)
{
- double? crossX = FindCrossing(_data, Definition.WhenVal, Definition.WhenEdge, Definition.WhenEdgeNumber, null, Definition.From, Definition.To);
+ double? crossX = FindCrossing(_data, Definition.WhenVal, Definition.WhenEdge, Definition.WhenEdgeNumber, Definition.WhenTd, Definition.From, Definition.To);
if (!crossX.HasValue)
{
return new MeasurementResult(Definition.Name, double.NaN, false, "WHEN", simulationName);
@@ -250,7 +264,7 @@ private MeasurementResult ComputeWhen(string simulationName)
private MeasurementResult ComputeFindWhen(string simulationName)
{
// Use the primary data (_data) for the WHEN condition
- double? crossX = FindCrossing(_data, Definition.WhenVal, Definition.WhenEdge, Definition.WhenEdgeNumber, null, Definition.From, Definition.To);
+ double? crossX = FindCrossing(_data, Definition.WhenVal, Definition.WhenEdge, Definition.WhenEdgeNumber, Definition.WhenTd, Definition.From, Definition.To);
if (!crossX.HasValue)
{
return new MeasurementResult(Definition.Name, double.NaN, false, "FIND_WHEN", simulationName);
@@ -262,6 +276,22 @@ private MeasurementResult ComputeFindWhen(string simulationName)
return new MeasurementResult(Definition.Name, value, true, "FIND_WHEN", simulationName);
}
+ private MeasurementResult ComputeFindAt(string simulationName)
+ {
+ if (!Definition.At.HasValue)
+ {
+ return new MeasurementResult(Definition.Name, double.NaN, false, "FIND_AT", simulationName);
+ }
+
+ if (_data.Count == 0)
+ {
+ return new MeasurementResult(Definition.Name, double.NaN, false, "FIND_AT", simulationName);
+ }
+
+ double value = InterpolateY(_data, Definition.At.Value);
+ return new MeasurementResult(Definition.Name, value, true, "FIND_AT", simulationName);
+ }
+
private MeasurementResult ComputeMin(string simulationName)
{
var windowed = GetWindowedData(_data);
@@ -403,13 +433,31 @@ private MeasurementResult ComputeInteg(string simulationName)
private MeasurementResult ComputeDeriv(string simulationName)
{
- if (!Definition.At.HasValue)
+ double targetX;
+
+ if (Definition.At.HasValue)
+ {
+ targetX = Definition.At.Value;
+ }
+ else if (Definition.WhenSignal != null)
+ {
+ // DERIV ... WHEN: find the crossing point first using _data (the WHEN signal)
+ double? crossX = FindCrossing(_data, Definition.WhenVal, Definition.WhenEdge, Definition.WhenEdgeNumber, Definition.WhenTd, Definition.From, Definition.To);
+ if (!crossX.HasValue)
+ {
+ return new MeasurementResult(Definition.Name, double.NaN, false, "DERIV", simulationName);
+ }
+
+ targetX = crossX.Value;
+ }
+ else
{
return new MeasurementResult(Definition.Name, double.NaN, false, "DERIV", simulationName);
}
- double targetX = Definition.At.Value;
- var windowed = GetWindowedData(_data);
+ // Use _findData for the derivative signal when WHEN is used, otherwise _data
+ var derivData = _findData.Count > 0 ? _findData : _data;
+ var windowed = GetWindowedData(derivData);
if (windowed.Count < 2)
{
diff --git a/src/SpiceSharpParser/SpiceSharpParser.csproj b/src/SpiceSharpParser/SpiceSharpParser.csproj
index 4b0e5218..541b5630 100644
--- a/src/SpiceSharpParser/SpiceSharpParser.csproj
+++ b/src/SpiceSharpParser/SpiceSharpParser.csproj
@@ -22,18 +22,14 @@
MIT
latest
- 3.2.10
+ 3.2.11
-
+
1701;1702;
-
-
-
-
diff --git a/src/docs/articles/meas.md b/src/docs/articles/meas.md
index 9e03e944..dc665cab 100644
--- a/src/docs/articles/meas.md
+++ b/src/docs/articles/meas.md
@@ -13,7 +13,7 @@ The `.MEAS` (or `.MEASURE`) statement extracts a single scalar number from simul
|-------|---------|--------|
| `analysis_type` | Which simulation type to measure | `TRAN`, `AC`, `DC`, `OP`, `NOISE` |
| `name` | A label you choose — this is the key used to look up the result in C# | Any identifier (e.g. `rise_time`, `vmax`) |
-| `measurement_spec` | What to measure and how (described in detail below) | One of the 11 measurement types |
+| `measurement_spec` | What to measure and how (described in detail below) | One of the 12 measurement types |
## Quick Reference — All Measurement Types
@@ -22,6 +22,7 @@ The `.MEAS` (or `.MEASURE`) statement extracts a single scalar number from simul
| `TRIG … TARG` | Time (or sweep) difference between two threshold crossings | [TRIG/TARG](#trigtarg--timing-measurements) |
| `WHEN` | Time (or sweep value) at which a signal crosses a threshold | [WHEN](#when--threshold-crossing-time) |
| `FIND … WHEN` | Value of signal A at the moment signal B crosses a threshold | [FIND/WHEN](#findwhen--value-at-a-threshold-crossing) |
+| `FIND … AT` | Value of a signal at a specific point on the abscissa | [FIND/AT](#findat--value-at-a-specific-point) |
| `MAX` | Maximum value of a signal | [MAX](#max--maximum-value) |
| `MIN` | Minimum value of a signal | [MIN](#min--minimum-value) |
| `AVG` | Time-weighted average (trapezoidal mean) | [AVG](#avg--average-value) |
@@ -39,12 +40,15 @@ These qualifiers can be combined with most measurement types:
|-----------|---------|---------|
| `VAL=` | Threshold voltage/current value | `VAL=5.0` |
| `RISE=` | Match the *n*th **rising** crossing | `RISE=1` (first rising edge) |
+| `RISE=LAST` | Match the **last** rising crossing | `RISE=LAST` |
| `FALL=` | Match the *n*th **falling** crossing | `FALL=2` (second falling edge) |
+| `FALL=LAST` | Match the **last** falling crossing | `FALL=LAST` |
| `CROSS=` | Match the *n*th crossing in **either** direction | `CROSS=3` |
-| `TD=