# Behavioral resistor → C‑OSDI code generation

*For **why** this (and the capacitor generator) exist, read `docs/osdi_codegen_overview/`
first. The capacitor counterpart is `docs/osdi_cap_codegen/`.*

This feature turns **behavioral (voltage‑dependent) resistors** in a netlist into
**native OSDI devices that are generated as C, compiled with the system `gcc`, and
loaded at parse time** — with no OpenVAF and no hand‑written model. It is the
productized successor to the B‑source path for resistors.

A resistor whose value depends on its own terminal voltage, e.g.

```
Rpoly a b R='rsh*L/W*(1 + pvc1*(tanh(pvc2*abs(v(a,b)/L)+pvc3) - tanh(pvc3)))'
```

is normally rewritten by ngspice into a behavioral **B‑source** current
(`i = v(a,b)/R`). That works but is comparatively slow. This feature instead emits
a small C device implementing the exact same law **and its analytic Jacobian**,
compiles it, and instantiates it as an `N` (OSDI) device — evaluated as compiled
native code.

---

## Policy: OSDI by default, B‑source as the last resort

| Situation | Result |
|---|---|
| A behavioral resistor that can be emitted | → **C‑OSDI device** (default, nothing to set) |
| Unsupported construct (`pwl`, `ddt`, `time`, `hertz`, `i(...)`, >2 terminals) | → **warning + kept as B‑source** |
| No C compiler (`gcc`) on the system | → warning once, **all kept as B‑source** |
| User wants the old behavioral path | → `set osdi_res_bsource` (force B‑source) |

Genuine non‑resistor B‑sources you wrote yourself (anything that is not a
two‑terminal voltage‑controlled current source) are left untouched **silently** —
only resistor‑shaped devices that *cannot* be emitted produce a fallback warning.

---

## How to use it

### Enable / disable (in `.spiceinit`)
The switch is read at **parse time**, so it must be set before the netlist is read —
i.e. in `.spiceinit` (current directory, then `$HOME`), **not** in a `.control`
block inside the deck.

```
* .spiceinit
set ngbehavior=hsa        ; needed for R='...' HSPICE-style syntax
                          ; (OSDI codegen is ON by default — nothing to add)

* to force the old behavioral path instead:
* set osdi_res_bsource
```

Optional knob:
```
set osdi_res_include=/path/to/dir   ; where gcc finds osdi.h
                                    ; (defaults to the build's src/osdi)
```

### Run
```
ngspice -b mydeck.cir
```
On stdout you will see, for example:
```
osdi-res: 30 resistor(s) -> 2 C-OSDI model(s); 0 kept as B-source
```

### What lands on disk
For each **unique** resistor law the generator writes, in the current directory:
```
osdires_osdires0.c     osdires_osdires0.osdi
osdires_osdires1.c     osdires_osdires1.osdi
...
```
These are the generated C source and the compiled shared object. They are named
`osdires0`, `osdires1`, … — **one per distinct law, not one per resistor** (see
*Deduplication* below).

### Requirements
* A working `gcc` on `PATH`.
* `osdi.h` reachable (default `src/osdi`, override with `set osdi_res_include`).

---

## Deduplication — one model per *law*, many *instances*

A `.osdi` is a compiled device **type**, like a `.model` — not a per‑instance object.
The generator computes a **structural signature** of each resistor’s expression
(operator/function tree, with the numeric constants blanked out and coefficient
**signs canonicalized**). Resistors with the same signature share **one** compiled
model; their differing numbers ride along as **per‑instance parameters**.

So a deck with 30 behavioral resistors spanning 5 foundry families and many
geometries typically compiles to just **2 models** — one “body” law and one “end”
law — while each resistor becomes an `N`‑device instance carrying its own values:

```
nRbo_famA  m1 m2 osdires1 m=1 p0=<R0>  p3=<Leff> p5=<c1> ...
nRbo_famB  m3 m4 osdires1 m=1 p0=<R0'> p3=<Leff'> p5=<c1'> ...   ← same type, different consts
nRe1_famA  s  m1 osdires0 m=1 p0=<R0>  p2=<c2> ...               ← the other type
```

The physics per resistor is preserved exactly (verified **bit‑exact** vs the
B‑source); only the C code is shared. On a full chip (~thousands of poly‑Rs) this
means ~2 `gcc` compiles instead of thousands.

**`m` (mfactor) is a first‑class instance parameter.** An explicit `m=` on the
original resistor is honored and scales current *and* conductance linearly
(`i(m=2) = 2·i(m=1)`), so one `N`‑device with `m=k` stands in for `k` parallel
devices. It is not part of the signature, so instances with different `m` still
share a model.

---

## How it works (internals)

Pipeline for one resistor:

```
Rxxx n1 n2 R='f(v(n1,n2),temper)'
        │  inpcom.c b_transformation  (existing)
        ▼
b<xxx> n1 n2 i = v(n1,n2)/(f)        (behavioral B-source, params still symbolic)
        │  subckt expansion + numparam  →  f is now numeric per instance
        ▼
   osdi_res_transform()  (NEW, src/frontend/osdi_rescodegen.c)
        │  INPgetTree(f)         → parse tree for I(v)
        │  pt->derivs[k]         → dI/dv  (ngspice's own symbolic differentiator, PTdifferentiate)
        │  collect constants     → per-instance params p0..pk (signs canonicalized)
        │  signature → dedup cache → compile once per law:
        │      emit C  →  gcc -O2 -shared -fPIC -I<osdi>  →  load_osdi()
        ▼
N<xxx> n1 n2 osdiresK m=<m> p0=.. p1=..   (+ one ".model osdiresK osdiresK()")
```

Key points:

* **Reuses ngspice’s own engine.** The current expression is parsed by
  `INPgetTree`; the Jacobian `dI/dv` comes from `PTdifferentiate`
  (`pt->derivs[]`). The generator never re‑implements parsing or differentiation —
  it just walks those two trees and prints C, so any differentiable law works
  (tanh, sqrt, exp, powers, ternaries, …), not just the foundry `tanh` form.

* **Hook point.** `osdi_res_transform(deck)` runs in `inp.c` immediately after
  `inp_subcktexpand()` — the first point where each instance’s coefficients are
  numeric (only `v(n1,n2)` and `temper` remain symbolic). Terminals are bound **by
  name** from the card (`n1→+`, `n2→−`).

* **Generated device.** OSDI v0.3 C: 2 nodes, 4 Jacobian stamps, instance params
  `m, p0..pk`. `eval()` reads the node voltages, computes `I`, `dI/dvp`, `dI/dvn`
  from the emitted expressions, scales by `m`, and stores residual + conductances;
  the load functions stamp them. `temper` is delivered as the instance temperature
  in `setup_instance` (Celsius).

* **Emitter mapping.** Parse nodes → C: arithmetic inline, `PTF_*` functions →
  libm (`tanh`,`sqrt`,`exp`,…) or small helpers (`osdi_sgn`,`osdi_pwr`,`osdi_power`,
  `osdi_ustep`,`osdi_uramp`). `pwl`/`ddt`/`time`/`hertz` are rejected → fallback.

### Files
| File | Change |
|---|---|
| `src/frontend/osdi_rescodegen.c` | **new** — emitter, dedup cache, compile/load, the pass, the `osdigen` command |
| `src/frontend/inp.c` | call `osdi_res_transform()` after `inp_subcktexpand()` |
| `src/frontend/com_dl.h` | prototypes |
| `src/frontend/commands.c` | register the `osdigen` command |
| `src/frontend/Makefile.am` | add the new source |

---

## `osdigen` — a manual/debug command

Emits the C for a single current expression without running the automatic pass
(useful to inspect what would be generated). Requires a circuit to be loaded first
(for node context).

```
osdigen <model> <n1> <n2> <current-expression>
```
Example (inside a `.control` block, with any small deck loaded):
```
osdigen genR a b v(a,b)/(R0*(1+c1*(tanh(c2*abs(v(a,b)/Leff)+c3)-tanh(c3))))
```
prints the emitted `I`, `dI/dn1`, the structural signature, the per‑instance
parameter list, and writes `genR.c`. Compile with:
```
gcc -O2 -shared -fPIC -I<src/osdi> genR.c -o genR.osdi -lm
```

---

## Verifying

Two harnesses (in `netlist/`):

* **`polyr_verify.py`** — per foundry family, compares an OSDI chain vs the
  behavioral chain; checks bit‑exactness, current symmetry `i(+V) = −i(−V)`, and
  solver independence (KLU == sparse).
* **`m4_verify.py`** — generates an all‑families behavioral deck and runs it twice
  (OSDI default vs `set osdi_res_bsource`), confirming every current is **bit‑exact**
  and reporting the dedup (30 resistors → 2 models).

Both currently pass at `0.000e+00`.

---

## Limitations / notes

* Only **two‑terminal** resistor‑form devices (`i = f(v(n1,n2), temper)`). An
  expression referencing other nodes, `i(...)`, `time`, `hertz`, `pwl`, or `ddt`
  falls back to the B‑source.
* Generated `.c`/`.osdi` files are written to the **current working directory**.
* The include path to `osdi.h` currently defaults to the build tree; a deployed
  install should point `osdi_res_include` at the installed header (or the header
  can be embedded into the generated C in a later revision).
