# 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)

### pin

### MAC interface (rmii)

* 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

* Far Loopback

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

## eth-module
### pin map

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

### rst_gen

### eth_rst_gen

#### gen_50M

### packet_gen

#### 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

#### crc_gen

#### ether_header_gen

```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