Skip to content

Commit ce5b9bb

Browse files
PWL REPEAT FOR + REPEAT FOREVER
1 parent b7528ed commit ce5b9bb

8 files changed

Lines changed: 978 additions & 46 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ LTspice mode covers syntax such as:
128128
- `.backanno` and selected output/viewer options as warning no-ops
129129
- one-argument `.TRAN` with a derived step policy
130130
- scalar expression aliases, `table(...)` / `tbl(...)`, and smooth limiters `uplim(...)` / `dnlim(...)`
131-
- source waveforms including `EXP(...)`, finite-cycle `PULSE(... Ncycles)`, finite-cycle `SINE(... Ncycles)`, and local two-column `PWL file=<path>` data
131+
- source waveforms including `EXP(...)`, finite-cycle `PULSE(... Ncycles)`, finite-cycle `SINE(... Ncycles)`, local two-column `PWL file=<path>` data, and simple LTspice `PWL REPEAT FOR` / `REPEAT FOREVER` blocks
132132
- independent-source topology options: `Rser`, `Cpar`, `load`, and `R=<value>`
133133
- model parameter aliases: resistor and capacitor models accept `tc=<tc1>[,<tc2>]`; voltage switches accept `von` / `voff`; current switches accept `ion` / `ioff`
134134
- switch model series options `Vser` and `Lser`

‎roadmap/ltspice-compatibility-matrix.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: LTspice Compatibility Matrix
3-
status: P3 Baseline + .FOUR Support Refresh + Custom Passives + Ideal Diodes + Golden Evidence + Include/Lib Path Evidence + Waveform Docs Refresh + PWL Text Variants + Finite SINE + Source/R-C/L Parasitics
3+
status: P3 Baseline + .FOUR Support Refresh + Custom Passives + Ideal Diodes + Golden Evidence + Include/Lib Path Evidence + Waveform Docs Refresh + PWL Text Variants + PWL Repeat Variants + Finite SINE + Source/R-C/L Parasitics
44
scope: SpiceSharpParser + SpiceSharp
55
last_reviewed: 2026-07-07
66
---
@@ -58,7 +58,8 @@ Compatibility classes:
5858
| `EXP(...)` source waveform | Accepted | Produces internal exponential waveform | TRAN supported | Analytic fixture plus optional LTspice-backed transient golden | Invalid count/tau diagnostics name `EXP` | Current parser/runtime | P2 supports the six-argument form `EXP(v1 v2 td1 tau1 td2 tau2)`. |
5959
| `PULSE(... Ncycles)` | Accepted in LTspice mode | Produces finite-cycle pulse waveform | TRAN supported | Analytic transient fixture plus optional LTspice-backed transient golden | Invalid period or cycle count produces targeted `PULSE` diagnostic | Current parser/runtime | The eighth LTspice argument limits the waveform to `Ncycles` periods, then returns to the initial value. |
6060
| `SINE(... Ncycles)` | Accepted in LTspice mode | Produces finite-cycle sine waveform | TRAN supported | Analytic transient fixture plus optional LTspice-backed transient golden | Invalid frequency or cycle count produces targeted `SINE` diagnostic | Current parser/runtime | The seventh LTspice argument limits the waveform to `Ncycles` periods after the delay, then returns to the offset value. |
61-
| `PWL file=<path>` source waveform | Accepted | Produces PWL waveform from a local two-column text file with optional header row | TRAN supported | Analytic transient fixture | Missing file, empty file, no data rows, malformed rows, and unsupported LTspice repeat forms produce targeted `PWL` diagnostics | Current parser/runtime | File data may start after leading blank lines or full-line comments beginning with `;`, `#`, `*`, or `//`; space, comma, semicolon, and tab delimiters are fixture-backed. Broader LTspice PWL repeat variants are deferred. |
61+
| `PWL file=<path>` source waveform | Accepted | Produces PWL waveform from a local two-column text file with optional header row | TRAN supported | Analytic transient fixture | Missing file, empty file, no data rows, and malformed rows produce targeted `PWL` diagnostics | Current parser/runtime | File data may start after leading blank lines or full-line comments beginning with `;`, `#`, `*`, or `//`; space, comma, semicolon, and tab delimiters are fixture-backed. |
62+
| `PWL REPEAT FOR <n>` / `REPEAT FOREVER` | Accepted in LTspice mode | Produces finite expanded PWL or runtime repeating PWL waveform | TRAN supported | Analytic transient fixture plus optional LTspice-backed transient golden for `REPEAT FOR` | Bare `REPEAT`, missing `ENDREPEAT`, bad repeat count, odd point data, non-increasing local times, contradictory zero-time boundaries, nested repeats, trailing parameters after `ENDREPEAT`, and unsupported combined forms produce targeted `PWL` diagnostics | Current parser/runtime | Supports simple non-nested repeat blocks with local repeat times. Time/value scale factors, `SCOPEDATA`, trigger restarts, relative `+time` points, and nested/combined PWL specs remain deferred. |
6263
| Independent source `tbl=(expr,x1,y1,...)` | Accepted in LTspice mode | Lowered to behavioral `table(...)` source | OP supported | Analytic fixture | Invalid form names `tbl` | Current parser/runtime | Parser shim uses the existing source/table expression path. |
6364
| Source `wavefile=<path> chan=<n> [amplitude=<value>]` | Accepted | Produces wave-file waveform | Existing wave-file behavior | Smoke/diagnostic fixture | Missing `wavefile`, missing `chan`, missing file, and invalid `chan` produce targeted validation | Current parser/runtime | Channel defaults are not inferred. |
6465
| Source `Rser`, `Cpar`, `load`, `R=<value>` | Accepted | Synthesizes helper R/C entities in LTspice mode | OP supported for resistance helpers; Cpar read fixture | Analytic/parser fixture plus optional LTspice-backed OP golden | Malformed value forms produce targeted LTspice source-option diagnostics | Current parser/runtime | Parser-only topology synthesis: `Rser` adds a series resistor, `Cpar` adds a shunt capacitor, `load` adds a shunt resistor, and `R=<value>` maps to series resistance on voltage sources and load resistance on current sources. Core source models are unchanged. |

‎roadmap/ltspice-netlist-compatibility-plan.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ This roadmap is intentionally parser-first and evidence-first:
3838
- `.TRAN` accepts traditional numeric forms and trailing `UIC`; P1 LTspice mode also accepts `.tran <Tstop>` and `.tran <Tstop> UIC` by deriving `step = Tstop / 50.0`. LTspice `startup`, `steady`, `nodiscard`, and `step` modifiers remain targeted errors.
3939
- LTspice output/viewer `.options` such as `plotwinsize`, `plotreltol`, `plotvntol`, `plotabstol`, `numdgt`, `measdgt`, `meascplxfmt`, `baudrate`, and `fastaccess` are warning no-ops only in LTspice mode.
4040
- LTspice behavior-changing `.options` such as `cshunt`, `gshunt`, `srcsteps`, `gminsteps`, `trtol`, `chgtol`, `pivrel`, `pivtol`, and `ptrantau` remain targeted errors.
41-
- Source waveform mappings cover `SIN` / `SINE`, `PULSE`, `EXP`, `PWL`, `AM`, `SFFM`, and wave-file input. LTspice finite-cycle `PULSE(... Ncycles)` and `SINE(... Ncycles)` are supported, PWL file parsing supports optional header rows, leading blank/comment lines, and space/comma/semicolon/tab delimiters, unsupported PWL repeat syntax produces targeted diagnostics, wave-file channel defaults are not inferred, and topology-changing independent-source options synthesize parser helper components.
41+
- Source waveform mappings cover `SIN` / `SINE`, `PULSE`, `EXP`, `PWL`, `AM`, `SFFM`, and wave-file input. LTspice finite-cycle `PULSE(... Ncycles)` and `SINE(... Ncycles)` are supported, PWL file parsing supports optional header rows, leading blank/comment lines, and space/comma/semicolon/tab delimiters, simple non-nested LTspice `PWL REPEAT FOR` / `REPEAT FOREVER` blocks are supported, broader PWL variants still produce targeted diagnostics, wave-file channel defaults are not inferred, and topology-changing independent-source options synthesize parser helper components.
4242
- MOS model generation currently covers legacy levels 1, 2, and 3. LTspice `VDMOS` and advanced monolithic levels such as BSIM/EKV/HiSIM variants are runtime or intentional-unsupported candidates.
4343
- Distributed-line support currently starts from lossless `T`. LTspice lossy `O` / `LTRA` and uniform RC-line `URC` models need engine triage before runnable support is claimed.
4444
- P3 LTspice mode maps R/C model `tc=a[,b]`, switch `von`/`voff`, and current-switch `ion`/`ioff` aliases where they lower to existing parameters.
@@ -212,7 +212,8 @@ Implemented P2 behavior:
212212
- Added LTspice-style smooth limiting support for `uplim(...)` and `dnlim(...)`, while keeping unary `~` as a targeted diagnostic.
213213
- Added six-argument `EXP(v1 v2 td1 tau1 td2 tau2)` source waveform support with argument-count and positive-tau diagnostics.
214214
- Added LTspice-mode finite-cycle `PULSE(... Ncycles)` and `SINE(... Ncycles)` support with targeted diagnostics for invalid period/frequency and cycle-count arguments.
215-
- Added PWL file fixtures for supported local two-column text variants with optional header rows, leading blank/comment lines, and space/comma/semicolon/tab delimiters, plus targeted diagnostics for missing files, empty files, missing data rows, malformed rows, and unsupported LTspice repeat syntax.
215+
- Added PWL file fixtures for supported local two-column text variants with optional header rows, leading blank/comment lines, and space/comma/semicolon/tab delimiters, plus targeted diagnostics for missing files, empty files, missing data rows, and malformed rows.
216+
- Added LTspice-mode support for simple non-nested `PWL REPEAT FOR <n>` and `REPEAT FOREVER` blocks with local repeat times, analytic transient fixtures, malformed-repeat diagnostics, and optional LTspice-backed transient golden evidence for `REPEAT FOR`.
216217
- Added LTspice-mode `tbl=(expr,x1,y1,...)` independent-source lowering to the existing behavioral `table(...)` path.
217218
- Improved `wavefile=<path> chan=<n> [amplitude=<value>]` validation so missing `wavefile`, missing `chan`, missing files, and invalid `chan` values produce targeted diagnostics.
218219
- Added LTspice-mode parser synthesis for topology-changing independent-source options: `Rser` adds a series resistor, `Cpar` adds a shunt capacitor, `load` adds a shunt resistor, and `R=<value>` maps to series resistance on voltage sources or load resistance on current sources.
@@ -221,7 +222,7 @@ Remaining follow-up:
221222

222223
- Compare existing random functions with LTspice semantics before making numeric claims.
223224
- Decide whether LTspice boolean XOR can be added without breaking existing `^` exponent behavior.
224-
- Defer LTspice PWL repeat variants until fixture-backed runtime behavior is specified.
225+
- Defer nested/combined LTspice PWL specs, trigger restarts, time/value scale factors, `SCOPEDATA`, and relative `+time` points until fixture-backed runtime behavior is specified.
225226

226227
Acceptance criteria:
227228

‎src/SpiceSharpParser.IntegrationTests/LTspiceCompatibility/LTspiceCompatibilityP2Tests.cs‎

Lines changed: 136 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,70 @@ public void When_LtspicePwlFileUsesSupportedTextVariant_Expect_TransientReferenc
131131
}
132132
}
133133

134+
[Fact]
135+
public void When_LtspicePwlRepeatForIsRead_Expect_ExpandedTransientReferenceValues()
136+
{
137+
var model = GetSpiceSharpModelWithCompatibility(
138+
CompatibilityOptions.LTspice,
139+
"LTspice P2 - PWL repeat for",
140+
"V1 out 0 PWL REPEAT FOR 3 (1n,1,3n,3) ENDREPEAT",
141+
"R1 out 0 1k",
142+
".tran 0.5n 9.5n",
143+
".save V(out)",
144+
".end");
145+
146+
AssertNoValidationIssues(model.ValidationResult);
147+
148+
var exports = RunTransientSimulation(model, "V(out)");
149+
Assert.NotEmpty(exports);
150+
Assert.True(EqualsWithTol(exports, PwlRepeatForReference));
151+
}
152+
153+
[Fact]
154+
public void When_LtspicePwlRepeatForeverIsRead_Expect_RepeatingTransientReferenceValues()
155+
{
156+
var model = GetSpiceSharpModelWithCompatibility(
157+
CompatibilityOptions.LTspice,
158+
"LTspice P2 - PWL repeat forever",
159+
"V1 out 0 PWL REPEAT FOREVER (0,0,1n,1,2n,0) ENDREPEAT",
160+
"R1 out 0 1k",
161+
".tran 0.5n 5n",
162+
".save V(out)",
163+
".end");
164+
165+
AssertNoValidationIssues(model.ValidationResult);
166+
167+
var exports = RunTransientSimulation(model, "V(out)");
168+
Assert.NotEmpty(exports);
169+
Assert.True(EqualsWithTol(exports, PwlRepeatTriangleReference));
170+
}
171+
172+
[Theory]
173+
[InlineData("V1 out 0 PWL REPEAT FOR 2 (0,0,1n,1,2n,0) ENDREPEAT")]
174+
[InlineData("V1 out 0 PWL REPEAT for=2 (0,0,1n,1,2n,0) ENDREPEAT")]
175+
[InlineData("V1 out 0 PWL repeat=2 (0,0,1n,1,2n,0) endrepeat")]
176+
public void When_LtspicePwlRepeatCountVariantIsRead_Expect_FiniteRepeat(string sourceLine)
177+
{
178+
var model = GetSpiceSharpModelWithCompatibility(
179+
CompatibilityOptions.LTspice,
180+
"LTspice P2 - PWL repeat count variant",
181+
sourceLine,
182+
"R1 out 0 1k",
183+
".tran 0.5n 5n",
184+
".save V(out)",
185+
".end");
186+
187+
AssertNoValidationIssues(model.ValidationResult);
188+
189+
var exports = RunTransientSimulation(model, "V(out)");
190+
Assert.NotEmpty(exports);
191+
Assert.True(EqualsWithTol(exports, PwlFiniteRepeatTriangleReference));
192+
}
193+
134194
[Theory]
135195
[InlineData("V1 out 0 PWL file=\"missing.txt\"", "does not exist")]
136-
[InlineData("V1 out 0 PWL repeat 0 0 1n 1 endrepeat", "repeat")]
137-
public void When_LtspicePwlFileOrRepeatSyntaxIsUnsupported_Expect_TargetedError(string sourceLine, string expectedMessage)
196+
[InlineData("V1 out 0 PWL repeat 0 0 1n 1 endrepeat", "FOR")]
197+
public void When_LtspicePwlFileOrBareRepeatSyntaxIsInvalid_Expect_TargetedError(string sourceLine, string expectedMessage)
138198
{
139199
var tempDirectory = CreateTempDirectory();
140200

@@ -160,6 +220,30 @@ public void When_LtspicePwlFileOrRepeatSyntaxIsUnsupported_Expect_TargetedError(
160220
}
161221
}
162222

223+
[Theory]
224+
[InlineData("V1 out 0 PWL REPEAT FOR 2 (0,0,1n,0)", "ENDREPEAT")]
225+
[InlineData("V1 out 0 PWL REPEAT FOR 0 (0,0,1n,0) ENDREPEAT", "repeat count")]
226+
[InlineData("V1 out 0 PWL REPEAT FOR 2 (0,0,1n) ENDREPEAT", "time/value")]
227+
[InlineData("V1 out 0 PWL REPEAT FOR 2 (0,0,0,0) ENDREPEAT", "increasing")]
228+
[InlineData("V1 out 0 PWL REPEAT FOR 2 (0,0,1n,1) ENDREPEAT", "contradictory")]
229+
[InlineData("V1 out 0 PWL REPEAT FOR 2 (0,0,+1n,0) ENDREPEAT", "relative")]
230+
[InlineData("V1 out 0 PWL ENDREPEAT", "requires")]
231+
public void When_LtspicePwlRepeatSyntaxIsMalformed_Expect_TargetedError(string sourceLine, string expectedMessage)
232+
{
233+
var model = GetSpiceSharpModelWithCompatibility(
234+
CompatibilityOptions.LTspice,
235+
"LTspice P2 - malformed PWL repeat",
236+
sourceLine,
237+
"R1 out 0 1k",
238+
".tran 1n 6n",
239+
".save V(out)",
240+
".end");
241+
242+
Assert.True(model.ValidationResult.HasError);
243+
AssertErrorContains(model.ValidationResult, "PWL");
244+
AssertErrorContains(model.ValidationResult, expectedMessage);
245+
}
246+
163247
[Theory]
164248
[InlineData("", "empty")]
165249
[InlineData("time,value", "no data")]
@@ -625,6 +709,56 @@ private static double PwlFileReference(double time)
625709
return Math.Max(0.0, (2e-9 - time) / 1e-9);
626710
}
627711

712+
private static double PwlRepeatForReference(double time)
713+
{
714+
const double period = 3e-9;
715+
const double firstPointTime = 1e-9;
716+
const double stopTime = 9e-9;
717+
718+
if (time >= stopTime)
719+
{
720+
return 3.0;
721+
}
722+
723+
var cycle = (int)Math.Floor(time / period);
724+
var localTime = time - (cycle * period);
725+
if (localTime < firstPointTime)
726+
{
727+
if (cycle == 0)
728+
{
729+
return 1.0;
730+
}
731+
732+
return 3.0 + ((1.0 - 3.0) * localTime / firstPointTime);
733+
}
734+
735+
return 1.0 + ((3.0 - 1.0) * (localTime - firstPointTime) / (period - firstPointTime));
736+
}
737+
738+
private static double PwlRepeatTriangleReference(double time)
739+
{
740+
const double halfPeriod = 1e-9;
741+
const double period = 2e-9;
742+
var localTime = time % period;
743+
if (localTime <= halfPeriod)
744+
{
745+
return localTime / halfPeriod;
746+
}
747+
748+
return (period - localTime) / halfPeriod;
749+
}
750+
751+
private static double PwlFiniteRepeatTriangleReference(double time)
752+
{
753+
const double stopTime = 4e-9;
754+
if (time >= stopTime)
755+
{
756+
return 0.0;
757+
}
758+
759+
return PwlRepeatTriangleReference(time);
760+
}
761+
628762
public static IEnumerable<object[]> PwlFileFormatCases()
629763
{
630764
yield return new object[]

0 commit comments

Comments
 (0)