Skip to content

Commit 3d4f75f

Browse files
authored
Conditional optional ports (#510)
Simplifies some of the model port-optional from the wrapper microcontroller refactors. Resolves #507
1 parent 1392be7 commit 3d4f75f

8 files changed

Lines changed: 63 additions & 52 deletions

File tree

‎edg/core/Blocks.py‎

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ def __init__(self) -> None:
289289
self._param_docs = IdentityDict[ConstraintExpr, str]()
290290

291291
self._ports: SubElementDict[BasePort] = self.manager.new_dict(BasePort)
292-
self._required_ports = IdentitySet[BasePort]()
292+
self._required_ports = IdentityDict[BasePort, Optional[BoolExpr]]() # port -> precondition for required
293293
self._port_docs = IdentityDict[BasePort, str]()
294294

295295
self._connects = self.manager.new_dict(Connection, anon_prefix="anon_link")
@@ -397,12 +397,20 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None:
397397
for name, port in self._ports.items():
398398
if port in self._required_ports:
399399
if isinstance(port, Port):
400-
port.is_connected()._populate_expr_proto(edgir.add_pair(pb.constraints, f"(reqd){name}"), ref_map)
400+
connected_condition = port.is_connected()
401401
elif isinstance(port, Vector):
402-
(port.length() > 0)._populate_expr_proto(edgir.add_pair(pb.constraints, f"(reqd){name}"), ref_map)
402+
connected_condition = port.length() > 0
403403
else:
404404
raise ValueError(f"unknown non-optional port type {port}")
405405

406+
precondition = self._required_ports[port]
407+
if precondition is not None:
408+
self._check_constraint(precondition)
409+
connected_condition = precondition.implies(connected_condition)
410+
411+
# add required constraints first
412+
connected_condition._populate_expr_proto(edgir.add_pair(pb.constraints, f"(reqd){name}"), ref_map)
413+
406414
self._constraints.finalize() # needed for source locator generation
407415

408416
self._add_doc_metadata()
@@ -525,7 +533,7 @@ def assign(
525533

526534
T = TypeVar("T", bound=BasePort)
527535

528-
def Port(self, tpe: T, *, optional: bool = False, doc: Optional[str] = None) -> T:
536+
def Port(self, tpe: T, *, optional: BoolLike = False, doc: Optional[str] = None) -> T:
529537
"""Registers a port for this Block"""
530538
if self._elaboration_state != BlockElaborationState.init:
531539
raise BlockDefinitionError(
@@ -539,8 +547,13 @@ def Port(self, tpe: T, *, optional: bool = False, doc: Optional[str] = None) ->
539547
elt = tpe._bind(self)
540548
self._ports.register(elt)
541549

542-
if not optional:
543-
self._required_ports.add(elt)
550+
if isinstance(optional, bool):
551+
if not optional:
552+
self._required_ports[elt] = None
553+
elif isinstance(optional, BoolExpr):
554+
self._required_ports[elt] = ~optional
555+
else:
556+
raise EdgTypeError(f"optional flag to Port(...)", optional, (bool, BoolExpr))
544557

545558
if doc is not None:
546559
self._port_docs[elt] = doc

‎edg/core/HierarchyBlock.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ def ArgParameter(self, param: CastableType, *, doc: Optional[str] = None) -> Con
672672
T = TypeVar("T", bound=BasePort)
673673

674674
@override
675-
def Port(self, tpe: T, tags: Iterable[PortTag] = [], *, optional: bool = False, doc: Optional[str] = None) -> T:
675+
def Port(self, tpe: T, tags: Iterable[PortTag] = [], *, optional: BoolLike = False, doc: Optional[str] = None) -> T:
676676
"""Registers a port for this Block"""
677677
if not isinstance(tpe, (Port, Vector)):
678678
raise NotImplementedError("Non-Port (eg, Vector) ports not (yet?) supported")

‎edg/core/test_block.py‎

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def __init__(self) -> None:
1414
self.base_float = self.Parameter(FloatExpr())
1515
self.base_port = self.Port(TestPortBase()) # required to test required constraint
1616
self.base_port_constr = self.Port(TestPortBase(self.base_float), optional=True)
17+
self.base_port_optional = self.Port(TestPortBase(), optional=self.base_port.is_connected())
1718

1819

1920
@abstract_block_default(lambda: TestBlock)
@@ -56,23 +57,36 @@ def test_param_def(self) -> None:
5657
self.assertTrue(self.pb.params[0].value.HasField("floating"))
5758

5859
def test_port_def(self) -> None:
59-
self.assertEqual(len(self.pb.ports), 2)
60+
self.assertEqual(len(self.pb.ports), 3)
6061
self.assertEqual(self.pb.ports[0].name, "base_port")
6162
self.assertEqual(self.pb.ports[0].value.lib_elem.target.name, "edg.core.test_elaboration_common.TestPortBase")
6263
self.assertEqual(self.pb.ports[1].name, "base_port_constr")
6364
self.assertEqual(self.pb.ports[1].value.lib_elem.target.name, "edg.core.test_elaboration_common.TestPortBase")
65+
self.assertEqual(self.pb.ports[2].name, "base_port_optional")
66+
self.assertEqual(self.pb.ports[2].value.lib_elem.target.name, "edg.core.test_elaboration_common.TestPortBase")
6467

65-
def test_connected_constraint(self) -> None:
68+
def test_required_constraint(self) -> None:
6669
expected_constr = edgir.ValueExpr()
6770
expected_constr.ref.steps.add().name = "base_port"
6871
expected_constr.ref.steps.add().reserved_param = edgir.IS_CONNECTED
6972
self.assertEqual(self.pb.constraints[0].name, "(reqd)base_port")
7073
self.assertEqual(self.pb.constraints[0].value, expected_constr)
7174

75+
def test_optional_required_constraint(self) -> None:
76+
expected_constr = edgir.ValueExpr()
77+
expected_constr.binary.op = edgir.BinaryExpr.Op.IMPLIES
78+
expected_constr.binary.lhs.unary.op = edgir.UnaryExpr.Op.NOT
79+
expected_constr.binary.lhs.unary.val.ref.steps.add().name = "base_port"
80+
expected_constr.binary.lhs.unary.val.ref.steps.add().reserved_param = edgir.IS_CONNECTED
81+
expected_constr.binary.rhs.ref.steps.add().name = "base_port_optional"
82+
expected_constr.binary.rhs.ref.steps.add().reserved_param = edgir.IS_CONNECTED
83+
self.assertEqual(self.pb.constraints[1].name, "(reqd)base_port_optional")
84+
self.assertEqual(self.pb.constraints[1].value, expected_constr)
85+
7286
def test_port_init(self) -> None:
73-
self.assertEqual(self.pb.constraints[1].name, "(init)base_port_constr.float_param")
87+
self.assertEqual(self.pb.constraints[2].name, "(init)base_port_constr.float_param")
7488
self.assertEqual(
75-
self.pb.constraints[1].value, edgir.AssignRef(["base_port_constr", "float_param"], ["base_float"])
89+
self.pb.constraints[2].value, edgir.AssignRef(["base_port_constr", "float_param"], ["base_float"])
7690
)
7791

7892

@@ -114,10 +128,11 @@ def test_superclass(self) -> None:
114128
self.assertEqual(self.pb.ports[1].value.lib_elem.target.name, "edg.core.test_elaboration_common.TestPortBase")
115129

116130
def test_port_def(self) -> None:
117-
self.assertEqual(len(self.pb.ports), 3)
118-
self.assertEqual(self.pb.ports[2].name, "port_lit")
119-
self.assertEqual(self.pb.ports[2].value.lib_elem.target.name, "edg.core.test_elaboration_common.TestPortBase")
131+
self.assertEqual(len(self.pb.ports), 4)
132+
self.assertEqual(self.pb.ports[3].name, "port_lit")
133+
self.assertEqual(self.pb.ports[3].value.lib_elem.target.name, "edg.core.test_elaboration_common.TestPortBase")
120134
self.assertEqual(self.pb.constraints[0].name, "(reqd)base_port")
135+
self.assertEqual(self.pb.constraints[1].name, "(reqd)base_port_optional")
121136

122137
def test_param_def(self) -> None:
123138
self.assertEqual(len(self.pb.params), 4)
@@ -129,32 +144,32 @@ def test_param_def(self) -> None:
129144
self.assertTrue(self.pb.params[3].value.HasField("array"))
130145

131146
def test_superclass_init(self) -> None:
132-
self.assertEqual(self.pb.constraints[1].name, "(init)base_port_constr.float_param")
147+
self.assertEqual(self.pb.constraints[2].name, "(init)base_port_constr.float_param")
133148
self.assertEqual(
134-
self.pb.constraints[1].value, edgir.AssignRef(["base_port_constr", "float_param"], ["base_float"])
149+
self.pb.constraints[2].value, edgir.AssignRef(["base_port_constr", "float_param"], ["base_float"])
135150
)
136151

137152
def test_port_init(self) -> None:
138-
self.assertEqual(self.pb.constraints[2].name, "(init)port_lit.float_param")
153+
self.assertEqual(self.pb.constraints[3].name, "(init)port_lit.float_param")
139154

140155
def test_param_init(self) -> None:
141-
self.assertEqual(self.pb.constraints[3].name, "(init)range_init")
142-
self.assertEqual(self.pb.constraints[3].value, edgir.AssignLit(["range_init"], Range(-4.2, -1.3)))
156+
self.assertEqual(self.pb.constraints[4].name, "(init)range_init")
157+
self.assertEqual(self.pb.constraints[4].value, edgir.AssignLit(["range_init"], Range(-4.2, -1.3)))
143158

144159
expected_assign = edgir.ValueExpr()
145160
expected_assign.assign.dst.CopyFrom(edgir.LocalPathList(["array_init"]))
146161
expected_array = expected_assign.assign.src.array
147162
expected_array.vals.add().CopyFrom(edgir.lit_to_expr(False))
148163
expected_array.vals.add().CopyFrom(edgir.lit_to_expr(True))
149164
expected_array.vals.add().CopyFrom(edgir.lit_to_expr(False))
150-
self.assertEqual(self.pb.constraints[4].name, "(init)array_init")
151-
self.assertEqual(self.pb.constraints[4].value, expected_assign)
165+
self.assertEqual(self.pb.constraints[5].name, "(init)array_init")
166+
self.assertEqual(self.pb.constraints[5].value, expected_assign)
152167

153168
expected_assign = edgir.ValueExpr()
154169
expected_assign.assign.dst.CopyFrom(edgir.LocalPathList(["array_empty"]))
155170
expected_assign.assign.src.array.SetInParent()
156-
self.assertEqual(self.pb.constraints[5].name, "(init)array_empty")
157-
self.assertEqual(self.pb.constraints[5].value, expected_assign)
171+
self.assertEqual(self.pb.constraints[6].name, "(init)array_empty")
172+
self.assertEqual(self.pb.constraints[6].value, expected_assign)
158173

159174
def test_docs(self) -> None:
160175
self.assertEqual(self.pb.meta.members.node["_docs"].members.node[""].text_leaf, "Test docstring")

‎edg/parts/microcontroller/Esp32.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ class Esp32_Wroom_32_Device(
6262
def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [], **kwargs: Any) -> None:
6363
super().__init__(**kwargs)
6464

65-
self._model = self.ArgParameter(_model)
6665
self._allowed_pins = self.ArgParameter(_allowed_pins)
6766
self.generator_param(self._allowed_pins)
6867

@@ -85,8 +84,7 @@ def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = []
8584
pullup_capable=True,
8685
pulldown_capable=True,
8786
)
88-
self.chip_pu = self.Port(self._dio_model, optional=True)
89-
self.require((~self._model).implies(self.chip_pu.is_connected()), "chip_pu must not be left floating")
87+
self.chip_pu = self.Port(self._dio_model, optional=_model)
9088
# section 2.4, table 5: strapping IOs that need a fixed value to boot, TODO currently not allocatable post-boot
9189
self.io0 = self.Port(self._dio_model, optional=True) # default pullup (SPI boot), set low to download boot
9290
self.io2 = self.Port(

‎edg/parts/microcontroller/Esp32c3.py‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ def _system_pinmap(self) -> Mapping[Union[Iterable[str], str], Union[Passive, Ha
5858
def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [], **kwargs: Any) -> None:
5959
super().__init__(**kwargs)
6060

61-
self._model = self.ArgParameter(_model)
6261
self._allowed_pins = self.ArgParameter(_allowed_pins)
6362
self.generator_param(self._allowed_pins)
6463

@@ -93,9 +92,9 @@ def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = []
9392

9493
# 10ppm requirement from ESP32-C3-WROOM schematic, and in ESP32 hardware design guidelines
9594
self.xtal = self.Port( # vdda domain assumed
96-
CrystalDriver(frequency_limits=40 * MHertz(tol=10e-6), voltage_out=self.vdda.link().voltage), optional=True
95+
CrystalDriver(frequency_limits=40 * MHertz(tol=10e-6), voltage_out=self.vdda.link().voltage),
96+
optional=_model,
9797
)
98-
self.require((~self._model).implies(self.xtal.is_connected()))
9998

10099
# section 2.4: strapping IOs that need a fixed value to boot, and currently can't be allocated as GPIO
101100
# TODO model from different 3.3v domains
@@ -108,19 +107,17 @@ def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = []
108107
pullup_capable=True,
109108
pulldown_capable=True,
110109
)
111-
self.en = self.Port(DigitalSink.from_bidir(self._dio_model), optional=True) # needs external pullup
112-
self.io2 = self.Port(self._dio_model, optional=True) # needs external pullup; affects IO glitching on boot
113-
self.io8 = self.Port(self._dio_model, optional=True) # needs external pullup, required for download boot
110+
self.en = self.Port(DigitalSink.from_bidir(self._dio_model), optional=_model) # needs external pullup
111+
self.io2 = self.Port(self._dio_model, optional=_model) # needs external pullup; affects IO glitching on boot
112+
self.io8 = self.Port(self._dio_model, optional=_model) # needs external pullup, required for download boot
114113
self.io9 = self.Port(
115114
self._dio_model, optional=True
116115
) # internally pulled up for SPI boot, connect to GND for download
117-
self.require((~self._model).implies(self.en.is_connected() & self.io2.is_connected() & self.io8.is_connected()))
118116

119117
# similarly, the programming UART is fixed and allocated separately
120118
self.uart0 = self.Port(UartPort(self._dio_model), optional=True)
121119

122-
self.lna_in = self.Port(Passive(), optional=True)
123-
self.require((~self._model).implies(self.lna_in.is_connected()))
120+
self.lna_in = self.Port(Passive(), optional=_model)
124121

125122
@override
126123
def generate(self) -> None:

‎edg/parts/microcontroller/Esp32s3.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ class Esp32s3_Wroom_1_Device(
6565
def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [], **kwargs: Any) -> None:
6666
super().__init__(**kwargs)
6767

68-
self._model = self.ArgParameter(_model)
6968
self._allowed_pins = self.ArgParameter(_allowed_pins)
7069
self.generator_param(self._allowed_pins)
7170

@@ -89,8 +88,7 @@ def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = []
8988
pulldown_capable=True,
9089
)
9190

92-
self.chip_pu = self.Port(self._dio_model, optional=True)
93-
self.require((~self._model).implies(self.chip_pu.is_connected()), "chip_pu must not be left floating")
91+
self.chip_pu = self.Port(self._dio_model, optional=_model)
9492
self.io0 = self.Port(
9593
self._dio_model, optional=True
9694
) # table 2-11, default pullup (SPI boot), set low to download boot

‎edg/parts/microcontroller/Rp2040.py‎

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,10 @@ def __init__(self, *, _model: BoolLike = False, _allowed_pins: ArrayStringLike =
111111
pulldown_capable=True,
112112
)
113113

114-
self.qspi = self.Port(SpiController(self._dio_std_model), optional=True) # TODO actually QSPI
115-
self.qspi_cs = self.Port(self._dio_std_model, optional=True)
116-
self.qspi_sd2 = self.Port(self._dio_std_model, optional=True)
117-
self.qspi_sd3 = self.Port(self._dio_std_model, optional=True)
118-
self.require(
119-
(~self._model).implies(
120-
self.qspi.is_connected()
121-
& self.qspi_cs.is_connected()
122-
& self.qspi_sd2.is_connected()
123-
& self.qspi_sd3.is_connected()
124-
),
125-
"SPI memory required",
126-
)
114+
self.qspi = self.Port(SpiController(self._dio_std_model), optional=self._model) # TODO actually QSPI
115+
self.qspi_cs = self.Port(self._dio_std_model, optional=self._model)
116+
self.qspi_sd2 = self.Port(self._dio_std_model, optional=self._model)
117+
self.qspi_sd3 = self.Port(self._dio_std_model, optional=self._model)
127118

128119
self.xosc = self.Port(
129120
CrystalDriver(

‎edg/parts/microcontroller/Stm32f303.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import *
22

3-
from deprecated import deprecated
43
from typing_extensions import override
54

65
from ...circuits import *

0 commit comments

Comments
 (0)