|
| 1 | +# Function-Style LAPLACE And B-Source LAPLACE |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +Add `LAPLACE(input, transfer)` support inside behavioral source expressions, including `VALUE={...}`, `VALUE {...}`, and `B ... V=/I=` forms. The implementation should be AST-based: parse the behavioral expression, detect `LAPLACE(...)` calls, validate their arguments, and lower them into existing SpiceSharp Laplace source entities. |
| 6 | + |
| 7 | +There are two target shapes: |
| 8 | + |
| 9 | +- A whole-expression `LAPLACE(...)` maps directly to an existing Laplace source entity. |
| 10 | +- A mixed expression, such as `1 + 2*LAPLACE(...)`, is lowered by generating internal voltage-output Laplace helper sources and replacing each call with `V(<helperNode>)` in the final behavioral expression. |
| 11 | + |
| 12 | +This keeps runtime behavior on the existing SpiceSharp Laplace components instead of inventing a new behavioral runtime function. |
| 13 | + |
| 14 | +## Goals |
| 15 | + |
| 16 | +- Support function-style Laplace expressions in all expression-based voltage/current source paths. |
| 17 | +- Support `B` sources: |
| 18 | + - `B<name> <out+> <out-> V={LAPLACE(...)}` |
| 19 | + - `B<name> <out+> <out-> I={LAPLACE(...)}` |
| 20 | + - mixed `B` expressions containing one or more `LAPLACE(...)` calls. |
| 21 | +- Support `VALUE` forms on existing source generators: |
| 22 | + - `E`, `V`, and `H` expression outputs as voltage sources. |
| 23 | + - `G`, `I`, and `F` expression outputs as current sources. |
| 24 | +- Keep existing source-level `E/G/F/H ... LAPLACE ...` behavior unchanged. |
| 25 | +- Reuse the existing Laplace transfer parser, coefficient normalization, validation messages, and entity types wherever practical. |
| 26 | +- Preserve current non-Laplace behavioral source behavior. |
| 27 | + |
| 28 | +## Out Of Scope |
| 29 | + |
| 30 | +- Inline option arguments inside the function call, for example `LAPLACE(V(in), H(s), TD=1n)`. |
| 31 | +- Arbitrary Laplace input expressions, for example `LAPLACE(V(a)-V(b), H(s))`. |
| 32 | +- Function-style delay expressions such as `exp(-s*td)` or broader PSpice/LTspice dialect forms. |
| 33 | +- Supporting `LAPLACE(...)` inside `.FUNC` definitions as a dynamic function. |
| 34 | +- Hiding generated helper entities from every low-level circuit inspection API. |
| 35 | + |
| 36 | +## Supported Syntax |
| 37 | + |
| 38 | +The supported function signature is: |
| 39 | + |
| 40 | +```spice |
| 41 | +LAPLACE(<input>, <transfer>) |
| 42 | +``` |
| 43 | + |
| 44 | +Accepted input shapes: |
| 45 | + |
| 46 | +```spice |
| 47 | +V(node) |
| 48 | +V(node1,node2) |
| 49 | +I(source) |
| 50 | +``` |
| 51 | + |
| 52 | +Accepted output contexts: |
| 53 | + |
| 54 | +```spice |
| 55 | +E1 out 0 VALUE={LAPLACE(V(in), 1/(1+s*tau))} |
| 56 | +G1 out 0 VALUE={LAPLACE(V(in), gm/(1+s*tau))} |
| 57 | +V1 out 0 VALUE={LAPLACE(V(in), wc/(s+wc))} |
| 58 | +I1 out 0 VALUE={LAPLACE(V(in), gm*wc/(s+wc))} |
| 59 | +B1 out 0 V={LAPLACE(V(in), 1/(1+s*tau))} |
| 60 | +B2 out 0 I={LAPLACE(V(in), gm/(1+s*tau))} |
| 61 | +B3 out 0 V={1 + 2*LAPLACE(V(in), 1/(1+s))} |
| 62 | +B4 out 0 I={LAPLACE(V(a), 1/(1+s*t1)) - LAPLACE(V(b), 1/(1+s*t2))} |
| 63 | +``` |
| 64 | + |
| 65 | +The function name is case-insensitive. Whitespace inside the expression should follow the existing behavioral expression parser rules. |
| 66 | + |
| 67 | +## Entity Mapping |
| 68 | + |
| 69 | +When the entire expression is exactly one `LAPLACE(...)` call, create the final Laplace entity directly: |
| 70 | + |
| 71 | +| Output kind | Input kind | Entity | |
| 72 | +| --- | --- | --- | |
| 73 | +| Voltage | Voltage | `LaplaceVoltageControlledVoltageSource` | |
| 74 | +| Voltage | Current | `LaplaceCurrentControlledVoltageSource` | |
| 75 | +| Current | Voltage | `LaplaceVoltageControlledCurrentSource` | |
| 76 | +| Current | Current | `LaplaceCurrentControlledCurrentSource` | |
| 77 | + |
| 78 | +Output kind is determined by the source path: |
| 79 | + |
| 80 | +| Source form | Output kind | |
| 81 | +| --- | --- | |
| 82 | +| `E ... VALUE=...` | Voltage | |
| 83 | +| `H ... VALUE=...` | Voltage | |
| 84 | +| `V ... VALUE=...` | Voltage | |
| 85 | +| `B ... V=...` | Voltage | |
| 86 | +| `G ... VALUE=...` | Current | |
| 87 | +| `F ... VALUE=...` | Current | |
| 88 | +| `I ... VALUE=...` | Current | |
| 89 | +| `B ... I=...` | Current | |
| 90 | + |
| 91 | +For mixed expressions, always lower each `LAPLACE(...)` call to a voltage-output helper: |
| 92 | + |
| 93 | +| Laplace input kind | Helper entity | |
| 94 | +| --- | --- | |
| 95 | +| Voltage input | `LaplaceVoltageControlledVoltageSource` connected from helper node to `0` | |
| 96 | +| Current input | `LaplaceCurrentControlledVoltageSource` connected from helper node to `0` | |
| 97 | + |
| 98 | +Then replace the original `LAPLACE(...)` call in the final behavioral expression with: |
| 99 | + |
| 100 | +```spice |
| 101 | +V(<helperNode>) |
| 102 | +``` |
| 103 | + |
| 104 | +The final behavioral source remains a `BehavioralVoltageSource` or `BehavioralCurrentSource` according to the output kind. |
| 105 | + |
| 106 | +## Lowering Algorithm |
| 107 | + |
| 108 | +1. Identify the expression-bearing parameter: |
| 109 | + - `AssignmentParameter` named `VALUE`, `V`, or `I`. |
| 110 | + - `WordParameter` `VALUE` followed by an `ExpressionParameter`. |
| 111 | + - bare `ExpressionParameter` paths that are already treated as behavioral expressions. |
| 112 | +2. Parse the raw expression with the existing expression parser into an AST. |
| 113 | +3. Traverse the AST and find case-insensitive `FunctionNode` calls named `laplace`. |
| 114 | +4. Do not traverse inside a `laplace` call's arguments for further lowering. Nested calls inside input or transfer arguments are invalid through normal input/transfer validation. |
| 115 | +5. Validate each `laplace` call: |
| 116 | + - exactly two arguments; |
| 117 | + - first argument is a supported voltage or current probe shape; |
| 118 | + - second argument is a proper rational polynomial in `s`; |
| 119 | + - finite coefficients; |
| 120 | + - finite, non-singular DC gain; |
| 121 | + - denominator is non-zero; |
| 122 | + - transfer order stays within existing `LaplaceExpressionOptions.MaxOrder`. |
| 123 | +6. If zero calls are found, return "not handled" and let existing behavioral logic run unchanged. |
| 124 | +7. If the root AST node is exactly a single `laplace` call: |
| 125 | + - create the direct final Laplace entity from the mapping table above; |
| 126 | + - no helper node is needed. |
| 127 | +8. If the expression is mixed: |
| 128 | + - allocate one helper source and helper node per call; |
| 129 | + - replace the call node with a voltage probe node for the helper node; |
| 130 | + - serialize the modified AST back to a behavioral expression string; |
| 131 | + - create helper entities first; |
| 132 | + - create and return the final behavioral source using the modified expression. |
| 133 | + |
| 134 | +## AST And Formatting Requirements |
| 135 | + |
| 136 | +- Do not locate or replace `LAPLACE(...)` using string slicing or regular expressions. |
| 137 | +- Add internal APIs so existing Laplace parsers can work from parsed `Node` instances: |
| 138 | + - `LaplaceExpressionParser.Parse(Node node)` |
| 139 | + - input parsing from a `Node` for `V(...)` and `I(...)`. |
| 140 | +- Add a small internal behavioral expression formatter for modified ASTs. It should emit syntax accepted by the existing behavioral parser: |
| 141 | + - binary operators with parentheses; |
| 142 | + - `**` for power if that is what existing expressions expect; |
| 143 | + - `V(node)` and `I(source)` probes; |
| 144 | + - normal function calls for non-Laplace functions; |
| 145 | + - ternary and unary operators where already supported. |
| 146 | +- Keep reader and writer formatting code separate from C# interpolation formatting. The reader needs a SPICE expression string; the writer needs generated C# statements. |
| 147 | + |
| 148 | +## Options And Multipliers |
| 149 | + |
| 150 | +Existing source-level `E/G/F/H ... LAPLACE ...` option behavior remains unchanged. |
| 151 | + |
| 152 | +For function-style `LAPLACE(...)` expressions: |
| 153 | + |
| 154 | +- `TD=` and `DELAY=` are accepted as trailing source-level parameters only if exactly one `LAPLACE(...)` call is present. |
| 155 | +- The delay option applies to that direct Laplace entity or helper source. |
| 156 | +- If more than one `LAPLACE(...)` call is present and a trailing `TD=` or `DELAY=` option is present, emit a reader validation error. |
| 157 | +- `M=` must preserve existing current-source behavior: |
| 158 | + - for source-level Laplace syntax, keep folding `M` into the Laplace numerator as today; |
| 159 | + - for direct whole-expression function-style Laplace, folding `M` into the numerator is acceptable when it is semantically equivalent; |
| 160 | + - for mixed current-source behavioral expressions, apply existing `M` behavior to the final current source expression, not to every helper; |
| 161 | + - for voltage-output `VALUE`/`B V=` mixed expressions, reject trailing `M=` unless an existing source path already supports it. |
| 162 | + |
| 163 | +This avoids changing existing `B ... I={expr} M=...` behavior while still allowing direct function-style Laplace to use the current Laplace coefficient path. |
| 164 | + |
| 165 | +## Naming And Collision Rules |
| 166 | + |
| 167 | +Generated helpers should use deterministic, reserved names: |
| 168 | + |
| 169 | +```text |
| 170 | +__ssp_laplace_<sanitizedSourceName>_<index> |
| 171 | +__ssp_laplace_<sanitizedSourceName>_<index>_src |
| 172 | +``` |
| 173 | + |
| 174 | +Rules: |
| 175 | + |
| 176 | +- Sanitize to ASCII letters, digits, and underscores. |
| 177 | +- Prefix with `__ssp_laplace_`. |
| 178 | +- Use a stable index based on traversal order. |
| 179 | +- If the generated entity name already exists in `context.ContextEntities`, append an incrementing suffix. |
| 180 | +- Helper node names should be local names and go through the same name-generation/scoping path as ordinary node names when subcircuits are expanded. |
| 181 | +- The final rewritten behavioral expression should reference the local helper node name. Existing expression resolution should scope it during subcircuit expansion. |
| 182 | + |
| 183 | +## Reader Implementation Plan |
| 184 | + |
| 185 | +1. Refactor Laplace transfer parsing. |
| 186 | + - Add `LaplaceExpressionParser.Parse(Node node)`. |
| 187 | + - Keep `Parse(string expression)` as a wrapper that parses the string then calls `Parse(Node)`. |
| 188 | + - Keep current validation messages unless the function-style path needs a more specific "laplace function expects two arguments" message. |
| 189 | + |
| 190 | +2. Refactor Laplace input parsing. |
| 191 | + - Move voltage/current input parsing out of private string-only helpers or add parallel `Node`-based helpers. |
| 192 | + - Preserve existing string parser behavior for source-level forms. |
| 193 | + - Node-based validation should accept the same shapes as existing source-level validation. |
| 194 | + |
| 195 | +3. Add lowerer types in the source generator area. |
| 196 | + - `LaplaceFunctionExpressionLowerer` |
| 197 | + - `LaplaceFunctionLoweringResult` |
| 198 | + - `LaplaceFunctionCallDefinition` |
| 199 | + - `LaplaceOutputKind` |
| 200 | + - Keep them internal. |
| 201 | + |
| 202 | +4. Add entity factory helpers. |
| 203 | + - Share direct entity creation between `VoltageSourceGenerator`, `CurrentSourceGenerator`, `ArbitraryBehavioralGenerator`, and the lowerer. |
| 204 | + - Avoid duplicating numerator/denominator/delay assignment logic. |
| 205 | + - Do not change the public `IComponentGenerator.Generate(...)` interface. |
| 206 | + |
| 207 | +5. Update generators. |
| 208 | + - Remove the current early rejection for `LAPLACE(...)`. |
| 209 | + - Before normal behavioral source creation, invoke the lowerer. |
| 210 | + - If the lowerer returns a direct final entity, return it. |
| 211 | + - If the lowerer returns helpers and a final behavioral expression, add helpers to `context.ContextEntities` and return the final behavioral source. |
| 212 | + - If the lowerer reports validation errors, return `null`. |
| 213 | + - If the lowerer finds no `LAPLACE(...)`, continue current behavior unchanged. |
| 214 | + |
| 215 | +6. Keep parse actions correct. |
| 216 | + - Final behavioral sources created after mixed lowering must still set `Parameters.Expression`. |
| 217 | + - `Parameters.ParseAction` must use the existing simulation-aware resolver setup. |
| 218 | + - If the rewritten expression has functions or spice properties, retain the current before-setup parse-action refresh behavior. |
| 219 | + |
| 220 | +## Writer Implementation Plan |
| 221 | + |
| 222 | +1. Add writer-side lowering support in `SourceWriterHelper`. |
| 223 | + - Reuse the same AST parser and validation logic. |
| 224 | + - Use the writer evaluation context for parameters. |
| 225 | + - Emit comments when lowering fails, matching existing writer error style. |
| 226 | + |
| 227 | +2. Update `ArbitraryBehavioralWriter`. |
| 228 | + - Route `V=` and `I=` expressions through the new writer helper before plain behavioral output. |
| 229 | + |
| 230 | +3. Update `VoltageSourceWriter` and `CurrentSourceWriter`. |
| 231 | + - Route `VALUE` and expression-style behavioral paths through the writer helper before plain behavioral output. |
| 232 | + |
| 233 | +4. Emit helper C# statements before final behavioral source statements for mixed expressions. |
| 234 | + |
| 235 | +5. Ensure generated C# uses the same helper names and final expression shape as the reader path. |
| 236 | + |
| 237 | +## Validation And Diagnostics |
| 238 | + |
| 239 | +Use reader validation errors, not runtime exceptions, for expected invalid netlist forms. |
| 240 | + |
| 241 | +Add or preserve clear messages for: |
| 242 | + |
| 243 | +- `laplace function expects exactly two arguments` |
| 244 | +- `laplace input expression must be V(node), V(node1,node2), or I(source)` |
| 245 | +- `laplace transfer expression must be a rational polynomial in s` |
| 246 | +- `laplace transfer function is improper; numerator degree exceeds denominator degree` |
| 247 | +- `laplace transfer function has singular DC gain` |
| 248 | +- `laplace transfer coefficients must be finite` |
| 249 | +- `laplace delay can be specified only once` |
| 250 | +- `laplace delay must be non-negative` |
| 251 | +- `laplace source-level delay options can be used only when one LAPLACE call is present` |
| 252 | +- `laplace option arguments inside LAPLACE(...) are not supported` |
| 253 | + |
| 254 | +For mixed expressions, include the parent source line info when possible. For transfer-specific errors, keep the source expression line info if individual argument line info is not available. |
| 255 | + |
| 256 | +## Test Plan |
| 257 | + |
| 258 | +### Unit Tests |
| 259 | + |
| 260 | +Add tests around the lowerer and updated source generators: |
| 261 | + |
| 262 | +- Direct `E ... VALUE={LAPLACE(V(in), 1/(1+s*tau))}` creates `LaplaceVoltageControlledVoltageSource`. |
| 263 | +- Direct `G ... VALUE={LAPLACE(V(in), gm/(1+s*tau))}` creates `LaplaceVoltageControlledCurrentSource`. |
| 264 | +- Direct `H ... VALUE={LAPLACE(I(VSENSE), 1000/(s+1000))}` creates `LaplaceCurrentControlledVoltageSource`. |
| 265 | +- Direct `F ... VALUE={LAPLACE(I(VSENSE), 1/(1+s))}` creates `LaplaceCurrentControlledCurrentSource`. |
| 266 | +- `B ... V={LAPLACE(V(in), 1/(1+s))}` creates a voltage-output Laplace entity. |
| 267 | +- `B ... I={LAPLACE(V(in), gm/(1+s))}` creates a current-output Laplace entity. |
| 268 | +- Mixed `B ... V={1 + 2*LAPLACE(V(in), 1/(1+s))}` creates one helper plus final behavioral voltage source. |
| 269 | +- Mixed `B ... I={LAPLACE(V(a), 1/(1+s*t1)) - LAPLACE(V(b), 1/(1+s*t2))}` creates two helpers plus final behavioral current source. |
| 270 | +- Mixed expressions preserve non-Laplace functions such as `if()`, `abs()`, and parameters. |
| 271 | +- Source-level `TD=` and `DELAY=` apply with one function call and fail with two calls. |
| 272 | +- Current-source `M=` behavior is preserved for mixed expressions. |
| 273 | +- Invalid function argument count fails. |
| 274 | +- Invalid input shape fails. |
| 275 | +- Invalid transfer fails. |
| 276 | +- User-defined `.FUNC LAPLACE(...)` does not override built-in detection. |
| 277 | + |
| 278 | +### Integration Tests |
| 279 | + |
| 280 | +Add OP, AC, and focused transient coverage: |
| 281 | + |
| 282 | +- OP parity: `E VALUE={LAPLACE(...)}` equals source-level `E ... LAPLACE ...`. |
| 283 | +- OP parity: `B V={LAPLACE(...)}` equals equivalent `E ... LAPLACE ...`. |
| 284 | +- OP parity: `B I={LAPLACE(...)}` through a grounded resistor matches expected current-source sign. |
| 285 | +- AC low-pass cutoff magnitude and phase for `VALUE={LAPLACE(...)}`. |
| 286 | +- AC mixed expression: DC offset plus low-pass term has expected low-frequency and cutoff behavior. |
| 287 | +- Multiple-call AC expression combines two Laplace helper outputs correctly. |
| 288 | +- Transient smoke test for a mixed first-order low-pass expression. |
| 289 | +- Subcircuit expansion smoke test to verify helper node/entity names are scoped and do not collide. |
| 290 | + |
| 291 | +### Writer Tests |
| 292 | + |
| 293 | +- Direct function-style VALUE emits the matching Laplace constructor and parameters. |
| 294 | +- Direct B source LAPLACE emits the matching Laplace constructor and parameters. |
| 295 | +- Mixed expression emits helper Laplace source statements before the final behavioral source statement. |
| 296 | +- Writer emits useful comments for invalid function-style Laplace expressions. |
| 297 | + |
| 298 | +### Regression Tests |
| 299 | + |
| 300 | +- Existing source-level `E/G/F/H ... LAPLACE ...` tests continue passing. |
| 301 | +- Existing non-Laplace `VALUE`, `B V=`, and `B I=` tests continue passing. |
| 302 | +- Existing unsupported forms still fail if they are still out of scope. |
| 303 | + |
| 304 | +## Documentation Updates |
| 305 | + |
| 306 | +Update: |
| 307 | + |
| 308 | +- `src/docs/articles/laplace.md` |
| 309 | +- `src/docs/articles/laplace-basics.md` |
| 310 | +- `src/docs/articles/behavioral-source.md` |
| 311 | +- `src/docs/articles/vcvs.md` |
| 312 | +- `src/docs/articles/vccs.md` |
| 313 | +- `src/docs/articles/voltage-source.md` |
| 314 | +- `src/docs/articles/current-source.md` |
| 315 | +- `src/docs/articles/intro.md` |
| 316 | + |
| 317 | +Docs should cover: |
| 318 | + |
| 319 | +- Function-style syntax. |
| 320 | +- B source voltage and current examples. |
| 321 | +- Mixed expression examples. |
| 322 | +- Input and transfer limitations. |
| 323 | +- `TD=` / `DELAY=` limitation for multiple calls. |
| 324 | +- The fact that helper entities are an implementation detail and may appear during low-level circuit inspection. |
| 325 | + |
| 326 | +Remove "not supported yet" statements for `VALUE={LAPLACE(...)}` and `B` source LAPLACE forms once implementation and tests are complete. |
| 327 | + |
| 328 | +## Acceptance Criteria |
| 329 | + |
| 330 | +- Supported netlists read without validation errors. |
| 331 | +- Unsupported function-style forms fail during reading with clear validation errors. |
| 332 | +- OP, AC, and transient tests pass for direct and mixed forms. |
| 333 | +- Generated C# output matches reader behavior for direct and mixed forms. |
| 334 | +- Existing source-level Laplace and non-Laplace behavioral source behavior is unchanged. |
| 335 | +- Documentation accurately reflects the supported subset. |
| 336 | + |
| 337 | +## Risks |
| 338 | + |
| 339 | +- Mixed-expression lowering creates extra entities and nodes. The names must be collision-resistant and stable. |
| 340 | +- AST formatting must produce expressions that the existing behavioral parser can parse again. |
| 341 | +- Current-source `M=` has existing semantics. The implementation must not accidentally apply it twice or move it from the final behavioral expression to every helper. |
| 342 | +- Subcircuit expansion may expose naming bugs if helper nodes are generated with already-scoped names instead of local names. |
| 343 | +- Writer support can drift from reader behavior unless both share the parser/lowering model. |
| 344 | + |
| 345 | +## Assumptions |
| 346 | + |
| 347 | +- The existing SpiceSharp Laplace source components are the correct runtime implementation for all supported function-style forms. |
| 348 | +- Helper voltage outputs are a valid numeric representation of each `LAPLACE(...)` term inside mixed expressions. |
| 349 | +- Internal helper entities may be visible during low-level circuit inspection, but their names are reserved and deterministic. |
| 350 | +- Inline function options and arbitrary input expressions remain future work. |
0 commit comments