Skip to content

Commit b690c01

Browse files
authored
Support arbitrary diodes in DiodePowerMerge (#491)
Replaces the static 2-input version with a generator.
1 parent 76ca6e7 commit b690c01

2 files changed

Lines changed: 94 additions & 36 deletions

File tree

edg/parts/PowerConditioning.py

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import warnings
2-
from typing import Optional, cast, Any
2+
from typing import Optional, Any
33

44
from typing_extensions import override
55

@@ -42,49 +42,63 @@ def __init__(self, voltage_drop: RangeLike, reverse_recovery_time: RangeLike = R
4242
self.connect(self.pwr_out.net, self.pwr_in.net, self.diode.cathode)
4343

4444

45-
class DiodePowerMerge(PowerConditioner, Block):
46-
"""Diode power merge block for two voltage sources."""
45+
class DiodePowerMerge(PowerConditioner, GeneratorBlock):
46+
"""Diode power merge block for multiple voltage sources."""
4747

48-
def __init__(self, voltage_drop: RangeLike, reverse_recovery_time: RangeLike = (0, float("inf"))) -> None:
48+
def __init__(self, voltage_drop: RangeLike, reverse_recovery_time: RangeLike = RangeExpr.ALL) -> None:
4949
super().__init__()
50+
self.voltage_drop = self.ArgParameter(voltage_drop)
51+
self.reverse_recovery_time = self.ArgParameter(reverse_recovery_time)
5052

51-
self.pwr_in1 = self.Port(VoltageSink(current_draw=RangeExpr()))
52-
self.pwr_in2 = self.Port(VoltageSink(current_draw=RangeExpr()))
53-
self.pwr_out = self.Port(
54-
VoltageSource( # use the spec voltage drop to avoid circular dependencies downstream
55-
voltage_out=(self.pwr_in1.link().voltage - voltage_drop).hull(
56-
self.pwr_in2.link().voltage - voltage_drop
53+
self.pwr_ins = self.Port(Vector(VoltageSink.empty()))
54+
self.pwr_out = self.Port(VoltageSource(voltage_out=RangeExpr()))
55+
self.generator_param(self.pwr_ins.requested())
56+
57+
@override
58+
def generate(self) -> None:
59+
super().generate()
60+
61+
input_hull = self.pwr_ins.map_extract(lambda pwr_in: pwr_in.link().voltage).hull()
62+
# use the spec voltage drop to avoid circular dependencies downstream
63+
self.assign(self.pwr_out.voltage_out, input_hull - self.voltage_drop)
64+
65+
requested = self.get(self.pwr_ins.requested())
66+
assert len(requested) > 0, "power inputs required"
67+
68+
self.diodes = ElementDict[Diode]()
69+
self.pwr_ins.defined()
70+
for name in requested:
71+
pwr_in = self.pwr_ins.append_elt(VoltageSink(current_draw=self.pwr_out.link().current_drawn), name)
72+
self.diodes[name] = diode = self.Block(
73+
Diode(
74+
reverse_voltage=(0, self.pwr_out.voltage_out.upper() - pwr_in.link().voltage.lower()),
75+
current=self.pwr_out.link().current_drawn,
76+
voltage_drop=self.voltage_drop,
77+
reverse_recovery_time=self.reverse_recovery_time,
5778
)
5879
)
59-
)
80+
self.connect(pwr_in.net, diode.anode)
81+
self.connect(self.pwr_out.net, diode.cathode)
6082

61-
output_lower = (
62-
self.pwr_in1.link().voltage.lower().min(self.pwr_in2.link().voltage.lower())
63-
- RangeExpr._to_expr_type(voltage_drop).upper()
64-
)
65-
self.diode1 = self.Block(
66-
Diode(
67-
reverse_voltage=(0, self.pwr_in1.link().voltage.upper() - output_lower),
68-
current=self.pwr_out.link().current_drawn,
69-
voltage_drop=voltage_drop,
70-
reverse_recovery_time=reverse_recovery_time,
83+
def __getattr__(self, item: str) -> Any:
84+
if item == "pwr_in1":
85+
warnings.warn(
86+
f"Use pwr_ins.request(...) instead.",
87+
DeprecationWarning,
88+
stacklevel=2,
7189
)
72-
)
73-
self.diode2 = self.Block(
74-
Diode(
75-
reverse_voltage=(0, self.pwr_in2.link().voltage.upper() - output_lower),
76-
current=self.pwr_out.link().current_drawn,
77-
voltage_drop=voltage_drop,
78-
reverse_recovery_time=reverse_recovery_time,
90+
return self.pwr_ins.request("1")
91+
elif item == "pwr_in2":
92+
warnings.warn(
93+
f"Use pwr_ins.request(...) instead.",
94+
DeprecationWarning,
95+
stacklevel=2,
7996
)
80-
)
81-
82-
self.assign(self.pwr_in1.current_draw, self.pwr_out.link().current_drawn)
83-
self.assign(self.pwr_in2.current_draw, self.pwr_out.link().current_drawn)
84-
85-
self.connect(self.pwr_in1.net, self.diode1.anode)
86-
self.connect(self.pwr_in2.net, self.diode2.anode)
87-
self.connect(self.pwr_out.net, self.diode1.cathode, self.diode2.cathode)
97+
return self.pwr_ins.request("2")
98+
else:
99+
raise AttributeError(
100+
item
101+
) # ideally we'd use super().__getattr__(...), but that's not defined in base classes
88102

89103

90104
class PriorityPowerOr(PowerConditioner, KiCadSchematicBlock, Block):

edg/parts/test_diodemerge.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import unittest
2+
3+
from .JlcDiode import *
4+
from .PowerConditioning import DiodePowerMerge
5+
6+
7+
class DiodeMergeTestTop(DesignTop):
8+
def __init__(self) -> None:
9+
super().__init__()
10+
self.dut = self.Block(DiodePowerMerge(voltage_drop=(0, 1) * Volt))
11+
(self.srca,), _ = self.chain(
12+
self.dut.pwr_ins.request(), self.Block(DummyVoltageSource(voltage_out=(12, 14) * Volt))
13+
)
14+
(self.srcb,), _ = self.chain(
15+
self.dut.pwr_ins.request(), self.Block(DummyVoltageSource(voltage_out=(4, 5) * Volt))
16+
)
17+
(self.sink,), _ = self.chain(self.dut.pwr_out, self.Block(DummyVoltageSink(current_draw=(0.5, 1.5) * Amp)))
18+
19+
@override
20+
def refinements(self) -> Refinements:
21+
return Refinements(
22+
class_refinements=[
23+
(Diode, CustomDiode),
24+
],
25+
class_values=[
26+
(CustomDiode, ["footprint_spec"], "Diode_SMD:D_SOD-123"),
27+
],
28+
)
29+
30+
31+
class DiodeMergeTestCase(unittest.TestCase):
32+
def test_diode_merge(self) -> None:
33+
compiled = ScalaCompiler.compile(DiodeMergeTestTop)
34+
35+
self.assertEqual(compiled.get_value(["dut", "pwr_out", "voltage_out"]), Range(3.0, 14.0))
36+
self.assertEqual(compiled.get_value(["dut", "pwr_ins", "0", "current_draw"]), Range(0.5, 1.5))
37+
self.assertEqual(compiled.get_value(["dut", "pwr_ins", "1", "current_draw"]), Range(0.5, 1.5))
38+
self.assertEqual(compiled.get_value(["dut", "diodes[0]", "fp_footprint"]), "Diode_SMD:D_SOD-123")
39+
self.assertEqual(compiled.get_value(["dut", "diodes[0]", "current"]), Range(0.5, 1.5))
40+
self.assertEqual(compiled.get_value(["dut", "diodes[0]", "reverse_voltage"]), Range(0, 2.0))
41+
self.assertEqual(compiled.get_value(["dut", "diodes[1]", "fp_footprint"]), "Diode_SMD:D_SOD-123")
42+
self.assertEqual(compiled.get_value(["dut", "diodes[1]", "current"]), Range(0.5, 1.5))
43+
self.assertEqual(compiled.get_value(["dut", "diodes[1]", "reverse_voltage"]), Range(0, 10.0))
44+
self.assertEqual(compiled.get_value(["dut", "diodes[2]", "fp_footprint"]), None) # doesn't exist

0 commit comments

Comments
 (0)