From 2cb38621840df37103c0c512a56f68b0a1d33a6c Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 25 May 2026 16:51:28 -0700 Subject: [PATCH 01/28] refactor arm mcus --- edg/parts/microcontroller/Lpc1549.py | 13 +++++++-- edg/parts/microcontroller/Stm32f103.py | 33 +++++++++++----------- edg/parts/microcontroller/Stm32g031.py | 6 ++-- edg/parts/microcontroller/Stm32g431.py | 8 ++++-- edg/parts/microcontroller/Stm32l432.py | 7 +++-- examples/Keyboard/Keyboard.net.ref | 28 +++++++++--------- examples/Keyboard/Keyboard.svgpcb.js | 16 +++++------ examples/SwdDebugger/SwdDebugger.net.ref | 26 ++++++++--------- examples/SwdDebugger/SwdDebugger.svgpcb.js | 12 ++++---- examples/TofArray/TofArray.net.ref | 26 ++++++++--------- examples/TofArray/TofArray.svgpcb.js | 16 +++++------ 11 files changed, 101 insertions(+), 90 deletions(-) diff --git a/edg/parts/microcontroller/Lpc1549.py b/edg/parts/microcontroller/Lpc1549.py index ec908d498..e819eac71 100644 --- a/edg/parts/microcontroller/Lpc1549.py +++ b/edg/parts/microcontroller/Lpc1549.py @@ -370,7 +370,6 @@ class Lpc1549Base( IoControllerWithSwdTargetConnector, WithCrystalGenerator, IoControllerPowerRequired, - BaseIoControllerExportable, GeneratorBlock, ): DEVICE: Type[Lpc1549Base_Device] = Lpc1549Base_Device @@ -378,8 +377,13 @@ class Lpc1549Base( def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self.ic: Lpc1549Base_Device - self.generator_param(self.reset.is_connected()) + self.generator_param( + self.reset.is_connected(), + self.pin_assigns, + self.gpio.requested(), + self.usb.requested(), + self.can.requested(), + ) @override def contents(self) -> None: @@ -412,6 +416,9 @@ def contents(self) -> None: def generate(self) -> None: super().generate() + # add a passthrough for gpio (DigitalBidir) to allow the SWD pins to be attached, if using + self._wrap_inner(self.ic, {DigitalBidir: lambda port, assign: port}) + if self.get(self.reset.is_connected()): self.connect(self.reset, self.ic.reset) diff --git a/edg/parts/microcontroller/Stm32f103.py b/edg/parts/microcontroller/Stm32f103.py index c9eb5b7e3..dca5196df 100644 --- a/edg/parts/microcontroller/Stm32f103.py +++ b/edg/parts/microcontroller/Stm32f103.py @@ -294,7 +294,6 @@ class Stm32f103Base( IoControllerWithSwdTargetConnector, WithCrystalGenerator, IoControllerPowerRequired, - BaseIoControllerExportable, GeneratorBlock, ): DEVICE: Type[Stm32f103Base_Device] = Stm32f103Base_Device @@ -302,8 +301,13 @@ class Stm32f103Base( def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self.ic: Stm32f103Base_Device - self.generator_param(self.reset.is_connected()) + self.generator_param( + self.reset.is_connected(), + self.pin_assigns, + self.gpio.requested(), + self.can.requested(), + self.usb.requested(), + ) @override def contents(self) -> None: @@ -329,24 +333,19 @@ def contents(self) -> None: def generate(self) -> None: super().generate() - if self.get(self.reset.is_connected()): - self.connect(self.reset, self.ic.nrst) - - ExportType = TypeVar("ExportType", bound=Port) - - @override - def _make_export_vector( - self, self_io: ExportType, inner_vector: Vector[ExportType], name: str, assign: Optional[str] - ) -> Optional[str]: - if isinstance(self_io, UsbDevicePort): # assumed at most one USB port generates - inner_io = inner_vector.request(name) + def usb_export_transform(self_io: BasePort, assign: Optional[str]) -> Optional[BasePort]: self.usb_pull = self.Block( UsbDpPullUp(resistance=1.5 * kOhm(tol=0.01)) ) # required by datasheet Table 44 # TODO proper tolerancing? self.connect(self.usb_pull.pwr, self.pwr) - self.connect(inner_io, self_io, self.usb_pull.usb) - return assign - return super()._make_export_vector(self_io, inner_vector, name, assign) + self.connect(self_io, self.usb_pull.usb) + return self_io + + # add a passthrough for gpio (DigitalBidir) to allow the SWD pins to be attached, if using + self._wrap_inner(self.ic, {UsbDevicePort: usb_export_transform, DigitalBidir: lambda port, assign: port}) + + if self.get(self.reset.is_connected()): + self.connect(self.reset, self.ic.nrst) @override def _crystal_required(self) -> bool: # crystal needed for CAN or USB b/c tighter freq tolerance diff --git a/edg/parts/microcontroller/Stm32g031.py b/edg/parts/microcontroller/Stm32g031.py index 441f25f69..d3d4c948e 100644 --- a/edg/parts/microcontroller/Stm32g031.py +++ b/edg/parts/microcontroller/Stm32g031.py @@ -240,7 +240,6 @@ class Stm32g031Base( Microcontroller, IoControllerWithSwdTargetConnector, IoControllerPowerRequired, - BaseIoControllerExportable, GeneratorBlock, ): DEVICE: Type[Stm32g031Base_Device] = Stm32g031Base_Device @@ -248,7 +247,7 @@ class Stm32g031Base( def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self.ic: Stm32g031Base_Device - self.generator_param(self.reset.is_connected()) + self.generator_param(self.reset.is_connected(), self.pin_assigns, self.gpio.requested()) @override def contents(self) -> None: @@ -267,6 +266,9 @@ def contents(self) -> None: def generate(self) -> None: super().generate() + # add a passthrough for gpio (DigitalBidir) to allow the SWD pins to be attached, if using + self._wrap_inner(self.ic, {DigitalBidir: lambda port, assign: port}) + if self.get(self.reset.is_connected()): self.connect(self.reset, self.ic.nrst) # otherwise NRST has internal pull-up diff --git a/edg/parts/microcontroller/Stm32g431.py b/edg/parts/microcontroller/Stm32g431.py index 00b99edb3..3f9c48da5 100644 --- a/edg/parts/microcontroller/Stm32g431.py +++ b/edg/parts/microcontroller/Stm32g431.py @@ -267,15 +267,13 @@ class Stm32g431Base( Microcontroller, IoControllerWithSwdTargetConnector, IoControllerPowerRequired, - BaseIoControllerExportable, GeneratorBlock, ): DEVICE: Type[Stm32g431Base_Device] = Stm32g431Base_Device def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self.ic: Stm32g431Base_Device - self.generator_param(self.reset.is_connected()) + self.generator_param(self.reset.is_connected(), self.pin_assigns, self.gpio.requested()) @override def contents(self) -> None: @@ -296,6 +294,10 @@ def contents(self) -> None: @override def generate(self) -> None: super().generate() + + # add a passthrough for gpio (DigitalBidir) to allow the SWD pins to be attached, if using + self._wrap_inner(self.ic, {DigitalBidir: lambda port, assign: port}) + if self.get(self.reset.is_connected()): self.connect(self.reset, self.ic.nrst) # otherwise NRST has internal pull-up diff --git a/edg/parts/microcontroller/Stm32l432.py b/edg/parts/microcontroller/Stm32l432.py index 569b06e5e..90a7686d5 100644 --- a/edg/parts/microcontroller/Stm32l432.py +++ b/edg/parts/microcontroller/Stm32l432.py @@ -244,15 +244,13 @@ class Stm32l432Base( IoControllerWithSwdTargetConnector, WithCrystalGenerator, IoControllerPowerRequired, - BaseIoControllerExportable, GeneratorBlock, ): DEVICE: Type[Stm32l432Base_Device] = Stm32l432Base_Device def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self.ic: Stm32l432Base_Device - self.generator_param(self.reset.is_connected()) + self.generator_param(self.reset.is_connected(), self.pin_assigns, self.gpio.requested(), self.can.requested()) @override def contents(self) -> None: @@ -275,6 +273,9 @@ def contents(self) -> None: def generate(self) -> None: super().generate() + # add a passthrough for gpio (DigitalBidir) to allow the SWD pins to be attached, if using + self._wrap_inner(self.ic, {DigitalBidir: lambda port, assign: port}) + if self.get(self.reset.is_connected()): self.connect(self.reset, self.ic.nrst) # otherwise NRST has internal pull-up diff --git a/examples/Keyboard/Keyboard.net.ref b/examples/Keyboard/Keyboard.net.ref index f4dfc08a2..e3f510ffb 100644 --- a/examples/Keyboard/Keyboard.net.ref +++ b/examples/Keyboard/Keyboard.net.ref @@ -156,18 +156,6 @@ (property (name "edg_value") (value "50V 1uF X5R ±10% 0603 Multilayer Ceramic Capacitors MLCC - SMD/SMT ROHS")) (sheetpath (names "/mcu/") (tstamps "/02850146/")) (tstamps "15dd03c3")) -(comp (ref "R3") - (value "mcu.usb_pull") - (footprint "Resistor_SMD:R_0603_1608Metric") - (property (name "Sheetname") (value "mcu")) - (property (name "Sheetfile") (value "edg.parts.microcontroller.Stm32f103.Stm32f103_48")) - (property (name "edg_path") (value "mcu.usb_pull.dp")) - (property (name "edg_short_path") (value "mcu.usb_pull")) - (property (name "edg_refdes") (value "R3")) - (property (name "edg_part") (value "0603WAF1501T5E (UNI-ROYAL(Uniroyal Elec))")) - (property (name "edg_value") (value "±1% 1/10W Thick Film Resistors 75V ±100ppm/℃ -55℃~+155℃ 1.5kΩ 0603 Chip Resistor - Surface Mount ROHS")) - (sheetpath (names "/mcu/") (tstamps "/02850146/")) - (tstamps "0f5f0367")) (comp (ref "X1") (value "mcu.crystal.package") (footprint "Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mm") @@ -216,6 +204,18 @@ (property (name "edg_value") (value "PinHeader1.27 Shrouded 2x5")) (sheetpath (names "/mcu/") (tstamps "/02850146/")) (tstamps "02ae014f")) +(comp (ref "R3") + (value "mcu.usb_pull") + (footprint "Resistor_SMD:R_0603_1608Metric") + (property (name "Sheetname") (value "mcu")) + (property (name "Sheetfile") (value "edg.parts.microcontroller.Stm32f103.Stm32f103_48")) + (property (name "edg_path") (value "mcu.usb_pull.dp")) + (property (name "edg_short_path") (value "mcu.usb_pull")) + (property (name "edg_refdes") (value "R3")) + (property (name "edg_part") (value "0603WAF1501T5E (UNI-ROYAL(Uniroyal Elec))")) + (property (name "edg_value") (value "±1% 1/10W Thick Film Resistors 75V ±100ppm/℃ -55℃~+155℃ 1.5kΩ 0603 Chip Resistor - Surface Mount ROHS")) + (sheetpath (names "/mcu/") (tstamps "/02850146/")) + (tstamps "0f5f0367")) (comp (ref "SW1") (value "sw.sw[0,0]") (footprint "Switch_Keyboard_Hotswap_Kailh:SW_Hotswap_Kailh_MX") @@ -426,8 +426,8 @@ (node (ref C6) (pin 1)) (node (ref C7) (pin 1)) (node (ref C8) (pin 1)) - (node (ref R3) (pin 1)) - (node (ref J2) (pin 1))) + (node (ref J2) (pin 1)) + (node (ref R3) (pin 1))) (net (code 8) (name "mcu.gpio.0_0") (node (ref U2) (pin 10)) (node (ref SW1) (pin 2)) diff --git a/examples/Keyboard/Keyboard.svgpcb.js b/examples/Keyboard/Keyboard.svgpcb.js index 45172caa7..cb3b12aca 100644 --- a/examples/Keyboard/Keyboard.svgpcb.js +++ b/examples/Keyboard/Keyboard.svgpcb.js @@ -66,11 +66,6 @@ const C8 = board.add(C_0603_1608Metric, { translate: pt(1.332, 0.647), rotate: 0, id: 'C8' }) -// mcu.usb_pull.dp -const R3 = board.add(R_0603_1608Metric, { - translate: pt(1.488, 0.647), rotate: 0, - id: 'R3' -}) // mcu.crystal.package const X1 = board.add(Crystal_SMD_3225_4Pin_3_2x2_5mm, { translate: pt(1.201, 0.512), rotate: 0, @@ -78,12 +73,12 @@ const X1 = board.add(Crystal_SMD_3225_4Pin_3_2x2_5mm, { }) // mcu.crystal.cap_a const C9 = board.add(C_0603_1608Metric, { - translate: pt(1.644, 0.647), rotate: 0, + translate: pt(1.488, 0.647), rotate: 0, id: 'C9' }) // mcu.crystal.cap_b const C10 = board.add(C_0603_1608Metric, { - translate: pt(1.800, 0.647), rotate: 0, + translate: pt(1.644, 0.647), rotate: 0, id: 'C10' }) // mcu.swd.conn @@ -91,6 +86,11 @@ const J2 = board.add(PinHeader_2x05_P1_27mm_Vertical_SMD, { translate: pt(1.732, 0.146), rotate: 0, id: 'J2' }) +// mcu.usb_pull.dp +const R3 = board.add(R_0603_1608Metric, { + translate: pt(1.800, 0.647), rotate: 0, + id: 'R3' +}) board.setNetlist([ {name: "usb.pwr", pads: [["J1", "A4"], ["J1", "A9"], ["J1", "B4"], ["J1", "B9"], ["U1", "3"], ["C1", "1"]]}, @@ -99,7 +99,7 @@ board.setNetlist([ {name: "usb.usb.dm", pads: [["J1", "A7"], ["J1", "B7"], ["U2", "32"]]}, {name: "usb.conn.cc.cc1", pads: [["J1", "A5"], ["R1", "2"]]}, {name: "usb.conn.cc.cc2", pads: [["J1", "B5"], ["R2", "2"]]}, - {name: "reg.pwr_out", pads: [["U1", "2"], ["C2", "1"], ["U2", "1"], ["U2", "24"], ["U2", "36"], ["U2", "48"], ["U2", "9"], ["C3", "1"], ["C4", "1"], ["C5", "1"], ["C6", "1"], ["C7", "1"], ["C8", "1"], ["R3", "1"], ["J2", "1"]]}, + {name: "reg.pwr_out", pads: [["U1", "2"], ["C2", "1"], ["U2", "1"], ["U2", "24"], ["U2", "36"], ["U2", "48"], ["U2", "9"], ["C3", "1"], ["C4", "1"], ["C5", "1"], ["C6", "1"], ["C7", "1"], ["C8", "1"], ["J2", "1"], ["R3", "1"]]}, {name: "mcu.gpio.0_0", pads: [["U2", "10"], ["SW1", "2"], ["SW2", "2"], ["SW3", "2"]]}, {name: "mcu.gpio.0_1", pads: [["U2", "11"], ["SW4", "2"], ["SW5", "2"], ["SW6", "2"]]}, {name: "mcu.gpio.1_0", pads: [["U2", "12"], ["D1", "2"], ["D4", "2"]]}, diff --git a/examples/SwdDebugger/SwdDebugger.net.ref b/examples/SwdDebugger/SwdDebugger.net.ref index 8b0eb1db1..76987738a 100644 --- a/examples/SwdDebugger/SwdDebugger.net.ref +++ b/examples/SwdDebugger/SwdDebugger.net.ref @@ -240,18 +240,6 @@ (property (name "edg_value") (value "25V 1uF X5R ±10% 0402 Multilayer Ceramic Capacitors MLCC - SMD/SMT ROHS")) (sheetpath (names "/mcu/") (tstamps "/02850146/")) (tstamps "15dd03c3")) -(comp (ref "SR3") - (value "mcu.usb_pull") - (footprint "Resistor_SMD:R_0402_1005Metric") - (property (name "Sheetname") (value "mcu")) - (property (name "Sheetfile") (value "edg.parts.microcontroller.Stm32f103.Stm32f103_48")) - (property (name "edg_path") (value "mcu.usb_pull.dp")) - (property (name "edg_short_path") (value "mcu.usb_pull")) - (property (name "edg_refdes") (value "SR3")) - (property (name "edg_part") (value "0402WGF1501TCE (UNI-ROYAL(Uniroyal Elec))")) - (property (name "edg_value") (value "±1% 1/16W Thick Film Resistors 50V ±100ppm/℃ -55℃~+155℃ 1.5kΩ 0402 Chip Resistor - Surface Mount ROHS")) - (sheetpath (names "/mcu/") (tstamps "/02850146/")) - (tstamps "0f5f0367")) (comp (ref "SU4") (value "mcu.crystal") (footprint "Crystal:Resonator_SMD_Murata_CSTxExxV-3Pin_3.0x1.1mm") @@ -276,6 +264,18 @@ (property (name "edg_value") (value "PinHeader1.27 Shrouded 2x5")) (sheetpath (names "/mcu/") (tstamps "/02850146/")) (tstamps "02ae014f")) +(comp (ref "SR3") + (value "mcu.usb_pull") + (footprint "Resistor_SMD:R_0402_1005Metric") + (property (name "Sheetname") (value "mcu")) + (property (name "Sheetfile") (value "edg.parts.microcontroller.Stm32f103.Stm32f103_48")) + (property (name "edg_path") (value "mcu.usb_pull.dp")) + (property (name "edg_short_path") (value "mcu.usb_pull")) + (property (name "edg_refdes") (value "SR3")) + (property (name "edg_part") (value "0402WGF1501TCE (UNI-ROYAL(Uniroyal Elec))")) + (property (name "edg_value") (value "±1% 1/16W Thick Film Resistors 50V ±100ppm/℃ -55℃~+155℃ 1.5kΩ 0402 Chip Resistor - Surface Mount ROHS")) + (sheetpath (names "/mcu/") (tstamps "/02850146/")) + (tstamps "0f5f0367")) (comp (ref "SU5") (value "usb_esd") (footprint "Package_TO_SOT_SMD:SOT-23") @@ -557,8 +557,8 @@ (node (ref SC8) (pin 1)) (node (ref SC9) (pin 1)) (node (ref SC10) (pin 1)) - (node (ref SR3) (pin 1)) (node (ref SJ2) (pin 1)) + (node (ref SR3) (pin 1)) (node (ref SR6) (pin 1)) (node (ref SR12) (pin 1))) (net (code 4) (name "Svtarget") diff --git a/examples/SwdDebugger/SwdDebugger.svgpcb.js b/examples/SwdDebugger/SwdDebugger.svgpcb.js index bf0f0bdb2..4c7a0155a 100644 --- a/examples/SwdDebugger/SwdDebugger.svgpcb.js +++ b/examples/SwdDebugger/SwdDebugger.svgpcb.js @@ -100,11 +100,6 @@ const SC10 = board.add(C_0402_1005Metric, { translate: pt(0.147, 0.613), rotate: 0, id: 'SC10' }) -// mcu.usb_pull.dp -const SR3 = board.add(R_0402_1005Metric, { - translate: pt(0.399, 0.463), rotate: 0, - id: 'SR3' -}) // mcu.crystal const SU4 = board.add(Resonator_SMD_Murata_CSTxExxV_3Pin_3_0x1_1mm, { translate: pt(0.079, 0.508), rotate: 0, @@ -115,6 +110,11 @@ const SJ2 = board.add(PinHeader_2x05_P1_27mm_Vertical_SMD, { translate: pt(0.614, 0.146), rotate: 0, id: 'SJ2' }) +// mcu.usb_pull.dp +const SR3 = board.add(R_0402_1005Metric, { + translate: pt(0.399, 0.463), rotate: 0, + id: 'SR3' +}) // usb_esd const SU5 = board.add(SOT_23, { translate: pt(1.520, 1.225), rotate: 0, @@ -209,7 +209,7 @@ const SR15 = board.add(R_0402_1005Metric, { board.setNetlist([ {name: "Svusb", pads: [["SJ1", "A4"], ["SJ1", "A9"], ["SJ1", "B4"], ["SJ1", "B9"], ["SD1", "1"], ["SU1", "1"], ["SU1", "3"], ["SC1", "1"], ["SU2", "1"], ["SC3", "1"]]}, {name: "Sgnd", pads: [["SJ1", "A1"], ["SJ1", "A12"], ["SJ1", "B1"], ["SJ1", "B12"], ["SJ1", "S1"], ["SR1", "1"], ["SR2", "1"], ["SD1", "2"], ["SU1", "2"], ["SC1", "2"], ["SC2", "2"], ["SU2", "2"], ["SC3", "2"], ["SC4", "2"], ["SU3", "23"], ["SU3", "35"], ["SU3", "44"], ["SU3", "47"], ["SU3", "8"], ["SC5", "2"], ["SC6", "2"], ["SC7", "2"], ["SC8", "2"], ["SC9", "2"], ["SC10", "2"], ["SU4", "2"], ["SJ2", "3"], ["SJ2", "5"], ["SJ2", "9"], ["SU5", "3"], ["SR4", "2"], ["SR5", "2"], ["SSW1", "2"], ["SJ3", "3"], ["SJ3", "5"], ["SJ3", "9"], ["SR13", "2"], ["SR15", "2"]]}, - {name: "Sv3v3", pads: [["SU1", "5"], ["SC2", "1"], ["SU3", "1"], ["SU3", "24"], ["SU3", "36"], ["SU3", "48"], ["SU3", "9"], ["SC5", "1"], ["SC6", "1"], ["SC7", "1"], ["SC8", "1"], ["SC9", "1"], ["SC10", "1"], ["SR3", "1"], ["SJ2", "1"], ["SR6", "1"], ["SR12", "1"]]}, + {name: "Sv3v3", pads: [["SU1", "5"], ["SC2", "1"], ["SU3", "1"], ["SU3", "24"], ["SU3", "36"], ["SU3", "48"], ["SU3", "9"], ["SC5", "1"], ["SC6", "1"], ["SC7", "1"], ["SC8", "1"], ["SC9", "1"], ["SC10", "1"], ["SJ2", "1"], ["SR3", "1"], ["SR6", "1"], ["SR12", "1"]]}, {name: "Svtarget", pads: [["SU2", "5"], ["SC4", "1"], ["SJ3", "1"], ["SD4", "2"], ["SR14", "1"]]}, {name: "Susb_chain_0.d_P", pads: [["SJ1", "A6"], ["SJ1", "B6"], ["SU3", "33"], ["SR3", "2"], ["SU5", "2"]]}, {name: "Susb_chain_0.d_N", pads: [["SJ1", "A7"], ["SJ1", "B7"], ["SU3", "32"], ["SU5", "1"]]}, diff --git a/examples/TofArray/TofArray.net.ref b/examples/TofArray/TofArray.net.ref index 33105948f..cb7adfffa 100644 --- a/examples/TofArray/TofArray.net.ref +++ b/examples/TofArray/TofArray.net.ref @@ -252,18 +252,6 @@ (property (name "edg_value") (value "50V 1uF X5R ±10% 0603 Multilayer Ceramic Capacitors MLCC - SMD/SMT ROHS")) (sheetpath (names "/mcu/") (tstamps "/02850146/")) (tstamps "15dd03c3")) -(comp (ref "R3") - (value "mcu.usb_pull") - (footprint "Resistor_SMD:R_0603_1608Metric") - (property (name "Sheetname") (value "mcu")) - (property (name "Sheetfile") (value "edg.parts.microcontroller.Stm32f103.Stm32f103_48")) - (property (name "edg_path") (value "mcu.usb_pull.dp")) - (property (name "edg_short_path") (value "mcu.usb_pull")) - (property (name "edg_refdes") (value "R3")) - (property (name "edg_part") (value "0603WAF1501T5E (UNI-ROYAL(Uniroyal Elec))")) - (property (name "edg_value") (value "±1% 1/10W Thick Film Resistors 75V ±100ppm/℃ -55℃~+155℃ 1.5kΩ 0603 Chip Resistor - Surface Mount ROHS")) - (sheetpath (names "/mcu/") (tstamps "/02850146/")) - (tstamps "0f5f0367")) (comp (ref "X1") (value "mcu.crystal.package") (footprint "Crystal:Crystal_SMD_3225-4Pin_3.2x2.5mm") @@ -312,6 +300,18 @@ (property (name "edg_value") (value "")) (sheetpath (names "/mcu/") (tstamps "/02850146/")) (tstamps "02ae014f")) +(comp (ref "R3") + (value "mcu.usb_pull") + (footprint "Resistor_SMD:R_0603_1608Metric") + (property (name "Sheetname") (value "mcu")) + (property (name "Sheetfile") (value "edg.parts.microcontroller.Stm32f103.Stm32f103_48")) + (property (name "edg_path") (value "mcu.usb_pull.dp")) + (property (name "edg_short_path") (value "mcu.usb_pull")) + (property (name "edg_refdes") (value "R3")) + (property (name "edg_part") (value "0603WAF1501T5E (UNI-ROYAL(Uniroyal Elec))")) + (property (name "edg_value") (value "±1% 1/10W Thick Film Resistors 75V ±100ppm/℃ -55℃~+155℃ 1.5kΩ 0603 Chip Resistor - Surface Mount ROHS")) + (sheetpath (names "/mcu/") (tstamps "/02850146/")) + (tstamps "0f5f0367")) (comp (ref "SW1") (value "sw1") (footprint "Button_Switch_SMD:SW_SPST_SKQG_WithoutStem") @@ -972,8 +972,8 @@ (node (ref C6) (pin 1)) (node (ref C7) (pin 1)) (node (ref C8) (pin 1)) - (node (ref R3) (pin 1)) (node (ref J3) (pin 1)) + (node (ref R3) (pin 1)) (node (ref D2) (pin 2)) (node (ref D3) (pin 2)) (node (ref D4) (pin 2)) diff --git a/examples/TofArray/TofArray.svgpcb.js b/examples/TofArray/TofArray.svgpcb.js index 6f96cbcfb..7c8565b4c 100644 --- a/examples/TofArray/TofArray.svgpcb.js +++ b/examples/TofArray/TofArray.svgpcb.js @@ -105,11 +105,6 @@ const C8 = board.add(C_0603_1608Metric, { translate: pt(0.214, 0.647), rotate: 0, id: 'C8' }) -// mcu.usb_pull.dp -const R3 = board.add(R_0603_1608Metric, { - translate: pt(0.370, 0.647), rotate: 0, - id: 'R3' -}) // mcu.crystal.package const X1 = board.add(Crystal_SMD_3225_4Pin_3_2x2_5mm, { translate: pt(0.083, 0.512), rotate: 0, @@ -117,12 +112,12 @@ const X1 = board.add(Crystal_SMD_3225_4Pin_3_2x2_5mm, { }) // mcu.crystal.cap_a const C9 = board.add(C_0603_1608Metric, { - translate: pt(0.526, 0.647), rotate: 0, + translate: pt(0.370, 0.647), rotate: 0, id: 'C9' }) // mcu.crystal.cap_b const C10 = board.add(C_0603_1608Metric, { - translate: pt(0.682, 0.647), rotate: 0, + translate: pt(0.526, 0.647), rotate: 0, id: 'C10' }) // mcu.swd.conn @@ -130,6 +125,11 @@ const J3 = board.add(Tag_Connect_TC2050_IDC_FP_2x05_P1_27mm_Vertical, { translate: pt(0.661, 0.167), rotate: 0, id: 'J3' }) +// mcu.usb_pull.dp +const R3 = board.add(R_0603_1608Metric, { + translate: pt(0.682, 0.647), rotate: 0, + id: 'R3' +}) // sw1.package const SW1 = board.add(SW_SPST_SKQG_WithoutStem, { translate: pt(0.742, 1.424), rotate: 0, @@ -364,7 +364,7 @@ const D7 = board.add(LED_LiteOn_LTST_C19HE1WT, { board.setNetlist([ {name: "vusb", pads: [["J1", "A4"], ["J1", "A9"], ["J1", "B4"], ["J1", "B9"], ["TP1", "1"], ["U1", "3"], ["C1", "1"], ["U11", "1"], ["U11", "6"], ["C23", "1"], ["C24", "1"]]}, {name: "gnd", pads: [["J1", "A1"], ["J1", "A12"], ["J1", "B1"], ["J1", "B12"], ["J1", "S1"], ["R1", "1"], ["R2", "1"], ["J2", "3"], ["TP2", "1"], ["U1", "1"], ["C1", "2"], ["C2", "2"], ["D1", "2"], ["U2", "23"], ["U2", "35"], ["U2", "44"], ["U2", "47"], ["U2", "8"], ["C3", "2"], ["C4", "2"], ["C5", "2"], ["C6", "2"], ["C7", "2"], ["C8", "2"], ["X1", "2"], ["X1", "4"], ["C9", "2"], ["C10", "2"], ["J3", "2"], ["J3", "3"], ["J3", "5"], ["SW1", "2"], ["U3", "12"], ["U3", "2"], ["U3", "3"], ["U3", "4"], ["U3", "6"], ["C11", "2"], ["C12", "2"], ["U4", "12"], ["U4", "2"], ["U4", "3"], ["U4", "4"], ["U4", "6"], ["C13", "2"], ["C14", "2"], ["U5", "12"], ["U5", "2"], ["U5", "3"], ["U5", "4"], ["U5", "6"], ["C15", "2"], ["C16", "2"], ["U6", "12"], ["U6", "2"], ["U6", "3"], ["U6", "4"], ["U6", "6"], ["C17", "2"], ["C18", "2"], ["U7", "12"], ["U7", "2"], ["U7", "3"], ["U7", "4"], ["U7", "6"], ["C19", "2"], ["C20", "2"], ["U8", "3"], ["U9", "2"], ["U9", "8"], ["C21", "2"], ["U10", "3"], ["C22", "2"], ["U11", "7"], ["U11", "9"], ["C23", "2"], ["C24", "2"], ["C26", "2"]]}, - {name: "v3v3", pads: [["U1", "2"], ["C2", "1"], ["TP3", "1"], ["D1", "1"], ["U2", "1"], ["U2", "24"], ["U2", "36"], ["U2", "48"], ["U2", "9"], ["C3", "1"], ["C4", "1"], ["C5", "1"], ["C6", "1"], ["C7", "1"], ["C8", "1"], ["R3", "1"], ["J3", "1"], ["D2", "2"], ["D3", "2"], ["D4", "2"], ["D5", "2"], ["D6", "2"], ["U3", "1"], ["U3", "11"], ["C11", "1"], ["C12", "1"], ["U4", "1"], ["U4", "11"], ["C13", "1"], ["C14", "1"], ["U5", "1"], ["U5", "11"], ["C15", "1"], ["C16", "1"], ["U6", "1"], ["U6", "11"], ["C17", "1"], ["C18", "1"], ["U7", "1"], ["U7", "11"], ["C19", "1"], ["C20", "1"], ["R4", "1"], ["R5", "1"], ["U9", "3"], ["C21", "1"], ["D7", "2"]]}, + {name: "v3v3", pads: [["U1", "2"], ["C2", "1"], ["TP3", "1"], ["D1", "1"], ["U2", "1"], ["U2", "24"], ["U2", "36"], ["U2", "48"], ["U2", "9"], ["C3", "1"], ["C4", "1"], ["C5", "1"], ["C6", "1"], ["C7", "1"], ["C8", "1"], ["J3", "1"], ["R3", "1"], ["D2", "2"], ["D3", "2"], ["D4", "2"], ["D5", "2"], ["D6", "2"], ["U3", "1"], ["U3", "11"], ["C11", "1"], ["C12", "1"], ["U4", "1"], ["U4", "11"], ["C13", "1"], ["C14", "1"], ["U5", "1"], ["U5", "11"], ["C15", "1"], ["C16", "1"], ["U6", "1"], ["U6", "11"], ["C17", "1"], ["C18", "1"], ["U7", "1"], ["U7", "11"], ["C19", "1"], ["C20", "1"], ["R4", "1"], ["R5", "1"], ["U9", "3"], ["C21", "1"], ["D7", "2"]]}, {name: "sw1_chain_0", pads: [["U2", "19"], ["SW1", "1"]]}, {name: "leds_chain_0.0", pads: [["U2", "20"], ["RN1", "8"]]}, {name: "leds_chain_0.1", pads: [["U2", "25"], ["RN1", "7"]]}, From e59e10f9bf62194d6d72c08edf0346cca923cd1e Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 25 May 2026 18:16:02 -0700 Subject: [PATCH 02/28] Update nRF52840.py --- edg/parts/microcontroller/nRF52840.py | 511 ++++++++++++++++++++------ 1 file changed, 398 insertions(+), 113 deletions(-) diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index 9adc4609e..4234989a8 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -329,104 +329,10 @@ def __init__(self, **kwargs: Any) -> None: self._io_ports.insert(0, self.swd) -class Holyiot_18010_Device(Nrf52840_Base, InternalSubcircuit): - SYSTEM_PIN_REMAP: Dict[str, Union[str, List[str]]] = { - "Vdd": "14", - "Vss": ["1", "25", "37"], - "Vbus": "22", - "nRESET": "21", - } - RESOURCE_PIN_REMAP = { # boundary pins only, inner pins ignored - "P1.11": "2", - "P1.10": "3", - "P1.13": "4", - "P1.15": "5", - "P0.03": "6", - "P0.02": "7", - "P0.28": "8", - "P0.29": "9", - "P0.30": "10", - "P0.31": "11", - "P0.04": "12", - "P0.05": "13", - "P0.07": "15", - "P1.09": "16", - "P0.12": "17", - "P0.23": "18", - "P0.21": "19", - "P0.19": "20", - "D-": "23", - "D+": "24", - "P0.22": "26", - "P1.00": "27", - "P1.03": "28", - "P1.01": "29", - "P1.02": "30", - "SWCLK": "31", - "SWDIO": "32", - "P1.04": "33", - "P1.06": "34", - "P0.09": "35", - "P0.10": "36", - } - - @override - def generate(self) -> None: - super().generate() - - self.footprint( - "U", - "edg:Holyiot-18010-NRF52840", - self._make_pinning(), - mfr="Holyiot", - part="18010", - datasheet="http://www.holyiot.com/tp/2019042516322180424.pdf", - ) - - -class Holyiot_18010( - Microcontroller, - Radiofrequency, - Resettable, - Nrf52840_Interfaces, - IoControllerWithSwdTargetConnector, - IoControllerPowerRequired, - BaseIoControllerExportable, - GeneratorBlock, +class Mdbt50q_1mv2_Device( + Nrf52840_Interfaces, BaseIoControllerPinmapGenerator, InternalSubcircuit, JlcPart, GeneratorBlock, FootprintBlock ): - """Wrapper around the Holyiot 18010 that includes supporting components (programming port)""" - - def __init__(self, **kwargs: Any) -> None: - super().__init__(**kwargs) - self.ic: Holyiot_18010_Device - self.ic = self.Block(Holyiot_18010_Device(pin_assigns=ArrayStringExpr())) - self.pwr_usb = self.Export(self.ic.pwr_usb, optional=True) - self.generator_param(self.reset.is_connected()) - - @override - def contents(self) -> None: - super().contents() - self.connect(self.pwr, self.ic.pwr) - self.connect(self.gnd, self.ic.gnd) - - self.connect(self.swd_node, self.ic.swd) - self.connect(self.reset_node, self.ic.nreset) - - @override - def generate(self) -> None: - super().generate() - if self.get(self.reset.is_connected()): - self.connect(self.reset, self.ic.nreset) - - -class Mdbt50q_1mv2_Device(Nrf52840_Base, InternalSubcircuit, JlcPart): - SYSTEM_PIN_REMAP: Dict[str, Union[str, List[str]]] = { - "Vdd": ["28", "30"], # 28=Vdd, 30=VddH; 31=DccH is disconnected - from section 8.3 for input voltage <3.6v - "Vss": ["1", "2", "15", "33", "55"], - "Vbus": "32", - "nRESET": "40", - } - RESOURCE_PIN_REMAP = { # boundary pins only, inner pins ignored + _PIN_MAPPING = { # boundary pins only, inner pins ignored "P1.10": "3", "P1.11": "4", "P1.12": "5", @@ -480,6 +386,53 @@ class Mdbt50q_1mv2_Device(Nrf52840_Base, InternalSubcircuit, JlcPart): "P1.01": "61", } + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + + self.gnd = self.Port(Ground(), [Common]) + self.pwr = self.Port( + VoltageSink( + voltage_limits=(1.75, 3.6) * Volt, # 1.75 minimum for power-on reset + current_draw=(0, 212 / 64 + 4.8) * mAmp + + self.io_current_draw.upper(), # CPU @ max 212 Coremarks + 4.8mA in RF transmit + ), + [Power], + ) + + self.pwr_usb = self.Port( + VoltageSink( + voltage_limits=(4.35, 5.5) * Volt, + current_draw=(0.262, 7.73) * mAmp, # CPU/USB sleeping to everything active + ), + optional=True, + ) + self.require((self.usb.length() > 0).implies(self.pwr_usb.is_connected()), "USB require Vbus connected") + + # Additional ports (on top of IoController) + # Crystals from table 15, 32, 33 + # TODO Table 32, model crystal load capacitance and series resistance ratings + self.xtal = self.Port( + CrystalDriver(frequency_limits=(1, 25) * MHertz, voltage_out=self.pwr.link().voltage), optional=True + ) + # Assumed from "32kHz crystal" in 14.5 + self.xtal_rtc = self.Port( + CrystalDriver(frequency_limits=(32, 33) * kHertz, voltage_out=self.pwr.link().voltage), optional=True + ) + + self._dio_model = DigitalBidir.from_supply( + self.gnd, + self.pwr, + voltage_limit_tolerance=(-0.3, 0.3) * Volt, + current_limits=(-6, 6) * mAmp, # minimum current, high drive, Vdd>2.7 + input_threshold_factor=(0.3, 0.7), + pullup_capable=True, + pulldown_capable=True, + ) + self._dio_lf_model = self._dio_model # "standard drive, low frequency IO only" (differences not modeled) + self.swd = self.Port(SwdTargetPort.empty()) + self.nreset = self.Port(DigitalSink.from_bidir(self._dio_model), optional=True) + self._io_ports.insert(0, self.swd) + @override def generate(self) -> None: super().generate() @@ -495,6 +448,253 @@ def generate(self) -> None: datasheet="https://www.raytac.com/download/index.php?index_id=43", ) + @override + def _system_pinmap(self) -> Dict[str, Union[Passive, HasPassivePort]]: + return { + "28": self.pwr, # Vdd + "30": self.pwr, # VddH + # "31": DccH is disconnected - from section 8.3 for input voltage <3.6v + "1": self.gnd, + "2": self.gnd, + "15": self.gnd, + "33": self.gnd, + "55": self.gnd, + "32": self.pwr_usb, + "40": self.nreset, + } + + @override + def _io_pinmap(self) -> PinMapUtil: + """Returns the mappable for given the input power and ground references. + This separates the system pins definition from the IO pins definition.""" + adc_model = AnalogSink.from_supply( + self.gnd, + self.pwr, + voltage_limit_tolerance=(0, 0), # datasheet 6.23.2, analog inputs cannot exceed Vdd or be lower than Vss + signal_limit_tolerance=(0, 0), + impedance=Range.from_lower(1) * MOhm, + ) + + uart_model = UartPort(DigitalBidir.empty()) + spi_model = SpiController(DigitalBidir.empty(), (125, 32000) * kHertz) + spi_peripheral_model = SpiPeripheral(DigitalBidir.empty(), (125, 32000) * kHertz) # tristated by CS pin + i2c_model = I2cController(DigitalBidir.empty()) + i2c_target_model = I2cTarget(DigitalBidir.empty()) + i2s_model = I2sController(DigitalBidir.empty()) + + hf_io_pins = [ + "P0.00", + "P0.01", + "P0.26", + "P0.27", + "P0.04", + "P0.05", + "P0.06", + "P0.07", + "P0.08", + "P1.08", + "P1.09", + "P0.11", + "P0.12", + "P0.14", + "P0.16", + "P0.19", + "P0.21", + "P0.23", + "P0.25", # 'P0.18' + "P0.13", + "P0.15", + "P0.17", + "P0.20", + "P0.22", + "P0.24", + "P1.00", + ] + + return PinMapUtil( + [ # Section 7.1.2 with QIAA aQFN73 & QFAA QFN48 pins only + PinResource("P0.31", {"P0.31": self._dio_lf_model, "AIN7": adc_model}), + PinResource("P0.29", {"P0.29": self._dio_lf_model, "AIN5": adc_model}), + PinResource("P0.02", {"P0.02": self._dio_lf_model, "AIN0": adc_model}), + PinResource("P1.15", {"P1.15": self._dio_lf_model}), + PinResource("P1.13", {"P1.13": self._dio_lf_model}), + PinResource("P1.10", {"P1.10": self._dio_lf_model}), + PinResource("P0.30", {"P0.30": self._dio_lf_model, "AIN6": adc_model}), + PinResource("P0.28", {"P0.28": self._dio_lf_model, "AIN4": adc_model}), + PinResource("P0.03", {"P0.03": self._dio_lf_model, "AIN1": adc_model}), + PinResource("P1.14", {"P1.14": self._dio_lf_model}), + PinResource("P1.12", {"P1.12": self._dio_lf_model}), + PinResource("P1.11", {"P1.11": self._dio_lf_model}), + PinResource("P0.00", {"P0.00": self._dio_model}), # TODO also 32.768 kHz crystal in + PinResource("P0.01", {"P0.01": self._dio_model}), # TODO also 32.768 kHz crystal in + PinResource("P0.26", {"P0.26": self._dio_model}), + PinResource("P0.27", {"P0.27": self._dio_model}), + PinResource("P0.04", {"P0.04": self._dio_model, "AIN2": adc_model}), + PinResource("P0.10", {"P0.10": self._dio_lf_model}), # TODO also NFC2 + PinResource("P0.05", {"P0.05": self._dio_model, "AIN3": adc_model}), + PinResource("P0.06", {"P0.06": self._dio_model}), + PinResource("P0.09", {"P0.09": self._dio_lf_model}), # TODO also NFC1 + PinResource("P0.07", {"P0.07": self._dio_model}), + PinResource("P0.08", {"P0.08": self._dio_model}), + PinResource("P1.08", {"P1.08": self._dio_model}), + PinResource("P1.07", {"P1.07": self._dio_lf_model}), + PinResource("P1.09", {"P1.09": self._dio_model}), + PinResource("P1.06", {"P1.06": self._dio_lf_model}), + PinResource("P0.11", {"P0.11": self._dio_model}), + PinResource("P1.05", {"P1.05": self._dio_lf_model}), + PinResource("P0.12", {"P0.12": self._dio_model}), + PinResource("P1.04", {"P1.04": self._dio_lf_model}), + PinResource("P1.03", {"P1.03": self._dio_lf_model}), + PinResource("P1.02", {"P1.02": self._dio_lf_model}), + PinResource("P1.01", {"P1.01": self._dio_lf_model}), + PinResource("P0.14", {"P0.14": self._dio_model}), + PinResource("P0.16", {"P0.16": self._dio_model}), + # PinResource('P0.18', {'P0.18': dio_model}), # configurable as RESET, mappable + PinResource("P0.19", {"P0.19": self._dio_model}), + PinResource("P0.21", {"P0.21": self._dio_model}), + PinResource("P0.23", {"P0.23": self._dio_model}), + PinResource("P0.25", {"P0.25": self._dio_model}), + PinResource("P0.13", {"P0.13": self._dio_model}), + PinResource("P0.15", {"P0.15": self._dio_model}), + PinResource("P0.17", {"P0.17": self._dio_model}), + PinResource("P0.20", {"P0.20": self._dio_model}), + PinResource("P0.22", {"P0.22": self._dio_model}), + PinResource("P0.24", {"P0.24": self._dio_model}), + PinResource( + "P1.00", {"P1.00": self._dio_model} + ), # TRACEDATA[0] and SWO, if used as IO must clear TRACECONFIG reg + PeripheralFixedPin( + "SWD", + SwdTargetPort(self._dio_model), + { + "swclk": "SWCLK", + "swdio": "SWDIO", + }, + ), + PeripheralFixedPin("USBD", UsbDevicePort(), {"dp": "D+", "dm": "D-"}), + PeripheralFixedResource( + "SPIM0", + spi_model, + { + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIM1", + spi_model, + { + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIM2", + spi_model, + { + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIM3", + spi_model, + { + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIS0", + spi_peripheral_model, + { # TODO shared resource w/ SPI controller + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIS1", + spi_peripheral_model, + { # TODO shared resource w/ SPI controller + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIS2", + spi_peripheral_model, + { # TODO shared resource w/ SPI controller + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "TWIM0", + i2c_model, + { + "scl": hf_io_pins, + "sda": hf_io_pins, + }, + ), + PeripheralFixedResource( + "TWIM1", + i2c_model, + { + "scl": hf_io_pins, + "sda": hf_io_pins, + }, + ), + PeripheralFixedResource( + "TWIS0", + i2c_target_model, + { # TODO shared resource w/ I2C controller + "scl": hf_io_pins, + "sda": hf_io_pins, + }, + ), + PeripheralFixedResource( + "TWIS1", + i2c_target_model, + { # TODO shared resource w/ I2C controller + "scl": hf_io_pins, + "sda": hf_io_pins, + }, + ), + PeripheralFixedResource( + "UARTE0", + uart_model, + { + "tx": hf_io_pins, + "rx": hf_io_pins, + }, + ), + PeripheralFixedResource( + "UARTE1", + uart_model, + { + "tx": hf_io_pins, + "rx": hf_io_pins, + }, + ), + PeripheralFixedResource( + "I2S", + i2s_model, + { + "sck": hf_io_pins, + "ws": hf_io_pins, + "sd": hf_io_pins, + }, + ), + ] + ).remap_pins(self._PIN_MAPPING) + class Mdbt50q_1mv2( Microcontroller, @@ -503,19 +703,20 @@ class Mdbt50q_1mv2( Nrf52840_Interfaces, IoControllerWithSwdTargetConnector, IoControllerPowerRequired, - BaseIoControllerExportable, GeneratorBlock, ): - """Wrapper around the Mdbt50q_1mv2 that includes the reference schematic""" + """Wrapper around the Mdbt50q_1mv2 that includes the reference schematic.""" + + # in the absence of a chip-level subcircuit, this is used as the authoritative base device model + # that other modules should wrap def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self.ic: Mdbt50q_1mv2_Device self.ic = self.Block( Mdbt50q_1mv2_Device(pin_assigns=ArrayStringExpr()) ) # defined in generator to mix in SWO/TDI self.pwr_usb = self.Export(self.ic.pwr_usb, optional=True) - self.generator_param(self.reset.is_connected()) + self.generator_param(self.reset.is_connected(), self.pin_assigns, self.gpio.requested(), self.usb.requested()) @override def contents(self) -> None: @@ -533,23 +734,107 @@ def contents(self) -> None: def generate(self) -> None: super().generate() + def usb_export_transform(self_io: BasePort, assign: Optional[str]) -> Optional[BasePort]: + self.vbus_cap = self.Block(DecouplingCapacitor(10 * uFarad(tol=0.2))).connected(self.gnd, self.pwr_usb) + self.usb_res = self.Block(UsbSeriesResistor(27 * Ohm(tol=0.05))) + self.connect(self_io, self.usb_res.exterior) + return self.usb_res.interior + + # add a passthrough for gpio (DigitalBidir) to allow the SWD pins to be attached, if using + self._wrap_inner(self.ic, {UsbDevicePort: usb_export_transform, DigitalBidir: lambda port, assign: port}) + if self.get(self.reset.is_connected()): self.connect(self.reset, self.ic.nreset) - ExportType = TypeVar("ExportType", bound=Port) + +class Holyiot_18010_Device(Nrf52840_Base, InternalSubcircuit): + SYSTEM_PIN_REMAP: Dict[str, Union[str, List[str]]] = { + "Vdd": "14", + "Vss": ["1", "25", "37"], + "Vbus": "22", + "nRESET": "21", + } + RESOURCE_PIN_REMAP = { # boundary pins only, inner pins ignored + "P1.11": "2", + "P1.10": "3", + "P1.13": "4", + "P1.15": "5", + "P0.03": "6", + "P0.02": "7", + "P0.28": "8", + "P0.29": "9", + "P0.30": "10", + "P0.31": "11", + "P0.04": "12", + "P0.05": "13", + "P0.07": "15", + "P1.09": "16", + "P0.12": "17", + "P0.23": "18", + "P0.21": "19", + "P0.19": "20", + "D-": "23", + "D+": "24", + "P0.22": "26", + "P1.00": "27", + "P1.03": "28", + "P1.01": "29", + "P1.02": "30", + "SWCLK": "31", + "SWDIO": "32", + "P1.04": "33", + "P1.06": "34", + "P0.09": "35", + "P0.10": "36", + } + + @override + def generate(self) -> None: + super().generate() + + self.footprint( + "U", + "edg:Holyiot-18010-NRF52840", + self._make_pinning(), + mfr="Holyiot", + part="18010", + datasheet="http://www.holyiot.com/tp/2019042516322180424.pdf", + ) + + +class Holyiot_18010( + Microcontroller, + Radiofrequency, + Resettable, + Nrf52840_Interfaces, + IoControllerWithSwdTargetConnector, + IoControllerPowerRequired, + BaseIoControllerExportable, + GeneratorBlock, +): + """Wrapper around the Holyiot 18010 that includes supporting components (programming port)""" + + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + self.ic: Holyiot_18010_Device + self.ic = self.Block(Holyiot_18010_Device(pin_assigns=ArrayStringExpr())) + self.pwr_usb = self.Export(self.ic.pwr_usb, optional=True) + self.generator_param(self.reset.is_connected()) @override - def _make_export_vector( - self, self_io: ExportType, inner_vector: Vector[ExportType], name: str, assign: Optional[str] - ) -> Optional[str]: - if isinstance(self_io, UsbDevicePort): # assumed at most one USB port generates - inner_io = inner_vector.request(name) - (self.usb_res,), self.usb_chain = self.chain( - inner_io, self.Block(UsbSeriesResistor(27 * Ohm(tol=0.05))), self_io - ) - self.vbus_cap = self.Block(DecouplingCapacitor(10 * uFarad(tol=0.2))).connected(self.gnd, self.pwr_usb) - return assign - return super()._make_export_vector(self_io, inner_vector, name, assign) + def contents(self) -> None: + super().contents() + self.connect(self.pwr, self.ic.pwr) + self.connect(self.gnd, self.ic.gnd) + + self.connect(self.swd_node, self.ic.swd) + self.connect(self.reset_node, self.ic.nreset) + + @override + def generate(self) -> None: + super().generate() + if self.get(self.reset.is_connected()): + self.connect(self.reset, self.ic.nreset) class Feather_Nrf52840( From 2f1d24ace80de8e476588b1d49e757dd4b02e266 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 25 May 2026 18:37:02 -0700 Subject: [PATCH 03/28] Update nRF52840.py --- edg/parts/microcontroller/nRF52840.py | 123 +++++++++++++++++++++----- 1 file changed, 102 insertions(+), 21 deletions(-) diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index 4234989a8..ef83c5c30 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -712,9 +712,7 @@ class Mdbt50q_1mv2( def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self.ic = self.Block( - Mdbt50q_1mv2_Device(pin_assigns=ArrayStringExpr()) - ) # defined in generator to mix in SWO/TDI + self.ic = self.Block(Mdbt50q_1mv2_Device(pin_assigns=ArrayStringExpr())) self.pwr_usb = self.Export(self.ic.pwr_usb, optional=True) self.generator_param(self.reset.is_connected(), self.pin_assigns, self.gpio.requested(), self.usb.requested()) @@ -747,14 +745,10 @@ def usb_export_transform(self_io: BasePort, assign: Optional[str]) -> Optional[B self.connect(self.reset, self.ic.nreset) -class Holyiot_18010_Device(Nrf52840_Base, InternalSubcircuit): - SYSTEM_PIN_REMAP: Dict[str, Union[str, List[str]]] = { - "Vdd": "14", - "Vss": ["1", "25", "37"], - "Vbus": "22", - "nRESET": "21", - } - RESOURCE_PIN_REMAP = { # boundary pins only, inner pins ignored +class Holyiot_18010_Device( + Nrf52840_Interfaces, IoControllerWrapped, InternalSubcircuit, GeneratorBlock, FootprintBlock +): + _PIN_REMAPPING = { # boundary pins only, inner pins ignored "P1.11": "2", "P1.10": "3", "P1.13": "4", @@ -788,10 +782,41 @@ class Holyiot_18010_Device(Nrf52840_Base, InternalSubcircuit): "P0.10": "36", } + SYSTEM_PIN_REMAP: Dict[str, Union[str, List[str]]] = { + "Vdd": "14", + "Vss": ["1", "25", "37"], + "Vbus": "22", + "nRESET": "21", + } + + def __init__(self, model_pin_assigns: ArrayStringLike): + super().__init__() + self.model_pin_assigns = self.ArgParameter(model_pin_assigns) + self.gnd = self.Port(Ground.empty()) + self.vdd_nrf = self.Port(VoltageSink.empty(), optional=True) + self.vbus = self.Port(VoltageSink.empty(), optional=True) + self.p0_18 = self.Port(DigitalSink.empty(), optional=True) # nRESET + self.swd = self.Port(SwdTargetPort.empty()) + self._io_ports.insert(0, self.swd) + @override def generate(self) -> None: super().generate() + pinning: Dict[str, HasPassivePort] = { + "14": self.vdd_nrf, + "1": self.gnd, + "25": self.gnd, + "37": self.gnd, + "22": self.vbus, + "21": self.p0_18, + } + remap_pinnings, remap_pin_assigns = self._remap_pinning_assigns( + self.get(self.model_pin_assigns), self._PIN_REMAPPING + ) + pinning.update(remap_pinnings) + self.assign(self.actual_pin_assigns, self._remap_assigns_to_value(remap_pin_assigns)) + self.footprint( "U", "edg:Holyiot-18010-NRF52840", @@ -802,39 +827,95 @@ def generate(self) -> None: ) +class Holyiot_18010_Subcircuit( + Nrf52840_Interfaces, + IoControllerWithSwdTargetConnector, + InternalSubcircuit, + GeneratorBlock, +): + """Wrapper around the Mdbt50q_1mv2 that includes the reference schematic.""" + + # in the absence of a chip-level subcircuit, this is used as the authoritative base device model + # that other modules should wrap + + def __init__(self, model_pin_assigns: ArrayStringLike, **kwargs) -> None: + super().__init__(**kwargs) + self.ic = self.Block(Holyiot_18010_Device(model_pin_assigns=model_pin_assigns)) + self.pwr_usb = self.Export(self.ic.vbus, optional=True) + self.reset = self.Export(self.ic.p0_18, optional=True) + self.generator_param(self.reset.is_connected(), self.pin_assigns, self.gpio.requested(), self.usb.requested()) + + @override + def contents(self) -> None: + super().contents() + self.connect(self.pwr, self.ic.vdd_nrf) + self.connect(self.gnd, self.ic.gnd) + + self.connect(self.swd_node, self.ic.swd) + + with self.implicit_connect(ImplicitConnect(self.pwr, [Power]), ImplicitConnect(self.gnd, [Common])) as imp: + self.vcc_cap = imp.Block(DecouplingCapacitor(10 * uFarad(tol=0.2))) + + @override + def generate(self) -> None: + super().generate() + + def usb_export_transform(self_io: BasePort, assign: Optional[str]) -> Optional[BasePort]: + self.vbus_cap = self.Block(DecouplingCapacitor(10 * uFarad(tol=0.2))).connected(self.gnd, self.pwr_usb) + self.usb_res = self.Block(UsbSeriesResistor(27 * Ohm(tol=0.05))) + self.connect(self_io, self.usb_res.exterior) + return self.usb_res.interior + + # add a passthrough for gpio (DigitalBidir) to allow the SWD pins to be attached, if using + self._wrap_inner(self.ic, {UsbDevicePort: usb_export_transform, DigitalBidir: lambda port, assign: port}) + + if self.get(self.reset.is_connected()): + self.connect(self.reset, self.ic.nreset) + + class Holyiot_18010( Microcontroller, Radiofrequency, Resettable, Nrf52840_Interfaces, - IoControllerWithSwdTargetConnector, IoControllerPowerRequired, - BaseIoControllerExportable, GeneratorBlock, + WrapperSubboardBlock, ): """Wrapper around the Holyiot 18010 that includes supporting components (programming port)""" def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self.ic: Holyiot_18010_Device - self.ic = self.Block(Holyiot_18010_Device(pin_assigns=ArrayStringExpr())) - self.pwr_usb = self.Export(self.ic.pwr_usb, optional=True) + self.model = self.Block(Mdbt50q_1mv2(pin_assigns=ArrayStringExpr())) + self.pwr_usb = self.Export(self.model.pwr_usb, optional=True) + self.generator_param(self.reset.is_connected()) @override def contents(self) -> None: super().contents() - self.connect(self.pwr, self.ic.pwr) - self.connect(self.gnd, self.ic.gnd) - self.connect(self.swd_node, self.ic.swd) - self.connect(self.reset_node, self.ic.nreset) + model_pin_assigns = self._export_ios_inner(self.model) + self.assign(self.model.pin_assigns, model_pin_assigns) + self.connect(self.pwr, self.model.pwr) + self.connect(self.gnd, self.model.gnd) + + self.device = self.Block( + Holyiot_18010_Subcircuit(model_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.pwr, self.device.pwr) + self.export_tap(self.pwr_usb, self.device.pwr_usb) @override def generate(self) -> None: super().generate() + if self.get(self.reset.is_connected()): - self.connect(self.reset, self.ic.nreset) + self.connect(self.reset, self.model.nreset) + self.export_tap(self.reset, self.device.reset) class Feather_Nrf52840( From c5ef561b5a91c15b64819b7abbdf9cae9aef10ef Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 25 May 2026 18:47:38 -0700 Subject: [PATCH 04/28] wip --- edg/abstract_parts/IoControllerWrapped.py | 2 ++ edg/parts/microcontroller/Rp2040.py | 13 +++++-------- edg/parts/microcontroller/nRF52840.py | 17 ++++++----------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/edg/abstract_parts/IoControllerWrapped.py b/edg/abstract_parts/IoControllerWrapped.py index 7202b3347..1c0ae03ee 100644 --- a/edg/abstract_parts/IoControllerWrapped.py +++ b/edg/abstract_parts/IoControllerWrapped.py @@ -9,6 +9,8 @@ class IoControllerWrapped(BaseIoController): with an outer WrapperSubboardBlock to implement e.g. a dev board or module around a modeling subcircuit. Provides some utility functions to remap pin assignments from the model to the footprint. + + In this class, pin_assigns is treated as the model's pin assigns and internally remapped. """ def _remap_pinning_assigns( diff --git a/edg/parts/microcontroller/Rp2040.py b/edg/parts/microcontroller/Rp2040.py index c4ce7bce4..0f0c973b8 100644 --- a/edg/parts/microcontroller/Rp2040.py +++ b/edg/parts/microcontroller/Rp2040.py @@ -384,15 +384,14 @@ class Xiao_Rp2040_Device(Rp2040_Interfaces, IoControllerWrapped, InternalSubcirc "GPIO3": "11", } - def __init__(self, model_pin_assigns: ArrayStringLike): - super().__init__() - self.model_pin_assigns = self.ArgParameter(model_pin_assigns) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.gnd = self.Port(Ground.empty(), optional=True) self.v3v3 = self.Port(VoltageSink.empty(), optional=True) self.v3v3_out = self.Port(VoltageSource.empty(), optional=True) self.vcc = self.Port(VoltageSink.empty(), optional=True) # VUsb self.vcc_out = self.Port(VoltageSource.empty(), optional=True) - self.generator_param(self.v3v3.is_connected(), self.vcc.is_connected(), self.model_pin_assigns) + self.generator_param(self.v3v3.is_connected(), self.vcc.is_connected(), self.pin_assigns) # TODO MOVE TO INFRASTRUCTURE for io_port in self._io_ports: @@ -412,9 +411,7 @@ def generate(self) -> None: "13": self.gnd, "14": self.vcc if self.get(self.vcc.is_connected()) else self.vcc_out, # VUsb } - remap_pinnings, remap_pin_assigns = self._remap_pinning_assigns( - self.get(self.model_pin_assigns), self._PIN_REMAPPING - ) + remap_pinnings, remap_pin_assigns = self._remap_pinning_assigns(self.get(self.pin_assigns), self._PIN_REMAPPING) pinning.update(remap_pinnings) self.assign(self.actual_pin_assigns, self._remap_assigns_to_value(remap_pin_assigns)) @@ -496,7 +493,7 @@ def contents(self) -> None: model_pin_assigns = self._export_ios_inner(self.model) self.assign(self.model.pin_assigns, model_pin_assigns) - self.device = self.Block(Xiao_Rp2040_Device(model_pin_assigns=self.model.actual_pin_assigns), external=True) + 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) diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index ef83c5c30..4fa7428de 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -789,9 +789,8 @@ class Holyiot_18010_Device( "nRESET": "21", } - def __init__(self, model_pin_assigns: ArrayStringLike): - super().__init__() - self.model_pin_assigns = self.ArgParameter(model_pin_assigns) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.gnd = self.Port(Ground.empty()) self.vdd_nrf = self.Port(VoltageSink.empty(), optional=True) self.vbus = self.Port(VoltageSink.empty(), optional=True) @@ -811,9 +810,7 @@ def generate(self) -> None: "22": self.vbus, "21": self.p0_18, } - remap_pinnings, remap_pin_assigns = self._remap_pinning_assigns( - self.get(self.model_pin_assigns), self._PIN_REMAPPING - ) + remap_pinnings, remap_pin_assigns = self._remap_pinning_assigns(self.get(self.pin_assigns), self._PIN_REMAPPING) pinning.update(remap_pinnings) self.assign(self.actual_pin_assigns, self._remap_assigns_to_value(remap_pin_assigns)) @@ -838,9 +835,9 @@ class Holyiot_18010_Subcircuit( # in the absence of a chip-level subcircuit, this is used as the authoritative base device model # that other modules should wrap - def __init__(self, model_pin_assigns: ArrayStringLike, **kwargs) -> None: + def __init__(self, **kwargs) -> None: super().__init__(**kwargs) - self.ic = self.Block(Holyiot_18010_Device(model_pin_assigns=model_pin_assigns)) + self.ic = self.Block(Holyiot_18010_Device(pin_assigns=self.pin_assigns)) self.pwr_usb = self.Export(self.ic.vbus, optional=True) self.reset = self.Export(self.ic.p0_18, optional=True) self.generator_param(self.reset.is_connected(), self.pin_assigns, self.gpio.requested(), self.usb.requested()) @@ -900,9 +897,7 @@ def contents(self) -> None: self.connect(self.pwr, self.model.pwr) self.connect(self.gnd, self.model.gnd) - self.device = self.Block( - Holyiot_18010_Subcircuit(model_pin_assigns=self.model.actual_pin_assigns), external=True - ) + self.device = self.Block(Holyiot_18010_Subcircuit(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) From 14892ac5b87841dd3349c3ddae7615acc22129f1 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 25 May 2026 18:59:25 -0700 Subject: [PATCH 05/28] not making sense --- edg/parts/microcontroller/nRF52840.py | 42 +++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index 4fa7428de..0c1362e23 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -797,6 +797,16 @@ def __init__(self, **kwargs): self.p0_18 = self.Port(DigitalSink.empty(), optional=True) # nRESET self.swd = self.Port(SwdTargetPort.empty()) self._io_ports.insert(0, self.swd) + self.generator_param(self.pin_assigns) + + # TODO MOVE TO INFRASTRUCTURE + for io_port in self._io_ports: + if isinstance(io_port, Vector): + self.generator_param(io_port.requested()) + elif isinstance(io_port, Port): + self.generator_param(io_port.is_connected()) + else: + raise NotImplementedError(f"unknown port type {io_port}") @override def generate(self) -> None: @@ -881,9 +891,27 @@ class Holyiot_18010( ): """Wrapper around the Holyiot 18010 that includes supporting components (programming port)""" - def __init__(self, **kwargs: Any) -> None: + def __init__( + self, + swd_connect_swo: BoolLike = False, # TODO SwdTargetConnector should only define the interface + swd_connect_tdi: BoolLike = False, + swd_connect_reset: BoolLike = True, + **kwargs: Any, + ) -> None: super().__init__(**kwargs) - self.model = self.Block(Mdbt50q_1mv2(pin_assigns=ArrayStringExpr())) + + self.swd_connect_swo = self.ArgParameter(swd_connect_swo) + self.swd_connect_tdi = self.ArgParameter(swd_connect_tdi) + self.swd_connect_reset = self.ArgParameter(swd_connect_reset) + + self.model = self.Block( + Mdbt50q_1mv2( + pin_assigns=ArrayStringExpr(), + swd_connect_swo=self.swd_connect_swo, + swd_connect_tdi=self.swd_connect_tdi, + swd_connect_reset=self.swd_connect_reset, + ) + ) self.pwr_usb = self.Export(self.model.pwr_usb, optional=True) self.generator_param(self.reset.is_connected()) @@ -897,7 +925,15 @@ def contents(self) -> None: self.connect(self.pwr, self.model.pwr) self.connect(self.gnd, self.model.gnd) - self.device = self.Block(Holyiot_18010_Subcircuit(pin_assigns=self.model.actual_pin_assigns), external=True) + self.device = self.Block( + Holyiot_18010_Subcircuit( + pin_assigns=self.model.actual_pin_assigns, + swd_connect_swo=self.swd_connect_swo, + swd_connect_tdi=self.swd_connect_tdi, + swd_connect_reset=self.swd_connect_reset, + ), + 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) From cf1f173977e670cdffbbad0e96ce2ba3e4713413 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 25 May 2026 19:17:44 -0700 Subject: [PATCH 06/28] wip --- edg/abstract_parts/IoControllerWrapped.py | 5 ++++- edg/parts/microcontroller/Rp2040.py | 2 +- edg/parts/microcontroller/nRF52840.py | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/edg/abstract_parts/IoControllerWrapped.py b/edg/abstract_parts/IoControllerWrapped.py index 1c0ae03ee..7297e78af 100644 --- a/edg/abstract_parts/IoControllerWrapped.py +++ b/edg/abstract_parts/IoControllerWrapped.py @@ -54,7 +54,10 @@ def remap_port_recursive(port: Port, prefix: str = "") -> None: seen_names.add(subport_name) elif isinstance(io_port, Port): if self.get(io_port.is_connected()): - raise NotImplementedError("TODO implement me") + port_name = io_port._name_from(self) + assert port_name not in seen_names, f"duplicate pin name {port_name}" + remap_port_recursive(io_port, port_name) + seen_names.add(port_name) else: raise NotImplementedError(f"unknown port type {io_port}") diff --git a/edg/parts/microcontroller/Rp2040.py b/edg/parts/microcontroller/Rp2040.py index 0f0c973b8..75f552fbc 100644 --- a/edg/parts/microcontroller/Rp2040.py +++ b/edg/parts/microcontroller/Rp2040.py @@ -384,7 +384,7 @@ class Xiao_Rp2040_Device(Rp2040_Interfaces, IoControllerWrapped, InternalSubcirc "GPIO3": "11", } - def __init__(self, **kwargs): + def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self.gnd = self.Port(Ground.empty(), optional=True) self.v3v3 = self.Port(VoltageSink.empty(), optional=True) diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index 0c1362e23..4e44d9d07 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -789,7 +789,7 @@ class Holyiot_18010_Device( "nRESET": "21", } - def __init__(self, **kwargs): + def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self.gnd = self.Port(Ground.empty()) self.vdd_nrf = self.Port(VoltageSink.empty(), optional=True) @@ -845,7 +845,7 @@ class Holyiot_18010_Subcircuit( # in the absence of a chip-level subcircuit, this is used as the authoritative base device model # that other modules should wrap - def __init__(self, **kwargs) -> None: + def __init__(self, **kwargs: Any) -> None : super().__init__(**kwargs) self.ic = self.Block(Holyiot_18010_Device(pin_assigns=self.pin_assigns)) self.pwr_usb = self.Export(self.ic.vbus, optional=True) From e9044f01909484b1a0b36deb3bcec30a1aca6fde Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 25 May 2026 19:37:20 -0700 Subject: [PATCH 07/28] generate resource names for peripheralfixedpin --- edg/abstract_parts/PinMappable.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/edg/abstract_parts/PinMappable.py b/edg/abstract_parts/PinMappable.py index fc40eb065..8655ac0f1 100644 --- a/edg/abstract_parts/PinMappable.py +++ b/edg/abstract_parts/PinMappable.py @@ -55,13 +55,17 @@ class BaseDelegatingPinMapResource(BasePinMapResource): class PinResource(BaseLeafPinMapResource): """A resource for a single chip pin, which can be one of several port types (eg, an ADC and DIO sharing a pin).""" - def __init__(self, pin: str, name_models: Mapping[str, Union[Passive, HasPassivePort]]): + def __init__(self, pin: str, name_models: Mapping[str, Union[Passive, HasPassivePort]], name: Optional[str] = None): self.pin = pin self.name_models = name_models + if name is not None: + self.name = name + else: + self.name = pin @override def __repr__(self) -> str: - return f"PinResource({self.pin}, {self.name_models})" + return f"PinResource({self.name}, {self.pin}, {self.name_models})" @override def __eq__(self, other: Any) -> bool: @@ -79,14 +83,24 @@ class PeripheralFixedPin(BaseLeafPinMapResource): """A resource for a peripheral as a bundle port, where the internal ports are fixed. No allocation happens. The internal port model must be fully defined here.""" - def __init__(self, name: str, port_model: Port, inner_allowed_pins: Dict[str, str]): + def __init__( + self, + name: str, + port_model: Port, + inner_allowed_pins: Dict[str, str], + inner_resources: Optional[Dict[str, str]] = None, + ): self.name = name self.port_model = port_model self.inner_allowed_pins = inner_allowed_pins + if inner_resources is not None: + self.inner_resources = inner_resources + else: + self.inner_resources = inner_allowed_pins @override def __repr__(self) -> str: - return f"PeripheralFixedPin({self.name}, {self.port_model.__class__.__name__} {self.inner_allowed_pins})" + return f"PeripheralFixedPin({self.name}, {self.port_model.__class__.__name__} {self.inner_allowed_pins} {self.inner_resources})" @override def __eq__(self, other: Any) -> bool: @@ -96,6 +110,7 @@ def __eq__(self, other: Any) -> bool: and self.name == other.name and self.port_model is other.port_model and self.inner_allowed_pins == other.inner_allowed_pins + and self.inner_resources == other.inner_resources ) @@ -274,7 +289,7 @@ def remap_pins(self, pinmap: Dict[str, str]) -> "PinMapUtil": def remap_resource(resource: BasePinMapResource) -> Optional[BasePinMapResource]: if isinstance(resource, PinResource): if resource.pin in pinmap: - return PinResource(pinmap[resource.pin], resource.name_models) + return PinResource(pinmap[resource.pin], resource.name_models, resource.name) else: return None elif isinstance(resource, PeripheralFixedPin): @@ -283,7 +298,7 @@ def remap_resource(resource: BasePinMapResource) -> Optional[BasePinMapResource] for elt_name, elt_pin in resource.inner_allowed_pins.items() if elt_pin in pinmap } - return PeripheralFixedPin(resource.name, resource.port_model, remapped_pins) + return PeripheralFixedPin(resource.name, resource.port_model, remapped_pins, resource.inner_resources) elif isinstance(resource, BaseDelegatingPinMapResource): return resource else: @@ -363,7 +378,7 @@ def try_allocate_resource( if isinstance(resource, PinResource): # single pin: just assign it sub_assignments.check_empty() resource_name, resource_model = resource.get_name_model_for_type(port_type) - allocated_resource = AllocatedResource(resource_model, port_name, resource_name, resource.pin) + allocated_resource = AllocatedResource(resource_model, port_name, resource.name, resource.pin) return allocated_resource elif isinstance(resource, PeripheralFixedPin): # fixed pin: check user-assignment, or assign first inner_pin_map: Dict[str, Tuple[str, Optional[str]]] = {} @@ -372,7 +387,7 @@ def try_allocate_resource( if inner_assignment is not None and inner_assignment != inner_pin: raise BadUserAssignError(f"invalid assignment to {port_name}.{inner_name}: {inner_assignment}") - inner_pin_map[inner_name] = (inner_pin, None) + inner_pin_map[inner_name] = (inner_pin, resource.inner_resources[inner_name]) inner_sub_assignments.check_empty() sub_assignments.check_empty() From 87d65981ba317d6d5b29c996c728d9d13c31f270 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 25 May 2026 19:38:40 -0700 Subject: [PATCH 08/28] it kinda builds --- edg/parts/microcontroller/nRF52840.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index 4e44d9d07..f82483046 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -782,13 +782,6 @@ class Holyiot_18010_Device( "P0.10": "36", } - SYSTEM_PIN_REMAP: Dict[str, Union[str, List[str]]] = { - "Vdd": "14", - "Vss": ["1", "25", "37"], - "Vbus": "22", - "nRESET": "21", - } - def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self.gnd = self.Port(Ground.empty()) @@ -827,7 +820,7 @@ def generate(self) -> None: self.footprint( "U", "edg:Holyiot-18010-NRF52840", - self._make_pinning(), + pinning, mfr="Holyiot", part="18010", datasheet="http://www.holyiot.com/tp/2019042516322180424.pdf", @@ -845,9 +838,9 @@ class Holyiot_18010_Subcircuit( # in the absence of a chip-level subcircuit, this is used as the authoritative base device model # that other modules should wrap - def __init__(self, **kwargs: Any) -> None : + def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self.ic = self.Block(Holyiot_18010_Device(pin_assigns=self.pin_assigns)) + self.ic = self.Block(Holyiot_18010_Device(pin_assigns=ArrayStringExpr())) self.pwr_usb = self.Export(self.ic.vbus, optional=True) self.reset = self.Export(self.ic.p0_18, optional=True) self.generator_param(self.reset.is_connected(), self.pin_assigns, self.gpio.requested(), self.usb.requested()) From 7d7bb2ef8f29f4f691efc7c8b9d2f6856a65230f Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Mon, 25 May 2026 23:44:57 -0700 Subject: [PATCH 09/28] seemed to work? --- edg/parts/microcontroller/nRF52840.py | 127 ++++++++++---------------- 1 file changed, 46 insertions(+), 81 deletions(-) diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index f82483046..e6ec11eb8 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -332,6 +332,9 @@ def __init__(self, **kwargs: Any) -> None: class Mdbt50q_1mv2_Device( Nrf52840_Interfaces, 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 + _PIN_MAPPING = { # boundary pins only, inner pins ignored "P1.10": "3", "P1.11": "4", @@ -408,17 +411,6 @@ def __init__(self, **kwargs: Any) -> None: ) self.require((self.usb.length() > 0).implies(self.pwr_usb.is_connected()), "USB require Vbus connected") - # Additional ports (on top of IoController) - # Crystals from table 15, 32, 33 - # TODO Table 32, model crystal load capacitance and series resistance ratings - self.xtal = self.Port( - CrystalDriver(frequency_limits=(1, 25) * MHertz, voltage_out=self.pwr.link().voltage), optional=True - ) - # Assumed from "32kHz crystal" in 14.5 - self.xtal_rtc = self.Port( - CrystalDriver(frequency_limits=(32, 33) * kHertz, voltage_out=self.pwr.link().voltage), optional=True - ) - self._dio_model = DigitalBidir.from_supply( self.gnd, self.pwr, @@ -707,9 +699,6 @@ class Mdbt50q_1mv2( ): """Wrapper around the Mdbt50q_1mv2 that includes the reference schematic.""" - # in the absence of a chip-level subcircuit, this is used as the authoritative base device model - # that other modules should wrap - def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self.ic = self.Block(Mdbt50q_1mv2_Device(pin_assigns=ArrayStringExpr())) @@ -745,7 +734,7 @@ def usb_export_transform(self_io: BasePort, assign: Optional[str]) -> Optional[B self.connect(self.reset, self.ic.nreset) -class Holyiot_18010_Device( +class Holyiot_18010_Footprint( Nrf52840_Interfaces, IoControllerWrapped, InternalSubcircuit, GeneratorBlock, FootprintBlock ): _PIN_REMAPPING = { # boundary pins only, inner pins ignored @@ -827,51 +816,44 @@ def generate(self) -> None: ) -class Holyiot_18010_Subcircuit( +class Holyiot_18010_Device( Nrf52840_Interfaces, - IoControllerWithSwdTargetConnector, + BaseIoController, InternalSubcircuit, GeneratorBlock, + WrapperSubboardBlock, ): - """Wrapper around the Mdbt50q_1mv2 that includes the reference schematic.""" - - # in the absence of a chip-level subcircuit, this is used as the authoritative base device model - # that other modules should wrap def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self.ic = self.Block(Holyiot_18010_Device(pin_assigns=ArrayStringExpr())) - self.pwr_usb = self.Export(self.ic.vbus, optional=True) - self.reset = self.Export(self.ic.p0_18, optional=True) - self.generator_param(self.reset.is_connected(), self.pin_assigns, self.gpio.requested(), self.usb.requested()) + + self.model = self.Block(Mdbt50q_1mv2_Device(pin_assigns=ArrayStringExpr())) + self.gnd = self.Export(self.model.gnd) + self.pwr = self.Export(self.model.pwr) + self.pwr_usb = self.Export(self.model.pwr_usb, optional=True) + self.reset = self.Export(self.model.nreset, optional=True) + self.swd = self.Export(self.model.swd, optional=True) @override def contents(self) -> None: super().contents() - self.connect(self.pwr, self.ic.vdd_nrf) - self.connect(self.gnd, self.ic.gnd) - self.connect(self.swd_node, self.ic.swd) + model_pin_assigns = self._export_ios_inner(self.model) + self.assign(self.model.pin_assigns, model_pin_assigns) - with self.implicit_connect(ImplicitConnect(self.pwr, [Power]), ImplicitConnect(self.gnd, [Common])) as imp: - self.vcc_cap = imp.Block(DecouplingCapacitor(10 * uFarad(tol=0.2))) + self.device = self.Block(Holyiot_18010_Footprint(pin_assigns=self.model.actual_pin_assigns)) + 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.pwr, self.device.vdd_nrf) + self.export_tap(self.pwr_usb, self.device.vbus) + self.export_tap(self.reset, self.device.p0_18) + self.export_tap(self.swd, self.device.swd) @override def generate(self) -> None: super().generate() - def usb_export_transform(self_io: BasePort, assign: Optional[str]) -> Optional[BasePort]: - self.vbus_cap = self.Block(DecouplingCapacitor(10 * uFarad(tol=0.2))).connected(self.gnd, self.pwr_usb) - self.usb_res = self.Block(UsbSeriesResistor(27 * Ohm(tol=0.05))) - self.connect(self_io, self.usb_res.exterior) - return self.usb_res.interior - - # add a passthrough for gpio (DigitalBidir) to allow the SWD pins to be attached, if using - self._wrap_inner(self.ic, {UsbDevicePort: usb_export_transform, DigitalBidir: lambda port, assign: port}) - - if self.get(self.reset.is_connected()): - self.connect(self.reset, self.ic.nreset) - class Holyiot_18010( Microcontroller, @@ -879,67 +861,50 @@ class Holyiot_18010( Resettable, Nrf52840_Interfaces, IoControllerPowerRequired, + IoControllerWithSwdTargetConnector, GeneratorBlock, - WrapperSubboardBlock, ): """Wrapper around the Holyiot 18010 that includes supporting components (programming port)""" def __init__( self, - swd_connect_swo: BoolLike = False, # TODO SwdTargetConnector should only define the interface - swd_connect_tdi: BoolLike = False, - swd_connect_reset: BoolLike = True, **kwargs: Any, ) -> None: super().__init__(**kwargs) - self.swd_connect_swo = self.ArgParameter(swd_connect_swo) - self.swd_connect_tdi = self.ArgParameter(swd_connect_tdi) - self.swd_connect_reset = self.ArgParameter(swd_connect_reset) - - self.model = self.Block( - Mdbt50q_1mv2( - pin_assigns=ArrayStringExpr(), - swd_connect_swo=self.swd_connect_swo, - swd_connect_tdi=self.swd_connect_tdi, - swd_connect_reset=self.swd_connect_reset, - ) - ) - self.pwr_usb = self.Export(self.model.pwr_usb, optional=True) + self.ic = self.Block(Holyiot_18010_Device(pin_assigns=ArrayStringExpr())) + self.pwr_usb = self.Export(self.ic.pwr_usb, optional=True) - self.generator_param(self.reset.is_connected()) + self.generator_param(self.reset.is_connected(), self.pin_assigns, self.gpio.requested(), self.usb.requested()) @override def contents(self) -> None: super().contents() - model_pin_assigns = self._export_ios_inner(self.model) - self.assign(self.model.pin_assigns, model_pin_assigns) - self.connect(self.pwr, self.model.pwr) - self.connect(self.gnd, self.model.gnd) - - self.device = self.Block( - Holyiot_18010_Subcircuit( - pin_assigns=self.model.actual_pin_assigns, - swd_connect_swo=self.swd_connect_swo, - swd_connect_tdi=self.swd_connect_tdi, - swd_connect_reset=self.swd_connect_reset, - ), - 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.pwr, self.device.pwr) - self.export_tap(self.pwr_usb, self.device.pwr_usb) + self.connect(self.gnd, self.ic.gnd) + self.connect(self.pwr, self.ic.pwr) + + self.connect(self.swd_node, self.ic.swd) + self.connect(self.reset_node, self.ic.reset) + + with self.implicit_connect(ImplicitConnect(self.pwr, [Power]), ImplicitConnect(self.gnd, [Common])) as imp: + self.vcc_cap = imp.Block(DecouplingCapacitor(10 * uFarad(tol=0.2))) @override def generate(self) -> None: super().generate() + def usb_export_transform(self_io: BasePort, assign: Optional[str]) -> Optional[BasePort]: + self.vbus_cap = self.Block(DecouplingCapacitor(10 * uFarad(tol=0.2))).connected(self.gnd, self.pwr_usb) + self.usb_res = self.Block(UsbSeriesResistor(27 * Ohm(tol=0.05))) + self.connect(self_io, self.usb_res.exterior) + return self.usb_res.interior + + # add a passthrough for gpio (DigitalBidir) to allow the SWD pins to be attached, if using + self._wrap_inner(self.ic, {UsbDevicePort: usb_export_transform, DigitalBidir: lambda port, assign: port}) + if self.get(self.reset.is_connected()): - self.connect(self.reset, self.model.nreset) - self.export_tap(self.reset, self.device.reset) + self.connect(self.reset, self.ic.reset) class Feather_Nrf52840( From b6819ede31083c09480dbb60d450fdb2bb54778a Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 26 May 2026 00:01:15 -0700 Subject: [PATCH 10/28] Update Rp2040.py --- edg/parts/microcontroller/Rp2040.py | 35 ++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/edg/parts/microcontroller/Rp2040.py b/edg/parts/microcontroller/Rp2040.py index 75f552fbc..070109200 100644 --- a/edg/parts/microcontroller/Rp2040.py +++ b/edg/parts/microcontroller/Rp2040.py @@ -51,16 +51,17 @@ class Rp2040_Device( "SWCLK": "24", } - def __init__(self, **kwargs: Any) -> None: + def __init__(self, *, _model: BoolLike = False, **kwargs: Any) -> None: super().__init__(**kwargs) + self._model = self.ArgParameter(_model) + self.gnd = self.Port(Ground(), [Common]) self.iovdd = self.Port( VoltageSink( voltage_limits=(1.62, 3.63) * Volt, # Table 628 current_draw=(1.2, 4.3) * mAmp + self.io_current_draw.upper(), # Table 629 ), - [Power], ) self.dvdd = self.Port( @@ -75,6 +76,7 @@ def __init__(self, **kwargs: Any) -> None: current_limits=(0, 100) * mAmp, # Table 1, max current ) ) + self.vreg_vin = self.Port( VoltageSink( voltage_limits=(1.62, 3.63) * Volt, # Table 628 @@ -107,10 +109,19 @@ def __init__(self, **kwargs: Any) -> None: pulldown_capable=True, ) - self.qspi = self.Port(SpiController(self._dio_std_model)) # TODO actually QSPI - self.qspi_cs = self.Port(self._dio_std_model) - self.qspi_sd2 = self.Port(self._dio_std_model) - self.qspi_sd3 = self.Port(self._dio_std_model) + self.qspi = self.Port(SpiController(self._dio_std_model), optional=True) # TODO actually QSPI + self.qspi_cs = self.Port(self._dio_std_model, optional=True) + self.qspi_sd2 = self.Port(self._dio_std_model, optional=True) + self.qspi_sd3 = self.Port(self._dio_std_model, optional=True) + self.require( + (~self._model).implies( + self.qspi.is_connected() + & self.qspi_cs.is_connected() + & self.qspi_sd2.is_connected() + & self.qspi_sd3.is_connected() + ), + "SPI memory required", + ) self.xosc = self.Port( CrystalDriver( @@ -119,7 +130,7 @@ def __init__(self, **kwargs: Any) -> None: optional=True, ) - self.swd = self.Port(SwdTargetPort.empty()) + self.swd = self.Port(SwdTargetPort.empty(), optional=True) self.run = self.Port(DigitalSink.from_bidir(self._dio_ft_model), optional=True) # internally pulled up self._io_ports.insert(0, self.swd) @@ -338,7 +349,7 @@ def contents(self) -> None: self.connect(self.ic.qspi_sd2, mem_qspi.io2) self.connect(self.ic.qspi_sd3, mem_qspi.io3) - self.connect(self.pwr, self.ic.vreg_vin, self.ic.adc_avdd, self.ic.usb_vdd) + self.connect(self.pwr, self.ic.iovdd, self.ic.vreg_vin, self.ic.adc_avdd, self.ic.usb_vdd) self.connect(self.ic.vreg_vout, self.ic.dvdd) self.dvdd_cap = ElementDict[DecouplingCapacitor]() @@ -489,7 +500,7 @@ def contents(self) -> None: "ground required if power used", ) - self.model = self.Block(Rp2040(pin_assigns=ArrayStringExpr())) + self.model = self.Block(Rp2040_Device(pin_assigns=ArrayStringExpr(), _model=True)) model_pin_assigns = self._export_ios_inner(self.model) self.assign(self.model.pin_assigns, model_pin_assigns) @@ -501,8 +512,10 @@ def contents(self) -> None: def generate(self) -> None: super().generate() + 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, self.model.pwr) + self.connect(self.pwr, model_pwr) self.export_tap(self.pwr, self.device.v3v3) else: # board sources power from USB self.pwr_out_model = self.Block( @@ -511,7 +524,7 @@ def generate(self) -> None: current_limits=UsbConnector.USB2_CURRENT_LIMITS, ) ) - self.connect(self.pwr_out_model.pwr, self.model.pwr) + self.connect(self.pwr_out_model.pwr, model_pwr) if self.get(self.pwr_out.is_connected()): self.connect(self.pwr_out, self.pwr_out_model.pwr) self.export_tap(self.pwr_out, self.device.v3v3_out) From 7cf5f3a71c83b7b85244c7f5a47e438083d1dd45 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 26 May 2026 00:06:55 -0700 Subject: [PATCH 11/28] fix pinmap test with remap test --- edg/abstract_parts/test_pinmappable.py | 60 ++++++++++++++++++++------ 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/edg/abstract_parts/test_pinmappable.py b/edg/abstract_parts/test_pinmappable.py index 210753e5d..3b1985851 100644 --- a/edg/abstract_parts/test_pinmappable.py +++ b/edg/abstract_parts/test_pinmappable.py @@ -61,10 +61,42 @@ def test_assign_assigned(self) -> None: # fully user-specified ).allocate( [(DigitalBidir, ["DIO3", "DIO2"]), (AnalogSink, ["AIO4", "AIO5"])], ["DIO3=3", "DIO2=2", "AIO4=4", "AIO5=5"] ) - self.assertIn(AllocatedResource(dio_model, "DIO3", "PIO3", "3"), allocated) - self.assertIn(AllocatedResource(dio_model, "DIO2", "PIO2", "2"), allocated) - self.assertIn(AllocatedResource(ain_model, "AIO4", "AIn4", "4"), allocated) - self.assertIn(AllocatedResource(ain_model, "AIO5", "AIn5", "5"), allocated) + self.assertIn(AllocatedResource(dio_model, "DIO3", "3", "3"), allocated) + self.assertIn(AllocatedResource(dio_model, "DIO2", "2", "2"), allocated) + self.assertIn(AllocatedResource(ain_model, "AIO4", "4", "4"), allocated) + self.assertIn(AllocatedResource(ain_model, "AIO5", "5", "5"), allocated) + + def test_assign_remapped(self) -> None: # fully user-specified + dio_model = DigitalBidir() + ain_model = AnalogSink() + allocated = ( + PinMapUtil( + [ + PinResource("P1", {"PIO1": dio_model}), + PinResource("P2", {"PIO2": dio_model}), + PinResource("P3", {"PIO3": dio_model, "AIn3": ain_model}), + PinResource("P4", {"PIO4": dio_model, "AIn4": ain_model}), + PinResource("P5", {"AIn5": ain_model}), + ] + ) + .remap_pins( + { + "P1": "1", + "P2": "2", + "P3": "3", + "P4": "4", + "P5": "5", + } + ) + .allocate( + [(DigitalBidir, ["DIO3", "DIO2"]), (AnalogSink, ["AIO4", "AIO5"])], + ["DIO3=3", "DIO2=2", "AIO4=4", "AIO5=5"], + ) + ) + self.assertIn(AllocatedResource(dio_model, "DIO3", "P3", "3"), allocated) + self.assertIn(AllocatedResource(dio_model, "DIO2", "P2", "2"), allocated) + self.assertIn(AllocatedResource(ain_model, "AIO4", "P4", "4"), allocated) + self.assertIn(AllocatedResource(ain_model, "AIO5", "P5", "5"), allocated) def test_assign_mixed(self) -> None: # mix of user-specified and automatic assignments, assuming greedy algo dio_model = DigitalBidir() @@ -78,10 +110,10 @@ def test_assign_mixed(self) -> None: # mix of user-specified and automatic assi PinResource("5", {"AIn5": ain_model}), ] ).allocate([(DigitalBidir, ["DIO3", "DIO1"]), (AnalogSink, ["AIO5", "AIO4"])], ["DIO3=3", "AIO4=4"]) - self.assertIn(AllocatedResource(dio_model, "DIO3", "PIO3", "3"), allocated) - self.assertIn(AllocatedResource(dio_model, "DIO1", "PIO1", "1"), allocated) - self.assertIn(AllocatedResource(ain_model, "AIO4", "AIn4", "4"), allocated) - self.assertIn(AllocatedResource(ain_model, "AIO5", "AIn5", "5"), allocated) + self.assertIn(AllocatedResource(dio_model, "DIO3", "3", "3"), allocated) + self.assertIn(AllocatedResource(dio_model, "DIO1", "1", "1"), allocated) + self.assertIn(AllocatedResource(ain_model, "AIO4", "4", "4"), allocated) + self.assertIn(AllocatedResource(ain_model, "AIO5", "5", "5"), allocated) def test_assign_bad(self) -> None: # bad user-specified assignments dio_model = DigitalBidir() @@ -123,7 +155,7 @@ def test_assign_bundle_fixed(self) -> None: PeripheralFixedPin("USB0", usb_model, {"dm": "2", "dp": "3"}), ] ).allocate([(UsbDevicePort, ["usb"])], ["usb.dm=2", "usb.dp=3"]) - self.assertIn(AllocatedResource(usb_model, "usb", "USB0", {"dm": ("2", None), "dp": ("3", None)}), allocated) + self.assertIn(AllocatedResource(usb_model, "usb", "USB0", {"dm": ("2", "2"), "dp": ("3", "3")}), allocated) def test_assign_bundle_fixed_auto(self) -> None: usb_model = UsbDevicePort() @@ -132,7 +164,7 @@ def test_assign_bundle_fixed_auto(self) -> None: PeripheralFixedPin("USB0", usb_model, {"dm": "2", "dp": "3"}), ] ).allocate([(UsbDevicePort, ["usb"])]) - self.assertIn(AllocatedResource(usb_model, "usb", "USB0", {"dm": ("2", None), "dp": ("3", None)}), allocated) + self.assertIn(AllocatedResource(usb_model, "usb", "USB0", {"dm": ("2", "2"), "dp": ("3", "3")}), allocated) def test_assign_bundle_fixed_badspec(self) -> None: usb_model = UsbDevicePort() @@ -163,7 +195,7 @@ def test_assign_bundle_delegating(self) -> None: ).allocate([(UartPort, ["uart"])], ["uart.tx=1", "uart.rx=3"]) self.assertEqual(allocated[0].name, "uart") self.assertEqual(allocated[0].resource_name, "UART0") - self.assertEqual(allocated[0].pin, {"tx": ("1", "PIO1"), "rx": ("3", "PIO3")}) + self.assertEqual(allocated[0].pin, {"tx": ("1", "1"), "rx": ("3", "3")}) def test_assign_bundle_delegating_auto(self) -> None: dio_model = DigitalBidir() @@ -179,7 +211,7 @@ def test_assign_bundle_delegating_auto(self) -> None: ).allocate([(UartPort, ["uart"])]) self.assertEqual(allocated[0].name, "uart") self.assertEqual(allocated[0].resource_name, "UART0") - self.assertEqual(allocated[0].pin, {"tx": ("1", "PIO1"), "rx": ("2", "PIO2")}) + self.assertEqual(allocated[0].pin, {"tx": ("1", "1"), "rx": ("2", "2")}) def test_assign_bundle_delegating_badspec(self) -> None: dio_model = DigitalBidir() @@ -223,7 +255,7 @@ def test_assign_bundle_delegating_fixed(self) -> None: ).allocate([(UartPort, ["uart"])]) self.assertEqual(allocated[0].name, "uart") self.assertEqual(allocated[0].resource_name, "UART0") - self.assertEqual(allocated[0].pin, {"tx": ("3", "PIO3"), "rx": ("1", "PIO1")}) + self.assertEqual(allocated[0].pin, {"tx": ("3", "3"), "rx": ("1", "1")}) assert isinstance(allocated[0].port_model, UartPort) self.assertTrue(allocated[0].port_model.tx.voltage_out.initializer is not None) @@ -241,4 +273,4 @@ def test_assign_bundle_delegating_notconnected(self) -> None: ).allocate([(UartPort, ["uart"])], ["uart.tx=NC"]) self.assertEqual(allocated[0].name, "uart") self.assertEqual(allocated[0].resource_name, "UART0") - self.assertEqual(allocated[0].pin, {"rx": ("1", "PIO1")}) + self.assertEqual(allocated[0].pin, {"rx": ("1", "1")}) From 4aebbe0d048ed1ea5e763223078a9f31241063a9 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 26 May 2026 00:14:35 -0700 Subject: [PATCH 12/28] update netlists --- examples/Multimeter/Multimeter.net.ref | 64 ++++++++++++------------ examples/Multimeter/Multimeter.svgpcb.js | 26 +++++----- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/examples/Multimeter/Multimeter.net.ref b/examples/Multimeter/Multimeter.net.ref index 33b26525c..3c2ed3555 100644 --- a/examples/Multimeter/Multimeter.net.ref +++ b/examples/Multimeter/Multimeter.net.ref @@ -396,6 +396,30 @@ (property (name "edg_value") (value "X5R 25V ±10% 10uF 0805 Multilayer Ceramic Capacitors MLCC - SMD/SMT ROHS")) (sheetpath (names "/mcu/") (tstamps "/02850146/")) (tstamps "0b5902d0")) +(comp (ref "J2") + (value "mcu.swd") + (footprint "Connector:Tag-Connect_TC2050-IDC-NL_2x05_P1.27mm_Vertical") + (property (name "Sheetname") (value "mcu")) + (property (name "Sheetfile") (value "edg.parts.microcontroller.nRF52840.Mdbt50q_1mv2")) + (property (name "edg_path") (value "mcu.swd.conn")) + (property (name "edg_short_path") (value "mcu.swd")) + (property (name "edg_refdes") (value "J2")) + (property (name "edg_part") (value "")) + (property (name "edg_value") (value "")) + (sheetpath (names "/mcu/") (tstamps "/02850146/")) + (tstamps "02ae014f")) +(comp (ref "C8") + (value "mcu.vbus_cap") + (footprint "Capacitor_SMD:C_0805_2012Metric") + (property (name "Sheetname") (value "mcu")) + (property (name "Sheetfile") (value "edg.parts.microcontroller.nRF52840.Mdbt50q_1mv2")) + (property (name "edg_path") (value "mcu.vbus_cap.cap")) + (property (name "edg_short_path") (value "mcu.vbus_cap")) + (property (name "edg_refdes") (value "C8")) + (property (name "edg_part") (value "CL21A106KAYNNNE (Samsung Electro-Mechanics)")) + (property (name "edg_value") (value "X5R 25V ±10% 10uF 0805 Multilayer Ceramic Capacitors MLCC - SMD/SMT ROHS")) + (sheetpath (names "/mcu/") (tstamps "/02850146/")) + (tstamps "0f3a0354")) (comp (ref "R6") (value "mcu.usb_res.dp") (footprint "Resistor_SMD:R_0603_1608Metric") @@ -420,30 +444,6 @@ (property (name "edg_value") (value "±1% 1/10W Thick Film Resistors 75V ±200ppm/℃ -55℃~+155℃ 27Ω 0603 Chip Resistor - Surface Mount ROHS")) (sheetpath (names "/mcu/usb_res/") (tstamps "/02850146/0be502f4/")) (tstamps "013700d2")) -(comp (ref "C8") - (value "mcu.vbus_cap") - (footprint "Capacitor_SMD:C_0805_2012Metric") - (property (name "Sheetname") (value "mcu")) - (property (name "Sheetfile") (value "edg.parts.microcontroller.nRF52840.Mdbt50q_1mv2")) - (property (name "edg_path") (value "mcu.vbus_cap.cap")) - (property (name "edg_short_path") (value "mcu.vbus_cap")) - (property (name "edg_refdes") (value "C8")) - (property (name "edg_part") (value "CL21A106KAYNNNE (Samsung Electro-Mechanics)")) - (property (name "edg_value") (value "X5R 25V ±10% 10uF 0805 Multilayer Ceramic Capacitors MLCC - SMD/SMT ROHS")) - (sheetpath (names "/mcu/") (tstamps "/02850146/")) - (tstamps "0f3a0354")) -(comp (ref "J2") - (value "mcu.swd") - (footprint "Connector:Tag-Connect_TC2050-IDC-NL_2x05_P1.27mm_Vertical") - (property (name "Sheetname") (value "mcu")) - (property (name "Sheetfile") (value "edg.parts.microcontroller.nRF52840.Mdbt50q_1mv2")) - (property (name "edg_path") (value "mcu.swd.conn")) - (property (name "edg_short_path") (value "mcu.swd")) - (property (name "edg_refdes") (value "J2")) - (property (name "edg_part") (value "")) - (property (name "edg_value") (value "")) - (sheetpath (names "/mcu/") (tstamps "/02850146/")) - (tstamps "02ae014f")) (comp (ref "R8") (value "vbatsense.top_res") (footprint "Resistor_SMD:R_0603_1608Metric") @@ -1315,10 +1315,10 @@ (node (ref U5) (pin 33)) (node (ref U5) (pin 55)) (node (ref C7) (pin 2)) - (node (ref C8) (pin 2)) (node (ref J2) (pin 2)) (node (ref J2) (pin 3)) (node (ref J2) (pin 5)) + (node (ref C8) (pin 2)) (node (ref R9) (pin 2)) (node (ref U6) (pin 3)) (node (ref SW2) (pin 2)) @@ -1549,17 +1549,17 @@ (net (code 35) (name "mcu.reset_node") (node (ref U5) (pin 40)) (node (ref J2) (pin 6))) -(net (code 36) (name "mcu.usb_chain_0.d_P") +(net (code 36) (name "mcu.swd.tdi") + (node (ref J2) (pin 7))) +(net (code 37) (name "mcu.swd.swo") + (node (ref U5) (pin 47)) + (node (ref J2) (pin 8))) +(net (code 38) (name "mcu.usb_res.interior.dp") (node (ref U5) (pin 35)) (node (ref R6) (pin 2))) -(net (code 37) (name "mcu.usb_chain_0.d_N") +(net (code 39) (name "mcu.usb_res.interior.dm") (node (ref U5) (pin 34)) (node (ref R7) (pin 2))) -(net (code 38) (name "mcu.swd.tdi") - (node (ref J2) (pin 7))) -(net (code 39) (name "mcu.swd.swo") - (node (ref U5) (pin 47)) - (node (ref J2) (pin 8))) (net (code 40) (name "vbatsense.output") (node (ref U5) (pin 9)) (node (ref R8) (pin 2)) diff --git a/examples/Multimeter/Multimeter.svgpcb.js b/examples/Multimeter/Multimeter.svgpcb.js index 3cafe9ed5..b0b7ea2fc 100644 --- a/examples/Multimeter/Multimeter.svgpcb.js +++ b/examples/Multimeter/Multimeter.svgpcb.js @@ -165,6 +165,16 @@ const C7 = board.add(C_0805_2012Metric, { translate: pt(0.596, 0.728), rotate: 0, id: 'C7' }) +// mcu.swd.conn +const J2 = board.add(Tag_Connect_TC2050_IDC_NL_2x05_P1_27mm_Vertical, { + translate: pt(0.245, 0.822), rotate: 0, + id: 'J2' +}) +// mcu.vbus_cap.cap +const C8 = board.add(C_0805_2012Metric, { + translate: pt(0.770, 0.728), rotate: 0, + id: 'C8' +}) // mcu.usb_res.dp.res const R6 = board.add(R_0603_1608Metric, { translate: pt(0.588, 0.834), rotate: 0, @@ -175,16 +185,6 @@ const R7 = board.add(R_0603_1608Metric, { translate: pt(0.744, 0.834), rotate: 0, id: 'R7' }) -// mcu.vbus_cap.cap -const C8 = board.add(C_0805_2012Metric, { - translate: pt(0.770, 0.728), rotate: 0, - id: 'C8' -}) -// mcu.swd.conn -const J2 = board.add(Tag_Connect_TC2050_IDC_NL_2x05_P1_27mm_Vertical, { - translate: pt(0.245, 0.822), rotate: 0, - id: 'J2' -}) // vbatsense.div.top_res const R8 = board.add(R_0603_1608Metric, { translate: pt(3.628, 2.237), rotate: 0, @@ -537,7 +537,7 @@ const C31 = board.add(C_1206_3216Metric, { }) board.setNetlist([ - {name: "gnd", pads: [["U1", "2"], ["J1", "A1"], ["J1", "A12"], ["J1", "B1"], ["J1", "B12"], ["J1", "S1"], ["R1", "1"], ["R2", "1"], ["R4", "1"], ["Q2", "2"], ["SW1", "2"], ["U2", "2"], ["C1", "2"], ["C2", "2"], ["D3", "2"], ["U3", "2"], ["C3", "2"], ["C4", "2"], ["D4", "2"], ["U4", "2"], ["C5", "2"], ["C6", "2"], ["D5", "2"], ["U5", "1"], ["U5", "15"], ["U5", "2"], ["U5", "33"], ["U5", "55"], ["C7", "2"], ["C8", "2"], ["J2", "2"], ["J2", "3"], ["J2", "5"], ["R9", "2"], ["U6", "3"], ["SW2", "2"], ["SW3", "2"], ["J3", "2"], ["C9", "2"], ["C10", "2"], ["U7", "7"], ["U7", "9"], ["C11", "2"], ["C12", "2"], ["C14", "2"], ["R18", "2"], ["U8", "2"], ["C15", "2"], ["U9", "2"], ["U9", "3"], ["C16", "2"], ["U10", "2"], ["C17", "2"], ["U11", "2"], ["C18", "2"], ["U12", "2"], ["C19", "2"], ["U13", "2"], ["C20", "2"], ["U14", "19"], ["U14", "2"], ["U14", "3"], ["C21", "2"], ["C22", "2"], ["C23", "2"], ["C24", "2"], ["C25", "2"], ["U15", "2"], ["C26", "2"], ["U16", "2"], ["C27", "2"], ["U17", "2"], ["C28", "2"], ["U18", "2"], ["C29", "2"], ["U19", "2"], ["C30", "2"], ["C31", "2"]]}, + {name: "gnd", pads: [["U1", "2"], ["J1", "A1"], ["J1", "A12"], ["J1", "B1"], ["J1", "B12"], ["J1", "S1"], ["R1", "1"], ["R2", "1"], ["R4", "1"], ["Q2", "2"], ["SW1", "2"], ["U2", "2"], ["C1", "2"], ["C2", "2"], ["D3", "2"], ["U3", "2"], ["C3", "2"], ["C4", "2"], ["D4", "2"], ["U4", "2"], ["C5", "2"], ["C6", "2"], ["D5", "2"], ["U5", "1"], ["U5", "15"], ["U5", "2"], ["U5", "33"], ["U5", "55"], ["C7", "2"], ["J2", "2"], ["J2", "3"], ["J2", "5"], ["C8", "2"], ["R9", "2"], ["U6", "3"], ["SW2", "2"], ["SW3", "2"], ["J3", "2"], ["C9", "2"], ["C10", "2"], ["U7", "7"], ["U7", "9"], ["C11", "2"], ["C12", "2"], ["C14", "2"], ["R18", "2"], ["U8", "2"], ["C15", "2"], ["U9", "2"], ["U9", "3"], ["C16", "2"], ["U10", "2"], ["C17", "2"], ["U11", "2"], ["C18", "2"], ["U12", "2"], ["C19", "2"], ["U13", "2"], ["C20", "2"], ["U14", "19"], ["U14", "2"], ["U14", "3"], ["C21", "2"], ["C22", "2"], ["C23", "2"], ["C24", "2"], ["C25", "2"], ["U15", "2"], ["C26", "2"], ["U16", "2"], ["C27", "2"], ["U17", "2"], ["C28", "2"], ["U18", "2"], ["C29", "2"], ["U19", "2"], ["C30", "2"], ["C31", "2"]]}, {name: "vbat", pads: [["U1", "1"], ["R3", "1"], ["Q1", "2"]]}, {name: "v5v", pads: [["U2", "4"], ["C2", "1"], ["TP1", "1"], ["D3", "1"], ["U3", "1"], ["U3", "3"], ["C3", "1"], ["U4", "1"], ["U4", "3"], ["C5", "1"], ["U7", "1"], ["U7", "6"], ["C11", "1"], ["C12", "1"]]}, {name: "v3v3", pads: [["U3", "5"], ["C4", "1"], ["TP2", "1"], ["D4", "1"], ["U5", "28"], ["U5", "30"], ["C7", "1"], ["J2", "1"], ["D6", "2"], ["J3", "7"], ["R13", "1"], ["C9", "1"], ["R24", "1"]]}, @@ -572,10 +572,10 @@ board.setNetlist([ {name: "mcu.swd_node.swdio", pads: [["U5", "51"], ["J2", "10"]]}, {name: "mcu.swd_node.swclk", pads: [["U5", "53"], ["J2", "9"]]}, {name: "mcu.reset_node", pads: [["U5", "40"], ["J2", "6"]]}, - {name: "mcu.usb_chain_0.d_P", pads: [["U5", "35"], ["R6", "2"]]}, - {name: "mcu.usb_chain_0.d_N", pads: [["U5", "34"], ["R7", "2"]]}, {name: "mcu.swd.tdi", pads: [["J2", "7"]]}, {name: "mcu.swd.swo", pads: [["U5", "47"], ["J2", "8"]]}, + {name: "mcu.usb_res.interior.dp", pads: [["U5", "35"], ["R6", "2"]]}, + {name: "mcu.usb_res.interior.dm", pads: [["U5", "34"], ["R7", "2"]]}, {name: "vbatsense.output", pads: [["U5", "9"], ["R8", "2"], ["R9", "1"]]}, {name: "rgb.package.k_red", pads: [["D6", "3"], ["R10", "1"]]}, {name: "rgb.package.k_green", pads: [["D6", "4"], ["R11", "1"]]}, From f92b2d092d8309577a955f266082f6d9a2ee2b2d Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 26 May 2026 00:43:03 -0700 Subject: [PATCH 13/28] wip dev board refactor --- edg/parts/microcontroller/nRF52840.py | 76 ++++++++++++--------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index e6ec11eb8..e2f519436 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -907,20 +907,9 @@ def usb_export_transform(self_io: BasePort, assign: Optional[str]) -> Optional[B self.connect(self.reset, self.ic.reset) -class Feather_Nrf52840( - IoControllerUsbOut, IoControllerPowerOut, Nrf52840_Ios, IoController, GeneratorBlock, FootprintBlock -): - """Feather nRF52840 socketed dev board as either power source or sink""" +class Feather_Nrf52840_Device(Nrf52840_Interfaces, IoControllerWrapped, GeneratorBlock, FootprintBlock): - SYSTEM_PIN_REMAP: Dict[str, Union[str, List[str]]] = { - "Vdd": "2", # 3v3 - "Vss": "4", - # 'reset': '1', - "Vbus": "26", - # 'EN': '27', # controls the onboard 3.3 LDO, internally pulled up - # 'Vbat': '28', - } - RESOURCE_PIN_REMAP = { # boundary pins only, inner pins ignored + _PIN_REMAPPING = { # boundary pins only, inner pins ignored "P0.31": "3", # AREF "P0.04": "5", # A0 "P0.05": "6", # A1 @@ -949,39 +938,22 @@ class Feather_Nrf52840( # note onboard VBAT sense divider at P0.29 } - @override - def _vddio(self) -> Port[VoltageLink]: - if self.get(self.pwr.is_connected()): # board sinks power - return self.pwr - else: - return self.pwr_out - - @override - def _system_pinmap(self) -> Dict[str, Union[Passive, HasPassivePort]]: - if self.get(self.pwr.is_connected()): # board sinks power - self.require(~self.vusb_out.is_connected(), "can't source USB power if power input connected") - self.require(~self.pwr_out.is_connected(), "can't source 3v3 power if power input connected") - return VariantPinRemapper( - { - "Vdd": self.pwr, - "Vss": self.gnd, - } - ).remap(self.SYSTEM_PIN_REMAP) - else: # board sources power (default) - return VariantPinRemapper( - { - "Vdd": self.pwr_out, - "Vss": self.gnd, - "Vbus": self.vusb_out, - } - ).remap(self.SYSTEM_PIN_REMAP) - @override def contents(self) -> None: super().contents() - self.gnd.init_from(Ground()) - self.pwr.init_from(self._vdd_model()) + self.gnd = self.Port(Ground.empty(), optional=True) + self.pwr = self.Port(VoltageSink.empty(), optional=True) + self.pwr_out = self.Port(VoltageSink.empty(), optional=True) + + 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", + ) mbr120_drop = (0, 0.340) * Volt ap2112_3v3_out = 3.3 * Volt(tol=0.015) # note dropout voltage up to 400mV, current up to 600mA @@ -1001,11 +973,29 @@ def contents(self) -> None: def generate(self) -> None: super().generate() + pinning: Dict[str, HasPassivePort] = { + "2": self.pwr if self.get(self.pwr.is_connected()) else self.pwr_out, + "4": self.gnd, + # "1": reset, + "26": self.vusb_out, + # 'EN': '27', # controls the onboard 3.3 LDO, internally pulled up + # 'Vbat': '28', + } + remap_pinnings, remap_pin_assigns = self._remap_pinning_assigns(self.get(self.pin_assigns), self._PIN_REMAPPING) + pinning.update(remap_pinnings) + self.assign(self.actual_pin_assigns, self._remap_assigns_to_value(remap_pin_assigns)) + self.footprint( "U", "bldc:FEATHERWING_NODIM", - self._make_pinning(), + pinning, mfr="Adafruit", part="Feather nRF52840 Express", datasheet="https://learn.adafruit.com/assets/68545", ) + + +class Feather_Nrf52840( + IoControllerUsbOut, IoControllerPowerOut, Nrf52840_Interfaces, IoController, GeneratorBlock, FootprintBlock +): + """Feather nRF52840 socketed dev board as either power source or sink""" From 9dfe78f44d71446ed27542c277b4eb0e367f834e Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 27 May 2026 22:43:49 -0700 Subject: [PATCH 14/28] wip --- ...rWrapped.py => BaseIoControllerWrapped.py} | 2 +- edg/abstract_parts/__init__.py | 2 +- edg/parts/microcontroller/Rp2040.py | 4 +- edg/parts/microcontroller/nRF52840.py | 117 +++++++++++++----- 4 files changed, 88 insertions(+), 37 deletions(-) rename edg/abstract_parts/{IoControllerWrapped.py => BaseIoControllerWrapped.py} (98%) diff --git a/edg/abstract_parts/IoControllerWrapped.py b/edg/abstract_parts/BaseIoControllerWrapped.py similarity index 98% rename from edg/abstract_parts/IoControllerWrapped.py rename to edg/abstract_parts/BaseIoControllerWrapped.py index 7297e78af..054f25834 100644 --- a/edg/abstract_parts/IoControllerWrapped.py +++ b/edg/abstract_parts/BaseIoControllerWrapped.py @@ -4,7 +4,7 @@ from .IoController import BaseIoController -class IoControllerWrapped(BaseIoController): +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. diff --git a/edg/abstract_parts/__init__.py b/edg/abstract_parts/__init__.py index d2b35fdc7..27f2c284e 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 .IoControllerWrapped import IoControllerWrapped +from .BaseIoControllerWrapped import BaseIoControllerWrapped from .IoControllerInterfaceMixins import ( IoControllerSpiPeripheral, IoControllerI2cTarget, diff --git a/edg/parts/microcontroller/Rp2040.py b/edg/parts/microcontroller/Rp2040.py index 070109200..e430fdbe8 100644 --- a/edg/parts/microcontroller/Rp2040.py +++ b/edg/parts/microcontroller/Rp2040.py @@ -378,7 +378,9 @@ def _crystal_required(self) -> bool: # crystal needed for USB b/c tighter freq return len(self.get(self.usb.requested())) > 0 or super()._crystal_required() -class Xiao_Rp2040_Device(Rp2040_Interfaces, IoControllerWrapped, InternalSubcircuit, GeneratorBlock, FootprintBlock): +class Xiao_Rp2040_Device( + Rp2040_Interfaces, BaseIoControllerWrapped, InternalSubcircuit, GeneratorBlock, FootprintBlock +): """Footprint-only device model for the Xiao RP2040 microcontroller dev board""" _PIN_REMAPPING = { diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index e2f519436..ff251bc2b 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -735,7 +735,7 @@ def usb_export_transform(self_io: BasePort, assign: Optional[str]) -> Optional[B class Holyiot_18010_Footprint( - Nrf52840_Interfaces, IoControllerWrapped, InternalSubcircuit, GeneratorBlock, FootprintBlock + Nrf52840_Interfaces, BaseIoControllerWrapped, InternalSubcircuit, GeneratorBlock, FootprintBlock ): _PIN_REMAPPING = { # boundary pins only, inner pins ignored "P1.11": "2", @@ -907,7 +907,7 @@ def usb_export_transform(self_io: BasePort, assign: Optional[str]) -> Optional[B self.connect(self.reset, self.ic.reset) -class Feather_Nrf52840_Device(Nrf52840_Interfaces, IoControllerWrapped, GeneratorBlock, FootprintBlock): +class Feather_Nrf52840_Device(Nrf52840_Interfaces, BaseIoControllerWrapped, GeneratorBlock, FootprintBlock): _PIN_REMAPPING = { # boundary pins only, inner pins ignored "P0.31": "3", # AREF @@ -938,46 +938,24 @@ class Feather_Nrf52840_Device(Nrf52840_Interfaces, IoControllerWrapped, Generato # note onboard VBAT sense divider at P0.29 } - @override - def contents(self) -> None: - super().contents() - + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) self.gnd = self.Port(Ground.empty(), optional=True) - self.pwr = self.Port(VoltageSink.empty(), optional=True) - self.pwr_out = self.Port(VoltageSink.empty(), optional=True) - - 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", - ) + # power ports are paassive so directionality and concrete types can be resolved at the higher modeling level + self.pwr = self.Port(Passive.empty(), optional=True) + self.vusb = self.Port(Passive.empty(), optional=True) - mbr120_drop = (0, 0.340) * Volt - ap2112_3v3_out = 3.3 * Volt(tol=0.015) # note dropout voltage up to 400mV, current up to 600mA - self.vusb_out.init_from( - VoltageSource( - voltage_out=UsbConnector.USB2_VOLTAGE_RANGE - mbr120_drop, - current_limits=UsbConnector.USB2_CURRENT_LIMITS, - ) - ) - self.pwr_out.init_from( - VoltageSource(voltage_out=ap2112_3v3_out, current_limits=UsbConnector.USB2_CURRENT_LIMITS) - ) - - self.generator_param(self.pwr.is_connected()) + self.generator_param(self.pin_assigns) @override def generate(self) -> None: super().generate() - pinning: Dict[str, HasPassivePort] = { - "2": self.pwr if self.get(self.pwr.is_connected()) else self.pwr_out, + pinning: Dict[str, Union[HasPassivePort, Passive]] = { + "2": self.pwr, "4": self.gnd, # "1": reset, - "26": self.vusb_out, + "26": self.vusb, # 'EN': '27', # controls the onboard 3.3 LDO, internally pulled up # 'Vbat': '28', } @@ -996,6 +974,77 @@ def generate(self) -> None: class Feather_Nrf52840( - IoControllerUsbOut, IoControllerPowerOut, Nrf52840_Interfaces, IoController, GeneratorBlock, FootprintBlock + IoControllerUsbOut, + IoControllerPowerOut, + Nrf52840_Interfaces, + IoController, + WrapperSubboardBlock, + GeneratorBlock, + FootprintBlock, ): """Feather nRF52840 socketed dev board as either power source or sink""" + + _MBR120_DROP = (0, 0.340) * Volt + _AP2112_3V3_OUT = 3.3 * Volt(tol=0.015) # note dropout voltage up to 400mV, current up to 600mA + + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + self.generator_param( + self.gnd.is_connected(), + self.pwr.is_connected(), + self.pwr_out.is_connected(), + self.vusb_out.is_connected(), + ) + + @override + def contents(self) -> None: + super().contents() + + 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=ArrayStringExpr())) + model_pin_assigns = self._export_ios_inner(self.model) + self.assign(self.model.pin_assigns, model_pin_assigns) + + 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) + + @override + def generate(self) -> None: + super().generate() + + 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.pwr, self.model.pwr) + if self.get(self.pwr_out.is_connected()): + self.connect(self.pwr_out, self.pwr_out_model.pwr) + 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.export_tap(self.vusb_out.net, self.device.vusb) + + self.export_tap(self.gnd, self.device.gnd) + if self.get(self.gnd.is_connected()): + self.connect(self.gnd, self.model.gnd) + else: + self.gnd_model = self.Block(DummyGround()) + self.connect(self.gnd_model.gnd, self.model.gnd) From 8208949bc757bc48601181e0ad865a713055f607 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 27 May 2026 22:46:05 -0700 Subject: [PATCH 15/28] Update nRF52840.py --- edg/parts/microcontroller/nRF52840.py | 315 -------------------------- 1 file changed, 315 deletions(-) diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index ff251bc2b..26b9b59b7 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -14,321 +14,6 @@ class Nrf52840_Interfaces( """Defines base interfaces for nRF52840 microcontrollers""" -@non_library -class Nrf52840_Ios(Nrf52840_Interfaces, BaseIoControllerPinmapGenerator, GeneratorBlock, FootprintBlock): - """nRF52840 IO mappings - https://infocenter.nordicsemi.com/pdf/nRF52840_PS_v1.7.pdf""" - - RESOURCE_PIN_REMAP: Dict[str, str] # resource name in base -> pin name - - @abstractmethod - def _vddio(self) -> Port[VoltageLink]: ... - - def _vdd_model(self) -> VoltageSink: - return VoltageSink( - voltage_limits=(1.75, 3.6) * Volt, # 1.75 minimum for power-on reset - current_draw=(0, 212 / 64 + 4.8) * mAmp - + self.io_current_draw.upper(), # CPU @ max 212 Coremarks + 4.8mA in RF transmit - ) - - def _dio_model(self, pwr: Port[VoltageLink]) -> DigitalBidir: - return DigitalBidir.from_supply( - self.gnd, - pwr, - voltage_limit_tolerance=(-0.3, 0.3) * Volt, - current_limits=(-6, 6) * mAmp, # minimum current, high drive, Vdd>2.7 - input_threshold_factor=(0.3, 0.7), - pullup_capable=True, - pulldown_capable=True, - ) - - @override - def _io_pinmap(self) -> PinMapUtil: - """Returns the mappable for given the input power and ground references. - This separates the system pins definition from the IO pins definition.""" - pwr = self._vddio() - dio_model = self._dio_model(pwr) - dio_lf_model = dio_model # "standard drive, low frequency IO only" (differences not modeled) - - adc_model = AnalogSink.from_supply( - self.gnd, - pwr, - voltage_limit_tolerance=(0, 0), # datasheet 6.23.2, analog inputs cannot exceed Vdd or be lower than Vss - signal_limit_tolerance=(0, 0), - impedance=Range.from_lower(1) * MOhm, - ) - - uart_model = UartPort(DigitalBidir.empty()) - spi_model = SpiController(DigitalBidir.empty(), (125, 32000) * kHertz) - spi_peripheral_model = SpiPeripheral(DigitalBidir.empty(), (125, 32000) * kHertz) # tristated by CS pin - i2c_model = I2cController(DigitalBidir.empty()) - i2c_target_model = I2cTarget(DigitalBidir.empty()) - i2s_model = I2sController(DigitalBidir.empty()) - - hf_io_pins = [ - "P0.00", - "P0.01", - "P0.26", - "P0.27", - "P0.04", - "P0.05", - "P0.06", - "P0.07", - "P0.08", - "P1.08", - "P1.09", - "P0.11", - "P0.12", - "P0.14", - "P0.16", - "P0.19", - "P0.21", - "P0.23", - "P0.25", # 'P0.18' - "P0.13", - "P0.15", - "P0.17", - "P0.20", - "P0.22", - "P0.24", - "P1.00", - ] - - return PinMapUtil( - [ # Section 7.1.2 with QIAA aQFN73 & QFAA QFN48 pins only - PinResource("P0.31", {"P0.31": dio_lf_model, "AIN7": adc_model}), - PinResource("P0.29", {"P0.29": dio_lf_model, "AIN5": adc_model}), - PinResource("P0.02", {"P0.02": dio_lf_model, "AIN0": adc_model}), - PinResource("P1.15", {"P1.15": dio_lf_model}), - PinResource("P1.13", {"P1.13": dio_lf_model}), - PinResource("P1.10", {"P1.10": dio_lf_model}), - PinResource("P0.30", {"P0.30": dio_lf_model, "AIN6": adc_model}), - PinResource("P0.28", {"P0.28": dio_lf_model, "AIN4": adc_model}), - PinResource("P0.03", {"P0.03": dio_lf_model, "AIN1": adc_model}), - PinResource("P1.14", {"P1.14": dio_lf_model}), - PinResource("P1.12", {"P1.12": dio_lf_model}), - PinResource("P1.11", {"P1.11": dio_lf_model}), - PinResource("P0.00", {"P0.00": dio_model}), # TODO also 32.768 kHz crystal in - PinResource("P0.01", {"P0.01": dio_model}), # TODO also 32.768 kHz crystal in - PinResource("P0.26", {"P0.26": dio_model}), - PinResource("P0.27", {"P0.27": dio_model}), - PinResource("P0.04", {"P0.04": dio_model, "AIN2": adc_model}), - PinResource("P0.10", {"P0.10": dio_lf_model}), # TODO also NFC2 - PinResource("P0.05", {"P0.05": dio_model, "AIN3": adc_model}), - PinResource("P0.06", {"P0.06": dio_model}), - PinResource("P0.09", {"P0.09": dio_lf_model}), # TODO also NFC1 - PinResource("P0.07", {"P0.07": dio_model}), - PinResource("P0.08", {"P0.08": dio_model}), - PinResource("P1.08", {"P1.08": dio_model}), - PinResource("P1.07", {"P1.07": dio_lf_model}), - PinResource("P1.09", {"P1.09": dio_model}), - PinResource("P1.06", {"P1.06": dio_lf_model}), - PinResource("P0.11", {"P0.11": dio_model}), - PinResource("P1.05", {"P1.05": dio_lf_model}), - PinResource("P0.12", {"P0.12": dio_model}), - PinResource("P1.04", {"P1.04": dio_lf_model}), - PinResource("P1.03", {"P1.03": dio_lf_model}), - PinResource("P1.02", {"P1.02": dio_lf_model}), - PinResource("P1.01", {"P1.01": dio_lf_model}), - PinResource("P0.14", {"P0.14": dio_model}), - PinResource("P0.16", {"P0.16": dio_model}), - # PinResource('P0.18', {'P0.18': dio_model}), # configurable as RESET, mappable - PinResource("P0.19", {"P0.19": dio_model}), - PinResource("P0.21", {"P0.21": dio_model}), - PinResource("P0.23", {"P0.23": dio_model}), - PinResource("P0.25", {"P0.25": dio_model}), - PinResource("P0.13", {"P0.13": dio_model}), - PinResource("P0.15", {"P0.15": dio_model}), - PinResource("P0.17", {"P0.17": dio_model}), - PinResource("P0.20", {"P0.20": dio_model}), - PinResource("P0.22", {"P0.22": dio_model}), - PinResource("P0.24", {"P0.24": dio_model}), - PinResource( - "P1.00", {"P1.00": dio_model} - ), # TRACEDATA[0] and SWO, if used as IO must clear TRACECONFIG reg - PeripheralFixedPin( - "SWD", - SwdTargetPort(dio_model), - { - "swclk": "SWCLK", - "swdio": "SWDIO", - }, - ), - PeripheralFixedPin("USBD", UsbDevicePort(), {"dp": "D+", "dm": "D-"}), - PeripheralFixedResource( - "SPIM0", - spi_model, - { - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIM1", - spi_model, - { - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIM2", - spi_model, - { - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIM3", - spi_model, - { - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIS0", - spi_peripheral_model, - { # TODO shared resource w/ SPI controller - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIS1", - spi_peripheral_model, - { # TODO shared resource w/ SPI controller - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIS2", - spi_peripheral_model, - { # TODO shared resource w/ SPI controller - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "TWIM0", - i2c_model, - { - "scl": hf_io_pins, - "sda": hf_io_pins, - }, - ), - PeripheralFixedResource( - "TWIM1", - i2c_model, - { - "scl": hf_io_pins, - "sda": hf_io_pins, - }, - ), - PeripheralFixedResource( - "TWIS0", - i2c_target_model, - { # TODO shared resource w/ I2C controller - "scl": hf_io_pins, - "sda": hf_io_pins, - }, - ), - PeripheralFixedResource( - "TWIS1", - i2c_target_model, - { # TODO shared resource w/ I2C controller - "scl": hf_io_pins, - "sda": hf_io_pins, - }, - ), - PeripheralFixedResource( - "UARTE0", - uart_model, - { - "tx": hf_io_pins, - "rx": hf_io_pins, - }, - ), - PeripheralFixedResource( - "UARTE1", - uart_model, - { - "tx": hf_io_pins, - "rx": hf_io_pins, - }, - ), - PeripheralFixedResource( - "I2S", - i2s_model, - { - "sck": hf_io_pins, - "ws": hf_io_pins, - "sd": hf_io_pins, - }, - ), - ] - ).remap_pins(self.RESOURCE_PIN_REMAP) - - -@abstract_block -class Nrf52840_Base(Nrf52840_Ios, GeneratorBlock): - SYSTEM_PIN_REMAP: Dict[str, Union[str, List[str]]] # pin name in base -> pin name(s) - - @override - def _vddio(self) -> Port[VoltageLink]: - return self.pwr - - @override - def _system_pinmap(self) -> Dict[str, Union[Passive, HasPassivePort]]: - return VariantPinRemapper( - { - "Vdd": self.pwr, - "Vss": self.gnd, - "Vbus": self.pwr_usb, - "nRESET": self.nreset, - } - ).remap(self.SYSTEM_PIN_REMAP) - - def __init__(self, **kwargs: Any) -> None: - super().__init__(**kwargs) - - self.gnd = self.Port(Ground(), [Common]) - self.pwr = self.Port(self._vdd_model(), [Power]) - - self.pwr_usb = self.Port( - VoltageSink( - voltage_limits=(4.35, 5.5) * Volt, - current_draw=(0.262, 7.73) * mAmp, # CPU/USB sleeping to everything active - ), - optional=True, - ) - self.require((self.usb.length() > 0).implies(self.pwr_usb.is_connected()), "USB require Vbus connected") - - # Additional ports (on top of IoController) - # Crystals from table 15, 32, 33 - # TODO Table 32, model crystal load capacitance and series resistance ratings - self.xtal = self.Port( - CrystalDriver(frequency_limits=(1, 25) * MHertz, voltage_out=self.pwr.link().voltage), optional=True - ) - # Assumed from "32kHz crystal" in 14.5 - self.xtal_rtc = self.Port( - CrystalDriver(frequency_limits=(32, 33) * kHertz, voltage_out=self.pwr.link().voltage), optional=True - ) - - self.swd = self.Port(SwdTargetPort.empty()) - self.nreset = self.Port(DigitalSink.from_bidir(self._dio_model(self.pwr)), optional=True) - self._io_ports.insert(0, self.swd) - - class Mdbt50q_1mv2_Device( Nrf52840_Interfaces, BaseIoControllerPinmapGenerator, InternalSubcircuit, JlcPart, GeneratorBlock, FootprintBlock ): From 520a9bd1dadd6404c9ba7ce39cf26b56e195d633 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 27 May 2026 23:03:54 -0700 Subject: [PATCH 16/28] cleaning --- edg/abstract_parts/BaseIoControllerWrapped.py | 24 ++++++++++++++ edg/parts/microcontroller/Rp2040.py | 10 +----- edg/parts/microcontroller/nRF52840.py | 33 ++++++++----------- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/edg/abstract_parts/BaseIoControllerWrapped.py b/edg/abstract_parts/BaseIoControllerWrapped.py index 054f25834..1afab34a0 100644 --- a/edg/abstract_parts/BaseIoControllerWrapped.py +++ b/edg/abstract_parts/BaseIoControllerWrapped.py @@ -13,6 +13,30 @@ class BaseIoControllerWrapped(BaseIoController): In this class, pin_assigns is treated as the model's pin assigns and internally remapped. """ + @staticmethod + def _remap_pin_assigns_list(remapping: Dict[str, str], pin_assigns: List[str]) -> List[str]: + """Given a remapping dict and a list of pin assigns, returns a new list of pin assigns with the remapping applied""" + remapped_assigns = [] + for assign in pin_assigns: + name, pindef = assign.split("=") + pin = pindef.split(",")[0].strip() # take the first (gpio name) if multiple + remapped_pin = remapping.get(pin) + if remapped_pin is not None: + remapped_assigns.append(f"{name.strip()}={remapped_pin}") + else: + remapped_assigns.append(assign) # pass through unknown assigns, which may be IO names + return remapped_assigns + + def _generator_param_all_ios(self) -> None: + # declare all IOs as generator params, required for _remap_pinning_assigns + for io_port in self._io_ports: + if isinstance(io_port, Vector): + self.generator_param(io_port.requested()) + elif isinstance(io_port, Port): + self.generator_param(io_port.is_connected()) + else: + raise NotImplementedError(f"unknown port type {io_port}") + def _remap_pinning_assigns( self, model_pin_assigns: List[str], remapping: Dict[str, str] ) -> Tuple[Dict[str, HasPassivePort], Dict[str, str]]: diff --git a/edg/parts/microcontroller/Rp2040.py b/edg/parts/microcontroller/Rp2040.py index e430fdbe8..cd1158d61 100644 --- a/edg/parts/microcontroller/Rp2040.py +++ b/edg/parts/microcontroller/Rp2040.py @@ -405,15 +405,7 @@ def __init__(self, **kwargs: Any) -> None: self.vcc = self.Port(VoltageSink.empty(), optional=True) # VUsb self.vcc_out = self.Port(VoltageSource.empty(), optional=True) self.generator_param(self.v3v3.is_connected(), self.vcc.is_connected(), self.pin_assigns) - - # TODO MOVE TO INFRASTRUCTURE - for io_port in self._io_ports: - if isinstance(io_port, Vector): - self.generator_param(io_port.requested()) - elif isinstance(io_port, Port): - self.generator_param(io_port.is_connected()) - else: - raise NotImplementedError(f"unknown port type {io_port}") + self._generator_param_all_ios() @override def generate(self) -> None: diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index 26b9b59b7..4adb5ad54 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -106,7 +106,7 @@ def __init__(self, **kwargs: Any) -> None: pulldown_capable=True, ) self._dio_lf_model = self._dio_model # "standard drive, low frequency IO only" (differences not modeled) - self.swd = self.Port(SwdTargetPort.empty()) + self.swd = self.Port(SwdTargetPort.empty(), optional=True) self.nreset = self.Port(DigitalSink.from_bidir(self._dio_model), optional=True) self._io_ports.insert(0, self.swd) @@ -465,15 +465,7 @@ def __init__(self, **kwargs: Any) -> None: self.swd = self.Port(SwdTargetPort.empty()) self._io_ports.insert(0, self.swd) self.generator_param(self.pin_assigns) - - # TODO MOVE TO INFRASTRUCTURE - for io_port in self._io_ports: - if isinstance(io_port, Vector): - self.generator_param(io_port.requested()) - elif isinstance(io_port, Port): - self.generator_param(io_port.is_connected()) - else: - raise NotImplementedError(f"unknown port type {io_port}") + self._generator_param_all_ios() @override def generate(self) -> None: @@ -631,6 +623,7 @@ def __init__(self, **kwargs: Any) -> None: self.vusb = self.Port(Passive.empty(), optional=True) self.generator_param(self.pin_assigns) + self._generator_param_all_ios() @override def generate(self) -> None: @@ -675,6 +668,7 @@ class Feather_Nrf52840( def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self.generator_param( + self.pin_assigns, self.gnd.is_connected(), self.pwr.is_connected(), self.pwr_out.is_connected(), @@ -682,8 +676,8 @@ def __init__(self, **kwargs: Any) -> None: ) @override - def contents(self) -> None: - super().contents() + def generate(self) -> None: + super().generate() self.require( self.pwr.is_connected().implies(~self.vusb_out.is_connected()), @@ -694,18 +688,19 @@ def contents(self) -> None: "can't source 3v3 power if power input connected", ) - self.model = self.Block(Mdbt50q_1mv2_Device(pin_assigns=ArrayStringExpr())) - model_pin_assigns = self._export_ios_inner(self.model) - self.assign(self.model.pin_assigns, model_pin_assigns) + self.model = self.Block( + Mdbt50q_1mv2_Device( + pin_assigns=BaseIoControllerWrapped._remap_pin_assigns_list( + {v: k for k, v in Feather_Nrf52840_Device._PIN_REMAPPING.items()}, self.get(self.pin_assigns) + ) + ) + ) + self._export_ios_inner(self.model) 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) - @override - def generate(self) -> None: - super().generate() - 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) From 7f23b3fcc7890096830b6805e68c332251b13054 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 27 May 2026 23:08:52 -0700 Subject: [PATCH 17/28] refactoring --- edg/parts/microcontroller/Rp2040.py | 18 ++++++++++-------- edg/parts/microcontroller/nRF52840.py | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/edg/parts/microcontroller/Rp2040.py b/edg/parts/microcontroller/Rp2040.py index cd1158d61..03119e303 100644 --- a/edg/parts/microcontroller/Rp2040.py +++ b/edg/parts/microcontroller/Rp2040.py @@ -453,6 +453,7 @@ class Xiao_Rp2040( def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self.generator_param( + self.pin_assigns, self.gnd.is_connected(), self.pwr.is_connected(), self.pwr_out.is_connected(), @@ -461,8 +462,8 @@ def __init__(self, **kwargs: Any) -> None: ) @override - def contents(self) -> None: - super().contents() + def generate(self) -> None: + super().generate() self.pwr_vin.init_from( VoltageSink( # based on RS3236-3.3 @@ -495,17 +496,18 @@ def contents(self) -> None: ) self.model = self.Block(Rp2040_Device(pin_assigns=ArrayStringExpr(), _model=True)) - model_pin_assigns = self._export_ios_inner(self.model) - self.assign(self.model.pin_assigns, model_pin_assigns) + self._export_ios_inner(self.model) + self.assign( + self.model.pin_assigns, + BaseIoControllerWrapped._remap_pin_assigns_list( + {v: k for k, v in Xiao_Rp2040_Device._PIN_REMAPPING.items()}, self.get(self.pin_assigns) + ), + ) 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) - @override - def generate(self) -> None: - super().generate() - 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 diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index 4adb5ad54..4695f4dc5 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -510,13 +510,19 @@ def __init__(self, **kwargs: Any) -> None: self.pwr_usb = self.Export(self.model.pwr_usb, optional=True) self.reset = self.Export(self.model.nreset, optional=True) self.swd = self.Export(self.model.swd, optional=True) + self.generator_param(self.pin_assigns) @override - def contents(self) -> None: - super().contents() + def generate(self) -> None: + super().generate() - model_pin_assigns = self._export_ios_inner(self.model) - self.assign(self.model.pin_assigns, model_pin_assigns) + self._export_ios_inner(self.model) + self.assign( + self.model.pin_assigns, + BaseIoControllerWrapped._remap_pin_assigns_list( + {v: k for k, v in Holyiot_18010_Footprint._PIN_REMAPPING.items()}, self.get(self.pin_assigns) + ), + ) self.device = self.Block(Holyiot_18010_Footprint(pin_assigns=self.model.actual_pin_assigns)) self.assign(self.actual_pin_assigns, self.device.actual_pin_assigns) @@ -527,10 +533,6 @@ def contents(self) -> None: self.export_tap(self.reset, self.device.p0_18) self.export_tap(self.swd, self.device.swd) - @override - def generate(self) -> None: - super().generate() - class Holyiot_18010( Microcontroller, From edfb21496a6983dddb257c998a95ba412eb0dde3 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 27 May 2026 23:22:15 -0700 Subject: [PATCH 18/28] update references --- examples/BldcController/BldcController.net.ref | 2 +- examples/BldcController/BldcController.svgpcb.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/BldcController/BldcController.net.ref b/examples/BldcController/BldcController.net.ref index f1e554dcd..cb7cf5a6e 100644 --- a/examples/BldcController/BldcController.net.ref +++ b/examples/BldcController/BldcController.net.ref @@ -41,7 +41,7 @@ (footprint "bldc:FEATHERWING_NODIM") (property (name "Sheetname") (value "")) (property (name "Sheetfile") (value "")) - (property (name "edg_path") (value "mcu")) + (property (name "edg_path") (value "mcu.device")) (property (name "edg_short_path") (value "mcu")) (property (name "edg_refdes") (value "U1")) (property (name "edg_part") (value "Feather nRF52840 Express (Adafruit)")) diff --git a/examples/BldcController/BldcController.svgpcb.js b/examples/BldcController/BldcController.svgpcb.js index 1385b8e9a..cbd17e214 100644 --- a/examples/BldcController/BldcController.svgpcb.js +++ b/examples/BldcController/BldcController.svgpcb.js @@ -15,7 +15,7 @@ const H3 = board.add(JlcToolingHole_1_152mm, { translate: pt(0.485, 1.803), rotate: 0, id: 'H3' }) -// mcu +// mcu.device const U1 = board.add(FEATHERWING_NODIM, { translate: pt(0.643, 1.763), rotate: 0, id: 'U1' From c4f97a864a457c53386b767d8b7995f095435e97 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 27 May 2026 23:28:15 -0700 Subject: [PATCH 19/28] cleaning structure --- edg/abstract_parts/BaseIoControllerWrapped.py | 28 +++++++++++++++---- edg/parts/microcontroller/Rp2040.py | 6 ++-- edg/parts/microcontroller/nRF52840.py | 12 +++++--- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/edg/abstract_parts/BaseIoControllerWrapped.py b/edg/abstract_parts/BaseIoControllerWrapped.py index 1afab34a0..e26da7318 100644 --- a/edg/abstract_parts/BaseIoControllerWrapped.py +++ b/edg/abstract_parts/BaseIoControllerWrapped.py @@ -14,17 +14,32 @@ class BaseIoControllerWrapped(BaseIoController): """ @staticmethod - def _remap_pin_assigns_list(remapping: Dict[str, str], pin_assigns: List[str]) -> List[str]: - """Given a remapping dict and a list of pin assigns, returns a new list of pin assigns with the remapping applied""" - remapped_assigns = [] + def _remap_pin_assigns_list( + remapping: Dict[str, str], + pin_assigns: List[str], + *, + invert_remapping: bool = False, + discard_unremappable: bool = False, + ) -> Dict[str, str]: + """Given a remapping dict and a list of pin assigns, returns the mapping as a dict with the remapping applied. + If invert_remapping is True, the remapping dict is inverted before applying. + If discard_unremappable is True, assigns not present in the remapping dict are discarded. + Otherwise, they are passed unmodified. + """ + if invert_remapping: + remapping = {v: k for k, v in remapping.items()} + + remapped_assigns = {} for assign in pin_assigns: name, pindef = assign.split("=") pin = pindef.split(",")[0].strip() # take the first (gpio name) if multiple remapped_pin = remapping.get(pin) if remapped_pin is not None: - remapped_assigns.append(f"{name.strip()}={remapped_pin}") + remapped_assigns[name.strip()] = remapped_pin + elif not discard_unremappable: + remapped_assigns[name.strip()] = pindef # pass unmodified if not remappable, eg bundle containers else: - remapped_assigns.append(assign) # pass through unknown assigns, which may be IO names + pass # discarded return remapped_assigns def _generator_param_all_ios(self) -> None: @@ -87,7 +102,8 @@ def remap_port_recursive(port: Port, prefix: str = "") -> None: return pinning, actual_pin_assigns - def _remap_assigns_to_value(self, assigns: Dict[str, str]) -> List[str]: + @staticmethod + def _remap_assigns_to_value(assigns: Dict[str, str]) -> List[str]: """Given a dict of pin assigns from _remap_pinning_assigns, returns a list of assign strings for use in self.actual_pin_assigns""" return [f"{name}={assign}" for name, assign in assigns.items()] diff --git a/edg/parts/microcontroller/Rp2040.py b/edg/parts/microcontroller/Rp2040.py index 03119e303..00688c05b 100644 --- a/edg/parts/microcontroller/Rp2040.py +++ b/edg/parts/microcontroller/Rp2040.py @@ -499,8 +499,10 @@ def generate(self) -> None: self._export_ios_inner(self.model) self.assign( self.model.pin_assigns, - BaseIoControllerWrapped._remap_pin_assigns_list( - {v: k for k, v in Xiao_Rp2040_Device._PIN_REMAPPING.items()}, self.get(self.pin_assigns) + BaseIoControllerWrapped._remap_assigns_to_value( + BaseIoControllerWrapped._remap_pin_assigns_list( + Xiao_Rp2040_Device._PIN_REMAPPING, self.get(self.pin_assigns), invert_remapping=True + ) ), ) diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index 4695f4dc5..aa80b2068 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -519,8 +519,10 @@ def generate(self) -> None: self._export_ios_inner(self.model) self.assign( self.model.pin_assigns, - BaseIoControllerWrapped._remap_pin_assigns_list( - {v: k for k, v in Holyiot_18010_Footprint._PIN_REMAPPING.items()}, self.get(self.pin_assigns) + BaseIoControllerWrapped._remap_assigns_to_value( + BaseIoControllerWrapped._remap_pin_assigns_list( + Holyiot_18010_Footprint._PIN_REMAPPING, self.get(self.pin_assigns), invert_remapping=True + ) ), ) @@ -692,8 +694,10 @@ def generate(self) -> None: self.model = self.Block( Mdbt50q_1mv2_Device( - pin_assigns=BaseIoControllerWrapped._remap_pin_assigns_list( - {v: k for k, v in Feather_Nrf52840_Device._PIN_REMAPPING.items()}, self.get(self.pin_assigns) + pin_assigns=BaseIoControllerWrapped._remap_assigns_to_value( + BaseIoControllerWrapped._remap_pin_assigns_list( + Feather_Nrf52840_Device._PIN_REMAPPING, self.get(self.pin_assigns), invert_remapping=True + ) ) ) ) From 114c62eaafb51d0b74cd3a90ed286ecd05d65003 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 27 May 2026 23:51:00 -0700 Subject: [PATCH 20/28] refactoring --- edg/abstract_parts/BaseIoControllerWrapped.py | 47 +++++++------------ edg/parts/microcontroller/Rp2040.py | 6 +-- edg/parts/microcontroller/nRF52840.py | 12 ++--- 3 files changed, 26 insertions(+), 39 deletions(-) diff --git a/edg/abstract_parts/BaseIoControllerWrapped.py b/edg/abstract_parts/BaseIoControllerWrapped.py index e26da7318..f6e1b49d7 100644 --- a/edg/abstract_parts/BaseIoControllerWrapped.py +++ b/edg/abstract_parts/BaseIoControllerWrapped.py @@ -19,12 +19,10 @@ def _remap_pin_assigns_list( pin_assigns: List[str], *, invert_remapping: bool = False, - discard_unremappable: bool = False, ) -> Dict[str, str]: """Given a remapping dict and a list of pin assigns, returns the mapping as a dict with the remapping applied. If invert_remapping is True, the remapping dict is inverted before applying. - If discard_unremappable is True, assigns not present in the remapping dict are discarded. - Otherwise, they are passed unmodified. + Assigns not present in the remapping dict are passed unchanged, eg for non-pin-assigns like bundle containers. """ if invert_remapping: remapping = {v: k for k, v in remapping.items()} @@ -36,10 +34,8 @@ def _remap_pin_assigns_list( remapped_pin = remapping.get(pin) if remapped_pin is not None: remapped_assigns[name.strip()] = remapped_pin - elif not discard_unremappable: - remapped_assigns[name.strip()] = pindef # pass unmodified if not remappable, eg bundle containers else: - pass # discarded + remapped_assigns[name.strip()] = pindef # pass unmodified if not remappable, eg bundle containers return remapped_assigns def _generator_param_all_ios(self) -> None: @@ -52,33 +48,24 @@ def _generator_param_all_ios(self) -> None: else: raise NotImplementedError(f"unknown port type {io_port}") - def _remap_pinning_assigns( - self, model_pin_assigns: List[str], remapping: Dict[str, str] - ) -> Tuple[Dict[str, HasPassivePort], Dict[str, str]]: - """Given the actual pin assignments and a remapping dict, returns the pinning dict for the footprint - and the updated actual pin assignments. - Generates concrete ports elements for IO Vectors""" + def _remap_to_footprint_pinning( + self, pin_assigns: Dict[str, str], valid_pins: Iterable[str] + ) -> Dict[str, HasPassivePort]: + """Given the pin assigns in a dict form as port name -> footprint pin, eg from _remap_pin_assigns_list, + returns the footprint-compatible form as footprint pin -> port object. + + This simplified pin assignment tool requires all pins to be assigned. + It does not automatically assigns unassigned pins, that is assumed to have happened at a higher level.""" pinning: Dict[str, HasPassivePort] = {} - actual_pin_assigns: Dict[str, str] = {} seen_names: Set[str] = set() - model_pin_assigns_dict: Dict[str, str] = {} - for assign in model_pin_assigns: - name, pindef = assign.split("=") - pins = pindef.split(",") - model_pin_assigns_dict[name.strip()] = pins[0].strip() # use the GPIO name - def remap_port_recursive(port: Port, prefix: str = "") -> None: """Remaps a port, recursively for bundles""" if isinstance(port, HasPassivePort): - if prefix not in model_pin_assigns_dict: - raise ValueError(f"pin {prefix} not assigned") - pin = model_pin_assigns_dict[prefix] - if pin not in remapping: - raise ValueError(f"pin {pin} not in remapping") - remapped_pin = remapping[pin] - pinning[remapped_pin] = port - actual_pin_assigns[prefix] = f"{pin}, {remapped_pin}" + pin = pin_assigns.get(prefix) + assert pin is not None, f"pin {prefix} not assigned" + assert pin in valid_pins, f"pin {pin} not in valid pins {valid_pins}" + pinning[pin] = port for subport_name, subport in port._ports.items(): remap_port_recursive(subport, f"{prefix}.{subport_name}") @@ -88,19 +75,19 @@ def remap_port_recursive(port: Port, prefix: str = "") -> None: io_port.defined() for subport_name in self.get(io_port.requested()): assert subport_name not in seen_names, f"duplicate pin name {subport_name}" + seen_names.add(subport_name) subport = io_port.append_elt(io_port._tpe.empty(), subport_name) remap_port_recursive(subport, subport_name) - seen_names.add(subport_name) elif isinstance(io_port, Port): if self.get(io_port.is_connected()): port_name = io_port._name_from(self) assert port_name not in seen_names, f"duplicate pin name {port_name}" - remap_port_recursive(io_port, port_name) seen_names.add(port_name) + remap_port_recursive(io_port, port_name) else: raise NotImplementedError(f"unknown port type {io_port}") - return pinning, actual_pin_assigns + return pinning @staticmethod def _remap_assigns_to_value(assigns: Dict[str, str]) -> List[str]: diff --git a/edg/parts/microcontroller/Rp2040.py b/edg/parts/microcontroller/Rp2040.py index 00688c05b..60c4283e7 100644 --- a/edg/parts/microcontroller/Rp2040.py +++ b/edg/parts/microcontroller/Rp2040.py @@ -416,9 +416,9 @@ def generate(self) -> None: "13": self.gnd, "14": self.vcc if self.get(self.vcc.is_connected()) else self.vcc_out, # VUsb } - remap_pinnings, remap_pin_assigns = self._remap_pinning_assigns(self.get(self.pin_assigns), self._PIN_REMAPPING) - pinning.update(remap_pinnings) - self.assign(self.actual_pin_assigns, self._remap_assigns_to_value(remap_pin_assigns)) + remapped_pin_assigns = self._remap_pin_assigns_list(self._PIN_REMAPPING, self.get(self.pin_assigns)) + pinning.update(self._remap_to_footprint_pinning(remapped_pin_assigns, self._PIN_REMAPPING.values())) + self.assign(self.actual_pin_assigns, self._remap_assigns_to_value(remapped_pin_assigns)) self.footprint( "U", diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index aa80b2068..8e5aac29b 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -479,9 +479,9 @@ def generate(self) -> None: "22": self.vbus, "21": self.p0_18, } - remap_pinnings, remap_pin_assigns = self._remap_pinning_assigns(self.get(self.pin_assigns), self._PIN_REMAPPING) - pinning.update(remap_pinnings) - self.assign(self.actual_pin_assigns, self._remap_assigns_to_value(remap_pin_assigns)) + remapped_pin_assigns = self._remap_pin_assigns_list(self._PIN_REMAPPING, self.get(self.pin_assigns)) + pinning.update(self._remap_to_footprint_pinning(remapped_pin_assigns, self._PIN_REMAPPING.values())) + self.assign(self.actual_pin_assigns, self._remap_assigns_to_value(remapped_pin_assigns)) self.footprint( "U", @@ -641,9 +641,9 @@ def generate(self) -> None: # 'EN': '27', # controls the onboard 3.3 LDO, internally pulled up # 'Vbat': '28', } - remap_pinnings, remap_pin_assigns = self._remap_pinning_assigns(self.get(self.pin_assigns), self._PIN_REMAPPING) - pinning.update(remap_pinnings) - self.assign(self.actual_pin_assigns, self._remap_assigns_to_value(remap_pin_assigns)) + remapped_pin_assigns = self._remap_pin_assigns_list(self._PIN_REMAPPING, self.get(self.pin_assigns)) + pinning.update(self._remap_to_footprint_pinning(remapped_pin_assigns, self._PIN_REMAPPING.values())) + self.assign(self.actual_pin_assigns, self._remap_assigns_to_value(remapped_pin_assigns)) self.footprint( "U", From 1742166c91dd1491735ec343b6379f6dccb8866a Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Thu, 28 May 2026 00:00:34 -0700 Subject: [PATCH 21/28] Update PinMappable.py --- edg/abstract_parts/PinMappable.py | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/edg/abstract_parts/PinMappable.py b/edg/abstract_parts/PinMappable.py index 8655ac0f1..b3efc1f82 100644 --- a/edg/abstract_parts/PinMappable.py +++ b/edg/abstract_parts/PinMappable.py @@ -323,6 +323,43 @@ def resource_pin(resource: BasePinMapResource) -> List[str]: return PinMapUtil(remapped_resources, self.transforms) + def filter_pins(self, allowed_pins: List[str]) -> "PinMapUtil": + """Returns a new PinMapUtil with only the specified pins kept. + If the allowed_pins list is empty, returns the input (all pins kept). + allowed_pins may be specified as a pin name or pin number.""" + + if not allowed_pins: + return self + + def filter_resource(resource: BasePinMapResource) -> Optional[BasePinMapResource]: + if isinstance(resource, PinResource): + if resource.pin in allowed_pins or resource.name in allowed_pins: + return resource + else: + return None + elif isinstance(resource, PeripheralFixedPin): + # TODO need to simultaneously filter pins and resource names + filtered_pins = { + elt_name: elt_pin + for elt_name, elt_pin in resource.inner_allowed_pins.items() + if elt_pin in allowed_pins or elt_name in allowed_pins + } + if filtered_pins: + return PeripheralFixedPin( + resource.name, resource.port_model, filtered_pins, resource.inner_resources + ) + else: + return None + elif isinstance(resource, BaseDelegatingPinMapResource): + return resource + else: + raise NotImplementedError(f"unknown resource {resource}") + + filtered_resources_raw = [filter_resource(resource) for resource in self.resources] + filtered_resources = [resource for resource in filtered_resources_raw if resource is not None] + + return PinMapUtil(filtered_resources, self.transforms) + @staticmethod def _resource_port_types(resource: BasePinMapResource) -> List[Type[Port]]: if isinstance(resource, PinResource): From 0dd1800adec305422cb5aeaa980dbc68cd0b2fbd Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Thu, 28 May 2026 00:24:19 -0700 Subject: [PATCH 22/28] pin filtering --- edg/abstract_parts/PinMappable.py | 17 +++++++------- edg/abstract_parts/test_pinmappable.py | 31 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/edg/abstract_parts/PinMappable.py b/edg/abstract_parts/PinMappable.py index b3efc1f82..f30eab09b 100644 --- a/edg/abstract_parts/PinMappable.py +++ b/edg/abstract_parts/PinMappable.py @@ -338,15 +338,16 @@ def filter_resource(resource: BasePinMapResource) -> Optional[BasePinMapResource else: return None elif isinstance(resource, PeripheralFixedPin): - # TODO need to simultaneously filter pins and resource names - filtered_pins = { - elt_name: elt_pin - for elt_name, elt_pin in resource.inner_allowed_pins.items() - if elt_pin in allowed_pins or elt_name in allowed_pins - } - if filtered_pins: + filtered_keys = [] + for key, pin in resource.inner_allowed_pins.items(): + if pin in allowed_pins or resource.inner_resources[key] in allowed_pins: + filtered_keys.append(key) + if filtered_keys: return PeripheralFixedPin( - resource.name, resource.port_model, filtered_pins, resource.inner_resources + resource.name, + resource.port_model, + {k: v for k, v in resource.inner_allowed_pins.items() if k in filtered_keys}, + {k: v for k, v in resource.inner_resources.items() if k in filtered_keys}, ) else: return None diff --git a/edg/abstract_parts/test_pinmappable.py b/edg/abstract_parts/test_pinmappable.py index 3b1985851..61775f853 100644 --- a/edg/abstract_parts/test_pinmappable.py +++ b/edg/abstract_parts/test_pinmappable.py @@ -98,6 +98,37 @@ def test_assign_remapped(self) -> None: # fully user-specified self.assertIn(AllocatedResource(ain_model, "AIO4", "P4", "4"), allocated) self.assertIn(AllocatedResource(ain_model, "AIO5", "P5", "5"), allocated) + def test_assign_filtered(self) -> None: # fully user-specified + dio_model = DigitalBidir() + ain_model = AnalogSink() + allocated = ( + PinMapUtil( + [ + PinResource("P1", {"PIO1": dio_model}), + PinResource("P2", {"PIO2": dio_model}), + PinResource("P3", {"PIO3": dio_model, "AIn3": ain_model}), + PinResource("P4", {"PIO4": dio_model, "AIn4": ain_model}), + PinResource("P5", {"AIn5": ain_model}), + ] + ) + .remap_pins( + { + "P1": "1", + "P2": "2", + "P3": "3", + "P4": "4", + "P5": "5", + } + ) + .filter_pins(["P2", "4"]) + .allocate( + [(DigitalBidir, ["DIO2"]), (AnalogSink, ["AIO4"])], + [], + ) + ) + self.assertIn(AllocatedResource(dio_model, "DIO2", "P2", "2"), allocated) + self.assertIn(AllocatedResource(ain_model, "AIO4", "P4", "4"), allocated) + def test_assign_mixed(self) -> None: # mix of user-specified and automatic assignments, assuming greedy algo dio_model = DigitalBidir() ain_model = AnalogSink() From 841fb91e64f00e00c973c0c1393e83cedf590576 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Thu, 28 May 2026 00:35:39 -0700 Subject: [PATCH 23/28] use allowed_pins --- edg/abstract_parts/test_pinmappable.py | 43 ++- edg/parts/microcontroller/Rp2040.py | 240 ++++++++-------- edg/parts/microcontroller/nRF52840.py | 384 +++++++++++++------------ 3 files changed, 353 insertions(+), 314 deletions(-) diff --git a/edg/abstract_parts/test_pinmappable.py b/edg/abstract_parts/test_pinmappable.py index 61775f853..563f1191a 100644 --- a/edg/abstract_parts/test_pinmappable.py +++ b/edg/abstract_parts/test_pinmappable.py @@ -100,15 +100,12 @@ def test_assign_remapped(self) -> None: # fully user-specified def test_assign_filtered(self) -> None: # fully user-specified dio_model = DigitalBidir() - ain_model = AnalogSink() allocated = ( PinMapUtil( [ PinResource("P1", {"PIO1": dio_model}), PinResource("P2", {"PIO2": dio_model}), - PinResource("P3", {"PIO3": dio_model, "AIn3": ain_model}), - PinResource("P4", {"PIO4": dio_model, "AIn4": ain_model}), - PinResource("P5", {"AIn5": ain_model}), + PinResource("P3", {"PIO3": dio_model}), ] ) .remap_pins( @@ -116,18 +113,40 @@ def test_assign_filtered(self) -> None: # fully user-specified "P1": "1", "P2": "2", "P3": "3", - "P4": "4", - "P5": "5", } ) - .filter_pins(["P2", "4"]) - .allocate( - [(DigitalBidir, ["DIO2"]), (AnalogSink, ["AIO4"])], - [], + .filter_pins(["P1", "P3"]) # test both pin name and pin number + .allocate([(DigitalBidir, ["DIO1", "DIO3"])]) + ) + self.assertIn(AllocatedResource(dio_model, "DIO1", "P1", "1"), allocated) + self.assertIn(AllocatedResource(dio_model, "DIO3", "P3", "3"), allocated) + + def test_assign_filtered_empty(self) -> None: + dio_model = DigitalBidir() + allocated = ( + PinMapUtil( + [ + PinResource("P1", {"PIO1": dio_model}), + PinResource("P2", {"PIO2": dio_model}), + ] ) + .filter_pins([]) + .allocate([(DigitalBidir, ["DIO1", "DIO2"])]) ) - self.assertIn(AllocatedResource(dio_model, "DIO2", "P2", "2"), allocated) - self.assertIn(AllocatedResource(ain_model, "AIO4", "P4", "4"), allocated) + self.assertIn(AllocatedResource(dio_model, "DIO1", "P1", "P1"), allocated) + self.assertIn(AllocatedResource(dio_model, "DIO2", "P2", "P2"), allocated) + + def test_assign_filtered_overflow(self) -> None: + dio_model = DigitalBidir() + with self.assertRaises(AutomaticAllocationError): + PinMapUtil( + [ + PinResource("1", {"PIO1": dio_model}), + PinResource("3", {"PIO3": dio_model}), + ] + ).filter_pins( + ["1"] + ).allocate([(DigitalBidir, ["DIO1", "DIO2"])]) def test_assign_mixed(self) -> None: # mix of user-specified and automatic assignments, assuming greedy algo dio_model = DigitalBidir() diff --git a/edg/parts/microcontroller/Rp2040.py b/edg/parts/microcontroller/Rp2040.py index 60c4283e7..c5d08a12a 100644 --- a/edg/parts/microcontroller/Rp2040.py +++ b/edg/parts/microcontroller/Rp2040.py @@ -51,9 +51,11 @@ class Rp2040_Device( "SWCLK": "24", } - def __init__(self, *, _model: BoolLike = False, **kwargs: Any) -> None: + def __init__(self, *, allowed_pins: ArrayStringLike = [], _model: BoolLike = False, **kwargs: Any) -> None: super().__init__(**kwargs) + self.allowed_pins = self.ArgParameter(allowed_pins) # TODO move to infrastructure + self.generator_param(self.allowed_pins) self._model = self.ArgParameter(_model) self.gnd = self.Port(Ground(), [Common]) @@ -200,112 +202,116 @@ def _io_pinmap(self) -> PinMapUtil: i2c_model = I2cController(DigitalBidir.empty()) i2c_target_model = I2cTarget(DigitalBidir.empty()) - return PinMapUtil( - [ - PinResource("GPIO0", {"GPIO0": self._dio_ft_model}), - PinResource("GPIO1", {"GPIO1": self._dio_ft_model}), - PinResource("GPIO2", {"GPIO2": self._dio_ft_model}), - PinResource("GPIO3", {"GPIO3": self._dio_ft_model}), - PinResource("GPIO4", {"GPIO4": self._dio_ft_model}), - PinResource("GPIO5", {"GPIO5": self._dio_ft_model}), - PinResource("GPIO6", {"GPIO6": self._dio_ft_model}), - PinResource("GPIO7", {"GPIO7": self._dio_ft_model}), - PinResource("GPIO8", {"GPIO8": self._dio_ft_model}), - PinResource("GPIO9", {"GPIO9": self._dio_ft_model}), - PinResource("GPIO10", {"GPIO10": self._dio_ft_model}), - PinResource("GPIO11", {"GPIO11": self._dio_ft_model}), - PinResource("GPIO12", {"GPIO12": self._dio_ft_model}), - PinResource("GPIO13", {"GPIO13": self._dio_ft_model}), - PinResource("GPIO14", {"GPIO14": self._dio_ft_model}), - PinResource("GPIO15", {"GPIO15": self._dio_ft_model}), - PinResource("GPIO16", {"GPIO16": self._dio_ft_model}), - PinResource("GPIO17", {"GPIO17": self._dio_ft_model}), - PinResource("GPIO18", {"GPIO18": self._dio_ft_model}), - PinResource("GPIO19", {"GPIO19": self._dio_ft_model}), - PinResource("GPIO20", {"GPIO20": self._dio_ft_model}), - PinResource("GPIO21", {"GPIO21": self._dio_ft_model}), - PinResource("GPIO22", {"GPIO22": self._dio_ft_model}), - PinResource("GPIO23", {"GPIO23": self._dio_ft_model}), - PinResource("GPIO24", {"GPIO24": self._dio_ft_model}), - PinResource("GPIO25", {"GPIO25": self._dio_ft_model}), - PinResource("GPIO26", {"GPIO26": self._dio_std_model, "ADC0": adc_model}), - PinResource("GPIO27", {"GPIO27": self._dio_std_model, "ADC1": adc_model}), - PinResource("GPIO28", {"GPIO28": self._dio_std_model, "ADC2": adc_model}), - PinResource("GPIO29", {"GPIO29": self._dio_std_model, "ADC3": adc_model}), - # fixed-pin peripherals - PeripheralFixedPin("USB", UsbDevicePort(self._dio_usb_model), {"dm": "USB_DM", "dp": "USB_DP"}), - # reassignable peripherals - PeripheralFixedResource( - "UART0", - uart_model, - {"tx": ["GPIO0", "GPIO12", "GPIO16", "GPIO28"], "rx": ["GPIO1", "GPIO13", "GPIO17", "GPIO29"]}, - ), - PeripheralFixedResource( - "UART1", - uart_model, - {"tx": ["GPIO4", "GPIO8", "GPIO20", "GPIO24"], "rx": ["GPIO5", "GPIO9", "GPIO21", "GPIO25"]}, - ), - PeripheralFixedResource( - "SPI0", - spi_model, - { - "miso": ["GPIO0", "GPIO4", "GPIO16", "GPIO20"], # RX - "sck": ["GPIO2", "GPIO6", "GPIO18", "GPIO22"], - "mosi": ["GPIO3", "GPIO7", "GPIO19", "GPIO23"], # TX - }, - ), - PeripheralFixedResource( - "SPI1", - spi_model, - { - "miso": ["GPIO8", "GPIO12", "GPIO24", "GPIO28"], # RX - "sck": ["GPIO10", "GPIO14", "GPIO26"], - "mosi": ["GPIO11", "GPIO15", "GPIO27"], # TX - }, - ), - # SPI peripheral omitted, since TX tri-state is not tied to CS and must be controlled in software - PeripheralFixedResource( - "I2C0", - i2c_model, - { - "sda": ["GPIO0", "GPIO4", "GPIO8", "GPIO12", "GPIO16", "GPIO20", "GPIO24", "GPIO28"], - "scl": ["GPIO1", "GPIO5", "GPIO9", "GPIO13", "GPIO17", "GPIO21", "GPIO25", "GPIO20"], - }, - ), - PeripheralFixedResource( - "I2C1", - i2c_model, - { - "sda": ["GPIO2", "GPIO6", "GPIO10", "GPIO14", "GPIO18", "GPIO22", "GPIO24", "GPIO26"], - "scl": ["GPIO3", "GPIO7", "GPIO11", "GPIO15", "GPIO19", "GPIO23", "GPIO25", "GPIO27"], - }, - ), - PeripheralFixedResource( - "I2C0_T", - i2c_target_model, - { # TODO shared resource w/ I2C controller - "sda": ["GPIO0", "GPIO4", "GPIO8", "GPIO12", "GPIO16", "GPIO20", "GPIO24", "GPIO28"], - "scl": ["GPIO1", "GPIO5", "GPIO9", "GPIO13", "GPIO17", "GPIO21", "GPIO25", "GPIO20"], - }, - ), - PeripheralFixedResource( - "I2C1_T", - i2c_target_model, - { # TODO shared resource w/ I2C controller - "sda": ["GPIO2", "GPIO6", "GPIO10", "GPIO14", "GPIO18", "GPIO22", "GPIO24", "GPIO26"], - "scl": ["GPIO3", "GPIO7", "GPIO11", "GPIO15", "GPIO19", "GPIO23", "GPIO25", "GPIO27"], - }, - ), - PeripheralFixedPin( - "SWD", - SwdTargetPort(self._dio_std_model), - { - "swdio": "SWDIO", - "swclk": "SWCLK", - }, - ), - ] - ).remap_pins(self._PIN_MAPPING) + return ( + PinMapUtil( + [ + PinResource("GPIO0", {"GPIO0": self._dio_ft_model}), + PinResource("GPIO1", {"GPIO1": self._dio_ft_model}), + PinResource("GPIO2", {"GPIO2": self._dio_ft_model}), + PinResource("GPIO3", {"GPIO3": self._dio_ft_model}), + PinResource("GPIO4", {"GPIO4": self._dio_ft_model}), + PinResource("GPIO5", {"GPIO5": self._dio_ft_model}), + PinResource("GPIO6", {"GPIO6": self._dio_ft_model}), + PinResource("GPIO7", {"GPIO7": self._dio_ft_model}), + PinResource("GPIO8", {"GPIO8": self._dio_ft_model}), + PinResource("GPIO9", {"GPIO9": self._dio_ft_model}), + PinResource("GPIO10", {"GPIO10": self._dio_ft_model}), + PinResource("GPIO11", {"GPIO11": self._dio_ft_model}), + PinResource("GPIO12", {"GPIO12": self._dio_ft_model}), + PinResource("GPIO13", {"GPIO13": self._dio_ft_model}), + PinResource("GPIO14", {"GPIO14": self._dio_ft_model}), + PinResource("GPIO15", {"GPIO15": self._dio_ft_model}), + PinResource("GPIO16", {"GPIO16": self._dio_ft_model}), + PinResource("GPIO17", {"GPIO17": self._dio_ft_model}), + PinResource("GPIO18", {"GPIO18": self._dio_ft_model}), + PinResource("GPIO19", {"GPIO19": self._dio_ft_model}), + PinResource("GPIO20", {"GPIO20": self._dio_ft_model}), + PinResource("GPIO21", {"GPIO21": self._dio_ft_model}), + PinResource("GPIO22", {"GPIO22": self._dio_ft_model}), + PinResource("GPIO23", {"GPIO23": self._dio_ft_model}), + PinResource("GPIO24", {"GPIO24": self._dio_ft_model}), + PinResource("GPIO25", {"GPIO25": self._dio_ft_model}), + PinResource("GPIO26", {"GPIO26": self._dio_std_model, "ADC0": adc_model}), + PinResource("GPIO27", {"GPIO27": self._dio_std_model, "ADC1": adc_model}), + PinResource("GPIO28", {"GPIO28": self._dio_std_model, "ADC2": adc_model}), + PinResource("GPIO29", {"GPIO29": self._dio_std_model, "ADC3": adc_model}), + # fixed-pin peripherals + PeripheralFixedPin("USB", UsbDevicePort(self._dio_usb_model), {"dm": "USB_DM", "dp": "USB_DP"}), + # reassignable peripherals + PeripheralFixedResource( + "UART0", + uart_model, + {"tx": ["GPIO0", "GPIO12", "GPIO16", "GPIO28"], "rx": ["GPIO1", "GPIO13", "GPIO17", "GPIO29"]}, + ), + PeripheralFixedResource( + "UART1", + uart_model, + {"tx": ["GPIO4", "GPIO8", "GPIO20", "GPIO24"], "rx": ["GPIO5", "GPIO9", "GPIO21", "GPIO25"]}, + ), + PeripheralFixedResource( + "SPI0", + spi_model, + { + "miso": ["GPIO0", "GPIO4", "GPIO16", "GPIO20"], # RX + "sck": ["GPIO2", "GPIO6", "GPIO18", "GPIO22"], + "mosi": ["GPIO3", "GPIO7", "GPIO19", "GPIO23"], # TX + }, + ), + PeripheralFixedResource( + "SPI1", + spi_model, + { + "miso": ["GPIO8", "GPIO12", "GPIO24", "GPIO28"], # RX + "sck": ["GPIO10", "GPIO14", "GPIO26"], + "mosi": ["GPIO11", "GPIO15", "GPIO27"], # TX + }, + ), + # SPI peripheral omitted, since TX tri-state is not tied to CS and must be controlled in software + PeripheralFixedResource( + "I2C0", + i2c_model, + { + "sda": ["GPIO0", "GPIO4", "GPIO8", "GPIO12", "GPIO16", "GPIO20", "GPIO24", "GPIO28"], + "scl": ["GPIO1", "GPIO5", "GPIO9", "GPIO13", "GPIO17", "GPIO21", "GPIO25", "GPIO20"], + }, + ), + PeripheralFixedResource( + "I2C1", + i2c_model, + { + "sda": ["GPIO2", "GPIO6", "GPIO10", "GPIO14", "GPIO18", "GPIO22", "GPIO24", "GPIO26"], + "scl": ["GPIO3", "GPIO7", "GPIO11", "GPIO15", "GPIO19", "GPIO23", "GPIO25", "GPIO27"], + }, + ), + PeripheralFixedResource( + "I2C0_T", + i2c_target_model, + { # TODO shared resource w/ I2C controller + "sda": ["GPIO0", "GPIO4", "GPIO8", "GPIO12", "GPIO16", "GPIO20", "GPIO24", "GPIO28"], + "scl": ["GPIO1", "GPIO5", "GPIO9", "GPIO13", "GPIO17", "GPIO21", "GPIO25", "GPIO20"], + }, + ), + PeripheralFixedResource( + "I2C1_T", + i2c_target_model, + { # TODO shared resource w/ I2C controller + "sda": ["GPIO2", "GPIO6", "GPIO10", "GPIO14", "GPIO18", "GPIO22", "GPIO24", "GPIO26"], + "scl": ["GPIO3", "GPIO7", "GPIO11", "GPIO15", "GPIO19", "GPIO23", "GPIO25", "GPIO27"], + }, + ), + PeripheralFixedPin( + "SWD", + SwdTargetPort(self._dio_std_model), + { + "swdio": "SWDIO", + "swclk": "SWCLK", + }, + ), + ] + ) + .remap_pins(self._PIN_MAPPING) + .filter_pins(self.get(self.allowed_pins)) + ) class Rp2040( @@ -495,16 +501,18 @@ def generate(self) -> None: "ground required if power used", ) - self.model = self.Block(Rp2040_Device(pin_assigns=ArrayStringExpr(), _model=True)) - self._export_ios_inner(self.model) - self.assign( - self.model.pin_assigns, - BaseIoControllerWrapped._remap_assigns_to_value( - BaseIoControllerWrapped._remap_pin_assigns_list( - Xiao_Rp2040_Device._PIN_REMAPPING, self.get(self.pin_assigns), invert_remapping=True - ) - ), + self.model = self.Block( + Rp2040_Device( + pin_assigns=BaseIoControllerWrapped._remap_assigns_to_value( + BaseIoControllerWrapped._remap_pin_assigns_list( + Xiao_Rp2040_Device._PIN_REMAPPING, self.get(self.pin_assigns), invert_remapping=True + ) + ), + allowed_pins=list(Xiao_Rp2040_Device._PIN_REMAPPING.keys()), + _model=True, + ) ) + 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) diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index 8e5aac29b..81dc46760 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -74,9 +74,12 @@ class Mdbt50q_1mv2_Device( "P1.01": "61", } - def __init__(self, **kwargs: Any) -> None: + def __init__(self, allowed_pins: ArrayStringLike = [], **kwargs: Any) -> None: super().__init__(**kwargs) + self.allowed_pins = self.ArgParameter(allowed_pins) # TODO move to infrastructure + self.generator_param(self.allowed_pins) + self.gnd = self.Port(Ground(), [Common]) self.pwr = self.Port( VoltageSink( @@ -188,189 +191,193 @@ def _io_pinmap(self) -> PinMapUtil: "P1.00", ] - return PinMapUtil( - [ # Section 7.1.2 with QIAA aQFN73 & QFAA QFN48 pins only - PinResource("P0.31", {"P0.31": self._dio_lf_model, "AIN7": adc_model}), - PinResource("P0.29", {"P0.29": self._dio_lf_model, "AIN5": adc_model}), - PinResource("P0.02", {"P0.02": self._dio_lf_model, "AIN0": adc_model}), - PinResource("P1.15", {"P1.15": self._dio_lf_model}), - PinResource("P1.13", {"P1.13": self._dio_lf_model}), - PinResource("P1.10", {"P1.10": self._dio_lf_model}), - PinResource("P0.30", {"P0.30": self._dio_lf_model, "AIN6": adc_model}), - PinResource("P0.28", {"P0.28": self._dio_lf_model, "AIN4": adc_model}), - PinResource("P0.03", {"P0.03": self._dio_lf_model, "AIN1": adc_model}), - PinResource("P1.14", {"P1.14": self._dio_lf_model}), - PinResource("P1.12", {"P1.12": self._dio_lf_model}), - PinResource("P1.11", {"P1.11": self._dio_lf_model}), - PinResource("P0.00", {"P0.00": self._dio_model}), # TODO also 32.768 kHz crystal in - PinResource("P0.01", {"P0.01": self._dio_model}), # TODO also 32.768 kHz crystal in - PinResource("P0.26", {"P0.26": self._dio_model}), - PinResource("P0.27", {"P0.27": self._dio_model}), - PinResource("P0.04", {"P0.04": self._dio_model, "AIN2": adc_model}), - PinResource("P0.10", {"P0.10": self._dio_lf_model}), # TODO also NFC2 - PinResource("P0.05", {"P0.05": self._dio_model, "AIN3": adc_model}), - PinResource("P0.06", {"P0.06": self._dio_model}), - PinResource("P0.09", {"P0.09": self._dio_lf_model}), # TODO also NFC1 - PinResource("P0.07", {"P0.07": self._dio_model}), - PinResource("P0.08", {"P0.08": self._dio_model}), - PinResource("P1.08", {"P1.08": self._dio_model}), - PinResource("P1.07", {"P1.07": self._dio_lf_model}), - PinResource("P1.09", {"P1.09": self._dio_model}), - PinResource("P1.06", {"P1.06": self._dio_lf_model}), - PinResource("P0.11", {"P0.11": self._dio_model}), - PinResource("P1.05", {"P1.05": self._dio_lf_model}), - PinResource("P0.12", {"P0.12": self._dio_model}), - PinResource("P1.04", {"P1.04": self._dio_lf_model}), - PinResource("P1.03", {"P1.03": self._dio_lf_model}), - PinResource("P1.02", {"P1.02": self._dio_lf_model}), - PinResource("P1.01", {"P1.01": self._dio_lf_model}), - PinResource("P0.14", {"P0.14": self._dio_model}), - PinResource("P0.16", {"P0.16": self._dio_model}), - # PinResource('P0.18', {'P0.18': dio_model}), # configurable as RESET, mappable - PinResource("P0.19", {"P0.19": self._dio_model}), - PinResource("P0.21", {"P0.21": self._dio_model}), - PinResource("P0.23", {"P0.23": self._dio_model}), - PinResource("P0.25", {"P0.25": self._dio_model}), - PinResource("P0.13", {"P0.13": self._dio_model}), - PinResource("P0.15", {"P0.15": self._dio_model}), - PinResource("P0.17", {"P0.17": self._dio_model}), - PinResource("P0.20", {"P0.20": self._dio_model}), - PinResource("P0.22", {"P0.22": self._dio_model}), - PinResource("P0.24", {"P0.24": self._dio_model}), - PinResource( - "P1.00", {"P1.00": self._dio_model} - ), # TRACEDATA[0] and SWO, if used as IO must clear TRACECONFIG reg - PeripheralFixedPin( - "SWD", - SwdTargetPort(self._dio_model), - { - "swclk": "SWCLK", - "swdio": "SWDIO", - }, - ), - PeripheralFixedPin("USBD", UsbDevicePort(), {"dp": "D+", "dm": "D-"}), - PeripheralFixedResource( - "SPIM0", - spi_model, - { - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIM1", - spi_model, - { - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIM2", - spi_model, - { - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIM3", - spi_model, - { - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIS0", - spi_peripheral_model, - { # TODO shared resource w/ SPI controller - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIS1", - spi_peripheral_model, - { # TODO shared resource w/ SPI controller - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "SPIS2", - spi_peripheral_model, - { # TODO shared resource w/ SPI controller - "sck": hf_io_pins, - "miso": hf_io_pins, - "mosi": hf_io_pins, - }, - ), - PeripheralFixedResource( - "TWIM0", - i2c_model, - { - "scl": hf_io_pins, - "sda": hf_io_pins, - }, - ), - PeripheralFixedResource( - "TWIM1", - i2c_model, - { - "scl": hf_io_pins, - "sda": hf_io_pins, - }, - ), - PeripheralFixedResource( - "TWIS0", - i2c_target_model, - { # TODO shared resource w/ I2C controller - "scl": hf_io_pins, - "sda": hf_io_pins, - }, - ), - PeripheralFixedResource( - "TWIS1", - i2c_target_model, - { # TODO shared resource w/ I2C controller - "scl": hf_io_pins, - "sda": hf_io_pins, - }, - ), - PeripheralFixedResource( - "UARTE0", - uart_model, - { - "tx": hf_io_pins, - "rx": hf_io_pins, - }, - ), - PeripheralFixedResource( - "UARTE1", - uart_model, - { - "tx": hf_io_pins, - "rx": hf_io_pins, - }, - ), - PeripheralFixedResource( - "I2S", - i2s_model, - { - "sck": hf_io_pins, - "ws": hf_io_pins, - "sd": hf_io_pins, - }, - ), - ] - ).remap_pins(self._PIN_MAPPING) + return ( + PinMapUtil( + [ # Section 7.1.2 with QIAA aQFN73 & QFAA QFN48 pins only + PinResource("P0.31", {"P0.31": self._dio_lf_model, "AIN7": adc_model}), + PinResource("P0.29", {"P0.29": self._dio_lf_model, "AIN5": adc_model}), + PinResource("P0.02", {"P0.02": self._dio_lf_model, "AIN0": adc_model}), + PinResource("P1.15", {"P1.15": self._dio_lf_model}), + PinResource("P1.13", {"P1.13": self._dio_lf_model}), + PinResource("P1.10", {"P1.10": self._dio_lf_model}), + PinResource("P0.30", {"P0.30": self._dio_lf_model, "AIN6": adc_model}), + PinResource("P0.28", {"P0.28": self._dio_lf_model, "AIN4": adc_model}), + PinResource("P0.03", {"P0.03": self._dio_lf_model, "AIN1": adc_model}), + PinResource("P1.14", {"P1.14": self._dio_lf_model}), + PinResource("P1.12", {"P1.12": self._dio_lf_model}), + PinResource("P1.11", {"P1.11": self._dio_lf_model}), + PinResource("P0.00", {"P0.00": self._dio_model}), # TODO also 32.768 kHz crystal in + PinResource("P0.01", {"P0.01": self._dio_model}), # TODO also 32.768 kHz crystal in + PinResource("P0.26", {"P0.26": self._dio_model}), + PinResource("P0.27", {"P0.27": self._dio_model}), + PinResource("P0.04", {"P0.04": self._dio_model, "AIN2": adc_model}), + PinResource("P0.10", {"P0.10": self._dio_lf_model}), # TODO also NFC2 + PinResource("P0.05", {"P0.05": self._dio_model, "AIN3": adc_model}), + PinResource("P0.06", {"P0.06": self._dio_model}), + PinResource("P0.09", {"P0.09": self._dio_lf_model}), # TODO also NFC1 + PinResource("P0.07", {"P0.07": self._dio_model}), + PinResource("P0.08", {"P0.08": self._dio_model}), + PinResource("P1.08", {"P1.08": self._dio_model}), + PinResource("P1.07", {"P1.07": self._dio_lf_model}), + PinResource("P1.09", {"P1.09": self._dio_model}), + PinResource("P1.06", {"P1.06": self._dio_lf_model}), + PinResource("P0.11", {"P0.11": self._dio_model}), + PinResource("P1.05", {"P1.05": self._dio_lf_model}), + PinResource("P0.12", {"P0.12": self._dio_model}), + PinResource("P1.04", {"P1.04": self._dio_lf_model}), + PinResource("P1.03", {"P1.03": self._dio_lf_model}), + PinResource("P1.02", {"P1.02": self._dio_lf_model}), + PinResource("P1.01", {"P1.01": self._dio_lf_model}), + PinResource("P0.14", {"P0.14": self._dio_model}), + PinResource("P0.16", {"P0.16": self._dio_model}), + # PinResource('P0.18', {'P0.18': dio_model}), # configurable as RESET, mappable + PinResource("P0.19", {"P0.19": self._dio_model}), + PinResource("P0.21", {"P0.21": self._dio_model}), + PinResource("P0.23", {"P0.23": self._dio_model}), + PinResource("P0.25", {"P0.25": self._dio_model}), + PinResource("P0.13", {"P0.13": self._dio_model}), + PinResource("P0.15", {"P0.15": self._dio_model}), + PinResource("P0.17", {"P0.17": self._dio_model}), + PinResource("P0.20", {"P0.20": self._dio_model}), + PinResource("P0.22", {"P0.22": self._dio_model}), + PinResource("P0.24", {"P0.24": self._dio_model}), + PinResource( + "P1.00", {"P1.00": self._dio_model} + ), # TRACEDATA[0] and SWO, if used as IO must clear TRACECONFIG reg + PeripheralFixedPin( + "SWD", + SwdTargetPort(self._dio_model), + { + "swclk": "SWCLK", + "swdio": "SWDIO", + }, + ), + PeripheralFixedPin("USBD", UsbDevicePort(), {"dp": "D+", "dm": "D-"}), + PeripheralFixedResource( + "SPIM0", + spi_model, + { + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIM1", + spi_model, + { + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIM2", + spi_model, + { + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIM3", + spi_model, + { + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIS0", + spi_peripheral_model, + { # TODO shared resource w/ SPI controller + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIS1", + spi_peripheral_model, + { # TODO shared resource w/ SPI controller + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "SPIS2", + spi_peripheral_model, + { # TODO shared resource w/ SPI controller + "sck": hf_io_pins, + "miso": hf_io_pins, + "mosi": hf_io_pins, + }, + ), + PeripheralFixedResource( + "TWIM0", + i2c_model, + { + "scl": hf_io_pins, + "sda": hf_io_pins, + }, + ), + PeripheralFixedResource( + "TWIM1", + i2c_model, + { + "scl": hf_io_pins, + "sda": hf_io_pins, + }, + ), + PeripheralFixedResource( + "TWIS0", + i2c_target_model, + { # TODO shared resource w/ I2C controller + "scl": hf_io_pins, + "sda": hf_io_pins, + }, + ), + PeripheralFixedResource( + "TWIS1", + i2c_target_model, + { # TODO shared resource w/ I2C controller + "scl": hf_io_pins, + "sda": hf_io_pins, + }, + ), + PeripheralFixedResource( + "UARTE0", + uart_model, + { + "tx": hf_io_pins, + "rx": hf_io_pins, + }, + ), + PeripheralFixedResource( + "UARTE1", + uart_model, + { + "tx": hf_io_pins, + "rx": hf_io_pins, + }, + ), + PeripheralFixedResource( + "I2S", + i2s_model, + { + "sck": hf_io_pins, + "ws": hf_io_pins, + "sd": hf_io_pins, + }, + ), + ] + ) + .remap_pins(self._PIN_MAPPING) + .filter_pins(self.get(self.allowed_pins)) + ) class Mdbt50q_1mv2( @@ -504,7 +511,11 @@ class Holyiot_18010_Device( def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - self.model = self.Block(Mdbt50q_1mv2_Device(pin_assigns=ArrayStringExpr())) + self.model = self.Block( + Mdbt50q_1mv2_Device( + pin_assigns=ArrayStringExpr(), allowed_pins=list(Holyiot_18010_Footprint._PIN_REMAPPING.keys()) + ) + ) self.gnd = self.Export(self.model.gnd) self.pwr = self.Export(self.model.pwr) self.pwr_usb = self.Export(self.model.pwr_usb, optional=True) @@ -698,7 +709,8 @@ def generate(self) -> None: BaseIoControllerWrapped._remap_pin_assigns_list( Feather_Nrf52840_Device._PIN_REMAPPING, self.get(self.pin_assigns), invert_remapping=True ) - ) + ), + allowed_pins=list(Feather_Nrf52840_Device._PIN_REMAPPING.keys()), ) ) self._export_ios_inner(self.model) From 1280c7f1ef11b71d1141e20147f1a3d1077cde15 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Thu, 28 May 2026 19:37:18 -0700 Subject: [PATCH 24/28] Create test_mcu_wrapper.py --- edg/parts/microcontroller/test_mcu_wrapper.py | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 edg/parts/microcontroller/test_mcu_wrapper.py diff --git a/edg/parts/microcontroller/test_mcu_wrapper.py b/edg/parts/microcontroller/test_mcu_wrapper.py new file mode 100644 index 000000000..4011666f7 --- /dev/null +++ b/edg/parts/microcontroller/test_mcu_wrapper.py @@ -0,0 +1,65 @@ +import unittest +from typing_extensions import override + +from .Rp2040 import Xiao_Rp2040 +from ...circuits import * + + +class OverallocateTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.pwr = self.Block(DummyVoltageSource(voltage_out=3.3 * Volt(tol=0))) + self.gnd = self.Block(DummyGround()) + with self.implicit_connect( + ImplicitConnect(self.pwr.pwr, [Power]), + ImplicitConnect(self.gnd.gnd, [Common]), + ) as imp: + self.dut = imp.Block(Xiao_Rp2040()) + + self.ios = ElementDict[DummyDigitalSource]() + for i in range(12): # device only has 11 IOs + self.ios[i] = self.Block(DummyDigitalSource()) + self.connect(self.ios[i].io, self.dut.gpio.request(str(i))) + + +class BaseMcuTest(DesignTop): + def __init__(self) -> None: + super().__init__() + self.pwr = self.Block(DummyVoltageSource(voltage_out=3.3 * Volt(tol=0))) + self.gnd = self.Block(DummyGround()) + with self.implicit_connect( + ImplicitConnect(self.pwr.pwr, [Power]), + ImplicitConnect(self.gnd.gnd, [Common]), + ) as imp: + self.dut = imp.Block(Xiao_Rp2040()) + + self.ios = ElementDict[DummyDigitalSource]() + for i in range(2): + self.ios[i] = self.Block(DummyDigitalSource()) + self.connect(self.ios[i].io, self.dut.gpio.request(str(i))) + + +class AutoPinsTest(BaseMcuTest): + pass + + +class AssignedPinsTest(BaseMcuTest): + @override + def refinements(self) -> Refinements: + return Refinements(instance_values=[(["dut", "pin_assigns"], ["0=1", "1=3"])]) + + +class McuWrapperTestCase(unittest.TestCase): + def test_overallocate(self) -> None: + with self.assertRaises(CompilerCheckError): + ScalaCompiler.compile(OverallocateTest) + + def test_auto_pins(self) -> None: + compiled = ScalaCompiler.compile(AutoPinsTest) + self.assertEqual(compiled.get_value(["dut", "actual_pin_assigns"]), ["0=7", "1=8"]) + self.assertEqual(compiled.get_value(["dut", "model", "actual_pin_assigns"]), ["0=GPIO0, 2", "1=GPIO1, 3"]) + + def test_assigned_pins(self) -> None: + compiled = ScalaCompiler.compile(AssignedPinsTest) + self.assertEqual(compiled.get_value(["dut", "actual_pin_assigns"]), ["0=1", "1=3"]) + self.assertEqual(compiled.get_value(["dut", "model", "actual_pin_assigns"]), ["0=GPIO26, 38", "1=GPIO28, 40"]) From 64fefbcd0faac413360b484349d443b30ce7918c Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Thu, 28 May 2026 20:01:25 -0700 Subject: [PATCH 25/28] wip --- edg/abstract_parts/BaseIoControllerWrapped.py | 2 +- edg/parts/microcontroller/nRF52840.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/edg/abstract_parts/BaseIoControllerWrapped.py b/edg/abstract_parts/BaseIoControllerWrapped.py index f6e1b49d7..23d12df87 100644 --- a/edg/abstract_parts/BaseIoControllerWrapped.py +++ b/edg/abstract_parts/BaseIoControllerWrapped.py @@ -55,7 +55,7 @@ def _remap_to_footprint_pinning( returns the footprint-compatible form as footprint pin -> port object. This simplified pin assignment tool requires all pins to be assigned. - It does not automatically assigns unassigned pins, that is assumed to have happened at a higher level.""" + It does not automatically assign unassigned pins, that is assumed to have happened at a higher level.""" pinning: Dict[str, HasPassivePort] = {} seen_names: Set[str] = set() diff --git a/edg/parts/microcontroller/nRF52840.py b/edg/parts/microcontroller/nRF52840.py index 81dc46760..4189f6a73 100644 --- a/edg/parts/microcontroller/nRF52840.py +++ b/edg/parts/microcontroller/nRF52840.py @@ -633,7 +633,7 @@ class Feather_Nrf52840_Device(Nrf52840_Interfaces, BaseIoControllerWrapped, Gene def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self.gnd = self.Port(Ground.empty(), optional=True) - # power ports are paassive so directionality and concrete types can be resolved at the higher modeling level + # power ports are passive so directionality and concrete types can be resolved at the higher modeling level self.pwr = self.Port(Passive.empty(), optional=True) self.vusb = self.Port(Passive.empty(), optional=True) @@ -740,9 +740,9 @@ def generate(self) -> None: ) self.export_tap(self.vusb_out.net, self.device.vusb) - self.export_tap(self.gnd, self.device.gnd) if self.get(self.gnd.is_connected()): self.connect(self.gnd, self.model.gnd) else: self.gnd_model = self.Block(DummyGround()) self.connect(self.gnd_model.gnd, self.model.gnd) + self.export_tap(self.gnd, self.device.gnd) From f7b6c5262f204478b494b27ef2e767d78b80cc6d Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Thu, 28 May 2026 22:47:40 -0700 Subject: [PATCH 26/28] update tp2040 power net style, update examples --- edg/parts/microcontroller/Rp2040.py | 22 +++++++++---------- examples/BasicKeyboard/BasicKeyboard.net.ref | 2 +- .../BasicKeyboard/BasicKeyboard.svgpcb.js | 2 +- .../TestBlinkyBasic/TestBlinkyBasic.net.ref | 2 +- .../TestBlinkyBasic/TestBlinkyBasic.svgpcb.js | 2 +- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/edg/parts/microcontroller/Rp2040.py b/edg/parts/microcontroller/Rp2040.py index c5d08a12a..88c0e2fb8 100644 --- a/edg/parts/microcontroller/Rp2040.py +++ b/edg/parts/microcontroller/Rp2040.py @@ -406,21 +406,19 @@ class Xiao_Rp2040_Device( def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) self.gnd = self.Port(Ground.empty(), optional=True) - self.v3v3 = self.Port(VoltageSink.empty(), optional=True) - self.v3v3_out = self.Port(VoltageSource.empty(), optional=True) - self.vcc = self.Port(VoltageSink.empty(), optional=True) # VUsb - self.vcc_out = self.Port(VoltageSource.empty(), optional=True) - self.generator_param(self.v3v3.is_connected(), self.vcc.is_connected(), self.pin_assigns) + self.v3v3 = self.Port(Passive.empty(), optional=True) + self.vcc = self.Port(Passive.empty(), optional=True) # VUsb + self.generator_param(self.pin_assigns) self._generator_param_all_ios() @override def generate(self) -> None: super().generate() - pinning: Dict[str, HasPassivePort] = { - "12": self.v3v3 if self.get(self.v3v3.is_connected()) else self.v3v3_out, + pinning: Dict[str, Union[Passive, HasPassivePort]] = { + "12": self.v3v3, "13": self.gnd, - "14": self.vcc if self.get(self.vcc.is_connected()) else self.vcc_out, # VUsb + "14": self.vcc, # VUsb } remapped_pin_assigns = self._remap_pin_assigns_list(self._PIN_REMAPPING, self.get(self.pin_assigns)) pinning.update(self._remap_to_footprint_pinning(remapped_pin_assigns, self._PIN_REMAPPING.values())) @@ -522,7 +520,7 @@ def generate(self) -> None: 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, self.device.v3v3) + self.export_tap(self.pwr.net, self.device.v3v3) else: # board sources power from USB self.pwr_out_model = self.Block( DummyVoltageSource( @@ -533,12 +531,12 @@ def generate(self) -> None: self.connect(self.pwr_out_model.pwr, model_pwr) if self.get(self.pwr_out.is_connected()): self.connect(self.pwr_out, self.pwr_out_model.pwr) - self.export_tap(self.pwr_out, self.device.v3v3_out) + self.export_tap(self.pwr_out.net, self.device.v3v3) if self.get(self.pwr_vin.is_connected()): - self.export_tap(self.pwr_vin, self.device.vcc) + self.export_tap(self.pwr_vin.net, self.device.vcc) if self.get(self.vusb_out.is_connected()): - self.export_tap(self.vusb_out, self.device.vcc_out) + self.export_tap(self.vusb_out.net, self.device.vcc) self.export_tap(self.gnd, self.device.gnd) if self.get(self.gnd.is_connected()): diff --git a/examples/BasicKeyboard/BasicKeyboard.net.ref b/examples/BasicKeyboard/BasicKeyboard.net.ref index d9309348f..3b2479df2 100644 --- a/examples/BasicKeyboard/BasicKeyboard.net.ref +++ b/examples/BasicKeyboard/BasicKeyboard.net.ref @@ -183,7 +183,7 @@ (node (ref U1) (pin 13))) (net (code 7) (name "mcu.pwr_out") (node (ref U1) (pin 12))) -(net (code 8) (name "mcu.device.vcc_out") +(net (code 8) (name "mcu.device.vcc") (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 12949d788..2fe774a62 100644 --- a/examples/BasicKeyboard/BasicKeyboard.svgpcb.js +++ b/examples/BasicKeyboard/BasicKeyboard.svgpcb.js @@ -15,7 +15,7 @@ board.setNetlist([ {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.vcc_out", pads: [["U1", "14"]]}, + {name: "mcu.device.vcc", 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 4f6524606..27af5ce41 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_out") +(net (code 3) (name "mcu.device.vcc") (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 64dacd4ad..ca7adcd27 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_out", pads: [["U1", "14"]]}, + {name: "mcu.device.vcc", pads: [["U1", "14"]]}, {name: "led.signal", pads: [["U1", "7"], ["D1", "2"]]}, {name: "led.package.k", pads: [["D1", "1"], ["R1", "1"]]} ]) From 82b8aed7758e9d6013c6375fc4921f45b57740e2 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Fri, 29 May 2026 00:44:10 -0700 Subject: [PATCH 27/28] wip --- edg/abstract_parts/BaseIoControllerWrapped.py | 10 ----- edg/abstract_parts/IoController.py | 11 +++++ edg/abstract_parts/PinMappable.py | 43 +++++++++++-------- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/edg/abstract_parts/BaseIoControllerWrapped.py b/edg/abstract_parts/BaseIoControllerWrapped.py index 23d12df87..ee48bfa87 100644 --- a/edg/abstract_parts/BaseIoControllerWrapped.py +++ b/edg/abstract_parts/BaseIoControllerWrapped.py @@ -38,16 +38,6 @@ def _remap_pin_assigns_list( remapped_assigns[name.strip()] = pindef # pass unmodified if not remappable, eg bundle containers return remapped_assigns - def _generator_param_all_ios(self) -> None: - # declare all IOs as generator params, required for _remap_pinning_assigns - for io_port in self._io_ports: - if isinstance(io_port, Vector): - self.generator_param(io_port.requested()) - elif isinstance(io_port, Port): - self.generator_param(io_port.is_connected()) - else: - raise NotImplementedError(f"unknown port type {io_port}") - def _remap_to_footprint_pinning( self, pin_assigns: Dict[str, str], valid_pins: Iterable[str] ) -> Dict[str, HasPassivePort]: diff --git a/edg/abstract_parts/IoController.py b/edg/abstract_parts/IoController.py index b1946f1b7..b3b16170d 100644 --- a/edg/abstract_parts/IoController.py +++ b/edg/abstract_parts/IoController.py @@ -206,6 +206,17 @@ def _export_tap_ios_inner(self, inner: "BaseIoController") -> None: inner_io = inner_ios_by_type[self_io_type] self.export_tap(self_io, inner_io) + def _generator_param_all_ios(self) -> None: + """Declares all BaseIoController IOs as generator params. This must be a GeneratorBlock.""" + assert isinstance(self, GeneratorBlock) + for io_port in self._io_ports: + if isinstance(io_port, Vector): + self.generator_param(io_port.requested()) + elif isinstance(io_port, Port): + self.generator_param(io_port.is_connected()) + else: + raise NotImplementedError(f"unknown port type {io_port}") + @staticmethod def _instantiate_from( ios: List[BasePort], allocations: List[AllocatedResource] diff --git a/edg/abstract_parts/PinMappable.py b/edg/abstract_parts/PinMappable.py index f30eab09b..70bb0cdff 100644 --- a/edg/abstract_parts/PinMappable.py +++ b/edg/abstract_parts/PinMappable.py @@ -53,19 +53,24 @@ class BaseDelegatingPinMapResource(BasePinMapResource): class PinResource(BaseLeafPinMapResource): - """A resource for a single chip pin, which can be one of several port types (eg, an ADC and DIO sharing a pin).""" + """A resource for a single chip pin, which can be one of several port types (eg, an ADC and DIO sharing a pin). - def __init__(self, pin: str, name_models: Mapping[str, Union[Passive, HasPassivePort]], name: Optional[str] = None): + Generally, this is initially created with pin = pin name (like GPIO0), then remapped so the pin = pin number, + with the pinname inheriting the prior pin name.""" + + def __init__( + self, pin: str, name_models: Mapping[str, Union[Passive, HasPassivePort]], pinname: Optional[str] = None + ): self.pin = pin self.name_models = name_models - if name is not None: - self.name = name + if pinname is not None: + self.pinname = pinname else: - self.name = pin + self.pinname = pin @override def __repr__(self) -> str: - return f"PinResource({self.name}, {self.pin}, {self.name_models})" + return f"PinResource({self.pinname}, {self.pin}, {self.name_models})" @override def __eq__(self, other: Any) -> bool: @@ -88,19 +93,19 @@ def __init__( name: str, port_model: Port, inner_allowed_pins: Dict[str, str], - inner_resources: Optional[Dict[str, str]] = None, + inner_pinnames: Optional[Dict[str, str]] = None, ): self.name = name self.port_model = port_model self.inner_allowed_pins = inner_allowed_pins - if inner_resources is not None: - self.inner_resources = inner_resources + if inner_pinnames is not None: + self.inner_pinnames = inner_pinnames else: - self.inner_resources = inner_allowed_pins + self.inner_pinnames = inner_allowed_pins @override def __repr__(self) -> str: - return f"PeripheralFixedPin({self.name}, {self.port_model.__class__.__name__} {self.inner_allowed_pins} {self.inner_resources})" + return f"PeripheralFixedPin({self.name}, {self.port_model.__class__.__name__} {self.inner_allowed_pins} {self.inner_pinnames})" @override def __eq__(self, other: Any) -> bool: @@ -110,7 +115,7 @@ def __eq__(self, other: Any) -> bool: and self.name == other.name and self.port_model is other.port_model and self.inner_allowed_pins == other.inner_allowed_pins - and self.inner_resources == other.inner_resources + and self.inner_pinnames == other.inner_pinnames ) @@ -289,7 +294,7 @@ def remap_pins(self, pinmap: Dict[str, str]) -> "PinMapUtil": def remap_resource(resource: BasePinMapResource) -> Optional[BasePinMapResource]: if isinstance(resource, PinResource): if resource.pin in pinmap: - return PinResource(pinmap[resource.pin], resource.name_models, resource.name) + return PinResource(pinmap[resource.pin], resource.name_models, resource.pinname) else: return None elif isinstance(resource, PeripheralFixedPin): @@ -298,7 +303,7 @@ def remap_resource(resource: BasePinMapResource) -> Optional[BasePinMapResource] for elt_name, elt_pin in resource.inner_allowed_pins.items() if elt_pin in pinmap } - return PeripheralFixedPin(resource.name, resource.port_model, remapped_pins, resource.inner_resources) + return PeripheralFixedPin(resource.name, resource.port_model, remapped_pins, resource.inner_pinnames) elif isinstance(resource, BaseDelegatingPinMapResource): return resource else: @@ -333,21 +338,21 @@ def filter_pins(self, allowed_pins: List[str]) -> "PinMapUtil": def filter_resource(resource: BasePinMapResource) -> Optional[BasePinMapResource]: if isinstance(resource, PinResource): - if resource.pin in allowed_pins or resource.name in allowed_pins: + if resource.pin in allowed_pins or resource.pinname in allowed_pins: return resource else: return None elif isinstance(resource, PeripheralFixedPin): filtered_keys = [] for key, pin in resource.inner_allowed_pins.items(): - if pin in allowed_pins or resource.inner_resources[key] in allowed_pins: + if pin in allowed_pins or resource.inner_pinnames[key] in allowed_pins: filtered_keys.append(key) if filtered_keys: return PeripheralFixedPin( resource.name, resource.port_model, {k: v for k, v in resource.inner_allowed_pins.items() if k in filtered_keys}, - {k: v for k, v in resource.inner_resources.items() if k in filtered_keys}, + {k: v for k, v in resource.inner_pinnames.items() if k in filtered_keys}, ) else: return None @@ -416,7 +421,7 @@ def try_allocate_resource( if isinstance(resource, PinResource): # single pin: just assign it sub_assignments.check_empty() resource_name, resource_model = resource.get_name_model_for_type(port_type) - allocated_resource = AllocatedResource(resource_model, port_name, resource.name, resource.pin) + allocated_resource = AllocatedResource(resource_model, port_name, resource.pinname, resource.pin) return allocated_resource elif isinstance(resource, PeripheralFixedPin): # fixed pin: check user-assignment, or assign first inner_pin_map: Dict[str, Tuple[str, Optional[str]]] = {} @@ -425,7 +430,7 @@ def try_allocate_resource( if inner_assignment is not None and inner_assignment != inner_pin: raise BadUserAssignError(f"invalid assignment to {port_name}.{inner_name}: {inner_assignment}") - inner_pin_map[inner_name] = (inner_pin, resource.inner_resources[inner_name]) + inner_pin_map[inner_name] = (inner_pin, resource.inner_pinnames[inner_name]) inner_sub_assignments.check_empty() sub_assignments.check_empty() From 6e77acff62611da93de34a7816cd3d0a61ca394e Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Fri, 29 May 2026 00:52:59 -0700 Subject: [PATCH 28/28] wip utility --- edg/abstract_parts/IoController.py | 37 +++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/edg/abstract_parts/IoController.py b/edg/abstract_parts/IoController.py index b3b16170d..7dc2002eb 100644 --- a/edg/abstract_parts/IoController.py +++ b/edg/abstract_parts/IoController.py @@ -207,7 +207,8 @@ def _export_tap_ios_inner(self, inner: "BaseIoController") -> None: self.export_tap(self_io, inner_io) def _generator_param_all_ios(self) -> None: - """Declares all BaseIoController IOs as generator params. This must be a GeneratorBlock.""" + """Declares all BaseIoController IOs as generator params. + This must be a GeneratorBlock.""" assert isinstance(self, GeneratorBlock) for io_port in self._io_ports: if isinstance(io_port, Vector): @@ -217,6 +218,40 @@ def _generator_param_all_ios(self) -> None: else: raise NotImplementedError(f"unknown port type {io_port}") + def _generator_pin_dict(self) -> Dict[str, Port]: + """Returns a dict of pin name to port for all IO ports, recursing into bundles Ports. + This includes both the bundle container Port and their (recursive) contents. + Users of this may want to filter by the port type. + + For Vector-typed IO ports, this generates the subports and must be authoritative. + This cannot be used with anything else that generates vector sub-ports. + This must be a GeneratorBlock.""" + assert isinstance(self, GeneratorBlock) + + pin_dict: Dict[str, Port] = {} + + def recurse_port(port: Port, prefix: str = "") -> None: + assert prefix not in pin_dict, f"duplicate pin name {prefix}" + pin_dict[prefix] = port + + for subport_name, subport in port._ports.items(): + recurse_port(subport, f"{prefix}.{subport_name}") + + for io_port in self._io_ports: + if isinstance(io_port, Vector): + io_port.defined() + for subport_name in self.get(io_port.requested()): + subport = io_port.append_elt(io_port._tpe.empty(), subport_name) + recurse_port(subport, subport_name) + elif isinstance(io_port, Port): + if self.get(io_port.is_connected()): + port_name = io_port._name_from(self) + recurse_port(io_port, port_name) + else: + raise NotImplementedError(f"unknown port type {io_port}") + + return pin_dict + @staticmethod def _instantiate_from( ios: List[BasePort], allocations: List[AllocatedResource]