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.
109 lines
3.5 KiB
109 lines
3.5 KiB
"""Tests for XSPICE code model plugin loading and simulation."""
|
|
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from pyngspice import _cpp_available
|
|
|
|
|
|
def _get_codemodel_dir():
|
|
"""Return the path to pre-built .cm files."""
|
|
import pyngspice
|
|
return os.path.join(os.path.dirname(pyngspice.__file__), "codemodels")
|
|
|
|
|
|
def _cm_path(name):
|
|
"""Return full path to a .cm file."""
|
|
return os.path.join(_get_codemodel_dir(), f"{name}.cm")
|
|
|
|
|
|
CM_CATEGORIES = ["analog", "digital", "spice2poly", "table", "tlines", "xtradev", "xtraevt"]
|
|
|
|
|
|
@pytest.mark.skipif(not _cpp_available, reason="C++ extension not built")
|
|
class TestCodeModelLoading:
|
|
"""Test that all pre-built .cm plugins load successfully."""
|
|
|
|
@pytest.mark.parametrize("category", CM_CATEGORIES)
|
|
def test_load_cm(self, category):
|
|
"""Each .cm plugin should exist and load without error."""
|
|
from pyngspice import Simulator
|
|
|
|
path = _cm_path(category)
|
|
assert os.path.exists(path), f"{category}.cm not found at {path}"
|
|
assert os.path.getsize(path) > 0, f"{category}.cm is empty"
|
|
|
|
sim = Simulator()
|
|
sim.initialize()
|
|
ok = sim.command(f"codemodel {path}")
|
|
assert ok, f"Failed to load {category}.cm"
|
|
|
|
def test_load_all(self):
|
|
"""All 7 .cm plugins should load into a single simulator instance."""
|
|
from pyngspice import Simulator
|
|
|
|
sim = Simulator()
|
|
sim.initialize()
|
|
for category in CM_CATEGORIES:
|
|
path = _cm_path(category)
|
|
if os.path.exists(path):
|
|
ok = sim.command(f"codemodel {path}")
|
|
assert ok, f"Failed to load {category}.cm"
|
|
|
|
|
|
@pytest.mark.skipif(not _cpp_available, reason="C++ extension not built")
|
|
class TestCodeModelSimulation:
|
|
"""Test running simulations with XSPICE code models."""
|
|
|
|
def _load_analog(self, sim):
|
|
"""Load analog.cm into a simulator instance."""
|
|
path = _cm_path("analog")
|
|
if not os.path.exists(path):
|
|
pytest.skip("analog.cm not built")
|
|
sim.command(f"codemodel {path}")
|
|
|
|
def test_gain_dc(self):
|
|
"""Gain block with gain=3.0 should triple the input voltage."""
|
|
from pyngspice import Simulator
|
|
|
|
sim = Simulator()
|
|
sim.initialize()
|
|
self._load_analog(sim)
|
|
|
|
sim.command("circbyline Gain DC Test")
|
|
sim.command("circbyline V1 in 0 DC 2.0")
|
|
sim.command("circbyline A1 in out gain_model")
|
|
sim.command("circbyline .model gain_model gain(gain=3.0)")
|
|
sim.command("circbyline R1 out 0 1k")
|
|
sim.command("circbyline .dc V1 0 5 1")
|
|
sim.command("circbyline .end")
|
|
sim.command("run")
|
|
|
|
vecs = sim.all_vectors()
|
|
assert "out" in vecs
|
|
assert "in" in vecs
|
|
|
|
def test_gain_sweep_values(self):
|
|
"""Verify gain block output values match expected gain * input."""
|
|
from pyngspice import Simulator
|
|
|
|
sim = Simulator()
|
|
sim.initialize()
|
|
self._load_analog(sim)
|
|
|
|
sim.command("circbyline Gain Sweep Test")
|
|
sim.command("circbyline V1 in 0 DC 1.0")
|
|
sim.command("circbyline A1 in out gain_model")
|
|
sim.command("circbyline .model gain_model gain(gain=2.0)")
|
|
sim.command("circbyline R1 out 0 1k")
|
|
sim.command("circbyline .dc V1 0 5 1")
|
|
sim.command("circbyline .end")
|
|
sim.command("run")
|
|
|
|
# Use ngspice print command and check output
|
|
sim.clear_output()
|
|
sim.command("print out")
|
|
output = sim.get_output()
|
|
# Output should contain values that are 2x the input
|
|
assert "out" in output.lower()
|