diff --git a/.circleci/config.yml b/.circleci/config.yml index 8eea387e..c876d1f0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,16 +44,28 @@ commands: command: | wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo apt install ./google-chrome-stable_current_amd64.deb + install-xtensa-toolchain: + parameters: + variant: + type: string + steps: + - run: + name: "Install Xtensa toolchain" + command: | + curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<>.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<>.tar.gz + sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<>.tar.gz + sudo ln -s /usr/local/xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/xtensa-esp32-elf-ld + rm xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<>.tar.gz llvm-source-linux: steps: - restore_cache: keys: - - llvm-source-10-v0 + - llvm-source-10-v1 - run: name: "Fetch LLVM source" command: make llvm-source - save_cache: - key: llvm-source-10-v0 + key: llvm-source-10-v1 paths: - llvm-project build-wasi-libc: @@ -95,7 +107,7 @@ commands: - lib/wasi-libc/sysroot - run: go test -v -tags=llvm<> ./cgo ./compileopts ./interp ./transform . - run: make gen-device -j4 - - run: make smoketest + - run: make smoketest XTENSA=0 - run: make wasmtest - save_cache: key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} @@ -121,6 +133,8 @@ commands: gcc-avr \ avr-libc - install-node + - install-xtensa-toolchain: + variant: "linux-amd64" - restore_cache: keys: - go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} @@ -128,7 +142,7 @@ commands: - llvm-source-linux - restore_cache: keys: - - llvm-build-10-linux-v0-assert + - llvm-build-10-linux-v1-assert - run: name: "Build LLVM" command: | @@ -146,7 +160,7 @@ commands: make ASSERT=1 llvm-build fi - save_cache: - key: llvm-build-10-linux-v0-assert + key: llvm-build-10-linux-v1-assert paths: llvm-build - run: make ASSERT=1 @@ -179,6 +193,8 @@ commands: gcc-avr \ avr-libc - install-node + - install-xtensa-toolchain: + variant: "linux-amd64" - restore_cache: keys: - go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} @@ -186,7 +202,7 @@ commands: - llvm-source-linux - restore_cache: keys: - - llvm-build-10-linux-v0 + - llvm-build-10-linux-v1 - run: name: "Build LLVM" command: | @@ -204,7 +220,7 @@ commands: make llvm-build fi - save_cache: - key: llvm-build-10-linux-v0 + key: llvm-build-10-linux-v1 paths: llvm-build - build-wasi-libc @@ -250,23 +266,25 @@ commands: sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz ln -s /usr/local/go/bin/go /usr/local/bin/go HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu + - install-xtensa-toolchain: + variant: "macos" - restore_cache: keys: - go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-macos-v2-{{ checksum "go.mod" }} - restore_cache: keys: - - llvm-source-10-macos-v0 + - llvm-source-10-macos-v1 - run: name: "Fetch LLVM source" command: make llvm-source - save_cache: - key: llvm-source-10-macos-v0 + key: llvm-source-10-macos-v1 paths: - llvm-project - restore_cache: keys: - - llvm-build-10-macos-v0 + - llvm-build-10-macos-v1 - run: name: "Build LLVM" command: | @@ -278,7 +296,7 @@ commands: make llvm-build fi - save_cache: - key: llvm-build-10-macos-v0 + key: llvm-build-10-macos-v1 paths: llvm-build - restore_cache: diff --git a/.gitignore b/.gitignore index 760bbf0a..4d0f5f6f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ docs/_build src/device/avr/*.go src/device/avr/*.ld src/device/avr/*.s +src/device/esp/*.go src/device/nrf/*.go src/device/nrf/*.s src/device/nxp/*.go diff --git a/Makefile b/Makefile index a13a5914..a8f08cf8 100644 --- a/Makefile +++ b/Makefile @@ -118,7 +118,7 @@ fmt-check: @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 -gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32 gen-device-kendryte gen-device-nxp +gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32 gen-device-kendryte gen-device-nxp gen-device-avr: $(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/ @@ -129,6 +129,10 @@ gen-device-avr: build/gen-device-svd: ./tools/gen-device-svd/*.go $(GO) build -o $@ ./tools/gen-device-svd/ +gen-device-esp: build/gen-device-svd + ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/ + GO111MODULE=off $(GO) fmt ./src/device/esp + gen-device-nrf: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/ GO111MODULE=off $(GO) fmt ./src/device/nrf @@ -156,13 +160,13 @@ gen-device-stm32: build/gen-device-svd # Get LLVM sources. $(LLVM_PROJECTDIR)/README.md: - git clone -b release/10.x --depth=1 https://github.com/llvm/llvm-project $(LLVM_PROJECTDIR) + git clone -b xtensa_release_10.0.1 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR) llvm-source: $(LLVM_PROJECTDIR)/README.md # Configure LLVM. TINYGO_SOURCE_DIR=$(shell pwd) $(LLVM_BUILDDIR)/build.ninja: llvm-source - mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION) + mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION) # Build LLVM. $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja @@ -341,6 +345,9 @@ ifneq ($(AVR), 0) @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1 @$(MD5SUM) test.hex +endif +ifneq ($(XTENSA), 0) + $(TINYGO) build -size short -o test.elf -target=esp32 examples/serial endif $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 @$(MD5SUM) test.hex diff --git a/azure-pipelines.yml b/azure-pipelines.yml index adf44f73..60b4a73d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,20 +14,24 @@ jobs: inputs: version: '1.15' - checkout: self - - task: CacheBeta@0 + - task: Cache@2 displayName: Cache LLVM source inputs: - key: llvm-source-10-windows-v0 + key: llvm-source-10-windows-v1 path: llvm-project - task: Bash@3 displayName: Download LLVM source inputs: targetType: inline - script: make llvm-source + script: | + make llvm-source + # Workaround for bad symlinks: + # https://github.com/microsoft/azure-pipelines-tasks/issues/13418 + rm -f llvm-project/libcxx/test/std/input.output/filesystems/Inputs/static_test_env/bad_symlink - task: CacheBeta@0 displayName: Cache LLVM build inputs: - key: llvm-build-10-windows-v0 + key: llvm-build-10-windows-v1 path: llvm-build - task: Bash@3 displayName: Build LLVM @@ -80,4 +84,4 @@ jobs: script: | export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu" unset GOROOT - make smoketest TINYGO=build/tinygo AVR=0 + make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0 diff --git a/lib/cmsis-svd b/lib/cmsis-svd index a155cfd8..2fc33580 160000 --- a/lib/cmsis-svd +++ b/lib/cmsis-svd @@ -1 +1 @@ -Subproject commit a155cfd832c9e6ddf193244d8d90489f4d089cc7 +Subproject commit 2fc335802cf97309ec4035caf276746b53efbd5b diff --git a/src/device/esp/esp32.S b/src/device/esp/esp32.S new file mode 100644 index 00000000..af8e9d66 --- /dev/null +++ b/src/device/esp/esp32.S @@ -0,0 +1,52 @@ + +// The following definitions were copied from: +// esp-idf/components/xtensa/include/xtensa/corebits.h +#define PS_WOE_MASK 0x00040000 +#define PS_OWB_MASK 0x00000F00 +#define PS_CALLINC_MASK 0x00030000 +#define PS_WOE PS_WOE_MASK + +// Only calling it call_start_cpu0 for consistency with ESP-IDF. +.section .text.call_start_cpu0 +1: + .long _stack_top +.global call_start_cpu0 +call_start_cpu0: + // We need to set the stack pointer to a different value. This is somewhat + // complicated in the Xtensa architecture. The code below is a modified + // version of the following code: + // https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47 + + // Disable WOE. + rsr.ps a2 + movi a3, ~(PS_WOE_MASK) + and a2, a2, a3 + wsr.ps a2 + rsync + + // Set WINDOWBASE to 1 << WINDOWSTART. + rsr.windowbase a2 + ssl a2 + movi a2, 1 + sll a2, a2 + wsr.windowstart a2 + rsync + + // Load new stack pointer. + l32r sp, 1b + + // Re-enable WOE. + rsr.ps a2 + movi a3, PS_WOE + or a2, a2, a3 + wsr.ps a2 + rsync + + // Jump to the runtime start function written in Go. + j main + +.section .text.tinygo_scanCurrentStack +.global tinygo_scanCurrentStack +tinygo_scanCurrentStack: + // TODO: save callee saved registers on the stack + j tinygo_scanstack diff --git a/src/machine/machine_esp32.go b/src/machine/machine_esp32.go new file mode 100644 index 00000000..9501f472 --- /dev/null +++ b/src/machine/machine_esp32.go @@ -0,0 +1,44 @@ +// +build esp32 + +package machine + +import "device/esp" + +const peripheralClock = 80000000 // 80MHz + +type PinMode uint8 + +const ( + PinOutput PinMode = iota + PinInput +) + +func (p Pin) Set(value bool) + +var ( + UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()} + UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()} + UART2 = UART{Bus: esp.UART2, Buffer: NewRingBuffer()} +) + +type UART struct { + Bus *esp.UART_Type + Buffer *RingBuffer +} + +func (uart UART) Configure(config UARTConfig) { + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + uart.Bus.CLKDIV.Set(peripheralClock / config.BaudRate) +} + +func (uart UART) WriteByte(b byte) error { + for (uart.Bus.STATUS.Get()>>16)&0xff >= 128 { + // Read UART_TXFIFO_CNT from the status register, which indicates how + // many bytes there are in the transmit buffer. Wait until there are + // less than 128 bytes in this buffer (the default buffer size). + } + uart.Bus.TX_FIFO.Set(b) + return nil +} diff --git a/src/machine/uart.go b/src/machine/uart.go index c68324b6..40d2d99c 100644 --- a/src/machine/uart.go +++ b/src/machine/uart.go @@ -1,4 +1,4 @@ -// +build avr nrf sam sifive stm32 k210 nxp +// +build avr esp nrf sam sifive stm32 k210 nxp package machine diff --git a/src/runtime/arch_xtensa.go b/src/runtime/arch_xtensa.go new file mode 100644 index 00000000..440521b6 --- /dev/null +++ b/src/runtime/arch_xtensa.go @@ -0,0 +1,15 @@ +// +build xtensa + +package runtime + +const GOARCH = "arm" // xtensa pretends to be arm + +// The bitness of the CPU (e.g. 8, 32, 64). +const TargetBits = 32 + +// Align on a word boundary. +func align(ptr uintptr) uintptr { + return (ptr + 3) &^ 3 +} + +func getCurrentStackPointer() uintptr diff --git a/src/runtime/interrupt/interrupt_xtensa.go b/src/runtime/interrupt/interrupt_xtensa.go new file mode 100644 index 00000000..dfcbc7db --- /dev/null +++ b/src/runtime/interrupt/interrupt_xtensa.go @@ -0,0 +1,31 @@ +// +build xtensa + +package interrupt + +import "device" + +// State represents the previous global interrupt state. +type State uintptr + +// Disable disables all interrupts and returns the previous interrupt state. It +// can be used in a critical section like this: +// +// state := interrupt.Disable() +// // critical section +// interrupt.Restore(state) +// +// Critical sections can be nested. Make sure to call Restore in the same order +// as you called Disable (this happens naturally with the pattern above). +func Disable() (state State) { + return State(device.AsmFull("rsil {}, 15", nil)) +} + +// Restore restores interrupts to what they were before. Give the previous state +// returned by Disable as a parameter. If interrupts were disabled before +// calling Disable, this will not re-enable interrupts, allowing for nested +// cricital sections. +func Restore(state State) { + device.AsmFull("wsr {state}, PS", map[string]interface{}{ + "state": state, + }) +} diff --git a/src/runtime/runtime_esp32.go b/src/runtime/runtime_esp32.go new file mode 100644 index 00000000..6f2ad6de --- /dev/null +++ b/src/runtime/runtime_esp32.go @@ -0,0 +1,126 @@ +// +build esp32 + +package runtime + +import ( + "device" + "device/esp" + "machine" + "unsafe" +) + +type timeUnit int64 + +var currentTime timeUnit + +func putchar(c byte) { + machine.UART0.WriteByte(c) +} + +func postinit() {} + +// This is the function called on startup right after the stack pointer has been +// set. +//export main +func main() { + // Disable both watchdog timers that are enabled by default on startup. + // Note that these watchdogs can be protected, but the ROM bootloader + // doesn't seem to protect them. + esp.RTCCNTL.WDTCONFIG0.Set(0) + esp.TIMG0.WDTCONFIG0.Set(0) + + // Switch SoC clock source to PLL (instead of the default which is XTAL). + // This switches the CPU (and APB) clock from 40MHz to 80MHz. + // Options: + // RTCCNTL_CLK_CONF_SOC_CLK_SEL: PLL (default XTAL) + // RTCCNTL_CLK_CONF_CK8M_DIV_SEL: 2 (default) + // RTCCNTL_CLK_CONF_DIG_CLK8M_D256_EN: Enable (default) + // RTCCNTL_CLK_CONF_CK8M_DIV: DIV256 (default) + // The only real change made here is modifying RTCCNTL_CLK_CONF_SOC_CLK_SEL, + // but setting a fixed value produces smaller code. + esp.RTCCNTL.CLK_CONF.Set((esp.RTCCNTL_CLK_CONF_SOC_CLK_SEL_PLL << esp.RTCCNTL_CLK_CONF_SOC_CLK_SEL_Pos) | + (2 << esp.RTCCNTL_CLK_CONF_CK8M_DIV_SEL_Pos) | + (esp.RTCCNTL_CLK_CONF_DIG_CLK8M_D256_EN_Enable << esp.RTCCNTL_CLK_CONF_DIG_CLK8M_D256_EN_Pos) | + (esp.RTCCNTL_CLK_CONF_CK8M_DIV_DIV256 << esp.RTCCNTL_CLK_CONF_CK8M_DIV_Pos)) + + // Switch CPU from 80MHz to 160MHz. This doesn't affect the APB clock, + // which is still running at 80MHz. + esp.DPORT.CPU_PER_CONF.Set(esp.DPORT_CPU_PER_CONF_CPUPERIOD_SEL_SEL_160) + + // Clear .bss section. .data has already been loaded by the ROM bootloader. + // Do this after increasing the CPU clock to possibly make startup slightly + // faster. + preinit() + + // Initialize UART. + machine.UART0.Configure(machine.UARTConfig{}) + + // Configure timer 0 in timer group 0, for timekeeping. + // EN: Enable the timer. + // INCREASE: Count up every tick (as opposed to counting down). + // DIVIDER: 16-bit prescaler, set to 2 for dividing the APB clock by two + // (40MHz). + esp.TIMG0.T0CONFIG.Set(esp.TIMG_T0CONFIG_T0_EN | esp.TIMG_T0CONFIG_T0_INCREASE | 2<IRAM + + /* Put the stack at the bottom of DRAM, so that the application will + * crash on stack overflow instead of silently corrupting memory. + * See: http://blog.japaric.io/stack-overflow-protection/ */ + .stack (NOLOAD) : + { + . = ALIGN(16); + . += _stack_size; + _stack_top = .; + } >DRAM + + /* Constant global variables. + * They are loaded in DRAM for ease of use. Eventually they should be stored + * in flash and loaded directly from there but they're kept in RAM to make + * sure they can always be accessed (even in interrupts). + */ + .rodata : ALIGN(4) + { + *(.rodata) + *(.rodata.*) + } >DRAM + + /* Mutable global variables. + */ + .data : ALIGN(4) + { + _sdata = ABSOLUTE(.); + *(.data) + *(.data.*) + _edata = ABSOLUTE(.); + } >DRAM + + /* Check that the boot ROM stack (for the APP CPU) does not overlap with the + * data that is loaded by the boot ROM. There may be ways to avoid this + * issue if it occurs in practice. + * The magic value here is _stack_sentry in the boot ROM ELF file. + */ + ASSERT(_edata < 0x3ffe1320, "the .data section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") + + /* Global variables that are mutable and zero-initialized. + * These must be zeroed at startup (unlike data, which is loaded by the + * bootloader). + */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN (4); + _sbss = ABSOLUTE(.); + *(.bss) + *(.bss.*) + . = ALIGN (4); + _ebss = ABSOLUTE(.); + } >DRAM +} + +/* For the garbage collector. + */ +_globals_start = _sdata; +_globals_end = _ebss; +_heap_start = _ebss; +_heap_end = ORIGIN(DRAM) + LENGTH(DRAM); + +_stack_size = 4K; + +/* From ESP-IDF, included here as long as picolibc doesn't compile. + */ +memcpy = 0x4000c2c8; +memset = 0x4000c44c; +__udivdi3 = 0x4000cff8; diff --git a/targets/xtensa.json b/targets/xtensa.json new file mode 100644 index 00000000..276a8131 --- /dev/null +++ b/targets/xtensa.json @@ -0,0 +1,22 @@ +{ + "llvm-target": "xtensa", + "goos": "linux", + "goarch": "arm", + "build-tags": ["xtensa", "baremetal", "linux", "arm"], + "gc": "conservative", + "scheduler": "none", + "compiler": "clang", + "cflags": [ + "--target=xtensa", + "-Oz", + "-Werror", + "-fshort-enums", + "-Wno-macro-redefined", + "-Qunused-arguments", + "-fno-exceptions", "-fno-unwind-tables", + "-ffunction-sections", "-fdata-sections" + ], + "ldflags": [ + "--gc-sections" + ] +} diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 2e417b3e..d6e1ca68 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -19,23 +19,25 @@ var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$") var enumBitSpecifier = regexp.MustCompile("^#[x01]+$") type SVDFile struct { - XMLName xml.Name `xml:"device"` - Name string `xml:"name"` - Description string `xml:"description"` - LicenseText string `xml:"licenseText"` - Peripherals []struct { - Name string `xml:"name"` - Description string `xml:"description"` - BaseAddress string `xml:"baseAddress"` - GroupName string `xml:"groupName"` - DerivedFrom string `xml:"derivedFrom,attr"` - Interrupts []struct { - Name string `xml:"name"` - Index int `xml:"value"` - } `xml:"interrupt"` - Registers []*SVDRegister `xml:"registers>register"` - Clusters []*SVDCluster `xml:"registers>cluster"` - } `xml:"peripherals>peripheral"` + XMLName xml.Name `xml:"device"` + Name string `xml:"name"` + Description string `xml:"description"` + LicenseText string `xml:"licenseText"` + Peripherals []SVDPeripheral `xml:"peripherals>peripheral"` +} + +type SVDPeripheral struct { + Name string `xml:"name"` + Description string `xml:"description"` + BaseAddress string `xml:"baseAddress"` + GroupName string `xml:"groupName"` + DerivedFrom string `xml:"derivedFrom,attr"` + Interrupts []struct { + Name string `xml:"name"` + Index int `xml:"value"` + } `xml:"interrupt"` + Registers []*SVDRegister `xml:"registers>register"` + Clusters []*SVDCluster `xml:"registers>cluster"` } type SVDRegister struct { @@ -139,6 +141,11 @@ func cleanName(text string) string { } text = string(result) } + if len(text) != 0 && (text[0] >= '0' && text[0] <= '9') { + // Identifiers may not start with a number. + // Add an underscore instead. + text = "_" + text + } return text } @@ -163,7 +170,12 @@ func readSVD(path, sourceURL string) (*Device, error) { interrupts := make(map[string]*interrupt) var peripheralsList []*peripheral - for _, periphEl := range device.Peripherals { + // Some SVD files have peripheral elements derived from a peripheral that + // comes later in the file. To make sure this works, sort the peripherals if + // needed. + orderedPeripherals := orderPeripherals(device.Peripherals) + + for _, periphEl := range orderedPeripherals { description := formatText(periphEl.Description) baseAddress, err := strconv.ParseUint(periphEl.BaseAddress, 0, 32) if err != nil { @@ -172,6 +184,9 @@ func readSVD(path, sourceURL string) (*Device, error) { // Some group names (for example the STM32H7A3x) have an invalid // group name. Replace invalid characters with "_". groupName := cleanName(periphEl.GroupName) + if groupName == "" { + groupName = cleanName(periphEl.Name) + } for _, interrupt := range periphEl.Interrupts { addInterrupt(interrupts, interrupt.Name, interrupt.Name, interrupt.Index, description) @@ -396,6 +411,34 @@ func readSVD(path, sourceURL string) (*Device, error) { }, nil } +// orderPeripherals sorts the peripherals so that derived peripherals come after +// base peripherals. This is necessary for some SVD files. +func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral { + var sortedPeripherals []*SVDPeripheral + var missingBasePeripherals []*SVDPeripheral + knownBasePeripherals := map[string]struct{}{} + for i := range input { + p := &input[i] + groupName := p.GroupName + if groupName == "" { + groupName = p.Name + } + knownBasePeripherals[groupName] = struct{}{} + if p.DerivedFrom != "" { + if _, ok := knownBasePeripherals[p.DerivedFrom]; !ok { + missingBasePeripherals = append(missingBasePeripherals, p) + continue + } + } + sortedPeripherals = append(sortedPeripherals, p) + } + + // Let's hope all base peripherals are now included. + sortedPeripherals = append(sortedPeripherals, missingBasePeripherals...) + + return sortedPeripherals +} + func addInterrupt(interrupts map[string]*interrupt, name, interruptName string, index int, description string) { if _, ok := interrupts[name]; ok { if interrupts[name].Value != index { @@ -654,6 +697,7 @@ func parseRegister(groupName string, regEl *SVDRegister, baseAddress uint64, bit if !unicode.IsUpper(rune(regName[0])) && !unicode.IsDigit(rune(regName[0])) { regName = strings.ToUpper(regName) } + regName = cleanName(regName) bitfields := parseBitfields(groupName, regName, regEl.Fields, bitfieldPrefix) return []*PeripheralField{&PeripheralField{