Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 66 additions & 8 deletions bom/helpers.zen
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""BOM matching helper functions"""

load("../utils.zen", "e96", "e24")
load("../units.zen", "ResistanceRange", "Voltage", "Power", "Capacitance")


def prop(c, names):
Expand Down Expand Up @@ -89,14 +90,15 @@ def iec60062_3digit(value):
return sig_digits + str(mult)


def resistor_series(series, min_val, max_val, max_v, tolerance, e_series, encode, suffix):
def resistor_series(manufacturer, series, resistance_range, max_v, power, tolerance, e_series, encode, suffix):
"""Helper to create resistor series entry.

Args:
manufacturer: Manufacturer name (e.g., "Panasonic Electronic Components")
series: Series prefix (e.g., "ERJ-2RKF")
min_val: Min resistance (e.g., "10Ohm")
max_val: Max resistance (e.g., "1MOhm")
resistance_range: Resistance range (e.g., "10Ohm – 1MOhm")
max_v: Max voltage (e.g., "50V")
power: Power rating (e.g., "0.1W")
tolerance: Tolerance string (e.g., "1%")
e_series: List of E-series (e.g., ["e96", "e24"])
encode: Encoding function (iec60062_3digit or iec60062_4digit)
Expand All @@ -106,17 +108,73 @@ def resistor_series(series, min_val, max_val, max_v, tolerance, e_series, encode
Resistor series dictionary
"""
return {
"manufacturer": manufacturer,
"series": series,
"min": min_val,
"max": max_val,
"max_v": max_v,
"resistance_range": ResistanceRange(resistance_range),
"max_v": Voltage(max_v),
"power": Power(power),
"tolerance": tolerance,
"e_series": e_series,
"encode": encode,
"suffix": suffix,
}


def panasonic_erj(series, resistance_range, voltage, power, tolerance, e_series, suffix, encode):
"""Shorthand for Panasonic ERJ resistor series.

Args:
series: Series name (e.g., "ERJ-3EKF")
resistance_range: Resistance range (e.g., "10Ohm – 1MOhm")
voltage: Max voltage (e.g., "75V")
power: Power rating (e.g., "0.333W")
tolerance: Tolerance string (e.g., "1%")
e_series: List of E-series (e.g., ["e96", "e24"])
suffix: MPN suffix (e.g., "V")
encode: Encoding function (iec60062_3digit or iec60062_4digit)

Returns:
Resistor series dictionary
"""
return resistor_series(
"Panasonic Electronic Components",
series,
resistance_range,
voltage,
power,
tolerance,
e_series,
encode,
suffix,
)


def discrete_resistor_series(manufacturer, series, max_v, power, tolerance, values_with_mpns):
"""Helper to create discrete resistor series (non-E-series, exact values only).

Args:
manufacturer: Manufacturer name (e.g., "Stackpole Electronics Inc")
series: Series name (e.g., "CSNL2512")
max_v: Max voltage (e.g., "170V")
power: Power rating (e.g., "2W")
tolerance: Tolerance string (e.g., "1%")
values_with_mpns: List of tuples [(resistance_str, mpn), ...]
e.g., [("0.5mOhm", "CSNL2512FTL500"), ("1mOhm", "CSNL2512FT1L00")]

Returns:
Discrete resistor series dictionary
"""
return {
"type": "discrete",
"manufacturer": manufacturer,
"series": series,
"values": values_with_mpns,
"max_v": Voltage(max_v),
"power": Power(power),
"tolerance": tolerance,
}


def murata_cap(dielectric, voltage, cap, base_mpn, suffixes, manufacturer="Murata Electronics"):
"""Helper to create Murata capacitor entry with auto-generated packaging variants.

Expand All @@ -135,8 +193,8 @@ def murata_cap(dielectric, voltage, cap, base_mpn, suffixes, manufacturer="Murat

return {
"dielectric": dielectric,
"voltage": voltage,
"cap": cap,
"voltage": Voltage(voltage),
"cap": Capacitance(cap),
"mpns": mpns,
"manufacturer": manufacturer,
}
Expand Down
111 changes: 84 additions & 27 deletions bom/match_generics.zen
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
Automatically assigns house MPNs to resistors and capacitors.
"""

load("../units.zen", "Voltage", "Resistance", "Capacitance")
load("../units.zen", "Voltage", "Resistance", "ResistanceRange", "Capacitance", "Power")
load(
"helpers.zen",
"prop",
"set_primary_and_alts",
"find_e_series_match",
"iec60062_3digit",
"iec60062_4digit",
"resistor_series",
"panasonic_erj",
"discrete_resistor_series",
"murata_cap",
)

Expand All @@ -31,16 +32,50 @@ _DIELECTRIC_RANK = {
# Resistor series by package
SERIES_BY_PKG = {
"0402": [
resistor_series("ERJ-H2CF", "1Ohm", "10Ohm", "50V", "1%", ["e96", "e24"], iec60062_4digit, "X"),
resistor_series("ERJ-2RKF", "10Ohm", "1MOhm", "50V", "1%", ["e96", "e24"], iec60062_4digit, "X"),
resistor_series("ERJ-U02J", "1MOhm", "10MOhm", "50V", "5%", ["e24"], iec60062_3digit, "X"),
resistor_series("ERJ-PA2F", "1Ohm", "1MOhm", "50V", "1%", ["e96", "e24"], iec60062_4digit, "X"),
panasonic_erj("ERJ-H2CF", "1Ohm – 10Ohm", "50V", "0.1W", "1%", ["e96", "e24"], "X", iec60062_4digit),
panasonic_erj("ERJ-2RKF", "10Ohm – 1MOhm", "50V", "0.1W", "1%", ["e96", "e24"], "X", iec60062_4digit),
panasonic_erj("ERJ-U02J", "1MOhm – 10MOhm", "50V", "0.1W", "5%", ["e24"], "X", iec60062_3digit),
panasonic_erj("ERJ-PA2F", "1Ohm – 1MOhm", "50V", "0.1W", "1%", ["e96", "e24"], "X", iec60062_4digit),
],
"0603": [
resistor_series("ERJ-H3QF", "1Ohm", "10Ohm", "75V", "1%", ["e96", "e24"], iec60062_4digit, "V"),
resistor_series("ERJ-3EKF", "10Ohm", "1MOhm", "75V", "1%", ["e96", "e24"], iec60062_4digit, "V"),
resistor_series("ERJ-U03J", "1MOhm", "10MOhm", "75V", "5%", ["e24"], iec60062_3digit, "V"),
resistor_series("ERJ-PA3F", "1Ohm", "1MOhm", "75V", "1%", ["e96", "e24"], iec60062_4digit, "V"),
panasonic_erj("ERJ-H3QF", "1Ohm – 10Ohm", "75V", "0.333W", "1%", ["e96", "e24"], "V", iec60062_4digit),
panasonic_erj("ERJ-3EKF", "10Ohm – 1MOhm", "75V", "0.333W", "1%", ["e96", "e24"], "V", iec60062_4digit),
panasonic_erj("ERJ-U03J", "1MOhm – 10MOhm", "75V", "0.333W", "5%", ["e24"], "V", iec60062_3digit),
panasonic_erj("ERJ-PA3F", "1Ohm – 1MOhm", "75V", "0.333W", "1%", ["e96", "e24"], "V", iec60062_4digit),
],
"2512": [
# Stackpole CSNL series: Current sense resistors
discrete_resistor_series(
"Stackpole Electronics Inc",
"CSNL2512",
"170V",
"2W",
"1%",
[
("1mOhm", "CSNL2512FT1L00"),
("2mOhm", "CSNL2512FT2L00"),
("3mOhm", "CSNL2512FT3L00"),
("4mOhm", "CSNL2512FT4L00"),
("5mOhm", "CSNL2512FT5L00"),
("10mOhm", "CSNL2512FT10L0"),
],
),
# YAGEO PA_E series: AEC-Q200 current sense resistors
discrete_resistor_series(
"YAGEO",
"PA_E",
"170V",
"2W",
"1%",
[
("1mOhm", "PA2512FKF7W0R001E"),
("2mOhm", "PA2512FKF7W0R002E"),
("3mOhm", "PA2512FKF7W0R003E"),
("4mOhm", "PA2512FKF7W0R004E"),
("5mOhm", "PA2512FKF7W0R005E"),
("10mOhm", "PA2512FKF7W0R01E"),
],
),
],
}

Expand Down Expand Up @@ -104,34 +139,56 @@ def assign_house_resistor(c, series_by_pkg):
resistance = Resistance(prop(c, ["resistance", "Resistance"]))
pkg = prop(c, ["package", "Package"])
v_req = prop(c, ["Voltage", "voltage"])
voltage_constraint = Voltage(v_req).value if v_req else None
voltage_constraint = Voltage(v_req) if v_req else None
p_req = prop(c, ["Power", "power"])
power_constraint = Power(p_req) if p_req else None

# Skip 0 ohm resistors - they can't be matched to E-series
if resistance.value <= 0:
if resistance.value <= 0.0:
return

series_list = series_by_pkg.get(pkg, [])
matches = []
manufacturer = None

for spec in series_list:
spec_min = Resistance(spec["min"]).value
spec_max = Resistance(spec["max"]).value
spec_max_v = Voltage(spec["max_v"]).value
spec_type = spec.get("type", "e_series")
spec_max_v = spec["max_v"]
spec_power = spec["power"]
spec_tol = float(spec["tolerance"].replace("%", "")) / 100.0

if voltage_constraint and voltage_constraint > spec_max_v:
continue

matched = find_e_series_match(resistance, spec_tol, spec["e_series"])
if not matched or matched.value < spec_min or matched.value > spec_max:
if power_constraint and power_constraint > spec_power:
continue

encoder = spec.get("encode", iec60062_4digit)
code = encoder(matched.value)
mpn = spec["series"] + code + spec.get("suffix", "X")
matches.append(mpn)
if spec_type == "discrete":
# Discrete series: exact value match with tolerance
r_with_tol = resistance.with_tolerance(spec_tol) if resistance.tolerance <= 0 else resistance

for val_str, mpn in spec["values"]:
val_r = Resistance(val_str).with_tolerance(spec_tol)
if val_r.within(r_with_tol):
matches.append(mpn)
if manufacturer == None:
manufacturer = spec["manufacturer"]
else:
# E-series: standard matching logic
spec_range = spec["resistance_range"]
matched = find_e_series_match(resistance, spec_tol, spec["e_series"])
if not matched or matched.value < spec_range.min or matched.value > spec_range.max:
continue

encoder = spec.get("encode", iec60062_4digit)
code = encoder(matched.value)
mpn = spec["series"] + code + spec.get("suffix", "X")
matches.append(mpn)
if manufacturer == None:
manufacturer = spec["manufacturer"]

if matches:
set_primary_and_alts(c, matches[0], "Panasonic Electronic Components", matches[1:])
set_primary_and_alts(c, matches[0], manufacturer, matches[1:])
c.matcher = "assign_house_resistor"
return

Expand All @@ -156,7 +213,7 @@ def assign_house_capacitor(c, house_caps_by_pkg):
pkg = prop(c, ["package", "Package"])
req_diel = prop(c, ["dielectric", "Dielectric"])
v_req = prop(c, ["voltage", "Voltage"])
req_voltage = Voltage(v_req).value if v_req else None
req_voltage = Voltage(v_req) if v_req else None

if cap.tolerance <= 0:
cap = cap.with_tolerance(0.2)
Expand All @@ -165,8 +222,8 @@ def assign_house_capacitor(c, house_caps_by_pkg):
matches = []

for p in parts:
p_cap = Capacitance(p["cap"])
p_voltage = Voltage(p["voltage"]).value
p_cap = p["cap"]
p_voltage = p["voltage"]

if req_diel:
h_rank = _DIELECTRIC_RANK.get(p["dielectric"], 99)
Expand All @@ -180,9 +237,9 @@ def assign_house_capacitor(c, house_caps_by_pkg):
if not p_cap.within(cap):
continue

err = abs(p_cap.value - cap.value) / cap.value
err = p_cap.diff(cap) / cap
diel_rank = _DIELECTRIC_RANK.get(p["dielectric"], 99)
score = (err, p_cap.tolerance, -p_voltage, diel_rank)
score = (err, p_cap.tolerance, p_voltage * -1, diel_rank)

matches.append((score, p))

Expand Down
8 changes: 6 additions & 2 deletions generics/Resistor.zen
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
load("../config.zen", "config_unit")
load("../units.zen", "Resistance", "Voltage")
load("../units.zen", "Resistance", "Voltage", "Power")
load("../utils.zen", "format_value")

# -----------------------------------------------------------------------------
Expand All @@ -22,6 +22,7 @@ mpn = config("mpn", str, optional=True)
manufacturer = config("manufacturer", str, optional=True)
mount = config("mount", Mount, default=Mount("SMD"), optional=True)
voltage = config_unit("voltage", Voltage, optional=True)
power = config_unit("power", Power, optional=True)
use_us_symbol = config("use_us_symbol", bool, default=False, optional=True)

do_not_populate = config("do_not_populate", bool, default=False)
Expand All @@ -34,12 +35,15 @@ if exclude_from_bom:

# Properties – combined and normalized
properties = {
"value": format_value(value, voltage),
"value": format_value(value, voltage, power),
"package": package,
"resistance": value,
"voltage": voltage,
}

if power:
properties["power"] = power

# -----------------------------------------------------------------------------
# IO ports
# -----------------------------------------------------------------------------
Expand Down