diff --git a/edg/abstract_parts/IoController.py b/edg/abstract_parts/IoController.py index 628beda79..0a4506349 100644 --- a/edg/abstract_parts/IoController.py +++ b/edg/abstract_parts/IoController.py @@ -330,6 +330,16 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.gnd = self.Port(Ground.empty(), [Common], optional=True) self.pwr = self.Port(VoltageSink.empty(), [Power], optional=True) + @override + def contents(self) -> None: + super().contents() + # self-powered microcontrollers (like USB dev boards) can be used without GND + # for instance with keyswitches connected to only GPIO + self.require( + self.pwr.is_connected().implies(self.gnd.is_connected()), + "gnd must be connected if pwr connected", + ) + @non_library class IoControllerPowerRequired(IoController): diff --git a/edg/abstract_parts/IoControllerInterfaceMixins.py b/edg/abstract_parts/IoControllerInterfaceMixins.py index f9565d196..0ad1f4031 100644 --- a/edg/abstract_parts/IoControllerInterfaceMixins.py +++ b/edg/abstract_parts/IoControllerInterfaceMixins.py @@ -1,4 +1,6 @@ -from typing import Any +from typing import Any, Union + +from typing_extensions import override from ..electronics_interfaces import * from .IoController import BaseIoController, IoController @@ -117,9 +119,60 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: doc="Power output port, typically of the device's Vdd or VddIO rail at 3.3v", ) + @override + def contents(self) -> None: + super().contents() + if isinstance(self, IoController): + self.require( + self.pwr_out.is_connected().implies(~self.pwr.is_connected()), + "can only connect one of pwr and pwr_out (same physical pin)", + ) + self.require( + self.pwr_out.is_connected().implies(self.gnd.is_connected()), + "gnd must be connected if pwr_out connected", + ) + + def _generate_gnd_node(self) -> Ground: + """Helper function that returns a ground node, either directly taking the gnd port if available, + or generating an internal ground node. + + Requires self.gnd.is_connected() as a generator param. + """ + assert isinstance(self, IoController) + if self.get(self.gnd.is_connected()): # gnd connected externally + return self.gnd + else: + self.gnd_model = self.Block(DummyGround()) + return self.gnd_model.io + + def _generate_pwr_node( + self, voltage_out: RangeLike, current_limits: RangeLike + ) -> Union[VoltageSink, VoltageSource]: + """Helper function that returns a power node, either directly taking the pwr port if available, + or generating an internal voltage node and optionally connecting it to pwr_out (if used). + + Requires self.pwr.is_connected(), self.pwr_out.is_connected as generator params. + """ + assert isinstance(self, IoController) + if self.get(self.pwr.is_connected()): # power supplied externally + return self.pwr + else: + self.pwr_out_model = self.Block( + DummyVoltageSource( + voltage_out=voltage_out, # tolerance is a guess + current_limits=current_limits, + ) + ) + if self.get(self.pwr_out.is_connected()): + self.connect(self.pwr_out, self.pwr_out_model.io) + return self.pwr_out_model.io + class IoControllerUsbOut(BlockInterfaceMixin[IoController]): - """IO controller mixin that provides an output of the IO controller's USB Vbus.""" + """IO controller mixin that provides an output of the IO controller's USB Vbus. + + If also used with IoControllerVin, it is assumed that pwr_vin is the same physical pin as vusb_out. + TODO: support devices with separate Vin and Vbus""" def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @@ -127,6 +180,25 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: VoltageSource.empty(), optional=True, doc="Power output port of the device's Vbus, typically 5v" ) + @override + def contents(self) -> None: + super().contents() + if isinstance(self, IoController): + self.require( + self.vusb_out.is_connected().implies(self.gnd.is_connected()), + "gnd must be connected if vusb_out connected", + ) + self.require( + self.vusb_out.is_connected().implies(~self.pwr.is_connected()), + "can't sink logic-level pwr if sourcing power from USB", + ) + + if isinstance(self, IoControllerVin): + self.require( + self.vusb_out.is_connected().implies(~self.pwr_vin.is_connected()), + "can only connect one of pwr_vin and vusb_out (same physical pin)", + ) + class IoControllerVin(BlockInterfaceMixin[IoController]): """IO controller mixin that provides a >=5v input to the device, typically upstream of the Vbus-to-3.3 regulator.""" @@ -136,3 +208,16 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.pwr_vin = self.Port( VoltageSink.empty(), optional=True, doc="Power input pin, typically rated for 5v or a bit beyond." ) + + @override + def contents(self) -> None: + super().contents() + if isinstance(self, IoController): + self.require( + self.pwr_vin.is_connected().implies(self.gnd.is_connected()), + "gnd must be connected if pwr_vin connected", + ) + self.require( + self.pwr_vin.is_connected().implies(~self.pwr.is_connected()), + "can't sink logic-level pwr if powered from external vin", + ) diff --git a/edg/abstract_parts/BaseIoControllerWrapped.py b/edg/abstract_parts/IoControllerWrapper.py similarity index 80% rename from edg/abstract_parts/BaseIoControllerWrapped.py rename to edg/abstract_parts/IoControllerWrapper.py index a0abb4186..f9d2b3ff5 100644 --- a/edg/abstract_parts/BaseIoControllerWrapped.py +++ b/edg/abstract_parts/IoControllerWrapper.py @@ -1,9 +1,25 @@ -from typing import * +from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple, Type, Union +from typing_extensions import override from ..electronics_interfaces import * from .IoController import BaseIoController +class BaseIoControllerModelable(BaseIoController): + """Base class for a BaseIoController that can (optionally) be used as a (non-physical) model. + This only adds parameters as a standard interface and is not functional. + Subclasses must plumb these parameters.""" + + def __init__( + self, *args: Any, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [], **kwargs: Any + ) -> None: + super().__init__(*args, **kwargs) + + self._model = self.ArgParameter(_model) + self._allowed_pins = self.ArgParameter(_allowed_pins) + self.generator_param(self._allowed_pins) + + class BaseIoControllerWrapped(BaseIoController): """Base class for IoController wrapped blocks, particularly footprints that are used with an outer WrapperSubboardBlock to implement e.g. a dev board or module around a modeling subcircuit. @@ -204,6 +220,9 @@ def _make_model_pinning(self, remapping: Dict[str, str], device_assigns: List[st remapping is specified as the forward remapping, from pinname to device pinnum. Requires _generator_param_all_ios, so all the IOs names are available. + + In most cases, use _wrap_inner_model_device which provides all the wrapping functionality, though + this may be useful where other logic needs to happen with parameters. """ inverse_remapping = {v: k for k, v in remapping.items()} @@ -227,3 +246,33 @@ def _make_model_pinning(self, remapping: Dict[str, str], device_assigns: List[st remapped_assigns.append(assign) return remapped_assigns + + @override + def _wrap_inner(self, *args: Any, **kwargs: Any) -> None: + raise NotImplementedError # use _wrap_inner_model_device instead + + def _wrap_inner_model_device( + self, model: BaseIoControllerModelable, device: BaseIoControllerWrapped, remapping: Dict[str, str] + ) -> None: + """A version of _wrap_inner, but for the model (non-physical, directly connected) and device (physical, + export tapped) for a wrapper block. + + Both the model and device should have pin_assigns unassigned, since this will assign them. + The model must also have _allowed_pins unassigned. + + Export IO transforms are not supported. Where needed, the wrapper should instead represent + the device footprint instead of the application circuit, which should be defined at one level of hierarchy + higher. + + Requires _generator_param_all_ios, so all the IOs names are available, and pin_assigns to be a generator param. + """ + assert isinstance(self, GeneratorBlock) + + self.assign(model.pin_assigns, self._make_model_pinning(remapping, self.get(self.pin_assigns))) + self.assign(model._allowed_pins, list(remapping.keys())) + self._export_ios_inner(model) + self.assign(self.io_current_draw, model.io_current_draw) + + self.assign(device.pin_assigns, model.actual_pin_assigns) + self._export_tap_ios_inner(device) + self.assign(self.actual_pin_assigns, device.actual_pin_assigns) diff --git a/edg/abstract_parts/__init__.py b/edg/abstract_parts/__init__.py index 6a43150fc..9448e81f1 100644 --- a/edg/abstract_parts/__init__.py +++ b/edg/abstract_parts/__init__.py @@ -129,7 +129,7 @@ from .IoController import BaseIoController, IoController, IoControllerPowerRequired, BaseIoControllerPinmapGenerator from .IoControllerExportable import BaseIoControllerExportable -from .BaseIoControllerWrapped import BaseIoControllerWrapped, BaseIoControllerWrapper +from .IoControllerWrapper import BaseIoControllerModelable, BaseIoControllerWrapped, BaseIoControllerWrapper from .IoControllerInterfaceMixins import ( IoControllerSpiPeripheral, IoControllerI2cTarget, diff --git a/edg/electronics_interfaces/GroundDummy.py b/edg/electronics_interfaces/GroundDummy.py index 746fc5ac2..e55e68736 100644 --- a/edg/electronics_interfaces/GroundDummy.py +++ b/edg/electronics_interfaces/GroundDummy.py @@ -9,7 +9,7 @@ class DummyGround(BaseDummyBlock[GroundLink]): def __init__(self) -> None: super().__init__() - self.io = self.Port(Ground(), [Common, InOut]) + self.io: Ground = self.Port(Ground(), [Common, InOut]) def __getattr__(self, item: str) -> Any: if item == "gnd": diff --git a/edg/electronics_interfaces/VoltageDummy.py b/edg/electronics_interfaces/VoltageDummy.py index 61c39a4b8..3bead0901 100644 --- a/edg/electronics_interfaces/VoltageDummy.py +++ b/edg/electronics_interfaces/VoltageDummy.py @@ -16,7 +16,7 @@ def __init__( ) -> None: super().__init__() - self.io = self.Port( + self.io: VoltageSource = self.Port( VoltageSource( voltage_out=voltage_out, current_limits=current_limits, @@ -55,7 +55,7 @@ def __init__( ) -> None: super().__init__() - self.io = self.Port( + self.io: VoltageSink = self.Port( VoltageSink( voltage_limits=voltage_limit, current_draw=current_draw, diff --git a/edg/parts/microcontroller/Esp32.py b/edg/parts/microcontroller/Esp32.py index 1aaee3090..518401468 100644 --- a/edg/parts/microcontroller/Esp32.py +++ b/edg/parts/microcontroller/Esp32.py @@ -25,7 +25,12 @@ class Esp32_Interfaces( class Esp32_Wroom_32_Device( - Esp32_Interfaces, InternalSubcircuit, BaseIoControllerPinmapGenerator, FootprintBlock, JlcPart + Esp32_Interfaces, + InternalSubcircuit, + BaseIoControllerModelable, + BaseIoControllerPinmapGenerator, + FootprintBlock, + JlcPart, ): """ESP32-WROOM-32 module @@ -59,12 +64,9 @@ class Esp32_Wroom_32_Device( "GPIO23": "37", } - def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [], **kwargs: Any) -> None: + def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self._allowed_pins = self.ArgParameter(_allowed_pins) - self.generator_param(self._allowed_pins) - self.pwr = self.Port( VoltageSink( voltage_limits=(3.0, 3.6) * Volt, # section 5.2, table 14, most restrictive limits @@ -84,7 +86,7 @@ def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [] pullup_capable=True, pulldown_capable=True, ) - self.chip_pu = self.Port(self._dio_model, optional=_model) + self.chip_pu = self.Port(self._dio_model, optional=self._model) # section 2.4, table 5: strapping IOs that need a fixed value to boot, TODO currently not allocatable post-boot self.io0 = self.Port(self._dio_model, optional=True) # default pullup (SPI boot), set low to download boot self.io2 = self.Port( @@ -395,40 +397,21 @@ def generate(self) -> None: super().generate() self.model = self.Block( - Esp32_Wroom_32_Device( - pin_assigns=self._make_model_pinning( - Freenove_Esp32_Wrover_Device._PIN_REMAPPING, self.get(self.pin_assigns) - ), - _model=True, - _allowed_pins=list(Freenove_Esp32_Wrover_Device._PIN_REMAPPING.keys()), - ) + Esp32_Wroom_32_Device(pin_assigns=ArrayStringExpr(), _model=True, _allowed_pins=ArrayStringExpr()) ) - self._export_ios_inner(self.model) + self.device = self.Block(Freenove_Esp32_Wrover_Device(pin_assigns=ArrayStringExpr()), external=True) + self._wrap_inner_model_device(self.model, self.device, Freenove_Esp32_Wrover_Device._PIN_REMAPPING) - self.device = self.Block(Freenove_Esp32_Wrover_Device(pin_assigns=self.model.actual_pin_assigns), external=True) - self._export_tap_ios_inner(self.device) - self.assign(self.actual_pin_assigns, self.device.actual_pin_assigns) + self.connect(self._generate_gnd_node(), self.model.gnd) + self.export_tap(self.gnd, self.device.gnd) - if self.get(self.gnd.is_connected()): - self.connect(self.gnd, self.model.gnd) - self.export_tap(self.gnd, self.device.gnd) - else: - self.gnd_model = self.Block(DummyGround()).connected(self.model.gnd) - - if self.get(self.pwr.is_connected()): # power supplied externally - self.connect(self.pwr, self.model.pwr) - self.export_tap(self.pwr.net, self.device.v3v3) - else: # board sources power from USB - self.pwr_out_model = self.Block( - DummyVoltageSource( - voltage_out=3.3 * Volt(tol=0.05), # tolerance is a guess - current_limits=UsbConnector.USB2_CURRENT_LIMITS, - ) - ) - self.connect(self.pwr_out_model.io, self.model.pwr) - if self.get(self.pwr_out.is_connected()): - self.connect(self.pwr_out, self.pwr_out_model.io) - self.export_tap(self.pwr_out.net, self.device.v3v3) + self.connect( + self._generate_pwr_node( + voltage_out=3.3 * Volt(tol=0.05), + current_limits=UsbConnector.USB2_CURRENT_LIMITS, # tolerance is a guess + ), + self.model.pwr, + ) + self.export_tap((self.pwr if self.get(self.pwr.is_connected()) else self.pwr_out).net, self.device.v3v3) - if self.get(self.vusb_out.is_connected()): - self.export_tap(self.vusb_out.net, self.device.vcc) + self.export_tap(self.vusb_out.net, self.device.vcc) diff --git a/edg/parts/microcontroller/Esp32c3.py b/edg/parts/microcontroller/Esp32c3.py index 0f6407e8d..d74efa553 100644 --- a/edg/parts/microcontroller/Esp32c3.py +++ b/edg/parts/microcontroller/Esp32c3.py @@ -20,7 +20,14 @@ class Esp32c3_Interfaces( """Defines base interfaces for ESP32C3 microcontrollers""" -class Esp32c3_Device(Esp32c3_Interfaces, BaseIoControllerPinmapGenerator, InternalSubcircuit, FootprintBlock, JlcPart): +class Esp32c3_Device( + Esp32c3_Interfaces, + BaseIoControllerModelable, + BaseIoControllerPinmapGenerator, + InternalSubcircuit, + FootprintBlock, + JlcPart, +): RESOURCE_PIN_REMAP = { "GPIO0": "4", @@ -55,12 +62,9 @@ def _system_pinmap(self) -> Mapping[Union[Iterable[str], str], Union[Passive, Ha "29": self.xtal.xtal_out, } - def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [], **kwargs: Any) -> None: + def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self._allowed_pins = self.ArgParameter(_allowed_pins) - self.generator_param(self._allowed_pins) - self.gnd = self.Port(Ground(), [Common]) self.vdda = self.Port( # models total current draw VoltageSink( @@ -93,7 +97,7 @@ def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [] # 10ppm requirement from ESP32-C3-WROOM schematic, and in ESP32 hardware design guidelines self.xtal = self.Port( # vdda domain assumed CrystalDriver(frequency_limits=40 * MHertz(tol=10e-6), voltage_out=self.vdda.link().voltage), - optional=_model, + optional=self._model, ) # section 2.4: strapping IOs that need a fixed value to boot, and currently can't be allocated as GPIO @@ -107,9 +111,9 @@ def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [] pullup_capable=True, pulldown_capable=True, ) - self.en = self.Port(DigitalSink.from_bidir(self._dio_model), optional=_model) # needs external pullup - self.io2 = self.Port(self._dio_model, optional=_model) # needs external pullup; affects IO glitching on boot - self.io8 = self.Port(self._dio_model, optional=_model) # needs external pullup, required for download boot + self.en = self.Port(DigitalSink.from_bidir(self._dio_model), optional=self._model) # needs external pullup + self.io2 = self.Port(self._dio_model, optional=self._model) # needs external pullup; affects boot IO glitching + self.io8 = self.Port(self._dio_model, optional=self._model) # needs external pullup, required for download boot self.io9 = self.Port( self._dio_model, optional=True ) # internally pulled up for SPI boot, connect to GND for download @@ -117,7 +121,7 @@ def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [] # similarly, the programming UART is fixed and allocated separately self.uart0 = self.Port(UartPort(self._dio_model), optional=True) - self.lna_in = self.Port(Passive(), optional=_model) + self.lna_in = self.Port(Passive(), optional=self._model) @override def generate(self) -> None: @@ -378,11 +382,7 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self.model = self.Block( - Esp32c3_Device( - pin_assigns=ArrayStringExpr(), - _model=True, - _allowed_pins=list(Esp32c3_Wroom02_Footprint._PIN_REMAPPING.keys()), - ) + Esp32c3_Device(pin_assigns=ArrayStringExpr(), _model=True, _allowed_pins=ArrayStringExpr()) ) self.gnd = self.Export(self.model.gnd, [Common]) self.v3v3 = self.Export(self.model.vdd3p3, [Power]) @@ -399,15 +399,9 @@ def __init__(self, **kwargs: Any) -> None: def generate(self) -> None: super().generate() - self._export_ios_inner(self.model) - self.assign( - self.model.pin_assigns, - self._make_model_pinning(Esp32c3_Wroom02_Footprint._PIN_REMAPPING, self.get(self.pin_assigns)), - ) + self.device = self.Block(Esp32c3_Wroom02_Footprint(pin_assigns=ArrayStringExpr()), external=True) + self._wrap_inner_model_device(self.model, self.device, Esp32c3_Wroom02_Footprint._PIN_REMAPPING) - self.device = self.Block(Esp32c3_Wroom02_Footprint(pin_assigns=self.model.actual_pin_assigns), external=True) - self.assign(self.actual_pin_assigns, self.device.actual_pin_assigns) - self._export_tap_ios_inner(self.device) self.export_tap(self.gnd, self.device.gnd) self.export_tap(self.v3v3, self.device.v3v3) self.export_tap(self.en, self.device.en) @@ -565,41 +559,24 @@ def generate(self) -> None: super().generate() self.model = self.Block( - Esp32c3_Device( - pin_assigns=self._make_model_pinning(Xiao_Esp32c3_Device._PIN_REMAPPING, self.get(self.pin_assigns)), - _model=True, - _allowed_pins=list(Xiao_Esp32c3_Device._PIN_REMAPPING.keys()), - ) + Esp32c3_Device(pin_assigns=ArrayStringExpr(), _model=True, _allowed_pins=ArrayStringExpr()) ) - self._export_ios_inner(self.model) - - self.device = self.Block(Xiao_Esp32c3_Device(pin_assigns=self.model.actual_pin_assigns), external=True) - self._export_tap_ios_inner(self.device) - self.assign(self.actual_pin_assigns, self.device.actual_pin_assigns) + self.device = self.Block(Xiao_Esp32c3_Device(pin_assigns=ArrayStringExpr()), external=True) + self._wrap_inner_model_device(self.model, self.device, Xiao_Esp32c3_Device._PIN_REMAPPING) - if self.get(self.gnd.is_connected()): - self.connect(self.gnd, self.model.gnd) - self.export_tap(self.gnd, self.device.gnd) - else: - self.gnd_model = self.Block(DummyGround()).connected(self.model.gnd) + self.connect(self._generate_gnd_node(), self.model.gnd) + self.export_tap(self.gnd, self.device.gnd) - self.connect( + model_pwr = self.connect( self.model.vdda, self.model.vdd3p3, self.model.vdd3p3_rtc, self.model.vdd3p3_cpu, self.model.vdd_spi ) - if self.get(self.pwr.is_connected()): # power supplied externally - self.connect(self.pwr, self.model.vdd3p3) - self.export_tap(self.pwr.net, self.device.v3v3) - else: # board sources power from USB - self.pwr_out_model = self.Block( - DummyVoltageSource( - voltage_out=3.3 * Volt(tol=0.05), # tolerance is a guess - current_limits=UsbConnector.USB2_CURRENT_LIMITS, - ) - ) - self.connect(self.pwr_out_model.io, self.model.vdd3p3) - if self.get(self.pwr_out.is_connected()): - self.connect(self.pwr_out, self.pwr_out_model.io) - self.export_tap(self.pwr_out.net, self.device.v3v3) + self.connect( + self._generate_pwr_node( + voltage_out=3.3 * Volt(tol=0.05), + current_limits=UsbConnector.USB2_CURRENT_LIMITS, # tolerance is a guess + ), + model_pwr, + ) + self.export_tap((self.pwr if self.get(self.pwr.is_connected()) else self.pwr_out).net, self.device.v3v3) - if self.get(self.vusb_out.is_connected()): - self.export_tap(self.vusb_out.net, self.device.vusb) + self.export_tap(self.vusb_out.net, self.device.vusb) diff --git a/edg/parts/microcontroller/Esp32s3.py b/edg/parts/microcontroller/Esp32s3.py index 80d50ce71..a3d69b530 100644 --- a/edg/parts/microcontroller/Esp32s3.py +++ b/edg/parts/microcontroller/Esp32s3.py @@ -24,7 +24,12 @@ class Esp32s3_Interfaces( class Esp32s3_Wroom_1_Device( - Esp32s3_Interfaces, BaseIoControllerPinmapGenerator, InternalSubcircuit, FootprintBlock, JlcPart + Esp32s3_Interfaces, + BaseIoControllerModelable, + BaseIoControllerPinmapGenerator, + InternalSubcircuit, + FootprintBlock, + JlcPart, ): _PIN_MAPPING = { "GPIO4": "4", @@ -62,12 +67,9 @@ class Esp32s3_Wroom_1_Device( "GPIO1": "39", } - def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [], **kwargs: Any) -> None: + def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self._allowed_pins = self.ArgParameter(_allowed_pins) - self.generator_param(self._allowed_pins) - self.pwr = self.Port( VoltageSink( # assumes single-rail module voltage_limits=(3.0, 3.6) * Volt, # table 4-2 @@ -88,7 +90,7 @@ def __init__(self, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [] pulldown_capable=True, ) - self.chip_pu = self.Port(self._dio_model, optional=_model) + self.chip_pu = self.Port(self._dio_model, optional=self._model) self.io0 = self.Port( self._dio_model, optional=True ) # table 2-11, default pullup (SPI boot), set low to download boot @@ -373,42 +375,21 @@ def generate(self) -> None: super().generate() self.model = self.Block( - Esp32s3_Wroom_1_Device( - pin_assigns=self._make_model_pinning( - Freenove_Esp32s3_Wrover_Device._PIN_REMAPPING, self.get(self.pin_assigns) - ), - _model=True, - _allowed_pins=list(Freenove_Esp32s3_Wrover_Device._PIN_REMAPPING.keys()), - ) + Esp32s3_Wroom_1_Device(pin_assigns=ArrayStringExpr(), _model=True, _allowed_pins=ArrayStringExpr()) ) - self._export_ios_inner(self.model) + self.device = self.Block(Freenove_Esp32s3_Wrover_Device(pin_assigns=ArrayStringExpr()), external=True) + self._wrap_inner_model_device(self.model, self.device, Freenove_Esp32s3_Wrover_Device._PIN_REMAPPING) - self.device = self.Block( - Freenove_Esp32s3_Wrover_Device(pin_assigns=self.model.actual_pin_assigns), external=True - ) - self._export_tap_ios_inner(self.device) - self.assign(self.actual_pin_assigns, self.device.actual_pin_assigns) + self.connect(self._generate_gnd_node(), self.model.gnd) + self.export_tap(self.gnd, self.device.gnd) - if self.get(self.gnd.is_connected()): - self.connect(self.gnd, self.model.gnd) - self.export_tap(self.gnd, self.device.gnd) - else: - self.gnd_model = self.Block(DummyGround()).connected(self.model.gnd) - - if self.get(self.pwr.is_connected()): # power supplied externally - self.connect(self.pwr, self.model.pwr) - self.export_tap(self.pwr.net, self.device.v3v3) - else: # board sources power from USB - self.pwr_out_model = self.Block( - DummyVoltageSource( - voltage_out=3.3 * Volt(tol=0.05), # tolerance is a guess - current_limits=UsbConnector.USB2_CURRENT_LIMITS, - ) - ) - self.connect(self.pwr_out_model.io, self.model.pwr) - if self.get(self.pwr_out.is_connected()): - self.connect(self.pwr_out, self.pwr_out_model.io) - self.export_tap(self.pwr_out.net, self.device.v3v3) + self.connect( + self._generate_pwr_node( + voltage_out=3.3 * Volt(tol=0.05), + current_limits=UsbConnector.USB2_CURRENT_LIMITS, # tolerance is a guess + ), + self.model.pwr, + ) + self.export_tap((self.pwr if self.get(self.pwr.is_connected()) else self.pwr_out).net, self.device.v3v3) - if self.get(self.vusb_out.is_connected()): - self.export_tap(self.vusb_out.net, self.device.vusb) + self.export_tap(self.vusb_out.net, self.device.vusb) diff --git a/edg/parts/microcontroller/Rp2040.py b/edg/parts/microcontroller/Rp2040.py index 467f3e0e6..c99b0239d 100644 --- a/edg/parts/microcontroller/Rp2040.py +++ b/edg/parts/microcontroller/Rp2040.py @@ -12,7 +12,13 @@ class Rp2040_Interfaces(IoControllerI2cTarget, IoControllerUsb, BaseIoController class Rp2040_Device( - Rp2040_Interfaces, BaseIoControllerPinmapGenerator, InternalSubcircuit, GeneratorBlock, JlcPart, FootprintBlock + Rp2040_Interfaces, + BaseIoControllerModelable, + BaseIoControllerPinmapGenerator, + InternalSubcircuit, + GeneratorBlock, + JlcPart, + FootprintBlock, ): _PIN_MAPPING = { "GPIO0": "2", @@ -51,13 +57,9 @@ class Rp2040_Device( "SWCLK": "24", } - def __init__(self, *, _model: BoolLike = False, _allowed_pins: ArrayStringLike = [], **kwargs: Any) -> None: + def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self._allowed_pins = self.ArgParameter(_allowed_pins) - self.generator_param(self._allowed_pins) - self._model = self.ArgParameter(_model) - self.gnd = self.Port(Ground(), [Common]) self.iovdd = self.Port( VoltageSink( @@ -465,61 +467,24 @@ def generate(self) -> None: VoltageSource(voltage_out=UsbConnector.USB2_VOLTAGE_RANGE, current_limits=UsbConnector.USB2_CURRENT_LIMITS) ) - self.require( - ~self.pwr_vin.is_connected() | ~self.vusb_out.is_connected(), "cannot use both VUsb out and VUsb in" - ) - self.require( - (self.pwr_vin.is_connected() | self.vusb_out.is_connected()).implies(~self.pwr.is_connected()), - "cannot use 3.3v input if VUsb used", - ) - self.require(~self.pwr_out.is_connected() | ~self.pwr.is_connected(), "cannot use both 3.3v out and 3.3v in") - self.require( - ( - self.pwr_vin.is_connected() - | self.vusb_out.is_connected() - | self.pwr.is_connected() - | self.pwr_out.is_connected() - ).implies(self.gnd.is_connected()), - "ground required if power used", - ) - self.model = self.Block( - Rp2040_Device( - pin_assigns=self._make_model_pinning(Xiao_Rp2040_Device._PIN_REMAPPING, self.get(self.pin_assigns)), - _model=True, - _allowed_pins=list(Xiao_Rp2040_Device._PIN_REMAPPING.keys()), - ) + Rp2040_Device(pin_assigns=ArrayStringExpr(), _model=True, _allowed_pins=ArrayStringExpr()) ) - self._export_ios_inner(self.model) - - self.device = self.Block(Xiao_Rp2040_Device(pin_assigns=self.model.actual_pin_assigns), external=True) - self._export_tap_ios_inner(self.device) - self.assign(self.actual_pin_assigns, self.device.actual_pin_assigns) + self.device = self.Block(Xiao_Rp2040_Device(pin_assigns=ArrayStringExpr()), external=True) + self._wrap_inner_model_device(self.model, self.device, Xiao_Rp2040_Device._PIN_REMAPPING) - if self.get(self.gnd.is_connected()): - self.connect(self.gnd, self.model.gnd) - self.export_tap(self.gnd, self.device.gnd) - else: - self.gnd_model = self.Block(DummyGround()).connected(self.model.gnd) + self.connect(self._generate_gnd_node(), self.model.gnd) + self.export_tap(self.gnd, self.device.gnd) self.connect(self.model.vreg_vout, self.model.dvdd) model_pwr = self.connect(self.model.iovdd, self.model.vreg_vin, self.model.adc_avdd, self.model.usb_vdd) - if self.get(self.pwr.is_connected()): # power supplied externally - self.connect(self.pwr, model_pwr) - self.export_tap(self.pwr.net, self.device.v3v3) - else: # board sources power from USB - self.pwr_out_model = self.Block( - DummyVoltageSource( - voltage_out=3.3 * Volt(tol=0.05), # tolerance is a guess - current_limits=UsbConnector.USB2_CURRENT_LIMITS, - ) - ) - self.connect(self.pwr_out_model.io, model_pwr) - if self.get(self.pwr_out.is_connected()): - self.connect(self.pwr_out, self.pwr_out_model.io) - self.export_tap(self.pwr_out.net, self.device.v3v3) - - if self.get(self.pwr_vin.is_connected()): - self.export_tap(self.pwr_vin.net, self.device.vcc) - if self.get(self.vusb_out.is_connected()): - self.export_tap(self.vusb_out.net, self.device.vcc) + self.connect( + self._generate_pwr_node( + voltage_out=3.3 * Volt(tol=0.05), + current_limits=UsbConnector.USB2_CURRENT_LIMITS, # tolerance is a guess + ), + model_pwr, + ) + self.export_tap((self.pwr if self.get(self.pwr.is_connected()) else self.pwr_out).net, self.device.v3v3) + + self.export_tap((self.pwr_vin if self.get(self.pwr_vin.is_connected()) else self.vusb_out).net, self.device.vcc) diff --git a/edg/parts/microcontroller/Stm32f303.py b/edg/parts/microcontroller/Stm32f303.py index 7ffad4f5e..0d9a10771 100644 --- a/edg/parts/microcontroller/Stm32f303.py +++ b/edg/parts/microcontroller/Stm32f303.py @@ -87,15 +87,6 @@ def __init__(self) -> None: def generate(self) -> None: super().generate() - self.require( - self.pwr.is_connected().implies(~self.vusb_out.is_connected()), - "can't source USB power if power input connected", - ) - self.require( - self.pwr.is_connected().implies(~self.pwr_out.is_connected()), - "can't source 3v3 power if power input connected", - ) - self.footprint( "U", "edg:Nucleo32", diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index cc599e6c3..34575f10a 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -15,7 +15,13 @@ class Nrf52840_Interfaces( class Mdbt50q_1mv2_Device( - Nrf52840_Interfaces, BaseIoControllerPinmapGenerator, InternalSubcircuit, JlcPart, GeneratorBlock, FootprintBlock + Nrf52840_Interfaces, + BaseIoControllerModelable, + BaseIoControllerPinmapGenerator, + InternalSubcircuit, + JlcPart, + GeneratorBlock, + FootprintBlock, ): # in the absence of a chip-level subcircuit, this is used as the authoritative base device model # that other modules should wrap @@ -74,12 +80,9 @@ class Mdbt50q_1mv2_Device( "P1.01": "61", } - def __init__(self, _allowed_pins: ArrayStringLike = [], **kwargs: Any) -> None: + def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self._allowed_pins = self.ArgParameter(_allowed_pins) - self.generator_param(self._allowed_pins) - self.gnd = self.Port(Ground(), [Common]) self.pwr = self.Port( VoltageSink( @@ -683,52 +686,25 @@ def __init__(self, **kwargs: Any) -> None: def generate(self) -> None: super().generate() - self.require( - self.pwr.is_connected().implies(~self.vusb_out.is_connected()), - "can't source USB power if power input connected", - ) - self.require( - self.pwr.is_connected().implies(~self.pwr_out.is_connected()), - "can't source 3v3 power if power input connected", - ) - self.model = self.Block( - Mdbt50q_1mv2_Device( - pin_assigns=self._make_model_pinning( - Feather_Nrf52840_Device._PIN_REMAPPING, self.get(self.pin_assigns) - ), - _allowed_pins=list(Feather_Nrf52840_Device._PIN_REMAPPING.keys()), - ) + Mdbt50q_1mv2_Device(pin_assigns=ArrayStringExpr(), _model=True, _allowed_pins=ArrayStringExpr()) ) - self._export_ios_inner(self.model) + self.device = self.Block(Feather_Nrf52840_Device(pin_assigns=ArrayStringExpr()), external=True) + self._wrap_inner_model_device(self.model, self.device, Feather_Nrf52840_Device._PIN_REMAPPING) - self.device = self.Block(Feather_Nrf52840_Device(pin_assigns=self.model.actual_pin_assigns), external=True) - self._export_tap_ios_inner(self.device) - self.assign(self.actual_pin_assigns, self.device.actual_pin_assigns) + self.connect(self._generate_gnd_node(), self.model.gnd) + self.export_tap(self.gnd, self.device.gnd) - if self.get(self.gnd.is_connected()): - self.connect(self.gnd, self.model.gnd) - self.export_tap(self.gnd, self.device.gnd) - else: - self.gnd_model = self.Block(DummyGround()).connected(self.model.gnd) - - if self.get(self.pwr.is_connected()): # power supplied externally - self.connect(self.pwr, self.model.pwr) - self.export_tap(self.pwr.net, self.device.pwr) - else: # board sources power from USB - self.pwr_out_model = self.Block( - DummyVoltageSource(voltage_out=self._AP2112_3V3_OUT, current_limits=UsbConnector.USB2_CURRENT_LIMITS) - ) - self.connect(self.pwr_out_model.io, self.model.pwr) - if self.get(self.pwr_out.is_connected()): - self.connect(self.pwr_out, self.pwr_out_model.io) - self.export_tap(self.pwr_out.net, self.device.pwr) - - if self.get(self.vusb_out.is_connected()): - self.vusb_out.init_from( - VoltageSource( - voltage_out=UsbConnector.USB2_VOLTAGE_RANGE - self._MBR120_DROP, - current_limits=UsbConnector.USB2_CURRENT_LIMITS, - ) + self.connect( + self._generate_pwr_node(voltage_out=self._AP2112_3V3_OUT, current_limits=UsbConnector.USB2_CURRENT_LIMITS), + self.model.pwr, + ) + self.export_tap((self.pwr if self.get(self.pwr.is_connected()) else self.pwr_out).net, self.device.pwr) + + self.vusb_out.init_from( + VoltageSource( + voltage_out=UsbConnector.USB2_VOLTAGE_RANGE - self._MBR120_DROP, + current_limits=UsbConnector.USB2_CURRENT_LIMITS, ) - self.export_tap(self.vusb_out.net, self.device.vusb) + ) + self.export_tap(self.vusb_out.net, self.device.vusb) diff --git a/edg/parts/microcontroller/test_mcu_wrapper.py b/edg/parts/microcontroller/test_mcu_wrapper.py index 59860f72b..4891d4d52 100644 --- a/edg/parts/microcontroller/test_mcu_wrapper.py +++ b/edg/parts/microcontroller/test_mcu_wrapper.py @@ -41,6 +41,14 @@ def __init__(self) -> None: self.ios[i] = self.Block(DummyDigitalSource()).connected(self.dut.gpio.request(str(i))) +class UnpoweredMcuTest(DesignTop): + # tests a wrapper without ground or power connections + def __init__(self) -> None: + super().__init__() + self.dut = self.Block(Xiao_Esp32c3()) + self.io = self.Block(DummyDigitalSource()).connected(self.dut.gpio.request()) + + class BaseMcuTest(DesignTop): def __init__(self) -> None: super().__init__() @@ -105,6 +113,9 @@ def test_overallocate(self) -> None: with self.assertRaises(CompilerCheckError): ScalaCompiler.compile(OverallocateTest) + def test_unpowered_mcu(self) -> None: + ScalaCompiler.compile(UnpoweredMcuTest) + def test_auto_pins(self) -> None: ScalaCompiler.compile(FullMcuTest) # check that it compiles without error diff --git a/examples/BasicKeyboard/BasicKeyboard.net.ref b/examples/BasicKeyboard/BasicKeyboard.net.ref index 8592cbd83..eaf128f3f 100644 --- a/examples/BasicKeyboard/BasicKeyboard.net.ref +++ b/examples/BasicKeyboard/BasicKeyboard.net.ref @@ -179,11 +179,11 @@ (node (ref U1) (pin 10)) (node (ref D3) (pin 2)) (node (ref D6) (pin 2))) -(net (code 6) (name "mcu.pwr_out") - (node (ref U1) (pin 12))) -(net (code 7) (name "mcu.device.gnd") +(net (code 6) (name "mcu.gnd") (node (ref U1) (pin 13))) -(net (code 8) (name "mcu.device.vcc") +(net (code 7) (name "mcu.pwr_out") + (node (ref U1) (pin 12))) +(net (code 8) (name "mcu.vusb_out") (node (ref U1) (pin 14))) (net (code 9) (name "sw.sw[0,0].sw") (node (ref SW1) (pin 1)) diff --git a/examples/BasicKeyboard/BasicKeyboard.svgpcb.js b/examples/BasicKeyboard/BasicKeyboard.svgpcb.js index ffd2be7cd..4505f0510 100644 --- a/examples/BasicKeyboard/BasicKeyboard.svgpcb.js +++ b/examples/BasicKeyboard/BasicKeyboard.svgpcb.js @@ -13,9 +13,9 @@ board.setNetlist([ {name: "mcu.gpio.1_0", pads: [["U1", "9"], ["D1", "2"], ["D4", "2"]]}, {name: "mcu.gpio.1_1", pads: [["U1", "11"], ["D2", "2"], ["D5", "2"]]}, {name: "mcu.gpio.1_2", pads: [["U1", "10"], ["D3", "2"], ["D6", "2"]]}, + {name: "mcu.gnd", pads: [["U1", "13"]]}, {name: "mcu.pwr_out", pads: [["U1", "12"]]}, - {name: "mcu.device.gnd", pads: [["U1", "13"]]}, - {name: "mcu.device.vcc", pads: [["U1", "14"]]}, + {name: "mcu.vusb_out", pads: [["U1", "14"]]}, {name: "sw.sw[0,0].sw", pads: [["SW1", "1"], ["D1", "1"]]}, {name: "sw.sw[0,1].sw", pads: [["SW2", "1"], ["D2", "1"]]}, {name: "sw.sw[0,2].sw", pads: [["SW3", "1"], ["D3", "1"]]}, diff --git a/examples/TestBlinkyBasic/TestBlinkyBasic.net.ref b/examples/TestBlinkyBasic/TestBlinkyBasic.net.ref index 27af5ce41..32b62551b 100644 --- a/examples/TestBlinkyBasic/TestBlinkyBasic.net.ref +++ b/examples/TestBlinkyBasic/TestBlinkyBasic.net.ref @@ -42,7 +42,7 @@ (node (ref R1) (pin 2))) (net (code 2) (name "mcu.pwr_out") (node (ref U1) (pin 12))) -(net (code 3) (name "mcu.device.vcc") +(net (code 3) (name "mcu.vusb_out") (node (ref U1) (pin 14))) (net (code 4) (name "led.signal") (node (ref U1) (pin 7)) diff --git a/examples/TestBlinkyBasic/TestBlinkyBasic.svgpcb.js b/examples/TestBlinkyBasic/TestBlinkyBasic.svgpcb.js index ca7adcd27..9981014ec 100644 --- a/examples/TestBlinkyBasic/TestBlinkyBasic.svgpcb.js +++ b/examples/TestBlinkyBasic/TestBlinkyBasic.svgpcb.js @@ -19,7 +19,7 @@ const R1 = board.add(R_0603_1608Metric, { board.setNetlist([ {name: "mcu.gnd", pads: [["U1", "13"], ["R1", "2"]]}, {name: "mcu.pwr_out", pads: [["U1", "12"]]}, - {name: "mcu.device.vcc", pads: [["U1", "14"]]}, + {name: "mcu.vusb_out", pads: [["U1", "14"]]}, {name: "led.signal", pads: [["U1", "7"], ["D1", "2"]]}, {name: "led.package.k", pads: [["D1", "1"], ["R1", "1"]]} ])