Add /api/library datasheet import and component GET/PUT with no exam. Reorganize pytest into datasheet, library, schematic, PCB, and AF+AI. Document Rust criteria (none chosen; no rustup) and coding conformity.
100 lines
3.3 KiB
Python
100 lines
3.3 KiB
Python
"""Crystal CL check — only fires when CL and cap values are known."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from tests.paths import SIMPLE_PROJECT, TAXONOMY
|
|
|
|
from backend.periscopex.crystal_cl_check import check_crystal_cl
|
|
from backend.periscopex.models import (
|
|
Component,
|
|
ComponentType,
|
|
DesignGraph,
|
|
Net,
|
|
NetType,
|
|
PinConnection,
|
|
SimpleComponentSpecs,
|
|
)
|
|
|
|
|
|
def _xtal_graph(*, cl_f: float | None, c1: str, c2: str, stray: float | None = None) -> DesignGraph:
|
|
values: dict = {}
|
|
if cl_f is not None:
|
|
values["load_capacitance_f"] = cl_f
|
|
if stray is not None:
|
|
values["stray_capacitance_f"] = stray
|
|
return DesignGraph(
|
|
components={
|
|
"X1": Component(
|
|
reference="X1",
|
|
value="8MHz",
|
|
footprint="",
|
|
component_type=ComponentType.CRYSTAL,
|
|
mpn="XTAL",
|
|
pins={"1": "XIN", "2": "XOUT", "3": "GND"},
|
|
specs=SimpleComponentSpecs(specs_type="crystal", values=values) if values else None,
|
|
),
|
|
"C1": Component(
|
|
reference="C1", value=c1, footprint="",
|
|
component_type=ComponentType.CAPACITOR,
|
|
pins={"1": "XIN", "2": "GND"},
|
|
),
|
|
"C2": Component(
|
|
reference="C2", value=c2, footprint="",
|
|
component_type=ComponentType.CAPACITOR,
|
|
pins={"1": "XOUT", "2": "GND"},
|
|
),
|
|
},
|
|
nets={
|
|
"XIN": Net(
|
|
name="XIN", net_type=NetType.SIGNAL,
|
|
pins=[
|
|
PinConnection(component_ref="X1", pin_number="1"),
|
|
PinConnection(component_ref="C1", pin_number="1"),
|
|
],
|
|
),
|
|
"XOUT": Net(
|
|
name="XOUT", net_type=NetType.SIGNAL,
|
|
pins=[
|
|
PinConnection(component_ref="X1", pin_number="2"),
|
|
PinConnection(component_ref="C2", pin_number="1"),
|
|
],
|
|
),
|
|
"GND": Net(
|
|
name="GND", net_type=NetType.GROUND,
|
|
pins=[
|
|
PinConnection(component_ref="X1", pin_number="3"),
|
|
PinConnection(component_ref="C1", pin_number="2"),
|
|
PinConnection(component_ref="C2", pin_number="2"),
|
|
],
|
|
),
|
|
},
|
|
)
|
|
|
|
|
|
def test_no_cl_in_specs_is_silent():
|
|
g = _xtal_graph(cl_f=None, c1="18p", c2="18p")
|
|
assert check_crystal_cl(g) == []
|
|
|
|
|
|
def test_series_above_cl_without_stray_warns():
|
|
# series of 22p || 22p = 11p — wait we need series > CL.
|
|
# 100p || 100p = 50p > CL 18p * 1.25
|
|
g = _xtal_graph(cl_f=18e-12, c1="100p", c2="100p")
|
|
findings = check_crystal_cl(g)
|
|
assert any(f.rule_id == "PE-XTAL-002" for f in findings)
|
|
|
|
|
|
def test_with_stray_mismatch_warns():
|
|
# 18p||18p = 9p + 2p stray = 11p vs CL 18p → below 0.75*18
|
|
g = _xtal_graph(cl_f=18e-12, c1="18p", c2="18p", stray=2e-12)
|
|
findings = check_crystal_cl(g)
|
|
assert any(f.rule_id == "PE-XTAL-002" for f in findings)
|
|
|
|
|
|
def test_simple_project_without_cl_silent():
|
|
from pathlib import Path
|
|
from backend.periscopex.models import DesignGraph
|
|
path = SIMPLE_PROJECT / "design_graph.json"
|
|
g = DesignGraph.model_validate_json(path.read_text())
|
|
assert check_crystal_cl(g) == []
|