You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add Xyce simulator support behind SimulatorInterface abstraction
Extract shared state and logic from NgspiceInterface into a
SimulatorInterface ABC, add XyceInterface for explicit-stepping
co-simulation via Xyce's C API, and make netlist generation
simulator-aware (YDAC devices, .TRAN, .PRINT TRAN for Xyce).
AnalogBlock gains a simulator="ngspice"|"xyce" parameter;
MixedSignalBridge gains simulator_lib= (ngspice_lib deprecated).
All existing tests pass unchanged. Remove deprecated sync_period_ns.
cocotbext-ams bridges cocotb's digital simulation with an analog SPICE simulator (ngspice or Xyce) via shared library APIs (ctypes). It uses **event-driven synchronization** rather than fixed-interval lock-step.
25
+
26
+
### Supported Simulators
27
+
28
+
-**ngspice** (default): Callback-driven via libngspice. `GetSyncData` callback triggers sync points.
29
+
-**Xyce**: Explicit-stepping via `xyce_simulateUntil()`. The stepping loop triggers sync points at each interval.
30
+
31
+
Both run inside a `@bridge` thread and periodically call `_on_sync_point()` (a `@resume` function). The difference is only what triggers that call — ngspice's internal callback vs Xyce's explicit loop.
32
+
33
+
### Module Dependency Flow
34
+
35
+
```
36
+
_bridge.py (MixedSignalBridge, AnalogBlock)
37
+
├── _simulator.py (SimulatorInterface — ABC with shared state)
38
+
│ ├── _ngspice.py (NgspiceInterface — ctypes wrapper for libngspice)
39
+
│ └── _xyce.py (XyceInterface — ctypes wrapper for Xyce C API)
40
+
├── _pins.py (DigitalPin — D/A and A/D conversion with hysteresis)
`SimulatorInterface` ABC holds all shared state (`_vsrc_values`, `_node_voltages`, `_spice_time`, `_next_sync_time`, `_prev_digital_values`, `_output_pin_configs`, `_vcd_writer`, etc.) and implements `_check_crossings()` and `_write_vcd()`. Subclasses implement `load_circuit()`, `run_simulation()`, `get_node_voltage()`, `set_vsrc()`, `halt()`, `reset()`, `is_running()`.
48
+
49
+
### Two Asymmetric Data Paths
50
+
51
+
**Digital → Analog:**`ValueChange` monitor coroutines update `_vsrc_values` dict instantly. For ngspice, it reads these via `GetVSRCData` callback. For Xyce, they are pushed via `xyce_updateTimeVoltagePairs()` at each step.
52
+
53
+
**Analog → Digital:** Voltages are read from the simulator and `_check_crossings()` detects threshold crossings. When a crossing is detected, the sync mechanism forces new digital values onto Verilog and advances digital time.
54
+
55
+
A fallback `max_sync_interval_ns` (default 100ns) ensures periodic sync even without crossings.
56
+
57
+
### Thread Model
58
+
59
+
- The simulator runs a blocking simulation in a `@bridge` thread
60
+
-**ngspice:**`GetVSRCData` / `SendData` / `GetSyncData` callbacks fire from the ngspice thread; `GetSyncData` calls `@resume` to sync
61
+
-**Xyce:** The explicit stepping loop calls `@resume` at each sync interval
62
+
-`_vsrc_values` dict is safe via GIL (cocotb writes, simulator reads)
63
+
-`_node_voltages` is only read at sync points when the simulator is paused
64
+
65
+
### Netlist Augmentation
66
+
67
+
`_netlist.py` wraps the user's `.subckt` with simulator-specific syntax:
The `simulator=` parameter on `generate_netlist()` dispatches to the right generator.
78
+
79
+
### Vector Name Normalization
80
+
81
+
ngspice reports names as `"tran1.v(d0)"`, `"v(d0)"`, or `"d0"`. All forms are stored in `_node_voltages` so lookups work with any variant. Xyce stores both the expression form (`v(d0)`) and bare name (`d0`).
82
+
83
+
## Testing Conventions
84
+
85
+
- Unit tests in `tests/` do NOT require ngspice or Xyce — they test pins, netlist generation, and VCD writing in isolation.
86
+
- Tests that need `NgspiceInterface` create mock instances via `__new__` and manually set required attributes (`_node_voltages`, `_crossing_detected`, `_prev_digital_values`, `_output_pin_configs`, `_spice_time`). These attributes now come from `SimulatorInterface.__init__()`.
87
+
- Xyce netlist generation is tested via `test_generate_xyce_netlist()` without requiring Xyce to be installed.
88
+
- Integration tests live in `examples/` and require ngspice + iverilog. Xyce integration testing requires Xyce installed.
89
+
90
+
## Git Commit Rules
91
+
92
+
- Never add "Co-Authored-By: Claude" or any Claude attribution to commit messages.
|`analog_blocks`| List of `AnalogBlock` descriptions |
295
+
|`max_sync_interval_ns`| Maximum time between sync points in nanoseconds (default 100.0) |
296
+
|`simulator_lib`| Path to the simulator shared library (auto-detected if None) |
297
+
244
298
| Method | Description |
245
299
|--------|-------------|
246
300
|`await start(duration_ns, analog_vcd=None, vcd_nodes=None)`| Load circuit, start co-simulation. Pass `analog_vcd="file.vcd"` to record analog waveforms. `vcd_nodes` adds extra SPICE nodes beyond the auto-included output pins. |
247
301
|`await stop()`| Halt simulation, release forced signals |
248
302
|`set_analog_input(block, name, voltage)`| Change an analog input voltage at runtime |
249
303
|`get_analog_voltage(block, node)`| Probe any SPICE node voltage |
250
304
251
-
> **Migration note:** The old `sync_period_ns` parameter still works but emits a `DeprecationWarning`. Rename it to `max_sync_interval_ns`.
305
+
> **Migration note:** The old `ngspice_lib` parameter still works but emits a `DeprecationWarning`. Rename it to `simulator_lib`.
252
306
253
307
### Sync interval selection
254
308
@@ -275,10 +329,21 @@ paths, runtime analog control, VCD export, and waveform viewing.
275
329
276
330
## Architecture Details
277
331
332
+
### Simulator abstraction
333
+
334
+
Both ngspice and Xyce inherit from `SimulatorInterface`, which holds all
335
+
shared state (voltage source values, node voltages, crossing detection,
336
+
VCD writer) and implements common logic (`_check_crossings()`,
337
+
`_write_vcd()`). Subclasses implement the simulator-specific ctypes wrapper
338
+
and control flow.
339
+
278
340
### Thread model
279
341
280
342
The bridge uses cocotb's `@bridge` / `@resume` mechanism for thread
281
-
synchronization:
343
+
synchronization. Both simulators run a blocking simulation in a
344
+
`@bridge` thread and periodically call a `@resume` function at sync points:
345
+
346
+
**ngspice:**
282
347
283
348
1.`@bridge` runs ngspice's blocking `tran` command in a dedicated thread.
284
349
2.`GetVSRCData` fires on every ngspice evaluation step, reading the
@@ -288,42 +353,63 @@ synchronization:
288
353
4.`GetSyncData` fires at each internal timestep. If a crossing was detected
289
354
(or the fallback interval elapsed), it calls a `@resume` function that
290
355
blocks the ngspice thread and transfers control to the cocotb scheduler.
356
+
357
+
**Xyce:**
358
+
359
+
1.`@bridge` runs an explicit stepping loop in a dedicated thread.
360
+
2. At each step: push VSRC values via `xyce_updateTimeVoltagePairs()`,
361
+
advance via `xyce_simulateUntil()`, read voltages via
362
+
`xyce_obtainResponse()`, check crossings.
363
+
3. At sync intervals, calls the same `@resume` function as ngspice.
364
+
365
+
**Common to both:**
366
+
291
367
5. The cocotb scheduler forces new digital values onto Verilog and advances
292
368
digital time by the actual elapsed SPICE time via `await Timer(...)`.
293
-
6. When the `@resume` function returns, the ngspice thread resumes.
369
+
6. When the `@resume` function returns, the simulator thread resumes.
294
370
295
371
This is event-driven: sync only happens when analog outputs actually cross
296
372
a digital threshold, or at the fallback ceiling interval.
297
373
298
374
### Netlist augmentation
299
375
300
-
The bridge auto-generates a wrapper SPICE deck around the user's subcircuit:
301
-
- Digital input pins become `EXTERNAL` voltage sources (ngspice calls
302
-
`GetVSRCData` to read their values)
303
-
- Analog inputs also use `EXTERNAL` sources so they can be changed at runtime
304
-
- Output nodes are probed via `.save` directives
305
-
- Power supplies are added automatically
376
+
The bridge auto-generates a wrapper SPICE deck around the user's subcircuit,
0 commit comments