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
2 changes: 1 addition & 1 deletion edg/BoardTop.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def refinements(self) -> Refinements:
(Neopixel, Ws2812b),
],
class_values=[
(SelectorArea, ["footprint_area"], Range.from_lower(4.0)), # at least 0603
(SelectorArea, ["filter_area"], Range.from_lower(4.0)), # at least 0603
],
)

Expand Down
30 changes: 21 additions & 9 deletions edg/abstract_parts/PartsTablePart.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from abc import abstractmethod
from typing import Optional, Union, Any

Expand Down Expand Up @@ -56,8 +57,7 @@ class PartsTableSelector(PartsTablePart, GeneratorBlock, PartsTableBase):

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.generator_param(self.part)
self.generator_param(self.excluded_parts)
self.generator_param(self.part, self.excluded_parts)

def _row_filter(self, row: PartsTableRow) -> bool:
"""Returns whether the candidate row satisfies the requirements (should be kept).
Expand Down Expand Up @@ -88,6 +88,8 @@ def _row_generate(self, row: PartsTableRow) -> None:

@override
def generate(self) -> None:
super().generate()

matching_table = self._get_table().filter(lambda row: self._row_filter(row))
postprocessed_table = self._table_postprocess(matching_table)
postprocessed_table = postprocessed_table.sort_by(self._row_sort_by)
Expand All @@ -101,28 +103,38 @@ def generate(self) -> None:
class SelectorFootprint(PartsTablePart):
"""Mixin that allows a specified footprint, for Blocks that automatically select a part."""

def __init__(self, *args: Any, footprint_spec: StringLike = "", **kwargs: Any) -> None:
def __init__(
self, *args: Any, filter_footprints: ArrayStringLike = [], footprint_spec: StringLike = "", **kwargs: Any
) -> None:
super().__init__(*args, **kwargs)
self.footprint_spec = self.ArgParameter(footprint_spec) # actual_footprint left to the actual footprint
self.filter_footprints = self.ArgParameter(filter_footprints) # actual_footprint left to the actual footprint
self.footprint_spec = self.ArgParameter(footprint_spec) # deprecated, named because self.footprint taken


@non_library
class PartsTableFootprintFilter(PartsTableSelector, SelectorFootprint):
"""A combination of PartsTableSelector with SelectorFootprint, with row filtering on footprint_spec.
"""A combination of PartsTableSelector with SelectorFootprint, with row filtering on filter_footprints.
Does not create the footprint itself, this can be used as a base class where footprint filtering is desired
but an internal block is created instead."""

KICAD_FOOTPRINT = PartsTableColumn(str)

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.generator_param(self.footprint_spec)
self.generator_param(self.filter_footprints, self.footprint_spec)

@override
def generate(self) -> None:
super().generate()
if self.get(self.footprint_spec):
warnings.warn(f"footprint_spec replaced with filter_footprints taking an array", DeprecationWarning)

@override
def _row_filter(self, row: PartsTableRow) -> bool:
return super()._row_filter(row) and (
(not self.get(self.footprint_spec)) or self.get(self.footprint_spec) == row[self.KICAD_FOOTPRINT]
)
filter_footprints = self.get(self.filter_footprints)
if self.get(self.footprint_spec):
filter_footprints.append(self.get(self.footprint_spec))
return super()._row_filter(row) and ((not filter_footprints or row[self.KICAD_FOOTPRINT] in filter_footprints))


@non_library
Expand Down
25 changes: 20 additions & 5 deletions edg/abstract_parts/SelectorArea.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from typing import Any

from typing_extensions import override
Expand All @@ -9,7 +10,7 @@

@abstract_block
class SelectorArea(PartsTablePart):
"""A base mixin that defines a footprint_area range specification for blocks that automatically select parts.
"""A base mixin that defines a filter_area range specification for blocks that automatically select parts.
Provides no implementation, only defines the specification parameter.

Some common areas for SMD parts:
Expand All @@ -23,9 +24,16 @@ class SelectorArea(PartsTablePart):
2512 R=29.3376 D=29.3376
"""

def __init__(self, *args: Any, footprint_area: RangeLike = RangeExpr.ALL, **kwargs: Any) -> None:
def __init__(
self,
*args: Any,
filter_area: RangeLike = RangeExpr.ALL,
footprint_area: RangeLike = RangeExpr.ALL,
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)
self.footprint_area = self.ArgParameter(footprint_area)
self.filter_area = self.ArgParameter(filter_area)
self.footprint_area = self.ArgParameter(footprint_area) # deprecated

@classmethod
def _footprint_area(cls, footprint_name: str) -> float:
Expand All @@ -38,12 +46,19 @@ class PartsTableAreaSelector(PartsTableFootprintFilter, SelectorArea):

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.generator_param(self.footprint_area)
self.generator_param(self.filter_area, self.footprint_area)

@override
def generate(self) -> None:
super().generate()
if self.get(self.footprint_area) != Range.all():
warnings.warn(f"footprint_area replaced with filter_area", DeprecationWarning)

@override
def _row_filter(self, row: PartsTableRow) -> bool:
area = self.get(self.filter_area).intersect(self.get(self.footprint_area))
return super()._row_filter(row) and (
Range.exact(FootprintDataTable.area_of(row[self.KICAD_FOOTPRINT])).fuzzy_in(self.get(self.footprint_area))
Range.exact(FootprintDataTable.area_of(row[self.KICAD_FOOTPRINT])).fuzzy_in(area)
)

@classmethod
Expand Down
2 changes: 1 addition & 1 deletion edg/circuits/test_diodemerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def refinements(self) -> Refinements:
(Diode, CustomDiode),
],
class_values=[
(CustomDiode, ["footprint_spec"], "Diode_SMD:D_SOD-123"),
(CustomDiode, ["part_footprint"], "Diode_SMD:D_SOD-123"),
],
)

Expand Down
4 changes: 2 additions & 2 deletions edg/circuits/test_power_circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ def refinements(self) -> Refinements:
(SwitchFet, CustomFet),
],
instance_values=[
(["dut", "drv", "footprint_spec"], "Package_TO_SOT_SMD:SOT-23"),
(["dut", "ctl_fet", "footprint_spec"], "Package_TO_SOT_SMD:SOT-23"),
(["dut", "drv", "part_footprint"], "Package_TO_SOT_SMD:SOT-23"),
(["dut", "ctl_fet", "part_footprint"], "Package_TO_SOT_SMD:SOT-23"),
(["dut", "drv", "actual_gate_drive"], Range(1.0, 12)),
],
)
Expand Down
18 changes: 7 additions & 11 deletions edg/parts/human_interface/SpeakerDriver_Max98357a.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
from ...vendor_parts.jlc.JlcPart import JlcPart


class Max98357a_Device(InternalSubcircuit, JlcPart, SelectorFootprint, PartsTablePart, GeneratorBlock, FootprintBlock):
def __init__(self) -> None:
class Max98357a_Device(InternalSubcircuit, JlcPart, GeneratorBlock, FootprintBlock):
def __init__(self, *, part: StringLike = "") -> None:
super().__init__()

self.part = self.ArgParameter(part)

self.vdd = self.Port(
VoltageSink(
voltage_limits=(2.5, 5.5) * Volt,
Expand All @@ -28,15 +30,12 @@ def __init__(self) -> None:

self.out = self.Port(SpeakerDriverPort(AnalogSource()), [Output])

self.generator_param(self.part, self.footprint_spec)
self.generator_param(self.part)

@override
def generate(self) -> None:
super().generate()
if (
not self.get(self.footprint_spec)
or self.get(self.footprint_spec) == "Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.45x1.45mm"
):
if not self.get(self.part) or self.get(self.part) == "MAX98357AETE+T":
footprint = "Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.45x1.45mm"
pinning: Dict[str, Union[Passive, HasPassivePort]] = {
"4": self.vdd, # hard tied to left mode only TODO selectable SD_MODE
Expand All @@ -56,10 +55,7 @@ def generate(self) -> None:
part = "MAX98357AETE+T"
jlc_part = "C910544"
pnp_rot = -90
elif (
self.get(self.footprint_spec)
== "Package_BGA:Maxim_WLP-9_1.595x1.415_Layout3x3_P0.4mm_Ball0.27mm_Pad0.25mm_NSMD"
):
elif self.get(self.part) == "MAX98357AEWL+T":
footprint = "Package_BGA:Maxim_WLP-9_1.595x1.415_Layout3x3_P0.4mm_Ball0.27mm_Pad0.25mm_NSMD"
pinning = {
"A1": self.vdd, # hard tied to left mode only TODO selectable SD_MODE
Expand Down
6 changes: 3 additions & 3 deletions edg/parts/power/converter/LinearRegulators.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ def __init__(self, output_voltage: RangeLike):
self.assign(self.actual_dropout, (50, 250) * mVolt)

self.output_voltage = self.ArgParameter(output_voltage)
self.generator_param(self.output_voltage, self.part, self.footprint_spec)
self.generator_param(self.output_voltage, self.part, self.filter_footprints)

self.en = self.Port(
DigitalSink(
Expand Down Expand Up @@ -638,11 +638,11 @@ def generate(self) -> None:
]
# TODO should prefer parts by closeness to nominal (center) specified voltage
output_voltage_spec = self.get(self.output_voltage)
footprint_spec = self.get(self.footprint_spec)
filter_footprints = self.get(self.filter_footprints)
suitable_parts = [
part
for part in parts
if part[0] in output_voltage_spec and (not footprint_spec or footprint_spec == part[2])
if part[0] in output_voltage_spec and (not filter_footprints or part[2] in filter_footprints)
]
assert suitable_parts, "no regulator with compatible output"
part_output_voltage, part_number, footprint, jlc_number = suitable_parts[0]
Expand Down
23 changes: 10 additions & 13 deletions edg/vendor_parts/generic/CustomDiode.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@


class CustomDiode(Diode, FootprintBlock, GeneratorBlock):
"""Generic diode that automatically assigns footprint pinning."""

def __init__(
self,
*args: Any,
footprint_spec: StringLike = "",
manufacturer_spec: StringLike = "",
part_spec: StringLike = "",
part: StringLike = "",
part_footprint: StringLike = "",
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)
self.footprint_spec = self.ArgParameter(footprint_spec) # actual_footprint left to the actual footprint
self.manufacturer_spec = self.ArgParameter(manufacturer_spec)
self.part_spec = self.ArgParameter(part_spec)
self.part = self.ArgParameter(part)

self.generator_param(self.footprint_spec)
self.part_footprint = self.ArgParameter(part_footprint) # actual_footprint left to the actual footprint
self.generator_param(self.part_footprint)

# use ideal specs, which can be overridden with refinements
self.assign(self.actual_voltage_rating, Range.all())
Expand All @@ -31,10 +31,7 @@ def __init__(
def generate(self) -> None:
self.footprint(
self._standard_footprint().REFDES_PREFIX,
self.footprint_spec,
self._standard_footprint()._make_pinning(self, self.get(self.footprint_spec)),
mfr=self.manufacturer_spec,
part=self.part_spec,
value=self.part_spec,
datasheet="",
self.part_footprint,
self._standard_footprint()._make_pinning(self, self.get(self.part_footprint)),
part=self.part,
)
22 changes: 9 additions & 13 deletions edg/vendor_parts/generic/CustomFet.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,19 @@


class CustomFet(SwitchFet, FootprintBlock, GeneratorBlock):

def __init__(
self,
*args: Any,
footprint_spec: StringLike = "",
manufacturer_spec: StringLike = "",
part_spec: StringLike = "",
part: StringLike = "",
part_footprint: StringLike = "",
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)
self.footprint_spec = self.ArgParameter(footprint_spec) # actual_footprint left to the actual footprint
self.manufacturer_spec = self.ArgParameter(manufacturer_spec)
self.part_spec = self.ArgParameter(part_spec)
self.part = self.ArgParameter(part)

self.generator_param(self.footprint_spec)
self.part_footprint = self.ArgParameter(part_footprint) # actual_footprint left to the actual footprint
self.generator_param(self.part_footprint)

# use ideal specs, which can be overridden with refinements
self.assign(self.actual_drain_voltage_rating, Range.all())
Expand All @@ -34,10 +33,7 @@ def __init__(
def generate(self) -> None:
self.footprint(
self._standard_footprint().REFDES_PREFIX,
self.footprint_spec,
self._standard_footprint()._make_pinning(self, self.get(self.footprint_spec)),
mfr=self.manufacturer_spec,
part=self.part_spec,
value=self.part_spec,
datasheet="",
self.part_footprint,
self._standard_footprint()._make_pinning(self, self.get(self.part_footprint)),
part=self.part,
)
24 changes: 11 additions & 13 deletions edg/vendor_parts/generic/GenericCapacitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ class GenericMlcc(Capacitor, SelectorArea, FootprintBlock, GeneratorBlock):
MAX_CAP_PACKAGE = "Capacitor_SMD:C_1206_3216Metric" # default package for largest possible capacitor

def __init__(
self, *args: Any, footprint_spec: StringLike = "", derating_coeff: FloatLike = 1.0, **kwargs: Any
self, *args: Any, filter_footprints: ArrayStringLike = [], derating_coeff: FloatLike = 1.0, **kwargs: Any
) -> None:
"""
footprint specifies an optional constraint on footprint
derating_coeff specifies an optional derating coefficient (1.0 = no derating), that does not scale with package.
"""
super().__init__(*args, **kwargs)
self.footprint_spec = self.ArgParameter(footprint_spec)
self.filter_footprints = self.ArgParameter(filter_footprints)
self.derating_coeff = self.ArgParameter(derating_coeff)
self.generator_param(
self.capacitance, self.voltage, self.footprint_spec, self.footprint_area, self.derating_coeff
self.capacitance, self.voltage, self.filter_footprints, self.filter_area, self.derating_coeff
)

# Output values
Expand Down Expand Up @@ -119,18 +119,18 @@ def generate(self) -> None:
:param voltage: user-specified voltage
:param single_nominal_capacitance: used when no single cap with requested capacitance, must generate multiple parallel caps,
actually refers to max capacitance for a given part
:param footprint_spec: user-specified package footprint
:param derating_coeff: user-specified derating coefficient, if used then footprint_spec must be specified
:param filter_footprints: user-specified package footprint
:param derating_coeff: user-specified derating coefficient
"""
super().generate()
footprint = self.get(self.footprint_spec)
filter_footprints = self.get(self.filter_footprints)

def select_package(nominal_capacitance: float, voltage: Range) -> Optional[str]:
package_options = [
spec
for spec in self.PACKAGE_SPECS
if (not footprint or spec.name == footprint)
and (Range.exact(self._footprint_area(spec.name)).fuzzy_in(self.get(self.footprint_area)))
if (not filter_footprints or spec.name in filter_footprints)
and (Range.exact(self._footprint_area(spec.name)).fuzzy_in(self.get(self.filter_area)))
]

for package in package_options:
Expand All @@ -150,15 +150,13 @@ def select_package(nominal_capacitance: float, voltage: Range) -> Optional[str]:

self.assign(self.selected_nominal_capacitance, num_caps * nominal_capacitance)

if footprint == "":
split_package = self.MAX_CAP_PACKAGE
else:
split_package = footprint
valid_footprint = select_package(self.SINGLE_CAP_MAX, self.get(self.voltage))
assert valid_footprint is not None, "cannot select a valid footprint"

cap_model = DummyCapacitorFootprint(
capacitance=Range.exact(self.SINGLE_CAP_MAX),
voltage=self.voltage,
footprint=split_package,
footprint=valid_footprint,
value=f"{UnitUtils.num_to_prefix(self.SINGLE_CAP_MAX, 3)}F",
)
self.c = ElementDict[DummyCapacitorFootprint]()
Expand Down
10 changes: 5 additions & 5 deletions edg/vendor_parts/generic/GenericResistor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ def __init__(
*args: Any,
series: IntLike = 24,
tolerance: FloatLike = 0.01,
footprint_spec: StringLike = "",
filter_footprints: ArrayStringLike = [],
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)
self.series = self.ArgParameter(series)
self.tolerance = self.ArgParameter(tolerance)
self.footprint_spec = self.ArgParameter(footprint_spec)
self.filter_footprints = self.ArgParameter(filter_footprints)

self.generator_param(
self.resistance, self.power, self.series, self.tolerance, self.footprint_spec, self.footprint_area
self.resistance, self.power, self.series, self.tolerance, self.filter_footprints, self.filter_area
)

@override
Expand All @@ -58,8 +58,8 @@ def generate(self) -> None:
(package_power, package)
for package_power, package in self.PACKAGE_POWER
if package_power >= self.get(self.power).upper
and (not self.get(self.footprint_spec) or package == self.get(self.footprint_spec))
and (Range.exact(self._footprint_area(package)).fuzzy_in(self.get(self.footprint_area)))
and (not self.get(self.filter_footprints) or package in self.get(self.filter_footprints))
and (Range.exact(self._footprint_area(package)).fuzzy_in(self.get(self.filter_area)))
]
if not suitable_packages:
raise ValueError("no suitable resistor packages")
Expand Down
Loading
Loading