COMPAS’25 Tutorial - CVA6 SoC customization

Nicolas Derumigny

In this tutorial, we will learn how to add MMIO peripherals to an SoC orchestrated around the RISC-V CVA6. For that, we will use FPGA (Field Programmable Gate Array), a class of reprogrammable chips that can be user-configured at the logic gate level to create application-specific design or (as in our case), general-purpose cores called softcores.

The objective of this tutorial is to experiment the different levels of customization required to have functional devices:

Presentations of the boards

For this tutorial, we will use two boards: the small PYNQ-Z2 and the bigger ZCU104. Both are MPSoC (MultiProcessor System-on-Chip), which means that they integrates several fixed blocks (we call them IP for Intellectual Properties [of their design]) such as Arm cores, memory controller, Ethernet controllers, … These fixed IPs forms the PS (Processing Subsystem), while the FPGA is called PL (Programmable Logic). While communication channels exist between PS and PL, PS IPs are not directly accessible from the FPGA. It is for example impossible to use the (PS-wired) Ethernet controller on the PL without a transmitting program running on the Arm CPU to forward packets from the network interface to a shared PS-PL bus.

Therefore, we will first boot the Arm cores of the boards, and from it, install and boot our softcore on the PL. Two Linux with different ISA will cohabit on the board, each one linked to its own storage, peripherals and RAM region.

PYNQ-Z2

The PYNQ-Z2 is an entry-level (~200€) board manufactured by TUL. It features a ZYNQ XC7Z020-1CLG400C MPSoC and sufficient IO for small embedded projects (HDMI in/out, mic, GPIO, ethernet, 2 dual PMOD connectors). More precisely, the PYNQ-Z2 integrates two Cortex-A9 cores running at 650 MHz, a FPGA with 85K logic cells, 630 KB of block RAM and 220 DSP slices; and a total of 512 MiB of shared DDR3.

The reference manual of the PYNQ-Z2 is available here. Download it, as it contains important information for I/O mapping on the FPGA.

PYNQ-Z2 board

The PYNQ-Z2 is big enough to fit one trimmed-down CVA6 64-bit core without debug module, but with sufficient peripherals to boot Linux. This will be our base platform to customize our CVA6 SoC.

(Already done in this case): To share DRAM between the Arm cores and the CVA6 softcore, we used this tutorial to modify the device tree and add a kernel boot parameter to only allow Linux to use the first 256 MiB of RAM, leaving the other 256 MiB for the CVA6.

Note: Boards files (included in the provided Vivado images) are available here.

ZCU104

The ZCU104 is an mid-range (~2000€) board manufactured by AMD (formerly Xilinx). It features a ZYNQ Ultrascale+ XCZU7EV-2FFVC1156 MPSoC and sufficient IO for various projects (HDMI in/out, ethernet, M.2 socket, DDR4 SO-DIMM socket, 3 dual PMOD connectors, FMC connector). The ZCU104 integrates four Cortex-A53 cores running at 1,2 GHz, a FPGA of 504K logic cells, 38 MiB of block RAM and 1,728 DSP slices; and a total of 2 GiB MiB of PS-side DDR4 (shareable with the PL, and extensible on PL-side using the SO-DIMM socket).

The reference manual of the ZCU104 is available here. Once again, download it as we will use it later.

ZCU104 board

The ZCU104 is big enough to fit one 64-bit CVA6 core with its debug module as well as plenty of peripherals. We will use use it for Ethernet expansion as well as frequency modification.

Extensions

While both boards include a number of I/O components, none are sufficient as it to boot our softcore. Therefore, we will use PMOD (Peripheral MODule interface) and FMC modules to extend the capabilities of our FPGA and add disk, serial and ethernet communication capabilities.

Pmod

Pmod interface (Peripheral Module interface) is an open standard defined by Digilent in the Pmod Interface Specification for connecting peripheral modules to FPGA and microcontroller development boards using 6 pins. (Wikipedia)

Pmod is a general purpose interface allowing extensions of boards in a form factor similar to Raspberry Pi’s GPIO. Out of the 6 pins of each Pmod connectors, 4 are used for signals and 2 for power delivery (3.3 V / ground). On our boards, Pmod is available on dual-bundled connectors consisting of 12 pins (2 stacked interfaces).

Two Pmod connectors: one populated, one free

We will use Pmod extension modules for low-bandwidth peripherals: disk and serial (i.e. console).

Disk: MicroSD Card

Secure Digital (SD) is a proprietary, non-volatile, flash memory card format developed by the SD Association (SDA). Owing to their compact size, SD cards have been widely adopted in a variety of portable consumer electronics, including digital cameras, camcorders, video game consoles, mobile phones, action cameras, and camera drones. (Wikipedia)

SD cards are small flash memory storage devices dating back 1999. Several format and read mechanisms exist to satisfy various needs and speed requirements, from Low-Speed (12.5 MB/s) to SD-Express (3,940 MB/s). In this tutorial we will use the smallest form factor, MicroSD card, as base drive using a Pmod adapter due to their easiness of integration with our boards. The reference manual of the Pmod microSD adapter can be found here. Keep it bookmarked, as we will use it later.

Pmod microSD module

More precisely, we will use the slowest operating mode of an SD card: Serial Peripheral Interface (SPI), which is both simple to implement at the hardware level, widely supported, not subject to royalties and fast enough for the low frequency of our softcore.

Serial: USB-UART

A universal asynchronous receiver-transmitter (UART) is a peripheral device for asynchronous serial communication in which the data format and transmission speeds are configurable. (Wikipedia)

While every every machine nowadays is equipped with Ethernet and/or WiFi connection to be reachable over network, our softcore is not. The simplest way to interact is over a serial connection provided by a UART. As computers are not equipped anymore with serial ports, we will use a Pmod to USB-UART adapter to implement serial communication with the softcore. The reference manual of the Pmod microSD adapter can be found here. Once again, keep it bookmarked, as we will use it later.

PMOD USB-UART module

The module relies on a FTDI FT232RQ to expose the module’s UART to a host computer over USB. The module must be wired on another PL-side UART IP to communicate. It also integrates two red LEDs that blinks on transmission/reception of data, and a jumper that select the power source (USB or Pmod).

Ethernet: EVAL-CN0506-FMCZ add-in card

FPGA Mezzanine Card (FMC) is an ANSI/VITA (VMEbus International Trade Association) 57.1 standard that defines I/O mezzanine modules with connection to an FPGA or other device with re-configurable I/O capability. (Wikipedia)

The FMC interface is design for more bandwith-hungry expansions of FPGAs, such as networking cards. Our ZCU104 board integrates the LPC (Low Pin Count) variant of the FMC module. It is physically compatible with the EVAL-CN0506-FMCZ add-in card providing dual-PHY Ethernet interface. However, in practice, one should be careful about the supplied voltage - 1.2 V only - when the ZCU104 FMC connector can supply 0.8 V, 1.0 V and 1.2 V. Recompiling the FSBL (First Stage BootLoader) to support the correct voltage is necessary in order to have the module initializing, which is (thankfully) already done in our case.

References designs as well as documentation is available here, with design and integration files downloadable here. Once again, keep these files as we will use them later.

FMC Ethernet module

In this tutorial, we will use the EVAL-CN0506-FMCZ to add networking capabilities to our CVA6 softcore through the use of one Ethernet port.

Toolchains

FPGAs are programmed using a bitstream file, which is itself produced following the implementation and placing/routing of the modules. FPGA programming toolchains are closed-source, vendor-specific and not well considered by the community. AMD (formerly Xilinx) FPGAs relies on Vivado Design Suite, which is known for its large space requirements (~200 GiB).

Installing and launching Vivado 2024.1

For the sake of simplicity, we will use a compressed image of Vivado ML Suite 2024.1 (~30 GiB only!), provided on-site on USB keys.

To auto-mount it, copy the image to /opt/Xilinx_Vivado_ML_Suite_2024.1.sqsh and add the following to your /etc/fstab file:

/opt/Xilinx_Vivado_ML_Suite_2024.1.sqsh      /opt/Xilinx       squashfs     ro,relatime,errors=continue,threads=single      0  2

Then, run /opt/Xilinx/Vivado/2024.1/bin/vivado to launch the GUI interface. Note that the ncurses-compat-libs package is required as dependency to correctly execute it.

Compiling the cva6 project

FPGAs are programmed using HDL: Hardware Description Languages. The most popular are VHDL and Verilog. The Core-V CVA6 processor is a 6-stage, single-issue, in-order CPU which implements the 64-bit RISC-V instruction set. It is open-source (Apache license), written in SystemVerilog and currently maintained by Thales.

The source code for the PYNQ-Z2 board design is located in the git repo https://github.com/NicolasDerumigny/cva6.git, branch pynqz2_compas_bare. Be careful to clone it using the --recurse-submodule option!

Dependencies

A working RISC-V cross-compiler is needed to generate our bitstream, as the firmware (very first code executed by the softcore) is embedded on an on-chip ROM in binary format. The easiest way to have one is through the xPack GNU RISC-V Embedded GCC, whose installation method depends on your Linux distribution.

Arch:

Others:

Bitstream generation

To generate the bare CVA6 bitstream (~20-30 min), launch

source set-env.sh
make fpga

This will automatically create a project in ./corev_apu/fpga/ariane.xpr and launch the generation of a bitstream. Open the project to start customizing the SoC (this does not require to wait for the synthesis to finish, you can interrupt the bitstream generation starting from the synthesis step to modify freely the block design).

/!\ Some files (especially the firmware) are not recompiled when modified. Use git clean -xdf to start fresh after any device tree modifications!

Flashing the bitstream

While the bitstream (.bit) generated by Vivado contains all the information needed to configure the FPGA, it is not in the right format to be used as-it by the board management interface, where a .bin file is exepected.

Therefore, a .bit to .bin converter script is needed. Save it as create_bitstream.sh and place its location in your $PATH.

#!/usr/bin/bash
set -e
XILINX=/opt/Xilinx
XILINX_VERSION=2024.1
BOOTGEN=${XILINX}/Vitis/${XILINX_VERSION}/bin/bootgen
NAME=`basename $1`
OLDPWD=$PWD
if [ "$1" == "" ]; then
echo "Usage: $0 <bitstream.bit>"
exit
fi
cp $1 /tmp/bitstream.bit
cd /tmp
# This just parse the header to detect the part version
XILINX_VERSION_HEX=`echo -n $XILINX_VERSION |
xxd -p |
sed "s/\(..\)/\1 /g"`
PART=`xxd -p -l 300 /tmp/bitstream.bit |
tr -d "\n" |
sed "s/\(..\)/\1 /g" |
sed "s/.* ${XILINX_VERSION_HEX}00 62 00 .. //g" |
sed "s/00 .*//g" |
xxd -r -p`
echo "Detected part ${PART}"
if [ "$PART" == "xczu7ev-ffvc1156-2-e" ]; then
ARCH=zynqmp
cat > /tmp/bitstream.bif <<EOF
all: {
    [destination_device=pl] bitstream.bit
}
EOF
else
ARCH=zynq
cat > /tmp/bitstream.bif <<EOF
all: {
    bitstream.bit
}
EOF
fi
$BOOTGEN -arch $ARCH -image bitstream.bif -w -process_bitstream bin
rm bitstream.bit bitstream.bif
mv bitstream.bit.bin ${OLDPWD}/${NAME}.bin

To generate a bitstream, use create_bitstream.sh path/to/file.bin which will produce path/to/file.bin.bit. Then, copy file.bin.bit to the board using ssh/scp, and use sudo ./flash_bin.sh file.bin.bit to flash the FPGA. Congrats, the CVA6 should be booting!

Preparing the cva6 image

The CVA boot chain is composed of three elements:

The CVA6 SoC boot sequence

Therefore, the CVA6 boot image is composed of 3 partitions:

/!\ The CVA6 image building chain relies on Buildroot to build the Linux kernel, which downloads and recompiles a lot of dependencies, including GCC. This will take space (~20 GiB) and time (1 hour+)!

To build the image generation toolchain, clone the modified CVA6-sdk repo, branch linux_6.6_compas_pynq.

To compile the Linux image, go to ~/cva6_sdk and run:

make

To flash the image on the SD card, plug the card in your host PC and run:

sudo -E make flash-sdcard SDDEVICE=/dev/<SD_CARD_DEVICE>

BE CAREFUL TO SELECT THE RIGHT DEVICE, OR YOU MAY LOSE ALL YOUR DATA

Modifying the design

Block design

Vivado block design is a graphical representation of interconnected IPs on the FPGA. It allows fast customization of some IPs as well as user-friendly interfaces for various project I/O.

To modify the block design, generate a first bitstream, then open Vivado and select “open block design” on the left menu. You should land in the following panel:

Vivado Block Design

Constraint file

The constraint file (.xdc for Xilinx FPGAs) specifies the relationship between the external interfaces in the block design view and the physical FPGA pins, as well as various clocks or critical path length (hence giving constraints at the placement / routing step).

The current constraint file can be opened and edited directly in Vivado in the block design view using the top left “Source” pane and double-clicking in the file under the “Constraints” label, as illustrated in the following image:

Vivado Constraint File Editor

Address mapping

Address mapping (i.e. routing in between IPs that uses an addressable interface [AXI in our case]) allows specification of the routing (and not rewrite) of the communication to the right IP. It is accessible using the “Address Editor” tab in Vivado block design, where each tree represent one self-contained network.

Vivado Address Editor

Device Tree

A devicetree (also written device tree) is a data structure describing the hardware components of a particular computer so that the operating system’s kernel can use and manage those components, including the CPU or CPUs, the memory, the buses and the integrated peripherals. (Wikipedia)

Adding peripherals requires to modify the device tree in order to advertise the presence of our new controllers to the software. Two device tree are present in the project:

Adding UART

First, we will add a UART controller to our design, in order to see boot logs. For that, we will add a 16550-compatible controller to the CVA6 SoC, linked to our Pmod UART-USB adapter wired to our host machine. Note that the UART C driver is already present in the boot ROM, so we do not need to write any initialization / driver code.

Block Design

In the block design view, add a AXI UART 16550 controller and configure it in 16550 mode. Then, connect the following pins:

Inputs
Outputs
Addresses

In the Address Editor tab, set the /axi_uart16550_0/S_AXI Master Base Address to 0x1000_0000 and range to 64K.

Constraints:

In the constraint file, specifies the relationship between the design’s uart_rxd/uart_txd external IOs and the physical Pmod interface (i.e. FPGA pin). Use the PYNQ-Z2 documentation and the Pmod UART-USB documentation, deduce the FPGA pins corresponding to the chosen Pmod connector.

Then, add to the pynq_z2.xdc constraint file the following code, replacing <PIN> with the corresponding FPGA pin identifier.

## UART
set_property -dict {PACKAGE_PIN <PIN>  IOSTANDARD LVCMOS33} [get_ports uart_txd   ]; # module RXD -> FPGA TXD
set_property -dict {PACKAGE_PIN <PIN>  IOSTANDARD LVCMOS33} [get_ports uart_rxd   ]; # module TXD -> FPGA RXD

Device Tree

Insert into the device tree files the following entries, that advertise a 16550-compatible UART running at 25 MHz with a baud rate of 115,200 available on physical address 0x10000000, mapped on interrupt vector 1 (by RISC-V specification).

   L26: soc {
    ...
    uart@10000000 {
      compatible = "xlnx,axi-uart16550-1.01.a", "xlnx,xps-uart16550-2.00.a", "ns16550";
      reg = <0x0 0x10000000 0x0 0x1000>;
      clock-frequency = <25000000>;
      current-speed = <115200>;
      interrupt-parent = <&PLIC0>;
      interrupts = <1>;
      reg-shift = <2>; // regs are spaced on 32 bit boundary
      reg-io-width = <4>; // only 32-bit access are supported
    };
    ...
};

Adding SD Card

As SD card controllers are often proprietary and/or expansive, we will use standard Xilinx SPI controller to pilot accesses to the SD card. This addition is very similar to the UART one: instantiation of a controller block, definition of the constraints and edition of the device tree.

Block Design:

In the block design, add a AXI Quad SPI block, and configure it the following way: - Perfomance mode: Enable - SPI Mode: Standard - Transaction width: 8 - Frequency Ratio: 2x1 - No. of Slaves: 1 - Master mode: Enable - Byte Level Interrupt: Disable - FIFO: Enable - FIFO Depth: 256 - Startup Primitive: Disable - Async Clock Mode: Disable

AXI Quad SPI CVA6 SD Configuration

Then, connect the following interfaces:

Inputs
Outputs
Addresses

Similarly to the UART controller, in the Address Editor tab, set the /axi_quad_spi_0/axi_mm Master Base Address to 0x2000_0000 and range to 64K.

Constraints:

Once again, similarly to the UART controller, add to the pynq_z2.xdc constraint file the following code, replacing <PIN> with the corresponding FPGA pin identifier.

## SD card
# SPI mode, wired on PMOD
set_property -dict {PACKAGE_PIN <PIN> IOSTANDARD LVCMOS33} [get_ports spi_clk_o ];
set_property -dict {PACKAGE_PIN <PIN> IOSTANDARD LVCMOS33} [get_ports spi_miso ] ;
set_property -dict {PACKAGE_PIN <PIN> IOSTANDARD LVCMOS33} [get_ports spi_mosi ] ;
set_property -dict {PACKAGE_PIN <PIN> IOSTANDARD LVCMOS33} [get_ports spi_ss ]   ;

Device Tree:

Insert into the device tree files the following entries, that advertise an SPI controller running at 25 MHz, available on physical address 0x20000000, mapped on interrupt vector 2 (by RISC-V specification).

  L26: soc {
    ...
    xps-spi@20000000 {
      compatible = "xlnx,xps-spi-2.00.b", "xlnx,xps-spi-2.00.a";
      #address-cells = <1>;
      #size-cells = <0>;
      interrupt-parent = <&PLIC0>;
      interrupts = < 2 2 >;
      reg = < 0x0 0x20000000 0x0 0x1000 >;
      xlnx,family = "zynq7";
      xlnx,fifo-exist = <0x1>;
      xlnx,num-ss-bits = <0x1>;
      xlnx,num-transfer-bits = <0x8>;
      xlnx,sck-ratio = <0x4>;
      mmc@0 {
        compatible = "mmc-spi-slot";
        reg = <0>;
        spi-max-frequency = <25000000>;
        voltage-ranges = <3300 3300>;
        disable-wp;
      };
    };
    ...
  };

Adding JTAG / GDB

This part is not yet finished!

The CVA6 SoC debug chain

Block Design

OpenOCD + GDB usage

The AXI bus is not really fault-tolerant…

Adding board components

This part is not yet finished!

Buttons

This part is not yet finished!

Block Design

Controller

Addresses

Constraints

As we used board interfaces directly from the block design, constraints were automatically inserted. Yippee!

Device Tree

  leds {
    compatible = "gpio-leds";
    heartbeat-led0 {
      gpios = <&xlnx_gpio 0 GPIO_ACTIVE_HIGH>;
      linux,default-trigger = "heartbeat";
      retain-state-suspended;
    };
    heartbeat-led1 {
      gpios = <&xlnx_gpio 1 GPIO_ACTIVE_HIGH>;
      linux,default-trigger = "heartbeat";
      retain-state-suspended;
    };
    heartbeat-led2 {
      gpios = <&xlnx_gpio 2 GPIO_ACTIVE_HIGH>;
      linux,default-trigger = "heartbeat";
      retain-state-suspended;
    };
    heartbeat-led3 {
      gpios = <&xlnx_gpio 3 GPIO_ACTIVE_HIGH>;
      linux,default-trigger = "heartbeat";
      retain-state-suspended;
    };
  };
  ...
  L26: soc {
    ...
    xlnx_gpio: gpio@40000000 {
      #gpio-cells = <2>;
      compatible = "xlnx,xps-gpio-1.00.a";
      gpio-controller;
      reg = <0x0 0x40000000 0x0 0x10000>;
      xlnx,all-inputs = <0x0>;
      xlnx,all-outputs = <0x1>;
      xlnx,dout-default = <0x0>;
      xlnx,gpio-width = <0x4>;
      xlnx,interrupt-present = <0x0>;
      xlnx,is-dual = <0x0>;
      xlnx,tri-default = <0xffffffff>;
    };
    ...
  };

LEDs

LEDs are piloted using the same GPIO interface than buttons. Repeat the same steps, but insert the following device tree nodes:

Device Tree

  leds {
    compatible = "gpio-leds";
    heartbeat-led0 {
      gpios = <&xlnx_gpio 0 GPIO_ACTIVE_HIGH>;
      linux,default-trigger = "heartbeat";
      retain-state-suspended;
    };
    heartbeat-led1 {
      gpios = <&xlnx_gpio 1 GPIO_ACTIVE_HIGH>;
      linux,default-trigger = "heartbeat";
      retain-state-suspended;
    };
    heartbeat-led2 {
      gpios = <&xlnx_gpio 2 GPIO_ACTIVE_HIGH>;
      linux,default-trigger = "heartbeat";
      retain-state-suspended;
    };
    heartbeat-led3 {
      gpios = <&xlnx_gpio 3 GPIO_ACTIVE_HIGH>;
      linux,default-trigger = "heartbeat";
      retain-state-suspended;
    };
  };
  ...
  L26: soc {
    ...
    xlnx_gpio: gpio@<ADDRESS> {
      #gpio-cells = <2>;
      compatible = "xlnx,xps-gpio-1.00.a";
      gpio-controller;
      reg = <0x0 <ADDRESS> 0x0 0x10000>;
      xlnx,all-inputs = <0x0>;
      xlnx,all-outputs = <0x1>;
      xlnx,dout-default = <0x0>;
      xlnx,gpio-width = <0x4>;
      xlnx,interrupt-present = <0x0>;
      xlnx,is-dual = <0x0>;
      xlnx,tri-default = <0xffffffff>;
    };
    ...
  };

(ZCU104 only) Adding Ethernet

This part is not yet finished!

Block Design

Controller

Addresses

Constraints

## Ethernet
# Requires an FMC mezzanine card https://www.analog.com/en/resources/reference-designs/circuits-from-the-lab/cn0506.html
# Right/a adapter only
# Port a - MDIO
set_property -dict {PACKAGE_PIN A13  IOSTANDARD LVCMOS18 PULLUP true} [get_ports mdio_mdio_io]    ; # mdio_fmc_a, FMC_LPC_LA11_P
set_property -dict {PACKAGE_PIN A12  IOSTANDARD LVCMOS18} [get_ports mdio_mdc]                    ; # mdc_fmc_a, FMC_LPC_LA11_N
create_clock -name mdio_clk_a -period 400.0 [get_ports mdio_mdio_io]                              ;
# Port a - Other
set_property -dict {PACKAGE_PIN E17  IOSTANDARD LVCMOS18} [get_ports int_n]                       ; # int_n_a, FMC_LPC_LA08_N
set_property -dict {PACKAGE_PIN D16  IOSTANDARD LVCMOS18} [get_ports eth_rst]                     ; # reset_a, FMC_LPC_LA15_P
# Port a - RGMII
set_property -dict {PACKAGE_PIN F17  IOSTANDARD LVCMOS18} [get_ports rgmii_rxc]                 ; # rgmii_rxc_a, FMC_LPC_LA00_CC_P
set_property -dict {PACKAGE_PIN J15  IOSTANDARD LVCMOS18} [get_ports rgmii_rx_ctl]              ; # rgmii_rx_ctl_a, FMC_LPC_LA07_N
set_property -dict {PACKAGE_PIN L20  IOSTANDARD LVCMOS18} [get_ports {rgmii_rd[0]}]             ; # rgmii_rxd_a[0], FMC_LPC_LA02_P
set_property -dict {PACKAGE_PIN K20  IOSTANDARD LVCMOS18} [get_ports {rgmii_rd[1]}]             ; # rgmii_rxd_a[1], FMC_LPC_LA02_N
set_property -dict {PACKAGE_PIN K19  IOSTANDARD LVCMOS18} [get_ports {rgmii_rd[2]}]             ; # rgmii_rxd_a[2], FMC_LPC_LA03_P
set_property -dict {PACKAGE_PIN K18  IOSTANDARD LVCMOS18} [get_ports {rgmii_rd[3]}]             ; # rgmii_rxd_a[3], FMC_LPC_LA03_N
set_property -dict {PACKAGE_PIN L16  IOSTANDARD LVCMOS18 SLEW FAST} [get_ports rgmii_txc]       ; # rgmii_txc_a, FMC_LPC_LA04_N
set_property -dict {PACKAGE_PIN J16  IOSTANDARD LVCMOS18 SLEW FAST} [get_ports rgmii_tx_ctl]    ; # rgmii_tx_ctl_a, FMC_LPC_LA07_P
set_property -dict {PACKAGE_PIN H16  IOSTANDARD LVCMOS18 SLEW FAST} [get_ports {rgmii_td[0]}]   ; # rgmii_txd_a[0], FMC_LPC_LA09_P
set_property -dict {PACKAGE_PIN G16  IOSTANDARD LVCMOS18 SLEW FAST} [get_ports {rgmii_td[1]}]   ; # rgmii_txd_a[1], FMC_LPC_LA09_N
set_property -dict {PACKAGE_PIN H19  IOSTANDARD LVCMOS18 SLEW FAST} [get_ports {rgmii_td[2]}]   ; # rgmii_txd_a[2], FMC_LPC_LA06_P
set_property -dict {PACKAGE_PIN G19  IOSTANDARD LVCMOS18 SLEW FAST} [get_ports {rgmii_td[3]}]   ; # rgmii_txd_a[3], FMC_LPC_LA06_N

Device Tree

  L26: soc {
    ...
    eth_xlnx_dma: dma@41e00000 {
        #dma-cells = <1>;
        axistream-connected = <&eth_xlnx_mac>;
        axistream-control-connected = <&eth_xlnx_mac>;
        clock-frequency = <CLOCK_FREQUENCY>;
        compatible = "xlnx,eth-dma";
        reg = <0x0 0x41e00000 0x0 0x10000>;
        xlnx,addrwidth = <0x40>;
        xlnx,include-dre;
        xlnx,num-queues = <0x1>;
        interrupt-parent = <&PLIC0>;
        // TX - RX
        interrupts = <8 9>;
    };
    eth_xlnx_mac: ethernet@40c00000 {
        axistream-connected = <&eth_xlnx_dma>;
        axistream-control-connected = <&eth_xlnx_dma>;
        clock-frequency = <CLOCK_FREQUENCY>;
        compatible = "xlnx,axi-ethernet-7.2", "xlnx,axi-ethernet-1.00.a";
        device_type = "network";
        local-mac-address = [00 18 3e 02 e3 7f];
        phy-mode = "rgmii";
        reg = <0x0 0x40c00000 0x0 0x40000>;
        xlnx = <0x0>;
        xlnx,axiliteclkrate = <0x0>;
        xlnx,axisclkrate = <0x0>;
        xlnx,channel-ids = "1";
        xlnx,clockselection = <0x0>;
        xlnx,enableasyncsgmii = <0x0>;
        xlnx,gt-type = <0x0>;
        xlnx,gtinex = <0x0>;
        xlnx,gtlocation = <0x0>;
        xlnx,gtrefclksrc = <0x0>;
        xlnx,include-dre;
        xlnx,instantiatebitslice0 = <0x0>;
        xlnx,num-queues =  <0x1>;
        xlnx,phyaddr = <0x1>;
        xlnx,phyrst-board-interface-dummy-port = <0x0>;
        xlnx,rable = <0x0>;
        xlnx,rxcsum = <0x2>;
        xlnx,rxlane0-placement = <0x0>;
        xlnx,rxlane1-placement = <0x0>;
        xlnx,rxmem = <0x1000>;
        xlnx,rxnibblebitslice0used = <0x0>;
        xlnx,tx-in-upper-nibble = <0x1>;
        xlnx,txcsum = <0x2>;
        xlnx,txlane0-placement = <0x0>;
        xlnx,txlane1-placement = <0x0>;
        xlnx,versal-gt-board-flow = <0x0>;
        phy-handle=<&eth_xlnx_phy0>;
        interrupt-parent = <&PLIC0>;
        interrupts = <3>;
        eth_xlnx_mdio: mdio {
          #address-cells = <1>;
          #size-cells = <0>;
          eth_xlnx_phy0: phy@1 {
            #address-cells = <1>;
            #size-cells = <0>;
            device-type="ethernet-phy";
            reg = <1>;
          };
        };
      };
    };

Ethernet activity LEDs

Constraints

# Link activity status
set_property -dict {PACKAGE_PIN L17  IOSTANDARD LVCMOS18} [get_ports link_st]                     ; # link_st_a, FMC_LPC_LA04_P
# Port a - board led (input)
set_property -dict {PACKAGE_PIN E18  IOSTANDARD LVCMOS18} [get_ports led_0_a]                     ; # led_0_a, FMC_LPC_LA08_P
# Port a - right led (activity/status)
set_property -dict {PACKAGE_PIN G18 IOSTANDARD LVCMOS18} [get_ports led_ar_c_c2m]                 ; # led_ar_c_c2m, FMC_LPC_LA12_P
set_property -dict {PACKAGE_PIN F18 IOSTANDARD LVCMOS18} [get_ports led_ar_a_c2m]                 ; # led_ar_a_c2m, FMC_LPC_LA12_N
# Port a - left led (activity/status)
set_property -dict {PACKAGE_PIN G15  IOSTANDARD LVCMOS18} [get_ports led_al_c_c2m]                ; # led_al_c_c2m, FMC_LPC_LA13_P
set_property -dict {PACKAGE_PIN F15  IOSTANDARD LVCMOS18} [get_ports led_al_a_c2m]                ; # led_al_a_c2m, FMC_LPC_LA13_N

#### Device Tree

  leds {
    compatible = "gpio-leds";
    eth0_led100M {
      gpios = <&xlnx_eth_gpio 0 GPIO_ACTIVE_HIGH>;
      default-state = "off";
      linux,default-trigger = "axienet-40c00000:01:100Mbps";
    };
    eth0_led1G {
      gpios = <&xlnx_eth_gpio 1 GPIO_ACTIVE_HIGH>;
      default-state = "off";
      linux,default-trigger = "axienet-40c00000:01:1Gbps";
    };
  };
L26: soc {
    ...
    xlnx_eth_gpio: gpio@40010000 {
      #gpio-cells = <2>;
      compatible = "xlnx,xps-gpio-1.00.a";
      gpio-controller;
      reg = <0x0 0x40010000 0x0 0x10000>;
      xlnx,all-inputs = <0x0>;
      xlnx,all-outputs = <0x1>;
      xlnx,dout-default = <0x0>;
      xlnx,gpio-width = <0x2>;
      xlnx,interrupt-present = <0x0>;
      xlnx,is-dual = <0x0>;
      xlnx,tri-default = <0xffffffff>;
    };
     ...
  };

(ZCU104 only) Change frequency

This part is not yet finished!

Block Design

Change the clock wizard’s output from 50 to 100 MHz.

OpenSBI

diff --git a/platform/fpga/ariane/platform.c b/platform/fpga/ariane/platform.c
index d25506a..d53e413 100644
--- a/platform/fpga/ariane/platform.c
+++ b/platform/fpga/ariane/platform.c
@@ -18,7 +18,7 @@
 #include <sbi_utils/timer/aclint_mtimer.h>

 #define ARIANE_UART_ADDR                       0x10000000
-#define ARIANE_UART_FREQ                       50000000
+#define ARIANE_UART_FREQ                       100000000
 #define ARIANE_UART_BAUDRATE                   115200
 #define ARIANE_UART_REG_SHIFT                  2
 #define ARIANE_UART_REG_WIDTH                  4

Device Tree

   cpus {
     #address-cells = <1>;
     #size-cells = <0>;
-    timebase-frequency = <25000000>; // 25 MHz
+    timebase-frequency = <50000000>; // 50 MHz
     CPU0: cpu@0 {
-      clock-frequency = <50000000>; // 50 MHz
+      clock-frequency = <100000000>; // 100 MHz
       device_type = "cpu";
       reg = <0>;
       status = "okay";
@@ -102,7 +102,7 @@
     uart@10000000 {
       compatible = "ns16550";
       reg = <0x0 0x10000000 0x0 0x1000>;
-      clock-frequency = <50000000>;
+      clock-frequency = <100000000>;
       current-speed = <115200>;
       interrupt-parent = <&PLIC0>;
       interrupts = <1>;
@@ -149,7 +149,7 @@
         #dma-cells = <1>;
         axistream-connected = <&eth_xlnx_mac>;
         axistream-control-connected = <&eth_xlnx_mac>;
-        clock-frequency = <50000000>;
+        clock-frequency = <100000000>;
         compatible = "xlnx,eth-dma";
         reg = <0x0 0x41e00000 0x0 0x10000>;
         xlnx,addrwidth = <0x40>;
@@ -162,7 +162,7 @@
     eth_xlnx_mac: ethernet0@40c00000 {
         compatible = "xlnx,axi-ethernet-7.2", "xlnx,axi-ethernet-1.00.a";
         interrupts = <3>;
-        clock-frequency = <50000000>;
+        clock-frequency = <100000000>;
         phy-mode = "rgmii-id";
         xlnx,rxcsum = <0x2>;
         xlnx,rxmem = <0x1000>;