examples: add practical UDP loopback example with Versa ECP5

This commit is contained in:
Yehowshua Immanuel 2019-11-19 09:54:23 -05:00 committed by Florent Kermarrec
parent d2eb870445
commit 50cc7d0671
6 changed files with 203 additions and 1 deletions

View File

@ -14,3 +14,4 @@ Copyright (c) 2015-2019 Florent Kermarrec <florent@enjoy-digital.fr>
Copyright (c) 2016-2017 Tim 'mithro' Ansell <mithro@mithis.com>
Copyright (c) 2015-2017 Sebastien Bourdeauducq <sb@m-labs.hk>
Copyright (c) 2017-2018 whitequark <whitequark@whitequark.org>
Copyright (c) 2019 Yehowshua <yimmanuel3@gatech.edu>

3
README
View File

@ -61,7 +61,8 @@ enjoy-digital.fr.
python3 setup.py develop
cd ..
3. TODO: add/describe examples
3. Check out /examples/versa_ecp5_udp_loopback for a good practical example of how to get
started with the Liteeth core solo in an FPGA.
[> Tests
--------

View File

@ -0,0 +1,46 @@
## 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

@ -0,0 +1,15 @@
# 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

@ -0,0 +1,15 @@
# 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

@ -0,0 +1,124 @@
#!/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()