Skip to content

Commit cfa9dab

Browse files
authored
Refactor Analog ports for compositional passive (#463)
First step and example plan for #114, to move single-circuit ports to a compositional instead of inheritance model. This changes AnalogSink, AnalogSource to have an internal `net` sub-port, which is the actual net. Introduces an infrastructural HasPassivePort, which effectively fulfills the same function as (inheritance-based) CirucitPort, but with composition. Footprints implicitly understand HasPassivePort and automatically connect the internal net. For netlister net naming, the interior net name of ports is pruned out. Refactors all the adapt_to(Analog*) to create the fully-defined Analog* port in the wrapper, then connect its net to the internal passive-typed port. The leftover adapt_to(Analog*) cases are adapting within a block (eg, to connect to a peer-level sub-block) and cannot be removed. This probably forms the new recommended practice. Future PRs: - Refactor other single-circuit ports - Migrate uses to get rid of adapt_to - Remove netlister blocks (NetBlock, CircuitPort, CircuitLink, CircuitPortBridge) and refactor use sites - Fully make Passive replace CircuitPort
1 parent e196d3a commit cfa9dab

39 files changed

Lines changed: 397 additions & 364 deletions

edg/abstract_parts/AbstractAnalogSwitch.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,12 @@ def __init__(self) -> None:
129129
self.control = self.Export(self.device.control)
130130

131131
self.inputs = self.Port(Vector(AnalogSink.empty()))
132-
self.out = self.Export(
133-
self.device.com.adapt_to(
134-
AnalogSource(
135-
voltage_out=self.inputs.hull(lambda x: x.link().voltage),
136-
signal_out=self.inputs.hull(lambda x: x.link().signal),
137-
current_limits=self.device.analog_current_limits, # this device only, current draw propagated
138-
impedance=self.device.analog_on_resistance + self.inputs.hull(lambda x: x.link().source_impedance),
139-
)
132+
self.out = self.Port(
133+
AnalogSource(
134+
voltage_out=self.inputs.hull(lambda x: x.link().voltage),
135+
signal_out=self.inputs.hull(lambda x: x.link().signal),
136+
current_limits=self.device.analog_current_limits, # this device only, current draw propagated
137+
impedance=self.device.analog_on_resistance + self.inputs.hull(lambda x: x.link().source_impedance),
140138
)
141139
)
142140

@@ -145,18 +143,20 @@ def __init__(self) -> None:
145143
@override
146144
def generate(self) -> None:
147145
super().generate()
146+
147+
self.connect(self.out.net, self.device.com)
148+
148149
self.inputs.defined()
149150
for elt in self.get(self.inputs.requested()):
150-
self.connect(
151-
self.inputs.append_elt(AnalogSink.empty(), elt),
152-
self.device.inputs.request(elt).adapt_to(
153-
AnalogSink(
154-
voltage_limits=self.device.analog_voltage_limits, # this device only, voltages propagated
155-
current_draw=self.out.link().current_drawn,
156-
impedance=self.out.link().sink_impedance + self.device.analog_on_resistance,
157-
)
151+
input = self.inputs.append_elt(
152+
AnalogSink(
153+
voltage_limits=self.device.analog_voltage_limits, # this device only, voltages propagated
154+
current_draw=self.out.link().current_drawn,
155+
impedance=self.out.link().sink_impedance + self.device.analog_on_resistance,
158156
),
157+
elt,
159158
)
159+
self.connect(input.net, self.device.inputs.request(elt))
160160
if self.get(self.control_gnd.is_connected()):
161161
self.connect(self.control_gnd, self.device.control_gnd)
162162

@@ -182,13 +182,11 @@ def __init__(self) -> None:
182182
self.control = self.Export(self.device.control)
183183

184184
self.outputs = self.Port(Vector(AnalogSource.empty()))
185-
self.input = self.Export(
186-
self.device.com.adapt_to(
187-
AnalogSink(
188-
voltage_limits=self.device.analog_voltage_limits, # this device only, voltages propagated
189-
current_draw=self.outputs.hull(lambda x: x.link().current_drawn),
190-
impedance=self.device.analog_on_resistance + self.outputs.hull(lambda x: x.link().sink_impedance),
191-
)
185+
self.input = self.Port(
186+
AnalogSink(
187+
voltage_limits=self.device.analog_voltage_limits, # this device only, voltages propagated
188+
current_draw=self.outputs.hull(lambda x: x.link().current_drawn),
189+
impedance=self.device.analog_on_resistance + self.outputs.hull(lambda x: x.link().sink_impedance),
192190
)
193191
)
194192

@@ -197,19 +195,21 @@ def __init__(self) -> None:
197195
@override
198196
def generate(self) -> None:
199197
super().generate()
198+
199+
self.connect(self.input.net, self.device.com)
200+
200201
self.outputs.defined()
201202
for elt in self.get(self.outputs.requested()):
202-
self.connect(
203-
self.outputs.append_elt(AnalogSource.empty(), elt),
204-
self.device.inputs.request(elt).adapt_to(
205-
AnalogSource(
206-
voltage_out=self.input.link().voltage,
207-
signal_out=self.input.link().signal,
208-
current_limits=self.device.analog_current_limits, # this device only, voltages propagated
209-
impedance=self.input.link().source_impedance + self.device.analog_on_resistance,
210-
)
203+
output = self.outputs.append_elt(
204+
AnalogSource(
205+
voltage_out=self.input.link().voltage,
206+
signal_out=self.input.link().signal,
207+
current_limits=self.device.analog_current_limits, # this device only, voltages propagated
208+
impedance=self.input.link().source_impedance + self.device.analog_on_resistance,
211209
),
210+
elt,
212211
)
212+
self.connect(output.net, self.device.inputs.request(elt))
213213

214214
def demux_to(
215215
self, input: Optional[Port[AnalogLink]] = None, outputs: Optional[List[Port[AnalogLink]]] = None

edg/abstract_parts/AbstractCapacitor.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,10 +403,12 @@ def __init__(self, capacitance: RangeLike, *, exact_capacitance: BoolLike = Fals
403403
super().__init__()
404404

405405
self.cap = self.Block(Capacitor(capacitance, voltage=RangeExpr(), exact_capacitance=exact_capacitance))
406-
self.gnd = self.Export(self.cap.neg.adapt_to(Ground()), [Common])
407-
self.io = self.Export(self.cap.pos.adapt_to(AnalogSink()), [InOut]) # ideal open port
406+
self.gnd = self.Port(Ground.empty(), [Common])
407+
self.io = self.Port(AnalogSink(), [InOut]) # ideal open port
408408

409409
self.assign(self.cap.voltage, self.io.link().voltage - self.gnd.link().voltage)
410+
self.connect(self.cap.neg.adapt_to(Ground()), self.gnd) # TODO refactor #114
411+
self.connect(self.io.net, self.cap.pos)
410412

411413
def connected(
412414
self, gnd: Optional[Port[GroundLink]] = None, io: Optional[Port[AnalogLink]] = None

edg/abstract_parts/AbstractDiodes.py

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
from typing import Dict, Any
1+
from typing import Dict
22
from deprecated import deprecated
3-
from typing_extensions import override
43

54
from ..electronics_model import *
6-
from .DummyDevices import ForcedAnalogVoltage
75
from .Categories import *
86
from .PartsTable import PartsTableColumn, PartsTableRow
97
from .PartsTablePart import PartsTableSelector
@@ -222,27 +220,22 @@ class AnalogClampZenerDiode(Protection, KiCadImportableBlock):
222220
def __init__(self, voltage: RangeLike):
223221
super().__init__()
224222

225-
self.signal_in = self.Port(AnalogSink.empty(), [Input])
226-
self.signal_out = self.Port(AnalogSource.empty(), [Output])
227-
self.gnd = self.Port(Ground.empty(), [Common])
228-
229-
self.voltage = self.ArgParameter(voltage)
230-
231-
@override
232-
def contents(self) -> None:
233-
super().contents()
223+
self.diode = self.Block(ZenerDiode(zener_voltage=voltage))
234224

235-
self.diode = self.Block(ZenerDiode(zener_voltage=self.voltage))
236-
237-
self.forced = self.Block(
238-
ForcedAnalogVoltage(
239-
forced_voltage=self.signal_in.link().voltage.intersect(
225+
self.gnd = self.Port(Ground.empty(), [Common])
226+
self.signal_in = self.Port(AnalogSink(), [Input])
227+
self.signal_out = self.Port(
228+
AnalogSource(
229+
voltage_out=self.signal_in.link().voltage.intersect(
240230
self.gnd.link().voltage + (0, self.diode.actual_zener_voltage.upper())
241-
)
242-
)
231+
),
232+
signal_out=self.signal_in.link().signal,
233+
),
234+
[Output],
243235
)
244-
self.connect(self.signal_in, self.forced.signal_in)
245-
self.connect(self.signal_out, self.forced.signal_out, self.diode.cathode.adapt_to(AnalogSink()))
236+
self.assign(self.signal_in.current_draw, self.signal_out.link().current_drawn)
237+
238+
self.connect(self.signal_in.net, self.signal_out.net, self.diode.cathode)
246239
self.connect(self.diode.anode.adapt_to(Ground()), self.gnd)
247240

248241
@override

edg/abstract_parts/AbstractResistor.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -392,14 +392,21 @@ def __init__(
392392
):
393393
super().__init__()
394394

395-
self.signal_in = self.Port(AnalogSink.empty(), [Input])
396-
self.signal_out = self.Port(AnalogSource.empty(), [Output])
397-
398395
self.clamp_target = self.ArgParameter(clamp_target)
399396
self.clamp_current = self.ArgParameter(clamp_current)
400397
self.protection_voltage = self.ArgParameter(protection_voltage)
401398
self.zero_out = self.ArgParameter(zero_out)
402399

400+
self.signal_in = self.Port(AnalogSink(), [Input])
401+
self.signal_out = self.Port(
402+
AnalogSource(
403+
voltage_out=self.signal_in.link().voltage.intersect(self.clamp_target),
404+
signal_out=self.signal_in.link().signal,
405+
impedance=RangeExpr(),
406+
),
407+
[Output],
408+
)
409+
403410
@override
404411
def contents(self) -> None:
405412
super().contents()
@@ -415,17 +422,9 @@ def contents(self) -> None:
415422
)
416423
)
417424
)
418-
self.connect(self.res.a.adapt_to(AnalogSink()), self.signal_in)
419-
self.connect(
420-
self.res.b.adapt_to(
421-
AnalogSource(
422-
voltage_out=self.signal_in.link().voltage.intersect(self.clamp_target),
423-
signal_out=self.signal_in.link().signal,
424-
impedance=self.signal_in.link().source_impedance + self.res.actual_resistance,
425-
)
426-
),
427-
self.signal_out,
428-
)
425+
self.connect(self.res.a, self.signal_in.net)
426+
self.connect(self.res.b, self.signal_out.net)
427+
self.assign(self.signal_out.impedance, self.signal_in.link().source_impedance + self.res.actual_resistance)
429428

430429
@override
431430
def symbol_pinning(self, symbol_name: str) -> Dict[str, Port]:

edg/abstract_parts/AbstractSolidStateRelay.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,22 @@ def symbol_pinning(self, symbol_name: str) -> Dict[str, BasePort]:
111111
def __init__(self) -> None:
112112
super().__init__()
113113

114+
self.ic = self.Block(SolidStateRelay())
115+
114116
self.signal = self.Port(DigitalSink.empty())
115117
self.gnd = self.Port(Ground.empty(), [Common])
116118

117119
self.apull = self.Port(AnalogSink.empty())
118-
self.ain = self.Port(AnalogSink.empty())
120+
self.ain = self.Port(
121+
AnalogSink(
122+
voltage_limits=RangeExpr(),
123+
impedance=RangeExpr(),
124+
)
125+
)
119126
self.aout = self.Port(AnalogSource.empty())
127+
self.assign(self.ain.voltage_limits, self.apull.link().voltage + self.ic.load_voltage_limit)
128+
self.assign(self.ain.impedance, self.aout.link().sink_impedance + self.ic.load_resistance)
120129

121-
self.ic = self.Block(SolidStateRelay())
122130
self.res = self.Block(
123131
Resistor(
124132
resistance=(
@@ -134,15 +142,7 @@ def __init__(self) -> None:
134142
self.connect(self.res.a, self.ic.ledk)
135143
self.connect(self.res.b.adapt_to(Ground()), self.gnd)
136144

137-
self.connect(
138-
self.ain,
139-
self.ic.feta.adapt_to(
140-
AnalogSink(
141-
voltage_limits=self.apull.link().voltage + self.ic.load_voltage_limit,
142-
impedance=self.aout.link().sink_impedance + self.ic.load_resistance,
143-
)
144-
),
145-
)
145+
self.connect(self.ain.net, self.ic.feta)
146146
self.pull_merge = self.Block(MergedAnalogSource()).connected_from(
147147
self.apull,
148148
self.ic.fetb.adapt_to(

edg/abstract_parts/AbstractTestPoint.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ class AnalogTestPoint(BaseTypedTestPoint, Block):
120120

121121
def __init__(self, *args: Any) -> None:
122122
super().__init__(*args)
123-
self.io = self.Port(AnalogSink.empty(), [InOut])
124-
self.connect(self.io, self.tp.io.adapt_to(AnalogSink()))
123+
self.io: AnalogSink = self.Port(AnalogSink(), [InOut])
124+
self.connect(self.io.net, self.tp.io)
125125

126126
def connected(self, io: Port[AnalogLink]) -> "AnalogTestPoint":
127127
cast(Block, builder.get_enclosing_block()).connect(io, self.io)
@@ -135,7 +135,8 @@ class AnalogCoaxTestPoint(BaseRfTestPoint, Block):
135135

136136
def __init__(self, *args: Any) -> None:
137137
super().__init__(*args)
138-
self.io = self.Export(self.conn.sig.adapt_to(AnalogSink()), [InOut])
138+
self.io: AnalogSink = self.Port(AnalogSink(), [InOut])
139+
self.connect(self.io.net, self.conn.sig)
139140

140141
def connected(self, io: Port[AnalogLink]) -> "AnalogCoaxTestPoint":
141142
cast(Block, builder.get_enclosing_block()).connect(io, self.io)

edg/abstract_parts/DummyDevices.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -113,25 +113,11 @@ def __init__(self, forced_voltage: RangeLike, forced_current: RangeLike) -> None
113113
self.pwr_out = self.Port(VoltageSource(voltage_out=forced_voltage), [Output])
114114

115115

116-
class ForcedAnalogVoltage(DummyDevice, NetBlock):
117-
def __init__(self, forced_voltage: RangeLike = RangeExpr()) -> None:
118-
super().__init__()
119-
120-
self.signal_in = self.Port(AnalogSink(current_draw=RangeExpr()), [Input])
121-
122-
self.signal_out = self.Port(
123-
AnalogSource(voltage_out=forced_voltage, signal_out=self.signal_in.link().signal), [Output]
124-
)
125-
126-
self.assign(self.signal_in.current_draw, self.signal_out.link().current_drawn)
127-
128-
129-
class ForcedAnalogSignal(KiCadImportableBlock, DummyDevice, NetBlock):
116+
class ForcedAnalogSignal(KiCadImportableBlock, DummyDevice):
130117
def __init__(self, forced_signal: RangeLike = RangeExpr()) -> None:
131118
super().__init__()
132119

133120
self.signal_in = self.Port(AnalogSink(current_draw=RangeExpr()), [Input])
134-
135121
self.signal_out = self.Port(
136122
AnalogSource(
137123
voltage_out=self.signal_in.link().voltage,
@@ -142,6 +128,7 @@ def __init__(self, forced_signal: RangeLike = RangeExpr()) -> None:
142128
)
143129

144130
self.assign(self.signal_in.current_draw, self.signal_out.link().current_drawn)
131+
self.connect(self.signal_in.net, self.signal_out.net)
145132

146133
@override
147134
def symbol_pinning(self, symbol_name: str) -> Dict[str, BasePort]:

edg/abstract_parts/IoController.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from itertools import chain
2-
from typing import List, Dict, Tuple, Type, Optional, Any
2+
from typing import List, Dict, Tuple, Type, Optional, Any, Union
33

44
from deprecated import deprecated
55
from typing_extensions import override
66

77
from ..electronics_model import *
88
from .PinMappable import AllocatedResource, PinMappable, PinMapUtil
99
from .Categories import ProgrammableController
10+
from ..electronics_model.PassivePort import HasPassivePort
1011

1112

1213
@non_library
@@ -97,12 +98,12 @@ def _export_ios_from(self, inner: "BaseIoController", excludes: List[BasePort] =
9798
@staticmethod
9899
def _instantiate_from(
99100
ios: List[BasePort], allocations: List[AllocatedResource]
100-
) -> Tuple[Dict[str, CircuitPort], RangeExpr]:
101+
) -> Tuple[Dict[str, Union[CircuitPort, HasPassivePort]], RangeExpr]:
101102
"""Given a mapping of port types to IO ports and allocated resources from PinMapUtil,
102103
instantiate vector elements (if a vector) or init the port model (if a port)
103104
for the allocated resources using their data model and return the pin mapping."""
104105
ios_by_type = {io.elt_type() if isinstance(io, Vector) else type(io): io for io in ios}
105-
pinmap: Dict[str, CircuitPort] = {}
106+
pinmap: Dict[str, Union[CircuitPort, HasPassivePort]] = {}
106107

107108
ports_assigned = IdentitySet[Port]()
108109
io_current_draw_builder = RangeExpr._to_expr_type(RangeExpr.ZERO)
@@ -141,14 +142,14 @@ def _instantiate_from(
141142
# TODO: recurse into bundles, really needs a more unified way of handling current draw
142143

143144
if isinstance(allocation.pin, str):
144-
assert isinstance(io_port, CircuitPort)
145+
assert isinstance(io_port, (CircuitPort, HasPassivePort))
145146
pinmap[allocation.pin] = io_port
146147
elif allocation.pin is None:
147-
assert isinstance(io_port, CircuitPort) # otherwise discarded
148+
assert isinstance(io_port, (CircuitPort, HasPassivePort)) # otherwise discarded
148149
elif isinstance(allocation.pin, dict):
149150
for subport_name, (pin_name, pin_resource) in allocation.pin.items():
150151
subport = getattr(io_port, subport_name)
151-
assert isinstance(subport, CircuitPort), f"bad sub-port {pin_name} {subport}"
152+
assert isinstance(subport, (CircuitPort, HasPassivePort)), f"bad sub-port {pin_name} {subport}"
152153
pinmap[pin_name] = subport
153154
else:
154155
raise NotImplementedError(f"unknown allocation pin type {allocation.pin}")
@@ -183,7 +184,7 @@ def _io_pinmap(self) -> PinMapUtil:
183184
"""Implement me. Defines the assignable IO pinmaps."""
184185
raise NotImplementedError
185186

186-
def _make_pinning(self) -> Dict[str, CircuitPort]:
187+
def _make_pinning(self) -> Dict[str, Union[CircuitPort, HasPassivePort]]:
187188
allocation_list = []
188189
for io_port in self._io_ports:
189190
if isinstance(io_port, Vector): # derive Vector connections from requested

edg/abstract_parts/MergedBlocks.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def connected_from(self, *ins: Port[DigitalLink]) -> "MergedDigitalSource":
6969
return self
7070

7171

72-
class MergedAnalogSource(KiCadImportableBlock, DummyDevice, NetBlock, GeneratorBlock):
72+
class MergedAnalogSource(KiCadImportableBlock, DummyDevice, GeneratorBlock):
7373
@override
7474
def symbol_pinning(self, symbol_name: str) -> Dict[str, BasePort]:
7575
assert symbol_name.startswith("edg_importable:Merge") # can be any merge
@@ -89,10 +89,11 @@ def generate(self) -> None:
8989
super().generate()
9090
self.inputs.defined()
9191
for in_request in self.get(self.inputs.requested()):
92-
self.inputs.append_elt(
92+
elt_port = self.inputs.append_elt(
9393
AnalogSink(current_draw=self.output.link().current_drawn, impedance=self.output.link().sink_impedance),
9494
in_request,
9595
)
96+
self.connect(self.output.net, elt_port.net)
9697

9798
self.assign(self.output.voltage_out, self.inputs.hull(lambda x: x.link().voltage))
9899
self.assign(self.output.signal_out, self.inputs.hull(lambda x: x.link().signal))

0 commit comments

Comments
 (0)