diff --git a/backend/pinscopex/graph.py b/backend/pinscopex/graph.py index 3b0afa9..3f0da56 100644 --- a/backend/pinscopex/graph.py +++ b/backend/pinscopex/graph.py @@ -154,6 +154,11 @@ def _infer_net_properties(name: str) -> tuple[NetType, float | None]: voltage = _parse_rail_voltage(name) return NetType.POWER, voltage + # KiCad-style rails: 3V3_DIGITAL, 1V8_SI4684, 5V_USB (not I2C1-SCL-3V3). + if re.match(r"^\d+V\d*", upper): + voltage = _parse_rail_voltage(name) + return NetType.POWER, voltage + # Everything else is a signal return NetType.SIGNAL, None diff --git a/backend/pinscopex/passive_rail_check.py b/backend/pinscopex/passive_rail_check.py index 9a1a2b2..3bbfc95 100644 --- a/backend/pinscopex/passive_rail_check.py +++ b/backend/pinscopex/passive_rail_check.py @@ -217,7 +217,23 @@ def _is_i2c_pin( pin_num: str, net_name: str, ) -> bool: - return bool(_I2C_RE.search(_pin_blob(cons, pin_num, net_name))) + net = net_name or "" + if re.match(r"(?i)SPI([_-]|$)", net) or re.search( + r"(?i)\bSPI[_-]?(CLK|SCK|MOSI|MISO|CS|SS)\b", net, + ): + return False + primary = "" + if cons: + pin = cons.pin_by_number(pin_num) + if pin and pin.name: + primary = pin.name.split("/")[0].strip() + if re.search(r"(?i)\b(MISO|MOSI|SCLK|SCK)\b", primary): + return False + if _I2C_RE.search(net): + return True + if _I2C_RE.search(primary): + return True + return False def _is_reset_pin( @@ -241,7 +257,9 @@ def _is_ground_net(graph: DesignGraph, name: str) -> bool: def _is_power_net(graph: DesignGraph, name: str) -> bool: net = graph.nets.get(name) - return bool(net and net.net_type == NetType.POWER) + if net and net.net_type == NetType.POWER: + return True + return bool(re.match(r"^\d+V\d*", (name or "").upper())) def _capacitor_to_ground(graph: DesignGraph, power_net: str) -> bool: diff --git a/tests/test_passive_rail_check.py b/tests/test_passive_rail_check.py index 0450256..86afd8e 100644 --- a/tests/test_passive_rail_check.py +++ b/tests/test_passive_rail_check.py @@ -2,6 +2,7 @@ from __future__ import annotations +from backend.pinscopex.graph import _infer_net_properties from backend.pinscopex.models import ( Component, ComponentConstraints, @@ -19,6 +20,17 @@ from backend.pinscopex.passive_rail_check import ( ) +def test_ki_cad_voltage_prefix_is_power(): + ntype, volts = _infer_net_properties("3V3_DIGITAL") + assert ntype == NetType.POWER + assert volts == 3.3 + ntype, volts = _infer_net_properties("1V8_SI4684") + assert ntype == NetType.POWER + assert volts == 1.8 + ntype, _ = _infer_net_properties("I2C1-SCL-3V3") + assert ntype == NetType.SIGNAL + + def _graph(components, nets): net_objs = {} for name, (ntype, conns) in nets.items(): @@ -117,6 +129,44 @@ def test_i2c_pullup_present(): assert check_i2c_pullups(g, cons) == [] +def test_i2c_pullup_to_3v3_digital_typed_as_signal(): + cons = { + "UTEST": ComponentConstraints( + mpn="UTEST", + pintable=[Pin(number=8, name="SDA")], + absolute_maximum_ratings=[], rules=[], + ) + } + r = Component( + reference="R1", value="4.7k", footprint="", + component_type=ComponentType.RESISTOR, mpn="R1", + pins={"1": "I2C_SDA", "2": "3V3_DIGITAL"}, + ) + g = _graph( + {"U1": _ic("U1", {"8": "I2C_SDA"}), "R1": r}, + { + "I2C_SDA": (NetType.SIGNAL, [("U1", "8"), ("R1", "1")]), + "3V3_DIGITAL": (NetType.SIGNAL, [("R1", "2")]), + }, + ) + assert check_i2c_pullups(g, cons) == [] + + +def test_spi_pin_alias_sda_is_not_i2c(): + cons = { + "UTEST": ComponentConstraints( + mpn="UTEST", + pintable=[Pin(number=38, name="MISO/SDA")], + absolute_maximum_ratings=[], rules=[], + ) + } + g = _graph( + {"U1": _ic("U1", {"38": "SPI_MISO"})}, + {"SPI_MISO": (NetType.SIGNAL, [("U1", "38")])}, + ) + assert check_i2c_pullups(g, cons) == [] + + def test_reset_no_finding_when_gpio_drives(): cons = { "UTEST": ComponentConstraints(