Adding a Custom IP Node

Adding a Custom IP Node

This page contains an overview of how to add a custom IP node to a Zephyr project along with an example.

Table of Contents

Introduction

As you develop with Zephyr, you may need to add custom IP nodes that are not officially supported in the AMD Zephyr or mainline Zephyr repositories. This page will go over the requirements for adding an unsupported node as well as walk through a simple example step-by-step. If you are not already familiar with device trees and device tree bindings, please consult Zephyr’s device tree guide and their device tree bindings guide.

By default, Zephyr sets the chosen memory node as the largest memory node in the hardware design. Users can edit this manually after device tree generation.

Adding a Custom IP Node

Prerequisites

To add a new node to an existing device tree, there are two requirements: a device tree binding and the corresponding node. The device tree binding will determine the properties of the node while the node itself can be added by either hand editing the board’s <BOARD>.dts file or by creating a <BOARD>.overlay file. The recommended flow is to create an overlay since if you update your local Zephyr repo, the changes you’ve made to <BOARD>.dts may be erased.

Another reason to use <BOARD>.overlay is that when west lopper-command is called to generate a device tree, it removes any officially unsupported IP node from the device tree.

Choosing an IP Node

This tutorial will cover how to add a GPIO node to the MicroBlaze V processor in the 2024.2 AMD Zephyr release. This node was chosen because it is a common node that will serve as a good example. Moving forward, we will assume that we configured the GPIO as shown in the image below:

image-20250307-165553.png
AXI GPIO 2.0 Configuration

This configuration was chosen because it allows us to show many different properties enabled in the device tree.

Adding a Device tree Binding

Zephyr will look for bindings in the dts/bindings subdirectories of the following places:

  1. The Zephyr repository

  2. Your application source directory

  3. Your board directory

  4. Any shield directories

  5. Any directories manually included in the DTS_ROOT CMake variable

  6. Any module that defines a dts_root in its build settings

As mentioned above, the device tree binding defines what properties a node contains. In Zephyr, these are .yaml files and can be found in zephyr/dts/bindings/<device> (for example zephyr/dts/bindings/gpio is a directory containing various GPIO bindings). Looking at zephyr/dts/bindings/gpio, we can see that there are four different xlnx GPIO bindings already available with descriptions:

  1. xlnx,ps-gpio-bank.yaml – Xilinx Zynq 7000/ZynqMP MIO/EMIO GPIO controller bank node

  2. xlnx,ps-gpio.yaml – Xilinx Zynq 7000/ZynqMP MIO/EMIO GPIO controller node

  3. xlnx,xps-gpio-1.00.a.yaml – Xilinx AXI GPIO IP node

  4. xlnx,xps-gpio-1.00.a-gpio2.yaml – Xilinx AXI GPIO IP GPIO2 node

From the descriptions, it looks like what we’re looking for (a generic AXI GPIO node) is covered by xlnx,xps-gpio-1.00.a.yaml(link). Because we enabled dual channel in our configuration, we will also need xlnx,xps-gpio-1.00.a-gpio2.yaml(link). From looking at the files, we can see that there is one required property, reg, and a variety of properties we can enable/disable to match how we configured the GPIO in Vivado.

Furthermore, looking into the included device tree bindings, we find gpio-controller.yaml (link), base.yaml (link), and pm.yaml (link). These files are responsible for adding two more required properties, gpio-controller and #gpio-cells, as well as two more properties we are interested in for controlling interrupts, interrupt-parent and interrupts.

Adding a Device tree Overlay

Zephyr will look for .overlay files in the following places in your application source directory:

  1. If the file socs/<SOC>_<BOARD_QUALIFIERS>.overlay exists, it will be used.

  2. If the file boards/<BOARD>.overlay exists, it will be used in addition to the above.

  3. If the current board has multiple revisions and boards/<BOARD>_<revision>.overlay exists, it will be used in addition to the above.

  4. If one or more files have been found in the previous steps, the build system stops looking and just uses those files.

  5. Otherwise, if <BOARD>.overlay exists, it will be used, and the build system will stop looking for more files.

  6. Otherwise, if app.overlay exists, it will be used.

Before we can create the .overlay file, we should take a look at the existing device tree for the MicroBlaze V. We are going to want to add our GPIO under axi: soc, so we should start our overlay file from there. In this case, we will not be overwriting any of the default values, so we will jump straight to the GPIO nodes:

/ { axi: soc { axi_gpio_0_1: gpio@FACEFEED { compatible = "xlnx,xps-gpio-1.00.a"; reg = <0xFACEFEED 0x10000>; gpio-controller; #gpio-cells = <2>; interrupt-parent = <&axi_intc>; interrupts = <0x2 0x0>; xlnx,all-inputs = <1>; xlnx,all-outputs = <0>; xlnx,dout-default = <0x0>; xlnx,gpio-width = <0x20>; xlnx,tri-default = <0xffffffff>; xlnx,is-dual = <1>; xlnx,all-inputs-2 = <0>; xlnx,all-outputs-2 = <1>; xlnx,dout-default-2 = <0x0>; xlnx,gpio2-width = <0x20>; xlnx,tri-default-2 = <0xffffffff>; axi_gpio_0_2: gpio2 { compatible = "xlnx,xps-gpio-1.00.a-gpio2"; gpio-controller; #gpio-cells = <2>; }; }; }; };

Change 0xFACEFEED to the correct address and save the following file as mbv32.overlay in your application’s source directory.

For example, we could use zephyr/tests/misc/test_build as a base to make sure we formatted the overlay correctly before developing more.

Building the Application

Execute the following commands:

cd <path_to>/zephyr cp <path_to>/mbv32.overlay ./tests/misc/test_build/ west build -p -b mbv32 ./tests/misc/test_build/ cd build/zephyr

This folder will contain the final combined devicetree called zephyr.dts which will now include your added GPIO nodes:

/dts-v1/; / { #address-cells = < 0x1 >; #size-cells = < 0x1 >; model = "AMD MicroBlaze V 32bit"; compatible = "qemu,mbv", "amd,mbv"; cpus: cpus { #address-cells = < 0x1 >; #size-cells = < 0x0 >; timebase-frequency = < 0x5f5e100 >; cpu_0: cpu@0 { compatible = "amd,mbv32", "riscv"; device_type = "cpu"; reg = < 0x0 >; riscv,isa = "rv32imafdc"; i-cache-size = < 0x8000 >; d-cache-size = < 0x8000 >; clock-frequency = < 0x5f5e100 >; cpu0_intc: interrupt-controller { compatible = "riscv,cpu-intc"; interrupt-controller; #interrupt-cells = < 0x1 >; phandle = < 0x1 >; }; }; }; aliases { serial0 = &uart0; }; chosen { bootargs = "earlycon=sbi console=ttyUL0,115200"; stdout-path = "serial0:115200n8"; zephyr,console = &uart0; zephyr,shell-uart = &uart0; zephyr,sram = &ram0; }; ram0: memory@80000000 { device_type = "memory"; reg = < 0x80000000 0x80000000 >; }; lmb0: memory@0 { compatible = "mmio-sram"; reg = < 0x0 0x20000 >; }; clk100: clock { compatible = "fixed-clock"; #clock-cells = < 0x0 >; clock-frequency = < 0x5f5e100 >; phandle = < 0x3 >; }; axi: soc { #address-cells = < 0x1 >; #size-cells = < 0x1 >; compatible = "simple-bus"; ranges; bootph-all; axi_intc: interrupt-controller@41200000 { compatible = "xlnx,xps-intc-1.00.a"; reg = < 0x41200000 0x1000 >; interrupt-controller; interrupt-parent = < &cpu0_intc >; #interrupt-cells = < 0x2 >; interrupts-extended = < &cpu0_intc 0xb >; xlnx,kind-of-intr = < 0x1a >; xlnx,num-intr-inputs = < 0xb >; phandle = < 0x2 >; }; xlnx_timer0: timer@41c00000 { compatible = "amd,xps-timer-1.00.a"; reg = < 0x41c00000 0x1000 >; interrupt-parent = < &axi_intc >; interrupts = < 0x0 0x2 >; xlnx,one-timer-only = < 0x0 >; clocks = < &clk100 >; }; uart0: serial@40600000 { compatible = "xlnx,xps-uartlite-1.00.a"; reg = < 0x40600000 0x1000 >; interrupt-parent = < &axi_intc >; interrupts = < 0x1 0x2 >; clocks = < &clk100 >; current-speed = < 0x1c200 >; }; axi_gpio_0_1: gpio@FACEFEED { compatible = "xlnx,xps-gpio-1.00.a"; reg = < 0xfacefeed 0x10000 >; gpio-controller; #gpio-cells = < 0x2 >; interrupt-parent = < &axi_intc >; interrupts = < 0x2 0x0 >; xlnx,all-inputs = < 0x1 >; xlnx,all-outputs = < 0x0 >; xlnx,dout-default = < 0x0 >; xlnx,gpio-width = < 0x20 >; xlnx,tri-default = < 0xffffffff >; xlnx,is-dual = < 0x1 >; xlnx,all-inputs-2 = < 0x0 >; xlnx,all-outputs-2 = < 0x1 >; xlnx,dout-default-2 = < 0x0 >; xlnx,gpio2-width = < 0x20 >; xlnx,tri-default-2 = < 0xffffffff >; axi_gpio_0_2: gpio2 { compatible = "xlnx,xps-gpio-1.00.a-gpio2"; gpio-controller; #gpio-cells = < 0x2 >; }; }; }; };

Related content

© Copyright 2019 - 2022 Xilinx Inc. Privacy Policy