If you have arrived to this point, congratulation! This section assumes that you now master Vivado, so the explanations will be shorter.
As seen previously, Ethernet support is brought by an FMC adapter integrating the PHY layer. Therefore, the MAC layer need to be integrated on the FPGA, as well as a DMA IP for faster RAM access, and the usual address / constraint setup.
The structure of the Ethernet controller blocks is the following (to be integrated in the northbridge):
AXI interfaces must be connected to the northbridge (common bus), else: - mac_irq
is left unconnected - mm2s_introut
and s2mm_introut
are wired to ariane_peripherals_0
’s irq[7]
and irq[8]
- eth_interrupt
is wired to ariane_peripherals_0's
eth_irq_i` - all the other signals are external (to/from the card)
As the Ethernet controller has DMA capabilities, two new AXI masters are connected to the northbridge: ETH_DMA_M_AXI_SG
and ETH_DMA_M_AXI
, as well as two slaves: ETH_DMA_S_AXI
(AXILite DMA configuration) and ETH_S_AXI
(Ethernet configuration). Therefore:
Use the following 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
-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
Here is the corresponding documentation for the device tree bindings:
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>;
};
};
};
};
To correctly set up the Ethernel LED, we need to:
led_al_a_c2m
and led_al_c_c2m
, and set in the device tree that the left led color indicates the speed of the connectionled_ar_a_c2m
to constant 0 (single-color right led)led_0_a
to a new output led_ar_c_c2m
(right led indicates presence)Here is the corresponding constraints to have functioning Ethernet LEDs, using the signal names specified above:
# 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 As we use a GPIO controller for the LEDs, the bindings are similar than the one of section 6.
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>;
};
...
};
While changing frequency is thought to be simple, it can actually influence other components such as UART, that relies on clock frequency for time measurement. Here, we will see how to inform the software that the frequency has changed, and act accordingly.
/!\ This section requires changing the boot image. Follow the Bonus: Linux Image section first (~1-2h) before to have the necessary toolchain!
The provided project has already separated clock domains between the fixed 25 MHz clock (used for SD card accesses), the DDR 300 MHz clock and the CVA6 internal clock (50 MHz bt default). The only thing to do in hardware is to change the clock wizard’s output from 50 to 100 MHz. Neat!
As OpenSBI writes to the UART, it needs to be patched to be aware of the new bus frequency. Here is the corresponding patch:
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
Other components must also be advertised as running at 100 MHz at the device tree level: UART and Ethernet. Here is the corresponding patch:
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>;