From 381505a1e2f8c50b577b0264cf0b7288c6e87012 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Thu, 12 Mar 2026 00:38:27 -0700 Subject: [PATCH 01/29] wip analog compositional passive --- edg/abstract_parts/IoController.py | 4 +-- edg/electronics_model/AnalogPort.py | 27 ++++++++++----- edg/electronics_model/CircuitBlock.py | 12 ++++++- edg/electronics_model/NetlistGenerator.py | 2 +- edg/electronics_model/PassivePort.py | 40 ++++++++++++++--------- edg/parts/BoostConverter_DiodesInc.py | 2 +- edg/parts/SpeakerDriver_Max98357a.py | 6 ++-- 7 files changed, 60 insertions(+), 33 deletions(-) diff --git a/edg/abstract_parts/IoController.py b/edg/abstract_parts/IoController.py index aecaba0e6..1ce689d3a 100644 --- a/edg/abstract_parts/IoController.py +++ b/edg/abstract_parts/IoController.py @@ -144,15 +144,13 @@ def _instantiate_from( raise NotImplementedError(f"unknown port type {io_port}") if isinstance(allocation.pin, str): - assert isinstance(io_port, CircuitPort) pinmap[allocation.pin] = io_port elif allocation.pin is None: - assert isinstance(io_port, CircuitPort) # otherwise discarded + pass # discarded elif isinstance(allocation.pin, dict): assert isinstance(io_port, Bundle) for subport_name, (pin_name, pin_resource) in allocation.pin.items(): subport = getattr(io_port, subport_name) - assert isinstance(subport, CircuitPort), f"bad sub-port {pin_name} {subport}" pinmap[pin_name] = subport else: raise NotImplementedError(f"unknown allocation pin type {allocation.pin}") diff --git a/edg/electronics_model/AnalogPort.py b/edg/electronics_model/AnalogPort.py index eaef510b5..ad127c114 100644 --- a/edg/electronics_model/AnalogPort.py +++ b/edg/electronics_model/AnalogPort.py @@ -5,12 +5,13 @@ from typing_extensions import override from ..core import * -from .CircuitBlock import CircuitLink, CircuitPortAdapter +from .PassivePort import Passive +from .CircuitBlock import CircuitPortAdapter from .GroundPort import GroundLink -from .VoltagePorts import CircuitPort, CircuitPortBridge, VoltageLink, VoltageSource +from .VoltagePorts import VoltageLink, VoltageSource -class AnalogLink(CircuitLink): +class AnalogLink(Link): """Analog signal, a signal that carries information by varying voltage""" def __init__(self) -> None: @@ -34,6 +35,8 @@ def __init__(self) -> None: def contents(self) -> None: super().contents() + self.net = self.connect(self.sinks.map_extract(lambda sink: sink.net), self.source.net, flatten=True) + self.description = DescriptionString( "voltage: ", DescriptionString.FormatUnits(self.voltage, "V"), @@ -64,7 +67,7 @@ def contents(self) -> None: self.require(self.current_limits.contains(self.current_drawn), "overcurrent") -class AnalogBase(CircuitPort[AnalogLink]): +class AnalogBase(Port[AnalogLink]): link_type = AnalogLink # these are here (instead of in AnalogSource) since the port may be on the other side of a bridge @@ -72,7 +75,7 @@ def as_voltage_source(self) -> VoltageSource: return self._convert(AnalogSourceAdapterVoltageSource()) -class AnalogSinkBridge(CircuitPortBridge): +class AnalogSinkBridge(PortBridge): def __init__(self) -> None: super().__init__() @@ -94,6 +97,8 @@ def __init__(self) -> None: def contents(self) -> None: super().contents() + self.connect(self.outer_port.net, self.inner_link.net) + self.assign(self.outer_port.impedance, self.inner_link.link().sink_impedance) self.assign(self.outer_port.current_draw, self.inner_link.link().current_drawn) self.assign(self.outer_port.voltage_limits, self.inner_link.link().voltage_limits) @@ -103,7 +108,7 @@ def contents(self) -> None: self.assign(self.inner_link.signal_out, self.outer_port.link().signal) -class AnalogSourceBridge(CircuitPortBridge): # basic passthrough port, sources look the same inside and outside +class AnalogSourceBridge(PortBridge): # basic passthrough port, sources look the same inside and outside def __init__(self) -> None: super().__init__() @@ -130,6 +135,8 @@ def __init__(self) -> None: def contents(self) -> None: super().contents() + self.connect(self.outer_port.net, self.inner_link.net) + self.assign(self.outer_port.voltage_out, self.inner_link.link().voltage) self.assign(self.outer_port.signal_out, self.inner_link.link().signal) self.assign(self.outer_port.impedance, self.inner_link.link().source_impedance) @@ -141,7 +148,7 @@ def contents(self) -> None: self.assign(self.inner_link.impedance, self.outer_port.link().sink_impedance) -class AnalogSink(AnalogBase): +class AnalogSink(AnalogBase, Bundle): bridge_type = AnalogSinkBridge @staticmethod @@ -195,6 +202,8 @@ def __init__( """voltage_limits are the maximum recommended voltage levels of the device (before device damage occurs), signal_limits are for proper device functionality (e.g. non-RRIO opamps)""" super().__init__() + self.net = self.Port(Passive()) + self.voltage_limits = self.Parameter(RangeExpr(voltage_limits)) self.signal_limits = self.Parameter(RangeExpr(signal_limits)) self.current_draw = self.Parameter(RangeExpr(current_draw)) @@ -214,7 +223,7 @@ def __init__(self) -> None: self.assign(self.src.current_draw, self.dst.link().current_drawn) -class AnalogSource(AnalogBase): +class AnalogSource(AnalogBase, Bundle): bridge_type = AnalogSourceBridge @staticmethod @@ -256,6 +265,8 @@ def __init__( """voltage_out is the total voltage range the device can output (typically limited by power rails) regardless of controls and including transients, while signal_out is the intended operating range""" super().__init__() + self.net = self.Port(Passive()) + self.voltage_out = self.Parameter(RangeExpr(voltage_out)) self.signal_out = self.Parameter(RangeExpr(signal_out)) self.current_limits = self.Parameter(RangeExpr(current_limits)) diff --git a/edg/electronics_model/CircuitBlock.py b/edg/electronics_model/CircuitBlock.py index f34c401a4..01720b461 100644 --- a/edg/electronics_model/CircuitBlock.py +++ b/edg/electronics_model/CircuitBlock.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from typing import Generic, Any, Optional, List, Mapping, Dict from typing_extensions import TypeVar, override @@ -68,6 +69,7 @@ def footprint( assembly""" from ..core.Blocks import BlockElaborationState, BlockDefinitionError from .VoltagePorts import CircuitPort + from .PassivePort import Passive if self._elaboration_state not in ( BlockElaborationState.init, @@ -85,7 +87,15 @@ def footprint( pinning_array = [] for pin_name, pin_port in pinning.items(): if not isinstance(pin_port, CircuitPort): - raise EdgTypeError(f"Footprint(...) pin", pin_port, CircuitPort) + if hasattr(pin_port, 'net') and isinstance(pin_port.net, Passive): + warnings.warn( + "Use the internal passive .net port directly", + DeprecationWarning, + stacklevel=2 # Points the warning to the caller's code + ) + pin_port = pin_port.net + else: + raise EdgTypeError(f"Footprint(...) pin", pin_port, CircuitPort) pinning_array.append(f"{pin_name}={pin_port._name_from(self)}") self.assign(self.fp_pinning, pinning_array) diff --git a/edg/electronics_model/NetlistGenerator.py b/edg/electronics_model/NetlistGenerator.py index 6d6375f75..c98daf223 100644 --- a/edg/electronics_model/NetlistGenerator.py +++ b/edg/electronics_model/NetlistGenerator.py @@ -67,7 +67,7 @@ def empty(cls, path: TransformUtil.Path) -> "BoardScope": # returns a fresh, em class NetlistTransform(TransformUtil.Transform): @staticmethod def flatten_port(path: TransformUtil.Path, port: edgir.PortLike) -> Iterable[TransformUtil.Path]: - if port.HasField("port"): + if port.HasField("port") or port.HasField("bundle"): return [path] elif port.HasField("array") and port.array.HasField("ports"): return chain( diff --git a/edg/electronics_model/PassivePort.py b/edg/electronics_model/PassivePort.py index 91850e338..db30be00d 100644 --- a/edg/electronics_model/PassivePort.py +++ b/edg/electronics_model/PassivePort.py @@ -2,13 +2,17 @@ from typing import TypeVar, Type, Dict, Mapping +from typing_extensions import TYPE_CHECKING + from ..core import * from .GroundPort import Ground -from .AnalogPort import AnalogSource, AnalogSink from .CircuitBlock import CircuitLink, CircuitPortBridge, CircuitPortAdapter from .DigitalPorts import DigitalSource, DigitalSink, DigitalBidir from .VoltagePorts import CircuitPort, VoltageSource, VoltageSink +if TYPE_CHECKING: + from .AnalogPort import AnalogSource, AnalogSink + class PassiveLink(CircuitLink): """Copper-only connection""" @@ -127,7 +131,7 @@ def __init__( ) -class PassiveAdapterAnalogSource(CircuitPortAdapter[AnalogSource]): +class PassiveAdapterAnalogSource(CircuitPortAdapter["AnalogSource"]): # TODO we can't use **kwargs b/c the init hook needs an initializer list def __init__( self, @@ -136,6 +140,7 @@ def __init__( current_limits: RangeLike = RangeExpr.ALL, impedance: RangeLike = RangeExpr.ZERO, ): + from .AnalogPort import AnalogSource, AnalogSink super().__init__() self.src = self.Port(Passive()) self.dst = self.Port( @@ -145,7 +150,7 @@ def __init__( ) -class PassiveAdapterAnalogSink(CircuitPortAdapter[AnalogSink]): +class PassiveAdapterAnalogSink(CircuitPortAdapter["AnalogSink"]): # TODO we can't use **kwargs b/c the init hook needs an initializer list def __init__( self, @@ -154,6 +159,7 @@ def __init__( current_draw: RangeLike = RangeExpr.ZERO, impedance: RangeLike = RangeExpr.INF, ): + from .AnalogPort import AnalogSource, AnalogSink super().__init__() self.src = self.Port(Passive()) self.dst = self.Port( @@ -175,30 +181,32 @@ def __init__(self) -> None: class Passive(CircuitPort[PassiveLink]): """Basic copper-only port, which can be adapted to a more strongly typed Voltage/Digital/Analog* port""" - - adapter_type_map: Dict[Type[Port], Type[CircuitPortAdapter]] = { - Ground: PassiveAdapterGround, - VoltageSource: PassiveAdapterVoltageSource, - VoltageSink: PassiveAdapterVoltageSink, - DigitalSink: PassiveAdapterDigitalSink, - DigitalSource: PassiveAdapterDigitalSource, - DigitalBidir: PassiveAdapterDigitalBidir, - AnalogSink: PassiveAdapterAnalogSink, - AnalogSource: PassiveAdapterAnalogSource, - } link_type = PassiveLink bridge_type = PassiveBridge AdaptTargetType = TypeVar("AdaptTargetType", bound=CircuitPort) def adapt_to(self, that: AdaptTargetType) -> AdaptTargetType: + from .AnalogPort import AnalogSource, AnalogSink + + ADAPTER_TYPE_MAP: Dict[Type[Port], Type[CircuitPortAdapter]] = { + Ground: PassiveAdapterGround, + VoltageSource: PassiveAdapterVoltageSource, + VoltageSink: PassiveAdapterVoltageSink, + DigitalSink: PassiveAdapterDigitalSink, + DigitalSource: PassiveAdapterDigitalSource, + DigitalBidir: PassiveAdapterDigitalBidir, + AnalogSink: PassiveAdapterAnalogSink, + AnalogSource: PassiveAdapterAnalogSource, + } + # this is an experimental style that takes a port that has initializers but is not bound # and automatically creates an adapter from it, by matching the port parameter fields # with the adapter constructor argument fields by name assert isinstance(that, Port), "adapter target must be port" assert not that._is_bound(), "adapter target must be model only" - assert that.__class__ in self.adapter_type_map, f"no adapter to {that.__class__}" - adapter_cls = self.adapter_type_map[that.__class__] + assert that.__class__ in ADAPTER_TYPE_MAP, f"no adapter to {that.__class__}" + adapter_cls = ADAPTER_TYPE_MAP[that.__class__] # map initializers from that to constructor args adapter_init_kwargs = {} # make everything kwargs for simplicity diff --git a/edg/parts/BoostConverter_DiodesInc.py b/edg/parts/BoostConverter_DiodesInc.py index 0057e77bb..827fe1fac 100644 --- a/edg/parts/BoostConverter_DiodesInc.py +++ b/edg/parts/BoostConverter_DiodesInc.py @@ -31,7 +31,7 @@ def contents(self) -> None: { "1": self.sw, "2": self.gnd, - "3": self.fb, + "3": self.fb.net, "4": self.nshdn, "5": self.pwr_in, }, diff --git a/edg/parts/SpeakerDriver_Max98357a.py b/edg/parts/SpeakerDriver_Max98357a.py index 1990323ca..3e6cf4576 100644 --- a/edg/parts/SpeakerDriver_Max98357a.py +++ b/edg/parts/SpeakerDriver_Max98357a.py @@ -42,7 +42,7 @@ def generate(self) -> None: "4": self.vdd, # hard tied to left mode only TODO selectable SD_MODE "7": self.vdd, "8": self.vdd, - "9": self.out.a, # outp + "9": self.out.a.net, # outp "1": self.i2s.sd, # '2': gain_slot, # TODO: configurable gain, open = 9dB "10": self.out.b, # outn @@ -63,10 +63,10 @@ def generate(self) -> None: pinning = { "A1": self.vdd, # hard tied to left mode only TODO selectable SD_MODE "A2": self.vdd, - "A3": self.out.a, # outp + "A3": self.out.a.net, # outp "B1": self.i2s.sd, # 'B2': gain_slot, # TODO: configurable gain, open = 9dB - "B3": self.out.b, # outn + "B3": self.out.b.net, # outn "C1": self.i2s.sck, "C2": self.gnd, "C3": self.i2s.ws, From 7330b19cd608436a2595c6ca9a48579f68435d1f Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Fri, 13 Mar 2026 00:17:27 -0700 Subject: [PATCH 02/29] fix netlist differences --- edg/electronics_model/AnalogPort.py | 4 +++- edg/electronics_model/PassivePort.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/edg/electronics_model/AnalogPort.py b/edg/electronics_model/AnalogPort.py index ad127c114..1678f6f1f 100644 --- a/edg/electronics_model/AnalogPort.py +++ b/edg/electronics_model/AnalogPort.py @@ -35,7 +35,7 @@ def __init__(self) -> None: def contents(self) -> None: super().contents() - self.net = self.connect(self.sinks.map_extract(lambda sink: sink.net), self.source.net, flatten=True) + self.net = self.connect(self.source.net, self.sinks.map_extract(lambda sink: sink.net), flatten=True) self.description = DescriptionString( "voltage: ", @@ -222,6 +222,8 @@ def __init__(self) -> None: ) self.assign(self.src.current_draw, self.dst.link().current_drawn) + raise NotImplementedError # TODO IMPLEMENT ME + class AnalogSource(AnalogBase, Bundle): bridge_type = AnalogSourceBridge diff --git a/edg/electronics_model/PassivePort.py b/edg/electronics_model/PassivePort.py index db30be00d..6547e3f73 100644 --- a/edg/electronics_model/PassivePort.py +++ b/edg/electronics_model/PassivePort.py @@ -148,6 +148,7 @@ def __init__( voltage_out=voltage_out, signal_out=signal_out, current_limits=current_limits, impedance=impedance ) ) + self.connect(self.src, self.dst.net) class PassiveAdapterAnalogSink(CircuitPortAdapter["AnalogSink"]): @@ -170,6 +171,7 @@ def __init__( impedance=impedance, ) ) + self.connect(self.src, self.dst.net) class PassiveBridge(CircuitPortBridge): From 67fddf387a7b221f4eff3a3c6c4e005e8b7fe650 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 15 Mar 2026 12:22:41 -0700 Subject: [PATCH 03/29] wip --- edg/electronics_model/NetlistGenerator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/edg/electronics_model/NetlistGenerator.py b/edg/electronics_model/NetlistGenerator.py index c5a536c73..d00a1b1ce 100644 --- a/edg/electronics_model/NetlistGenerator.py +++ b/edg/electronics_model/NetlistGenerator.py @@ -286,6 +286,10 @@ def pin_name_goodness(pin1: TransformUtil.Path, pin2: TransformUtil.Path) -> int best_path = sorted(net, key=cmp_to_key(pin_name_goodness))[0] + # prune out the net interior link, if it exists + if best_path.links and len(best_path.links) > 1 and best_path.links[-1] == "net": + best_path = best_path._replace(links=best_path.links[:-1]) + return net_prefix + str(best_path) def scope_to_netlist(self, scope: BoardScope) -> Netlist: From 3fa0530649c083e5de6579f63fbacb1813d7bb6d Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 15 Mar 2026 12:25:36 -0700 Subject: [PATCH 04/29] wip --- edg/electronics_model/AnalogPort.py | 2 +- edg/electronics_model/NetlistGenerator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/edg/electronics_model/AnalogPort.py b/edg/electronics_model/AnalogPort.py index 1678f6f1f..a99857c7c 100644 --- a/edg/electronics_model/AnalogPort.py +++ b/edg/electronics_model/AnalogPort.py @@ -148,7 +148,7 @@ def contents(self) -> None: self.assign(self.inner_link.impedance, self.outer_port.link().sink_impedance) -class AnalogSink(AnalogBase, Bundle): +class AnalogSink(AnalogBase): bridge_type = AnalogSinkBridge @staticmethod diff --git a/edg/electronics_model/NetlistGenerator.py b/edg/electronics_model/NetlistGenerator.py index 010ef7260..96be47ae7 100644 --- a/edg/electronics_model/NetlistGenerator.py +++ b/edg/electronics_model/NetlistGenerator.py @@ -66,7 +66,7 @@ def empty(cls, path: TransformUtil.Path) -> "BoardScope": # returns a fresh, em class NetlistTransform(TransformUtil.Transform): @staticmethod def flatten_port(path: TransformUtil.Path, port: edgir.PortLike) -> Iterable[TransformUtil.Path]: - if port.HasField("port") or port.HasField("bundle"): + if port.HasField("port"): return [path] elif port.HasField("array") and port.array.HasField("ports"): return chain( From 86c8d0838998387b4dea2a1826b5534de4700770 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 15 Mar 2026 12:36:12 -0700 Subject: [PATCH 05/29] cleaning --- edg/electronics_model/AnalogPort.py | 2 +- examples/RobotOwl/RobotOwl.net | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/edg/electronics_model/AnalogPort.py b/edg/electronics_model/AnalogPort.py index a99857c7c..b2e704703 100644 --- a/edg/electronics_model/AnalogPort.py +++ b/edg/electronics_model/AnalogPort.py @@ -225,7 +225,7 @@ def __init__(self) -> None: raise NotImplementedError # TODO IMPLEMENT ME -class AnalogSource(AnalogBase, Bundle): +class AnalogSource(AnalogBase): bridge_type = AnalogSourceBridge @staticmethod diff --git a/examples/RobotOwl/RobotOwl.net b/examples/RobotOwl/RobotOwl.net index 589016563..8d07eed74 100644 --- a/examples/RobotOwl/RobotOwl.net +++ b/examples/RobotOwl/RobotOwl.net @@ -680,9 +680,9 @@ (node (ref U3) (pin 1)) (node (ref U1) (pin 19))) (net (code 7) (name "photodiode.out") - (node (ref U1) (pin 38)) (node (ref R3) (pin 2)) - (node (ref D2) (pin 1))) + (node (ref D2) (pin 1)) + (node (ref U1) (pin 38))) (net (code 8) (name "oled22.i2c.scl") (node (ref U1) (pin 4)) (node (ref J1) (pin 13))) @@ -703,10 +703,10 @@ (net (code 13) (name "spk_drv.i2s.sd") (node (ref U1) (pin 36)) (node (ref U6) (pin B1))) -(net (code 14) (name "spk_drv.out.a") +(net (code 14) (name "spk.input.a") (node (ref U6) (pin A3)) (node (ref J2) (pin 1))) -(net (code 15) (name "spk_drv.out.b") +(net (code 15) (name "spk.input.b") (node (ref U6) (pin B3)) (node (ref J2) (pin 2))) (net (code 16) (name "servo[0].pwm") @@ -722,9 +722,9 @@ (node (ref D14) (pin 2)) (node (ref J5) (pin 2))) (net (code 20) (name "reg_12v.fb.output") - (node (ref U2) (pin 3)) (node (ref R1) (pin 2)) - (node (ref R2) (pin 1))) + (node (ref R2) (pin 1)) + (node (ref U2) (pin 3))) (net (code 21) (name "reg_12v.power_path.switch") (node (ref U2) (pin 1)) (node (ref L1) (pin 2)) From 0b100aa69ed333823c49e2fe480f7297547138f6 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 15 Mar 2026 12:41:20 -0700 Subject: [PATCH 06/29] preserve original behavior --- edg/electronics_model/NetlistGenerator.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/edg/electronics_model/NetlistGenerator.py b/edg/electronics_model/NetlistGenerator.py index 96be47ae7..ced23fbd1 100644 --- a/edg/electronics_model/NetlistGenerator.py +++ b/edg/electronics_model/NetlistGenerator.py @@ -265,6 +265,14 @@ def name_net(net: Iterable[TransformUtil.Path], net_prefix: str) -> str: lambda pin: not (pin.ports and pin.ports[-1].isnumeric()), # disprefer number-only ports ] + def prune_net_component(path: TransformUtil.Path) -> TransformUtil.Path: + # prune out the net interior link, if it exists + if path.links and len(path.links) > 1 and path.links[-1] == "net": + return path._replace(links=path.links[:-1]) + else: + return path + net = map(prune_net_component, net) + def pin_name_goodness(pin1: TransformUtil.Path, pin2: TransformUtil.Path) -> int: assert not pin1.params and not pin2.params for test in CRITERIA: @@ -284,11 +292,6 @@ def pin_name_goodness(pin1: TransformUtil.Path, pin2: TransformUtil.Path) -> int return 0 best_path = sorted(net, key=cmp_to_key(pin_name_goodness))[0] - - # prune out the net interior link, if it exists - if best_path.links and len(best_path.links) > 1 and best_path.links[-1] == "net": - best_path = best_path._replace(links=best_path.links[:-1]) - return net_prefix + str(best_path) def scope_to_netlist(self, scope: BoardScope) -> Netlist: From 6ada796c02d8349c39a929a67fb9f6b8801e38fe Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 15 Mar 2026 22:02:16 -0700 Subject: [PATCH 07/29] wip? --- edg/electronics_model/NetlistGenerator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/edg/electronics_model/NetlistGenerator.py b/edg/electronics_model/NetlistGenerator.py index ba607863f..c4e42f216 100644 --- a/edg/electronics_model/NetlistGenerator.py +++ b/edg/electronics_model/NetlistGenerator.py @@ -261,7 +261,7 @@ def visit_portlike(self, context: TransformUtil.TransformContext, port: edgir.Po self.path_traverse_order.append(context.path) @staticmethod - def name_net(net: Iterable[TransformUtil.Path]) -> TransformUtil.Path: + def name_net(net: Iterable[TransformUtil.Path], path_ordering: Dict[TransformUtil.Path, int]) -> TransformUtil.Path: """Names a net based on all the paths of ports and links that are part of the net.""" # higher criteria are preferred, True or larger number is preferred CRITERIA: List[Callable[[TransformUtil.Path], Union[bool, int]]] = [ @@ -271,6 +271,7 @@ def name_net(net: Iterable[TransformUtil.Path]) -> TransformUtil.Path: lambda pin: len(pin.links), # prefer longer link paths lambda pin: -len(pin.ports), # prefer shorter (or no) port lengths lambda pin: not (pin.ports and pin.ports[-1].isnumeric()), # disprefer number-only ports + lambda pin: -path_ordering.get(pin.port_component(must_have_port=False), len(path_ordering)), # prefer earlier paths ] def prune_net_component(path: TransformUtil.Path) -> TransformUtil.Path: @@ -331,7 +332,7 @@ def scope_to_netlist(self, scope: BoardScope) -> Netlist: raise InvalidPackingException(f"packed pins {connected1}, {connected2} not connected") named_nets = sorted( - [(self.name_net(net), net) for net in nets], + [(self.name_net(net, path_ordering), net) for net in nets], key=lambda pair: path_ordering[pair[0].port_component(must_have_port=False)], ) From 0d7be096adcf5e01a63fc850c6dbdbddc8dbe7f2 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 15 Mar 2026 22:08:58 -0700 Subject: [PATCH 08/29] baseline --- examples/BleJoystick/BleJoystick.net | 108 +++++++++++++-------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/examples/BleJoystick/BleJoystick.net b/examples/BleJoystick/BleJoystick.net index 67392bc1c..efc7be9ed 100644 --- a/examples/BleJoystick/BleJoystick.net +++ b/examples/BleJoystick/BleJoystick.net @@ -656,111 +656,111 @@ (net (code 6) (name "Jusb.usb.dm") (node (ref JJ2) (pin A7)) (node (ref JJ2) (pin B7))) -(net (code 7) (name "Jmp2722.pwr_out") +(net (code 7) (name "Jusb.cc.cc1") + (node (ref JJ2) (pin A5)) + (node (ref JU1) (pin 1))) +(net (code 8) (name "Jusb.cc.cc2") + (node (ref JJ2) (pin B5)) + (node (ref JU1) (pin 22))) +(net (code 9) (name "Jmp2722.pwr_out") (node (ref JU1) (pin 13)) (node (ref JL1) (pin 2)) (node (ref JC6) (pin 1)) (node (ref JU2) (pin 2)) (node (ref JC7) (pin 1))) -(net (code 8) (name "Jmp2722.vrntc") +(net (code 10) (name "Jmp2722.vrntc") (node (ref JU1) (pin 7)) (node (ref JR1) (pin 1))) -(net (code 9) (name "Jmp2722.int") +(net (code 11) (name "Jmp2722.ntc1") + (node (ref JU1) (pin 10)) + (node (ref JR1) (pin 2)) + (node (ref JR2) (pin 1))) +(net (code 12) (name "Jmp2722.rst") + (node (ref JU1) (pin 17)) + (node (ref JU3) (pin 5)) + (node (ref JU4) (pin 1))) +(net (code 13) (name "Jmp2722.int") (node (ref JU1) (pin 8))) -(net (code 10) (name "Jmp2722.stat") +(net (code 14) (name "Jmp2722.stat") (node (ref JU1) (pin 11))) -(net (code 11) (name "Jmp2722.pg") +(net (code 15) (name "Jmp2722.pg") (node (ref JU1) (pin 9))) -(net (code 12) (name "Jmp2722.usb.dp") +(net (code 16) (name "Jmp2722.i2c.scl") + (node (ref JU1) (pin 16)) + (node (ref JU3) (pin 4)) + (node (ref JR13) (pin 2))) +(net (code 17) (name "Jmp2722.i2c.sda") + (node (ref JU1) (pin 15)) + (node (ref JU3) (pin 14)) + (node (ref JR14) (pin 2))) +(net (code 18) (name "Jmp2722.usb.dp") (node (ref JU1) (pin 21))) -(net (code 13) (name "Jmp2722.usb.dm") +(net (code 19) (name "Jmp2722.usb.dm") (node (ref JU1) (pin 20))) -(net (code 14) (name "Jmp2722.cc.cc1") - (node (ref JJ2) (pin A5)) - (node (ref JU1) (pin 1))) -(net (code 15) (name "Jmp2722.cc.cc2") - (node (ref JJ2) (pin B5)) - (node (ref JU1) (pin 22))) -(net (code 16) (name "Jmp2722.ic.pmid") +(net (code 20) (name "Jmp2722.ic.sw") + (node (ref JU1) (pin 4)) + (node (ref JC1) (pin 2)) + (node (ref JL1) (pin 1))) +(net (code 21) (name "Jmp2722.ic.pmid") (node (ref JU1) (pin 3)) (node (ref JC2) (pin 1))) -(net (code 17) (name "Jmp2722.ic.vcc") - (node (ref JU1) (pin 19)) - (node (ref JC4) (pin 1))) -(net (code 18) (name "Jmp2722.vbst_cap.pos") +(net (code 22) (name "Jmp2722.ic.bst") (node (ref JU1) (pin 6)) (node (ref JC1) (pin 1))) -(net (code 19) (name "Jmp2722.vbst_cap.neg") - (node (ref JU1) (pin 4)) - (node (ref JC1) (pin 2)) - (node (ref JL1) (pin 1))) -(net (code 20) (name "Jfake_ntc.output") - (node (ref JU1) (pin 10)) - (node (ref JR1) (pin 2)) - (node (ref JR2) (pin 1))) -(net (code 21) (name "Jmcu.program_uart_node.a_tx") +(net (code 23) (name "Jmp2722.ic.vcc") + (node (ref JU1) (pin 19)) + (node (ref JC4) (pin 1))) +(net (code 24) (name "Jmcu.program_uart_node.a_tx") (node (ref JU3) (pin 12)) (node (ref JJ3) (pin 3))) -(net (code 22) (name "Jmcu.program_uart_node.b_tx") +(net (code 25) (name "Jmcu.program_uart_node.b_tx") (node (ref JU3) (pin 11)) (node (ref JJ3) (pin 4))) -(net (code 23) (name "Jmcu.program_en_node") +(net (code 26) (name "Jmcu.program_en_node") (node (ref JU3) (pin 2)) (node (ref JJ3) (pin 6)) (node (ref JR3) (pin 2)) (node (ref JC11) (pin 1))) -(net (code 24) (name "Jmcu.program_boot_node") +(net (code 27) (name "Jmcu.program_boot_node") (node (ref JU3) (pin 8)) (node (ref JJ3) (pin 2)) (node (ref JSW1) (pin 1)) (node (ref JR10) (pin 2))) -(net (code 25) (name "Jstick.sw") - (node (ref JU1) (pin 17)) - (node (ref JU3) (pin 5)) - (node (ref JU4) (pin 1))) -(net (code 26) (name "Jstick.ax1") +(net (code 28) (name "Jstick.ax1") (node (ref JU4) (pin 4)) (node (ref JR4) (pin 1))) -(net (code 27) (name "Jstick.ax2") +(net (code 29) (name "Jstick.ax2") (node (ref JU4) (pin 7)) (node (ref JR6) (pin 1))) -(net (code 28) (name "Jax1_div.output") +(net (code 30) (name "Jax1_div.output") (node (ref JU3) (pin 3)) (node (ref JR4) (pin 2)) (node (ref JR5) (pin 1))) -(net (code 29) (name "Jax2_div.output") +(net (code 31) (name "Jax2_div.output") (node (ref JU3) (pin 15)) (node (ref JR6) (pin 2)) (node (ref JR7) (pin 1))) -(net (code 30) (name "Jtrig.out") +(net (code 32) (name "Jtrig.out") (node (ref JU5) (pin 2)) (node (ref JR8) (pin 1))) -(net (code 31) (name "Jtrig_div.output") +(net (code 33) (name "Jtrig_div.output") (node (ref JU3) (pin 17)) (node (ref JR8) (pin 2)) (node (ref JR9) (pin 1))) -(net (code 32) (name "Jsw[0].out") +(net (code 34) (name "Jsw[0].out") (node (ref JU3) (pin 10)) (node (ref JSW2) (pin 1))) -(net (code 33) (name "Jsw[1].out") +(net (code 35) (name "Jsw[1].out") (node (ref JU3) (pin 13)) (node (ref JSW3) (pin 1))) -(net (code 34) (name "Jsw[2].out") +(net (code 36) (name "Jsw[2].out") (node (ref JU3) (pin 6)) (node (ref JSW4) (pin 1))) -(net (code 35) (name "Jledr.res.a") +(net (code 37) (name "Jledr.package.k") (node (ref JD2) (pin 1)) (node (ref JR10) (pin 1))) -(net (code 36) (name "Jvbat_sense.output") +(net (code 38) (name "Jvbat_sense.output") (node (ref JU3) (pin 18)) (node (ref JR11) (pin 2)) - (node (ref JR12) (pin 1))) -(net (code 37) (name "Ji2c_pull.i2c.scl") - (node (ref JU1) (pin 16)) - (node (ref JU3) (pin 4)) - (node (ref JR13) (pin 2))) -(net (code 38) (name "Ji2c_pull.i2c.sda") - (node (ref JU1) (pin 15)) - (node (ref JU3) (pin 14)) - (node (ref JR14) (pin 2)))) + (node (ref JR12) (pin 1)))) ) \ No newline at end of file From 3659d2de5b530cb563472a606de59ca8a327305e Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 15 Mar 2026 22:12:22 -0700 Subject: [PATCH 09/29] Update NetlistGenerator.py --- edg/electronics_model/NetlistGenerator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/edg/electronics_model/NetlistGenerator.py b/edg/electronics_model/NetlistGenerator.py index c4e42f216..715e0cfdb 100644 --- a/edg/electronics_model/NetlistGenerator.py +++ b/edg/electronics_model/NetlistGenerator.py @@ -276,7 +276,9 @@ def name_net(net: Iterable[TransformUtil.Path], path_ordering: Dict[TransformUti def prune_net_component(path: TransformUtil.Path) -> TransformUtil.Path: # prune out the net interior link, if it exists - if path.links and len(path.links) > 1 and path.links[-1] == "net": + if path.ports and len(path.ports) > 1 and path.ports[-1] == "net": + return path._replace(ports=path.ports[:-1]) + elif path.links and len(path.links) > 1 and path.links[-1] == "net": return path._replace(links=path.links[:-1]) else: return path From 1f065e9152cee3cb60aa8d6fd74c78926a43cbf9 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 30 Mar 2026 22:32:42 -0700 Subject: [PATCH 10/29] create HasPassivePort, remove deprecations --- edg/electronics_model/AnalogPort.py | 8 ++--- edg/electronics_model/CircuitBlock.py | 15 +++----- edg/electronics_model/PassivePort.py | 50 +++++++++++++++++++++------ 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/edg/electronics_model/AnalogPort.py b/edg/electronics_model/AnalogPort.py index b2e704703..adaaee477 100644 --- a/edg/electronics_model/AnalogPort.py +++ b/edg/electronics_model/AnalogPort.py @@ -5,7 +5,7 @@ from typing_extensions import override from ..core import * -from .PassivePort import Passive +from .PassivePort import HasPassivePort from .CircuitBlock import CircuitPortAdapter from .GroundPort import GroundLink from .VoltagePorts import VoltageLink, VoltageSource @@ -148,7 +148,7 @@ def contents(self) -> None: self.assign(self.inner_link.impedance, self.outer_port.link().sink_impedance) -class AnalogSink(AnalogBase): +class AnalogSink(AnalogBase, HasPassivePort): bridge_type = AnalogSinkBridge @staticmethod @@ -202,7 +202,6 @@ def __init__( """voltage_limits are the maximum recommended voltage levels of the device (before device damage occurs), signal_limits are for proper device functionality (e.g. non-RRIO opamps)""" super().__init__() - self.net = self.Port(Passive()) self.voltage_limits = self.Parameter(RangeExpr(voltage_limits)) self.signal_limits = self.Parameter(RangeExpr(signal_limits)) @@ -225,7 +224,7 @@ def __init__(self) -> None: raise NotImplementedError # TODO IMPLEMENT ME -class AnalogSource(AnalogBase): +class AnalogSource(AnalogBase, HasPassivePort): bridge_type = AnalogSourceBridge @staticmethod @@ -267,7 +266,6 @@ def __init__( """voltage_out is the total voltage range the device can output (typically limited by power rails) regardless of controls and including transients, while signal_out is the intended operating range""" super().__init__() - self.net = self.Port(Passive()) self.voltage_out = self.Parameter(RangeExpr(voltage_out)) self.signal_out = self.Parameter(RangeExpr(signal_out)) diff --git a/edg/electronics_model/CircuitBlock.py b/edg/electronics_model/CircuitBlock.py index 01720b461..74851494b 100644 --- a/edg/electronics_model/CircuitBlock.py +++ b/edg/electronics_model/CircuitBlock.py @@ -1,6 +1,5 @@ from __future__ import annotations -import warnings from typing import Generic, Any, Optional, List, Mapping, Dict from typing_extensions import TypeVar, override @@ -67,9 +66,9 @@ def footprint( """Creates a footprint in this circuit block. Value is a one-line description of the part, eg 680R, 0.01uF, LPC1549, to be used as a aid during layout or assembly""" + from .PassivePort import HasPassivePort from ..core.Blocks import BlockElaborationState, BlockDefinitionError from .VoltagePorts import CircuitPort - from .PassivePort import Passive if self._elaboration_state not in ( BlockElaborationState.init, @@ -86,16 +85,10 @@ def footprint( pinning_array = [] for pin_name, pin_port in pinning.items(): + if isinstance(pin_port, HasPassivePort): + pin_port = pin_port.net if not isinstance(pin_port, CircuitPort): - if hasattr(pin_port, 'net') and isinstance(pin_port.net, Passive): - warnings.warn( - "Use the internal passive .net port directly", - DeprecationWarning, - stacklevel=2 # Points the warning to the caller's code - ) - pin_port = pin_port.net - else: - raise EdgTypeError(f"Footprint(...) pin", pin_port, CircuitPort) + raise EdgTypeError(f"Footprint(...) pin", pin_port, CircuitPort) pinning_array.append(f"{pin_name}={pin_port._name_from(self)}") self.assign(self.fp_pinning, pinning_array) diff --git a/edg/electronics_model/PassivePort.py b/edg/electronics_model/PassivePort.py index d90d01f43..dd7b37783 100644 --- a/edg/electronics_model/PassivePort.py +++ b/edg/electronics_model/PassivePort.py @@ -1,16 +1,16 @@ from __future__ import annotations -from typing import TypeVar, Type, Dict, Mapping +from typing import TypeVar, Type, Dict from typing_extensions import TYPE_CHECKING from ..core import * -from .GroundPort import Ground -from .CircuitBlock import CircuitLink, CircuitPortBridge, CircuitPortAdapter -from .DigitalPorts import DigitalSource, DigitalSink, DigitalBidir -from .VoltagePorts import CircuitPort, VoltageSource, VoltageSink +from .CircuitBlock import CircuitPort, CircuitLink, CircuitPortBridge, CircuitPortAdapter if TYPE_CHECKING: + from .GroundPort import Ground + from .VoltagePorts import VoltageSource, VoltageSink + from .DigitalPorts import DigitalSource, DigitalSink, DigitalBidir from .AnalogPort import AnalogSource, AnalogSink @@ -22,14 +22,16 @@ def __init__(self) -> None: self.passives = self.Port(Vector(Passive())) -class PassiveAdapterGround(CircuitPortAdapter[Ground]): +class PassiveAdapterGround(CircuitPortAdapter["Ground"]): def __init__(self, voltage_limits: RangeLike = RangeExpr.ALL): + from .GroundPort import Ground + super().__init__() self.src = self.Port(Passive()) self.dst = self.Port(Ground(voltage_limits=voltage_limits)) -class PassiveAdapterVoltageSource(CircuitPortAdapter[VoltageSource]): +class PassiveAdapterVoltageSource(CircuitPortAdapter["VoltageSource"]): # TODO we can't use **kwargs b/c init_in_parent needs the initializer list def __init__( self, @@ -38,6 +40,8 @@ def __init__( reverse_voltage_limits: RangeLike = RangeExpr.EMPTY, reverse_current_draw: RangeLike = RangeExpr.EMPTY, ): + from .VoltagePorts import VoltageSource + super().__init__() self.src = self.Port(Passive()) self.dst = self.Port( @@ -50,7 +54,7 @@ def __init__( ) -class PassiveAdapterVoltageSink(CircuitPortAdapter[VoltageSink]): +class PassiveAdapterVoltageSink(CircuitPortAdapter["VoltageSink"]): # TODO we can't use **kwargs b/c the init hook needs an initializer list def __init__( self, @@ -59,6 +63,8 @@ def __init__( reverse_voltage_out: RangeLike = RangeExpr.EMPTY, reverse_current_limits: RangeLike = RangeExpr.EMPTY, ): + from .VoltagePorts import VoltageSink + super().__init__() self.src = self.Port(Passive()) self.dst = self.Port( @@ -71,7 +77,7 @@ def __init__( ) -class PassiveAdapterDigitalSource(CircuitPortAdapter[DigitalSource]): +class PassiveAdapterDigitalSource(CircuitPortAdapter["DigitalSource"]): # TODO we can't use **kwargs b/c the init hook needs an initializer list def __init__( self, @@ -84,6 +90,8 @@ def __init__( low_driver: BoolLike = True, _bridged_internal: BoolLike = False, ): + from .DigitalPorts import DigitalSource + super().__init__() self.src = self.Port(Passive()) self.dst = self.Port( @@ -100,7 +108,7 @@ def __init__( ) -class PassiveAdapterDigitalSink(CircuitPortAdapter[DigitalSink]): +class PassiveAdapterDigitalSink(CircuitPortAdapter["DigitalSink"]): # TODO we can't use **kwargs b/c the init hook needs an initializer list def __init__( self, @@ -111,6 +119,8 @@ def __init__( pulldown_capable: BoolLike = False, _bridged_internal: BoolLike = False, ): + from .DigitalPorts import DigitalSink + super().__init__() self.src = self.Port(Passive()) self.dst = self.Port( @@ -125,7 +135,7 @@ def __init__( ) -class PassiveAdapterDigitalBidir(CircuitPortAdapter[DigitalBidir]): +class PassiveAdapterDigitalBidir(CircuitPortAdapter["DigitalBidir"]): # TODO we can't use **kwargs b/c the init hook needs an initializer list def __init__( self, @@ -140,6 +150,8 @@ def __init__( pulldown_capable: BoolLike = False, _bridged_internal: BoolLike = False, ): + from .DigitalPorts import DigitalBidir + super().__init__() self.src = self.Port(Passive()) self.dst = self.Port( @@ -167,6 +179,7 @@ def __init__( impedance: RangeLike = RangeExpr.ZERO, ): from .AnalogPort import AnalogSource, AnalogSink + super().__init__() self.src = self.Port(Passive()) self.dst = self.Port( @@ -187,6 +200,7 @@ def __init__( impedance: RangeLike = RangeExpr.INF, ): from .AnalogPort import AnalogSource, AnalogSink + super().__init__() self.src = self.Port(Passive()) self.dst = self.Port( @@ -207,14 +221,19 @@ def __init__(self) -> None: self.inner_link = self.Port(Passive()) +# TODO this should replace CircuitPort and should be the lowest level of abstraction port class Passive(CircuitPort[PassiveLink]): """Basic copper-only port, which can be adapted to a more strongly typed Voltage/Digital/Analog* port""" + link_type = PassiveLink bridge_type = PassiveBridge AdaptTargetType = TypeVar("AdaptTargetType", bound=CircuitPort) def adapt_to(self, that: AdaptTargetType) -> AdaptTargetType: + from .GroundPort import Ground + from .VoltagePorts import VoltageSource, VoltageSink + from .DigitalPorts import DigitalSource, DigitalSink, DigitalBidir from .AnalogPort import AnalogSource, AnalogSink ADAPTER_TYPE_MAP: Dict[Type[Port], Type[CircuitPortAdapter]] = { @@ -243,3 +262,12 @@ def adapt_to(self, that: AdaptTargetType) -> AdaptTargetType: adapter_init_kwargs[param_name] = param.initializer return self._convert(adapter_cls(**adapter_init_kwargs)) # type: ignore + + +class HasPassivePort(Port[Link]): + """A port that contains a single net as a passive port. + Some functionality may provide convenience functions on this to use the internal net.""" + + def __init__(self) -> None: + super().__init__() + self.net = self.Port(Passive()) From 03b9d5d0cba98ab260192644fc447a9a4f285ceb Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 31 Mar 2026 01:09:24 -0700 Subject: [PATCH 11/29] refactor some analog adapters --- edg/abstract_parts/DummyDevices.py | 8 ++++---- edg/abstract_parts/MergedBlocks.py | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/edg/abstract_parts/DummyDevices.py b/edg/abstract_parts/DummyDevices.py index 0ef9b3865..60d56d509 100644 --- a/edg/abstract_parts/DummyDevices.py +++ b/edg/abstract_parts/DummyDevices.py @@ -113,25 +113,24 @@ def __init__(self, forced_voltage: RangeLike, forced_current: RangeLike) -> None self.pwr_out = self.Port(VoltageSource(voltage_out=forced_voltage), [Output]) -class ForcedAnalogVoltage(DummyDevice, NetBlock): +class ForcedAnalogVoltage(DummyDevice): def __init__(self, forced_voltage: RangeLike = RangeExpr()) -> None: super().__init__() self.signal_in = self.Port(AnalogSink(current_draw=RangeExpr()), [Input]) - self.signal_out = self.Port( AnalogSource(voltage_out=forced_voltage, signal_out=self.signal_in.link().signal), [Output] ) self.assign(self.signal_in.current_draw, self.signal_out.link().current_drawn) + self.connect(self.signal_in.net, self.signal_out.net) -class ForcedAnalogSignal(KiCadImportableBlock, DummyDevice, NetBlock): +class ForcedAnalogSignal(KiCadImportableBlock, DummyDevice): def __init__(self, forced_signal: RangeLike = RangeExpr()) -> None: super().__init__() self.signal_in = self.Port(AnalogSink(current_draw=RangeExpr()), [Input]) - self.signal_out = self.Port( AnalogSource( voltage_out=self.signal_in.link().voltage, @@ -142,6 +141,7 @@ def __init__(self, forced_signal: RangeLike = RangeExpr()) -> None: ) self.assign(self.signal_in.current_draw, self.signal_out.link().current_drawn) + self.connect(self.signal_in.net, self.signal_out.net) @override def symbol_pinning(self, symbol_name: str) -> Dict[str, BasePort]: diff --git a/edg/abstract_parts/MergedBlocks.py b/edg/abstract_parts/MergedBlocks.py index 9adddd40a..4eb055bbb 100644 --- a/edg/abstract_parts/MergedBlocks.py +++ b/edg/abstract_parts/MergedBlocks.py @@ -69,7 +69,7 @@ def connected_from(self, *ins: Port[DigitalLink]) -> "MergedDigitalSource": return self -class MergedAnalogSource(KiCadImportableBlock, DummyDevice, NetBlock, GeneratorBlock): +class MergedAnalogSource(KiCadImportableBlock, DummyDevice, GeneratorBlock): @override def symbol_pinning(self, symbol_name: str) -> Dict[str, BasePort]: assert symbol_name.startswith("edg_importable:Merge") # can be any merge @@ -89,10 +89,11 @@ def generate(self) -> None: super().generate() self.inputs.defined() for in_request in self.get(self.inputs.requested()): - self.inputs.append_elt( + elt_port = self.inputs.append_elt( AnalogSink(current_draw=self.output.link().current_drawn, impedance=self.output.link().sink_impedance), in_request, ) + self.connect(self.output.net, elt_port.net) self.assign(self.output.voltage_out, self.inputs.hull(lambda x: x.link().voltage)) self.assign(self.output.signal_out, self.inputs.hull(lambda x: x.link().signal)) From 9ef2d9063cbe2ecc907b17b48fa3930a138dd697 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 4 Apr 2026 18:13:36 -0700 Subject: [PATCH 12/29] some cleanup, some remaining failures --- edg/electronics_model/AnalogPort.py | 18 ++++++++++-------- edg/electronics_model/GroundPort.py | 4 +++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/edg/electronics_model/AnalogPort.py b/edg/electronics_model/AnalogPort.py index adaaee477..e96970740 100644 --- a/edg/electronics_model/AnalogPort.py +++ b/edg/electronics_model/AnalogPort.py @@ -213,15 +213,17 @@ class AnalogSourceAdapterVoltageSource(CircuitPortAdapter[VoltageSource]): def __init__(self) -> None: super().__init__() self.src = self.Port(AnalogSink(current_draw=RangeExpr())) # otherwise ideal - self.dst = self.Port( - VoltageSource( - voltage_out=(self.src.link().voltage.upper(), self.src.link().voltage.upper()), - current_limits=(-float("inf"), float("inf")), - ) - ) + self.dst = self.Port(VoltageSource.empty()) self.assign(self.src.current_draw, self.dst.link().current_drawn) - - raise NotImplementedError # TODO IMPLEMENT ME + self.connect( + self.src.net.adapt_to( + VoltageSource( + voltage_out=(self.src.link().voltage.upper(), self.src.link().voltage.upper()), + current_limits=(-float("inf"), float("inf")), + ) + ), + self.dst, + ) class AnalogSource(AnalogBase, HasPassivePort): diff --git a/edg/electronics_model/GroundPort.py b/edg/electronics_model/GroundPort.py index bcfc05de5..566c6f2a5 100644 --- a/edg/electronics_model/GroundPort.py +++ b/edg/electronics_model/GroundPort.py @@ -4,6 +4,7 @@ from typing_extensions import override +from .PassivePort import PassiveAdapterGround from ..core import * from .CircuitBlock import CircuitPortBridge, CircuitPortAdapter, CircuitLink, CircuitPort from .Units import Volt, Ohm @@ -95,13 +96,14 @@ def __init__(self) -> None: from .AnalogPort import AnalogSource super().__init__() - self.src = self.Port(Ground()) + self.src = self.Port(Ground.empty()) self.dst = self.Port( AnalogSource( voltage_out=self.src.link().voltage, signal_out=self.src.link().voltage, ) ) + self.connect(self.dst.net.adapt_to(Ground()), self.src) class Ground(CircuitPort[GroundLink]): From b8719ff392023ec2a55dc66a3c90bd551e15e053 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 4 Apr 2026 18:52:18 -0700 Subject: [PATCH 13/29] moles being whacked --- edg/electronics_model/VoltagePorts.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/edg/electronics_model/VoltagePorts.py b/edg/electronics_model/VoltagePorts.py index 5c6adeaac..1b85b95ae 100644 --- a/edg/electronics_model/VoltagePorts.py +++ b/edg/electronics_model/VoltagePorts.py @@ -260,7 +260,7 @@ def __init__(self) -> None: from .AnalogPort import AnalogSource super().__init__() - self.src = self.Port(VoltageSink(current_draw=RangeExpr())) + self.src = self.Port(VoltageSink.empty()) self.dst = self.Port( AnalogSource( voltage_out=self.src.link().voltage, @@ -268,9 +268,7 @@ def __init__(self) -> None: impedance=(0, 0) * Ohm, # TODO not actually true, but pretty darn low? ) ) - - # TODO might be an overestimate - self.assign(self.src.current_draw, self.dst.link().current_drawn) + self.connect(self.dst.net.adapt_to(VoltageSink(current_draw=self.dst.link().current_drawn)), self.src) class VoltageSource(VoltageBase): From af5df647eee6cfb71e4157d26db1443c56765690 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 4 Apr 2026 21:50:51 -0700 Subject: [PATCH 14/29] fix test --- edg/electronics_model/KiCadSchematicBlock.py | 10 +++++++++- edg/electronics_model/test_kicad_import_blackbox.py | 5 +++-- examples/Multimeter/Multimeter.net | 2 +- examples/Multimeter/Multimeter.svgpcb.js | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/edg/electronics_model/KiCadSchematicBlock.py b/edg/electronics_model/KiCadSchematicBlock.py index f60dbfc57..3efb7c550 100644 --- a/edg/electronics_model/KiCadSchematicBlock.py +++ b/edg/electronics_model/KiCadSchematicBlock.py @@ -8,7 +8,7 @@ from ..core import * from .CircuitBlock import FootprintBlock from .VoltagePorts import CircuitPort -from .PassivePort import Passive +from .PassivePort import Passive, HasPassivePort from .KiCadImportableBlock import KiCadInstantiableBlock, KiCadImportableBlock from .KiCadSchematicParser import ( KiCadSchematic, @@ -324,8 +324,16 @@ def import_kicad( and isinstance(boundary_port, CircuitPort) and not isinstance(boundary_port, Passive) ): + # TODO remove after full compositional passive refactor #114 adapted = cast(Passive, net_ports[0]).adapt_to(boundary_port.__class__()) self.connect(adapted, boundary_port) + elif ( + auto_adapt + and can_adapt + and isinstance(boundary_port, HasPassivePort) + and not isinstance(boundary_port, Passive) + ): + self.connect(net_ports[0], boundary_port.net) else: self.connect(connection, boundary_port) diff --git a/edg/electronics_model/test_kicad_import_blackbox.py b/edg/electronics_model/test_kicad_import_blackbox.py index eafcbcafd..4b21f1200 100644 --- a/edg/electronics_model/test_kicad_import_blackbox.py +++ b/edg/electronics_model/test_kicad_import_blackbox.py @@ -122,8 +122,9 @@ def test_import_blackbox_autoadapt(self) -> None: expected_conn = edgir.ValueExpr() expected_conn.exported.exterior_port.ref.steps.add().name = "out" - expected_conn.exported.internal_block_port.ref.steps.add().name = "(adapter)res.b" - expected_conn.exported.internal_block_port.ref.steps.add().name = "dst" + expected_conn.exported.exterior_port.ref.steps.add().name = "net" + expected_conn.exported.internal_block_port.ref.steps.add().name = "res" + expected_conn.exported.internal_block_port.ref.steps.add().name = "b" self.assertIn(expected_conn, constraints) # blackbox definition not checked again diff --git a/examples/Multimeter/Multimeter.net b/examples/Multimeter/Multimeter.net index 9ada53b2f..0e89f83fc 100644 --- a/examples/Multimeter/Multimeter.net +++ b/examples/Multimeter/Multimeter.net @@ -1613,7 +1613,7 @@ (node (ref R17) (pin 2)) (node (ref R18) (pin 1)) (node (ref U8) (pin 3))) -(net (code 57) (name "inn_merge") +(net (code 57) (name "inn.port") (node (ref J5) (pin 1)) (node (ref U9) (pin 4)) (node (ref U12) (pin 4)) diff --git a/examples/Multimeter/Multimeter.svgpcb.js b/examples/Multimeter/Multimeter.svgpcb.js index 2c44e8d9d..74f0c1dfa 100644 --- a/examples/Multimeter/Multimeter.svgpcb.js +++ b/examples/Multimeter/Multimeter.svgpcb.js @@ -593,7 +593,7 @@ board.setNetlist([ {name: "spk_drv.inp_res.a", pads: [["R15", "1"], ["C13", "1"]]}, {name: "spk_drv.inn_res.a", pads: [["R16", "1"], ["C14", "1"]]}, {name: "ref_div.output", pads: [["R17", "2"], ["R18", "1"], ["U8", "3"]]}, - {name: "inn_merge", pads: [["J5", "1"], ["U9", "4"], ["U12", "4"], ["U14", "6"]]}, + {name: "inn.port", pads: [["J5", "1"], ["U9", "4"], ["U12", "4"], ["U14", "6"]]}, {name: "inp.port", pads: [["J6", "1"], ["R19", "1"], ["D7", "1"]]}, {name: "measure.range.switch.sw[0_0].com", pads: [["U10", "4"], ["U12", "3"]]}, {name: "measure.range.switch.sw[0_1].com", pads: [["U11", "4"], ["U12", "1"]]}, From 7956871eb92b748c6fb7396680087690e6b8b08b Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 4 Apr 2026 22:07:27 -0700 Subject: [PATCH 15/29] cleaning --- edg/abstract_parts/IoController.py | 5 ++++- edg/electronics_model/PassivePort.py | 2 +- edg/parts/BoostConverter_DiodesInc.py | 2 +- edg/parts/SpeakerDriver_Max98357a.py | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/edg/abstract_parts/IoController.py b/edg/abstract_parts/IoController.py index 57a698445..5fd715abc 100644 --- a/edg/abstract_parts/IoController.py +++ b/edg/abstract_parts/IoController.py @@ -7,6 +7,7 @@ from ..electronics_model import * from .PinMappable import AllocatedResource, PinMappable, PinMapUtil from .Categories import ProgrammableController +from ..electronics_model.PassivePort import HasPassivePort @non_library @@ -141,12 +142,14 @@ def _instantiate_from( # TODO: recurse into bundles, really needs a more unified way of handling current draw if isinstance(allocation.pin, str): + assert isinstance(io_port, (CircuitPort, HasPassivePort)) pinmap[allocation.pin] = io_port elif allocation.pin is None: - pass # discarded + assert isinstance(io_port, (CircuitPort, HasPassivePort)) # otherwise discarded elif isinstance(allocation.pin, dict): for subport_name, (pin_name, pin_resource) in allocation.pin.items(): subport = getattr(io_port, subport_name) + assert isinstance(subport, (CircuitPort, HasPassivePort)), f"bad sub-port {pin_name} {subport}" pinmap[pin_name] = subport else: raise NotImplementedError(f"unknown allocation pin type {allocation.pin}") diff --git a/edg/electronics_model/PassivePort.py b/edg/electronics_model/PassivePort.py index dd7b37783..6b36d4d84 100644 --- a/edg/electronics_model/PassivePort.py +++ b/edg/electronics_model/PassivePort.py @@ -221,7 +221,7 @@ def __init__(self) -> None: self.inner_link = self.Port(Passive()) -# TODO this should replace CircuitPort and should be the lowest level of abstraction port +# TODO this should replace CircuitPort and should be the lowest level of abstraction port, #114 class Passive(CircuitPort[PassiveLink]): """Basic copper-only port, which can be adapted to a more strongly typed Voltage/Digital/Analog* port""" diff --git a/edg/parts/BoostConverter_DiodesInc.py b/edg/parts/BoostConverter_DiodesInc.py index 827fe1fac..0057e77bb 100644 --- a/edg/parts/BoostConverter_DiodesInc.py +++ b/edg/parts/BoostConverter_DiodesInc.py @@ -31,7 +31,7 @@ def contents(self) -> None: { "1": self.sw, "2": self.gnd, - "3": self.fb.net, + "3": self.fb, "4": self.nshdn, "5": self.pwr_in, }, diff --git a/edg/parts/SpeakerDriver_Max98357a.py b/edg/parts/SpeakerDriver_Max98357a.py index 3e6cf4576..1990323ca 100644 --- a/edg/parts/SpeakerDriver_Max98357a.py +++ b/edg/parts/SpeakerDriver_Max98357a.py @@ -42,7 +42,7 @@ def generate(self) -> None: "4": self.vdd, # hard tied to left mode only TODO selectable SD_MODE "7": self.vdd, "8": self.vdd, - "9": self.out.a.net, # outp + "9": self.out.a, # outp "1": self.i2s.sd, # '2': gain_slot, # TODO: configurable gain, open = 9dB "10": self.out.b, # outn @@ -63,10 +63,10 @@ def generate(self) -> None: pinning = { "A1": self.vdd, # hard tied to left mode only TODO selectable SD_MODE "A2": self.vdd, - "A3": self.out.a.net, # outp + "A3": self.out.a, # outp "B1": self.i2s.sd, # 'B2': gain_slot, # TODO: configurable gain, open = 9dB - "B3": self.out.b.net, # outn + "B3": self.out.b, # outn "C1": self.i2s.sck, "C2": self.gnd, "C3": self.i2s.ws, From afb5d2fdb7ae81b09474e4806b712bfc16cf5593 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 4 Apr 2026 22:07:46 -0700 Subject: [PATCH 16/29] blacken --- edg/electronics_model/NetlistGenerator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/edg/electronics_model/NetlistGenerator.py b/edg/electronics_model/NetlistGenerator.py index bc6bf5926..c8cf93974 100644 --- a/edg/electronics_model/NetlistGenerator.py +++ b/edg/electronics_model/NetlistGenerator.py @@ -284,6 +284,7 @@ def prune_net_component(path: TransformUtil.Path) -> TransformUtil.Path: return path._replace(links=path.links[:-1]) else: return path + net = map(prune_net_component, net) def pin_name_goodness(pin1: TransformUtil.Path, pin2: TransformUtil.Path) -> int: From 7cf31a90b289d64ee372efd6f211b46aece3ca69 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 4 Apr 2026 22:23:29 -0700 Subject: [PATCH 17/29] Fix types --- edg/abstract_parts/IoController.py | 8 +++--- edg/abstract_parts/OpampCircuits.py | 11 +++++--- edg/abstract_parts/PinMappable.py | 3 ++- edg/electronics_model/CircuitBlock.py | 7 +++-- edg/electronics_model/GroundPort.py | 2 +- edg/electronics_model/KiCadSchematicBlock.py | 8 +++--- edg/electronics_model/PassivePort.py | 28 ++++++++++---------- edg/electronics_model/VoltagePorts.py | 2 +- edg/parts/SpeakerDriver_Max98357a.py | 5 ++-- 9 files changed, 41 insertions(+), 33 deletions(-) diff --git a/edg/abstract_parts/IoController.py b/edg/abstract_parts/IoController.py index 5fd715abc..2695f23c5 100644 --- a/edg/abstract_parts/IoController.py +++ b/edg/abstract_parts/IoController.py @@ -1,5 +1,5 @@ from itertools import chain -from typing import List, Dict, Tuple, Type, Optional, Any +from typing import List, Dict, Tuple, Type, Optional, Any, Union from deprecated import deprecated from typing_extensions import override @@ -98,12 +98,12 @@ def _export_ios_from(self, inner: "BaseIoController", excludes: List[BasePort] = @staticmethod def _instantiate_from( ios: List[BasePort], allocations: List[AllocatedResource] - ) -> Tuple[Dict[str, CircuitPort], RangeExpr]: + ) -> Tuple[Dict[str, Union[CircuitPort, HasPassivePort]], RangeExpr]: """Given a mapping of port types to IO ports and allocated resources from PinMapUtil, instantiate vector elements (if a vector) or init the port model (if a port) for the allocated resources using their data model and return the pin mapping.""" ios_by_type = {io.elt_type() if isinstance(io, Vector) else type(io): io for io in ios} - pinmap: Dict[str, CircuitPort] = {} + pinmap: Dict[str, Union[CircuitPort, HasPassivePort]] = {} ports_assigned = IdentitySet[Port]() io_current_draw_builder = RangeExpr._to_expr_type(RangeExpr.ZERO) @@ -184,7 +184,7 @@ def _io_pinmap(self) -> PinMapUtil: """Implement me. Defines the assignable IO pinmaps.""" raise NotImplementedError - def _make_pinning(self) -> Dict[str, CircuitPort]: + def _make_pinning(self) -> Dict[str, Union[CircuitPort, HasPassivePort]]: allocation_list = [] for io_port in self._io_ports: if isinstance(io_port, Vector): # derive Vector connections from requested diff --git a/edg/abstract_parts/OpampCircuits.py b/edg/abstract_parts/OpampCircuits.py index 1cf848e41..8df355277 100644 --- a/edg/abstract_parts/OpampCircuits.py +++ b/edg/abstract_parts/OpampCircuits.py @@ -1,5 +1,5 @@ from math import ceil, log10 -from typing import List, Tuple, Dict, Mapping +from typing import List, Tuple, Dict, Mapping, Union from typing_extensions import override @@ -11,6 +11,7 @@ from .Categories import OpampApplication from .DummyDevices import ForcedAnalogSignal from .ESeriesUtil import ESeriesRatioUtil, ESeriesUtil, ESeriesRatioValue +from ..electronics_model.PassivePort import HasPassivePort class OpampFollower(OpampApplication, KiCadSchematicBlock, KiCadImportableBlock): @@ -156,8 +157,10 @@ def generate(self) -> None: self.r2 = self.Block(Resistor(Range.from_tolerance(bottom_resistance, self.get(self.tolerance)))) if self.get(self.reference.is_connected()): - reference_type: CircuitPort = AnalogSink(impedance=self.r1.actual_resistance + self.r2.actual_resistance) - reference_node: CircuitPort = self.reference + reference_type: Union[CircuitPort, HasPassivePort] = AnalogSink( + impedance=self.r1.actual_resistance + self.r2.actual_resistance + ) + reference_node: Union[CircuitPort, HasPassivePort] = self.reference reference_range = self.reference.link().signal else: reference_type = Ground() @@ -310,7 +313,7 @@ def generate(self) -> None: if self.get(self.output_reference.is_connected()): output_neg_signal = self.output_reference.link().signal output_neg_voltage = self.output_reference.link().voltage - output_neg_node: CircuitPort = self.output_reference + output_neg_node: Union[CircuitPort, HasPassivePort] = self.output_reference else: output_neg_voltage = output_neg_signal = self.gnd.link().voltage output_neg_node = self.gnd.as_analog_source() diff --git a/edg/abstract_parts/PinMappable.py b/edg/abstract_parts/PinMappable.py index 70fa4e29a..2746f1bd2 100644 --- a/edg/abstract_parts/PinMappable.py +++ b/edg/abstract_parts/PinMappable.py @@ -4,6 +4,7 @@ from typing_extensions import override from ..electronics_model import * +from ..electronics_model.PassivePort import HasPassivePort @non_library @@ -55,7 +56,7 @@ class BaseDelegatingPinMapResource(BasePinMapResource): class PinResource(BaseLeafPinMapResource): """A resource for a single chip pin, which can be one of several port types (eg, an ADC and DIO sharing a pin).""" - def __init__(self, pin: str, name_models: Dict[str, CircuitPort]): + def __init__(self, pin: str, name_models: Mapping[str, Union[CircuitPort, HasPassivePort]]): self.pin = pin self.name_models = name_models diff --git a/edg/electronics_model/CircuitBlock.py b/edg/electronics_model/CircuitBlock.py index 74851494b..e9f99a13d 100644 --- a/edg/electronics_model/CircuitBlock.py +++ b/edg/electronics_model/CircuitBlock.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Generic, Any, Optional, List, Mapping, Dict +from typing import Generic, Any, Optional, List, Mapping, Dict, Union, TYPE_CHECKING from typing_extensions import TypeVar, override @@ -8,6 +8,9 @@ from ..core import * from ..core.HdlUserExceptions import EdgTypeError +if TYPE_CHECKING: + from .PassivePort import HasPassivePort + CircuitLinkType = TypeVar("CircuitLinkType", bound=Link, covariant=True, default=Link) @@ -57,7 +60,7 @@ def footprint( self, refdes: StringLike, footprint: StringLike, - pinning: Mapping[str, CircuitPort], + pinning: Mapping[str, Union[CircuitPort, "HasPassivePort"]], mfr: Optional[StringLike] = None, part: Optional[StringLike] = None, value: Optional[StringLike] = None, diff --git a/edg/electronics_model/GroundPort.py b/edg/electronics_model/GroundPort.py index 566c6f2a5..1059d6900 100644 --- a/edg/electronics_model/GroundPort.py +++ b/edg/electronics_model/GroundPort.py @@ -91,7 +91,7 @@ def __init__(self) -> None: ) -class GroundAdapterAnalogSource(CircuitPortAdapter["AnalogSource"]): +class GroundAdapterAnalogSource(PortAdapter["AnalogSource"]): def __init__(self) -> None: from .AnalogPort import AnalogSource diff --git a/edg/electronics_model/KiCadSchematicBlock.py b/edg/electronics_model/KiCadSchematicBlock.py index 3efb7c550..412d02f6f 100644 --- a/edg/electronics_model/KiCadSchematicBlock.py +++ b/edg/electronics_model/KiCadSchematicBlock.py @@ -1,7 +1,7 @@ import inspect import os from abc import abstractmethod -from typing import Type, Any, Optional, Mapping, Dict, List, Callable, Tuple, TypeVar, cast +from typing import Type, Any, Optional, Mapping, Dict, List, Callable, Tuple, TypeVar, cast, Union from typing_extensions import override @@ -123,7 +123,7 @@ class KiCadSchematicBlock(Block): @staticmethod def _port_from_pin( - pin: KiCadPin, mapping: Mapping[str, BasePort], conversions: Mapping[str, CircuitPort] + pin: KiCadPin, mapping: Mapping[str, BasePort], conversions: Mapping[str, Union[CircuitPort, HasPassivePort]] ) -> BasePort: """Returns the Port from a symbol's pin, using the provided mapping and applying conversions as needed.""" from .PassivePort import Passive @@ -153,7 +153,7 @@ def _port_from_pin( f"mapping defined for both number ${pin.pin_number} and name ${pin.pin_name}" ) elif f"{pin.refdes}.{pin.pin_number}" in conversions: - conversion: Optional[CircuitPort] = conversions[f"{pin.refdes}.{pin.pin_number}"] + conversion: Optional[Union[CircuitPort, HasPassivePort]] = conversions[f"{pin.refdes}.{pin.pin_number}"] elif f"{pin.refdes}.{pin.pin_name}" in conversions: conversion = conversions[f"{pin.refdes}.{pin.pin_name}"] else: @@ -203,7 +203,7 @@ def import_kicad( locals: Mapping[str, Any] = {}, *, nodes: Mapping[str, Optional[BasePort]] = {}, - conversions: Mapping[str, CircuitPort] = {}, + conversions: Mapping[str, Union[CircuitPort, HasPassivePort]] = {}, auto_adapt: bool = False, ) -> None: # ideally SYMBOL_MAP would be a class variable, but this causes a import loop with Opamp, diff --git a/edg/electronics_model/PassivePort.py b/edg/electronics_model/PassivePort.py index 6b36d4d84..01fc02f48 100644 --- a/edg/electronics_model/PassivePort.py +++ b/edg/electronics_model/PassivePort.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TypeVar, Type, Dict +from typing import TypeVar, Type, Dict, Union from typing_extensions import TYPE_CHECKING @@ -169,7 +169,7 @@ def __init__( ) -class PassiveAdapterAnalogSource(CircuitPortAdapter["AnalogSource"]): +class PassiveAdapterAnalogSource(PortAdapter["AnalogSource"]): # TODO we can't use **kwargs b/c the init hook needs an initializer list def __init__( self, @@ -190,7 +190,7 @@ def __init__( self.connect(self.src, self.dst.net) -class PassiveAdapterAnalogSink(CircuitPortAdapter["AnalogSink"]): +class PassiveAdapterAnalogSink(PortAdapter["AnalogSink"]): # TODO we can't use **kwargs b/c the init hook needs an initializer list def __init__( self, @@ -221,6 +221,15 @@ def __init__(self) -> None: self.inner_link = self.Port(Passive()) +class HasPassivePort(Port[Link]): + """A port that contains a single net as a passive port. + Some functionality may provide convenience functions on this to use the internal net.""" + + def __init__(self) -> None: + super().__init__() + self.net = self.Port(Passive()) + + # TODO this should replace CircuitPort and should be the lowest level of abstraction port, #114 class Passive(CircuitPort[PassiveLink]): """Basic copper-only port, which can be adapted to a more strongly typed Voltage/Digital/Analog* port""" @@ -228,7 +237,7 @@ class Passive(CircuitPort[PassiveLink]): link_type = PassiveLink bridge_type = PassiveBridge - AdaptTargetType = TypeVar("AdaptTargetType", bound=CircuitPort) + AdaptTargetType = TypeVar("AdaptTargetType", bound=Union[CircuitPort, HasPassivePort]) def adapt_to(self, that: AdaptTargetType) -> AdaptTargetType: from .GroundPort import Ground @@ -236,7 +245,7 @@ def adapt_to(self, that: AdaptTargetType) -> AdaptTargetType: from .DigitalPorts import DigitalSource, DigitalSink, DigitalBidir from .AnalogPort import AnalogSource, AnalogSink - ADAPTER_TYPE_MAP: Dict[Type[Port], Type[CircuitPortAdapter]] = { + ADAPTER_TYPE_MAP: Dict[Type[Port], Type[PortAdapter]] = { Ground: PassiveAdapterGround, VoltageSource: PassiveAdapterVoltageSource, VoltageSink: PassiveAdapterVoltageSink, @@ -262,12 +271,3 @@ def adapt_to(self, that: AdaptTargetType) -> AdaptTargetType: adapter_init_kwargs[param_name] = param.initializer return self._convert(adapter_cls(**adapter_init_kwargs)) # type: ignore - - -class HasPassivePort(Port[Link]): - """A port that contains a single net as a passive port. - Some functionality may provide convenience functions on this to use the internal net.""" - - def __init__(self) -> None: - super().__init__() - self.net = self.Port(Passive()) diff --git a/edg/electronics_model/VoltagePorts.py b/edg/electronics_model/VoltagePorts.py index 1b85b95ae..27965e213 100644 --- a/edg/electronics_model/VoltagePorts.py +++ b/edg/electronics_model/VoltagePorts.py @@ -255,7 +255,7 @@ def __init__(self) -> None: self.assign(self.src.current_draw, self.dst.link().current_drawn) # TODO might be an overestimate -class VoltageSinkAdapterAnalogSource(CircuitPortAdapter["AnalogSource"]): +class VoltageSinkAdapterAnalogSource(PortAdapter["AnalogSource"]): def __init__(self) -> None: from .AnalogPort import AnalogSource diff --git a/edg/parts/SpeakerDriver_Max98357a.py b/edg/parts/SpeakerDriver_Max98357a.py index 1990323ca..6abca4173 100644 --- a/edg/parts/SpeakerDriver_Max98357a.py +++ b/edg/parts/SpeakerDriver_Max98357a.py @@ -1,9 +1,10 @@ -from typing import Dict +from typing import Dict, Union from typing_extensions import override from ..abstract_parts import * from .JlcPart import JlcPart +from ..electronics_model.PassivePort import HasPassivePort class Max98357a_Device(InternalSubcircuit, JlcPart, SelectorFootprint, PartsTablePart, GeneratorBlock, FootprintBlock): @@ -38,7 +39,7 @@ def generate(self) -> None: or self.get(self.footprint_spec) == "Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.45x1.45mm" ): footprint = "Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.45x1.45mm" - pinning: Dict[str, CircuitPort] = { + pinning: Dict[str, Union[CircuitPort, HasPassivePort]] = { "4": self.vdd, # hard tied to left mode only TODO selectable SD_MODE "7": self.vdd, "8": self.vdd, From 2a60f638dc1e25f526239d52337c15eb3a686219 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 4 Apr 2026 22:44:12 -0700 Subject: [PATCH 18/29] Make adapters kicad-importable --- edg/electronics_model/AnalogPort.py | 4 ++-- edg/electronics_model/CircuitBlock.py | 11 ++++++++--- edg/electronics_model/GroundPort.py | 4 ++-- edg/electronics_model/PassivePort.py | 6 +++--- edg/electronics_model/VoltagePorts.py | 4 ++-- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/edg/electronics_model/AnalogPort.py b/edg/electronics_model/AnalogPort.py index e96970740..802faee9c 100644 --- a/edg/electronics_model/AnalogPort.py +++ b/edg/electronics_model/AnalogPort.py @@ -6,7 +6,7 @@ from ..core import * from .PassivePort import HasPassivePort -from .CircuitBlock import CircuitPortAdapter +from .CircuitBlock import CircuitPortAdapter, KicadImportablePortAdapter from .GroundPort import GroundLink from .VoltagePorts import VoltageLink, VoltageSource @@ -209,7 +209,7 @@ def __init__( self.impedance = self.Parameter(RangeExpr(impedance)) -class AnalogSourceAdapterVoltageSource(CircuitPortAdapter[VoltageSource]): +class AnalogSourceAdapterVoltageSource(KicadImportablePortAdapter[VoltageSource]): def __init__(self) -> None: super().__init__() self.src = self.Port(AnalogSink(current_draw=RangeExpr())) # otherwise ideal diff --git a/edg/electronics_model/CircuitBlock.py b/edg/electronics_model/CircuitBlock.py index e9f99a13d..0d8333d6d 100644 --- a/edg/electronics_model/CircuitBlock.py +++ b/edg/electronics_model/CircuitBlock.py @@ -143,22 +143,27 @@ def contents(self) -> None: self.net() -AdapterDstType = TypeVar("AdapterDstType", covariant=True, bound="CircuitPort", default="CircuitPort") +AdapterDstType = TypeVar("AdapterDstType", covariant=True, bound=Port, default=Port) -@abstract_block -class CircuitPortAdapter(KiCadImportableBlock, NetBaseBlock, PortAdapter[AdapterDstType], Generic[AdapterDstType]): +@non_library +class KicadImportablePortAdapter(KiCadImportableBlock, PortAdapter[AdapterDstType], Generic[AdapterDstType]): @override def symbol_pinning(self, symbol_name: str) -> Dict[str, BasePort]: assert symbol_name == "edg_importable:Adapter" return {"1": self.src, "2": self.dst} + +# TODO remove me once compositional passive refactoring complete, #114 +@abstract_block +class CircuitPortAdapter(KicadImportablePortAdapter[AdapterDstType], NetBaseBlock, Generic[AdapterDstType]): @override def contents(self) -> None: super().contents() self.net() +# TODO remove me once compositional passive refactoring complete, #114 @non_library # TODO make abstract instead? class CircuitLink(NetBaseBlock, Link): @override diff --git a/edg/electronics_model/GroundPort.py b/edg/electronics_model/GroundPort.py index 1059d6900..78a48a243 100644 --- a/edg/electronics_model/GroundPort.py +++ b/edg/electronics_model/GroundPort.py @@ -6,7 +6,7 @@ from .PassivePort import PassiveAdapterGround from ..core import * -from .CircuitBlock import CircuitPortBridge, CircuitPortAdapter, CircuitLink, CircuitPort +from .CircuitBlock import CircuitPortBridge, CircuitPortAdapter, CircuitLink, CircuitPort, KicadImportablePortAdapter from .Units import Volt, Ohm if TYPE_CHECKING: @@ -91,7 +91,7 @@ def __init__(self) -> None: ) -class GroundAdapterAnalogSource(PortAdapter["AnalogSource"]): +class GroundAdapterAnalogSource(KicadImportablePortAdapter["AnalogSource"]): def __init__(self) -> None: from .AnalogPort import AnalogSource diff --git a/edg/electronics_model/PassivePort.py b/edg/electronics_model/PassivePort.py index 01fc02f48..ad5de68ec 100644 --- a/edg/electronics_model/PassivePort.py +++ b/edg/electronics_model/PassivePort.py @@ -5,7 +5,7 @@ from typing_extensions import TYPE_CHECKING from ..core import * -from .CircuitBlock import CircuitPort, CircuitLink, CircuitPortBridge, CircuitPortAdapter +from .CircuitBlock import CircuitPort, CircuitLink, CircuitPortBridge, CircuitPortAdapter, KicadImportablePortAdapter if TYPE_CHECKING: from .GroundPort import Ground @@ -169,7 +169,7 @@ def __init__( ) -class PassiveAdapterAnalogSource(PortAdapter["AnalogSource"]): +class PassiveAdapterAnalogSource(KicadImportablePortAdapter["AnalogSource"]): # TODO we can't use **kwargs b/c the init hook needs an initializer list def __init__( self, @@ -190,7 +190,7 @@ def __init__( self.connect(self.src, self.dst.net) -class PassiveAdapterAnalogSink(PortAdapter["AnalogSink"]): +class PassiveAdapterAnalogSink(KicadImportablePortAdapter["AnalogSink"]): # TODO we can't use **kwargs b/c the init hook needs an initializer list def __init__( self, diff --git a/edg/electronics_model/VoltagePorts.py b/edg/electronics_model/VoltagePorts.py index 27965e213..a0f5cbf04 100644 --- a/edg/electronics_model/VoltagePorts.py +++ b/edg/electronics_model/VoltagePorts.py @@ -5,7 +5,7 @@ from typing_extensions import override from ..core import * -from .CircuitBlock import CircuitPort, CircuitPortBridge, CircuitLink, CircuitPortAdapter +from .CircuitBlock import CircuitPort, CircuitPortBridge, CircuitLink, CircuitPortAdapter, KicadImportablePortAdapter from .GroundPort import GroundLink, GroundReference from .Units import Volt, Ohm, Amp @@ -255,7 +255,7 @@ def __init__(self) -> None: self.assign(self.src.current_draw, self.dst.link().current_drawn) # TODO might be an overestimate -class VoltageSinkAdapterAnalogSource(PortAdapter["AnalogSource"]): +class VoltageSinkAdapterAnalogSource(KicadImportablePortAdapter["AnalogSource"]): def __init__(self) -> None: from .AnalogPort import AnalogSource From f08175aba61745a35063e5d410878a2cc60080ef Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 4 Apr 2026 22:59:54 -0700 Subject: [PATCH 19/29] fix for mypy upgrade --- edg/core/Core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/core/Core.py b/edg/core/Core.py index 6eede21cb..9e901bda1 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -294,7 +294,7 @@ def _get_bases_of(cls, base_type: Type[BaseType]) -> Tuple[List[Type[BaseType]], superclasses order is not defined (but MRO in current practice). mypy currently does not allow passing in abstract types, so generally calls to this need type: ignore.""" - direct_bases: Set[Type[HasMetadata]] = set() + direct_bases: Set[Type[HasMetadata.BaseType]] = set() def process_direct_base(bcls: Type[HasMetadata.BaseType]) -> None: if not issubclass(bcls, base_type): From a6fcb202495d56da423702260cb43367495ed87c Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 4 Apr 2026 23:16:41 -0700 Subject: [PATCH 20/29] clean up ordering --- edg/electronics_model/AnalogPort.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/edg/electronics_model/AnalogPort.py b/edg/electronics_model/AnalogPort.py index 802faee9c..874b21ab7 100644 --- a/edg/electronics_model/AnalogPort.py +++ b/edg/electronics_model/AnalogPort.py @@ -6,7 +6,7 @@ from ..core import * from .PassivePort import HasPassivePort -from .CircuitBlock import CircuitPortAdapter, KicadImportablePortAdapter +from .CircuitBlock import KicadImportablePortAdapter from .GroundPort import GroundLink from .VoltagePorts import VoltageLink, VoltageSource @@ -148,7 +148,7 @@ def contents(self) -> None: self.assign(self.inner_link.impedance, self.outer_port.link().sink_impedance) -class AnalogSink(AnalogBase, HasPassivePort): +class AnalogSink(HasPassivePort, AnalogBase): bridge_type = AnalogSinkBridge @staticmethod @@ -226,7 +226,7 @@ def __init__(self) -> None: ) -class AnalogSource(AnalogBase, HasPassivePort): +class AnalogSource(HasPassivePort, AnalogBase): bridge_type = AnalogSourceBridge @staticmethod From 7b1c3cc05da07d684e507ccc042ef5d682ca8789 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 4 Apr 2026 23:25:25 -0700 Subject: [PATCH 21/29] Revert "clean up ordering" This reverts commit a6fcb202495d56da423702260cb43367495ed87c. --- edg/electronics_model/AnalogPort.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/edg/electronics_model/AnalogPort.py b/edg/electronics_model/AnalogPort.py index 874b21ab7..802faee9c 100644 --- a/edg/electronics_model/AnalogPort.py +++ b/edg/electronics_model/AnalogPort.py @@ -6,7 +6,7 @@ from ..core import * from .PassivePort import HasPassivePort -from .CircuitBlock import KicadImportablePortAdapter +from .CircuitBlock import CircuitPortAdapter, KicadImportablePortAdapter from .GroundPort import GroundLink from .VoltagePorts import VoltageLink, VoltageSource @@ -148,7 +148,7 @@ def contents(self) -> None: self.assign(self.inner_link.impedance, self.outer_port.link().sink_impedance) -class AnalogSink(HasPassivePort, AnalogBase): +class AnalogSink(AnalogBase, HasPassivePort): bridge_type = AnalogSinkBridge @staticmethod @@ -226,7 +226,7 @@ def __init__(self) -> None: ) -class AnalogSource(HasPassivePort, AnalogBase): +class AnalogSource(AnalogBase, HasPassivePort): bridge_type = AnalogSourceBridge @staticmethod From 7fba83ad39d0b56014a4979cdbc0d5f88872c615 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 5 Apr 2026 00:36:53 -0700 Subject: [PATCH 22/29] first batch of changes --- edg/abstract_parts/AbstractCapacitor.py | 6 ++++-- edg/abstract_parts/PassiveFilters.py | 16 +++++++--------- edg/parts/DacI2c_Mcp4728.py | 6 +++--- edg/parts/Speakers.py | 5 +++-- examples/UsbSourceMeasure/UsbSourceMeasure.net | 8 ++++---- .../UsbSourceMeasure/UsbSourceMeasure.svgpcb.js | 8 ++++---- 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/edg/abstract_parts/AbstractCapacitor.py b/edg/abstract_parts/AbstractCapacitor.py index 84ca3352a..bdf39aca9 100644 --- a/edg/abstract_parts/AbstractCapacitor.py +++ b/edg/abstract_parts/AbstractCapacitor.py @@ -403,10 +403,12 @@ def __init__(self, capacitance: RangeLike, *, exact_capacitance: BoolLike = Fals super().__init__() self.cap = self.Block(Capacitor(capacitance, voltage=RangeExpr(), exact_capacitance=exact_capacitance)) - self.gnd = self.Export(self.cap.neg.adapt_to(Ground()), [Common]) - self.io = self.Export(self.cap.pos.adapt_to(AnalogSink()), [InOut]) # ideal open port + self.gnd = self.Port(Ground.empty(), [Common]) + self.io = self.Port(AnalogSink(), [InOut]) # ideal open port self.assign(self.cap.voltage, self.io.link().voltage - self.gnd.link().voltage) + self.connect(self.cap.neg.adapt_to(Ground()), self.gnd) # TODO refactor #114 + self.connect(self.io.net, self.cap.pos) def connected( self, gnd: Optional[Port[GroundLink]] = None, io: Optional[Port[AnalogLink]] = None diff --git a/edg/abstract_parts/PassiveFilters.py b/edg/abstract_parts/PassiveFilters.py index b905bc97e..9337455c7 100644 --- a/edg/abstract_parts/PassiveFilters.py +++ b/edg/abstract_parts/PassiveFilters.py @@ -76,17 +76,15 @@ class AnalogLowPassRc(DigitalFilter, Block): def __init__(self, impedance: RangeLike, cutoff_freq: RangeLike): super().__init__() - self.input = self.Port(AnalogSink.empty(), [Input]) - self.output = self.Port(AnalogSource.empty(), [Output]) + self.input = self.Port(AnalogSink(current_draw=RangeExpr()), [Input]) + self.output = self.Port( + AnalogSource(voltage_out=self.input.link().voltage, signal_out=self.input.link().signal), [Output] + ) + self.assign(self.input.current_draw, self.output.link().current_drawn) self.rc = self.Block(LowPassRc(impedance=impedance, cutoff_freq=cutoff_freq, voltage=self.input.link().voltage)) - self.connect(self.input, self.rc.input.adapt_to(AnalogSink(current_draw=self.output.link().current_drawn))) - self.connect( - self.output, - self.rc.output.adapt_to( - AnalogSource(voltage_out=self.input.link().voltage, signal_out=self.input.link().signal) - ), - ) + self.connect(self.input.net, self.rc.input) + self.connect(self.output.net, self.rc.output) self.gnd = self.Export(self.rc.gnd.adapt_to(Ground()), [Common]) diff --git a/edg/parts/DacI2c_Mcp4728.py b/edg/parts/DacI2c_Mcp4728.py index 21fa92956..49f614c5b 100644 --- a/edg/parts/DacI2c_Mcp4728.py +++ b/edg/parts/DacI2c_Mcp4728.py @@ -109,6 +109,6 @@ def generate(self) -> None: self.out_cap = ElementDict[Capacitor]() for i, out_port in [(0, self.out0), (1, self.out1), (2, self.out2), (3, self.out3)]: if self.get(out_port.is_connected()): - self.out_cap[i] = out_cap = self.Block(Capacitor(0.1 * uFarad(tol=0.2), out_port.link().voltage)) - self.connect(out_cap.pos.adapt_to(AnalogSink()), out_port) - self.connect(out_cap.neg.adapt_to(Ground()), self.gnd) + self.out_cap[i] = self.Block(AnalogCapacitor(0.1 * uFarad(tol=0.2))).connected( + gnd=self.gnd, io=out_port + ) diff --git a/edg/parts/Speakers.py b/edg/parts/Speakers.py index 5b27312bc..4416caebe 100644 --- a/edg/parts/Speakers.py +++ b/edg/parts/Speakers.py @@ -16,8 +16,9 @@ class ConnectorSpeaker(Speaker): def __init__(self, impedance: RangeLike = 8 * Ohm(tol=0)): super().__init__() + self.input.init_from(SpeakerPort(AnalogSink(impedance=impedance))) self.conn = self.Block(PassiveConnector()) - self.connect(self.input.a, self.conn.pins.request("1").adapt_to(AnalogSink(impedance=impedance))) - self.connect(self.input.b, self.conn.pins.request("2").adapt_to(AnalogSink(impedance=impedance))) + self.connect(self.input.a.net, self.conn.pins.request("1")) + self.connect(self.input.b.net, self.conn.pins.request("2")) diff --git a/examples/UsbSourceMeasure/UsbSourceMeasure.net b/examples/UsbSourceMeasure/UsbSourceMeasure.net index d6849c3f2..1aae42987 100644 --- a/examples/UsbSourceMeasure/UsbSourceMeasure.net +++ b/examples/UsbSourceMeasure/UsbSourceMeasure.net @@ -3449,7 +3449,7 @@ (footprint "Capacitor_SMD:C_0603_1608Metric") (property (name "Sheetname") (value "dac")) (property (name "Sheetfile") (value "edg.parts.DacI2c_Mcp4728.Mcp4728")) - (property (name "edg_path") (value "dac.out_cap[0]")) + (property (name "edg_path") (value "dac.out_cap[0].cap")) (property (name "edg_short_path") (value "dac.out_cap[0]")) (property (name "edg_refdes") (value "C97")) (property (name "edg_part") (value "CC0603KRX7R9BB104 (YAGEO)")) @@ -3461,7 +3461,7 @@ (footprint "Capacitor_SMD:C_0603_1608Metric") (property (name "Sheetname") (value "dac")) (property (name "Sheetfile") (value "edg.parts.DacI2c_Mcp4728.Mcp4728")) - (property (name "edg_path") (value "dac.out_cap[1]")) + (property (name "edg_path") (value "dac.out_cap[1].cap")) (property (name "edg_short_path") (value "dac.out_cap[1]")) (property (name "edg_refdes") (value "C98")) (property (name "edg_part") (value "CC0603KRX7R9BB104 (YAGEO)")) @@ -3473,7 +3473,7 @@ (footprint "Capacitor_SMD:C_0603_1608Metric") (property (name "Sheetname") (value "dac")) (property (name "Sheetfile") (value "edg.parts.DacI2c_Mcp4728.Mcp4728")) - (property (name "edg_path") (value "dac.out_cap[2]")) + (property (name "edg_path") (value "dac.out_cap[2].cap")) (property (name "edg_short_path") (value "dac.out_cap[2]")) (property (name "edg_refdes") (value "C99")) (property (name "edg_part") (value "CC0603KRX7R9BB104 (YAGEO)")) @@ -3485,7 +3485,7 @@ (footprint "Capacitor_SMD:C_0603_1608Metric") (property (name "Sheetname") (value "dac")) (property (name "Sheetfile") (value "edg.parts.DacI2c_Mcp4728.Mcp4728")) - (property (name "edg_path") (value "dac.out_cap[3]")) + (property (name "edg_path") (value "dac.out_cap[3].cap")) (property (name "edg_short_path") (value "dac.out_cap[3]")) (property (name "edg_refdes") (value "C100")) (property (name "edg_part") (value "CC0603KRX7R9BB104 (YAGEO)")) diff --git a/examples/UsbSourceMeasure/UsbSourceMeasure.svgpcb.js b/examples/UsbSourceMeasure/UsbSourceMeasure.svgpcb.js index be7eb180d..b1197fc20 100644 --- a/examples/UsbSourceMeasure/UsbSourceMeasure.svgpcb.js +++ b/examples/UsbSourceMeasure/UsbSourceMeasure.svgpcb.js @@ -1435,22 +1435,22 @@ const C96 = board.add(C_0805_2012Metric, { translate: pt(1.539, 4.033), rotate: 0, id: 'C96' }) -// dac.out_cap[0] +// dac.out_cap[0].cap const C97 = board.add(C_0603_1608Metric, { translate: pt(1.396, 4.201), rotate: 0, id: 'C97' }) -// dac.out_cap[1] +// dac.out_cap[1].cap const C98 = board.add(C_0603_1608Metric, { translate: pt(1.552, 4.201), rotate: 0, id: 'C98' }) -// dac.out_cap[2] +// dac.out_cap[2].cap const C99 = board.add(C_0603_1608Metric, { translate: pt(1.240, 4.298), rotate: 0, id: 'C99' }) -// dac.out_cap[3] +// dac.out_cap[3].cap const C100 = board.add(C_0603_1608Metric, { translate: pt(1.396, 4.298), rotate: 0, id: 'C100' From 9b8bc5417cb14dc54ccbd60071baba2c7acad9a3 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 5 Apr 2026 01:03:23 -0700 Subject: [PATCH 23/29] cleaning adapt_to --- edg/electronics_model/VoltagePorts.py | 2 +- edg/parts/BoostConverter_TexasInstruments.py | 13 +++++-------- edg/parts/DacI2c_Mcp4728.py | 2 +- edg/parts/ResistiveSensor.py | 15 +++++++++------ edg/parts/SpeakerDriver_Analog.py | 8 ++++---- examples/RobotCrawler/RobotCrawler.net | 6 +++--- examples/RobotCrawler/RobotCrawler.svgpcb.js | 6 +++--- examples/UsbSourceMeasure/UsbSourceMeasure.net | 12 ++++++------ .../UsbSourceMeasure/UsbSourceMeasure.svgpcb.js | 12 ++++++------ examples/test_usb_source_measure.py | 2 +- 10 files changed, 39 insertions(+), 39 deletions(-) diff --git a/edg/electronics_model/VoltagePorts.py b/edg/electronics_model/VoltagePorts.py index a0f5cbf04..081644db5 100644 --- a/edg/electronics_model/VoltagePorts.py +++ b/edg/electronics_model/VoltagePorts.py @@ -190,7 +190,7 @@ class VoltageBase(CircuitPort[VoltageLink]): # TODO: support isolation domains and offset grounds # these are here (instead of in VoltageSource) since the port may be on the other side of a bridge - def as_ground(self, current_draw: RangeLike) -> GroundReference: + def as_ground(self, current_draw: RangeLike = RangeExpr.ZERO) -> GroundReference: """Adapts this port to a ground. Current draw is the current drawn from this port, and is required since ground does not model current draw. """ diff --git a/edg/parts/BoostConverter_TexasInstruments.py b/edg/parts/BoostConverter_TexasInstruments.py index 1be48d352..704287c07 100644 --- a/edg/parts/BoostConverter_TexasInstruments.py +++ b/edg/parts/BoostConverter_TexasInstruments.py @@ -74,9 +74,9 @@ def contents(self) -> None: self.connect(self.fb.output, self.ic.fb) # TODO 10pF is the datasheet-suggested starting, point, but equation also available - self.cff = self.Block(Capacitor(10 * pFarad(tol=0.2), voltage=self.pwr_in.link().voltage)) - self.connect(self.cff.pos.adapt_to(VoltageSink()), self.pwr_out) - self.connect(self.cff.neg.adapt_to(AnalogSink()), self.ic.fb) + self.cff = self.Block(AnalogCapacitor(10 * pFarad(tol=0.2))).connected( + gnd=self.pwr_out.as_ground(), io=self.ic.fb + ) # power path calculation here - we don't use BoostConverterPowerPath since this IC operates in DCM # and has different component sizing guidelines @@ -261,13 +261,10 @@ def contents(self) -> None: self.connect(self.power_path.switch, self.ic.sw) self.cf = self.Block( - Capacitor( # arbitrary target tolerance for zero location for capacitance flexibility + AnalogCapacitor( # arbitrary target tolerance for zero location for capacitance flexibility capacitance=(1 / (8000 * Ohm(tol=0.35))).shrink_multiply(1 / (2 * math.pi * self.fb.actual_rtop)), - voltage=self.pwr_out.voltage_out, ) - ) - self.connect(self.cf.neg.adapt_to(AnalogSink()), self.ic.fb) - self.connect(self.cf.pos.adapt_to(VoltageSink()), self.pwr_out) + ).connected(gnd=self.pwr_out.as_ground(), io=self.ic.fb) self.rect = self.Block( Diode( diff --git a/edg/parts/DacI2c_Mcp4728.py b/edg/parts/DacI2c_Mcp4728.py index 49f614c5b..d2c109b58 100644 --- a/edg/parts/DacI2c_Mcp4728.py +++ b/edg/parts/DacI2c_Mcp4728.py @@ -106,7 +106,7 @@ def generate(self) -> None: self.vdd_cap[1] = self.Block(DecouplingCapacitor(10 * uFarad(tol=0.2))).connected(self.gnd, self.pwr) if self.get(self.output_caps): - self.out_cap = ElementDict[Capacitor]() + self.out_cap = ElementDict[AnalogCapacitor]() for i, out_port in [(0, self.out0), (1, self.out1), (2, self.out2), (3, self.out3)]: if self.get(out_port.is_connected()): self.out_cap[i] = self.Block(AnalogCapacitor(0.1 * uFarad(tol=0.2))).connected( diff --git a/edg/parts/ResistiveSensor.py b/edg/parts/ResistiveSensor.py index a32234c1c..7def15ee5 100644 --- a/edg/parts/ResistiveSensor.py +++ b/edg/parts/ResistiveSensor.py @@ -15,7 +15,9 @@ def __init__(self, resistance_range: RangeLike, fixed_resistance: RangeLike) -> self.fixed_resistance = self.ArgParameter(fixed_resistance) self.input = self.Port(VoltageSink.empty(), [Power]) - self.output = self.Port(AnalogSource.empty(), [Output]) + self.output = self.Port( + AnalogSource(voltage_out=RangeExpr(), signal_out=RangeExpr(), impedance=RangeExpr()), [Output] + ) self.gnd = self.Port(Ground.empty(), [Common]) # TODO deduplicate with ResistiveDivider class @@ -31,12 +33,13 @@ def contents(self) -> None: output_voltage = ResistiveDivider.divider_output( self.input.link().voltage, self.gnd.link().voltage, self.actual_ratio ) + self.assign(self.output.voltage_out, output_voltage) + self.assign(self.output.signal_out, output_voltage) + self.assign(self.output.impedance, self.actual_impedance) self.connect( - self.output, - self.top.b.adapt_to( - AnalogSource(voltage_out=output_voltage, signal_out=output_voltage, impedance=self.actual_impedance) - ), - self.bot.pins.request("1").adapt_to(AnalogSink()), + self.output.net, + self.top.b, + self.bot.pins.request("1"), ) self.connect(self.gnd, self.bot.pins.request("2").adapt_to(Ground())) diff --git a/edg/parts/SpeakerDriver_Analog.py b/edg/parts/SpeakerDriver_Analog.py index cfbdead4b..477d3ae31 100644 --- a/edg/parts/SpeakerDriver_Analog.py +++ b/edg/parts/SpeakerDriver_Analog.py @@ -57,7 +57,7 @@ def __init__(self) -> None: self.pwr = self.Export(self.ic.pwr, [Power]) self.gnd = self.Export(self.ic.gnd, [Common]) - self.sig = self.Port(AnalogSink.empty(), [Input]) + self.sig = self.Port(AnalogSink(), [Input]) self.spk = self.Port(SpeakerDriverPort(AnalogSource.empty()), [Output]) @override @@ -86,7 +86,7 @@ def contents(self) -> None: ) self.sig_res = self.Block(Resistor(resistance=20 * kOhm(tol=0.2))) self.fb_res = self.Block(Resistor(resistance=20 * kOhm(tol=0.2))) - self.connect(self.sig, self.sig_cap.neg.adapt_to(AnalogSink())) + self.connect(self.sig.net, self.sig_cap.neg) self.connect(self.sig_cap.pos, self.sig_res.a) self.connect(self.sig_res.b, self.fb_res.a, self.ic.inm) self.connect(self.spk.a, self.ic.vo1, self.fb_res.b.adapt_to(AnalogSink())) @@ -160,7 +160,7 @@ def __init__(self, gain: RangeLike = Range.from_tolerance(20, 0.2)): self.pwr = self.Export(self.ic.pwr, [Power]) self.gnd = self.Export(self.ic.gnd, [Common]) - self.sig = self.Port(AnalogSink.empty(), [Input]) + self.sig = self.Port(AnalogSink(), [Input]) self.spk = self.Port(SpeakerDriverPort(AnalogSource.empty()), [Output]) self.gain = self.ArgParameter(gain) @@ -198,7 +198,7 @@ def contents(self) -> None: voltage=self.sig.link().voltage, ) ) - self.connect(self.sig, self.inp_cap.neg.adapt_to(AnalogSink())) + self.connect(self.sig.net, self.inp_cap.neg) self.connect(self.inp_cap.pos, self.inp_res.a) self.connect(self.inp_res.b.adapt_to(AnalogSource()), self.ic.inp) diff --git a/examples/RobotCrawler/RobotCrawler.net b/examples/RobotCrawler/RobotCrawler.net index f43d27f11..3a16ad653 100644 --- a/examples/RobotCrawler/RobotCrawler.net +++ b/examples/RobotCrawler/RobotCrawler.net @@ -365,7 +365,7 @@ (footprint "Capacitor_SMD:C_0603_1608Metric") (property (name "Sheetname") (value "reg_14v")) (property (name "Sheetfile") (value "edg.parts.BoostConverter_TexasInstruments.Tps61040")) - (property (name "edg_path") (value "reg_14v.cff")) + (property (name "edg_path") (value "reg_14v.cff.cap")) (property (name "edg_short_path") (value "reg_14v.cff")) (property (name "edg_refdes") (value "RC5")) (property (name "edg_part") (value "CL10C100JB8NNNC (Samsung Electro-Mechanics)")) @@ -1694,7 +1694,7 @@ (node (ref RR14) (pin 1))) (net (code 4) (name "Rv14") (node (ref RR1) (pin 1)) - (node (ref RC5) (pin 1)) + (node (ref RC5) (pin 2)) (node (ref RD1) (pin 1)) (node (ref RC7) (pin 1)) (node (ref RTP4) (pin 1)) @@ -1824,7 +1824,7 @@ (node (ref RU3) (pin 3)) (node (ref RR1) (pin 2)) (node (ref RR2) (pin 1)) - (node (ref RC5) (pin 2))) + (node (ref RC5) (pin 1))) (net (code 41) (name "Rmcu.program_uart_node.a_tx") (node (ref RU6) (pin 37)) (node (ref RJ16) (pin 3))) diff --git a/examples/RobotCrawler/RobotCrawler.svgpcb.js b/examples/RobotCrawler/RobotCrawler.svgpcb.js index fc541e07f..5b34657ed 100644 --- a/examples/RobotCrawler/RobotCrawler.svgpcb.js +++ b/examples/RobotCrawler/RobotCrawler.svgpcb.js @@ -150,7 +150,7 @@ const RR2 = board.add(R_0603_1608Metric, { translate: pt(2.604, 2.906), rotate: 0, id: 'RR2' }) -// reg_14v.cff +// reg_14v.cff.cap const RC5 = board.add(C_0603_1608Metric, { translate: pt(2.760, 2.906), rotate: 0, id: 'RC5' @@ -615,7 +615,7 @@ board.setNetlist([ {name: "Rvbatt", pads: [["RJ1", "2"], ["RJ2", "2"], ["RJ3", "2"], ["RJ4", "2"], ["RJ5", "2"], ["RJ6", "2"], ["RJ7", "2"], ["RJ8", "2"], ["RJ9", "2"], ["RJ10", "2"], ["RJ11", "2"], ["RJ12", "2"], ["RJ13", "2"], ["RJ14", "2"], ["RJ15", "2"], ["RTP1", "1"], ["RU2", "2"], ["RC3", "1"], ["RU3", "4"], ["RU3", "5"], ["RL1", "1"], ["RC6", "1"], ["RU4", "3"], ["RC8", "1"], ["RU5", "3"], ["RC10", "1"], ["RD10", "2"], ["RC40", "1"], ["RD11", "2"], ["RC41", "1"], ["RD12", "2"], ["RC42", "1"], ["RD13", "2"], ["RC43", "1"], ["RD14", "2"], ["RC44", "1"], ["RD15", "2"], ["RC45", "1"], ["RD16", "2"], ["RC46", "1"], ["RD17", "2"], ["RC47", "1"], ["RD18", "2"], ["RC48", "1"], ["RD19", "2"], ["RC49", "1"]]}, {name: "Rgnd", pads: [["RJ1", "1"], ["RJ2", "3"], ["RJ3", "3"], ["RJ4", "3"], ["RJ5", "3"], ["RJ6", "3"], ["RJ7", "3"], ["RJ8", "3"], ["RJ9", "3"], ["RJ10", "3"], ["RJ11", "3"], ["RJ12", "3"], ["RJ13", "3"], ["RU1", "1"], ["RU1", "2"], ["RU1", "3"], ["RU1", "6"], ["RU1", "7"], ["RC1", "2"], ["RC2", "2"], ["RJ14", "3"], ["RJ15", "3"], ["RTP2", "1"], ["RU2", "1"], ["RC3", "2"], ["RC4", "2"], ["RU3", "2"], ["RR2", "2"], ["RC6", "2"], ["RC7", "2"], ["RU4", "1"], ["RC8", "2"], ["RC9", "2"], ["RU5", "1"], ["RC10", "2"], ["RC11", "2"], ["RU6", "1"], ["RU6", "40"], ["RU6", "41"], ["RC12", "2"], ["RC13", "2"], ["RJ16", "5"], ["RC14", "2"], ["RJ17", "5"], ["RU7", "23"], ["RU7", "35"], ["RU7", "44"], ["RU7", "47"], ["RU7", "8"], ["RC15", "2"], ["RC16", "2"], ["RC17", "2"], ["RC18", "2"], ["RC19", "2"], ["RC20", "2"], ["RJ18", "5"], ["RU8", "19"], ["RU8", "57"], ["RC21", "2"], ["RC22", "2"], ["RC23", "2"], ["RC24", "2"], ["RC25", "2"], ["RC26", "2"], ["RC27", "2"], ["RC28", "2"], ["RU9", "4"], ["RC29", "2"], ["RC30", "2"], ["RC31", "2"], ["RC32", "2"], ["RR6", "2"], ["RR7", "2"], ["RR8", "2"], ["RR9", "2"], ["RR10", "2"], ["RR11", "2"], ["RJ19", "1"], ["RJ19", "10"], ["RJ19", "11"], ["RJ19", "12"], ["RJ19", "14"], ["RJ19", "15"], ["RJ19", "16"], ["RJ19", "21"], ["RJ19", "22"], ["RJ19", "23"], ["RJ19", "24"], ["RJ19", "25"], ["RJ19", "26"], ["RJ19", "31"], ["RJ19", "7"], ["RR12", "2"], ["RC33", "2"], ["RC34", "2"], ["RC35", "2"], ["RC36", "2"], ["RD9", "1"], ["RJ20", "10"], ["RJ20", "17"], ["RJ20", "23"], ["RC37", "2"], ["RC38", "2"], ["RC39", "2"], ["RD10", "4"], ["RC40", "2"], ["RD11", "4"], ["RC41", "2"], ["RD12", "4"], ["RC42", "2"], ["RD13", "4"], ["RC43", "2"], ["RD14", "4"], ["RC44", "2"], ["RD15", "4"], ["RC45", "2"], ["RD16", "4"], ["RC46", "2"], ["RD17", "4"], ["RC47", "2"], ["RD18", "4"], ["RC48", "2"], ["RD19", "4"], ["RC49", "2"]]}, {name: "Rv3v3", pads: [["RU1", "12"], ["RU1", "5"], ["RU1", "8"], ["RC1", "1"], ["RC2", "1"], ["RU2", "3"], ["RC4", "1"], ["RTP3", "1"], ["RU6", "2"], ["RC12", "1"], ["RC13", "1"], ["RJ16", "1"], ["RR3", "1"], ["RJ17", "1"], ["RU7", "1"], ["RU7", "24"], ["RU7", "36"], ["RU7", "48"], ["RU7", "9"], ["RC15", "1"], ["RC16", "1"], ["RC17", "1"], ["RC18", "1"], ["RC19", "1"], ["RC20", "1"], ["RJ18", "1"], ["RU8", "1"], ["RU8", "10"], ["RU8", "22"], ["RU8", "33"], ["RU8", "42"], ["RU8", "43"], ["RU8", "44"], ["RU8", "48"], ["RU8", "49"], ["RC21", "1"], ["RC22", "1"], ["RC23", "1"], ["RC24", "1"], ["RC25", "1"], ["RC26", "1"], ["RC27", "1"], ["RC28", "1"], ["RU9", "8"], ["RC29", "1"], ["RR4", "1"], ["RR5", "1"], ["RJ19", "13"], ["RJ19", "17"], ["RJ19", "8"], ["RC35", "1"], ["RJ20", "14"], ["RC37", "1"], ["RR14", "1"]]}, - {name: "Rv14", pads: [["RR1", "1"], ["RC5", "1"], ["RD1", "1"], ["RC7", "1"], ["RTP4", "1"], ["RJ19", "27"], ["RJ19", "5"], ["RC36", "1"]]}, + {name: "Rv14", pads: [["RR1", "1"], ["RC5", "2"], ["RD1", "1"], ["RC7", "1"], ["RTP4", "1"], ["RJ19", "27"], ["RJ19", "5"], ["RC36", "1"]]}, {name: "Rv2v5", pads: [["RU4", "2"], ["RC9", "1"], ["RJ20", "21"]]}, {name: "Rv1v2", pads: [["RU5", "2"], ["RC11", "1"], ["RJ20", "15"]]}, {name: "Ri2c_chain_0.scl", pads: [["RU1", "13"], ["RU6", "10"], ["RU7", "21"], ["RU8", "37"], ["RR4", "2"], ["RTP5", "1"], ["RJ19", "18"], ["RJ20", "20"]]}, @@ -651,7 +651,7 @@ board.setNetlist([ {name: "Rservos_cam[1].pwm", pads: [["RJ15", "1"], ["RU7", "29"]]}, {name: "Rservos_cam[1].fb", pads: [["RJ15", "4"], ["RU7", "16"]]}, {name: "Rreg_14v.ic.sw", pads: [["RU3", "1"], ["RL1", "2"], ["RD1", "2"]]}, - {name: "Rreg_14v.ic.fb", pads: [["RU3", "3"], ["RR1", "2"], ["RR2", "1"], ["RC5", "2"]]}, + {name: "Rreg_14v.ic.fb", pads: [["RU3", "3"], ["RR1", "2"], ["RR2", "1"], ["RC5", "1"]]}, {name: "Rmcu.program_uart_node.a_tx", pads: [["RU6", "37"], ["RJ16", "3"]]}, {name: "Rmcu.program_uart_node.b_tx", pads: [["RU6", "36"], ["RJ16", "4"]]}, {name: "Rmcu.program_en_node", pads: [["RU6", "3"], ["RJ16", "6"], ["RR3", "2"], ["RC14", "1"]]}, diff --git a/examples/UsbSourceMeasure/UsbSourceMeasure.net b/examples/UsbSourceMeasure/UsbSourceMeasure.net index 1aae42987..0e948ee9a 100644 --- a/examples/UsbSourceMeasure/UsbSourceMeasure.net +++ b/examples/UsbSourceMeasure/UsbSourceMeasure.net @@ -569,7 +569,7 @@ (footprint "Capacitor_SMD:C_0603_1608Metric") (property (name "Sheetname") (value "reg_v12")) (property (name "Sheetfile") (value "edg.parts.BoostConverter_TexasInstruments.Lm2733")) - (property (name "edg_path") (value "reg_v12.cf")) + (property (name "edg_path") (value "reg_v12.cf.cap")) (property (name "edg_short_path") (value "reg_v12.cf")) (property (name "edg_refdes") (value "C16")) (property (name "edg_part") (value "CL10B221KB8NNNC (Samsung Electro-Mechanics)")) @@ -1241,7 +1241,7 @@ (footprint "Capacitor_SMD:C_0603_1608Metric") (property (name "Sheetname") (value "reg_vcontrol")) (property (name "Sheetfile") (value "edg.parts.BoostConverter_TexasInstruments.Lm2733")) - (property (name "edg_path") (value "reg_vcontrol.cf")) + (property (name "edg_path") (value "reg_vcontrol.cf.cap")) (property (name "edg_short_path") (value "reg_vcontrol.cf")) (property (name "edg_refdes") (value "C43")) (property (name "edg_part") (value "CL10C101JB8NNNC (Samsung Electro-Mechanics)")) @@ -4181,7 +4181,7 @@ (net (code 6) (name "v12") (node (ref R10) (pin 1)) (node (ref C15) (pin 1)) - (node (ref C16) (pin 1)) + (node (ref C16) (pin 2)) (node (ref D3) (pin 1)) (node (ref J5) (pin 2)) (node (ref C75) (pin 1)) @@ -4253,7 +4253,7 @@ (net (code 12) (name "vcontrol") (node (ref R20) (pin 1)) (node (ref C42) (pin 1)) - (node (ref C43) (pin 1)) + (node (ref C43) (pin 2)) (node (ref D7) (pin 1)) (node (ref TP8) (pin 1)) (node (ref U15) (pin 5)) @@ -4394,7 +4394,7 @@ (node (ref U4) (pin 3)) (node (ref R10) (pin 2)) (node (ref R11) (pin 1)) - (node (ref C16) (pin 2))) + (node (ref C16) (pin 1))) (net (code 36) (name "conv.reset") (node (ref U6) (pin 3)) (node (ref U7) (pin 3)) @@ -4473,7 +4473,7 @@ (node (ref U10) (pin 3)) (node (ref R20) (pin 2)) (node (ref R21) (pin 1)) - (node (ref C43) (pin 2))) + (node (ref C43) (pin 1))) (net (code 55) (name "filt_vcontroln.pwr_out") (node (ref FB2) (pin 2)) (node (ref U11) (pin 4)) diff --git a/examples/UsbSourceMeasure/UsbSourceMeasure.svgpcb.js b/examples/UsbSourceMeasure/UsbSourceMeasure.svgpcb.js index b1197fc20..6d8bc77b4 100644 --- a/examples/UsbSourceMeasure/UsbSourceMeasure.svgpcb.js +++ b/examples/UsbSourceMeasure/UsbSourceMeasure.svgpcb.js @@ -235,7 +235,7 @@ const C15 = board.add(C_0603_1608Metric, { translate: pt(6.213, 3.701), rotate: 0, id: 'C15' }) -// reg_v12.cf +// reg_v12.cf.cap const C16 = board.add(C_0603_1608Metric, { translate: pt(6.369, 3.701), rotate: 0, id: 'C16' @@ -515,7 +515,7 @@ const C42 = board.add(C_0805_2012Metric, { translate: pt(0.268, 4.033), rotate: 0, id: 'C42' }) -// reg_vcontrol.cf +// reg_vcontrol.cf.cap const C43 = board.add(C_0603_1608Metric, { translate: pt(0.370, 4.311), rotate: 0, id: 'C43' @@ -1607,13 +1607,13 @@ board.setNetlist([ {name: "vusb_ramp", pads: [["Q1", "5"], ["C2", "1"], ["C4", "1"], ["TP2", "1"], ["U2", "3"], ["C5", "1"], ["C7", "1"], ["R6", "1"], ["U5", "1"], ["R12", "1"]]}, {name: "v5", pads: [["R4", "1"], ["L1", "2"], ["C8", "1"], ["TP3", "1"], ["U4", "4"], ["U4", "5"], ["L3", "1"], ["C14", "1"], ["U6", "4"], ["C33", "1"], ["D4", "2"], ["U7", "4"], ["C35", "1"], ["D5", "2"], ["U8", "1"], ["U8", "3"], ["C37", "1"], ["U9", "1"], ["C39", "1"], ["U10", "4"], ["U10", "5"], ["L5", "1"], ["C41", "1"], ["D15", "4"], ["C86", "1"], ["D16", "4"], ["C87", "1"], ["D17", "4"], ["C88", "1"], ["D18", "4"], ["C89", "1"], ["D19", "4"], ["C90", "1"], ["D20", "4"], ["C91", "1"], ["C92", "1"], ["R87", "1"], ["Q14", "2"], ["U36", "4"], ["U36", "7"], ["U36", "8"], ["C93", "1"], ["C94", "1"]]}, {name: "v3v3", pads: [["U1", "4"], ["C1", "1"], ["R7", "1"], ["L2", "2"], ["C13", "1"], ["D2", "1"], ["TP4", "1"], ["U5", "4"], ["U5", "8"], ["C17", "1"], ["U25", "3"], ["U25", "4"], ["C67", "1"], ["C68", "1"], ["U26", "2"], ["C69", "1"], ["C70", "1"], ["J4", "1"], ["R66", "1"], ["D14", "2"], ["R68", "1"], ["R69", "1"], ["J5", "19"], ["J5", "20"], ["C73", "1"], ["C74", "1"], ["R71", "1"], ["U29", "16"], ["C78", "1"], ["U30", "5"], ["C81", "1"], ["R75", "1"], ["U31", "8"], ["C82", "1"], ["R80", "1"], ["U32", "5"], ["C83", "1"], ["U33", "4"], ["U33", "5"], ["C84", "1"], ["U34", "16"], ["U34", "2"], ["C85", "1"], ["R83", "1"], ["R84", "1"], ["J6", "2"], ["R89", "1"]]}, - {name: "v12", pads: [["R10", "1"], ["C15", "1"], ["C16", "1"], ["D3", "1"], ["J5", "2"], ["C75", "1"], ["C76", "1"]]}, + {name: "v12", pads: [["R10", "1"], ["C15", "1"], ["C16", "2"], ["D3", "1"], ["J5", "2"], ["C75", "1"], ["C76", "1"]]}, {name: "vconvin", pads: [["U5", "2"], ["R12", "2"], ["C18", "1"], ["C19", "1"], ["C20", "1"], ["C21", "1"], ["C22", "1"], ["C23", "1"], ["C24", "1"], ["Q4", "5"]]}, {name: "vconv", pads: [["C25", "1"], ["C26", "1"], ["C27", "1"], ["C28", "1"], ["C29", "1"], ["C30", "1"], ["C31", "1"], ["C32", "1"], ["Q6", "5"], ["D6", "1"], ["TP5", "1"], ["Q7", "2"], ["C56", "1"], ["C57", "1"], ["C58", "1"], ["R77", "1"], ["R81", "1"]]}, {name: "vanalog", pads: [["U8", "5"], ["C38", "1"], ["TP6", "1"], ["FB2", "1"], ["U12", "5"], ["C49", "1"], ["U13", "5"], ["C50", "1"], ["U14", "5"], ["C51", "1"], ["R88", "1"], ["U40", "8"], ["C110", "1"], ["U41", "8"], ["C111", "1"], ["U42", "8"], ["C112", "1"]]}, {name: "vref", pads: [["U9", "2"], ["TP7", "1"], ["R17", "1"], ["FB3", "1"], ["U38", "4"], ["C105", "1"]]}, {name: "vcenter", pads: [["R19", "2"], ["C40", "1"], ["R25", "1"], ["R38", "2"], ["U23", "6"], ["U24", "6"], ["J10", "1"], ["R90", "1"], ["U42", "3"]]}, - {name: "vcontrol", pads: [["R20", "1"], ["C42", "1"], ["C43", "1"], ["D7", "1"], ["TP8", "1"], ["U15", "5"], ["C54", "1"], ["U16", "5"], ["C55", "1"], ["U17", "5"], ["C60", "1"], ["U19", "5"], ["C61", "1"], ["U21", "5"], ["C62", "1"], ["U23", "8"], ["C64", "1"], ["U24", "8"], ["C65", "1"], ["U39", "8"], ["C109", "1"]]}, + {name: "vcontrol", pads: [["R20", "1"], ["C42", "1"], ["C43", "2"], ["D7", "1"], ["TP8", "1"], ["U15", "5"], ["C54", "1"], ["U16", "5"], ["C55", "1"], ["U17", "5"], ["C60", "1"], ["U19", "5"], ["C61", "1"], ["U21", "5"], ["C62", "1"], ["U23", "8"], ["C64", "1"], ["U24", "8"], ["C65", "1"], ["U39", "8"], ["C109", "1"]]}, {name: "vcontroln", pads: [["U11", "2"], ["C45", "1"], ["TP9", "1"], ["U15", "2"], ["C54", "2"], ["U16", "2"], ["C55", "2"], ["U23", "5"], ["C64", "2"], ["U24", "5"], ["C65", "2"], ["U39", "4"], ["C109", "2"]]}, {name: "usb_chain_0.d_P", pads: [["J1", "A6"], ["J1", "B6"], ["U26", "14"], ["U27", "2"]]}, {name: "usb_chain_0.d_N", pads: [["J1", "A7"], ["J1", "B7"], ["U26", "13"], ["U27", "1"]]}, @@ -1636,7 +1636,7 @@ board.setNetlist([ {name: "reg_3v3.ic.boot", pads: [["U3", "6"], ["C10", "1"]]}, {name: "reg_3v3.ic.en", pads: [["U3", "5"], ["R9", "2"]]}, {name: "reg_v12.ic.sw", pads: [["U4", "1"], ["L3", "2"], ["D3", "2"]]}, - {name: "reg_v12.ic.fb", pads: [["U4", "3"], ["R10", "2"], ["R11", "1"], ["C16", "2"]]}, + {name: "reg_v12.ic.fb", pads: [["U4", "3"], ["R10", "2"], ["R11", "1"], ["C16", "1"]]}, {name: "conv.reset", pads: [["U6", "3"], ["U7", "3"], ["U29", "7"], ["U31", "6"], ["U31", "7"]]}, {name: "conv.buck_pwm", pads: [["U6", "2"], ["R73", "2"], ["C79", "1"]]}, {name: "conv.boost_pwm", pads: [["U7", "2"], ["R74", "2"], ["C80", "1"]]}, @@ -1655,7 +1655,7 @@ board.setNetlist([ {name: "ref_div.output", pads: [["R17", "2"], ["R18", "1"], ["U40", "3"]]}, {name: "ref_buf.output", pads: [["R19", "1"], ["U40", "1"], ["U40", "2"]]}, {name: "reg_vcontrol.ic.sw", pads: [["U10", "1"], ["L5", "2"], ["D7", "2"]]}, - {name: "reg_vcontrol.ic.fb", pads: [["U10", "3"], ["R20", "2"], ["R21", "1"], ["C43", "2"]]}, + {name: "reg_vcontrol.ic.fb", pads: [["U10", "3"], ["R20", "2"], ["R21", "1"], ["C43", "1"]]}, {name: "filt_vcontroln.pwr_out", pads: [["FB2", "2"], ["U11", "4"], ["U11", "5"]]}, {name: "reg_vcontroln.ic.capn", pads: [["U11", "3"], ["C44", "2"]]}, {name: "reg_vcontroln.ic.capp", pads: [["U11", "6"], ["C44", "1"]]}, diff --git a/examples/test_usb_source_measure.py b/examples/test_usb_source_measure.py index a3e2062c1..20231bbf6 100644 --- a/examples/test_usb_source_measure.py +++ b/examples/test_usb_source_measure.py @@ -1032,7 +1032,7 @@ def refinements(self) -> Refinements: (["conv", "power_path", "out_cap", "cap", "require_basic_part"], False), (["control", "driver", "cap_in2", "cap", "voltage_rating_derating"], 0.9), (["control", "driver", "cap_in3", "cap", "voltage_rating_derating"], 0.9), - (["reg_vcontrol", "cf", "voltage_rating_derating"], 0.85), + (["reg_vcontrol", "cf", "cap", "voltage_rating_derating"], 0.85), (["reg_vcontrol", "power_path", "out_cap", "cap", "exact_capacitance"], False), (["reg_vcontrol", "power_path", "out_cap", "cap", "voltage_rating_derating"], 0.85), ( From cce9138652716c318471055018100fe063ab3325 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 5 Apr 2026 01:35:23 -0700 Subject: [PATCH 24/29] cleanup --- edg/abstract_parts/AbstractResistor.py | 27 +++++----- edg/abstract_parts/AbstractTestPoint.py | 7 +-- edg/abstract_parts/PassiveFilters.py | 71 ++++++++++--------------- examples/test_bldc_controller.py | 6 +-- 4 files changed, 47 insertions(+), 64 deletions(-) diff --git a/edg/abstract_parts/AbstractResistor.py b/edg/abstract_parts/AbstractResistor.py index 264ab95a5..249ce3a82 100644 --- a/edg/abstract_parts/AbstractResistor.py +++ b/edg/abstract_parts/AbstractResistor.py @@ -392,14 +392,21 @@ def __init__( ): super().__init__() - self.signal_in = self.Port(AnalogSink.empty(), [Input]) - self.signal_out = self.Port(AnalogSource.empty(), [Output]) - self.clamp_target = self.ArgParameter(clamp_target) self.clamp_current = self.ArgParameter(clamp_current) self.protection_voltage = self.ArgParameter(protection_voltage) self.zero_out = self.ArgParameter(zero_out) + self.signal_in = self.Port(AnalogSink(), [Input]) + self.signal_out = self.Port( + AnalogSource( + voltage_out=self.signal_in.link().voltage.intersect(self.clamp_target), + signal_out=self.signal_in.link().signal, + impedance=RangeExpr(), + ), + [Output], + ) + @override def contents(self) -> None: super().contents() @@ -415,17 +422,9 @@ def contents(self) -> None: ) ) ) - self.connect(self.res.a.adapt_to(AnalogSink()), self.signal_in) - self.connect( - self.res.b.adapt_to( - AnalogSource( - voltage_out=self.signal_in.link().voltage.intersect(self.clamp_target), - signal_out=self.signal_in.link().signal, - impedance=self.signal_in.link().source_impedance + self.res.actual_resistance, - ) - ), - self.signal_out, - ) + self.connect(self.res.a, self.signal_in.net) + self.connect(self.res.b, self.signal_out.net) + self.assign(self.signal_out.impedance, self.signal_in.link().source_impedance + self.res.actual_resistance) @override def symbol_pinning(self, symbol_name: str) -> Dict[str, Port]: diff --git a/edg/abstract_parts/AbstractTestPoint.py b/edg/abstract_parts/AbstractTestPoint.py index 7776443fd..e3253c212 100644 --- a/edg/abstract_parts/AbstractTestPoint.py +++ b/edg/abstract_parts/AbstractTestPoint.py @@ -120,8 +120,8 @@ class AnalogTestPoint(BaseTypedTestPoint, Block): def __init__(self, *args: Any) -> None: super().__init__(*args) - self.io = self.Port(AnalogSink.empty(), [InOut]) - self.connect(self.io, self.tp.io.adapt_to(AnalogSink())) + self.io: AnalogSink = self.Port(AnalogSink(), [InOut]) + self.connect(self.io.net, self.tp.io) def connected(self, io: Port[AnalogLink]) -> "AnalogTestPoint": cast(Block, builder.get_enclosing_block()).connect(io, self.io) @@ -135,7 +135,8 @@ class AnalogCoaxTestPoint(BaseRfTestPoint, Block): def __init__(self, *args: Any) -> None: super().__init__(*args) - self.io = self.Export(self.conn.sig.adapt_to(AnalogSink()), [InOut]) + self.io: AnalogSink = self.Port(AnalogSink(), [InOut]) + self.connect(self.io.net, self.conn.sig) def connected(self, io: Port[AnalogLink]) -> "AnalogCoaxTestPoint": cast(Block, builder.get_enclosing_block()).connect(io, self.io) diff --git a/edg/abstract_parts/PassiveFilters.py b/edg/abstract_parts/PassiveFilters.py index 9337455c7..d2ca71ac8 100644 --- a/edg/abstract_parts/PassiveFilters.py +++ b/edg/abstract_parts/PassiveFilters.py @@ -183,10 +183,22 @@ def symbol_pinning(self, symbol_name: str) -> Mapping[str, BasePort]: def __init__(self, impedance: RangeLike, cutoff_freq: RangeLike): super().__init__() - self.inn = self.Port(AnalogSink.empty()) - self.inp = self.Port(AnalogSink.empty()) - self.outn = self.Port(AnalogSource.empty()) - self.outp = self.Port(AnalogSource.empty()) + self.inn = self.Port(AnalogSink(impedance=RangeExpr(), current_draw=RangeExpr())) + self.inp = self.Port(AnalogSink(impedance=RangeExpr(), current_draw=RangeExpr())) + self.outn = self.Port( + AnalogSource( + voltage_out=self.inn.link().voltage, + signal_out=self.inn.link().signal, + impedance=RangeExpr(), + ) + ) + self.outp = self.Port( + AnalogSource( + voltage_out=self.inp.link().voltage, + signal_out=self.inp.link().signal, + impedance=RangeExpr(), + ) + ) self.impedance = self.ArgParameter(impedance) self.cutoff_freq = self.ArgParameter(cutoff_freq) @@ -197,48 +209,19 @@ def contents(self) -> None: self.rp = self.Block(Resistor(resistance=self.impedance)) self.rn = self.Block(Resistor(resistance=self.impedance)) + self.assign(self.inn.impedance, self.rn.actual_resistance + self.outn.link().sink_impedance) + self.assign(self.inn.current_draw, self.outn.link().current_drawn) + self.assign(self.inp.impedance, self.rp.actual_resistance + self.outp.link().sink_impedance) + self.assign(self.inp.current_draw, self.outp.link().current_drawn) + self.assign(self.outn.impedance, self.rn.actual_resistance + self.inn.link().source_impedance) + self.assign(self.outp.impedance, self.rp.actual_resistance + self.inp.link().source_impedance) + capacitance = (1 / self.cutoff_freq).shrink_multiply(1 / (2 * pi * self.impedance)) # capacitance is single-ended, halve it for differential self.c = self.Block( Capacitor(capacitance=0.5 * capacitance, voltage=self.inp.link().voltage - self.inn.link().voltage) ) - self.connect( - self.inp, - self.rp.a.adapt_to( - AnalogSink( - impedance=self.rp.actual_resistance + self.outp.link().sink_impedance, - current_draw=self.outp.link().current_drawn, - ) - ), - ) - self.connect( - self.inn, - self.rn.a.adapt_to( - AnalogSink( - impedance=self.rn.actual_resistance + self.outn.link().sink_impedance, - current_draw=self.outn.link().current_drawn, - ) - ), - ) - self.connect( - self.outp, - self.rp.b.adapt_to( - AnalogSource( - voltage_out=self.inp.link().voltage, - signal_out=self.inp.link().signal, - impedance=self.rp.actual_resistance + self.inp.link().source_impedance, - ) - ), - self.c.pos.adapt_to(AnalogSink()), - ) - self.connect( - self.outn, - self.rn.b.adapt_to( - AnalogSource( - voltage_out=self.inn.link().voltage, - signal_out=self.inn.link().signal, - impedance=self.rn.actual_resistance + self.inn.link().source_impedance, - ) - ), - self.c.neg.adapt_to(AnalogSink()), - ) + self.connect(self.inp.net, self.rp.a) + self.connect(self.inn.net, self.rn.a) + self.connect(self.outp.net, self.rp.b, self.c.pos) + self.connect(self.outn.net, self.rn.b, self.c.neg) diff --git a/examples/test_bldc_controller.py b/examples/test_bldc_controller.py index 9254daf97..c0a86aa1f 100644 --- a/examples/test_bldc_controller.py +++ b/examples/test_bldc_controller.py @@ -38,9 +38,9 @@ def __init__(self) -> None: [Power], ) self.gnd = self.Export(self.conn.pins.request("3").adapt_to(Ground()), [Common]) - self.out = self.Export( - self.conn.pins.request("2").adapt_to(AnalogSource.from_supply(self.gnd, self.pwr)), [Output] - ) + self.out = self.Export(AnalogSource.from_supply(self.gnd, self.pwr), [Output]) + + self.connect(self.out.net, self.conn.pins.request("2")) class I2cConnector(Connector, Block): From 5f2ce09d940a4b79441e17d723ccc36bc6f863fc Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 5 Apr 2026 12:07:28 -0700 Subject: [PATCH 25/29] refactoring wip --- edg/abstract_parts/AbstractAnalogSwitch.py | 64 +++++++++---------- edg/abstract_parts/AbstractSolidStateRelay.py | 22 +++---- edg/abstract_parts/PassiveFilters.py | 20 +++--- edg/abstract_parts/ResistiveDivider.py | 41 ++++++------ examples/test_iot_iron.py | 13 ++-- examples/test_robotcrawler.py | 13 ++-- 6 files changed, 83 insertions(+), 90 deletions(-) diff --git a/edg/abstract_parts/AbstractAnalogSwitch.py b/edg/abstract_parts/AbstractAnalogSwitch.py index b31375ee8..e58678ff2 100644 --- a/edg/abstract_parts/AbstractAnalogSwitch.py +++ b/edg/abstract_parts/AbstractAnalogSwitch.py @@ -129,14 +129,12 @@ def __init__(self) -> None: self.control = self.Export(self.device.control) self.inputs = self.Port(Vector(AnalogSink.empty())) - self.out = self.Export( - self.device.com.adapt_to( - AnalogSource( - voltage_out=self.inputs.hull(lambda x: x.link().voltage), - signal_out=self.inputs.hull(lambda x: x.link().signal), - current_limits=self.device.analog_current_limits, # this device only, current draw propagated - impedance=self.device.analog_on_resistance + self.inputs.hull(lambda x: x.link().source_impedance), - ) + self.out = self.Port( + AnalogSource( + voltage_out=self.inputs.hull(lambda x: x.link().voltage), + signal_out=self.inputs.hull(lambda x: x.link().signal), + current_limits=self.device.analog_current_limits, # this device only, current draw propagated + impedance=self.device.analog_on_resistance + self.inputs.hull(lambda x: x.link().source_impedance), ) ) @@ -145,18 +143,20 @@ def __init__(self) -> None: @override def generate(self) -> None: super().generate() + + self.connect(self.out.net, self.device.com) + self.inputs.defined() for elt in self.get(self.inputs.requested()): - self.connect( - self.inputs.append_elt(AnalogSink.empty(), elt), - self.device.inputs.request(elt).adapt_to( - AnalogSink( - voltage_limits=self.device.analog_voltage_limits, # this device only, voltages propagated - current_draw=self.out.link().current_drawn, - impedance=self.out.link().sink_impedance + self.device.analog_on_resistance, - ) + input = self.inputs.append_elt( + AnalogSink( + voltage_limits=self.device.analog_voltage_limits, # this device only, voltages propagated + current_draw=self.out.link().current_drawn, + impedance=self.out.link().sink_impedance + self.device.analog_on_resistance, ), + elt, ) + self.connect(input.net, self.device.inputs.request(elt)) if self.get(self.control_gnd.is_connected()): self.connect(self.control_gnd, self.device.control_gnd) @@ -182,13 +182,11 @@ def __init__(self) -> None: self.control = self.Export(self.device.control) self.outputs = self.Port(Vector(AnalogSource.empty())) - self.input = self.Export( - self.device.com.adapt_to( - AnalogSink( - voltage_limits=self.device.analog_voltage_limits, # this device only, voltages propagated - current_draw=self.outputs.hull(lambda x: x.link().current_drawn), - impedance=self.device.analog_on_resistance + self.outputs.hull(lambda x: x.link().sink_impedance), - ) + self.input = self.Port( + AnalogSink( + voltage_limits=self.device.analog_voltage_limits, # this device only, voltages propagated + current_draw=self.outputs.hull(lambda x: x.link().current_drawn), + impedance=self.device.analog_on_resistance + self.outputs.hull(lambda x: x.link().sink_impedance), ) ) @@ -197,19 +195,21 @@ def __init__(self) -> None: @override def generate(self) -> None: super().generate() + + self.connect(self.input.net, self.device.com) + self.outputs.defined() for elt in self.get(self.outputs.requested()): - self.connect( - self.outputs.append_elt(AnalogSource.empty(), elt), - self.device.inputs.request(elt).adapt_to( - AnalogSource( - voltage_out=self.input.link().voltage, - signal_out=self.input.link().signal, - current_limits=self.device.analog_current_limits, # this device only, voltages propagated - impedance=self.input.link().source_impedance + self.device.analog_on_resistance, - ) + output = self.outputs.append_elt( + AnalogSource( + voltage_out=self.input.link().voltage, + signal_out=self.input.link().signal, + current_limits=self.device.analog_current_limits, # this device only, voltages propagated + impedance=self.input.link().source_impedance + self.device.analog_on_resistance, ), + elt, ) + self.connect(output.net, self.device.inputs.request(elt)) def demux_to( self, input: Optional[Port[AnalogLink]] = None, outputs: Optional[List[Port[AnalogLink]]] = None diff --git a/edg/abstract_parts/AbstractSolidStateRelay.py b/edg/abstract_parts/AbstractSolidStateRelay.py index f146e2439..54bba84d6 100644 --- a/edg/abstract_parts/AbstractSolidStateRelay.py +++ b/edg/abstract_parts/AbstractSolidStateRelay.py @@ -111,14 +111,22 @@ def symbol_pinning(self, symbol_name: str) -> Dict[str, BasePort]: def __init__(self) -> None: super().__init__() + self.ic = self.Block(SolidStateRelay()) + self.signal = self.Port(DigitalSink.empty()) self.gnd = self.Port(Ground.empty(), [Common]) self.apull = self.Port(AnalogSink.empty()) - self.ain = self.Port(AnalogSink.empty()) + self.ain = self.Port( + AnalogSink( + voltage_limits=RangeExpr(), + impedance=RangeExpr(), + ) + ) self.aout = self.Port(AnalogSource.empty()) + self.assign(self.ain.voltage_limits, self.apull.link().voltage + self.ic.load_voltage_limit) + self.assign(self.ain.impedance, self.aout.link().sink_impedance + self.ic.load_resistance) - self.ic = self.Block(SolidStateRelay()) self.res = self.Block( Resistor( resistance=( @@ -134,15 +142,7 @@ def __init__(self) -> None: self.connect(self.res.a, self.ic.ledk) self.connect(self.res.b.adapt_to(Ground()), self.gnd) - self.connect( - self.ain, - self.ic.feta.adapt_to( - AnalogSink( - voltage_limits=self.apull.link().voltage + self.ic.load_voltage_limit, - impedance=self.aout.link().sink_impedance + self.ic.load_resistance, - ) - ), - ) + self.connect(self.ain.net, self.ic.feta) self.pull_merge = self.Block(MergedAnalogSource()).connected_from( self.apull, self.ic.fetb.adapt_to( diff --git a/edg/abstract_parts/PassiveFilters.py b/edg/abstract_parts/PassiveFilters.py index d2ca71ac8..72f0deba7 100644 --- a/edg/abstract_parts/PassiveFilters.py +++ b/edg/abstract_parts/PassiveFilters.py @@ -154,20 +154,18 @@ class LowPassRcDac(DigitalToAnalog, Block): def __init__(self, impedance: RangeLike, cutoff_freq: RangeLike): super().__init__() self.input = self.Port(DigitalSink.empty(), [Input]) - self.output = self.Port(AnalogSource.empty(), [Output]) + self.output = self.Port( + AnalogSource( + voltage_out=self.input.link().voltage, + signal_out=self.input.link().voltage, + impedance=impedance, # TODO use selected resistance from RC filter + ), + [Output], + ) self.rc = self.Block(LowPassRc(impedance=impedance, cutoff_freq=cutoff_freq, voltage=self.input.link().voltage)) self.connect(self.input, self.rc.input.adapt_to(DigitalSink(current_draw=self.output.link().current_drawn))) - self.connect( - self.output, - self.rc.output.adapt_to( - AnalogSource( - voltage_out=self.input.link().voltage, - signal_out=self.input.link().voltage, - impedance=impedance, # TODO use selected resistance from RC filter - ) - ), - ) + self.connect(self.output.net, self.rc.output) self.gnd = self.Export(self.rc.gnd.adapt_to(Ground()), [Common]) diff --git a/edg/abstract_parts/ResistiveDivider.py b/edg/abstract_parts/ResistiveDivider.py index 17ab56988..7bdb0e4fd 100644 --- a/edg/abstract_parts/ResistiveDivider.py +++ b/edg/abstract_parts/ResistiveDivider.py @@ -176,14 +176,12 @@ def __init__(self, impedance: RangeLike) -> None: output_voltage = ResistiveDivider.divider_output( self.input.link().voltage, self.gnd.link().voltage, self.div.actual_ratio ) - self.output = self.Export( - self.div.center.adapt_to( - AnalogSource( - voltage_out=output_voltage, - signal_out=output_voltage, - current_limits=RangeExpr.ALL, - impedance=self.div.actual_impedance, - ) + self.output = self.Port( + AnalogSource( + voltage_out=output_voltage, + signal_out=output_voltage, + current_limits=RangeExpr.ALL, + impedance=self.div.actual_impedance, ), [Output], ) @@ -193,6 +191,7 @@ def __init__(self, impedance: RangeLike) -> None: VoltageSink(current_draw=self.output.link().current_drawn, voltage_limits=RangeExpr.ALL) ), ) + self.connect(self.output.net, self.div.center) self.actual_rtop = self.Parameter(RangeExpr(self.div.actual_rtop)) self.actual_rbot = self.Parameter(RangeExpr(self.div.actual_rbot)) @@ -269,22 +268,20 @@ def __init__(self, ratio: RangeLike, impedance: RangeLike) -> None: self.div = self.Block(ResistiveDivider(ratio=ratio, impedance=impedance)) self.gnd = self.Export(self.div.bottom.adapt_to(Ground()), [Common]) - self.input = self.Port(AnalogSink.empty(), [Input]) # forward declaration + self.input = self.Port( + AnalogSink( + impedance=self.div.actual_series_impedance, + current_draw=RangeExpr(), + ), + [Input], + ) # forward declaration output_voltage = ResistiveDivider.divider_output( self.input.link().voltage, self.gnd.link().voltage, self.div.actual_ratio ) - self.output = self.Export( - self.div.center.adapt_to( - AnalogSource(voltage_out=output_voltage, signal_out=output_voltage, impedance=self.div.actual_impedance) - ), + self.output = self.Port( + AnalogSource(voltage_out=output_voltage, signal_out=output_voltage, impedance=self.div.actual_impedance), [Output], ) - self.connect( - self.input, - self.div.top.adapt_to( - AnalogSink( - impedance=self.div.actual_series_impedance, - current_draw=self.output.link().current_drawn, - ) - ), - ) + self.assign(self.input.current_draw, self.output.link().current_drawn) + self.connect(self.output.net, self.div.center) + self.connect(self.input.net, self.div.top) diff --git a/examples/test_iot_iron.py b/examples/test_iot_iron.py index eedb4b864..67c6b1a1e 100644 --- a/examples/test_iot_iron.py +++ b/examples/test_iot_iron.py @@ -17,25 +17,24 @@ def __init__( ): super().__init__() self.conn = self.Block(PinHeader254(3)) + self.isense_res = self.Block(CurrentSenseResistor(resistance=isense_resistance, sense_in_reqd=False)) self.gnd = self.Port(Ground.empty(), [Common]) self.pwr = self.Export(self.conn.pins.request("2").adapt_to(VoltageSink(current_draw=current_draw))) - self.thermocouple = self.Export( - self.conn.pins.request("3").adapt_to( - AnalogSource( - voltage_out=self.gnd.link().voltage + (0, 14.3) * mVolt, - signal_out=self.gnd.link().voltage + (0, 14.3) * mVolt, # up to ~350 C - ) + self.thermocouple = self.Port( + AnalogSource( + voltage_out=self.gnd.link().voltage + (0, 14.3) * mVolt, + signal_out=self.gnd.link().voltage + (0, 14.3) * mVolt, # up to ~350 C ), optional=True, ) - self.isense_res = self.Block(CurrentSenseResistor(resistance=isense_resistance, sense_in_reqd=False)) self.isense = self.Export(self.isense_res.sense_out) self.connect( self.conn.pins.request("1").adapt_to(VoltageSink(current_draw=current_draw)), self.isense_res.pwr_out ) self.connect(self.gnd.as_voltage_source(), self.isense_res.pwr_in) + self.connect(self.thermocouple.net, self.conn.pins.request("3")) class IotIron(JlcBoardTop): diff --git a/examples/test_robotcrawler.py b/examples/test_robotcrawler.py index 93196dd12..0321ceaca 100644 --- a/examples/test_robotcrawler.py +++ b/examples/test_robotcrawler.py @@ -23,15 +23,14 @@ def __init__(self) -> None: ) self.gnd = self.Export(self.conn.pins.request("3").adapt_to(Ground()), [Common]) - self.fb = self.Export( - self.conn.pins.request("4").adapt_to( - AnalogSource( # no specs given - voltage_out=(0.9, 2.1) - * Volt, # from https://www.pololu.com/blog/814/new-products-special-servos-with-position-feedback - signal_out=(0.9, 2.1) * Volt, - ) + self.fb = self.Port( + AnalogSource( # no specs given + voltage_out=(0.9, 2.1) + * Volt, # from https://www.pololu.com/blog/814/new-products-special-servos-with-position-feedback + signal_out=(0.9, 2.1) * Volt, ) ) + self.connect(self.fb.net, self.conn.pins.request("4")) @abstract_block From bda0ad883fc0823432cd9f8c6efaecca0ac4bbe5 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 5 Apr 2026 13:48:45 -0700 Subject: [PATCH 26/29] cleanup up diode --- edg/abstract_parts/AbstractDiodes.py | 35 ++++++++++++---------------- edg/abstract_parts/DummyDevices.py | 13 ----------- edg/abstract_parts/__init__.py | 1 - 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/edg/abstract_parts/AbstractDiodes.py b/edg/abstract_parts/AbstractDiodes.py index 1f9f76144..648c655f7 100644 --- a/edg/abstract_parts/AbstractDiodes.py +++ b/edg/abstract_parts/AbstractDiodes.py @@ -1,9 +1,7 @@ -from typing import Dict, Any +from typing import Dict from deprecated import deprecated -from typing_extensions import override from ..electronics_model import * -from .DummyDevices import ForcedAnalogVoltage from .Categories import * from .PartsTable import PartsTableColumn, PartsTableRow from .PartsTablePart import PartsTableSelector @@ -222,27 +220,24 @@ class AnalogClampZenerDiode(Protection, KiCadImportableBlock): def __init__(self, voltage: RangeLike): super().__init__() - self.signal_in = self.Port(AnalogSink.empty(), [Input]) - self.signal_out = self.Port(AnalogSource.empty(), [Output]) - self.gnd = self.Port(Ground.empty(), [Common]) - - self.voltage = self.ArgParameter(voltage) - - @override - def contents(self) -> None: - super().contents() - self.diode = self.Block(ZenerDiode(zener_voltage=self.voltage)) - self.forced = self.Block( - ForcedAnalogVoltage( - forced_voltage=self.signal_in.link().voltage.intersect( + self.gnd = self.Port(Ground.empty(), [Common]) + self.signal_in = self.Port(AnalogSink(), [Input]) + self.signal_out = self.Port( + AnalogSource( + voltage_out=self.signal_in.link().voltage.intersect( self.gnd.link().voltage + (0, self.diode.actual_zener_voltage.upper()) - ) - ) + ), + signal_out=self.signal_in.link().signal, + ), + [Output], ) - self.connect(self.signal_in, self.forced.signal_in) - self.connect(self.signal_out, self.forced.signal_out, self.diode.cathode.adapt_to(AnalogSink())) + self.assign(self.signal_in.current_draw, self.signal_out.link().current_drawn) + + self.voltage = self.ArgParameter(voltage) + + self.connect(self.signal_in.net, self.signal_out.net, self.diode.cathode) self.connect(self.diode.anode.adapt_to(Ground()), self.gnd) @override diff --git a/edg/abstract_parts/DummyDevices.py b/edg/abstract_parts/DummyDevices.py index 60d56d509..138e585c6 100644 --- a/edg/abstract_parts/DummyDevices.py +++ b/edg/abstract_parts/DummyDevices.py @@ -113,19 +113,6 @@ def __init__(self, forced_voltage: RangeLike, forced_current: RangeLike) -> None self.pwr_out = self.Port(VoltageSource(voltage_out=forced_voltage), [Output]) -class ForcedAnalogVoltage(DummyDevice): - def __init__(self, forced_voltage: RangeLike = RangeExpr()) -> None: - super().__init__() - - self.signal_in = self.Port(AnalogSink(current_draw=RangeExpr()), [Input]) - self.signal_out = self.Port( - AnalogSource(voltage_out=forced_voltage, signal_out=self.signal_in.link().signal), [Output] - ) - - self.assign(self.signal_in.current_draw, self.signal_out.link().current_drawn) - self.connect(self.signal_in.net, self.signal_out.net) - - class ForcedAnalogSignal(KiCadImportableBlock, DummyDevice): def __init__(self, forced_signal: RangeLike = RangeExpr()) -> None: super().__init__() diff --git a/edg/abstract_parts/__init__.py b/edg/abstract_parts/__init__.py index 49cba6a7b..3e311e54f 100644 --- a/edg/abstract_parts/__init__.py +++ b/edg/abstract_parts/__init__.py @@ -203,7 +203,6 @@ ForcedVoltageCurrentLimit, ForcedVoltage, ForcedVoltageCurrent, - ForcedAnalogVoltage, ForcedAnalogSignal, ForcedDigitalSinkCurrentDraw, ) From 73002331d0a4c6b93cf4f440338f402fccb59a64 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 5 Apr 2026 14:01:03 -0700 Subject: [PATCH 27/29] cleanup --- edg/abstract_parts/AbstractDiodes.py | 4 +-- edg/parts/SpeakerDriver_Analog.py | 45 ++++++++++++---------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/edg/abstract_parts/AbstractDiodes.py b/edg/abstract_parts/AbstractDiodes.py index 648c655f7..0c8b1f452 100644 --- a/edg/abstract_parts/AbstractDiodes.py +++ b/edg/abstract_parts/AbstractDiodes.py @@ -220,7 +220,7 @@ class AnalogClampZenerDiode(Protection, KiCadImportableBlock): def __init__(self, voltage: RangeLike): super().__init__() - self.diode = self.Block(ZenerDiode(zener_voltage=self.voltage)) + self.diode = self.Block(ZenerDiode(zener_voltage=voltage)) self.gnd = self.Port(Ground.empty(), [Common]) self.signal_in = self.Port(AnalogSink(), [Input]) @@ -235,8 +235,6 @@ def __init__(self, voltage: RangeLike): ) self.assign(self.signal_in.current_draw, self.signal_out.link().current_drawn) - self.voltage = self.ArgParameter(voltage) - self.connect(self.signal_in.net, self.signal_out.net, self.diode.cathode) self.connect(self.diode.anode.adapt_to(Ground()), self.gnd) diff --git a/edg/parts/SpeakerDriver_Analog.py b/edg/parts/SpeakerDriver_Analog.py index 477d3ae31..3cdf46886 100644 --- a/edg/parts/SpeakerDriver_Analog.py +++ b/edg/parts/SpeakerDriver_Analog.py @@ -21,9 +21,8 @@ def __init__(self) -> None: self.inp = self.Port(Passive()) # TODO these aren't actually documented w/ specs =( self.inm = self.Port(Passive()) - speaker_port = AnalogSource() - self.vo1 = self.Port(speaker_port) - self.vo2 = self.Port(speaker_port) + self.vo1 = self.Port(Passive()) + self.vo2 = self.Port(Passive()) self.byp = self.Port(Passive()) @@ -58,7 +57,7 @@ def __init__(self) -> None: self.gnd = self.Export(self.ic.gnd, [Common]) self.sig = self.Port(AnalogSink(), [Input]) - self.spk = self.Port(SpeakerDriverPort(AnalogSource.empty()), [Output]) + self.spk = self.Port(SpeakerDriverPort(AnalogSource()), [Output]) @override def contents(self) -> None: @@ -89,8 +88,8 @@ def contents(self) -> None: self.connect(self.sig.net, self.sig_cap.neg) self.connect(self.sig_cap.pos, self.sig_res.a) self.connect(self.sig_res.b, self.fb_res.a, self.ic.inm) - self.connect(self.spk.a, self.ic.vo1, self.fb_res.b.adapt_to(AnalogSink())) - self.connect(self.spk.b, self.ic.vo2) + self.connect(self.spk.a.net, self.ic.vo1, self.fb_res.b) + self.connect(self.spk.b.net, self.ic.vo2) self.connect(self.byp_cap.pos, self.ic.inp, self.ic.byp) @@ -118,11 +117,11 @@ def __init__(self) -> None: self.inp = self.Port(input_port) self.inn = self.Port(input_port) - speaker_port = AnalogSource( - impedance=RangeExpr.ZERO # TODO output impedance not given, but maximum Rl of 3.2-6.4ohm + self.vo = self.Port( + SpeakerDriverPort( + AnalogSource(impedance=RangeExpr.ZERO) # TODO output impedance not given, but maximum Rl of 3.2-6.4ohm + ) ) - self.vo1 = self.Port(speaker_port) - self.vo2 = self.Port(speaker_port) @override def contents(self) -> None: @@ -137,8 +136,8 @@ def contents(self) -> None: "1": self.pwr, # /SHDN "9": self.gnd, # exposed pad, "must be soldered to a grounded pad" "6": self.pwr, - "8": self.vo1, - "5": self.vo2, + "8": self.vo.a, # vo1 + "5": self.vo.b, # vo2 }, mfr="Texas Instruments", part="TPA2005D1", @@ -161,7 +160,7 @@ def __init__(self, gain: RangeLike = Range.from_tolerance(20, 0.2)): self.gnd = self.Export(self.ic.gnd, [Common]) self.sig = self.Port(AnalogSink(), [Input]) - self.spk = self.Port(SpeakerDriverPort(AnalogSource.empty()), [Output]) + self.spk = self.Export(self.ic.vo, [Output]) self.gain = self.ArgParameter(gain) @@ -215,9 +214,6 @@ def contents(self) -> None: self.connect(self.inn_cap.pos, self.inn_res.a) self.connect(self.inn_res.b.adapt_to(AnalogSource()), self.ic.inn) - self.connect(self.spk.a, self.ic.vo1) - self.connect(self.spk.b, self.ic.vo2) - class Pam8302a_Device(InternalSubcircuit, JlcPart, FootprintBlock): def __init__(self) -> None: @@ -238,11 +234,11 @@ def __init__(self) -> None: self.inp = self.Port(input_port) self.inn = self.Port(input_port) - speaker_port = AnalogSource( - impedance=RangeExpr.ZERO # TODO output impedance not given, but maximum Rl of 3.2-6.4ohm + self.vo = self.Port( + SpeakerDriverPort( + AnalogSource(impedance=RangeExpr.ZERO) # TODO output impedance not given, but maximum Rl of 3.2-6.4ohm + ) ) - self.vop = self.Port(speaker_port) - self.von = self.Port(speaker_port) @override def contents(self) -> None: @@ -254,10 +250,10 @@ def contents(self) -> None: # pin 2 is NC "3": self.inp, "4": self.inn, - "5": self.vop, + "5": self.vo.a, # vop "6": self.pwr, "7": self.gnd, - "8": self.von, + "8": self.vo.b, # von }, mfr="Diodes Incorporated", part="PAM8302AASCR", @@ -278,7 +274,7 @@ def __init__(self) -> None: self.gnd = self.Export(self.ic.gnd, [Common]) self.sig = self.Port(AnalogSink.empty(), [Input]) - self.spk = self.Port(SpeakerDriverPort(AnalogSource.empty()), [Output]) + self.spk = self.Export(self.ic.vo, [Output]) @override def contents(self) -> None: @@ -302,6 +298,3 @@ def contents(self) -> None: self.inn_cap = self.Block(in_cap_model) self.connect(self.gnd, self.inn_cap.neg.adapt_to(Ground())) self.connect(self.inn_cap.pos.adapt_to(AnalogSource()), self.ic.inn) - - self.connect(self.spk.a, self.ic.vop) - self.connect(self.spk.b, self.ic.von) From 6a68363e52c4f994ad51c7f48b21de84912ff159 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 5 Apr 2026 14:21:42 -0700 Subject: [PATCH 28/29] cleaning --- edg/electronics_model/GroundPort.py | 1 - edg/electronics_model/PassivePort.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/edg/electronics_model/GroundPort.py b/edg/electronics_model/GroundPort.py index 78a48a243..8af144a28 100644 --- a/edg/electronics_model/GroundPort.py +++ b/edg/electronics_model/GroundPort.py @@ -4,7 +4,6 @@ from typing_extensions import override -from .PassivePort import PassiveAdapterGround from ..core import * from .CircuitBlock import CircuitPortBridge, CircuitPortAdapter, CircuitLink, CircuitPort, KicadImportablePortAdapter from .Units import Volt, Ohm diff --git a/edg/electronics_model/PassivePort.py b/edg/electronics_model/PassivePort.py index ad5de68ec..25bc958bd 100644 --- a/edg/electronics_model/PassivePort.py +++ b/edg/electronics_model/PassivePort.py @@ -178,7 +178,7 @@ def __init__( current_limits: RangeLike = RangeExpr.ALL, impedance: RangeLike = RangeExpr.ZERO, ): - from .AnalogPort import AnalogSource, AnalogSink + from .AnalogPort import AnalogSource super().__init__() self.src = self.Port(Passive()) @@ -199,7 +199,7 @@ def __init__( current_draw: RangeLike = RangeExpr.ZERO, impedance: RangeLike = RangeExpr.INF, ): - from .AnalogPort import AnalogSource, AnalogSink + from .AnalogPort import AnalogSink super().__init__() self.src = self.Port(Passive()) From 844bb761c6ecf3767bd4ff6078a3a8d5d159139c Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 5 Apr 2026 23:31:17 -0700 Subject: [PATCH 29/29] Update ResistiveSensor.py --- edg/parts/ResistiveSensor.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/edg/parts/ResistiveSensor.py b/edg/parts/ResistiveSensor.py index 7def15ee5..2bae0ebd8 100644 --- a/edg/parts/ResistiveSensor.py +++ b/edg/parts/ResistiveSensor.py @@ -36,11 +36,7 @@ def contents(self) -> None: self.assign(self.output.voltage_out, output_voltage) self.assign(self.output.signal_out, output_voltage) self.assign(self.output.impedance, self.actual_impedance) - self.connect( - self.output.net, - self.top.b, - self.bot.pins.request("1"), - ) + self.connect(self.output.net, self.top.b, self.bot.pins.request("1")) self.connect(self.gnd, self.bot.pins.request("2").adapt_to(Ground())) self.assign(self.actual_impedance, 1 / (1 / self.top.actual_resistance + 1 / self.resistance_range))