CircuitCim Framework

5 minute read

Published:

Contents

  1. Intro to SPICE Algorithm
  2. Framework and your first circuit! (this article)
  3. More Static Linear Components
  4. Nonlinear and Diode
  5. MOSFET
  6. Time Variance
  7. Applications

Framework

To code SPICE, there are two main issues: how to keep the component information, and how to track their connectivity

Node Voltage Analysis

SPICE simplifies each component to sources and resistors. We use the infamous Node Voltage Analysis (with a few tweaks).

We first need to identify all the node voltages. We represent a circuit in a SPICE netlist, where all nodes mapped to an integer, and components connect like edges. The nodes are stored in a vector. Modelling a component may introduce additional internal nodes, which are also appended to the vector.

As a result, there will be an equation for each node.

Components

We use another array for circuit components. Each entry is a struct encoding value and connectivity. At each step, we add each component’s value into the resistance (conductance) matrix and solve for the new voltage.

Here we define the data structure to store the general resistor info

typedef struct { 
    int n1, n2;
    float R; 
} Res;

n1, n2 are the node labels the resistor is connected to, and R is the resistance.

Sources are a bit complicated, but a simple independent current source can be realized below:

typedef struct { 
    int n1, n2;
    float I;
} ISrc;

Generic Structure

We maintain a generic Comps[] array to keep track of all circuit components. Each entry is a generic struct Component, with the following contents:

  • The device’s type:
    • Static linear (STA_T, R)
    • Time-varying linear (LIN_T, LC)
    • Static nonlinear (NL_T, diode)
  • Function pointers to stamp the component (add it to the matrix)
  • Function pointers to update the component (update its internal approximation values, not needed for static, linear component)
  • A union of each component type, that can store
    • Nodes
    • Static values (E. resistance, capacitance)
    • Dynamic values (E. initial voltage, Vds)
typedef struct Component {
    CompType type; 
    void (*stamp) (struct Component*, float[][MAT_SIZE], float[]);
    void (*update) (struct Component*, float[][MAT_SIZE], float[]); 
    union {
        ISrc   isrc;
        Res    res;
    } u;
} Component;

// component registry
Component comps[MAX_COMPS];
int       ncomps;           // number of components in the circuit

Registering Components

Now we need to define the component register functions add_res() and add_isrc()

void add_res(int n1, int n2, float R) {
    Component *c = &comps[ncomps++];
    c->type      = STA_T;
    c->stamp     = res_stamp;
    c->u.res     = (Res){n1, n2, R};
}

void add_isrc(int n1, int n2, TransientSource *src) {
    Component *c = &comps[ncomps++];
    c->type      = STA_T;
    c->stamp     = isrc_stamp;
    c->u.isrc    = (ISrc){n1, n2, src};
}

TransientSource is a pointer to the source function \(i(t)\). You can set it to be a constant function. You can also change it to a float constant for now.

Stamping

Now time for node voltage analysis! Let’s count all currents leaving the node as positive.

The ground node is registered as -1.

void res_stamp(Component *c, float Gm[][MAT_SIZE], float I[]) {
    (void)I;                        // RHS not used
    Res *r = &c->u.res;
    float G = 1.0f / r->R;
    int   n1 = r->n1, n2 = r->n2;

    if (n1 != -1) Gm[n1][n1] += G;  // current leaving
    if (n2 != -1) Gm[n2][n2] += G;
    if (n1 != -1 && n2 != -1) {     // current entering
        Gm[n1][n2] -= G;
        Gm[n2][n1] -= G;
    }
}
void isrc_stamp(Component *c, float Gm[][MAT_SIZE], float I[]) {
    (void)Gm; (void)I;
    ISrc *s = &c->u.isrc;
    float Ieq = s.I
    int   n1 = s->n1, n2 = s->n2;

    if (n1 != -1) I[n1] += Ieq;
    if (n2 != -1) I[n2] -= Ieq;
}

Test Driver

We first write a simple driver program that reads a circuit, runs our simulation block, and returns the result in a CSV

#include <stdio.h>

// each test must define this:
void setup(void);               // defined in test programs
void stamp_static(void);        // adds resistors to the matrix

void print_header(FILE *f);     // write CSV header line
void print_row(FILE *f);        // write one line of data each time step

// global nodal matrix, state, and time
float G[MAT_SIZE][MAT_SIZE];
float Ivec[MAT_SIZE];
float v[MAT_SIZE], v_prev[MAT_SIZE];

/*
extern float G_sta[MAT_SIZE][MAT_SIZE];
extern float I_sta[MAT_SIZE];
extern float G_lin[MAT_SIZE][MAT_SIZE];
extern float I_lin[MAT_SIZE];
*/

float t;
extern float time_step; // time step for simulation
extern int nsteps;      // number of steps for simulation

int main(void) {
    // zero‑out everything
    memset(G,      0, sizeof G);
    memset(Ivec,   0, sizeof Ivec);
    memset(v,      0, sizeof v);
    memset(v_prev, 0, sizeof v_prev);
    t = 0.0f;

    // open CSV
    FILE *fp = fopen("results.csv", "w");
    if (!fp) { perror("fopen"); return 1; }
    print_header(fp);

    // build the circuit (calls your add_*()s)
    setup();

    // simulate
    clear_system();
    stamp_static();
    clear_system_sta();

    /*
    for (int n = 0; n < nsteps; n++) {
        t = n * time_step;
        update_all(t);
        print_row(fp); // write a row of data
    }
    */

    fclose(fp);
    return 0;
}

Our job is to write stamp_static() to add the resistor contribution to G and I.

Your First Circuit

Time to build a first circuit! It will be a simple current source with a resistor. There will only be one node 0 and the ground node -1

// Source-resistor circuit

#include "circuit.h"
#include "driver.h"
#include "input_funcs.h"
#include <string.h>  
float time_step = 5e-6f;    // arbitrary fir statuc curcyuts
int nsteps = 500;

void setup(void) {
    add_isrc(0, -1, 5);
    add_res(0, -1, 10);
    nnodes = 1;
}

// csv header
void print_header(FILE *f) {
    fprintf(f, "time,v0\n");
}

// csv row
void print_row(FILE *f) {
    fprintf(f, "%g,%g\n", t, v[0]);
}

and you can play around and add more linear components. See if it can solve the Tsividis problems!