example/udp_loopback: simplify/cleanup and make it more generic

This commit is contained in:
Florent Kermarrec 2019-11-20 08:53:16 +01:00
parent 50cc7d0671
commit e980b603cc
8 changed files with 186 additions and 200 deletions

View File

@ -0,0 +1,33 @@
## Purpose
Example of UDP loopback with LiteETH IP/UDP hardware stack. The FPGA will echo back any UDP packet it
receives on the specified port (default=8000) to the sender.
You can also found a detailed tutorial [here](https://yehowshuaimmanuel.com/fpga/migen/ethernet_ecp5/).
## Usage
#!bash
./versa_ecp5.py
./versa_ecp5.py load
The IP address assigned to the FPGA in this example is ``192.168.1.50`` and the Host is expected to
be configured with ``192.168.1.100``. Since ``192.168.1.XXX`` is a common address in home networks, a
collisiton is quite possible and in this case, you will need to re-configure the FPGA and the python
scripts accordingly.
Once you are able to ping the FPGA board from your computer, you can run the sender and listener scripts
and should see the date/time UDP packets emitted by the sender looped back to the the listener:
#!bash
$python3 listener.py &
$python3 sender.py
2019-11-20 08:31:00
2019-11-20 08:31:01
2019-11-20 08:31:01
2019-11-20 08:31:02
2019-11-20 08:31:02
2019-11-20 08:31:03
2019-11-20 08:31:03
2019-11-20 08:31:04
2019-11-20 08:31:04
[...]

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# This file is Copyright (c) 2019 Yehowshua Immanuel <yimmanuel3@gatech.edu>
# License: BSD
import socket
UDP_IP = "192.168.1.50"
UDP_PORT = 8000
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))
while True:
data, addr = sock.recvfrom(1024)
print(data.decode("utf-8"))

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
# This file is Copyright (c) 2019 Yehowshua Immanuel <yimmanuel3@gatech.edu>
# License: BSD
import socket
import time
import datetime
UDP_IP = "192.168.1.100"
UDP_PORT = 8000
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
t = datetime.datetime.fromtimestamp(time.time()).strftime("%Y-%m-%d %H:%M:%S")
print(t)
sock.sendto(t.encode('utf-8'), (UDP_IP, UDP_PORT))
time.sleep(0.5)

View File

@ -0,0 +1,118 @@
#!/usr/bin/env python3
# This file is Copyright (c) 2019 Yehowshua Immanuel <yimmanuel3@gatech.edu>
# This file is Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
# License: BSD
import sys
from migen import *
from litex.build.generic_platform import *
from litex.boards.platforms import versa_ecp5
from litex.soc.cores.clock import *
from litex.soc.integration.soc_core import *
from litex.soc.integration.builder import *
from liteeth.common import *
from liteeth.phy.ecp5rgmii import LiteEthPHYRGMII
from liteeth.core import LiteEthUDPIPCore
# CRG ----------------------------------------------------------------------------------------------
class _CRG(Module):
def __init__(self, platform, sys_clk_freq):
self.clock_domains.cd_sys = ClockDomain()
# # #
self.cd_sys.clk.attr.add("keep")
# clk / rst
clk100 = platform.request("clk100")
platform.add_period_constraint(clk100, 1e9/100e6)
# pll
self.submodules.pll = pll = ECP5PLL()
pll.register_clkin(clk100, 100e6)
pll.create_clkout(self.cd_sys, sys_clk_freq)
# UDPLoopback ------------------------------------------------------------------------------------------
class UDPLoopback(SoCMini):
def __init__(self, platform):
sys_clk_freq = int(150e6)
SoCMini.__init__(self, platform, sys_clk_freq, ident="UDPLoopback", ident_version=True)
# CRG --------------------------------------------------------------------------------------
self.submodules.crg = _CRG(platform, sys_clk_freq)
# Ethernet ---------------------------------------------------------------------------------
# phy
self.submodules.eth_phy = LiteEthPHYRGMII(
clock_pads = platform.request("eth_clocks"),
pads = platform.request("eth"))
self.add_csr("eth_phy")
# core
self.submodules.eth_core = LiteEthUDPIPCore(
phy = self.eth_phy,
mac_address = 0x10e2d5000000,
ip_address = "192.168.1.50",
clk_freq = sys_clk_freq)
# add udp loopback on port 6000 with dw=8
self.add_udp_loopback(6000, 8, 128, "loopback_8")
# add udp loopback on port 8000 with dw=32
self.add_udp_loopback(8000, 32, 128, "loopback_32")
# timing constraints
self.eth_phy.crg.cd_eth_rx.clk.attr.add("keep")
self.eth_phy.crg.cd_eth_tx.clk.attr.add("keep")
self.platform.add_period_constraint(self.eth_phy.crg.cd_eth_rx.clk, 1e9/125e6)
self.platform.add_period_constraint(self.eth_phy.crg.cd_eth_tx.clk, 1e9/125e6)
def add_udp_loopback(self, port, dw, depth, name=None):
port = self.eth_core.udp.crossbar.get_port(port, dw)
buf = stream.SyncFIFO(eth_udp_user_description(dw), depth//(dw//8))
if name is None:
self.submodules += buf
else:
setattr(self.submodules, name, buf)
self.comb += Port.connect(port, buf)
# Load ---------------------------------------------------------------------------------------------
def load():
import os
f = open("ecp5-versa5g.cfg", "w")
f.write(
"""
interface ftdi
ftdi_vid_pid 0x0403 0x6010
ftdi_channel 0
ftdi_layout_init 0xfff8 0xfffb
reset_config none
adapter_khz 25000
jtag newtap ecp5 tap -irlen 8 -expected-id 0x81112043
""")
f.close()
os.system("openocd -f ecp5-versa5g.cfg -c \"transport select jtag; init; svf build/gateware/top.svf; exit\"")
# Build --------------------------------------------------------------------------------------------
def main():
if "load" in sys.argv[1:]:
load()
exit()
else:
platform = versa_ecp5.Platform(toolchain="trellis")
soc = UDPLoopback(platform)
builder = Builder(soc, output_dir="build", csr_csv="tools/csr.csv")
vns = builder.build()
if __name__ == "__main__":
main()

View File

@ -1,46 +0,0 @@
## Purpose
Example of UDP loopback on versa ECP5 FPGA using the Liteeth UDP module.
The FPGA will echo back any UDP packet it recieves over RJ45 ethernet to the sender.
You can also view a rather detailed tutorial [here](https://yehowshuaimmanuel.com/fpga/migen/ethernet_ecp5/).
## Usage
#!bash
./udp.py build
./udp.py load
You will have to configure your ARP table manually since this example does not instantiate the Liteeth ARP module table.
The IP address assigned to the FPGA in this example is ``169.253.2.100``. You should make sure that your computer's
ethernet interface is on the same subnet, for example:
#!bash
$ifconfig en7 192.168.1.100 netmask 255.255.255.0
And then after that configure your ARP table:
#!bash
$arp -s 192.168.1.50 10:e2:d5:00:00:00 -iface en7
You should now be able to send and recieve UDP packets.
#!bash
$python3 listener.py &
$python3 sender.py
UDP target IP:192.168.1.50
UDP target port:8000
message:Hey.
received message:b'Heyn'
If everything is working, you should be able to see the ``received message`` line as shown above.
## Possible Problems
192.168.1.XXX is a common address in home networks a collsion is quite possible.
In particular, some ARP daemons will automatically add a higher priority duplicate IP entry for 192.168.1.50 to the Wi-Fi
interface making it challenging to route packets with the ethernet as the gateway.
To get around this, change the IP address of your ethernet and FPGA to something different from 192. You must change this
in in all the Python files in this directory.

View File

@ -1,15 +0,0 @@
# This file is Copyright (c) 2019 Yehowshua Immanuel <yimmanuel3@gatech.edu>
# License: BSD
import socket
UDP_IP = "192.168.1.100"
UDP_PORT = 8000
sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP, UDP_PORT))
while True:
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
print("received message:" + str(data))

View File

@ -1,15 +0,0 @@
# This file is Copyright (c) 2019 Yehowshua Immanuel <yimmanuel3@gatech.edu>
# License: BSD
import socket
UDP_IP = "192.168.1.50"
UDP_PORT = 8000
MESSAGE = "Hey."
print("UDP target IP:" + str(UDP_IP))
print("UDP target port:" + str(UDP_PORT))
print("message:" + str(MESSAGE))
sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
sock.sendto(MESSAGE.encode('utf-8'), (UDP_IP, UDP_PORT))

View File

@ -1,124 +0,0 @@
#!/usr/bin/env python3
# This file is Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr>
# This file is Copyright (c) 2019 Yehowshua Immanuel <yimmanuel3@gatech.edu>
# License: BSD
import sys
from migen import *
from migen.genlib.resetsync import AsyncResetSynchronizer
from litex.build.generic_platform import *
from litex.boards.platforms import versa_ecp5
from litex.soc.cores.clock import *
from litex.soc.integration.soc_core import *
from litex.soc.integration.builder import *
from liteeth.phy.ecp5rgmii import LiteEthPHYRGMII
from liteeth.core import LiteEthUDPIPCore
from liteeth.common import *
# CRG ----------------------------------------------------------------------------------------------
class _CRG(Module):
def __init__(self, platform, sys_clk_freq):
self.clock_domains.cd_sys = ClockDomain()
# # #
self.cd_sys.clk.attr.add("keep")
# clk / rst
clk100 = platform.request("clk100")
platform.add_period_constraint(clk100, 1e9/100e6)
# pll
self.submodules.pll = pll = ECP5PLL()
pll.register_clkin(clk100, 100e6)
pll.create_clkout(self.cd_sys, sys_clk_freq)
# UDPLoopback ------------------------------------------------------------------------------------------
class UDPLoopback(SoCMini):
def __init__(self, platform):
sys_clk_freq = int(150e6)
SoCMini.__init__(self, platform, sys_clk_freq, ident="UDPLoopback", ident_version=True)
# CRG --------------------------------------------------------------------------------------
self.submodules.crg = _CRG(platform, sys_clk_freq)
# Ethernet ---------------------------------------------------------------------------------
# phy
self.submodules.eth_phy = LiteEthPHYRGMII(
clock_pads = platform.request("eth_clocks"),
pads = platform.request("eth"))
self.add_csr("eth_phy")
# core
self.submodules.eth_core = LiteEthUDPIPCore(
phy = self.eth_phy,
mac_address = 0x10e2d5000000,
ip_address = "192.168.1.50",
clk_freq = sys_clk_freq)
# add udp loopback on port 6000 with dw=8
self.add_udp_loopback(6000, 8, 128, "loopback_8")
# add udp loopback on port 8000 with dw=32
self.add_udp_loopback(8000, 32, 128, "loopback_32")
# timing constraints - generates top.lpf
self.eth_phy.crg.cd_eth_rx.clk.attr.add("keep")
self.eth_phy.crg.cd_eth_tx.clk.attr.add("keep")
self.platform.add_period_constraint(self.eth_phy.crg.cd_eth_rx.clk, 1e9/125e6)
self.platform.add_period_constraint(self.eth_phy.crg.cd_eth_tx.clk, 1e9/125e6)
def add_udp_loopback(self, port, dw, depth, name=None):
port = self.eth_core.udp.crossbar.get_port(port, dw)
buf = stream.SyncFIFO(eth_udp_user_description(dw), depth//(dw//8))
if name is None:
self.submodules += buf
else:
setattr(self.submodules, name, buf)
self.comb += Port.connect(port, buf)
# Load ---------------------------------------------------------------------------------------------
def load():
import os
f = open("ecp5-versa5g.cfg", "w")
f.write(
"""
interface ftdi
ftdi_vid_pid 0x0403 0x6010
ftdi_channel 0
ftdi_layout_init 0xfff8 0xfffb
reset_config none
adapter_khz 25000
jtag newtap ecp5 tap -irlen 8 -expected-id 0x81112043
""")
f.close()
os.system("openocd -f ecp5-versa5g.cfg -c \"transport select jtag; init; svf build/gateware/top.svf; exit\"")
# Build --------------------------------------------------------------------------------------------
def main():
if "load" in sys.argv[1:]:
load()
exit()
if "build" in sys.argv[1:]:
platform = versa_ecp5.Platform(toolchain="trellis")
soc = UDPLoopback(platform)
builder = Builder(soc, output_dir="build", csr_csv="tools/csr.csv")
vns = builder.build()
else:
print("Usage:")
print("./udp.py build")
print("./udp.py load")
if __name__ == "__main__":
main()