Build System Basics

BOARD, CPU & FEATURES

FEATURES

What is a FEATURE?

A FEATURE is a mean of specifying valid/invalid dependencies and configurations.

Whenever a FEATURE is used there should be at some level a hardware requirement, whether this is a radio, a bus of a specific core architecture.

This is not a hard line, in some cases the line can be harder to establish than others. There are complicated cases like netif since a network interface could be fully implemented in software as a loop-back.

It's also important to note that a FEATURE does not mean there is a MODULE with the same name. There could be many implementations for the same FEATURE. The fact that many FEATURES translate directly into a MODULE is only by convenience.

e.g.

# all periph features correspond to a periph submodule
USEMODULE += $(filter periph_%,$(FEATURES_USED))

Providing a FEATURE

For a FEATURE to be provided by a board it must meet 2 criteria, and for periph_% and other hw (hardware) related FEATURES it must follow a 3rd criteria.

  • Needs the "hardware" or BSP support (toolchain, build system, flasher, etc.)
    • e.g.: stm32l152re has an SPI peripheral riotboot needs to be able to link and flash at an offset
  • Needs support in RIOT, an implementation of an api to interact with the hw
    • e.g.: cpu/stm32_common/periph/spi.c is implemented for stm32l1 riotboot needs an implementation of cpu_jump_to_image
  • Wiring between the cpu/soc(system on a chip) a bus and other cpu/_hw_ components.

All the FEATURES_%

  • FEATURES_PROVIDED are available hardware (including BSP) features (e.g.:periph_hwrng, periph_uart) or characteristics (e.g:arch_32bits) of a board.
  • FEATURES_CONFLICT are a series of FEATURES that can't be used at the same time for a particular BOARD.
  • FEATURES_REQUIRED are FEATURES that are needed by a MODULE or APPLICATION to work.
  • FEATURES_OPTIONAL are "nice to have" FEATURES, not needed but useful. If available they are always included.
  • FEATURES_REQUIRED_ANY are FEATURES of which (at least) one of is needed by a MODULE or APPLICATION. Alternatives are separated by a pipe (|) in order of preference, e.g.: FEATURES_REQUIRED_ANY += arch_avr8|arch_native if both are provide then arch_avr8 will be used.
  • FEATURES_BLACKLIST are FEATURES that can't be used by a MODULE or APPLICATION. They are usually used for hw characteristics like arch_ to easily resolve unsupported configurations for a group.
  • FEATURES_USED are the final list of FEATURES that will be used by an APPLICATION

Where to define FEATURES_%

  • FEATURES_PROVIDED, FEATURES_CONFLICT and FEATURES_CONFLICT_MSG are defined in Makefile.features
  • FEATURES_REQUIRED, FEATURES_OPTIONAL, FEATURES_REQUIRED_ANY, and FEATURES_BLACKLIST are defined by the application Makefile (examples/%/Makefile, tests/%/Makefile, etc.) or in Makefile.dep

CPU/CPU_MODEL

CPU and CPU_MODEL refer to the soc or mcu (microcontroller) present in a BOARD. The variables CPU, CPU_FAM, etc. are just arbitrary groupings to avoid code duplication. How this grouping is done depends on every implementation and the way each manufacturer groups there products.

These variables allows declaring the FEATURES that the mcu/soc provides as well as resolving dependencies.

FEATURES provided by a CPU/CPU_MODEL should not depend on the wiring of a specific BOARD but be intrinsic to the soc/mcu.

A CPU/CPU_MODEL might support FEATURES that will depend on the BOARD wiring, e.g.: bus (uart, spi) mappings. In this cases the FEATURE should be provided by the BOARD.

BOARD

In RIOTs build-system, a BOARD is a grouping of:

  • soc/mcu (CPU/CPU_MODEL)
    • e.g.: b-l072z-lrwan1 stm32l072cz
  • sensor/actuators (buttons and leds included) (drivers)
    • e.g.: b-l072z-lrwan1 leds and buttons
  • radios, ethernet, etc. devices (drivers)
    • e.g.: b-l072z-lrwan1 sx1276
  • programming/debugging tools
    • e.g.: b-l072z-lrwan1 stlink
  • configuration mapping cpu support capabilities to availability
    • e.g.: b-l072z-lrwan1 periph_conf.h, gpio_params

A board can have all the required FEATURES to interact with a radio or sensor/actuator, but it doesn't necessarily provide that FEATURE.

e.g.:

  • samr21-xpro provides a at86rf233 radio as well as the necessary periph_* features.
  • nucleo-* provide all periph_* features to use sx1272, and even a default configuration for the SX1272MB2xA shield, but not doesn't provide the radio.

If a board in /boards is connected to a radio shield, sensors, actuators, etc. then it is a different board than the one provided by default. Whenever you need to have a device mapping (in linux-arm, it would require a different device tree), then it is a different board and would need a different board/periph_conf.

A nucleo-* with a SX1272MB2xA is a different board in RIOT sense.

note: if devicetree is implemented this concept will change.

Variables declaration guidelines

This page contains basic guidelines about make variable declaration, it summarizes some of the pros and cons as well as specifies good and bad patterns in our build system. You might want to refer to gnu make documentation regarding these subjects.

Avoid unnecessary export

export OUTPUT = $(shell some-command)

Exporting a variable means it will be evaluated on every target call, which slows down the build system. Always avoid exporting a variable if unneeded.

If an export is actually needed by a sub-make then export the variable only for the needed targets using target-export-variables (more in makefiles/utils/variables.mk).

Exported variables ("global variable") are hard to remove, specially when badly documented. If no one knows why it's there and no one knows where it can be used then no one knows if it's safe to remove since it's present for every target. This is why global variables need clear documentation.

gnumake doc

Use memoized for variables referencing a function or command

recursively expanded variable:

OUTPUT = $(shell some-command $(ANOTHER_VARIABLE))
  • When using = the value of the variable is only declared, but not set, therefore the variable will only be evaluated when expanded (used) somewhere in the makefile. If is never expanded, some-command is never executed and ANOTHER_VARIABLE not expanded.
  • All variables or functions referenced by the declared variable will will be evaluated every time the variable is expanded. In the example some-command is executed every time OUTPUT is expanded, same for ANOTHER_VARIABLE. If some-command is slow this introduced unneeded overhead.
  • If the variable expansion doesn't involve evaluating a function the overhead is none.

simply expanded variable:

OUTPUT := $(shell some-command $(ANOTHER_VARIABLE))
  • When using := the value is only expanded once, expanding any reference to other variables or functions. If OUTPUT is always used at least once and evaluates a costly function (some command) then use :=.
  • When using := the variable will be evaluated even if not needed, which introduces unnecessary delay, in particular some command or functions evaluated by ANOTHER_VARIABLE are slow. It can also cause a failure in a worst-case scenario (think what happens if a tool is defined with := but you don't have the tool and you don't need it either).
  • The values of variables declared with := depend on the order of definition.

memoized:

OUTPUT = $(call memoized,OUTPUT,$(shell some-command))
  • memoized is a RIOT defined function that combines characteristics from both = and :=. The variable expansion will be deferred until its first usage, but further usage will consider it as a simply expanded variable, so it will use the already evaluated value. In the example some-command would be executed once or not at all (more in makefiles/utils/variables.mk).

gnumake doc

Additional documentation