# Ethernet device implemented in Chisel > 陳家揚 ## Environment ``` $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 46 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 22 On-line CPU(s) list: 0-21 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) Ultra 7 155H CPU family: 6 Model: 170 Thread(s) per core: 2 Core(s) per socket: 16 Socket(s): 1 Stepping: 4 CPU(s) scaling MHz: 21% CPU max MHz: 4800.0000 CPU min MHz: 400.0000 BogoMIPS: 5990.40 ``` * vivado 2024.1 Start vivado in ubuntu ```shell $ /tools/Xilinx/Vivado/2024.1/bin/vivado ``` ### [pynq-z2](https://www.e-elements.com.tw/products/fpga_system/xup_pynq/pynq-z2/) [Access to PHY module (Ethernet port) with PL](https://discuss.pynq.io/t/access-to-phy-module-ethernet-port-with-pl/3059) * Ethernet PHY > The PYNQ-Z2 has a Realtek RTL8211E-VL PHY supporting 10/100/1000 Ethernet. The PHY is connected to the Zynq RGMII controller. The auxiliary interrupt (INTB) and reset (PHYRSTB) signals connect to MIO pins MIO10 and MIO9, respectively. One of the Zynq PS Ethernet controllers can be connected to the appropriate MIO pins to control the Ethernet port. >The Zynq does not need to be configured for the PHY to establish a connection. After power-up the PHY starts with Auto Negotiation enabled, advertising 10/100/1000 link speeds and full duplex. The PHY will automatically establishes a link if there is an Ethernet-capable partner connected. Since the Ethernet interface of the PYNQ-Z2 is connected to the PS (Processing System) side, it is necessary to externally connect an Ethernet module to the PL (Programmable Logic) side. The module I chose is the `LAN8720`, which supports `RMII (Reduced Media Independent Interface)` interface. ## [LAN8720](https://ww1.microchip.com/downloads/en/devicedoc/8720a.pdf) ![image](https://hackmd.io/_uploads/S1LrkZIDkl.png =70%x) ### pin ![image](https://hackmd.io/_uploads/HkTq0bUv1g.png =70%x) ### MAC interface (rmii) ![image](https://hackmd.io/_uploads/rJYHBzUvyl.png =70%x) * support 10Mbps / 100Mbps data rates * A single clock reference is used for both transmit and receive * 2-bit (di-bit) wide transmit and receive data path transmit data - TXD[1:0] transmit strobe - TXEN receive data - RXD[1:0] receive error - RXER (Optional) carrier sense - CRS_DV Reference Clock - (RMII references usually define this signal as REF_CLK) ### Loop back * Near-end Loopback ![image](https://hackmd.io/_uploads/H1bXwZUwJx.png =70%x) * Far Loopback ![image](https://hackmd.io/_uploads/B1uSP-Lvkl.png =70%x) The far loopback mode is enabled by setting the FARLOOPBACK bit of the `Mode Control/Status Register` to “1”. In this mode, data that is received from the link partner on the MDI is looped back out to the link partner. * Connector Loopback ![image](https://hackmd.io/_uploads/rkuUvWIwye.png =70%x) ## eth-module ### pin map ![image](https://hackmd.io/_uploads/S1jHjtIDyl.png) pynq|LAN8720 -|- A20|TX1 W9|NC B19|TX-EN Y8|TX0 B20|RX0 Y7|RX1 Y17|CLK Y16|CRS F19|MDIO W10|MDC ### constraint file ``` set_property -dict { PACKAGE_PIN F19 IOSTANDARD LVCMOS33 } [get_ports { MDIO }]; #IO_L12N_T1_MRCC_34 Sch=rpio_08_r set_property -dict { PACKAGE_PIN W10 IOSTANDARD LVCMOS33 } [get_ports { MDC }]; #IO_L16P_T2_13 Sch=rpio_11_r set_property -dict { PACKAGE_PIN B20 IOSTANDARD LVCMOS33 } [get_ports { RX0 }]; #IO_L1N_T0_AD0N_35 Sch=rpio_12_r set_property -dict { PACKAGE_PIN B19 IOSTANDARD LVCMOS33 } [get_ports { TX-EN }]; #IO_L2P_T0_AD8P_35 Sch=rpio_16_r set_property -dict { PACKAGE_PIN Y8 IOSTANDARD LVCMOS33 } [get_ports { TX0 }]; #IO_L14N_T2_SRCC_13 Sch=rpio_19_r set_property -dict { PACKAGE_PIN A20 IOSTANDARD LVCMOS33 } [get_ports { TX1 }]; #IO_L2N_T0_AD8N_35 Sch=rpio_20_r set_property -dict { PACKAGE_PIN Y7 IOSTANDARD LVCMOS33 } [get_ports { RX1 }]; #IO_L13P_T2_MRCC_13 Sch=rpio_24_r set_property -dict { PACKAGE_PIN W9 IOSTANDARD LVCMOS33 } [get_ports { NC }]; #IO_L16N_T2_13 Sch=rpio_26_r set_property -dict { PACKAGE_PIN Y16 IOSTANDARD LVCMOS33 } [get_ports { CRS }]; #IO_L7P_T1_34 Sch=rpio_sd_r set_property -dict { PACKAGE_PIN Y17 IOSTANDARD LVCMOS33 } [get_ports { CLK }]; #IO_L7N_T1_34 Sch=rpio_sc_r ``` ### Netlist ![image](https://hackmd.io/_uploads/B1wi2Vswkg.png) ### rst_gen ![image](https://hackmd.io/_uploads/H114YUsv1l.png) ### eth_rst_gen ![image](https://hackmd.io/_uploads/HyvFKIiDyg.png) #### gen_50M ![image](https://hackmd.io/_uploads/B1llat8iDJl.png) ### packet_gen ![image](https://hackmd.io/_uploads/SJ9E5Lov1l.png) #### data of the packets In this Eth-packet generator, a timer is used to generate (simulate) the content of the data payload. The value of `packet_timer` is then assigned to the 8-bit wide `s_axis_tdata`, which serves as the input to `data_fifo` module. ```verilog logic [63:0] packet_timer; // increment the timer and create an enable pulse when reaching max always_ff@(posedge eth_clk) begin if (eth_rst == 1) begin packet_timer <= 0; packet_enable <= 0; end else begin packet_enable <= 0; if (packet_timer == packet_max) begin packet_timer <= 0; packet_enable <= 1; end else begin packet_timer <= packet_timer + 1; end end end ``` #### finite state machine * IDLE * At this stage, the module continues accumulating data from the `data_fifo` module until the total amount exceeds the payload size. * PREAMBLE * At this stage, the `preamble` field of the packet is transmitted. * SFD * At this stage, the `SFD` field of the packet is transmitted. * HEADER * At this stage, the `HEADER` field of the packet is transmitted. * DATA * At this stage, data transmission occurs. * FCS * Transmit the frame check sequence. * WAIT * Waiting for the next transmission. state|tx_valid|tx_data|fcs_en|fcs_rst -|-|-|-|- IDLE|0|0|0|1 PREAMBLE|1|preamble_buffer[RMII_WIDTH-1:0]|0|0 SFD|1|sfd_buffer[RMII_WIDTH-1:0]|0|0 HEADER|1|header_buffer[RMII_WIDTH-1:0]|1|0 DATA|1|data_buffer[RMII_WIDTH-1:0]|1|0 FCS|1|fcs_buffer[RMII_WIDTH-1:0]|0|0 WAIT|0|0|0|0 Since the RMII data width is 2 bits, transmitting a header requires $\frac{HEADER\_BYTES*8}{RMII\_WIDTH}$ iterations, where RMII_WIDTH = 2. ```verilog localparam HEADER_LENGTH = HEADER_BYTES*8/RMII_WIDTH; ``` #### data_fifo ![image](https://hackmd.io/_uploads/Hy8n9IsPkl.png) #### crc_gen ![image](https://hackmd.io/_uploads/ByD0q8sDJl.png) #### ether_header_gen ![image](https://hackmd.io/_uploads/BJh-iUiv1x.png) ```verilog typedef struct packed { // Ethernet Frame Header // no FCS, added later logic [1:0][7:0] eth_type_length; logic [5:0][7:0] mac_source; logic [5:0][7:0] mac_destination; } ethernet_header; ``` ## test script :::danger Illustrate the test plan and procedures. ::: I plan to use the following tools to capture the packets generated by the Ethernet module. * scapy * wireshark ```shell $ ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: wlp85s0f0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DORMANT group default qlen 1000 link/ether 6c:f6:da:84:33:ed brd ff:ff:ff:ff:ff:ff 4: enx00e04c684eaf: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 00:e0:4c:68:4e:af brd ff:ff:ff:ff:ff:ff ``` `test.py` ```python from scapy.all import Ether, sendp, sniff, Raw pc_mac = "00:e0:4c:68:4e:af" fpga_mac = "00:11:22:33:44:55" # Construct eth-frame pkt = Ether(dst=fpga_mac, src=pc_mac) / b"Hello FPGA!" # Transmit to certain network interface sendp(pkt, iface="enx00e04c684eaf") # Construct eth-frame with raw payload pkt = Ether(dst=fpga_mac, src=pc_mac) / Raw(b"Hello FPGA!") # Transmit to certain network interface sendp(pkt, iface="enx00e04c684eaf") ``` `sni.py` ```python sniff(iface="enx00e04c684eaf", count=2, prn=lambda x: x.summary()) ``` ```shell $ sudo python3 test.py ``` ```shell $ sudo python3 sni.py ``` ### result