Skip to content

Commit 31761d8

Browse files
authored
Compositional passive for Ground (#468)
Re-structures Ground() as containing a Passive-typed net. Towards #114 ... and associated refactors to eliminate most of the .adapt_to(Ground()): - Add DigitalCapacitor block, for a capacitor attached to a Digital link - Add AnalogSetpointResistor block, for a resistor from an Analog to Ground typically used as a programming / bias resistor - Refactors ports using this to be AnalogSource, typically assumed between vss and vdd - Refactors (abstract) Crystal to be fully Passive typed, to be consistent, and as an indication it should not be directly used in an application circuit - Refactors HasPassivePort to only define (and not instantiate) the net subport, to avoid generic type issues - Removes E2154fs091 and has it delegate the the waveshare EPD circuit - Adds better port / component typing to the EPD driver - Improves port typing for MAX17048 - Improve reset subcircuit structure for BH1750 and expose reset pin - Refactor Lm4871 subcircuit to use typed ports
1 parent 3d23412 commit 31761d8

124 files changed

Lines changed: 1159 additions & 1126 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

edg/abstract_parts/AbstractCapacitor.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def __init__(self, capacitance: RangeLike, *, exact_capacitance: BoolLike = Fals
366366
super().__init__()
367367

368368
self.cap = self.Block(Capacitor(capacitance, voltage=RangeExpr(), exact_capacitance=exact_capacitance))
369-
self.gnd = self.Export(self.cap.neg.adapt_to(Ground()), [Common])
369+
self.gnd = self.Port(Ground(), [Common])
370370
self.pwr = self.Export(
371371
self.cap.pos.adapt_to(
372372
VoltageSink.from_gnd(
@@ -375,7 +375,7 @@ def __init__(self, capacitance: RangeLike, *, exact_capacitance: BoolLike = Fals
375375
),
376376
[Power, InOut],
377377
)
378-
378+
self.connect(self.gnd.net, self.cap.neg)
379379
self.assign(self.cap.voltage, self.pwr.link().voltage - self.gnd.link().voltage)
380380

381381
# TODO there should be a way to forward the description string of the inner element
@@ -403,11 +403,11 @@ 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.Port(Ground.empty(), [Common])
406+
self.gnd = self.Port(Ground(), [Common])
407407
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
410+
self.connect(self.gnd.net, self.cap.neg)
411411
self.connect(self.io.net, self.cap.pos)
412412

413413
def connected(
@@ -478,6 +478,36 @@ def connected(
478478
return self
479479

480480

481+
class DigitalCapacitor(DiscreteApplication, KiCadImportableBlock):
482+
"""Capacitor attached to a digital line, that presents as an open model-wise."""
483+
484+
@override
485+
def symbol_pinning(self, symbol_name: str) -> Dict[str, BasePort]:
486+
assert symbol_name in ("Device:C", "Device:C_Small", "Device:C_Polarized", "Device:C_Polarized_Small")
487+
return {"1": self.io, "2": self.gnd}
488+
489+
def __init__(self, capacitance: RangeLike, *, exact_capacitance: BoolLike = False) -> None:
490+
super().__init__()
491+
492+
self.cap = self.Block(Capacitor(capacitance, voltage=RangeExpr(), exact_capacitance=exact_capacitance))
493+
self.gnd = self.Port(Ground(), [Common])
494+
self.io = self.Port(DigitalSink.empty(), [InOut]) # ideal open port
495+
496+
self.assign(self.cap.voltage, self.io.link().voltage - self.gnd.link().voltage)
497+
self.connect(self.gnd.net, self.cap.neg)
498+
self.connect(self.io, self.cap.pos.adapt_to(DigitalSink()))
499+
500+
def connected(
501+
self, gnd: Optional[Port[GroundLink]] = None, io: Optional[Port[DigitalLink]] = None
502+
) -> "DigitalCapacitor":
503+
"""Convenience function to connect both ports, returning this object so it can still be given a name."""
504+
if gnd is not None:
505+
cast(Block, builder.get_enclosing_block()).connect(gnd, self.gnd)
506+
if io is not None:
507+
cast(Block, builder.get_enclosing_block()).connect(io, self.io)
508+
return self
509+
510+
481511
class CombinedCapacitorElement(Capacitor): # to avoid an abstract part error
482512
@override
483513
def contents(self) -> None:

edg/abstract_parts/AbstractCrystal.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ def __init__(self, frequency: RangeLike) -> None:
2020
self.actual_frequency = self.Parameter(RangeExpr())
2121
self.actual_capacitance = self.Parameter(FloatExpr())
2222

23-
self.crystal = self.Port(CrystalPort(self.actual_frequency), [InOut]) # set by subclass
24-
self.gnd = self.Port(Ground(), [Common])
23+
self.xtal_in = self.Port(Passive())
24+
self.xtal_out = self.Port(Passive())
25+
self.gnd = self.Port(Passive())
2526

2627
@override
2728
def contents(self) -> None:
@@ -43,21 +44,21 @@ class CrystalStandardFootprint(StandardFootprint["Crystal"]):
4344

4445
FOOTPRINT_PINNING_MAP = {
4546
"Oscillator:Oscillator_SMD_Abracon_ASE-4Pin_3.2x2.5mm": lambda block: {
46-
"1": block.crystal.xtal_in,
47+
"1": block.xtal_in,
4748
"2": block.gnd,
48-
"3": block.crystal.xtal_out,
49+
"3": block.xtal_out,
4950
"4": block.gnd,
5051
},
5152
"Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mm": lambda block: {
52-
"1": block.crystal.xtal_in,
53+
"1": block.xtal_in,
5354
"2": block.gnd,
54-
"3": block.crystal.xtal_out,
55+
"3": block.xtal_out,
5556
"4": block.gnd,
5657
},
5758
"Crystal:Crystal_SMD_2520-4Pin_2.5x2.0mm": lambda block: {
58-
"1": block.crystal.xtal_in,
59+
"1": block.xtal_in,
5960
"2": block.gnd,
60-
"3": block.crystal.xtal_out,
61+
"3": block.xtal_out,
6162
"4": block.gnd,
6263
},
6364
}
@@ -91,8 +92,8 @@ def __init__(self, frequency: RangeLike) -> None:
9192
Should include load capacitors."""
9293
super().__init__()
9394

94-
self.crystal = self.Port(CrystalPort.empty(), [InOut])
9595
self.gnd = self.Port(Ground.empty(), [Common])
96+
self.crystal = self.Port(CrystalPort.empty(), [InOut])
9697

9798
self.frequency = self.ArgParameter(frequency)
9899

@@ -118,6 +119,9 @@ def contents(self) -> None:
118119

119120
self.package = self.Block(Crystal(self.frequency))
120121

122+
self.gnd.init_from(Ground())
123+
self.crystal.init_from(CrystalPort(self.package.actual_frequency))
124+
121125
cap_model = Capacitor(
122126
capacitance=(
123127
(self.package.actual_capacitance - self.PARASITIC_CAPACITANCE) * 2 * (1 - self.CAPACITOR_TOLERANCE),
@@ -127,10 +131,9 @@ def contents(self) -> None:
127131
)
128132
self.cap_a = self.Block(cap_model)
129133
self.cap_b = self.Block(cap_model)
130-
self.connect(self.crystal, self.package.crystal)
131-
self.connect(self.crystal.xtal_in, self.cap_a.pos)
132-
self.connect(self.crystal.xtal_out, self.cap_b.pos)
133-
self.connect(self.gnd, self.cap_a.neg.adapt_to(Ground()), self.cap_b.neg.adapt_to(Ground()), self.package.gnd)
134+
self.connect(self.crystal.xtal_in, self.package.xtal_in, self.cap_a.pos)
135+
self.connect(self.crystal.xtal_out, self.package.xtal_out, self.cap_b.pos)
136+
self.connect(self.gnd.net, self.cap_a.neg, self.cap_b.neg, self.package.gnd)
134137

135138

136139
class CeramicResonator(OscillatorReference):

edg/abstract_parts/AbstractDiodes.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def __init__(self, voltage: RangeLike):
194194
super().__init__()
195195

196196
self.pwr = self.Port(VoltageSink.empty(), [Power, InOut])
197-
self.gnd = self.Port(Ground.empty(), [Common])
197+
self.gnd = self.Port(Ground(), [Common])
198198

199199
self.voltage = self.ArgParameter(voltage)
200200

@@ -210,7 +210,7 @@ def contents(self) -> None:
210210
),
211211
self.pwr,
212212
)
213-
self.connect(self.diode.anode.adapt_to(Ground()), self.gnd)
213+
self.connect(self.gnd.net, self.diode.anode)
214214

215215

216216
@deprecated("Use AnalogClampResistor, which should be cheaper and cause less signal distortion")
@@ -222,7 +222,7 @@ def __init__(self, voltage: RangeLike):
222222

223223
self.diode = self.Block(ZenerDiode(zener_voltage=voltage))
224224

225-
self.gnd = self.Port(Ground.empty(), [Common])
225+
self.gnd = self.Port(Ground(), [Common])
226226
self.signal_in = self.Port(AnalogSink(), [Input])
227227
self.signal_out = self.Port(
228228
AnalogSource(
@@ -236,7 +236,7 @@ def __init__(self, voltage: RangeLike):
236236
self.assign(self.signal_in.current_draw, self.signal_out.link().current_drawn)
237237

238238
self.connect(self.signal_in.net, self.signal_out.net, self.diode.cathode)
239-
self.connect(self.diode.anode.adapt_to(Ground()), self.gnd)
239+
self.connect(self.gnd.net, self.diode.anode)
240240

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

edg/abstract_parts/AbstractLed.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def __init__(self, color: LedColorLike = Led.Any, *, current_draw: RangeLike = (
9898
self.target_current_draw = self.Parameter(RangeExpr(current_draw))
9999

100100
self.signal = self.Port(DigitalSink.empty(), [InOut])
101-
self.gnd = self.Port(Ground.empty(), [Common])
101+
self.gnd = self.Port(Ground(), [Common])
102102

103103
self.require(self.signal.current_draw.within((0, self.target_current_draw.upper())))
104104

@@ -118,7 +118,7 @@ def __init__(self, color: LedColorLike = Led.Any, *, current_draw: RangeLike = (
118118
)
119119

120120
self.connect(self.res.a, self.package.k)
121-
self.connect(self.res.b.adapt_to(Ground()), self.gnd)
121+
self.connect(self.gnd.net, self.res.b)
122122

123123

124124
class IndicatorLedArray(Light, GeneratorBlock):
@@ -227,7 +227,7 @@ def __init__(self, color: LedColorLike = Led.Any, *, current_draw: RangeLike = (
227227
self.target_current_draw = self.Parameter(RangeExpr(current_draw))
228228

229229
self.signal = self.Port(VoltageSink.empty(), [Power, InOut])
230-
self.gnd = self.Port(Ground.empty(), [Common])
230+
self.gnd = self.Port(Ground(), [Common])
231231

232232
self.require(self.signal.current_draw.within(current_draw))
233233

@@ -246,7 +246,7 @@ def __init__(self, color: LedColorLike = Led.Any, *, current_draw: RangeLike = (
246246
self.package.a.adapt_to(VoltageSink(current_draw=self.signal.link().voltage / self.res.actual_resistance)),
247247
)
248248
self.connect(self.res.a, self.package.k)
249-
self.connect(self.res.b.adapt_to(Ground()), self.gnd)
249+
self.connect(self.gnd.net, self.res.b)
250250

251251

252252
# TODO should there be some kind of abstract LED class, that works for both CA and CC type LEDs?

edg/abstract_parts/AbstractResistor.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,11 @@ def __init__(self, resistance: RangeLike) -> None:
228228

229229
self.res = self.Block(Resistor(resistance, 0 * Watt(tol=0))) # TODO automatically calculate power
230230

231-
self.gnd = self.Export(self.res.a.adapt_to(Ground()), [Common])
231+
self.gnd = self.Port(Ground(), [Common])
232232
self.io = self.Export(self.res.b.adapt_to(DigitalSource.pulldown_from_supply(self.gnd)), [InOut])
233233

234+
self.connect(self.gnd.net, self.res.a)
235+
234236
def connected(
235237
self, gnd: Optional[Port[GroundLink]] = None, io: Optional[Port[DigitalLink]] = None
236238
) -> "PulldownResistor":
@@ -327,7 +329,7 @@ def connected(
327329

328330

329331
class CurrentSenseResistor(DiscreteApplication, KiCadImportableBlock, GeneratorBlock):
330-
"""Current sense resistor with a power passthrough resistor and positive and negative sense temrinals."""
332+
"""Current sense resistor with a power passthrough resistor and positive and negative sense terminals."""
331333

332334
def __init__(self, resistance: RangeLike, sense_in_reqd: BoolLike = True) -> None:
333335
super().__init__()
@@ -372,6 +374,39 @@ def symbol_pinning(self, symbol_name: str) -> Dict[str, Port]:
372374
return {"1": self.pwr_in, "2": self.pwr_out, "sense_in": self.sense_in, "sense_out": self.sense_out}
373375

374376

377+
class AnalogSetpointResistor(DiscreteApplication, KiCadImportableBlock):
378+
"""AnalogSink-typed resistor that acts as a setpoint, aka bias or programming resistor"""
379+
380+
@override
381+
def symbol_pinning(self, symbol_name: str) -> Mapping[str, BasePort]:
382+
assert symbol_name in ("Device:R", "Device:R_Small")
383+
return {"1": self.io, "2": self.gnd}
384+
385+
def __init__(self, resistance: RangeLike) -> None:
386+
super().__init__()
387+
388+
self.gnd = self.Port(Ground())
389+
self.io = self.Port(AnalogSink(impedance=RangeExpr()), [InOut])
390+
391+
voltage = self.io.link().voltage - self.gnd.link().voltage
392+
self.res = self.Block(Resistor(resistance=resistance, power=voltage * voltage / resistance))
393+
self.actual_resistance = self.Parameter(RangeExpr(self.res.actual_resistance))
394+
395+
self.assign(self.io.impedance, self.res.actual_resistance)
396+
self.connect(self.gnd.net, self.res.a)
397+
self.connect(self.io.net, self.res.b)
398+
399+
def connected(
400+
self, gnd: Optional[Port[GroundLink]] = None, io: Optional[Port[AnalogLink]] = None
401+
) -> "AnalogSetpointResistor":
402+
"""Convenience function to connect both ports, returning this object so it can still be given a name."""
403+
if gnd is not None:
404+
cast(Block, builder.get_enclosing_block()).connect(gnd, self.gnd)
405+
if io is not None:
406+
cast(Block, builder.get_enclosing_block()).connect(io, self.io)
407+
return self
408+
409+
375410
class AnalogSeriesResistor(DiscreteApplication, KiCadImportableBlock):
376411
"""Analog passthrough series resistor"""
377412

edg/abstract_parts/AbstractSolidStateRelay.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def __init__(self) -> None:
5151
super().__init__()
5252

5353
self.signal = self.Port(DigitalSink.empty())
54-
self.gnd = self.Port(Ground.empty(), [Common])
54+
self.gnd = self.Port(Ground(), [Common])
5555

5656
self.pwr_in = self.Port(VoltageSink.empty())
5757
self.pwr_out = self.Port(VoltageSource.empty())
@@ -70,7 +70,7 @@ def __init__(self) -> None:
7070
self.ic.leda.adapt_to(DigitalSink(current_draw=self.signal.link().voltage / self.res.actual_resistance)),
7171
)
7272
self.connect(self.res.a, self.ic.ledk)
73-
self.connect(self.res.b.adapt_to(Ground()), self.gnd)
73+
self.connect(self.gnd.net, self.res.b)
7474

7575
self.connect(
7676
self.pwr_in,
@@ -114,7 +114,7 @@ def __init__(self) -> None:
114114
self.ic = self.Block(SolidStateRelay())
115115

116116
self.signal = self.Port(DigitalSink.empty())
117-
self.gnd = self.Port(Ground.empty(), [Common])
117+
self.gnd = self.Port(Ground(), [Common])
118118

119119
self.ain = self.Port(
120120
AnalogSink(
@@ -152,7 +152,7 @@ def __init__(self) -> None:
152152
self.ic.leda.adapt_to(DigitalSink(current_draw=self.signal.link().voltage / self.res.actual_resistance)),
153153
)
154154
self.connect(self.res.a, self.ic.ledk)
155-
self.connect(self.res.b.adapt_to(Ground()), self.gnd)
155+
self.connect(self.gnd.net, self.res.b)
156156

157157
self.connect(self.ain.net, self.ic.feta)
158158
self.connect(self.aout.net, self.ic.fetb)

edg/abstract_parts/AbstractSwitch.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class DigitalSwitch(HumanInterface):
9494
def __init__(self) -> None:
9595
super().__init__()
9696

97-
self.gnd = self.Port(Ground.empty(), [Common])
97+
self.gnd = self.Port(Ground(), [Common])
9898
self.out = self.Port(DigitalSource.empty(), [Output])
9999

100100
@override
@@ -103,7 +103,7 @@ def contents(self) -> None:
103103
self.package = self.Block(Switch(current=self.out.link().current_drawn, voltage=self.out.link().voltage))
104104

105105
self.connect(self.out, self.package.sw.adapt_to(DigitalSource.low_from_supply(self.gnd)))
106-
self.connect(self.gnd, self.package.com.adapt_to(Ground()))
106+
self.connect(self.gnd.net, self.package.com)
107107

108108

109109
@abstract_block_default(lambda: DigitalWrapperRotaryEncoder)
@@ -134,7 +134,8 @@ def contents(self) -> None:
134134
dio_model = DigitalSource.low_from_supply(self.gnd)
135135
self.connect(self.a, self.package.a.adapt_to(dio_model))
136136
self.connect(self.b, self.package.b.adapt_to(dio_model))
137-
self.connect(self.gnd, self.package.com.adapt_to(Ground()))
137+
self.gnd.init_from(Ground())
138+
self.connect(self.gnd.net, self.package.com)
138139

139140

140141
@abstract_block_default(lambda: DigitalWrapperRotaryEncoderWithSwitch)
@@ -194,7 +195,8 @@ def contents(self) -> None:
194195
self.connect(self.b, self.package.b.adapt_to(dio_model))
195196
self.connect(self.c, self.package.c.adapt_to(dio_model))
196197
self.connect(self.d, self.package.d.adapt_to(dio_model))
197-
self.connect(self.gnd, self.package.com.adapt_to(Ground()))
198+
self.gnd.init_from(Ground())
199+
self.connect(self.gnd.net, self.package.com)
198200

199201

200202
@abstract_block_default(lambda: DigitalWrapperDirectionSwitchWithCenter)

edg/abstract_parts/AbstractTestPoint.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ class GroundTestPoint(BaseTypedTestPoint, Block):
5959

6060
def __init__(self, *args: Any) -> None:
6161
super().__init__(*args)
62-
self.io = self.Port(Ground.empty(), [InOut])
63-
self.connect(self.io, self.tp.io.adapt_to(Ground()))
62+
self.io: Ground = self.Port(Ground(), [InOut])
63+
self.connect(self.io.net, self.tp.io)
6464

6565
def connected(self, io: Port[GroundLink]) -> "GroundTestPoint":
6666
cast(Block, builder.get_enclosing_block()).connect(io, self.io)

edg/abstract_parts/AbstractTvsDiode.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def __init__(self, working_voltage: RangeLike):
3838
super().__init__()
3939

4040
self.pwr = self.Port(VoltageSink.empty(), [Power, InOut])
41-
self.gnd = self.Port(Ground.empty(), [Common])
41+
self.gnd = self.Port(Ground(), [Common])
4242

4343
self.working_voltage = self.ArgParameter(working_voltage)
4444

@@ -54,7 +54,7 @@ def contents(self) -> None:
5454
),
5555
self.pwr,
5656
)
57-
self.connect(self.diode.anode.adapt_to(Ground()), self.gnd)
57+
self.connect(self.gnd.net, self.diode.anode)
5858

5959

6060
class DigitalTvsDiode(Protection):
@@ -64,7 +64,7 @@ def __init__(self, working_voltage: RangeLike, *, capacitance: RangeLike = Range
6464
super().__init__()
6565

6666
self.io = self.Port(DigitalSink.empty(), [InOut])
67-
self.gnd = self.Port(Ground.empty(), [Common])
67+
self.gnd = self.Port(Ground(), [Common])
6868

6969
self.working_voltage = self.ArgParameter(working_voltage)
7070
self.capacitance = self.ArgParameter(capacitance)
@@ -81,4 +81,4 @@ def contents(self) -> None:
8181
),
8282
self.io,
8383
)
84-
self.connect(self.diode.anode.adapt_to(Ground()), self.gnd)
84+
self.connect(self.gnd.net, self.diode.anode)

0 commit comments

Comments
 (0)