Skip to content

Adding a new board

Ayke edited this page Aug 6, 2019 · 8 revisions

Build tags

You may have noticed that TinyGo uses a lot more build tags than is common for Go. For example, Arduino Uno uses the following build tags: arduino, atmega328p, atmega, avr5, avr, baremetal, arm, linux. Why so many? And what does linux/arm have to do with it? It's mostly to support the large variation in microcontrollers. Let's break them down:

  • arduino: this is the board name. Actually, it's the Arduino Uno so arduino is a slightly wrong name because there are other Arduino boards like the Arduino Zero with a completely different architecture. But when talking about Arduino most people actually refer to the Arduino Uno so that's why it is this name.
  • atmega328p is the chip used in the Arduino Uno, the common ATmega328p.
  • atmega is the chip family. There is also the attiny, for example, but it isn't (yet) supported by TinyGo.
  • avr5 is the AVR sub-family of many families.
  • avr is the base instruction set architecture, which is a particular 8-bit Harvard-style architecture.
  • linux, arm are the oddballs here: the AVR is not Linux nor ARM. These build tags are added because some supported GOOS/GOARCH needs to be used otherwise many Go standard library packages will fail to compile. Any OS/ARCH could be picked, but Linux/ARM seems like a good choice. Because the syscall package is replaced on baremetal targets, most Linux-specific things are replaced already in the standard library.

In general, build tags are sorted from more specific to less specific in the .json target descriptions.

Adding a new board

First make sure the particular chip and chip family is supported, see below.

Adding a new board is relatively easy. You will need:

  • A new target description in .json format. It is easiest to copy an existing description from a board with the same chip and adjust it to your needs. The things that will need to be modified are at least the build tags, and likely the flash property as well if the board is flashed in a different way. The properties ocd-daemon and gdb-initial-cmds are necessary for on-chip debugging, remove these if you can't test this to avoid confusion.
  • Some definitions in src/machine/board_<boardname>.go. See the existing board definitions (close to the board you're adding support for) to see what needs to be added. This mostly concerns pin layout and sometimes CPU speed (for chips with varying CPU speeds like most AVRs).

Adding a new chip

A new chip needs support for the following things (in addition to board support):

  • Some runtime support to implement console output and sleeping. See the existing runtime support in src/runtime/runtime_<chip>.go files. But check how much of this is actually chip specific, often basic peripherals like UART are shared by chips in the same chip family and can thus be implemented for the whole family.
  • Possibly some support in the machine package. To implement support, exclude src/machine/machine_dummy.go with build tags and implement for this chip (or preferably chip family) in a new src/machine/machine_<chip>.go file.
  • A new linker script to define flash and RAM layout of this chip. Take a look at the existing linker scripts (targets/*.ld) to see how they're structured. In most cases, this will just be a few lines to define flash/RAM offsets and sizes: the hard work is done by an architecture-specific linker script.

Adding a new chip family

For a new chip family, like from a non-supported vendor, you will need to set up autogenerating register definitions. Also, you may want to look into how much runtime support can be shared between the chip family to avoid code duplication (see above).

Adding autogenerated register descriptions

For Cortex-M chips this is relatively easy. You'll need to find the .svd files, which are probably already included in the lib/cmsis-svd submodule. Adding support for autogenerating them may be as easy as adding a few lines to the Makefile to generate them with tools/gen-device-svd.py. But in general, chip vendors like to interpret the SVD specification in their own ways, which means that supporting a previously unsupported vendor may involve editing the generation script.

Adding a new architecture

Adding a new architecture can be challenging and requires support from LLVM (this means ESP8266 and ESP32 are not supported as of 2018). It will involve the following things:

  • Checking backend support from LLVM. This means that for example the Xtensa architecture as used in the popular ESP8266/ESP32 chips is not supported.
    No support from LLVM means the architecture can not be supported easily, although it might be an idea to write an IR-to-C translation layer and compiling with a supported compiler like GCC.
  • It may also require some changes to the compiler, but in general these should be relatively small.
  • A new backend will likely also require a new linker script. There are some conventions used in TinyGo which may be useful to follow, and also some conventions used by the architecture which are also useful to follow. One place where TinyGo deviates from the common memory layout is that it places the stack at lower memory addresses and makes it a fixed size, at least for Cortex-M device to support zero-cost stack overflow checking.
  • Once a proper garbage collector is in place, a new architecture will require support from this garbage collector. It depends on the architecture how difficult this is.

Supported architectures as of July 2019:

  • Most desktop systems
  • Cortex-M
  • RISC-V
  • WebAssembly
  • AVR (as used in the Arduino Uno). But note that the AVR backend is still considered experimental and is known to have bugs. It certainly produces suboptimal code.

Harvard architectures (like AVR) are harder to support efficiently as in general Go doesn't encode constness in pointer types. The current solution is to mark every global variable as non-const. This means globals (even constant globals) take up a lot of RAM, but it should be possible to optimize this to a large degree. Also, 8-bits systems are much harder to write a garbage collector for than a 32-bit system.

Clone this wiki locally