Skip to content

Commit 853cfc8

Browse files
committed
Add TVS generic, matcher
1 parent b90aad2 commit 853cfc8

File tree

4 files changed

+224
-0
lines changed

4 files changed

+224
-0
lines changed

bom/helpers.zen

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,41 @@ def ferrite_bead(impedance: str, frequency: str, current: str, dcr: str, mpn: st
341341
}
342342

343343

344+
def tvs(standoff_voltage: str, clamping_voltage: str, peak_pulse_power: str, mpn: str, manufacturer: str) -> dict:
345+
"""Helper to create TVS diode catalog entry.
346+
347+
Args:
348+
standoff_voltage: Reverse standoff voltage (e.g., "5V" or "5V to 6V")
349+
clamping_voltage: Clamping voltage @ Ipp (e.g., "9.2V" or "9V to 12V")
350+
peak_pulse_power: Peak pulse power (e.g., "200W" or "200W to 400W")
351+
mpn: Manufacturer part number
352+
manufacturer: Manufacturer name
353+
354+
Returns:
355+
TVS diode dictionary
356+
"""
357+
# Use ranges for all values to support flexible matching
358+
standoff_val = (
359+
VoltageRange(standoff_voltage)
360+
if "to" in standoff_voltage
361+
else VoltageRange(standoff_voltage + " to " + standoff_voltage)
362+
)
363+
clamping_val = (
364+
VoltageRange(clamping_voltage)
365+
if "to" in clamping_voltage
366+
else VoltageRange(clamping_voltage + " to " + clamping_voltage)
367+
)
368+
power_val = Power(peak_pulse_power)
369+
370+
return {
371+
"standoff_voltage": standoff_val,
372+
"clamping_voltage": clamping_val,
373+
"peak_pulse_power": power_val,
374+
"mpn": mpn,
375+
"manufacturer": manufacturer,
376+
}
377+
378+
344379
def match_component(match: dict, parts: tuple | list, matcher: str = "match_component"):
345380
"""Create a component modifier that matches specific properties and assigns MPN.
346381

bom/match_generics.zen

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ load(
3030
"led",
3131
"ferrite_bead",
3232
"pin_header",
33+
"tvs",
3334
)
3435

3536

@@ -249,6 +250,26 @@ HOUSE_PH = {
249250
("2.54mm", 2, 20, "Vertical"): [("61304021121", "Würth Elektronik")],
250251
}
251252

253+
# House TVS diode catalog by package (8/20µs pulse ratings per IEC 61000-4-5)
254+
HOUSE_TVS_BY_PKG = {
255+
"DO-219AB": [
256+
# Vishay eSMP series - 5V standoff
257+
tvs("5V", "9.2V", "1000W", "SMF5V0A-E3-08", "Vishay General Semiconductor - Diodes Division"),
258+
# Littelfuse SZSMF series - 5V standoff (AEC-Q101 automotive)
259+
tvs("5V", "9.2V", "1000W", "SZSMF5.0AT1G", "Littelfuse Inc."),
260+
# Vishay eSMP series - 20V standoff
261+
tvs("20V", "32.4V", "1000W", "SMF20A-E3-08", "Vishay General Semiconductor - Diodes Division"),
262+
# Littelfuse SMF series - 20V standoff
263+
tvs("20V", "32.4V", "1000W", "SMF20A", "Littelfuse Inc."),
264+
# Littelfuse SZSMF series - 20V standoff (AEC-Q101 automotive)
265+
tvs("20V", "32.4V", "1000W", "SZSMF20AT1G", "Littelfuse Inc."),
266+
],
267+
"DO-214AC": [
268+
# Vishay TransZorb series - 20V standoff
269+
tvs("20V", "32.4V", "2000W", "SMAJ20A-E3/61", "Vishay General Semiconductor - Diodes Division"),
270+
],
271+
}
272+
252273

253274
def assign_house_resistor(c, series_by_pkg):
254275
"""Assign house resistor MPNs."""
@@ -507,6 +528,51 @@ def assign_house_pin_header(c, house_ph):
507528
)
508529

509530

531+
def assign_house_tvs(c, house_tvs_by_pkg):
532+
"""Assign house TVS diode MPNs."""
533+
pkg = prop(c, ["package", "Package"])
534+
req_standoff = prop(c, ["reverse_standoff_voltage", "Reverse_standoff_voltage"])
535+
req_clamping = prop(c, ["reverse_clamping_voltage", "Reverse_clamping_voltage"])
536+
req_power = prop(c, ["peak_pulse_power", "Peak_pulse_power"])
537+
538+
parts = house_tvs_by_pkg.get(pkg, [])
539+
matches = []
540+
541+
for p in parts:
542+
# Standoff voltage: part must be within the requirement
543+
if req_standoff and p["standoff_voltage"] not in req_standoff:
544+
continue
545+
546+
# Clamping voltage: part must be within the requirement
547+
if req_clamping and p["clamping_voltage"] not in req_clamping:
548+
continue
549+
550+
# Power: part must be within the requirement
551+
if req_power and p["peak_pulse_power"] not in req_power:
552+
continue
553+
554+
matches.append(p)
555+
556+
if matches:
557+
# Use first match as primary, rest as alternatives
558+
manufacturer = matches[0]["manufacturer"]
559+
all_mpns = [p["mpn"] for p in matches]
560+
set_primary_and_alts(c, all_mpns[0], manufacturer, all_mpns[1:])
561+
c.matcher = "assign_house_tvs"
562+
return
563+
564+
# Generate warning
565+
warn(
566+
"No house TVS diode found for "
567+
+ c.value
568+
+ " "
569+
+ pkg
570+
+ ".\nTry using different component values or specify mpn directly "
571+
+ "(https://docs.pcb.new/pages/spec#match-component-match%2C-parts)",
572+
kind="bom.match_generic",
573+
)
574+
575+
510576
def assign_house_parts(c):
511577
"""Single modifier that dispatches component matching by type."""
512578
# Skip if already has MPN
@@ -522,6 +588,8 @@ def assign_house_parts(c):
522588
assign_house_led(c, HOUSE_LEDS_BY_PKG)
523589
elif c.type == "ferrite_bead":
524590
assign_house_ferrite_bead(c, HOUSE_FB_BY_PKG)
591+
elif c.type == "tvs":
592+
assign_house_tvs(c, HOUSE_TVS_BY_PKG)
525593
elif c.type == "connector":
526594
connector_type = prop(c, ["connector_type", "Connector_type"])
527595
if connector_type == "Pin Header":

generics/Tvs.zen

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
load("../units.zen", "PowerRange", "Voltage", "VoltageRange", "CurrentRange")
2+
load("../interfaces.zen", "Power", "Ground")
3+
load("../utils.zen", "format_value")
4+
5+
# Package JEDEC LxWxH
6+
# SMF DO-219AB 3.0×1.8×1.0
7+
# SMA DO-214AC 4.6×2.6×2.1
8+
# SMB DO-214AA 5.3×3.7×2.5
9+
# SMC DO-214AB 7.8×6.8×2.5
10+
Package = enum("DO-219AB", "DO-214AC", "DO-214AA", "DO-214AB")
11+
12+
USB_5V_STANDOFF_VOLTAGE = VoltageRange("5 to 6V")
13+
USB_5V_CLAMPING_VOLTAGE = VoltageRange("9 to 12V")
14+
USB_5V_POWER_PP = PowerRange("800 to 1200W")
15+
16+
package = config("package", Package, default=Package("DO-219AB"))
17+
# 8/20 μs waveform acc. IEC 61000-4-5
18+
reverse_standoff_voltage = config("reverse_standoff_voltage", VoltageRange, default=USB_5V_STANDOFF_VOLTAGE)
19+
reverse_clamping_voltage = config("reverse_clamping_voltage", VoltageRange, default=USB_5V_CLAMPING_VOLTAGE)
20+
peak_pulse_power = config("peak_pulse_power", PowerRange, default=USB_5V_POWER_PP)
21+
22+
mpn = config("mpn", str, optional=True)
23+
manufacturer = config("manufacturer", str, optional=True)
24+
25+
A = io("A", Ground)
26+
K = io("K", Power, default=Power("VCC", voltage=VoltageRange("5V")))
27+
28+
if reverse_clamping_voltage.min < reverse_standoff_voltage.max:
29+
error(
30+
"reverse clamping voltage ("
31+
+ str(reverse_clamping_voltage)
32+
+ ") must be >= reverse standoff voltage ("
33+
+ str(reverse_standoff_voltage)
34+
+ ")"
35+
)
36+
37+
if hasattr(K, "voltage"):
38+
if K.voltage.max > reverse_clamping_voltage.min:
39+
error(
40+
"Power net voltage ("
41+
+ str(K.voltage)
42+
+ ") must be <= reverse clamping voltage ("
43+
+ str(reverse_clamping_voltage)
44+
+ ")"
45+
)
46+
else:
47+
warn("No voltage specified for power net")
48+
49+
50+
def _footprint(package: Package) -> str:
51+
kicad_footprints = {
52+
Package("DO-219AB"): "@kicad-footprints/Diode_SMD.pretty/D_SMF.kicad_mod",
53+
Package("DO-214AC"): "@kicad-footprints/Diode_SMD.pretty/D_SMA.kicad_mod",
54+
Package("DO-214AA"): "@kicad-footprints/Diode_SMD.pretty/D_SMB.kicad_mod",
55+
Package("DO-214AB"): "@kicad-footprints/Diode_SMD.pretty/D_SMC.kicad_mod",
56+
}
57+
return kicad_footprints[package]
58+
59+
60+
Component(
61+
name="D",
62+
mpn=mpn,
63+
manufacturer=manufacturer,
64+
prefix="D",
65+
symbol=Symbol(library="@kicad-symbols/Device.kicad_sym", name="D_Zener"),
66+
footprint=File(_footprint(package)),
67+
properties={
68+
"value": format_value(reverse_standoff_voltage, "TVS Diode", peak_pulse_power),
69+
"package": package,
70+
"peak_pulse_power": peak_pulse_power,
71+
"reverse_standoff_voltage": reverse_standoff_voltage,
72+
"reverse_clamping_voltage": reverse_clamping_voltage,
73+
},
74+
type="tvs",
75+
pins={
76+
"A": A,
77+
"K": K,
78+
},
79+
)

generics/test/test_Tvs.zen

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Comprehensive test for TVS Diode component."""
2+
3+
load("../../interfaces.zen", "Power", "Ground")
4+
load("../../units.zen", "PowerRange", "Voltage", "VoltageRange")
5+
6+
Tvs = Module("../Tvs.zen")
7+
8+
vcc = Power("VCC", voltage=VoltageRange("3.3V"))
9+
gnd = Ground("GND")
10+
11+
# Test all Package values
12+
for package in Tvs.Package.values():
13+
name = "D_TVS_" + str(package)
14+
Tvs(
15+
name=name,
16+
package=package,
17+
peak_pulse_power=PowerRange("800 to 1200W"),
18+
reverse_clamping_voltage=VoltageRange("9 to 12V"),
19+
A=gnd,
20+
K=vcc,
21+
)
22+
23+
# Test with different voltage ratings
24+
Tvs(
25+
name="D_TVS_3V3",
26+
package="DO-219AB",
27+
peak_pulse_power=PowerRange("400 to 600W"),
28+
reverse_standoff_voltage=VoltageRange("3.3V"),
29+
reverse_clamping_voltage=VoltageRange("6 to 8V"),
30+
A=gnd,
31+
K=vcc,
32+
)
33+
34+
Tvs(
35+
name="D_TVS_12V",
36+
package="DO-214AC",
37+
peak_pulse_power=PowerRange("1200 to 1500W"),
38+
reverse_standoff_voltage=VoltageRange("12V"),
39+
reverse_clamping_voltage=VoltageRange("19 to 21V"),
40+
A=gnd,
41+
K=vcc,
42+
)

0 commit comments

Comments
 (0)