sim_back vs openvaf_jax: Compilation Pipeline Comparison¶
This document compares the OpenVAF sim_back crate's compilation pipeline with our openvaf_jax implementation. Understanding the differences helps identify where our implementation may be missing features or taking shortcuts.
Overview¶
| Aspect | sim_back (OpenVAF) | openvaf_jax |
|---|---|---|
| Target | OSDI (LLVM IR) | JAX Python code |
| Approach | Full compilation with optimization | Interpret MIR, generate Python |
| Optimization | Multi-pass (GVN, DCE, SCCP) | None (relies on JAX JIT) |
| Auto-diff | Internal MIR-based autodiff | Relies on JAX autodiff (partial) |
Pipeline Stages Comparison¶
sim_back Pipeline¶
openvaf_jax Pipeline¶
Stage-by-Stage Comparison¶
1. Input Representation¶
| sim_back | openvaf_jax |
|---|---|
Builds MIR from HIR using MirBuilder |
Receives pre-computed MIR from openvaf_py |
| Has full access to HIR for symbol resolution | Has serialized MIR data + metadata |
| Can modify/rebuild MIR during compilation | Read-only interpretation of MIR |
What we receive from openvaf_py:
- get_mir_instructions() - Eval function MIR
- get_init_mir_instructions() - Init function MIR
- get_dae_system() - Pre-computed DAE structure
- Metadata: params, nodes, cache_mapping
2. Optimization¶
| sim_back | openvaf_jax |
|---|---|
| Initial: DCE, SCCP, inst_combine, CFG simplify, GVN | None |
| PostDerivative: SCCP, inst_combine, CFG simplify, GVN | None |
| Final: Aggressive DCE | None |
Gap: We skip all IR-level optimization, relying on: - OpenVAF's optimization (runs before MIR export) - JAX's JIT compilation (XLA)
3. Topology Construction¶
| sim_back | openvaf_jax |
|---|---|
| Branch extraction from MIR | Pre-computed in dae_data['residuals'] |
Linearization of ddt(), noise |
Pre-computed (result embedded in MIR) |
| Small-signal detection | Not implemented |
| Implicit equation creation | Pre-computed |
sim_back's linearization logic:
For each ddt()/noise:
If only used in linear chain (fadd, fsub, fneg, fmul with non-OP-dep) →
Extract as separate "dimension" (react coefficient)
Else →
Create implicit equation (new unknown)
Our approach: OpenVAF has already done linearization. The MIR we receive has:
- Resistive contributions (resist)
- Reactive contributions (react)
- These are separate outputs in dae_data
4. DAE System Construction¶
| sim_back | openvaf_jax |
|---|---|
| Builds residuals from branch contributions | Receives dae_data['residuals'] |
| Runs autodiff on MIR to get Jacobian | Receives dae_data['jacobian'] |
Computes lim_rhs correction terms |
Receives lim_rhs_resist, lim_rhs_react |
| Handles mfactor scaling | Pre-computed |
| Extracts noise sources | Pre-computed (not used in JAX path) |
sim_back's autodiff:
let derivatives = auto_diff(ctx, residuals, unknowns);
// Returns: (residual_value, unknown) → derivative_value
Our approach: We receive pre-differentiated MIR. The Jacobian entries in dae_data['jacobian'] reference MIR values that are already the partial derivatives.
5. OP-Dependence Analysis¶
| sim_back | openvaf_jax |
|---|---|
| Tracks which values depend on unknowns | Not explicit |
| Uses dominance frontiers for control deps | CFG analysis for loops only |
Two-phase: init_op_dependent_insts, refresh_op_dependent_insts |
Implicit via init/eval split |
sim_back's OP-dependent roots:
- ParamKind::Voltage - Node voltages
- ParamKind::Current - Branch currents
- ParamKind::ImplicitUnknown - Internal unknowns
- System functions: $abstime, $temperature
Our approach: OpenVAF already split the code:
- init function: OP-independent (cached values)
- eval function: OP-dependent (per Newton iteration)
6. Initialization Extraction¶
| sim_back | openvaf_jax |
|---|---|
Initialization::new() splits MIR |
Pre-split by OpenVAF |
| GVN deduplication of cache slots | Pre-computed cache_mapping |
Creates separate init and eval MIR functions |
Receives separate MIR for each |
| Cache slot assignment | cache_mapping[i]['init_value'] → cache_mapping[i]['eval_param'] |
sim_back cache slot assignment:
fn ensure_cache_slot(inst, res, ty) -> CacheSlot {
let class = gvn.inst_class(inst); // GVN equivalence
cache_slots.insert((class, res), ty)
}
Our approach: cache_mapping from OpenVAF maps init values to eval params.
7. Node Collapse¶
| sim_back | openvaf_jax |
|---|---|
NodeCollapse::new() detects collapsible pairs |
collapsible_pairs from module |
| Runtime collapse decisions | collapse_decision_outputs from init |
Both handle: Nodes that can be shorted under certain parameter conditions.
8. Code Generation¶
| sim_back | openvaf_jax |
|---|---|
LLVM IR via osdi crate |
Python AST → source code |
| Type-safe register allocation | Dynamic Python typing |
| Inlined operations | JAX operations |
| Memory layout control | JAX arrays |
Feature Coverage¶
Fully Supported¶
| Feature | sim_back | openvaf_jax |
|---|---|---|
| Resistive contributions | ✅ | ✅ |
| Reactive contributions (ddt) | ✅ | ✅ |
| Sparse Jacobian | ✅ | ✅ |
| Node collapse | ✅ | ✅ |
| Parameter caching (init/eval split) | ✅ | ✅ |
| Limiting (lim_rhs correction) | ✅ | ✅ |
| mfactor scaling | ✅ | ✅ (pre-computed) |
| Small-signal data extraction | ✅ | ✅ |
Partially Supported¶
| Feature | sim_back | openvaf_jax | Gap |
|---|---|---|---|
| Implicit equations | ✅ | ⚠️ Partial | We handle pre-resolved implicit eqns |
| Switch branches | ✅ | ⚠️ Partial | Not fully tested |
| Temperature dependence | ✅ | ⚠️ | Need to verify param mapping |
Not Supported¶
| Feature | sim_back | openvaf_jax | Reason |
|---|---|---|---|
| Noise analysis | ✅ | ❌ | Not in scope |
| Runtime branch switching | ✅ | ❌ | Complex control flow |
Data Structure Mapping¶
Residuals¶
sim_back: openvaf_jax:
Residual { dae_data['residuals'][i] = {
resist: Value, 'resist_var': 'mir_XX',
react: Value, 'react_var': 'mir_YY',
resist_small_signal: Value, 'resist_small_signal_var': 'mir_SS1',
react_small_signal: Value, 'react_small_signal_var': 'mir_SS2',
resist_lim_rhs: Value, 'resist_lim_rhs_var': 'mir_ZZ',
react_lim_rhs: Value, 'react_lim_rhs_var': 'mir_WW',
nature_kind: Flow|Potential|Switch 'node_name': 'A'
} }
Additionally, dae_data['small_signal_params'] lists parameters known to be zero in large-signal analysis.
Jacobian¶
sim_back: openvaf_jax:
MatrixEntry { dae_data['jacobian'][i] = {
row: SimUnknown, 'row_node_name': 'A',
col: SimUnknown, 'col_node_name': 'B',
resist: Value, 'resist': 'mir_XX',
react: Value, 'react': 'mir_YY',
} }
Cache¶
sim_back: openvaf_jax:
Initialization { init_mir_data['cache_mapping'][i] = {
cached_vals: { 'init_value': 'mir_XX',
eval_value → CacheSlot 'eval_param': 42
}, }
cache_slots: {
(gvn_class, res_idx) → Type
}
}
Performance Implications¶
sim_back Advantages¶
- Multi-pass optimization reduces instruction count before codegen
- GVN deduplication minimizes cache size
- Aggressive DCE removes truly dead code
- LLVM backend can do further optimization
openvaf_jax Advantages¶
- JAX JIT provides XLA optimization at runtime
- vmap enables efficient batched device evaluation
- GPU acceleration via JAX backends
- Python ecosystem for debugging/analysis
Potential Improvements for openvaf_jax¶
- Dead code elimination - Some MIR values may be unused
- Constant folding - Could pre-compute constant expressions
- Cache optimization - Verify no redundant cache entries
- Small-signal support - For AC analysis in future
Recommendations¶
Short-term¶
- Verify all
dae_datafields are correctly used - Add validation for cache_mapping completeness
- Test limiting (lim_rhs) paths more thoroughly
Medium-term¶
- Consider MIR-level dead code elimination
- Profile cache usage to identify redundancies
- Implement small_signal handling if AC needed
Long-term¶
- Evaluate whether MIR-level optimization adds value
- Consider autodiff at Python level for flexibility
- Noise analysis support if needed
References¶
vendor/OpenVAF/docs/sim_back/sim_back.md- Main overviewvendor/OpenVAF/docs/sim_back/init.md- Initialization extractionvendor/OpenVAF/docs/sim_back/dae.md- DAE system constructionvendor/OpenVAF/docs/sim_back/topology.md- Branch/linearizationvendor/OpenVAF/docs/sim_back/context.md- Compilation context