Skip to content
This repository has been archived by the owner on Sep 3, 2024. It is now read-only.

Commit

Permalink
Layout: Add heuristic for decoupling caps & pull resistors (#238)
Browse files Browse the repository at this point in the history
Places pull resistors and decoupling caps next to the pads they are
acting upon and routes them (optionally).

- Graphinterfaces isconnect for self
- Pad: support multiple intfs per pad
- Routing: add extra pads option

---------

Co-authored-by: iopapamanoglou <ioannis@atopile.io>
  • Loading branch information
IoannisP-ITENG and iopapamanoglou authored Sep 3, 2024
1 parent 372bcc0 commit 80e2af7
Show file tree
Hide file tree
Showing 16 changed files with 441 additions and 42 deletions.
22 changes: 22 additions & 0 deletions examples/pcb_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,25 @@
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__)


class App(Module):
leds: F.PoweredLED
battery: F.Battery
eeprom: F.M24C08_FMN6TP

def __preinit__(self) -> None:
self.leds.power.connect(self.battery.power)
Expand All @@ -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
Expand All @@ -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 -----------------------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion src/faebryk/core/graphinterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 7 additions & 2 deletions src/faebryk/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
35 changes: 21 additions & 14 deletions src/faebryk/exporters/pcb/kicad/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
C_arc,
C_circle,
C_effects,
C_footprint,
C_fp_text,
C_kicad_pcb_file,
C_line,
C_polygon,
C_rect,
C_stroke,
C_text,
C_text_layer,
C_wh,
C_xy,
C_xyr,
Expand Down Expand Up @@ -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:
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -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=(
Expand Down Expand Up @@ -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

Expand All @@ -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.", "<F>.").replace("B.", "F.").replace("<F>.", "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]):
Expand All @@ -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"),
)
)

Expand Down
Loading

0 comments on commit 80e2af7

Please sign in to comment.