diff --git a/examples/pcb_layout.py b/examples/pcb_layout.py index d9d18816..a60f1c34 100644 --- a/examples/pcb_layout.py +++ b/examples/pcb_layout.py @@ -13,10 +13,17 @@ from faebryk.core.module import Module from faebryk.exporters.pcb.layout.absolute import LayoutAbsolute from faebryk.exporters.pcb.layout.extrude import LayoutExtrude +from faebryk.exporters.pcb.layout.heuristic_decoupling import ( + LayoutHeuristicElectricalClosenessDecouplingCaps, +) +from faebryk.exporters.pcb.layout.heuristic_pulls import ( + LayoutHeuristicElectricalClosenessPullResistors, +) from faebryk.exporters.pcb.layout.typehierarchy import LayoutTypeHierarchy from faebryk.libs.brightness import TypicalLuminousIntensity from faebryk.libs.examples.buildutil import apply_design_to_pcb from faebryk.libs.logging import setup_basic_logging +from faebryk.libs.units import P logger = logging.getLogger(__name__) @@ -24,6 +31,7 @@ class App(Module): leds: F.PoweredLED battery: F.Battery + eeprom: F.M24C08_FMN6TP def __preinit__(self) -> None: self.leds.power.connect(self.battery.power) @@ -34,6 +42,9 @@ def __preinit__(self) -> None: TypicalLuminousIntensity.APPLICATION_LED_INDICATOR_INSIDE.value.value ) + self.eeprom.power.voltage.merge(3.3 * P.V) + self.eeprom.set_address(0x0) + # Layout Point = F.has_pcb_position.Point L = F.has_pcb_position.layer_type @@ -56,11 +67,22 @@ def __preinit__(self) -> None: mod_type=F.Battery, layout=LayoutAbsolute(Point((0, 20, 0, L.BOTTOM_LAYER))), ), + LayoutTypeHierarchy.Level( + mod_type=F.M24C08_FMN6TP, + layout=LayoutAbsolute(Point((15, 10, 0, L.TOP_LAYER))), + ), ] ) self.add_trait(F.has_pcb_layout_defined(layout)) self.add_trait(F.has_pcb_position_defined(Point((50, 50, 0, L.NONE)))) + LayoutHeuristicElectricalClosenessDecouplingCaps.add_to_all_suitable_modules( + self + ) + LayoutHeuristicElectricalClosenessPullResistors.add_to_all_suitable_modules( + self + ) + # Boilerplate ----------------------------------------------------------------- diff --git a/src/faebryk/core/graphinterface.py b/src/faebryk/core/graphinterface.py index 5d7718e2..943e8d7d 100644 --- a/src/faebryk/core/graphinterface.py +++ b/src/faebryk/core/graphinterface.py @@ -61,7 +61,7 @@ def get_direct_connections(self) -> set["GraphInterface"]: return set(self.edges.keys()) def is_connected(self, other: "GraphInterface"): - return self.G.is_connected(self, other) + return self is other or self.G.is_connected(self, other) # Less graph-specific stuff diff --git a/src/faebryk/core/util.py b/src/faebryk/core/util.py index 1b33ce72..946512d9 100644 --- a/src/faebryk/core/util.py +++ b/src/faebryk/core/util.py @@ -493,8 +493,13 @@ def get_parent_of_type[T: Node](node: Node, parent_type: type[T]) -> T | None: return cast(parent_type, get_parent(node, lambda p: isinstance(p, parent_type))) -def get_parent_with_trait[TR: Trait](node: Node, trait: type[TR]): - for parent, _ in reversed(node.get_hierarchy()): +def get_parent_with_trait[TR: Trait]( + node: Node, trait: type[TR], include_self: bool = True +): + hierarchy = node.get_hierarchy() + if not include_self: + hierarchy = hierarchy[:-1] + for parent, _ in reversed(hierarchy): if parent.has_trait(trait): return parent, parent.get_trait(trait) raise ValueError("No parent with trait found") diff --git a/src/faebryk/exporters/pcb/kicad/transformer.py b/src/faebryk/exporters/pcb/kicad/transformer.py index 6ac0e658..1afe865c 100644 --- a/src/faebryk/exporters/pcb/kicad/transformer.py +++ b/src/faebryk/exporters/pcb/kicad/transformer.py @@ -25,6 +25,7 @@ C_arc, C_circle, C_effects, + C_footprint, C_fp_text, C_kicad_pcb_file, C_line, @@ -32,6 +33,7 @@ C_rect, C_stroke, C_text, + C_text_layer, C_wh, C_xy, C_xyr, @@ -420,7 +422,7 @@ def get_pad_pos_any(intf: F.Electrical) -> list[tuple[FPad, Point]]: # intf has no parent with footprint return [] - return [PCB_Transformer._get_pad_pos(fpad) for fpad in fpads] + return [PCB_Transformer.get_fpad_pos(fpad) for fpad in fpads] @staticmethod def get_pad_pos(intf: F.Electrical) -> tuple[FPad, Point] | None: @@ -429,10 +431,10 @@ def get_pad_pos(intf: F.Electrical) -> tuple[FPad, Point] | None: except ValueError: return None - return PCB_Transformer._get_pad_pos(fpad) + return PCB_Transformer.get_fpad_pos(fpad) @staticmethod - def _get_pad_pos(fpad: FPad): + def get_fpad_pos(fpad: FPad): fp, pad = fpad.get_trait(PCB_Transformer.has_linked_kicad_pad).get_pad() if len(pad) > 1: raise NotImplementedError( @@ -530,7 +532,7 @@ def _delete(self, obj: Any, prefix: str = ""): self._get_pcb_list_field(obj, prefix=prefix).remove(obj) def insert_via( - self, coord: tuple[float, float], net: str, size_drill: tuple[float, float] + self, coord: tuple[float, float], net: int, size_drill: tuple[float, float] ): self.pcb.vias.append( Via( @@ -548,7 +550,7 @@ def insert_text(self, text: str, at: C_xyr, font: Font, front: bool = True): GR_Text( text=text, at=at, - layer=f"{'F' if front else 'B'}.SilkS", + layer=C_text_layer(f"{'F' if front else 'B'}.SilkS"), effects=C_effects( font=font, justify=( @@ -725,12 +727,13 @@ def move_fp(self, fp: Footprint, coord: C_xyr, layer: str): if rot_angle: # Rotation vector in kicad footprint objs not relative to footprint rotation - # TODO: remove pad rotation, KiCad will do the rotating for us? + # or is it? for obj in fp.pads: - obj.at.r = (obj.at.r) % 360 + obj.at.r = (obj.at.r + rot_angle) % 360 # For some reason text rotates in the opposite direction + # or maybe not? for obj in fp.fp_texts + list(fp.propertys.values()): - obj.at.r = (obj.at.r - rot_angle) % 360 + obj.at.r = (obj.at.r + rot_angle) % 360 fp.at = coord @@ -739,17 +742,21 @@ def move_fp(self, fp: Footprint, coord: C_xyr, layer: str): if flip: - def _flip(x): + def _flip(x: str): return x.replace("F.", ".").replace("B.", "F.").replace(".", "B.") fp.layer = _flip(fp.layer) # TODO: sometimes pads are being rotated by kicad ?!?? - # for obj in fp.pads: - # obj.layers = [_flip(x) for x in obj.layers] + for obj in fp.pads: + obj.layers = [_flip(x) for x in obj.layers] - # for obj in get_all_geos(fp) + fp.fp_texts + list(fp.propertys.values()): - # obj.layer = _flip(obj.layer) + for obj in get_all_geos(fp) + fp.fp_texts + list(fp.propertys.values()): + if isinstance(obj, C_footprint.C_property): + obj = obj.layer + if isinstance(obj, C_fp_text): + obj = obj.layer + obj.layer = _flip(obj.layer) # Label if not any([x.text == "FBRK:autoplaced" for x in fp.fp_texts]): @@ -760,7 +767,7 @@ def _flip(x): at=C_xyr(0, 0, rot_angle), effects=C_effects(self.font), uuid=self.gen_uuid(mark=True), - layer="User.5", + layer=C_text_layer("User.5"), ) ) diff --git a/src/faebryk/exporters/pcb/layout/heuristic_decoupling.py b/src/faebryk/exporters/pcb/layout/heuristic_decoupling.py new file mode 100644 index 00000000..5827d96b --- /dev/null +++ b/src/faebryk/exporters/pcb/layout/heuristic_decoupling.py @@ -0,0 +1,253 @@ +# This file is part of the faebryk project +# SPDX-License-Identifier: MIT + +import logging +from dataclasses import dataclass +from enum import IntEnum + +import faebryk.library._F as F +from faebryk.core.module import Module +from faebryk.core.node import Node +from faebryk.exporters.pcb.kicad.transformer import PCB_Transformer +from faebryk.exporters.pcb.layout.layout import Layout +from faebryk.libs.kicad.fileformats import C_kicad_pcb_file, C_wh +from faebryk.libs.util import KeyErrorNotFound, NotNone, find + +logger = logging.getLogger(__name__) + + +KFootprint = C_kicad_pcb_file.C_kicad_pcb.C_pcb_footprint +KPad = KFootprint.C_pad + +# TODO move all those helpers and make them more general and precise + + +@dataclass +class Params: + distance_between_pad_edges: float = 1 + extra_rotation_of_footprint: float = 0 + + +class Side(IntEnum): + Right = 0 + Bottom = 90 + Left = 180 + Top = 270 + + def rot(self, angle=90): + return type(self)((self + angle) % 360) + + def rot_vector(self, vec: tuple[float, float]): + x, y = vec + if self == Side.Right: + return x, y + elif self == Side.Bottom: + return -y, x + elif self == Side.Left: + return -x, -y + elif self == Side.Top: + return y, -x + else: + assert False + + +def _get_pad_side(fp: KFootprint, pad: KPad) -> Side: + # TODO this def does not work well + + # relative to fp center + if pad.at.x < 0 and abs(pad.at.x) > abs(pad.at.y): + pos_side = Side.Left + elif pad.at.x > 0 and abs(pad.at.x) > abs(pad.at.y): + pos_side = Side.Right + elif pad.at.y < 0 and abs(pad.at.y) > abs(pad.at.x): + pos_side = Side.Top + else: + pos_side = Side.Bottom + + # pad size as heuristic + rot = (fp.at.r - pad.at.r) % 360 + assert pad.size.h is not None + if pad.size.h > pad.size.w and rot not in (90, 270): + pos_rot = {Side.Top, Side.Bottom} + else: + pos_rot = {Side.Right, Side.Left} + + if pos_side in pos_rot: + return pos_side + + assert False + + +type V2D = tuple[float, float] + + +def _vec_pad_center_to_edge(size: C_wh, side: Side): + assert size.h is not None + if side == Side.Top: + return 0, 0 - size.h / 2 + elif side == Side.Bottom: + return 0, 0 + size.h / 2 + elif side == Side.Left: + return 0 - size.w / 2, 0 + else: + return 0 + size.w / 2, 0 + + +def _next_to_pad( + fp: KFootprint, spad: KPad, dfp: KFootprint, dpad: KPad, params: Params +): + # TODO determine distance based on pads & footprint size + distance = params.distance_between_pad_edges + + def _add(v1: V2D, v2: V2D) -> V2D: + return v1[0] + v2[0], v1[1] + v2[1] + + def _sub(v1: V2D, v2: V2D) -> V2D: + return v1[0] - v2[0], v1[1] - v2[1] + + side = _get_pad_side(fp, spad) + + # rotate fp to let pads face each other (+extra rotation) + dside = _get_pad_side(dfp, dpad) + fp_rot_rel_to_source = ( + side.rot() - dside.rot() - 180 + params.extra_rotation_of_footprint + ) % 360 + + vec_distance_directed = side.rot_vector((distance, 0)) + vec_distance_from_pad_center = _add(vec_distance_directed, (spad.at.x, spad.at.y)) + + # make vec distance between edges instead of center + _spad_edge_vec = _vec_pad_center_to_edge(spad.size, side) + _dpad_edge_vec = Side(fp_rot_rel_to_source).rot_vector( + _vec_pad_center_to_edge(dpad.size, dside) + ) + vec_distance_from_pad_edge = _sub( + _add(vec_distance_from_pad_center, _spad_edge_vec), _dpad_edge_vec + ) + + # get vector to fp center (instead of pad center) + _vec_pad_to_fp = Side(fp_rot_rel_to_source).rot_vector((-dpad.at.x, -dpad.at.y)) + vec_to_fp_center = _add(vec_distance_from_pad_edge, _vec_pad_to_fp) + + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + f"Next to pad: {fp.name}|{fp.propertys['Reference'].value}.{spad.name}" + f" with {dfp.name}|{dfp.propertys['Reference'].value}.{dpad.name}:" + f"\n {fp.at=}" + f"\n {spad.at=} | {spad.size=}" + f"\n {dpad.at=} | {dpad.size=}" + f"\n {side=}" + f"\n {dside=}" + f"\n {fp_rot_rel_to_source=}" + f"\n {vec_distance_directed=}" + f"\n {vec_distance_from_pad_center=}" + f"\n {_spad_edge_vec=} | {_dpad_edge_vec=}" + f"\n {vec_distance_from_pad_edge=}" + f"\n {vec_to_fp_center=}" + ) + + # in fp system clockwise, in pcb counter clockwise + rot_pcb_coord_system = (-fp_rot_rel_to_source) % 360 + + return (*vec_to_fp_center, rot_pcb_coord_system) + + +def place_next_to_pad( + module: Module, + pad: F.Pad, + params: Params, +): + kfp, kpad = pad.get_trait(PCB_Transformer.has_linked_kicad_pad).get_pad() + if len(kpad) != 1: + raise NotImplementedError() + kpad = kpad[0] + + nfp = module.get_trait(F.has_footprint).get_footprint() + npad = find( + nfp.get_children(direct_only=True, types=F.Pad), + lambda p: p.net.is_connected_to(pad.net) is not None, + ) + nkfp, nkpad = npad.get_trait(PCB_Transformer.has_linked_kicad_pad).get_pad() + if len(nkpad) != 1: + raise NotImplementedError() + nkpad = nkpad[0] + + pos = _next_to_pad(kfp, kpad, nkfp, nkpad, params) + + module.add_trait( + F.has_pcb_position_defined_relative_to_parent( + ( + *pos, + F.has_pcb_position.layer_type.NONE, + ) + ) + ) + + +def place_next_to( + parent_intf: F.Electrical, + child: Module, + params: Params, + route: bool = False, +): + pads_intf = parent_intf.get_trait(F.has_linked_pad).get_pads() + if len(pads_intf) == 0: + raise KeyErrorNotFound() + if len(pads_intf) > 1: + # raise KeyErrorAmbiguous(list(pads_intf)) + pads_intf = sorted(pads_intf, key=lambda x: x.get_name()) + + parent_pad = next(iter(pads_intf)) + + intf = find( + child.get_children(direct_only=True, types=F.Electrical), + lambda x: x.is_connected_to(parent_intf) is not None, + ) + + logger.debug(f"Placing {intf} next to {parent_pad}") + place_next_to_pad(child, parent_pad, params) + + if route: + intf.add_trait( + F.has_pcb_routing_strategy_greedy_direct_line(extra_pads=[parent_pad]) + ) + + +class LayoutHeuristicElectricalClosenessDecouplingCaps(Layout): + Parameters = Params + + def __init__(self, params: Params | None = None): + super().__init__() + self._params = params or Params() + + def apply(self, *node: Node): + from faebryk.core.util import get_parent_of_type + + # Remove nodes that have a position defined + node = tuple( + n + for n in node + if not n.has_trait(F.has_pcb_position) and n.has_trait(F.has_footprint) + ) + + for n in node: + assert isinstance(n, F.Capacitor) + power = NotNone(get_parent_of_type(n, F.ElectricPower)) + + place_next_to(power.hv, n, route=True, params=self._params) + + @staticmethod + def find_module_candidates(node: Node): + from faebryk.core.util import get_parent_of_type + + return node.get_children( + direct_only=False, + types=F.Capacitor, + f_filter=lambda c: get_parent_of_type(c, F.ElectricPower) is not None, + ) + + @classmethod + def add_to_all_suitable_modules(cls, node: Node, params: Params | None = None): + layout = cls(params) + for c in cls.find_module_candidates(node): + c.add_trait(F.has_pcb_layout_defined(layout)) diff --git a/src/faebryk/exporters/pcb/layout/heuristic_pulls.py b/src/faebryk/exporters/pcb/layout/heuristic_pulls.py new file mode 100644 index 00000000..17df93ac --- /dev/null +++ b/src/faebryk/exporters/pcb/layout/heuristic_pulls.py @@ -0,0 +1,54 @@ +# This file is part of the faebryk project +# SPDX-License-Identifier: MIT + +import logging + +import faebryk.library._F as F +from faebryk.core.node import Node +from faebryk.exporters.pcb.layout.heuristic_decoupling import Params, place_next_to +from faebryk.exporters.pcb.layout.layout import Layout +from faebryk.libs.util import NotNone + +logger = logging.getLogger(__name__) + + +class LayoutHeuristicElectricalClosenessPullResistors(Layout): + Parameters = Params + + def __init__(self, params: Params | None = None): + super().__init__() + self._params = params or Params() + + def apply(self, *node: Node): + from faebryk.core.util import get_parent_of_type + from faebryk.library.ElectricLogic import ElectricLogic + from faebryk.library.Resistor import Resistor + + # Remove nodes that have a position defined + node = tuple( + n + for n in node + if not n.has_trait(F.has_pcb_position) and n.has_trait(F.has_footprint) + ) + + for n in node: + assert isinstance(n, Resistor) + logic = NotNone(get_parent_of_type(n, ElectricLogic)) + + place_next_to(logic.signal, n, route=True, params=self._params) + + @staticmethod + def find_module_candidates(node: Node): + from faebryk.core.util import get_parent_of_type + + return node.get_children( + direct_only=False, + types=F.Resistor, + f_filter=lambda c: get_parent_of_type(c, F.ElectricLogic) is not None, + ) + + @classmethod + def add_to_all_suitable_modules(cls, node: Node, params: Params | None = None): + layout = cls(params) + for c in cls.find_module_candidates(node): + c.add_trait(F.has_pcb_layout_defined(layout)) diff --git a/src/faebryk/exporters/pcb/routing/routing.py b/src/faebryk/exporters/pcb/routing/routing.py index 92cbb30c..a91dee86 100644 --- a/src/faebryk/exporters/pcb/routing/routing.py +++ b/src/faebryk/exporters/pcb/routing/routing.py @@ -220,7 +220,7 @@ def route_net(self, net: F.Net): if i > 0 and switch_point not in nodes: transformer.insert_via( coord=out_to_pcb(switch_point), - net=pcb_net.name, + net=pcb_net.number, size_drill=(0.45, 0.25), ) diff --git a/src/faebryk/exporters/pcb/routing/util.py b/src/faebryk/exporters/pcb/routing/util.py index 5bef9acb..fe9b6299 100644 --- a/src/faebryk/exporters/pcb/routing/util.py +++ b/src/faebryk/exporters/pcb/routing/util.py @@ -3,7 +3,7 @@ import logging from dataclasses import dataclass -from typing import TYPE_CHECKING, Iterable, Sequence +from typing import TYPE_CHECKING, Iterable, Mapping, Sequence import faebryk.library._F as F from faebryk.core.module import Module @@ -149,7 +149,7 @@ def apply_route_in_pcb(route: Route, transformer: "PCB_Transformer"): coord = round(obj.pos[0], 2), round(obj.pos[1], 2) transformer.insert_via( - net=pcb_net.name, + net=pcb_net.number, coord=coord, size_drill=obj.size_drill, ) @@ -163,14 +163,14 @@ def apply_route_in_pcb(route: Route, transformer: "PCB_Transformer"): transformer.insert_zone( net=pcb_net, - layer=layer_name, + layers=layer_name, polygon=obj.polygon, ) def get_internal_nets_of_node( node: Node, -) -> dict[F.Net | None, Iterable[ModuleInterface]]: +) -> Mapping[F.Net | None, Iterable[ModuleInterface]]: """ Returns all Nets occuring (at least partially) within Node and returns for each of those the corresponding mifs @@ -193,14 +193,19 @@ def get_internal_nets_of_node( return nets -def get_pads_pos_of_mifs(mifs: Sequence[F.Electrical]): +def get_pads_pos_of_mifs( + mifs: Sequence[F.Electrical], extra_pads: Sequence[F.Pad] | None = None +): from faebryk.exporters.pcb.kicad.transformer import PCB_Transformer - return { - pad_pos[0]: pad_pos[1] - for mif in mifs - for pad_pos in PCB_Transformer.get_pad_pos_any(mif) - } + pad_poss = [ + pad_pos for mif in mifs for pad_pos in PCB_Transformer.get_pad_pos_any(mif) + ] + + if extra_pads: + pad_poss.extend(PCB_Transformer.get_fpad_pos(fpad) for fpad in extra_pads) + + return {pad_pos[0]: pad_pos[1] for pad_pos in pad_poss} def group_pads_that_are_connected_already( diff --git a/src/faebryk/library/ESP32_C3_MINI_1.py b/src/faebryk/library/ESP32_C3_MINI_1.py index 07ab53aa..0f465029 100644 --- a/src/faebryk/library/ESP32_C3_MINI_1.py +++ b/src/faebryk/library/ESP32_C3_MINI_1.py @@ -6,6 +6,8 @@ import faebryk.library._F as F from faebryk.core.module import Module from faebryk.libs.library import L +from faebryk.libs.picker.picker import DescriptiveProperties +from faebryk.libs.units import P logger = logging.getLogger(__name__) @@ -34,7 +36,13 @@ def single_electric_reference(self): def __preinit__(self): # connect power decoupling caps - self.vdd3v3.decoupled.decouple() + self.vdd3v3.decoupled.decouple().capacitance.merge( + F.Range(100 * P.nF, 10 * P.uF) + ) + + e = self.esp32_c3 + for v33 in (e.vdd3p3, e.vdd3p3_cpu, e.vdd3p3_rtc, e.vdda): + self.vdd3v3.connect(v33) for lhs, rhs in zip(self.gpio, self.esp32_c3.gpio): lhs.connect(rhs) @@ -42,6 +50,17 @@ def __preinit__(self): # TODO: set the following in the pinmux # UART0 gpio 20/21 + self.chip_enable.connect(e.enable) + self.chip_enable.pulled.pull(up=True) + + F.has_descriptive_properties_defined.add_properties_to( + self, + { + DescriptiveProperties.manufacturer: "Espressif Systems", + DescriptiveProperties.partno: "ESP32-C3-MINI-1U-H4", + }, + ) + @L.rt_field def attach_to_footprint(self): gnd = self.vdd3v3.lv diff --git a/src/faebryk/library/M24C08_FMN6TP.py b/src/faebryk/library/M24C08_FMN6TP.py index c331f51d..6be5c458 100644 --- a/src/faebryk/library/M24C08_FMN6TP.py +++ b/src/faebryk/library/M24C08_FMN6TP.py @@ -6,6 +6,7 @@ import faebryk.library._F as F from faebryk.core.module import Module from faebryk.libs.library import L +from faebryk.libs.picker.picker import DescriptiveProperties from faebryk.libs.units import P logger = logging.getLogger(__name__) @@ -40,7 +41,17 @@ def __preinit__(self): ) self.data.terminate() - self.power.decoupled.decouple() + self.power.decoupled.decouple().capacitance.merge( + F.Range(10 * P.nF, 100 * P.nF) + ) + + F.has_descriptive_properties_defined.add_properties_to( + self, + { + DescriptiveProperties.manufacturer: "STMicroelectronics", + DescriptiveProperties.partno: "M24C08-FMN6TP", + }, + ) @L.rt_field def single_electric_reference(self): @@ -55,3 +66,7 @@ def set_address(self, addr: int): for i, e in enumerate(self.e): e.set(addr & (1 << i) != 0) + + datasheet = L.f_field(F.has_datasheet_defined)( + "https://eu.mouser.com/datasheet/2/389/m24c08_r-1849629.pdf" + ) diff --git a/src/faebryk/library/Pad.py b/src/faebryk/library/Pad.py index e7ac5703..491ca1c6 100644 --- a/src/faebryk/library/Pad.py +++ b/src/faebryk/library/Pad.py @@ -29,8 +29,8 @@ def find_pad_for_intf_with_parent_that_has_footprint( ) -> list["Pad"]: # This only finds directly attached pads # -> misses from parents / children nodes - if intf.has_trait(F.has_linked_pad): - return [intf.get_trait(F.has_linked_pad).get_pad()] + # if intf.has_trait(F.has_linked_pad): + # return list(intf.get_trait(F.has_linked_pad).get_pads()) # This is a bit slower, but finds them all _, footprint = F.Footprint.get_footprint_of_parent(intf) diff --git a/src/faebryk/library/_F.py b/src/faebryk/library/_F.py index e2727a32..a7879b3a 100644 --- a/src/faebryk/library/_F.py +++ b/src/faebryk/library/_F.py @@ -67,7 +67,6 @@ from faebryk.library.has_footprint_requirement_defined import has_footprint_requirement_defined from faebryk.library.has_multi_picker import has_multi_picker from faebryk.library.has_pcb_layout_defined import has_pcb_layout_defined -from faebryk.library.has_pcb_routing_strategy_greedy_direct_line import has_pcb_routing_strategy_greedy_direct_line from faebryk.library.has_single_connection_impl import has_single_connection_impl from faebryk.library.is_representable_by_single_value_defined import is_representable_by_single_value_defined from faebryk.library.DifferentialPair import DifferentialPair @@ -96,6 +95,7 @@ from faebryk.library.can_attach_via_pinmap_pinlist import can_attach_via_pinmap_pinlist from faebryk.library.has_equal_pins import has_equal_pins from faebryk.library.has_kicad_manual_footprint import has_kicad_manual_footprint +from faebryk.library.has_pcb_routing_strategy_greedy_direct_line import has_pcb_routing_strategy_greedy_direct_line from faebryk.library.BJT import BJT from faebryk.library.Diode import Diode from faebryk.library.MOSFET import MOSFET diff --git a/src/faebryk/library/has_linked_pad.py b/src/faebryk/library/has_linked_pad.py index 9164f19e..f8dbe627 100644 --- a/src/faebryk/library/has_linked_pad.py +++ b/src/faebryk/library/has_linked_pad.py @@ -12,4 +12,4 @@ class has_linked_pad(ModuleInterface.TraitT): @abstractmethod - def get_pad(self) -> "Pad": ... + def get_pads(self) -> set["Pad"]: ... diff --git a/src/faebryk/library/has_linked_pad_defined.py b/src/faebryk/library/has_linked_pad_defined.py index 68db2681..e109bd66 100644 --- a/src/faebryk/library/has_linked_pad_defined.py +++ b/src/faebryk/library/has_linked_pad_defined.py @@ -5,6 +5,8 @@ from typing import TYPE_CHECKING import faebryk.library._F as F +from faebryk.core.node import Node +from faebryk.core.trait import TraitImpl if TYPE_CHECKING: from faebryk.library.Pad import Pad @@ -13,7 +15,14 @@ class has_linked_pad_defined(F.has_linked_pad.impl()): def __init__(self, pad: "Pad") -> None: super().__init__() - self.pad = pad + self.pads = {pad} - def get_pad(self) -> "Pad": - return self.pad + def get_pads(self) -> set["Pad"]: + return self.pads + + def handle_duplicate(self, other: TraitImpl, node: Node) -> bool: + if not isinstance(other, has_linked_pad_defined): + return super().handle_duplicate(other, node) + + other.pads.update(self.pads) + return False diff --git a/src/faebryk/library/has_pcb_routing_strategy_greedy_direct_line.py b/src/faebryk/library/has_pcb_routing_strategy_greedy_direct_line.py index 031ff73b..970be68c 100644 --- a/src/faebryk/library/has_pcb_routing_strategy_greedy_direct_line.py +++ b/src/faebryk/library/has_pcb_routing_strategy_greedy_direct_line.py @@ -3,9 +3,10 @@ import logging from enum import Enum, auto -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Sequence import faebryk.library._F as F +from faebryk.core.moduleinterface import ModuleInterface from faebryk.libs.geometry.basic import Geometry if TYPE_CHECKING: @@ -20,9 +21,16 @@ class Topology(Enum): DIRECT = auto() # CHAIN = auto() - def __init__(self, topology: Topology = Topology.DIRECT, priority: float = 0.0): + def __init__( + self, + topology: Topology = Topology.DIRECT, + extra_mifs: Sequence[ModuleInterface] | None = None, + extra_pads: Sequence[F.Pad] | None = None, + ) -> None: super().__init__() self.topology = topology + self.extra_mifs = extra_mifs or [] + self.extra_pads = extra_pads or [] def calculate(self, transformer: "PCB_Transformer"): from faebryk.exporters.pcb.routing.util import ( @@ -42,7 +50,9 @@ def calculate(self, transformer: "PCB_Transformer"): # might make this very complex though def get_route_for_mifs_in_net(mifs) -> Route | None: - pads = get_pads_pos_of_mifs(mifs) + pads = get_pads_pos_of_mifs(mifs + self.extra_mifs, self.extra_pads) + + layers = {pos[3] for pos in pads.values()} layers = {pos[3] for pos in pads.values()} if len(layers) > 1: diff --git a/src/faebryk/libs/util.py b/src/faebryk/libs/util.py index c82cd782..26b3d7a8 100644 --- a/src/faebryk/libs/util.py +++ b/src/faebryk/libs/util.py @@ -101,8 +101,8 @@ def get_key[T, U](haystack: dict[T, U], needle: U) -> T: class KeyErrorNotFound(KeyError): ... -class KeyErrorAmbiguous(KeyError): - def __init__(self, duplicates: list, *args: object) -> None: +class KeyErrorAmbiguous[T](KeyError): + def __init__(self, duplicates: list[T], *args: object) -> None: super().__init__(*args) self.duplicates = duplicates