Skip module-die and truncated QFN pintable false PE-BOM hits (2.63.3).

WROOM lands are not the ESP32 die table (U11). QFN-48 pads 25–48 are
not extras when extraction stopped at 1–24 (U12). VQFN is the QFN
family (U19 24-QFN 4×4). Real extra signal pads still ERROR.
This commit is contained in:
2026-09-22 00:57:57 +02:00
parent cd7a7ca3a6
commit 8f434ffd31
4 changed files with 379 additions and 15 deletions
+244
View File
@@ -277,3 +277,247 @@ def test_lib_vs_embedded_pcb_is_separate_finding(tmp_path: Path):
if f.rule_id == "PE-BOM-011"
]
assert any("embedded" in f.finding.lower() or "PCB" in f.finding for f in findings)
def _ic(
ref: str,
mpn: str,
footprint: str,
n_sch: int,
) -> Component:
return Component(
reference=ref, value=mpn, footprint=footprint,
component_type=ComponentType.IC, mpn=mpn,
pins={str(i): "SIG" for i in range(1, n_sch + 1)},
)
def test_wroom_module_pads_vs_die_pintable_not_bom_011():
"""U11: WROOM land ≠ ESP32 die 61-pin pintable."""
cons = ComponentConstraints(
mpn="ESP32-S31-WROOM-3",
package_info=PackageInfo(
base_family="ESP", package="QFN-56", pin_count=61,
),
pintable=[Pin(number=str(i), name=f"P{i}") for i in range(1, 62)],
absolute_maximum_ratings=[],
rules=[],
)
pads = [LayoutPad(number=str(i), x=0, y=0, net="SIG") for i in range(1, 100)]
graph = DesignGraph(
components={
"U11": _ic(
"U11", "ESP32-S31-WROOM-3",
"MIKILAB_ESP32_S31_WROOM_3:ESP32-S31-WROOM-3", 41,
),
},
)
layout = LayoutGraph(
footprints={
"U11": LayoutFootprint(
reference="U11",
footprint="MIKILAB_ESP32_S31_WROOM_3:ESP32-S31-WROOM-3",
x=0, y=0, pads=pads,
),
},
)
ids = _ids(check_bom_pcb_datasheet(graph, {"ESP32-S31-WROOM-3": cons}, layout))
assert "PE-BOM-011" not in ids
def test_wroom_missing_die_pin_still_fires():
cons = ComponentConstraints(
mpn="ESP32-S31-WROOM-3",
package_info=PackageInfo(
base_family="ESP", package="QFN-56", pin_count=61,
),
pintable=[Pin(number=str(i), name=f"P{i}") for i in range(1, 62)],
absolute_maximum_ratings=[],
rules=[],
)
pads = [LayoutPad(number=str(i), x=0, y=0, net="SIG") for i in range(1, 60)]
graph = DesignGraph(
components={
"U11": _ic(
"U11", "ESP32-S31-WROOM-3",
"MIKILAB_ESP32_S31_WROOM_3:ESP32-S31-WROOM-3", 40,
),
},
)
layout = LayoutGraph(
footprints={
"U11": LayoutFootprint(
reference="U11",
footprint="MIKILAB_ESP32_S31_WROOM_3:ESP32-S31-WROOM-3",
x=0, y=0, pads=pads,
),
},
)
assert "PE-BOM-011" in _ids(
check_bom_pcb_datasheet(graph, {"ESP32-S31-WROOM-3": cons}, layout),
)
def test_qfn48_truncated_pintable_24_not_extra_error():
"""U12: pintable 124 vs QFN-48 lands is extraction, not a wrong footprint."""
cons = ComponentConstraints(
mpn="Si4684-A10-GM",
package_info=PackageInfo(
base_family="Skyworks", package="QFN-48", pin_count=24,
),
pintable=[Pin(number=str(i), name=f"P{i}") for i in range(1, 25)],
absolute_maximum_ratings=[],
rules=[],
)
pads = [LayoutPad(number=str(i), x=0, y=0, net="SIG") for i in range(1, 49)]
pads.append(LayoutPad(number="49", x=0, y=0, net="GND", pinfunction="EP"))
graph = DesignGraph(
components={
"U12": _ic(
"U12", "Si4684-A10-GM",
"Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.3x5.3mm_ThermalVias",
48,
),
},
)
layout = LayoutGraph(
footprints={
"U12": LayoutFootprint(
reference="U12",
footprint="Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.3x5.3mm_ThermalVias",
x=0, y=0, pads=pads,
),
},
)
assert "PE-BOM-011" not in _ids(
check_bom_pcb_datasheet(graph, {"Si4684-A10-GM": cons}, layout),
)
def test_qfn24_extra_pad_30_still_fires():
cons = ComponentConstraints(
mpn="IC24",
package_info=PackageInfo(base_family="QFN", package="QFN-24", pin_count=24),
pintable=[Pin(number=str(i), name=f"P{i}") for i in range(1, 25)],
absolute_maximum_ratings=[],
rules=[],
)
pads = [LayoutPad(number=str(i), x=0, y=0, net="SIG") for i in range(1, 25)]
pads.append(LayoutPad(number="30", x=1, y=0, net="SIG"))
graph = DesignGraph(
components={"U1": _ic("U1", "IC24", "Package_DFN_QFN:QFN-24-1EP", 24)},
)
layout = LayoutGraph(
footprints={
"U1": LayoutFootprint(
reference="U1", footprint="Package_DFN_QFN:QFN-24-1EP",
x=0, y=0, pads=pads,
),
},
)
assert "PE-BOM-011" in _ids(
check_bom_pcb_datasheet(graph, {"IC24": cons}, layout),
)
def test_qfn_vs_vqfn_same_24_qfn_land_not_bom_010():
"""U19: datasheet QFN vs CAD VQFN-24 4×4 is the same land family."""
cons = ComponentConstraints(
mpn="LAN8720A",
package_info=PackageInfo(
base_family="SMS", package="QFN-24", pin_count=24,
),
pintable=[Pin(number=str(i), name=f"P{i}") for i in range(1, 25)],
absolute_maximum_ratings=[],
rules=[],
)
pads = [LayoutPad(number=str(i), x=0, y=0, net="SIG") for i in range(1, 25)]
pads.append(LayoutPad(number="25", x=0, y=0, net="GND", pinfunction="EP"))
graph = DesignGraph(
components={
"U19": _ic(
"U19", "LAN8720A",
"Package_DFN_QFN:VQFN-24-1EP_4x4mm_P0.5mm_EP2.5x2.5mm_ThermalVias",
24,
),
},
schematic_fields={
"U19": {
"footprint": (
"Package_DFN_QFN:VQFN-24-1EP_4x4mm_P0.5mm_EP2.5x2.5mm_ThermalVias"
),
},
},
bom_fields={"U19": {"footprint": "VQFN-24"}},
)
layout = LayoutGraph(
footprints={
"U19": LayoutFootprint(
reference="U19",
footprint=(
"Package_DFN_QFN:VQFN-24-1EP_4x4mm_P0.5mm_EP2.5x2.5mm_ThermalVias"
),
x=0, y=0, pads=pads,
),
},
)
ids = _ids(check_bom_pcb_datasheet(graph, {"LAN8720A": cons}, layout))
assert "PE-BOM-010" not in ids
assert "PE-BOM-011" not in ids
def test_hubaudio_u11_u12_u19_software_not_error():
if not _HUB_PCB.is_file():
return
layout = parse_kicad_pcb(_HUB_PCB)
slim = LayoutGraph(
footprints={k: layout.footprints[k] for k in ("U11", "U12", "U19")
if k in layout.footprints},
)
graph = DesignGraph(
components={
"U11": _ic(
"U11", "ESP32-S31-WROOM-3",
"MIKILAB_ESP32_S31_WROOM_3:ESP32-S31-WROOM-3", 61,
),
"U12": _ic(
"U12", "Si4684-A10-GM",
"Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.3x5.3mm_ThermalVias",
48,
),
"U19": _ic(
"U19", "LAN8720A",
"Package_DFN_QFN:VQFN-24-1EP_4x4mm_P0.5mm_EP2.5x2.5mm_ThermalVias",
24,
),
},
)
cmap = {
"ESP32-S31-WROOM-3": ComponentConstraints(
mpn="ESP32-S31-WROOM-3",
package_info=PackageInfo(base_family="ESP", package="QFN-56", pin_count=61),
pintable=[Pin(number=str(i), name=f"P{i}") for i in range(1, 62)],
absolute_maximum_ratings=[],
rules=[],
),
"Si4684-A10-GM": ComponentConstraints(
mpn="Si4684-A10-GM",
package_info=PackageInfo(base_family="Sky", package="QFN-48", pin_count=24),
pintable=[Pin(number=str(i), name=f"P{i}") for i in range(1, 25)],
absolute_maximum_ratings=[],
rules=[],
),
"LAN8720A": ComponentConstraints(
mpn="LAN8720A",
package_info=PackageInfo(base_family="SMS", package="QFN-24", pin_count=24),
pintable=[Pin(number=str(i), name=f"P{i}") for i in range(1, 25)],
absolute_maximum_ratings=[],
rules=[],
),
}
findings = check_bom_pcb_datasheet(graph, cmap, slim)
err = [
f for f in findings
if f.rule_id in {"PE-BOM-010", "PE-BOM-011"} and f.status == "ERROR"
]
assert err == [], [f.finding for f in err]