mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-09-01 02:09:06 +00:00
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:
@@ -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.
|
||||
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user