5.9 KiB
Writing XSPICE Code Models for pyTesla
What This Is
XSPICE code models let you write circuit components in C. During simulation, ngspice calls your C function at each timestep with port voltages/states and you compute the outputs. This is how we'll add custom parts (gate drivers, controllers, digital logic) to the pyTesla part library.
How a Code Model Looks in a Netlist
* Analog component using a code model
A1 in out mymodel
.model mymodel custom_model(gain=2.0 offset=0.1)
* Digital component with bridge to analog world
A2 [digital_in] [digital_out] my_gate
.model my_gate d_and(rise_delay=1n fall_delay=1n)
* Analog-to-digital bridge (connects analog node to digital code model)
A3 [analog_node] [digital_node] adc1
.model adc1 adc_bridge(in_low=0.8 in_high=2.0)
The A device letter tells ngspice this is an XSPICE code model instance.
File Structure for a New Code Model
Each code model needs two files in src/xspice/icm/<category>/<model_name>/:
1. ifspec.ifs — Interface Specification
Declares the model's name, ports, and parameters. Not C — it's a simple
declarative format that the cmpp preprocessor converts to C.
NAME_TABLE:
C_Function_Name: cm_my_driver
Spice_Model_Name: my_driver
Description: "Half-bridge gate driver model"
PORT_TABLE:
Port_Name: hin lin ho lo
Description: "high in" "low in" "high out" "low out"
Direction: in in out out
Default_Type: d d v v
Allowed_Types: [d] [d] [v] [v]
Vector: no no no no
Vector_Bounds: - - - -
Null_Allowed: no no no no
PARAMETER_TABLE:
Parameter_Name: vcc dead_time
Description: "supply volts" "dead time seconds"
Data_Type: real real
Default_Value: 12.0 100e-9
Limits: [0.1 1000] [0 -]
Vector: no no
Vector_Bounds: - -
Null_Allowed: yes yes
2. cfunc.mod — C Implementation
Plain C with XSPICE macros for accessing ports/parameters. This is where your component's behavior goes.
#include <math.h>
void cm_my_driver(ARGS)
{
/* Read parameters */
double vcc = PARAM(vcc);
double dead_time = PARAM(dead_time);
/* Read digital input states */
Digital_State_t hin = INPUT_STATE(hin);
Digital_State_t lin = INPUT_STATE(lin);
/* Compute outputs */
if (INIT) {
/* First call — initialize state */
OUTPUT(ho) = 0.0;
OUTPUT(lo) = 0.0;
} else {
/* Normal operation */
if (hin == ONE)
OUTPUT(ho) = vcc;
else
OUTPUT(ho) = 0.0;
if (lin == ONE)
OUTPUT(lo) = vcc;
else
OUTPUT(lo) = 0.0;
}
/* Set output delay */
OUTPUT_DELAY(ho) = dead_time;
OUTPUT_DELAY(lo) = dead_time;
}
Key XSPICE Macros
| Macro | Purpose |
|---|---|
ARGS |
Function signature placeholder (always use in the function declaration) |
INIT |
True on first call — use for initialization |
PARAM(name) |
Read a model parameter value |
INPUT(name) |
Read analog input voltage/current |
INPUT_STATE(name) |
Read digital input (ONE, ZERO, UNKNOWN) |
OUTPUT(name) |
Set analog output value |
OUTPUT_STATE(name) |
Set digital output state |
OUTPUT_DELAY(name) |
Set propagation delay for output |
OUTPUT_STRENGTH(name) |
Set digital output drive strength |
PORT_SIZE(name) |
Number of elements in a vector port |
PARTIAL(out, in) |
Set partial derivative (for analog convergence) |
STATIC_VAR(name) |
Access persistent state between timesteps |
T(n) |
Current time at call (n=0) or previous calls (n=1,2) |
cm_event_alloc(tag, size) |
Allocate event-driven state memory |
cm_event_get_ptr(tag, n) |
Get state pointer (n=0 current, n=1 previous) |
Port Types
- v — analog voltage
- i — analog current
- vd — analog voltage differential
- id — analog current differential
- d — digital (ONE, ZERO, UNKNOWN)
Analog-Digital Bridges
To connect analog and digital ports, use bridge models in the netlist:
* Analog voltage -> Digital state
A_bridge1 [v_node] [d_node] adc1
.model adc1 adc_bridge(in_low=0.8 in_high=2.0)
* Digital state -> Analog voltage
A_bridge2 [d_node] [v_node] dac1
.model dac1 dac_bridge(out_low=0.0 out_high=3.3)
Verilog Co-Simulation (Future)
Once code models work, Verilog support follows:
- Write your FPGA design in Verilog
- Compile with Verilator:
verilator --cc design.v --exe - Build the output into a shared library (.dll)
- Use
d_cosimin the netlist:
A1 [clk din] [dout pwm] cosim1
.model cosim1 d_cosim(simulation="my_fpga_design.dll")
ngspice loads the DLL and runs the compiled Verilog alongside the analog sim.
Directory Layout for pyTesla Parts
src/xspice/icm/
├── analog/ # Built-in analog models (gain, limit, etc.)
├── digital/ # Built-in digital models (d_and, d_cosim, etc.)
├── pytesla/ # Our custom part library (new category)
│ ├── modpath.lst # List of model directories
│ ├── gate_driver/
│ │ ├── cfunc.mod
│ │ └── ifspec.ifs
│ ├── spark_gap/
│ │ ├── cfunc.mod
│ │ └── ifspec.ifs
│ └── ...
Build Process
After adding a new code model:
- Place
cfunc.modandifspec.ifsin the appropriate directory - Add the model name to
modpath.lst - Rebuild:
python build_mingw.py
The CMake build will run cmpp to preprocess the files, compile
the generated C, and register the model with ngspice automatically.
(Note: the ICM build step in CMakeLists.txt is not yet implemented — this document describes the target workflow once it is.)