You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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:

  1. Write your FPGA design in Verilog
  2. Compile with Verilator: verilator --cc design.v --exe
  3. Build the output into a shared library (.dll)
  4. Use d_cosim in 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:

  1. Place cfunc.mod and ifspec.ifs in the appropriate directory
  2. Add the model name to modpath.lst
  3. 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.)