Puya PY32F MCU support (#5106)

* pinout yamls removed

* machine: implement default UART pin configuration for Embedfire boards

* flash command moved to chip level so VS Code plugin can select bare chip

* machine: add support for alternate pin mode configuration

* add build targets for embedfire on py32

* add support for embedfire target in GNUmakefile for py32

* lib/py32-svd: remove duplicate DBGMCU.IDCODE CODE field

Update submodule to fix duplicate SetIDCODE/GetIDCODE method
declarations in the generated py32f002bxx.go device file.

* update subproject commit reference in py32-svd

* refactor: update CPU frequency handling in machine and runtime packages

* refactor: replace ConfigureUARTPin function with direct pin configuration in UART setup

* SetAltFunc documentation for GPIO pin alternate functions

* refactor: improve UART write and flush error handling with timeout

* machine/py32: generalize UART driver to any USART

Add a per-instance setup func to the UART type so the driver is no longer
hardwired to USART1. DefaultUART stays USART1; add UART2 (USART2) in a
separately build-tagged file since py32f002x parts lack USART2. RX IRQ
handlers reference runtime-assigned vars to break the init cycle, and setup
is assigned in the var initializer so it runs before InitSerial.

Add the py32f003_32k_4k target (32K flash / 4K RAM).

* Add canonical PY32F002, F003, and F030 density targets

Use CMSIS/pyocd device names for target files and build tags. Add all F003 and F030 densities plus F002A/B, correct F002B to 24K flash and its own startup file, fix the F030x8 pyocd target, and update Embedfire inheritance.

* Add targets for all PY32 CMSIS devices

* Support all PY32 register layout variants

* machine/py32: use generated register definitions

* machine/py32: fix alternate-function documentation

* machine/py32: report the configured CPU frequency

* machine/py32: use unsafe.Add for GPIO ports

* machine/py32: bound and yield UART polling

* machine/py32: configure UART pins from UARTConfig

* build: use the current PY32 SVD repository URL

* test: cover PY32 UART register layouts

* machine/py32: restore dynamic CPU frequency tracking

* machine/py32: share the USART TX-ready bit

* targets/py32: scale system stacks with RAM

* machine/py32: keep clock state internal

* targets/py32: normalize generated metadata

* machine/py32: normalize GPIO configuration

* machine/py32: fix T020 UART setup

* machine/py32: reduce clock variant files

* machine/py32: clean up UART variants

* machine/py32: decouple UART clock capability

* lib/py32-svd: use organization repository

* runtime/py32: name 24MHz HSI encodings

* machine/py32: derive startup clock from capability

* machine/py32: use generated USART clock mask

* machine/py32: name T020 8-bit UART mode

* ci: include PY32 in sharded smoke tests

* runtime: remove PY32 HSI frequency workaround
This commit is contained in:
Pavel Burgr
2026-08-29 08:41:43 +02:00
committed by GitHub
parent f6e502e121
commit b420a8be17
272 changed files with 3744 additions and 9 deletions
+3 -3
View File
@@ -668,12 +668,12 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre
// try use bitRange
// example string: "[20:16]"
parts := strings.Split(strings.Trim(*fieldEl.BitRange, "[]"), ":")
l, err := strconv.ParseUint(parts[1], 0, 32)
l, err := strconv.ParseUint(parts[1], 10, 32)
if err != nil {
panic(err)
}
lsb = uint32(l)
m, err := strconv.ParseUint(parts[0], 0, 32)
m, err := strconv.ParseUint(parts[0], 10, 32)
if err != nil {
panic(err)
}
@@ -1281,7 +1281,7 @@ const (
{{- "\n"}}
{{- end}}
// Highest interrupt number on this device.
IRQ_max = {{.interruptMax}}
IRQ_max = {{.interruptMax}}
)
// Pseudo function call that is replaced by the compiler with the actual
+59
View File
@@ -0,0 +1,59 @@
# PY32 target generator
This command generates TinyGo target JSON and linker scripts for Puya PY32
microcontrollers. Run it from the repository root:
```sh
make gen-target-py32
```
The normalized [device table](devices.csv) was derived from the `<device>` and
`<variant>` leaves in the official Puya CMSIS-Pack PDSC files. Memory values are
the inherited default `IROM1` and `IRAM1` regions. The three PY32E407 parts have
multiple adjacent default RAM regions; their linker definitions combine those
regions into one contiguous RAM range.
The table contains 87 concrete devices associated with 40 SVD families. The
pack also supplies `PY32F001xx.svd` without a corresponding PDSC device, so the
generator creates its family target but cannot create a concrete memory target.
Together these inputs produce family targets for all 41 available SVDs.
Cortex-M0+ families inherit `py32`; PY32E407, PY32F403, and PY32F410 families
inherit `py32-m4`, which uses TinyGo's standard soft-float Cortex-M4 ABI. Targets
whose SVD has no `GPIO.AFRH` register receive the `py32_no_gpio_afrh` build tag.
Puya's SVDs are inconsistent about GPIO `groupName`: some describe identical
ports as `GPIOA_Type`, `GPIOB_Type`, and so on, while others use one
`GPIO_Type`. The `py32-svd` updater validates the register structures and
patches the published SVDs to use the common `GPIO` group. This is a correction
to the vendor metadata, not a target capability, so neither TinyGo's generic
SVD generator nor the target build tags need PY32-specific handling for it.
Real register-layout differences are selected by generated capability tags.
These cover the `OSPEEDR`/`OSPDDER` GPIO spelling, RCC GPIO and UART clock
registers, `USART` versus `UART` blocks, split USART receive/transmit data
registers, and HSI selector availability. Keep these classifications in the
target generator rather than adding long family expressions to machine files.
All concrete target JSON files and linker scripts are generated so inheritance,
memory definitions, and stack sizes remain consistent. Verified flashing
commands are preserved in the generator; other targets omit them until a
compatible programmer identifier is known.
The system and interrupt stack is sized from available RAM: targets with up to
4 KiB use 1 KiB, targets with up to 16 KiB use 2 KiB, and larger targets use
4 KiB. This linker-defined stack is distinct from goroutine fallback stacks
selected by TinyGo's `--stack-size` option.
## Machine support status
A minimal program compiles for all 87 concrete targets. GPIO, RCC, runtime
clock setup, and the default serial block are selected according to each SVD's
register layout. This is compile-time coverage, not hardware validation for
every device. In particular, PY32F410 SVD interrupt metadata stops before the
serial interrupts, so its default USART is configured without receive
interrupts rather than assigning an unverified IRQ number. The M4 SVDs also do
not expose the M0+ `ICSCR.HSI_FS` selector, so the runtime preserves their reset
clock configuration and initially reports the vendor-defined 8 MHz reset
frequency. Custom clock HALs must update TinyGo's internal clock state, then
reconfigure SysTick and frequency-dependent peripherals.
+88
View File
@@ -0,0 +1,88 @@
part,family,core,flash_start,flash_size,ram_start,ram_size
py32e407xc,py32e407xx,m4,0x08000000,0x40000,0x20000000,0x1c000
py32e407xd,py32e407xx,m4,0x08000000,0x60000,0x20000000,0x24000
py32e407xe,py32e407xx,m4,0x08000000,0x80000,0x20000000,0x24000
py32f001cx4,py32f001cxx,m0,0x08000000,0x4000,0x20000000,0xc00
py32f002ax5,py32f002axx,m0,0x08000000,0x5000,0x20000000,0xc00
py32f002bx5,py32f002bxx,m0,0x08000000,0x6000,0x20000000,0xc00
py32f002cx5,py32f002cxx,m0,0x08000000,0x8000,0x20000000,0xc00
py32f002xx2,py32f002xxx,m0,0x08000000,0x2000,0x20000000,0xc00
py32f002xx4,py32f002xxx,m0,0x08000000,0x4000,0x20000000,0xc00
py32f002xx5,py32f002xxx,m0,0x08000000,0x6000,0x20000000,0xc00
py32f002zx4,py32f002zxx,m0,0x08000000,0x4000,0x20000000,0xc00
py32f003x4,py32f003xx,m0,0x08000000,0x4000,0x20000000,0x800
py32f003x6,py32f003xx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f003x7,py32f003xx,m0,0x08000000,0xc000,0x20000000,0x1800
py32f003x8,py32f003xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f021x8,py32f021xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f030x4,py32f030xx,m0,0x08000000,0x4000,0x20000000,0x800
py32f030x6,py32f030xx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f030x7,py32f030xx,m0,0x08000000,0xc000,0x20000000,0x1800
py32f030x8,py32f030xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f031x4,py32f031xx,m0,0x08000000,0x4000,0x20000000,0x800
py32f031x6,py32f031xx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f031x7,py32f031xx,m0,0x08000000,0xc000,0x20000000,0x1800
py32f031x8,py32f031xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f032x8,py32f032xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f040cx6,py32f040cxx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f040cx8,py32f040cxx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f040cx9,py32f040cxx,m0,0x08000000,0x18000,0x20000000,0x3000
py32f040cxb,py32f040cxx,m0,0x08000000,0x20000,0x20000000,0x4000
py32f040epxb,py32f040epxx,m0,0x08000000,0x20000,0x20000000,0x4000
py32f040ex6,py32f040exx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f040ex8,py32f040exx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f040ex9,py32f040exx,m0,0x08000000,0x18000,0x20000000,0x3000
py32f040exb,py32f040exx,m0,0x08000000,0x20000,0x20000000,0x4000
py32f040x6,py32f040xx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f040x8,py32f040xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f040x9,py32f040xx,m0,0x08000000,0x18000,0x20000000,0x3000
py32f040xb,py32f040xx,m0,0x08000000,0x20000,0x20000000,0x4000
py32f071cx6,py32f071cxx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f071cx8,py32f071cxx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f071cx9,py32f071cxx,m0,0x08000000,0x18000,0x20000000,0x3000
py32f071cxb,py32f071cxx,m0,0x08000000,0x20000,0x20000000,0x4000
py32f071ex6,py32f071exx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f071ex8,py32f071exx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f071ex9,py32f071exx,m0,0x08000000,0x18000,0x20000000,0x3000
py32f071exb,py32f071exx,m0,0x08000000,0x20000,0x20000000,0x4000
py32f071x6,py32f071xx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f071x8,py32f071xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f071x9,py32f071xx,m0,0x08000000,0x18000,0x20000000,0x3000
py32f071xb,py32f071xx,m0,0x08000000,0x20000,0x20000000,0x4000
py32f072cx6,py32f072cxx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f072cx8,py32f072cxx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f072cx9,py32f072cxx,m0,0x08000000,0x18000,0x20000000,0x3000
py32f072cxb,py32f072cxx,m0,0x08000000,0x20000,0x20000000,0x4000
py32f072ex6,py32f072exx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f072ex8,py32f072exx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f072ex9,py32f072exx,m0,0x08000000,0x18000,0x20000000,0x3000
py32f072exb,py32f072exx,m0,0x08000000,0x20000,0x20000000,0x4000
py32f072x6,py32f072xx,m0,0x08000000,0x8000,0x20000000,0x1000
py32f072x8,py32f072xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32f072x9,py32f072xx,m0,0x08000000,0x18000,0x20000000,0x3000
py32f072xb,py32f072xx,m0,0x08000000,0x20000,0x20000000,0x4000
py32f403xb,py32f403xx,m4,0x08000000,0x20000,0x20000000,0x10000
py32f403xc,py32f403xx,m4,0x08000000,0x40000,0x20000000,0x10000
py32f403xd,py32f403xx,m4,0x08000000,0x60000,0x20000000,0x10000
py32f410xb,py32f410xx,m4,0x08000000,0x20000,0x20000000,0x4000
py32l020x5,py32l020xx,m0,0x08000000,0x6000,0x20000000,0xc00
py32l090xb,py32l090xx,m0,0x08000000,0x20000,0x20000000,0x4000
py32l090xc,py32l090xx,m0,0x08000000,0x40000,0x20000000,0x8000
py32m010x5,py32m010xx,m0,0x08000000,0x6000,0x20000000,0xc00
py32m020x6,py32m020xx,m0,0x08000000,0x8000,0x20000000,0x1000
py32m030x8,py32m030xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32m031x8,py32m031xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32m070cxb,py32m070cxx,m0,0x08000000,0x20000,0x20000000,0x4000
py32m070xb,py32m070xx,m0,0x08000000,0x20000,0x20000000,0x4000
py32md310x8,py32md310xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32md320x8,py32md320xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32md410x8,py32md410xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32md420x8,py32md420xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32md430x8,py32md430xx,m0,0x08000000,0x10000,0x20000000,0x2000
py32t020bx5,py32t020xx,m0,0x08000000,0x5000,0x20000000,0x800
py32t020bx6,py32t020xx,m0,0x08000000,0x8000,0x20000000,0x1000
py32t020x5,py32t020xx,m0,0x08000000,0x5000,0x20000000,0x800
py32t020x6,py32t020xx,m0,0x08000000,0x8000,0x20000000,0x1000
py32t090xb,py32t090xx,m0,0x08000000,0x20000,0x20000000,0x4000
py32t090xc,py32t090xx,m0,0x08000000,0x40000,0x20000000,0x8000
py32t092xc,py32t092xx,m0,0x08000000,0x40000,0x20000000,0x8000
1 part family core flash_start flash_size ram_start ram_size
2 py32e407xc py32e407xx m4 0x08000000 0x40000 0x20000000 0x1c000
3 py32e407xd py32e407xx m4 0x08000000 0x60000 0x20000000 0x24000
4 py32e407xe py32e407xx m4 0x08000000 0x80000 0x20000000 0x24000
5 py32f001cx4 py32f001cxx m0 0x08000000 0x4000 0x20000000 0xc00
6 py32f002ax5 py32f002axx m0 0x08000000 0x5000 0x20000000 0xc00
7 py32f002bx5 py32f002bxx m0 0x08000000 0x6000 0x20000000 0xc00
8 py32f002cx5 py32f002cxx m0 0x08000000 0x8000 0x20000000 0xc00
9 py32f002xx2 py32f002xxx m0 0x08000000 0x2000 0x20000000 0xc00
10 py32f002xx4 py32f002xxx m0 0x08000000 0x4000 0x20000000 0xc00
11 py32f002xx5 py32f002xxx m0 0x08000000 0x6000 0x20000000 0xc00
12 py32f002zx4 py32f002zxx m0 0x08000000 0x4000 0x20000000 0xc00
13 py32f003x4 py32f003xx m0 0x08000000 0x4000 0x20000000 0x800
14 py32f003x6 py32f003xx m0 0x08000000 0x8000 0x20000000 0x1000
15 py32f003x7 py32f003xx m0 0x08000000 0xc000 0x20000000 0x1800
16 py32f003x8 py32f003xx m0 0x08000000 0x10000 0x20000000 0x2000
17 py32f021x8 py32f021xx m0 0x08000000 0x10000 0x20000000 0x2000
18 py32f030x4 py32f030xx m0 0x08000000 0x4000 0x20000000 0x800
19 py32f030x6 py32f030xx m0 0x08000000 0x8000 0x20000000 0x1000
20 py32f030x7 py32f030xx m0 0x08000000 0xc000 0x20000000 0x1800
21 py32f030x8 py32f030xx m0 0x08000000 0x10000 0x20000000 0x2000
22 py32f031x4 py32f031xx m0 0x08000000 0x4000 0x20000000 0x800
23 py32f031x6 py32f031xx m0 0x08000000 0x8000 0x20000000 0x1000
24 py32f031x7 py32f031xx m0 0x08000000 0xc000 0x20000000 0x1800
25 py32f031x8 py32f031xx m0 0x08000000 0x10000 0x20000000 0x2000
26 py32f032x8 py32f032xx m0 0x08000000 0x10000 0x20000000 0x2000
27 py32f040cx6 py32f040cxx m0 0x08000000 0x8000 0x20000000 0x1000
28 py32f040cx8 py32f040cxx m0 0x08000000 0x10000 0x20000000 0x2000
29 py32f040cx9 py32f040cxx m0 0x08000000 0x18000 0x20000000 0x3000
30 py32f040cxb py32f040cxx m0 0x08000000 0x20000 0x20000000 0x4000
31 py32f040epxb py32f040epxx m0 0x08000000 0x20000 0x20000000 0x4000
32 py32f040ex6 py32f040exx m0 0x08000000 0x8000 0x20000000 0x1000
33 py32f040ex8 py32f040exx m0 0x08000000 0x10000 0x20000000 0x2000
34 py32f040ex9 py32f040exx m0 0x08000000 0x18000 0x20000000 0x3000
35 py32f040exb py32f040exx m0 0x08000000 0x20000 0x20000000 0x4000
36 py32f040x6 py32f040xx m0 0x08000000 0x8000 0x20000000 0x1000
37 py32f040x8 py32f040xx m0 0x08000000 0x10000 0x20000000 0x2000
38 py32f040x9 py32f040xx m0 0x08000000 0x18000 0x20000000 0x3000
39 py32f040xb py32f040xx m0 0x08000000 0x20000 0x20000000 0x4000
40 py32f071cx6 py32f071cxx m0 0x08000000 0x8000 0x20000000 0x1000
41 py32f071cx8 py32f071cxx m0 0x08000000 0x10000 0x20000000 0x2000
42 py32f071cx9 py32f071cxx m0 0x08000000 0x18000 0x20000000 0x3000
43 py32f071cxb py32f071cxx m0 0x08000000 0x20000 0x20000000 0x4000
44 py32f071ex6 py32f071exx m0 0x08000000 0x8000 0x20000000 0x1000
45 py32f071ex8 py32f071exx m0 0x08000000 0x10000 0x20000000 0x2000
46 py32f071ex9 py32f071exx m0 0x08000000 0x18000 0x20000000 0x3000
47 py32f071exb py32f071exx m0 0x08000000 0x20000 0x20000000 0x4000
48 py32f071x6 py32f071xx m0 0x08000000 0x8000 0x20000000 0x1000
49 py32f071x8 py32f071xx m0 0x08000000 0x10000 0x20000000 0x2000
50 py32f071x9 py32f071xx m0 0x08000000 0x18000 0x20000000 0x3000
51 py32f071xb py32f071xx m0 0x08000000 0x20000 0x20000000 0x4000
52 py32f072cx6 py32f072cxx m0 0x08000000 0x8000 0x20000000 0x1000
53 py32f072cx8 py32f072cxx m0 0x08000000 0x10000 0x20000000 0x2000
54 py32f072cx9 py32f072cxx m0 0x08000000 0x18000 0x20000000 0x3000
55 py32f072cxb py32f072cxx m0 0x08000000 0x20000 0x20000000 0x4000
56 py32f072ex6 py32f072exx m0 0x08000000 0x8000 0x20000000 0x1000
57 py32f072ex8 py32f072exx m0 0x08000000 0x10000 0x20000000 0x2000
58 py32f072ex9 py32f072exx m0 0x08000000 0x18000 0x20000000 0x3000
59 py32f072exb py32f072exx m0 0x08000000 0x20000 0x20000000 0x4000
60 py32f072x6 py32f072xx m0 0x08000000 0x8000 0x20000000 0x1000
61 py32f072x8 py32f072xx m0 0x08000000 0x10000 0x20000000 0x2000
62 py32f072x9 py32f072xx m0 0x08000000 0x18000 0x20000000 0x3000
63 py32f072xb py32f072xx m0 0x08000000 0x20000 0x20000000 0x4000
64 py32f403xb py32f403xx m4 0x08000000 0x20000 0x20000000 0x10000
65 py32f403xc py32f403xx m4 0x08000000 0x40000 0x20000000 0x10000
66 py32f403xd py32f403xx m4 0x08000000 0x60000 0x20000000 0x10000
67 py32f410xb py32f410xx m4 0x08000000 0x20000 0x20000000 0x4000
68 py32l020x5 py32l020xx m0 0x08000000 0x6000 0x20000000 0xc00
69 py32l090xb py32l090xx m0 0x08000000 0x20000 0x20000000 0x4000
70 py32l090xc py32l090xx m0 0x08000000 0x40000 0x20000000 0x8000
71 py32m010x5 py32m010xx m0 0x08000000 0x6000 0x20000000 0xc00
72 py32m020x6 py32m020xx m0 0x08000000 0x8000 0x20000000 0x1000
73 py32m030x8 py32m030xx m0 0x08000000 0x10000 0x20000000 0x2000
74 py32m031x8 py32m031xx m0 0x08000000 0x10000 0x20000000 0x2000
75 py32m070cxb py32m070cxx m0 0x08000000 0x20000 0x20000000 0x4000
76 py32m070xb py32m070xx m0 0x08000000 0x20000 0x20000000 0x4000
77 py32md310x8 py32md310xx m0 0x08000000 0x10000 0x20000000 0x2000
78 py32md320x8 py32md320xx m0 0x08000000 0x10000 0x20000000 0x2000
79 py32md410x8 py32md410xx m0 0x08000000 0x10000 0x20000000 0x2000
80 py32md420x8 py32md420xx m0 0x08000000 0x10000 0x20000000 0x2000
81 py32md430x8 py32md430xx m0 0x08000000 0x10000 0x20000000 0x2000
82 py32t020bx5 py32t020xx m0 0x08000000 0x5000 0x20000000 0x800
83 py32t020bx6 py32t020xx m0 0x08000000 0x8000 0x20000000 0x1000
84 py32t020x5 py32t020xx m0 0x08000000 0x5000 0x20000000 0x800
85 py32t020x6 py32t020xx m0 0x08000000 0x8000 0x20000000 0x1000
86 py32t090xb py32t090xx m0 0x08000000 0x20000 0x20000000 0x4000
87 py32t090xc py32t090xx m0 0x08000000 0x40000 0x20000000 0x8000
88 py32t092xc py32t092xx m0 0x08000000 0x40000 0x20000000 0x8000
+254
View File
@@ -0,0 +1,254 @@
// Command gen-py32-targets generates PY32 target and linker definitions from
// normalized CMSIS-Pack device metadata.
package main
import (
"encoding/csv"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
)
type device struct {
Part string
Family string
Core string
FlashStart uint64
FlashSize uint64
RAMStart uint64
RAMSize uint64
}
type target struct {
Inherits []string `json:"inherits"`
BuildTags []string `json:"build-tags,omitempty"`
LinkerScript string `json:"linkerscript,omitempty"`
ExtraFiles []string `json:"extra-files,omitempty"`
FlashCommand string `json:"flash-command,omitempty"`
}
var orphanFamilies = map[string]string{
// This SVD is present in PY32F0xx_DFP, but that pack has no matching PDSC
// device entry from which a concrete memory configuration can be derived.
"py32f001xx": "m0",
}
var noGPIOAFRH = map[string]bool{
"py32f001cxx": true,
"py32f002bxx": true,
"py32f002cxx": true,
"py32f002xxx": true,
"py32f002zxx": true,
"py32l020xx": true,
"py32m010xx": true,
}
var gpioOSPDDER = familySet("py32e407xx", "py32f410xx", "py32l090xx", "py32t090xx", "py32t092xx")
var gpioClockAHB2 = familySet("py32e407xx", "py32f403xx")
var gpioClockAHB = familySet("py32f410xx")
var hsiFSOP = familySet("py32l090xx", "py32t090xx", "py32t092xx")
var noHSIFS = familySet("py32e407xx", "py32f403xx", "py32f410xx")
var uartType = familySet("py32t020xx")
var usartSplitData = familySet("py32e407xx")
var usartUnnumbered = familySet("py32f410xx")
var usart1ClockLiteral = familySet("py32f001cxx", "py32f001xx")
var uartClockAPB2 = familySet("py32e407xx", "py32f403xx", "py32f410xx")
var uartNoInterrupt = familySet("py32f410xx")
func familySet(names ...string) map[string]bool {
set := make(map[string]bool, len(names))
for _, name := range names {
set[name] = true
}
return set
}
func familyBuildTags(family string) []string {
tags := []string{family}
features := []struct {
name string
set map[string]bool
}{
{"py32_no_gpio_afrh", noGPIOAFRH},
{"py32_gpio_ospdder", gpioOSPDDER},
{"py32_gpio_clock_ahb2", gpioClockAHB2},
{"py32_gpio_clock_ahb", gpioClockAHB},
{"py32_hsi_fs_op", hsiFSOP},
{"py32_no_hsi_fs", noHSIFS},
{"py32_uart_type", uartType},
{"py32_usart_split_data", usartSplitData},
{"py32_usart_unnumbered", usartUnnumbered},
{"py32_usart1_clock_literal", usart1ClockLiteral},
{"py32_uart_clock_apb2", uartClockAPB2},
{"py32_uart_no_interrupt", uartNoInterrupt},
}
for _, feature := range features {
if feature.set[family] {
tags = append(tags, feature.name)
}
}
return tags
}
var flashCommands = map[string]string{
"py32f002ax5": "pyocd load -t py32f002ax5 {bin}",
"py32f002bx5": "pyocd load -t py32f002bx5 {bin}",
"py32f003x4": "pyocd load -t py32f003x4 {bin}",
"py32f003x6": "pyocd load -t py32f003x6 {bin}",
"py32f003x7": "pyocd load -t py32f003x7 {bin}",
"py32f003x8": "pyocd load -t py32f003x8 {bin}",
"py32f030x4": "pyocd load -t py32f030x4 {bin}",
"py32f030x6": "pyocd load -t py32f030x6 {bin}",
"py32f030x7": "pyocd load -t py32f030x7 {bin}",
"py32f030x8": "pyocd load -t py32f030x8 {bin}",
}
func main() {
table := flag.String("table", "tools/gen-py32-targets/devices.csv", "normalized device table")
out := flag.String("out", "targets", "target output directory")
flag.Parse()
devices, err := readDevices(*table)
if err != nil {
fatal(err)
}
if err := generate(*out, devices); err != nil {
fatal(err)
}
}
func fatal(err error) {
fmt.Fprintln(os.Stderr, "gen-py32-targets:", err)
os.Exit(1)
}
func readDevices(path string) ([]device, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
reader := csv.NewReader(file)
header, err := reader.Read()
if err != nil {
return nil, err
}
wantHeader := []string{"part", "family", "core", "flash_start", "flash_size", "ram_start", "ram_size"}
if strings.Join(header, ",") != strings.Join(wantHeader, ",") {
return nil, fmt.Errorf("unexpected CSV header %q", strings.Join(header, ","))
}
var devices []device
seen := make(map[string]bool)
for line := 2; ; line++ {
record, err := reader.Read()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return nil, fmt.Errorf("line %d: %w", line, err)
}
if seen[record[0]] {
return nil, fmt.Errorf("line %d: duplicate part %q", line, record[0])
}
seen[record[0]] = true
if record[2] != "m0" && record[2] != "m4" {
return nil, fmt.Errorf("line %d: unsupported core %q", line, record[2])
}
values := make([]uint64, 4)
for i, field := range record[3:] {
values[i], err = strconv.ParseUint(field, 0, 64)
if err != nil || values[i] == 0 {
return nil, fmt.Errorf("line %d: invalid memory value %q", line, field)
}
}
devices = append(devices, device{
Part: record[0], Family: record[1], Core: record[2],
FlashStart: values[0], FlashSize: values[1],
RAMStart: values[2], RAMSize: values[3],
})
}
return devices, nil
}
func systemStackSize(ramSize uint64) uint64 {
switch {
case ramSize <= 4*1024:
return 1024
case ramSize <= 16*1024:
return 2 * 1024
default:
return 4 * 1024
}
}
func generate(out string, devices []device) error {
if err := os.MkdirAll(out, 0o755); err != nil {
return err
}
families := make(map[string]string, len(orphanFamilies))
for name, core := range orphanFamilies {
families[name] = core
}
for _, device := range devices {
if core, ok := families[device.Family]; ok && core != device.Core {
return fmt.Errorf("family %q mixes %s and %s cores", device.Family, core, device.Core)
}
families[device.Family] = device.Core
}
familyNames := make([]string, 0, len(families))
for name := range families {
familyNames = append(familyNames, name)
}
sort.Strings(familyNames)
for _, family := range familyNames {
base := "py32"
if families[family] == "m4" {
base = "py32-m4"
}
spec := target{
Inherits: []string{base},
BuildTags: familyBuildTags(family),
ExtraFiles: []string{"src/device/py32/" + family + ".s"},
}
if err := writeJSON(filepath.Join(out, family+".json"), spec); err != nil {
return err
}
}
for _, device := range devices {
spec := target{
Inherits: []string{device.Family},
BuildTags: []string{device.Part},
LinkerScript: "targets/" + device.Part + ".ld",
FlashCommand: flashCommands[device.Part],
}
if err := writeJSON(filepath.Join(out, device.Part+".json"), spec); err != nil {
return err
}
stackSize := systemStackSize(device.RAMSize)
linker := fmt.Sprintf("MEMORY\n{\n FLASH_TEXT (rx) : ORIGIN = %#x, LENGTH = %#x\n RAM (xrw) : ORIGIN = %#x, LENGTH = %#x\n}\n\n_stack_size = %dK;\n\nINCLUDE \"targets/arm.ld\"\n", device.FlashStart, device.FlashSize, device.RAMStart, device.RAMSize, stackSize/1024)
if err := os.WriteFile(filepath.Join(out, device.Part+".ld"), []byte(linker), 0o644); err != nil {
return err
}
}
return nil
}
func writeJSON(path string, value any) error {
data, err := json.MarshalIndent(value, "", " ")
if err != nil {
return err
}
data = append(data, '\n')
return os.WriteFile(path, data, 0o644)
}
+158
View File
@@ -0,0 +1,158 @@
package main
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
func TestDeviceTable(t *testing.T) {
devices, err := readDevices("devices.csv")
if err != nil {
t.Fatal(err)
}
if got, want := len(devices), 87; got != want {
t.Fatalf("got %d concrete devices, want %d", got, want)
}
families := make(map[string]bool)
for family := range orphanFamilies {
families[family] = true
}
m4 := 0
for _, device := range devices {
families[device.Family] = true
if device.Core == "m4" {
m4++
}
}
if got, want := len(families), 41; got != want {
t.Fatalf("got %d SVD families, want %d", got, want)
}
if got, want := m4, 7; got != want {
t.Fatalf("got %d Cortex-M4 devices, want %d", got, want)
}
}
func TestSystemStackSize(t *testing.T) {
tests := []struct {
ramSize uint64
want uint64
}{
{2 * 1024, 1024},
{4 * 1024, 1024},
{4*1024 + 1, 2 * 1024},
{16 * 1024, 2 * 1024},
{16*1024 + 1, 4 * 1024},
{144 * 1024, 4 * 1024},
}
for _, test := range tests {
if got := systemStackSize(test.ramSize); got != test.want {
t.Errorf("systemStackSize(%d) = %d, want %d", test.ramSize, got, test.want)
}
}
}
func TestGenerate(t *testing.T) {
devices, err := readDevices("devices.csv")
if err != nil {
t.Fatal(err)
}
out := t.TempDir()
if err := generate(out, devices); err != nil {
t.Fatal(err)
}
entries, err := os.ReadDir(out)
if err != nil {
t.Fatal(err)
}
// 41 family JSON files, 87 generated concrete JSON files, and linker
// scripts for all 87 concrete targets.
if got, want := len(entries), 215; got != want {
t.Fatalf("generated %d files, want %d", got, want)
}
checkTarget(t, filepath.Join(out, "py32e407xc.json"), "py32e407xx", "targets/py32e407xc.ld", "")
checkTarget(t, filepath.Join(out, "py32f001xx.json"), "py32", "", "")
checkTarget(t, filepath.Join(out, "py32f002ax5.json"), "py32f002axx", "targets/py32f002ax5.ld", "pyocd load -t py32f002ax5 {bin}")
checkTarget(t, filepath.Join(out, "py32f030x8.json"), "py32f030xx", "targets/py32f030x8.ld", "pyocd load -t py32f030x8 {bin}")
checkTarget(t, filepath.Join(out, "py32f410xx.json"), "py32-m4", "", "")
checkBuildTags(t, filepath.Join(out, "py32e407xx.json"), "py32e407xx", "py32_gpio_ospdder", "py32_gpio_clock_ahb2", "py32_no_hsi_fs", "py32_usart_split_data", "py32_uart_clock_apb2")
checkBuildTags(t, filepath.Join(out, "py32f001cxx.json"), "py32f001cxx", "py32_no_gpio_afrh", "py32_usart1_clock_literal")
checkBuildTags(t, filepath.Join(out, "py32f001xx.json"), "py32f001xx", "py32_usart1_clock_literal")
checkBuildTags(t, filepath.Join(out, "py32f002cxx.json"), "py32f002cxx", "py32_no_gpio_afrh")
checkBuildTags(t, filepath.Join(out, "py32f032xx.json"), "py32f032xx")
checkBuildTags(t, filepath.Join(out, "py32f410xx.json"), "py32f410xx", "py32_gpio_ospdder", "py32_gpio_clock_ahb", "py32_no_hsi_fs", "py32_usart_unnumbered", "py32_uart_clock_apb2", "py32_uart_no_interrupt")
checkBuildTags(t, filepath.Join(out, "py32t020xx.json"), "py32t020xx", "py32_uart_type")
data, err := os.ReadFile(filepath.Join(out, "py32e407xc.ld"))
if err != nil {
t.Fatal(err)
}
want := "ORIGIN = 0x20000000, LENGTH = 0x1c000"
if !contains(string(data), want) {
t.Fatalf("E407 linker script does not contain combined contiguous RAM %q", want)
}
checkFileContains(t, filepath.Join(out, "py32f002ax5.ld"), "_stack_size = 1K;")
checkFileContains(t, filepath.Join(out, "py32f030x8.ld"), "_stack_size = 2K;")
checkFileContains(t, filepath.Join(out, "py32l090xc.ld"), "_stack_size = 4K;")
}
func checkFileContains(t *testing.T, path, want string) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(data), want) {
t.Errorf("%s does not contain %q", path, want)
}
}
func checkBuildTags(t *testing.T, path string, want ...string) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var spec target
if err := json.Unmarshal(data, &spec); err != nil {
t.Fatal(err)
}
if got := strings.Join(spec.BuildTags, ","); got != strings.Join(want, ",") {
t.Errorf("%s build tags are %q, want %q", path, got, strings.Join(want, ","))
}
}
func checkTarget(t *testing.T, path, inherited, linker, flashCommand string) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var spec target
if err := json.Unmarshal(data, &spec); err != nil {
t.Fatal(err)
}
if len(spec.Inherits) != 1 || spec.Inherits[0] != inherited {
t.Errorf("%s inherits %v, want %q", path, spec.Inherits, inherited)
}
if spec.LinkerScript != linker {
t.Errorf("%s linker script is %q, want %q", path, spec.LinkerScript, linker)
}
if spec.FlashCommand != flashCommand {
t.Errorf("%s flash command is %q, want %q", path, spec.FlashCommand, flashCommand)
}
}
func contains(value, substring string) bool {
for i := 0; i+len(substring) <= len(value); i++ {
if value[i:i+len(substring)] == substring {
return true
}
}
return false
}