Skip to content

Commit 86ac330

Browse files
authored
Refactor microcontroller wrappers, refactor nRF52 devices (#502)
Refactors the microcontroller wrapper: - Adds a BaseIoControllerWrapper utility class, which has a utility to perform the remapping from device-space to model-space. - Remapping matches on pin types to require all leaf IOs are part of the remapping dict - Refactors _export_tap_ios into this class - Restructures the remapping into utility functions and adds a convenience wrapper to use in microcontroller blocks. - Refactors STM32s, LPC1549 to eliminate BaseIoControllerExportable and use explicit _wrap_inner Note, ideally both the from and to remapping would be put in the wrapping class, however the to remapping requires the model block to be solved, which the current generator structure does not support. A future structure that enables this might be separating param-only generators from structural (circuit) generators. Updates the RP2040 to use this new style, adding a _model param to the device to remove requirement for some ports. Migrates the nRF52 devices to use microcontroller wrappers. Also removes the implicit Power tag from iovdd, as consistent with avoiding implicit tags on multiple power rails. Adds a unit test based on the RP2040 that tests automatic assignment, specified assignment (checking remapping), and overflow. Also refactors the base pin map util to: - save the original pin name (like GPIO0) and persist it across remaps (to pin numbers) - always display this instead of the "resource name" TODOs for future PRs: - Deprecate BaseIoControllerExportable - Refactor other microcontrollers
1 parent af892cf commit 86ac330

29 files changed

Lines changed: 1020 additions & 605 deletions
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
from typing import *
2+
3+
from ..electronics_interfaces import *
4+
from .IoController import BaseIoController
5+
6+
7+
class BaseIoControllerWrapped(BaseIoController):
8+
"""Base class for IoController wrapped blocks, particularly footprints that are used
9+
with an outer WrapperSubboardBlock to implement e.g. a dev board or module around a modeling subcircuit.
10+
11+
Provides some utility functions to remap pin assignments from the model to the footprint.
12+
13+
In this class, pin_assigns is treated as the model's pin assigns and internally remapped.
14+
"""
15+
16+
@staticmethod
17+
def _remap_model_pin_assigns(
18+
remapping: Dict[str, str], pin_assigns: List[str]
19+
) -> Dict[str, Tuple[Optional[str], Optional[str]]]:
20+
"""Given a remapping dict and a list of pin assigns, returns the mapping as a dict with the remapping applied.
21+
The output is (pin name, pin number), with both being optional.
22+
23+
Assigns not present in the remapping dict are passed unchanged, eg for non-pin-assigns like bundle containers.
24+
25+
Internal utility.
26+
"""
27+
remapped_assigns: Dict[str, Tuple[Optional[str], Optional[str]]] = {}
28+
for assign in pin_assigns:
29+
name, pindef = assign.split("=")
30+
name = name.strip()
31+
split = pindef.split(",")
32+
split = [s.strip() for s in split]
33+
if len(split) == 1: # should be a bundle name, since pins should have two parts
34+
assert name not in remapping
35+
remapped_assigns[name] = (split[0], None)
36+
elif len(split) == 2:
37+
pinname, pinnum = split[0], split[1]
38+
assert pinname in remapping
39+
remapped_assigns[name] = (pinname, remapping[pinname])
40+
else:
41+
raise ValueError(f"unable to parse assign {assign}")
42+
43+
return remapped_assigns
44+
45+
def _generator_pin_dict(self) -> Dict[str, Port]:
46+
"""Returns a dict of pin name to port for all IO ports, recursing into bundles Ports.
47+
This includes both the bundle container Port and their (recursive) contents.
48+
Users of this may want to filter by the port type.
49+
50+
For Vector-typed IO ports, this generates the subports and must be authoritative.
51+
This cannot be used with anything else that generates vector sub-ports.
52+
This must be a GeneratorBlock with requested() declared as generator params.
53+
54+
Internal utility.
55+
"""
56+
assert isinstance(self, GeneratorBlock)
57+
58+
pin_dict: Dict[str, Port] = {}
59+
60+
def recurse_port(port: Port, prefix: str) -> None:
61+
assert prefix not in pin_dict, f"duplicate pin name {prefix}"
62+
pin_dict[prefix] = port
63+
64+
for subport_name, subport in port._ports.items():
65+
recurse_port(subport, f"{prefix}.{subport_name}")
66+
67+
for io_port in self._io_ports:
68+
if isinstance(io_port, Vector):
69+
io_port.defined()
70+
for subport_name in self.get(io_port.requested()):
71+
subport = io_port.append_elt(io_port._tpe.empty(), subport_name)
72+
recurse_port(subport, subport_name)
73+
elif isinstance(io_port, Port):
74+
if self.get(io_port.is_connected()):
75+
port_name = io_port._name_from(self)
76+
recurse_port(io_port, port_name)
77+
else:
78+
raise NotImplementedError(f"unknown port type {io_port}")
79+
80+
return pin_dict
81+
82+
def _remap_to_footprint_pinning(
83+
self, pin_assigns: Dict[str, Tuple[Optional[str], Optional[str]]], pin_dict: Dict[str, Port]
84+
) -> Dict[str, HasPassivePort]:
85+
"""Generates pinning that can be passed into a footprint, given the pin assign dict from _remap_pin_assigns_list
86+
and pin dict from _generator_pin_dict.
87+
88+
This requires all pins to be assigned.
89+
90+
Internal utility.
91+
"""
92+
pinning: Dict[str, HasPassivePort] = {}
93+
94+
for name, assign in pin_assigns.items():
95+
assert name in pin_dict
96+
port = pin_dict[name]
97+
if not isinstance(port, HasPassivePort):
98+
continue # ignore non-leaf ports
99+
assert assign[1] is not None, f"pin {name} missing pin number assignment"
100+
pinning[assign[1]] = port
101+
102+
return pinning
103+
104+
@staticmethod
105+
def _remap_assigns_to_value(assigns: Dict[str, Tuple[Optional[str], Optional[str]]]) -> List[str]:
106+
"""Given a dict of pin assigns from _remap_pinning_assigns, returns a list of assign strings
107+
for use in self.actual_pin_assigns.
108+
109+
Internal utility.
110+
"""
111+
pin_assigns: List[str] = []
112+
for name, assign in assigns.items():
113+
if assign[0] is not None and assign[1] is not None:
114+
pin_assigns.append(f"{name}={assign[0]}, {assign[1]}")
115+
elif assign[0] is not None:
116+
pin_assigns.append(f"{name}={assign[0]}")
117+
elif assign[1] is not None:
118+
pin_assigns.append(f"{name}={assign[1]}")
119+
else:
120+
raise ValueError(f"invalid assign for {name}: {assign}")
121+
return pin_assigns
122+
123+
def _make_pinning(
124+
self, fixed_pinning: Dict[str, Union[Passive, HasPassivePort]], remapping: Dict[str, str]
125+
) -> Dict[str, Union[Passive, HasPassivePort]]:
126+
"""Creates the footprint pinning dict for the wrapped footprint, given the fixed pinning and
127+
remapping from pin name to this footprint's pin number.
128+
This generates pinning for all BaseIoController IOs.
129+
As a side effect, this assigns self.actual_pin_assigns.
130+
131+
This wraps the above helpers, this should be used in most cases.
132+
"""
133+
remapped_pin_assigns = self._remap_model_pin_assigns(remapping, self.get(self.pin_assigns))
134+
pin_dict = self._generator_pin_dict()
135+
fixed_pinning.update(self._remap_to_footprint_pinning(remapped_pin_assigns, pin_dict))
136+
self.assign(self.actual_pin_assigns, self._remap_assigns_to_value(remapped_pin_assigns))
137+
return fixed_pinning
138+
139+
140+
class BaseIoControllerWrapper(BaseIoController):
141+
"""Base class for a block that contains a BaseIoControllerWrapped as the physical footprint
142+
as well as a non-physical device model.
143+
144+
This provides utilities to remap pin assignments from the device specification to the model.
145+
"""
146+
147+
def _export_tap_ios_inner(self, inner: "BaseIoController") -> None:
148+
"""Export-taps all IO ports from some inner BaseIoController.
149+
This must be a SubboardBlock to support the export_tap connection.
150+
This must be called in contents() or generate(), after IOs have been defined."""
151+
from ..core.Blocks import BlockElaborationState
152+
153+
assert isinstance(self, WrapperSubboardBlock)
154+
assert self._elaboration_state in (
155+
BlockElaborationState.contents,
156+
BlockElaborationState.generate,
157+
), "can only run in contents() or generate()"
158+
159+
inner_ios_by_type = {self._type_of_io(io_port): io_port for io_port in inner._io_ports}
160+
for self_io in self._io_ports:
161+
self_io_type = self._type_of_io(self_io)
162+
assert self_io_type in inner_ios_by_type, f"inner missing IO of type {self_io_type}"
163+
inner_io = inner_ios_by_type[self_io_type]
164+
self.export_tap(self_io, inner_io)
165+
166+
def _generator_pin_type_dict(self) -> Dict[str, Type[Port]]:
167+
"""Returns a dict of pin name to port type for all IO ports, recursing into bundles Ports.
168+
This includes both the bundle container Port and their (recursive) contents.
169+
170+
This does not instantiate Vector subports.
171+
This must be a GeneratorBlock with requested() declared as generator params.
172+
173+
Internal utility.
174+
"""
175+
assert isinstance(self, GeneratorBlock)
176+
177+
pin_type_dict: Dict[str, Type[Port]] = {}
178+
179+
def recurse_port(port: Port, prefix: str) -> None:
180+
assert prefix not in pin_type_dict, f"duplicate pin name {prefix}"
181+
pin_type_dict[prefix] = type(port)
182+
183+
for subport_name, subport in port._ports.items():
184+
recurse_port(subport, f"{prefix}.{subport_name}")
185+
186+
for io_port in self._io_ports:
187+
if isinstance(io_port, Vector):
188+
for subport_name in self.get(io_port.requested()):
189+
recurse_port(io_port._tpe.empty(), subport_name)
190+
elif isinstance(io_port, Port):
191+
if self.get(io_port.is_connected()):
192+
port_name = io_port._name_from(self)
193+
recurse_port(io_port, port_name)
194+
else:
195+
raise NotImplementedError(f"unknown port type {io_port}")
196+
197+
return pin_type_dict
198+
199+
def _make_model_pinning(self, remapping: Dict[str, str], device_assigns: List[str]) -> List[str]:
200+
"""Remaps my own assigns (pinned in device-space) to model-space.
201+
remapping is specified as the forward remapping, from pinname to device pinnum.
202+
203+
Requires _generator_param_all_ios, so all the IOs names are available.
204+
"""
205+
inverse_remapping = {v: k for k, v in remapping.items()}
206+
207+
remapped_assigns: List[str] = []
208+
pin_types = self._generator_pin_type_dict()
209+
210+
for assign in device_assigns:
211+
name, pindef = assign.split("=")
212+
name = name.strip()
213+
pindef = pindef.strip()
214+
assert name in pin_types, f"assign {name} not in IO ports"
215+
pin_type = pin_types[name]
216+
if issubclass(pin_type, HasPassivePort):
217+
if pindef in inverse_remapping:
218+
remapped_assigns.append(f"{name}={inverse_remapping[pindef]}")
219+
elif pindef in remapping:
220+
remapped_assigns.append(assign)
221+
else:
222+
raise ValueError(f"assign {assign} has pindef {pindef} not in remapping")
223+
else:
224+
remapped_assigns.append(assign)
225+
226+
return remapped_assigns

edg/abstract_parts/IoController.py

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -187,24 +187,17 @@ def _wrap_inner(
187187
self.assign(self.actual_pin_assigns, inner.actual_pin_assigns)
188188
self.assign(self.io_current_draw, inner.io_current_draw)
189189

190-
def _export_tap_ios_inner(self, inner: "BaseIoController") -> None:
191-
"""Export-taps all IO ports from some inner BaseIoController.
192-
This must be a SubboardBlock to support the export_tap connection.
193-
This must be called in contents() or generate(), after IOs have been defined."""
194-
from ..core.Blocks import BlockElaborationState
195-
196-
assert isinstance(self, WrapperSubboardBlock)
197-
assert self._elaboration_state in (
198-
BlockElaborationState.contents,
199-
BlockElaborationState.generate,
200-
), "can only run in contents() or generate()"
201-
202-
inner_ios_by_type = {self._type_of_io(io_port): io_port for io_port in inner._io_ports}
203-
for self_io in self._io_ports:
204-
self_io_type = self._type_of_io(self_io)
205-
assert self_io_type in inner_ios_by_type, f"inner missing IO of type {self_io_type}"
206-
inner_io = inner_ios_by_type[self_io_type]
207-
self.export_tap(self_io, inner_io)
190+
def _generator_param_all_ios(self) -> None:
191+
"""Declares all BaseIoController IOs as generator params.
192+
This must be a GeneratorBlock."""
193+
assert isinstance(self, GeneratorBlock)
194+
for io_port in self._io_ports:
195+
if isinstance(io_port, Vector):
196+
self.generator_param(io_port.requested())
197+
elif isinstance(io_port, Port):
198+
self.generator_param(io_port.is_connected())
199+
else:
200+
raise NotImplementedError(f"unknown port type {io_port}")
208201

209202
@staticmethod
210203
def _instantiate_from(

edg/abstract_parts/IoControllerWrapped.py

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)