Skip to content

nodecum/zig-zephyr

Repository files navigation

zig-zephyr

Case study of interweaving zephyr and zig

Motivation

Zephyr is a mature RTOS which is written in C, Zig is a new envolving programming language.

We like to investigate the possibilities of using Zig in the context of Zephyr. This could mean:

  • writing the application in Zig
  • write Zephyr drivers or modules in Zig
  • use Zig as Build System

Could this be achieved in a minimal invasive approache?

One existing example for an aplication written in Zig using Zephyr can be found here: zig-zephyr-hello . What could be improved to get a smoother interaction without forcing us to write wrappers in C to make use of Zephyr from Zig.

Current Status

  • [X] Devicetree is translated to Zig because C Devicetree macros are not usable from Zig.
  • [X] Writing a Zephyr application in Zig, using C imported types and functions.
  • [X] Compiling Zephyr using zig cc as toolchain (linking zephyr_pre0.elf step does not work).
  • [ ] Translate KConfig to Zig
  • [ ] Building Zephyr using the Zig build system instead of CMake

Preconditions

  • A working Zephyr setup. I set it up as I have described it here. I used the zephyr sdk as non-zig toolchain.
  • In order to compile the Zig example the file
    zephyrproject/zephyr/scripts/build/gen_syscalls.py
        

    has to be patched by moving the line

    wrap += "\t" + "compiler_barrier();\n"
        

    just before the line:

    wrap += "#endif\n"
        

    or removing it at all. The reason for this is propably a bug in Zig which refuses to inline the syscall functions if the compiler (memory) barrier is there an results in undefined references.

  • An Arm Board for testing. For other boards the file of this repository
    zig-zephyr/cmake/compiler/zig/zig-target.cmake
        

    could be extended.

  • If using zig cc as toolchain, then the path of the zephyr sdk has to be adapted to your installation in the file CMakeLists.txt:
    set(ZEPHYR_SDK_INSTALL_DIR $ENV{HOME}/bin/zephyr-sdk-0.15.1)
        

    and the file zephyr-sdk-gcc.

  • The file src/c.zig contains a board (in our case for adafruit_feather_nrf52840) specific define:
    @cDefine("NRF52840_XXAA", "");
        

    this should be changed to suits your board.

Building the Examples

You have to adapt the commands for your board, I used the adafruit_feather_nrf52840 board. The C application (~src/main.c) is the blinky example taken from the Zephyr samples. The Zig application (~src/main.zig) is the equivalent in zig.

  • Compile main.c using Zephyr sdk
    west build -d build -b adafruit_feather_nrf52840 -p
        
  • Compile main.c using zig cc
    west build -d build -b adafruit_feather_nrf52840 -p -- -DZIGCC=1
        
  • Compile main.zig using Zephyr sdk
    west build -d build -b adafruit_feather_nrf52840 -p -- -DZIGMAIN=1
        
  • Compile main.zig using zig cc
    west build -d build -b adafruit_feather_nrf52840 -p -- -DZIGCC=1 -DZIGMAIN=1
        

Running

Is done like usual by flashing the image to the board, for example:

west flash -r blackmagicprobe --gdb-serial /dev/ttyACM0 --skip-rebuild --elf-file build/zephyr/zephyr.elf 

Details of the journey

Inspired from Lup Yuen Lee’s Article about Building an App with Zig and Apache NuttX RTOS we will try to translate C Code to Zig using zig translate-c. We have to do this in the context of the Zephyr Build process, all compiler flags have to be the same like for invoking the C compiler. Therefore I started with:

Using the zig cc C compiler to compile Zephyr

We try to setup a toolchain which uses zig cc as C compiler.

By following the Custom CMake Guide of Zephyr we use this repository as TOOLCHAIN_ROOT with the cmake files under the cmake directory and setting the TOOLCHAIN_VARIANT to zig.

For the generic (host) part of the build we use the zephyr-sdk toolchain. We just direct some files to the correspondig files in ZEPHYR_BASE. Zig specific settings are in cmake/compiler/zig/target.cmake. I just tried it for an arm board so it is written for satisfy this needs.

Remarks

The critical step was the Unfixed size binary Build Step which builds zephyr_pre0.elf. If we use zigs clang it stops at

[149/159] Linking C executable zephyr/zephyr_pre0.elf

and tells

ld.lld: error: cannot find linker script -Map

The C compiler is called for this step.

Then I tried to use the zephyr-sdk-gcc for this step by writing a conditional clause in zigcc, which is the wrapper for zig cc.

If we do this we have no complaining about the arguments but get an

undefined reference to `__aeabi_memclr8'

This could be solved by adding -lc_nano to the compiler ars which links in the nano libc.

To view the invocation of the compiler do:

cd build
ninja -v

Compile main.zig with zig build-obj and use zephyr C code directly

I used zig translate-c with the same compiler arguments we found Zephyr was using to compile main.c. From this I extracted the relevant part for main.zig. In this form all macros were already expanded. With some CMake wizardry I managed it to fit the compiled object file into the Zephyr app application (see zig.cmake) Then I was faced with the compiler_barrier() issue, mentioned in the *Preconditions. I was happy to got it to compile, but the expanded macros were not usable from a programmers point of view. Thats way I

Translate the Devicetree to Zig

My aim was to have an easy usable perdant to the devicetree_generated.h header file in Zig. Whereas the header file is an artwork of encoding the devicetree data into C Preprocessor Macros my goal was to code the devicetree data in a clean and simple way which could be human viewable and usable.

Therefore Zig’s reach possibilities to create and initialize struct’s were realy useful. We got a tree which looks nearly like the zepyr.dts:

pub const soc = struct {
  const gpio_50000300 = .{
    ._device = @as([*c]const c.struct_device, &c.__device_dts_ord_10),
    .reg = [_]u32{0x50000300, 0x200, 0x50000800, 0x300},
    .port = @as( u32, 1),
    .gpio_controller = true,
    .ngpios = @as( u32, 16),
    .status = "okay",
    .compatible = [_][]const u8{"nordic,nrf-gpio"},
    .wakeup_source = false,
  };
};
pub const leds = .{
  .compatible = [_][]const u8{"gpio-leds"},
  // 16
  .led_0 = .{
    .gpios = .{.ph=&soc.gpio_50000300,.pin=@as( u32, 15),.flags=@as( u32, 0)},
    .label = "Red LED",
  },
};

In this tree the phandles are real references of their targets. Thus refer to gpios of led_0 is as easy as writing:

dt.leds.led_0.gpios

which is the analog of Zephyrs:

 GPIO_DT_SPEC_GET( DT_PATH( leds, led_0), gpios)

Right now not all properties are translated, but to complete it is not too difficult. (Aliases, Labels, Memory Maps for example)

About

Case study of interweaving zephyr and zig

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published