From 31aea6fc4a70414ad91e53d6bd713328e06964ca Mon Sep 17 00:00:00 2001 From: Fischer Moseley <42497969+fischermoseley@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:18:52 -0700 Subject: [PATCH] ethernet: add initial (working!) amaranth-native design --- examples/amaranth/ethernet_io_core.py | 125 ++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 examples/amaranth/ethernet_io_core.py diff --git a/examples/amaranth/ethernet_io_core.py b/examples/amaranth/ethernet_io_core.py new file mode 100644 index 0000000..45cfbe2 --- /dev/null +++ b/examples/amaranth/ethernet_io_core.py @@ -0,0 +1,125 @@ +from time import sleep + +from amaranth import * +from amaranth.lib import io + +from manta import * + + +class EthernetIOCoreExample(Elaboratable): + def __init__(self, platform, port): + self.platform = platform + + # Create Manta instance + self.manta = Manta() + + # Configure it to communicate over Ethernet + self.manta.interface = EthernetInterface( + phy="LiteEthPHYRMII", + device="xc7a", + vendor="xilinx", + toolchain="vivado", + refclk_freq=50e6, + clk_freq = 50e6, + fpga_ip_addr = "10.0.0.2", + host_ip_addr = "10.0.0.1", + udp_port=2000, + ) + + # Autodetect the number of LEDs on the platform + resources = platform.resources.keys() + self.n_leds = max([i for name, i in resources if name == "led"]) + + # Add IOCore to Manta instance + self.leds = Signal(self.n_leds) + self.manta.cores.io = IOCore(outputs=[self.leds]) + + def elaborate(self, platform): + m = Module() + + # Create 50MHz clock domain + m.domains.ethclk = ethclk = ClockDomain() + m.submodules.divider = Instance( + "divider", + ("i", "clk", ClockSignal()), + ("o", "ethclk", ethclk.clk), + ) + platform.add_file("divider.sv", open("divider.sv")) + + # Add Manta as a submodule + m.submodules.manta = DomainRenamer("ethclk")(self.manta) + + # Wire each LED to Manta's IO Core output + for i in range(self.n_leds): + led = io.Buffer("o", platform.request("led", i, dir="-")) + m.d.comb += led.o.eq(self.leds[i]) + m.submodules += led + + # Wire Ethernet pins to the Manta instance + + # This is only required for Amaranth < 0.5.2 + eth_pin_names = ["mdio", "mdc", "reset", "rxd", "rxerr", "txd", "txen", "crs_dv", "int", "clk"] + eth_pin_dirs = {name: "-" for name in eth_pin_names} + eth_pins = platform.request("eth", dir=eth_pin_dirs) + + # For Amaranth > 0.5.2, this simpler syntax may be used: + # eth_pins = platform.request("eth") + + # self.manta.interface.set_phy_io( + # rmii_clocks_ref_clk = eth_pins.clk, + # rmii_rst_n = eth_pins.reset, + # rmii_rx_data = eth_pins.rxd, + # rmii_crs_dv = eth_pins.crs_dv, + # rmii_tx_en = eth_pins.txen, + # rmii_tx_data = eth_pins.txd, + # rmii_mdc = eth_pins.mdc, + # rmii_mdio = eth_pins.mdio, + # ) + + # m.submodules.rxd = rxd = io.Buffer("i", eth_pins.rxd) + # m.submodules.crs_dv = crs_dv = io.Buffer("i", eth_pins.crs_dv) + # m.submodules.txen = txen = io.Buffer("o", eth_pins.txen) + # m.submodules.txd = txd = io.Buffer("o", eth_pins.txd) + # m.submodules.mdc = mdc = io.Buffer("o", eth_pins.mdc) + # m.submodules.mdio = mdio = io.SingleEndedPort(io.Buffer("io", eth_pins.mdio)) + + m.submodules.eth_clk_io_buf = eth_clk_io_buf = io.Buffer("o", eth_pins.clk) + m.d.comb += eth_clk_io_buf.o.eq(ethclk.clk) + + self.manta.interface._phy_io = [ + ("i", "rmii_clocks_ref_clk", ethclk.clk), + ("o", "rmii_rst_n", eth_pins.reset.io), + ("i", "rmii_rx_data", eth_pins.rxd.io), + ("i", "rmii_crs_dv", eth_pins.crs_dv.io), + ("o", "rmii_tx_en", eth_pins.txen.io), + ("o", "rmii_tx_data", eth_pins.txd.io), + ("o", "rmii_mdc", eth_pins.mdc.io), + ("io", "rmii_mdio", eth_pins.mdio.io), + ] + + platform.add_file("liteeth.v", self.manta.interface.generate_liteeth_core()) + + return m + + def test(self): + # Build and program the FPGA + # self.platform.build(self, do_program=True) + + # Iterate through all the LEDs, blinking them off and on + i = 0 + while True: + self.manta.cores.io.set_probe("leds", 1 << i) + i = (i + 1) % self.n_leds + sleep(0.1) + + +# Amaranth has a built-in build system, and well as a set of platform +# definitions for a huge number of FPGA boards. The class defined above is +# very generic, as it specifies a design independent of any particular FGPA +# board. This means that by changing which platform you pass UARTIOCoreExample +# below, you can port this example to any FPGA board! + +if __name__ == "__main__": + from amaranth_boards.nexys4ddr import Nexys4DDRPlatform + + EthernetIOCoreExample(platform=Nexys4DDRPlatform(), port="auto").test()