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:
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.
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.
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.
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.
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.
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 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).
We will use Pmod extension modules for low-bandwidth peripherals: disk and serial (i.e. console).
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.
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.
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.
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).
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.
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.
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).
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.
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!
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:
riscv-none-elf-gcc-bin
from the AURexport RISCV=/opt/xpack/riscv-none-elf-gcc/
Others:
/opt/xpack/riscv-none-elf-gcc/
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!
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!
The CVA boot chain is composed of three elements:
Therefore, the CVA6 boot image is composed of 3 partitions:
/scratch
, for persistent data storage/!\ 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
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:
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:
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.
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:
./corev_apu/fpga/src/bootrom/cv64a6.dts.in
(CVA6 repo). git clean -xdf
is required after every modification for the change to be taken into account!./u-boot/arch/riscv/dts/cv64a6_pynq_z2.dts
(CVA6-sdk repo). make clean
is required after every modification for the change to be taken into account!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.
In the block design view, add a AXI UART 16550
controller and configure it in 16550 mode. Then, connect the following pins:
S_AXI
to the AXI interconnect block inside the northbridge one.s_axi_clk
to the clock generator’s clk_25
(25 MHz) pins_axi_aresetn
to the Processor System Reset’s peripherals_aresetn
pinfreeze
to a constant 0
valueUART.sin
to external uart_rxd
, and UART.sout
to external uart_txd
ip2intc_irpt
to uart_irq_i
input of ariane_peripherals
In the Address Editor
tab, set the /axi_uart16550_0/S_AXI
Master Base Address to 0x1000_0000
and range to 64K
.
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
-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 set_property
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
};
...
};
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.
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
Then, connect the following interfaces:
AXI_FULL
to the AXI interconnect block inside the northbridge one.ext_spi_clk
to the clock generator’s clk_50
pin (50 MHz, double of the maximum SPI frequency intended at the SPI interface).s_axi_aresetn
to the Processor System Reset’s peripherals_aresetn
pinfreeze
to a constant 0
valueSPI_0.io0_o
to external spi_mosi
, SPI_0.io1_i
to external spi_mosi
, SPI_0.ss_o
to external spi_clk_o
m and SPI_0.ss_o
to external spi_ss
ip2intc_irpt
to spi_irq_i
input of ariane_peripherals
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
.
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
-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 ] ; set_property
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;
};
};
...
};
This part is not yet finished!
The AXI bus is not really fault-tolerant…
This part is not yet finished!
This part is not yet finished!
Controller
Addresses
As we used board interfaces directly from the block design, constraints were automatically inserted. Yippee!
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 are piloted using the same GPIO interface than buttons. Repeat the same steps, but insert the following device tree nodes:
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>;
};
...
};
This part is not yet finished!
Controller
Addresses
## 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
-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
set_property -name mdio_clk_a -period 400.0 [get_ports mdio_mdio_io] ;
create_clock# Port a - Other
-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
set_property# Port a - RGMII
-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 set_property
L26: soc {
...
eth_xlnx_dma: dma@41e00000 {
#dma-cells = <1>;
axistream-connected = <ð_xlnx_mac>;
axistream-control-connected = <ð_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 = <ð_xlnx_dma>;
axistream-control-connected = <ð_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=<ð_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>;
};
};
};
};
# Link activity status
-dict {PACKAGE_PIN L17 IOSTANDARD LVCMOS18} [get_ports link_st] ; # link_st_a, FMC_LPC_LA04_P
set_property# Port a - board led (input)
-dict {PACKAGE_PIN E18 IOSTANDARD LVCMOS18} [get_ports led_0_a] ; # led_0_a, FMC_LPC_LA08_P
set_property# Port a - right led (activity/status)
-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
set_property# Port a - left led (activity/status)
-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 set_property
#### 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>;
};
...
};
This part is not yet finished!
Change the clock wizard’s output from 50 to 100 MHz.
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
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 = <ð_xlnx_mac>;
axistream-control-connected = <ð_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>;