From: Florent Kermarrec Date: Wed, 11 Nov 2015 12:56:17 +0000 (+0100) Subject: soc/cores: reintroduce liteeth_mini (until we switch to liteeth) X-Git-Tag: 24jan2021_ls180~2084 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=481163b233b561bec74de51086ab696e9c7bb1cc;p=litex.git soc/cores: reintroduce liteeth_mini (until we switch to liteeth) --- diff --git a/litex/boards/targets/kc705.py b/litex/boards/targets/kc705.py index 3d2af43c..9cef2f1d 100644 --- a/litex/boards/targets/kc705.py +++ b/litex/boards/targets/kc705.py @@ -13,9 +13,9 @@ from litex.soc.integration.soc_core import mem_decoder from litex.soc.integration.soc_sdram import * from litex.soc.integration.builder import * -from liteeth.phy import LiteEthPHY -from liteeth.core.mac import LiteEthMAC - +# TODO: use liteeth +from litex.soc.cores.liteeth_mini.phy import LiteEthPHY +from litex.soc.cores.liteeth_mini.mac import LiteEthMAC class _CRG(Module): def __init__(self, platform): diff --git a/litex/boards/targets/simple.py b/litex/boards/targets/simple.py index d1aed7bb..2c24cba0 100644 --- a/litex/boards/targets/simple.py +++ b/litex/boards/targets/simple.py @@ -9,8 +9,9 @@ from migen.genlib.io import CRG from litex.soc.integration.soc_core import * from litex.soc.integration.builder import * -from liteeth.phy import LiteEthPHY -from liteeth.core.mac import LiteEthMAC +# TODO: use liteeth +from litex.soc.cores.liteeth_mini.phy import LiteEthPHY +from litex.soc.cores.liteeth_mini.mac import LiteEthMAC class BaseSoC(SoCCore): diff --git a/litex/soc/cores/liteeth_mini/LICENSE b/litex/soc/cores/liteeth_mini/LICENSE new file mode 100644 index 00000000..cbbfe8be --- /dev/null +++ b/litex/soc/cores/liteeth_mini/LICENSE @@ -0,0 +1,28 @@ +Unless otherwise noted, LiteEth is copyright (C) 2015 Florent Kermarrec. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Other authors retain ownership of their contributions. If a submission can +reasonably be considered independently copyrightable, it's yours and we +encourage you to claim it with appropriate copyright notices. This submission +then falls under the "otherwise noted" category. All submissions are strongly +encouraged to use the two-clause BSD license reproduced above. diff --git a/litex/soc/cores/liteeth_mini/README b/litex/soc/cores/liteeth_mini/README new file mode 100644 index 00000000..595d2fc9 --- /dev/null +++ b/litex/soc/cores/liteeth_mini/README @@ -0,0 +1,58 @@ + __ _ __ ______ __ __ ____ _ + / / (_) /____ / __/ /_/ / / |/ (_)__ (_) + / /__/ / __/ -_) _// __/ _ \/ /|_/ / / _ \/ / + /____/_/\__/\__/___/\__/_//_/_/ /_/_/_//_/_/ + + Copyright 2012-2015 / EnjoyDigital / M-Labs Ltd + + A small footprint and configurable minimal Ethernet core + powered by Migen + +[> Intro +--------- +LiteEthMini is a subset of LiteEth (https://github.com/enjoy-digital/liteeth) +intended to be used with a CPU and a software stack. + +[> Features +----------- +- Ethernet MAC with various various PHYs (GMII, MII, RGMII, Loopback) +- SRAM storage and wishbone interface + +[> Possible improvements +------------------------- +- add DMA interface to MAC +- add SGMII PHY +- ... See below Support and consulting :) + +If you want to support these features, please contact us at florent [AT] +enjoy-digital.fr. You can also contact our partner on the public mailing list +devel [AT] lists.m-labs.hk. + +[> License +----------- +LiteEthMini is released under the very permissive two-clause BSD license. Under +the terms of this license, you are authorized to use LiteEthMini for closed-source +proprietary designs. +Even though we do not require you to do so, those things are awesome, so please +do them if possible: + - tell us that you are using LiteEthMini + - cite LiteEthMini in publications related to research it has helped + - send us feedback and suggestions for improvements + - send us bug reports when something goes wrong + - send us the modifications and improvements you have done to LiteEthMini. + +[> Support and consulting +-------------------------- +We love open-source hardware and like sharing our designs with others. + +LiteEthMini is mainly developed and maintained by EnjoyDigital. + +If you would like to know more about LiteEthMini or if you are already a happy +user and would like to extend it for your needs, EnjoyDigital can provide standard +commercial support as well as consulting services. + +So feel free to contact us, we'd love to work with you! (and eventually shorten +the list of the possible improvements :) + +[> Contact +E-mail: florent [AT] enjoy-digital.fr \ No newline at end of file diff --git a/litex/soc/cores/liteeth_mini/__init__.py b/litex/soc/cores/liteeth_mini/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/litex/soc/cores/liteeth_mini/common.py b/litex/soc/cores/liteeth_mini/common.py new file mode 100644 index 00000000..4638c76c --- /dev/null +++ b/litex/soc/cores/liteeth_mini/common.py @@ -0,0 +1,38 @@ +from migen import * +from migen.genlib.record import * + +from misoc.interconnect.csr import * +from misoc.interconnect.stream import * + + +class Port: + def connect(self, port): + r = [ + Record.connect(self.source, port.sink), + Record.connect(port.source, self.sink) + ] + return r + +eth_mtu = 1532 +eth_min_len = 46 +eth_interpacket_gap = 12 +eth_preamble = 0xD555555555555555 +buffer_depth = 2**log2_int(eth_mtu, need_pow2=False) + + +def eth_phy_description(dw): + payload_layout = [ + ("data", dw), + ("last_be", dw//8), + ("error", dw//8) + ] + return EndpointDescription(payload_layout, packetized=True) + + +def eth_mac_description(dw): + payload_layout = mac_header.get_layout() + [ + ("data", dw), + ("last_be", dw//8), + ("error", dw//8) + ] + return EndpointDescription(payload_layout, packetized=True) diff --git a/litex/soc/cores/liteeth_mini/mac/__init__.py b/litex/soc/cores/liteeth_mini/mac/__init__.py new file mode 100644 index 00000000..1338e637 --- /dev/null +++ b/litex/soc/cores/liteeth_mini/mac/__init__.py @@ -0,0 +1,25 @@ +from migen import * + +from litex.soc.interconnect.csr import * +from litex.soc.cores.liteeth_mini.common import * +from litex.soc.cores.liteeth_mini.mac.core import LiteEthMACCore +from litex.soc.cores.liteeth_mini.mac.frontend.wishbone import LiteEthMACWishboneInterface + + +class LiteEthMAC(Module, AutoCSR): + def __init__(self, phy, dw, + interface="wishbone", + endianness="big", + with_preamble_crc=True): + self.submodules.core = LiteEthMACCore(phy, dw, endianness, with_preamble_crc) + self.csrs = [] + if interface == "wishbone": + self.submodules.interface = LiteEthMACWishboneInterface(dw, 2, 2) + self.comb += Port.connect(self.interface, self.core) + self.ev, self.bus = self.interface.sram.ev, self.interface.bus + self.csrs = self.interface.get_csrs() + self.core.get_csrs() + else: + raise NotImplementedError + + def get_csrs(self): + return self.csrs diff --git a/litex/soc/cores/liteeth_mini/mac/core/__init__.py b/litex/soc/cores/liteeth_mini/mac/core/__init__.py new file mode 100644 index 00000000..82241f5d --- /dev/null +++ b/litex/soc/cores/liteeth_mini/mac/core/__init__.py @@ -0,0 +1,100 @@ +from migen import * + +from litex.soc.interconnect.csr import * +from litex.soc.cores.liteeth_mini.common import * +from litex.soc.cores.liteeth_mini.mac.core import gap, preamble, crc, padding, last_be +from litex.soc.cores.liteeth_mini.phy.mii import LiteEthPHYMII + + +class LiteEthMACCore(Module, AutoCSR): + def __init__(self, phy, dw, endianness="big", + with_preamble_crc=True, + with_padding=True): + if dw < phy.dw: + raise ValueError("Core data width({}) must be larger than PHY data width({})".format(dw, phy.dw)) + + rx_pipeline = [phy] + tx_pipeline = [phy] + + # Interpacket gap + tx_gap_inserter = gap.LiteEthMACGap(phy.dw) + rx_gap_checker = gap.LiteEthMACGap(phy.dw, ack_on_gap=True) + self.submodules += ClockDomainsRenamer("eth_tx")(tx_gap_inserter) + self.submodules += ClockDomainsRenamer("eth_rx")(rx_gap_checker) + + tx_pipeline += [tx_gap_inserter] + rx_pipeline += [rx_gap_checker] + + # Preamble / CRC + if with_preamble_crc: + self._preamble_crc = CSRStatus(reset=1) + # Preamble insert/check + preamble_inserter = preamble.LiteEthMACPreambleInserter(phy.dw) + preamble_checker = preamble.LiteEthMACPreambleChecker(phy.dw) + self.submodules += ClockDomainsRenamer("eth_tx")(preamble_inserter) + self.submodules += ClockDomainsRenamer("eth_rx")(preamble_checker) + + # CRC insert/check + crc32_inserter = crc.LiteEthMACCRC32Inserter(eth_phy_description(phy.dw)) + crc32_checker = crc.LiteEthMACCRC32Checker(eth_phy_description(phy.dw)) + self.submodules += ClockDomainsRenamer("eth_tx")(crc32_inserter) + self.submodules += ClockDomainsRenamer("eth_rx")(crc32_checker) + + tx_pipeline += [preamble_inserter, crc32_inserter] + rx_pipeline += [preamble_checker, crc32_checker] + + # Padding + if with_padding: + padding_inserter = padding.LiteEthMACPaddingInserter(phy.dw, 60) + padding_checker = padding.LiteEthMACPaddingChecker(phy.dw, 60) + self.submodules += ClockDomainsRenamer("eth_tx")(padding_inserter) + self.submodules += ClockDomainsRenamer("eth_rx")(padding_checker) + + tx_pipeline += [padding_inserter] + rx_pipeline += [padding_checker] + + # Delimiters + if dw != 8: + tx_last_be = last_be.LiteEthMACTXLastBE(phy.dw) + rx_last_be = last_be.LiteEthMACRXLastBE(phy.dw) + self.submodules += ClockDomainsRenamer("eth_tx")(tx_last_be) + self.submodules += ClockDomainsRenamer("eth_rx")(rx_last_be) + + tx_pipeline += [tx_last_be] + rx_pipeline += [rx_last_be] + + # Converters + if dw != phy.dw: + reverse = endianness == "big" + tx_converter = Converter(eth_phy_description(dw), + eth_phy_description(phy.dw), + reverse=reverse) + rx_converter = Converter(eth_phy_description(phy.dw), + eth_phy_description(dw), + reverse=reverse) + self.submodules += ClockDomainsRenamer("eth_tx")(tx_converter) + self.submodules += ClockDomainsRenamer("eth_rx")(rx_converter) + + tx_pipeline += [tx_converter] + rx_pipeline += [rx_converter] + + # Cross Domain Crossing + if isinstance(phy, LiteEthPHYMII): + fifo_depth = 8 + else: + fifo_depth = 64 + tx_cdc = AsyncFIFO(eth_phy_description(dw), fifo_depth) + rx_cdc = AsyncFIFO(eth_phy_description(dw), fifo_depth) + self.submodules += ClockDomainsRenamer({"write": "sys", "read": "eth_tx"})(tx_cdc) + self.submodules += ClockDomainsRenamer({"write": "eth_rx", "read": "sys"})(rx_cdc) + + tx_pipeline += [tx_cdc] + rx_pipeline += [rx_cdc] + + tx_pipeline_r = list(reversed(tx_pipeline)) + for s, d in zip(tx_pipeline_r, tx_pipeline_r[1:]): + self.comb += s.source.connect(d.sink) + for s, d in zip(rx_pipeline, rx_pipeline[1:]): + self.comb += s.source.connect(d.sink) + self.sink = tx_pipeline[-1].sink + self.source = rx_pipeline[-1].source diff --git a/litex/soc/cores/liteeth_mini/mac/core/crc.py b/litex/soc/cores/liteeth_mini/mac/core/crc.py new file mode 100644 index 00000000..05a7f00e --- /dev/null +++ b/litex/soc/cores/liteeth_mini/mac/core/crc.py @@ -0,0 +1,287 @@ +from collections import OrderedDict +from functools import reduce +from operator import xor + +from migen import * +from migen.genlib.misc import chooser + +from litex.soc.interconnect.stream import * + + +class LiteEthMACCRCEngine(Module): + """Cyclic Redundancy Check Engine + + Compute next CRC value from last CRC value and data input using + an optimized asynchronous LFSR. + + Parameters + ---------- + data_width : int + Width of the data bus. + width : int + Width of the CRC. + polynom : int + Polynom of the CRC (ex: 0x04C11DB7 for IEEE 802.3 CRC) + + Attributes + ---------- + data : in + Data input. + last : in + last CRC value. + next : + next CRC value. + """ + def __init__(self, data_width, width, polynom): + self.data = Signal(data_width) + self.last = Signal(width) + self.next = Signal(width) + + # # # + + def _optimize_eq(l): + """ + Replace even numbers of XORs in the equation + with an equivalent XOR + """ + d = OrderedDict() + for e in l: + if e in d: + d[e] += 1 + else: + d[e] = 1 + r = [] + for key, value in d.items(): + if value%2 != 0: + r.append(key) + return r + + # compute and optimize CRC's LFSR + curval = [[("state", i)] for i in range(width)] + for i in range(data_width): + feedback = curval.pop() + [("din", i)] + for j in range(width-1): + if (polynom & (1<<(j+1))): + curval[j] += feedback + curval[j] = _optimize_eq(curval[j]) + curval.insert(0, feedback) + + # implement logic + for i in range(width): + xors = [] + for t, n in curval[i]: + if t == "state": + xors += [self.last[n]] + elif t == "din": + xors += [self.data[n]] + self.comb += self.next[i].eq(reduce(xor, xors)) + + +@ResetInserter() +@CEInserter() +class LiteEthMACCRC32(Module): + """IEEE 802.3 CRC + + Implement an IEEE 802.3 CRC generator/checker. + + Parameters + ---------- + data_width : int + Width of the data bus. + + Attributes + ---------- + d : in + Data input. + value : out + CRC value (used for generator). + error : out + CRC error (used for checker). + """ + width = 32 + polynom = 0x04C11DB7 + init = 2**width-1 + check = 0xC704DD7B + def __init__(self, data_width): + self.data = Signal(data_width) + self.value = Signal(self.width) + self.error = Signal() + + # # # + + self.submodules.engine = LiteEthMACCRCEngine(data_width, self.width, self.polynom) + reg = Signal(self.width, reset=self.init) + self.sync += reg.eq(self.engine.next) + self.comb += [ + self.engine.data.eq(self.data), + self.engine.last.eq(reg), + + self.value.eq(~reg[::-1]), + self.error.eq(self.engine.next != self.check) + ] + + +class LiteEthMACCRCInserter(Module): + """CRC Inserter + + Append a CRC at the end of each packet. + + Parameters + ---------- + description : description + description of the dataflow. + + Attributes + ---------- + sink : in + Packets input without CRC. + source : out + Packets output with CRC. + """ + def __init__(self, crc_class, description): + self.sink = sink = Sink(description) + self.source = source = Source(description) + self.busy = Signal() + + # # # + + dw = len(sink.data) + crc = crc_class(dw) + fsm = FSM(reset_state="IDLE") + self.submodules += crc, fsm + + fsm.act("IDLE", + crc.reset.eq(1), + sink.ack.eq(1), + If(sink.stb & sink.sop, + sink.ack.eq(0), + NextState("COPY"), + ) + ) + fsm.act("COPY", + crc.ce.eq(sink.stb & source.ack), + crc.data.eq(sink.data), + Record.connect(sink, source), + source.eop.eq(0), + If(sink.stb & sink.eop & source.ack, + NextState("INSERT"), + ) + ) + ratio = crc.width//dw + if ratio > 1: + cnt = Signal(max=ratio, reset=ratio-1) + cnt_done = Signal() + fsm.act("INSERT", + source.stb.eq(1), + chooser(crc.value, cnt, source.data, reverse=True), + If(cnt_done, + source.eop.eq(1), + If(source.ack, NextState("IDLE")) + ) + ) + self.comb += cnt_done.eq(cnt == 0) + self.sync += \ + If(fsm.ongoing("IDLE"), + cnt.eq(cnt.reset) + ).Elif(fsm.ongoing("INSERT") & ~cnt_done, + cnt.eq(cnt - source.ack) + ) + else: + fsm.act("INSERT", + source.stb.eq(1), + source.eop.eq(1), + source.data.eq(crc.value), + If(source.ack, NextState("IDLE")) + ) + self.comb += self.busy.eq(~fsm.ongoing("IDLE")) + + +class LiteEthMACCRC32Inserter(LiteEthMACCRCInserter): + def __init__(self, description): + LiteEthMACCRCInserter.__init__(self, LiteEthMACCRC32, description) + + +class LiteEthMACCRCChecker(Module): + """CRC Checker + + Check CRC at the end of each packet. + + Parameters + ---------- + description : description + description of the dataflow. + + Attributes + ---------- + sink : in + Packets input with CRC. + source : out + Packets output without CRC and "error" set to 0 + on eop when CRC OK / set to 1 when CRC KO. + """ + def __init__(self, crc_class, description): + self.sink = sink = Sink(description) + self.source = source = Source(description) + self.busy = Signal() + + # # # + + dw = len(sink.data) + crc = crc_class(dw) + self.submodules += crc + ratio = crc.width//dw + + fifo = ResetInserter()(SyncFIFO(description, ratio + 1)) + self.submodules += fifo + + fsm = FSM(reset_state="RESET") + self.submodules += fsm + + fifo_in = Signal() + fifo_out = Signal() + fifo_full = Signal() + + self.comb += [ + fifo_full.eq(fifo.fifo.level == ratio), + fifo_in.eq(sink.stb & (~fifo_full | fifo_out)), + fifo_out.eq(source.stb & source.ack), + + Record.connect(sink, fifo.sink), + fifo.sink.stb.eq(fifo_in), + self.sink.ack.eq(fifo_in), + + source.stb.eq(sink.stb & fifo_full), + source.sop.eq(fifo.source.sop), + source.eop.eq(sink.eop), + fifo.source.ack.eq(fifo_out), + source.payload.eq(fifo.source.payload), + + source.error.eq(sink.error | crc.error), + ] + + fsm.act("RESET", + crc.reset.eq(1), + fifo.reset.eq(1), + NextState("IDLE"), + ) + self.comb += crc.data.eq(sink.data) + fsm.act("IDLE", + If(sink.stb & sink.sop & sink.ack, + crc.ce.eq(1), + NextState("COPY") + ) + ) + fsm.act("COPY", + If(sink.stb & sink.ack, + crc.ce.eq(1), + If(sink.eop, + NextState("RESET") + ) + ) + ) + self.comb += self.busy.eq(~fsm.ongoing("IDLE")) + + +class LiteEthMACCRC32Checker(LiteEthMACCRCChecker): + def __init__(self, description): + LiteEthMACCRCChecker.__init__(self, LiteEthMACCRC32, description) diff --git a/litex/soc/cores/liteeth_mini/mac/core/gap.py b/litex/soc/cores/liteeth_mini/mac/core/gap.py new file mode 100644 index 00000000..30c35660 --- /dev/null +++ b/litex/soc/cores/liteeth_mini/mac/core/gap.py @@ -0,0 +1,42 @@ +import math + +from migen import * +from migen.genlib.fsm import * + +from litex.soc.interconnect.stream import Sink, Source +from litex.soc.cores.liteeth_mini.common import eth_phy_description, eth_interpacket_gap + + +class LiteEthMACGap(Module): + def __init__(self, dw, ack_on_gap=False): + self.sink = sink = Sink(eth_phy_description(dw)) + self.source = source = Source(eth_phy_description(dw)) + + # # # + + gap = math.ceil(eth_interpacket_gap/(dw//8)) + counter = Signal(max=gap) + counter_reset = Signal() + counter_ce = Signal() + self.sync += \ + If(counter_reset, + counter.eq(0) + ).Elif(counter_ce, + counter.eq(counter + 1) + ) + + self.submodules.fsm = fsm = FSM(reset_state="COPY") + fsm.act("COPY", + counter_reset.eq(1), + Record.connect(sink, source), + If(sink.stb & sink.eop & sink.ack, + NextState("GAP") + ) + ) + fsm.act("GAP", + counter_ce.eq(1), + sink.ack.eq(int(ack_on_gap)), + If(counter == (gap-1), + NextState("COPY") + ) + ) diff --git a/litex/soc/cores/liteeth_mini/mac/core/last_be.py b/litex/soc/cores/liteeth_mini/mac/core/last_be.py new file mode 100644 index 00000000..255ed7c7 --- /dev/null +++ b/litex/soc/cores/liteeth_mini/mac/core/last_be.py @@ -0,0 +1,46 @@ +from migen import * + +from litex.soc.interconnect.stream import * +from litex.soc.cores.liteeth_mini.common import eth_phy_description + + +class LiteEthMACTXLastBE(Module): + def __init__(self, dw): + self.sink = sink = Sink(eth_phy_description(dw)) + self.source = source = Source(eth_phy_description(dw)) + + # # # + + ongoing = Signal() + self.sync += \ + If(sink.stb & sink.ack, + If(sink.sop, + ongoing.eq(1) + ).Elif(sink.last_be, + ongoing.eq(0) + ) + ) + self.comb += [ + source.stb.eq(sink.stb & (sink.sop | ongoing)), + source.sop.eq(sink.sop), + source.eop.eq(sink.last_be), + source.data.eq(sink.data), + sink.ack.eq(source.ack) + ] + + +class LiteEthMACRXLastBE(Module): + def __init__(self, dw): + self.sink = sink = Sink(eth_phy_description(dw)) + self.source = source = Source(eth_phy_description(dw)) + + # # # + + self.comb += [ + source.stb.eq(sink.stb), + source.sop.eq(sink.sop), + source.eop.eq(sink.eop), + source.data.eq(sink.data), + source.last_be.eq(sink.eop), + sink.ack.eq(source.ack) + ] diff --git a/litex/soc/cores/liteeth_mini/mac/core/padding.py b/litex/soc/cores/liteeth_mini/mac/core/padding.py new file mode 100644 index 00000000..1bdba909 --- /dev/null +++ b/litex/soc/cores/liteeth_mini/mac/core/padding.py @@ -0,0 +1,68 @@ +import math + +from migen import * + +from litex.soc.interconnect.stream import * +from litex.soc.cores.liteeth_mini.common import eth_phy_description + + +class LiteEthMACPaddingInserter(Module): + def __init__(self, dw, padding): + self.sink = sink = Sink(eth_phy_description(dw)) + self.source = source = Source(eth_phy_description(dw)) + + # # # + + padding_limit = math.ceil(padding/(dw/8))-1 + + counter = Signal(16, reset=1) + counter_done = Signal() + counter_reset = Signal() + counter_ce = Signal() + self.sync += If(counter_reset, + counter.eq(1) + ).Elif(counter_ce, + counter.eq(counter + 1) + ) + self.comb += [ + counter_reset.eq(sink.stb & sink.sop & sink.ack), + counter_ce.eq(source.stb & source.ack), + counter_done.eq(counter >= padding_limit), + ] + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + fsm.act("IDLE", + Record.connect(sink, source), + If(source.stb & source.ack, + counter_ce.eq(1), + If(sink.eop, + If(~counter_done, + source.eop.eq(0), + NextState("PADDING") + ) + ) + ) + ) + fsm.act("PADDING", + source.stb.eq(1), + source.eop.eq(counter_done), + source.data.eq(0), + If(source.ack, + If(counter_done, + NextState("IDLE") + ) + ) + ) + + +class LiteEthMACPaddingChecker(Module): + def __init__(self, dw, packet_min_length): + self.sink = sink = Sink(eth_phy_description(dw)) + self.source = source = Source(eth_phy_description(dw)) + + # # # + + # TODO: see if we should drop the packet when + # payload size < minimum ethernet payload size + self.comb += Record.connect(sink, source) + diff --git a/litex/soc/cores/liteeth_mini/mac/core/preamble.py b/litex/soc/cores/liteeth_mini/mac/core/preamble.py new file mode 100644 index 00000000..0da28788 --- /dev/null +++ b/litex/soc/cores/liteeth_mini/mac/core/preamble.py @@ -0,0 +1,156 @@ +from migen import * +from migen.genlib.fsm import * +from migen.genlib.misc import chooser +from migen.genlib.record import Record + +from litex.soc.interconnect.stream import * +from litex.soc.cores.liteeth_mini.common import eth_phy_description, eth_preamble + + +class LiteEthMACPreambleInserter(Module): + def __init__(self, dw): + self.sink = Sink(eth_phy_description(dw)) + self.source = Source(eth_phy_description(dw)) + + # # # + + preamble = Signal(64, reset=eth_preamble) + cnt_max = (64//dw)-1 + cnt = Signal(max=cnt_max+1) + clr_cnt = Signal() + inc_cnt = Signal() + + self.sync += \ + If(clr_cnt, + cnt.eq(0) + ).Elif(inc_cnt, + cnt.eq(cnt+1) + ) + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + fsm.act("IDLE", + self.sink.ack.eq(1), + clr_cnt.eq(1), + If(self.sink.stb & self.sink.sop, + self.sink.ack.eq(0), + NextState("INSERT"), + ) + ) + fsm.act("INSERT", + self.source.stb.eq(1), + self.source.sop.eq(cnt == 0), + chooser(preamble, cnt, self.source.data), + If(cnt == cnt_max, + If(self.source.ack, NextState("COPY")) + ).Else( + inc_cnt.eq(self.source.ack) + ) + ) + + self.comb += [ + self.source.data.eq(self.sink.data), + self.source.last_be.eq(self.sink.last_be) + ] + fsm.act("COPY", + Record.connect(self.sink, self.source, leave_out=set(["data", "last_be"])), + self.source.sop.eq(0), + + If(self.sink.stb & self.sink.eop & self.source.ack, + NextState("IDLE"), + ) + ) + + +class LiteEthMACPreambleChecker(Module): + def __init__(self, dw): + self.sink = Sink(eth_phy_description(dw)) + self.source = Source(eth_phy_description(dw)) + + # # # + + preamble = Signal(64, reset=eth_preamble) + cnt_max = (64//dw) - 1 + cnt = Signal(max=cnt_max+1) + clr_cnt = Signal() + inc_cnt = Signal() + + self.sync += \ + If(clr_cnt, + cnt.eq(0) + ).Elif(inc_cnt, + cnt.eq(cnt+1) + ) + + discard = Signal() + clr_discard = Signal() + set_discard = Signal() + + self.sync += \ + If(clr_discard, + discard.eq(0) + ).Elif(set_discard, + discard.eq(1) + ) + + sop = Signal() + clr_sop = Signal() + set_sop = Signal() + self.sync += \ + If(clr_sop, + sop.eq(0) + ).Elif(set_sop, + sop.eq(1) + ) + + ref = Signal(dw) + match = Signal() + self.comb += [ + chooser(preamble, cnt, ref), + match.eq(self.sink.data == ref) + ] + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + fsm.act("IDLE", + self.sink.ack.eq(1), + clr_cnt.eq(1), + clr_discard.eq(1), + If(self.sink.stb & self.sink.sop, + clr_cnt.eq(0), + inc_cnt.eq(1), + clr_discard.eq(0), + set_discard.eq(~match), + NextState("CHECK"), + ) + ) + fsm.act("CHECK", + self.sink.ack.eq(1), + If(self.sink.stb, + set_discard.eq(~match), + If(cnt == cnt_max, + If(discard | (~match), + NextState("IDLE") + ).Else( + set_sop.eq(1), + NextState("COPY") + ) + ).Else( + inc_cnt.eq(1) + ) + ) + ) + self.comb += [ + self.source.data.eq(self.sink.data), + self.source.last_be.eq(self.sink.last_be) + ] + fsm.act("COPY", + Record.connect(self.sink, self.source, leave_out=set(["data", "last_be"])), + self.source.sop.eq(sop), + clr_sop.eq(self.source.stb & self.source.ack), + + If(self.source.stb & self.source.eop & self.source.ack, + NextState("IDLE"), + ) + ) diff --git a/litex/soc/cores/liteeth_mini/mac/frontend/__init__.py b/litex/soc/cores/liteeth_mini/mac/frontend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/litex/soc/cores/liteeth_mini/mac/frontend/sram.py b/litex/soc/cores/liteeth_mini/mac/frontend/sram.py new file mode 100644 index 00000000..46aa8bc9 --- /dev/null +++ b/litex/soc/cores/liteeth_mini/mac/frontend/sram.py @@ -0,0 +1,252 @@ +from litex.soc import * + +from litex.soc.interconnect.csr import * +from litex.soc.interconnect.csr_eventmanager import * +from litex.soc.interconnect.stream import * + +from litex.soc.cores.liteeth_mini.common import eth_phy_description + + +class LiteEthMACSRAMWriter(Module, AutoCSR): + def __init__(self, dw, depth, nslots=2): + self.sink = sink = Sink(eth_phy_description(dw)) + self.crc_error = Signal() + + slotbits = max(log2_int(nslots), 1) + lengthbits = log2_int(depth*4) # length in bytes + + self._slot = CSRStatus(slotbits) + self._length = CSRStatus(lengthbits) + + self.submodules.ev = EventManager() + self.ev.available = EventSourceLevel() + self.ev.finalize() + + # # # + + # packet dropped if no slot available + sink.ack.reset = 1 + + # length computation + increment = Signal(3) + self.comb += \ + If(sink.last_be[3], + increment.eq(1) + ).Elif(sink.last_be[2], + increment.eq(2) + ).Elif(sink.last_be[1], + increment.eq(3) + ).Else( + increment.eq(4) + ) + counter = Signal(lengthbits) + counter_reset = Signal() + counter_ce = Signal() + self.sync += If(counter_reset, + counter.eq(0) + ).Elif(counter_ce, + counter.eq(counter + increment) + ) + + # slot computation + slot = Signal(slotbits) + slot_ce = Signal() + self.sync += If(slot_ce, slot.eq(slot + 1)) + + ongoing = Signal() + + # status fifo + fifo = SyncFIFO([("slot", slotbits), ("length", lengthbits)], nslots) + self.submodules += fifo + + # fsm + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + fsm.act("IDLE", + If(sink.stb & sink.sop, + If(fifo.sink.ack, + ongoing.eq(1), + counter_ce.eq(1), + NextState("WRITE") + ) + ) + ) + fsm.act("WRITE", + counter_ce.eq(sink.stb), + ongoing.eq(1), + If(sink.stb & sink.eop, + If((sink.error & sink.last_be) != 0, + NextState("DISCARD") + ).Else( + NextState("TERMINATE") + ) + ) + ) + fsm.act("DISCARD", + counter_reset.eq(1), + NextState("IDLE") + ) + self.comb += [ + fifo.sink.slot.eq(slot), + fifo.sink.length.eq(counter) + ] + fsm.act("TERMINATE", + counter_reset.eq(1), + slot_ce.eq(1), + fifo.sink.stb.eq(1), + NextState("IDLE") + ) + self.comb += [ + fifo.source.ack.eq(self.ev.available.clear), + self.ev.available.trigger.eq(fifo.source.stb), + self._slot.status.eq(fifo.source.slot), + self._length.status.eq(fifo.source.length), + ] + + # memory + mems = [None]*nslots + ports = [None]*nslots + for n in range(nslots): + mems[n] = Memory(dw, depth) + ports[n] = mems[n].get_port(write_capable=True) + self.specials += ports[n] + self.mems = mems + + cases = {} + for n, port in enumerate(ports): + cases[n] = [ + ports[n].adr.eq(counter[2:]), + ports[n].dat_w.eq(sink.data), + If(sink.stb & ongoing, + ports[n].we.eq(0xf) + ) + ] + self.comb += Case(slot, cases) + + +class LiteEthMACSRAMReader(Module, AutoCSR): + def __init__(self, dw, depth, nslots=2): + self.source = source = Source(eth_phy_description(dw)) + + slotbits = max(log2_int(nslots), 1) + lengthbits = log2_int(depth*4) # length in bytes + self.lengthbits = lengthbits + + self._start = CSR() + self._ready = CSRStatus() + self._slot = CSRStorage(slotbits) + self._length = CSRStorage(lengthbits) + + self.submodules.ev = EventManager() + self.ev.done = EventSourcePulse() + self.ev.finalize() + + # # # + + # command fifo + fifo = SyncFIFO([("slot", slotbits), ("length", lengthbits)], nslots) + self.submodules += fifo + self.comb += [ + fifo.sink.stb.eq(self._start.re), + fifo.sink.slot.eq(self._slot.storage), + fifo.sink.length.eq(self._length.storage), + self._ready.status.eq(fifo.sink.ack) + ] + + # length computation + counter = Signal(lengthbits) + counter_reset = Signal() + counter_ce = Signal() + self.sync += If(counter_reset, + counter.eq(0) + ).Elif(counter_ce, + counter.eq(counter + 4) + ) + + + # fsm + first = Signal() + last = Signal() + last_d = Signal() + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + fsm.act("IDLE", + counter_reset.eq(1), + If(fifo.source.stb, + NextState("CHECK") + ) + ) + fsm.act("CHECK", + If(~last_d, + NextState("SEND"), + ).Else( + NextState("END"), + ) + ) + length_lsb = fifo.source.length[0:2] + self.comb += [ + If(last, + If(length_lsb == 3, + source.last_be.eq(0b0010) + ).Elif(length_lsb == 2, + source.last_be.eq(0b0100) + ).Elif(length_lsb == 1, + source.last_be.eq(0b1000) + ).Else( + source.last_be.eq(0b0001) + ) + ) + ] + fsm.act("SEND", + source.stb.eq(1), + source.sop.eq(first), + source.eop.eq(last), + If(source.ack, + counter_ce.eq(~last), + NextState("CHECK") + ) + ) + fsm.act("END", + fifo.source.ack.eq(1), + self.ev.done.trigger.eq(1), + NextState("IDLE") + ) + + # first/last computation + self.sync += [ + If(fsm.ongoing("IDLE"), + first.eq(1) + ).Elif(source.stb & source.ack, + first.eq(0) + ) + ] + self.comb += last.eq((counter + 4) >= fifo.source.length) + self.sync += last_d.eq(last) + + # memory + rd_slot = fifo.source.slot + + mems = [None]*nslots + ports = [None]*nslots + for n in range(nslots): + mems[n] = Memory(dw, depth) + ports[n] = mems[n].get_port() + self.specials += ports[n] + self.mems = mems + + cases = {} + for n, port in enumerate(ports): + self.comb += ports[n].adr.eq(counter[2:]) + cases[n] = [source.data.eq(port.dat_r)] + self.comb += Case(rd_slot, cases) + + +class LiteEthMACSRAM(Module, AutoCSR): + def __init__(self, dw, depth, nrxslots, ntxslots): + self.submodules.writer = LiteEthMACSRAMWriter(dw, depth, nrxslots) + self.submodules.reader = LiteEthMACSRAMReader(dw, depth, ntxslots) + self.submodules.ev = SharedIRQ(self.writer.ev, self.reader.ev) + self.sink, self.source = self.writer.sink, self.reader.source diff --git a/litex/soc/cores/liteeth_mini/mac/frontend/wishbone.py b/litex/soc/cores/liteeth_mini/mac/frontend/wishbone.py new file mode 100644 index 00000000..72b99df0 --- /dev/null +++ b/litex/soc/cores/liteeth_mini/mac/frontend/wishbone.py @@ -0,0 +1,44 @@ +from migen import * +from migen.fhdl.simplify import FullMemoryWE + +from litex.soc.interconnect import wishbone +from litex.soc.interconnect.csr import * +from litex.soc.interconnect.stream import * +from litex.soc.cores.liteeth_mini.common import eth_phy_description, buffer_depth +from litex.soc.cores.liteeth_mini.mac.frontend import sram + + +class LiteEthMACWishboneInterface(Module, AutoCSR): + def __init__(self, dw, nrxslots=2, ntxslots=2): + self.sink = Sink(eth_phy_description(dw)) + self.source = Source(eth_phy_description(dw)) + self.bus = wishbone.Interface() + + # # # + + # storage in SRAM + sram_depth = buffer_depth//(dw//8) + self.submodules.sram = sram.LiteEthMACSRAM(dw, sram_depth, nrxslots, ntxslots) + self.comb += [ + Record.connect(self.sink, self.sram.sink), + Record.connect(self.sram.source, self.source) + ] + + # Wishbone interface + wb_rx_sram_ifs = [wishbone.SRAM(self.sram.writer.mems[n], read_only=True) + for n in range(nrxslots)] + # TODO: FullMemoryWE should move to Mibuild + wb_tx_sram_ifs = [FullMemoryWE()(wishbone.SRAM(self.sram.reader.mems[n], read_only=False)) + for n in range(ntxslots)] + wb_sram_ifs = wb_rx_sram_ifs + wb_tx_sram_ifs + + wb_slaves = [] + decoderoffset = log2_int(sram_depth) + decoderbits = log2_int(len(wb_sram_ifs)) + for n, wb_sram_if in enumerate(wb_sram_ifs): + def slave_filter(a, v=n): + return a[decoderoffset:decoderoffset+decoderbits] == v + wb_slaves.append((slave_filter, wb_sram_if.bus)) + self.submodules += wb_sram_if + wb_con = wishbone.Decoder(self.bus, wb_slaves, register=True) + self.submodules += wb_con diff --git a/litex/soc/cores/liteeth_mini/phy/__init__.py b/litex/soc/cores/liteeth_mini/phy/__init__.py new file mode 100644 index 00000000..a5d5ae92 --- /dev/null +++ b/litex/soc/cores/liteeth_mini/phy/__init__.py @@ -0,0 +1,23 @@ +from misoc.cores.liteeth_mini.common import * + + +def LiteEthPHY(clock_pads, pads, clk_freq=None, **kwargs): + # Autodetect PHY + if hasattr(clock_pads, "gtx") and len(pads.tx_data) == 8: + if hasattr(clock_pads, "tx"): + # This is a 10/100/1G PHY + from misoc.cores.liteeth_mini.phy.gmii_mii import LiteEthPHYGMIIMII + return LiteEthPHYGMIIMII(clock_pads, pads, clk_freq=clk_freq, **kwargs) + else: + # This is a pure 1G PHY + from misoc.cores.liteeth_mini.phy.gmii import LiteEthPHYGMII + return LiteEthPHYGMII(clock_pads, pads, **kwargs) + elif hasattr(pads, "rx_ctl"): + # This is a 10/100/1G RGMII PHY + raise ValueError("RGMII PHYs are specific to vendors (for now), use direct instantiation") + elif len(pads.tx_data) == 4: + # This is a MII PHY + from misoc.cores.liteeth_mini.phy.mii import LiteEthPHYMII + return LiteEthPHYMII(clock_pads, pads, **kwargs) + else: + raise ValueError("Unable to autodetect PHY from platform file, use direct instantiation") diff --git a/litex/soc/cores/liteeth_mini/phy/gmii.py b/litex/soc/cores/liteeth_mini/phy/gmii.py new file mode 100644 index 00000000..bb3fc0bb --- /dev/null +++ b/litex/soc/cores/liteeth_mini/phy/gmii.py @@ -0,0 +1,98 @@ +from migen import * +from migen.genlib.io import DDROutput +from migen.genlib.resetsync import AsyncResetSynchronizer + +from misoc.cores.liteeth_mini.common import * + + +class LiteEthPHYGMIITX(Module): + def __init__(self, pads, pads_register=True): + self.sink = sink = Sink(eth_phy_description(8)) + + # # # + + if hasattr(pads, "tx_er"): + self.sync += pads.tx_er.eq(0) + pads_eq = [ + pads.tx_en.eq(sink.stb), + pads.tx_data.eq(sink.data) + ] + if pads_register: + self.sync += pads_eq + else: + self.comb += pads_eq + self.comb += sink.ack.eq(1) + + +class LiteEthPHYGMIIRX(Module): + def __init__(self, pads): + self.source = source = Source(eth_phy_description(8)) + + # # # + + dv_d = Signal() + self.sync += dv_d.eq(pads.dv) + + sop = Signal() + eop = Signal() + self.comb += [ + sop.eq(pads.dv & ~dv_d), + eop.eq(~pads.dv & dv_d) + ] + self.sync += [ + source.stb.eq(pads.dv), + source.sop.eq(sop), + source.data.eq(pads.rx_data) + ] + self.comb += source.eop.eq(eop) + + +class LiteEthPHYGMIICRG(Module, AutoCSR): + def __init__(self, clock_pads, pads, with_hw_init_reset, mii_mode=0): + self._reset = CSRStorage() + + # # # + + self.clock_domains.cd_eth_rx = ClockDomain() + self.clock_domains.cd_eth_tx = ClockDomain() + + # RX : Let the synthesis tool insert the appropriate clock buffer + self.comb += self.cd_eth_rx.clk.eq(clock_pads.rx) + + # TX : GMII: Drive clock_pads.gtx, clock_pads.tx unused + # MII: Use PHY clock_pads.tx as eth_tx_clk, do not drive clock_pads.gtx + self.specials += DDROutput(1, mii_mode, clock_pads.gtx, ClockSignal("eth_tx")) + # XXX Xilinx specific, replace BUFGMUX with a generic clock buffer? + self.specials += Instance("BUFGMUX", + i_I0=self.cd_eth_rx.clk, + i_I1=clock_pads.tx, + i_S=mii_mode, + o_O=self.cd_eth_tx.clk) + + if with_hw_init_reset: + reset = Signal() + counter = Signal(max=512) + counter_done = Signal() + counter_ce = Signal() + self.sync += If(counter_ce, counter.eq(counter + 1)) + self.comb += [ + counter_done.eq(counter == 256), + counter_ce.eq(~counter_done), + reset.eq(~counter_done | self._reset.storage) + ] + else: + reset = self._reset.storage + self.comb += pads.rst_n.eq(~reset) + self.specials += [ + AsyncResetSynchronizer(self.cd_eth_tx, reset), + AsyncResetSynchronizer(self.cd_eth_rx, reset), + ] + + +class LiteEthPHYGMII(Module, AutoCSR): + def __init__(self, clock_pads, pads, with_hw_init_reset=True): + self.dw = 8 + self.submodules.crg = LiteEthPHYGMIICRG(clock_pads, pads, with_hw_init_reset) + self.submodules.tx = ClockDomainsRenamer("eth_tx")(LiteEthPHYGMIITX(pads)) + self.submodules.rx = ClockDomainsRenamer("eth_rx")(LiteEthPHYGMIIRX(pads)) + self.sink, self.source = self.tx.sink, self.rx.source diff --git a/litex/soc/cores/liteeth_mini/phy/gmii_mii.py b/litex/soc/cores/liteeth_mini/phy/gmii_mii.py new file mode 100644 index 00000000..6946c6c3 --- /dev/null +++ b/litex/soc/cores/liteeth_mini/phy/gmii_mii.py @@ -0,0 +1,170 @@ +from migen import * +from migen.genlib.io import DDROutput +from migen.genlib.cdc import PulseSynchronizer + +from misoc.interconnect.stream import * +from misoc.cores.liteeth_mini.common import * +from misoc.cores.liteeth_mini.phy.gmii import LiteEthPHYGMIICRG +from misoc.cores.liteeth_mini.phy.mii import LiteEthPHYMIITX, LiteEthPHYMIIRX +from misoc.cores.liteeth_mini.phy.gmii import LiteEthPHYGMIITX, LiteEthPHYGMIIRX + + +modes = { + "GMII": 0, + "MII": 1 +} + +tx_pads_layout = [("tx_er", 1), ("tx_en", 1), ("tx_data", 8)] +rx_pads_layout = [("rx_er", 1), ("dv", 1), ("rx_data", 8)] + + +class LiteEthPHYGMIIMIITX(Module): + def __init__(self, pads, mode): + self.sink = sink = Sink(eth_phy_description(8)) + + # # # + + gmii_tx_pads = Record(tx_pads_layout) + gmii_tx = LiteEthPHYGMIITX(gmii_tx_pads, pads_register=False) + self.submodules += gmii_tx + + mii_tx_pads = Record(tx_pads_layout) + mii_tx = LiteEthPHYMIITX(mii_tx_pads, pads_register=False) + self.submodules += mii_tx + + demux = Demultiplexer(eth_phy_description(8), 2) + self.submodules += demux + self.comb += [ + demux.sel.eq(mode == modes["MII"]), + Record.connect(sink, demux.sink), + Record.connect(demux.source0, gmii_tx.sink), + Record.connect(demux.source1, mii_tx.sink), + ] + + if hasattr(pads, "tx_er"): + self.comb += pads.tx_er.eq(0) + self.sync += [ + If(mode == modes["MII"], + pads.tx_en.eq(mii_tx_pads.tx_en), + pads.tx_data.eq(mii_tx_pads.tx_data), + ).Else( + pads.tx_en.eq(gmii_tx_pads.tx_en), + pads.tx_data.eq(gmii_tx_pads.tx_data), + ) + ] + + +class LiteEthPHYGMIIMIIRX(Module): + def __init__(self, pads, mode): + self.source = source = Source(eth_phy_description(8)) + + # # # + + pads_d = Record(rx_pads_layout) + self.sync += [ + pads_d.dv.eq(pads.dv), + pads_d.rx_data.eq(pads.rx_data) + ] + + gmii_rx = LiteEthPHYGMIIRX(pads_d) + self.submodules += gmii_rx + + mii_rx = LiteEthPHYMIIRX(pads_d) + self.submodules += mii_rx + + mux = Multiplexer(eth_phy_description(8), 2) + self.submodules += mux + self.comb += [ + mux.sel.eq(mode == modes["MII"]), + Record.connect(gmii_rx.source, mux.sink0), + Record.connect(mii_rx.source, mux.sink1), + Record.connect(mux.source, source) + ] + + +class LiteEthGMIIMIIModeDetection(Module, AutoCSR): + def __init__(self, clk_freq): + self.mode = Signal() + self._mode = CSRStatus() + + # # # + + mode = Signal() + update_mode = Signal() + self.sync += \ + If(update_mode, + self.mode.eq(mode) + ) + self.comb += self._mode.status.eq(self.mode) + + # Principle: + # sys_clk >= 125MHz + # eth_rx <= 125Mhz + # We generate ticks every 1024 clock cycles in eth_rx domain + # and measure ticks period in sys_clk domain. + + # Generate a tick every 1024 clock cycles (eth_rx clock domain) + eth_tick = Signal() + eth_counter = Signal(10) + self.sync.eth_rx += eth_counter.eq(eth_counter + 1) + self.comb += eth_tick.eq(eth_counter == 0) + + # Synchronize tick (sys clock domain) + sys_tick = Signal() + eth_ps = PulseSynchronizer("eth_rx", "sys") + self.comb += [ + eth_ps.i.eq(eth_tick), + sys_tick.eq(eth_ps.o) + ] + self.submodules += eth_ps + + # sys_clk domain counter + sys_counter = Signal(24) + sys_counter_reset = Signal() + sys_counter_ce = Signal() + self.sync += [ + If(sys_counter_reset, + sys_counter.eq(0) + ).Elif(sys_counter_ce, + sys_counter.eq(sys_counter + 1) + ) + ] + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + fsm.act("IDLE", + sys_counter_reset.eq(1), + If(sys_tick, + NextState("COUNT") + ) + ) + fsm.act("COUNT", + sys_counter_ce.eq(1), + If(sys_tick, + NextState("DETECTION") + ) + ) + fsm.act("DETECTION", + update_mode.eq(1), + # if freq < 125MHz-5% use MII mode + If(sys_counter > int((clk_freq/125000000)*1024*1.05), + mode.eq(1) + # if freq >= 125MHz-5% use GMII mode + ).Else( + mode.eq(0) + ), + NextState("IDLE") + ) + + +class LiteEthPHYGMIIMII(Module, AutoCSR): + def __init__(self, clock_pads, pads, clk_freq, with_hw_init_reset=True): + self.dw = 8 + # Note: we can use GMII CRG since it also handles tx clock pad used for MII + self.submodules.mode_detection = LiteEthGMIIMIIModeDetection(clk_freq) + mode = self.mode_detection.mode + self.submodules.crg = LiteEthPHYGMIICRG(clock_pads, pads, with_hw_init_reset, mode == modes["MII"]) + self.submodules.tx = ClockDomainsRenamer("eth_tx")(LiteEthPHYGMIIMIITX(pads, mode)) + self.submodules.rx = ClockDomainsRenamer("eth_rx")(LiteEthPHYGMIIMIIRX(pads, mode)) + self.sink, self.source = self.tx.sink, self.rx.source diff --git a/litex/soc/cores/liteeth_mini/phy/loopback.py b/litex/soc/cores/liteeth_mini/phy/loopback.py new file mode 100644 index 00000000..28b1430e --- /dev/null +++ b/litex/soc/cores/liteeth_mini/phy/loopback.py @@ -0,0 +1,35 @@ +from migen import * + +from misoc.interconnect.csr import * +from misoc.interconnect.stream import * +from misoc.cores.liteeth_mini.common import * +from misoc.cores.liteeth.mini.generic import * + + +class LiteEthPHYLoopbackCRG(Module, AutoCSR): + def __init__(self): + self._reset = CSRStorage() + + # # # + + self.clock_domains.cd_eth_rx = ClockDomain() + self.clock_domains.cd_eth_tx = ClockDomain() + self.comb += [ + self.cd_eth_rx.clk.eq(ClockSignal()), + self.cd_eth_tx.clk.eq(ClockSignal()) + ] + + reset = self._reset.storage + self.comb += [ + self.cd_eth_rx.rst.eq(reset), + self.cd_eth_tx.rst.eq(reset) + ] + + +class LiteEthPHYLoopback(Module, AutoCSR): + def __init__(self): + self.dw = 8 + self.submodules.crg = LiteEthLoopbackPHYCRG() + self.sink = Sink(eth_phy_description(8)) + self.source = Source(eth_phy_description(8)) + self.comb += Record.connect(self.sink, self.source) diff --git a/litex/soc/cores/liteeth_mini/phy/mii.py b/litex/soc/cores/liteeth_mini/phy/mii.py new file mode 100644 index 00000000..c5bf5271 --- /dev/null +++ b/litex/soc/cores/liteeth_mini/phy/mii.py @@ -0,0 +1,110 @@ +from migen import * + +from misoc.interconnect.csr import * +from misoc.interconnect.stream import * +from misoc.cores.liteeth_mini.common import * + + +def converter_description(dw): + payload_layout = [("data", dw)] + return EndpointDescription(payload_layout, packetized=True) + + +class LiteEthPHYMIITX(Module): + def __init__(self, pads, pads_register=True): + self.sink = sink = Sink(eth_phy_description(8)) + + # # # + + if hasattr(pads, "tx_er"): + self.sync += pads.tx_er.eq(0) + converter = Converter(converter_description(8), + converter_description(4)) + self.submodules += converter + self.comb += [ + converter.sink.stb.eq(sink.stb), + converter.sink.data.eq(sink.data), + sink.ack.eq(converter.sink.ack), + converter.source.ack.eq(1) + ] + pads_eq = [ + pads.tx_en.eq(converter.source.stb), + pads.tx_data.eq(converter.source.data) + ] + if pads_register: + self.sync += pads_eq + else: + self.comb += pads_eq + + +class LiteEthPHYMIIRX(Module): + def __init__(self, pads): + self.source = source = Source(eth_phy_description(8)) + + # # # + + sop = Signal(reset=1) + sop_set = Signal() + sop_clr = Signal() + self.sync += If(sop_set, sop.eq(1)).Elif(sop_clr, sop.eq(0)) + + converter = Converter(converter_description(4), + converter_description(8)) + converter = ResetInserter()(converter) + self.submodules += converter + + self.sync += [ + converter.reset.eq(~pads.dv), + converter.sink.stb.eq(1), + converter.sink.data.eq(pads.rx_data) + ] + self.sync += [ + sop_set.eq(~pads.dv), + sop_clr.eq(pads.dv) + ] + self.comb += [ + converter.sink.sop.eq(sop), + converter.sink.eop.eq(~pads.dv) + ] + self.comb += Record.connect(converter.source, source) + + +class LiteEthPHYMIICRG(Module, AutoCSR): + def __init__(self, clock_pads, pads, with_hw_init_reset): + self._reset = CSRStorage() + + # # # + + if hasattr(clock_pads, "phy"): + self.sync.base50 += clock_pads.phy.eq(~clock_pads.phy) + + self.clock_domains.cd_eth_rx = ClockDomain() + self.clock_domains.cd_eth_tx = ClockDomain() + self.comb += self.cd_eth_rx.clk.eq(clock_pads.rx) + self.comb += self.cd_eth_tx.clk.eq(clock_pads.tx) + + if with_hw_init_reset: + reset = Signal() + counter_done = Signal() + self.submodules.counter = counter = Counter(max=512) + self.comb += [ + counter_done.eq(counter.value == 256), + counter.ce.eq(~counter_done), + reset.eq(~counter_done | self._reset.storage) + ] + else: + reset = self._reset.storage + self.comb += pads.rst_n.eq(~reset) + self.specials += [ + AsyncResetSynchronizer(self.cd_eth_tx, reset), + AsyncResetSynchronizer(self.cd_eth_rx, reset), + ] + + +class LiteEthPHYMII(Module, AutoCSR): + def __init__(self, clock_pads, pads, with_hw_init_reset=True): + self.dw = 8 + self.submodules.crg = LiteEthPHYMIICRG(clock_pads, pads, with_hw_init_reset) + self.submodules.tx = ClockDomainsRenamer("eth_tx")(LiteEthPHYMIITX(pads)) + self.submodules.rx = ClockDomainsRenamer("eth_tx")(LiteEthPHYMIIRX(pads)) + self.sink, self.source = self.tx.sink, self.rx.source diff --git a/litex/soc/cores/liteeth_mini/phy/s6rgmii.py b/litex/soc/cores/liteeth_mini/phy/s6rgmii.py new file mode 100644 index 00000000..1f312ace --- /dev/null +++ b/litex/soc/cores/liteeth_mini/phy/s6rgmii.py @@ -0,0 +1,161 @@ +# RGMII PHY for Spartan-6 + +from migen import * +from migen.genlib.io import DDROutput +from migen.genlib.misc import WaitTimer +from migen.genlib.fsm import FSM, NextState + +from misoc.interconnect.stream import * +from misoc.interconnect.csr import * +from misoc.cores.liteeth_mini.common import * + + +class LiteEthPHYRGMIITX(Module): + def __init__(self, pads, pads_register=True): + self.sink = sink = Sink(eth_phy_description(8)) + + # # # + + self.specials += Instance("ODDR2", + p_DDR_ALIGNMENT="C0", p_INIT=0, p_SRTYPE="ASYNC", + i_C0=ClockSignal("eth_tx"), i_C1=~ClockSignal("eth_tx"), + i_CE=1, i_S=0, i_R=0, + i_D0=sink.stb, i_D1=sink.stb, o_Q=pads.tx_ctl, + ) + for i in range(4): + self.specials += Instance("ODDR2", + p_DDR_ALIGNMENT="C0", p_INIT=0, p_SRTYPE="ASYNC", + i_C0=ClockSignal("eth_tx"), i_C1=~ClockSignal("eth_tx"), + i_CE=1, i_S=0, i_R=0, + i_D0=sink.data[i], i_D1=sink.data[4+i], o_Q=pads.tx_data[i], + ) + self.comb += sink.ack.eq(1) + + +class LiteEthPHYRGMIIRX(Module): + def __init__(self, pads): + self.source = source = Source(eth_phy_description(8)) + + # # # + + rx_ctl = Signal() + rx_data = Signal(8) + + self.specials += Instance("IDDR2", + p_DDR_ALIGNMENT="C0", p_INIT_Q0=0, p_INIT_Q1=0, p_SRTYPE="ASYNC", + i_C0=ClockSignal("eth_rx"), i_C1=~ClockSignal("eth_rx"), + i_CE=1, i_S=0, i_R=0, + i_D=pads.rx_ctl, o_Q1=rx_ctl, + ) + for i in range(4): + self.specials += Instance("IDDR2", + p_DDR_ALIGNMENT="C0", p_INIT_Q0=0, p_INIT_Q1=0, p_SRTYPE="ASYNC", + i_C0=ClockSignal("eth_rx"), i_C1=~ClockSignal("eth_rx"), + i_CE=1, i_S=0, i_R=0, + i_D=pads.rx_data[i], o_Q0=rx_data[4+i], o_Q1=rx_data[i], + ) + + + rx_ctl_d = Signal() + self.sync += rx_ctl_d.eq(rx_ctl) + + sop = Signal() + eop = Signal() + self.comb += [ + sop.eq(rx_ctl & ~rx_ctl_d), + eop.eq(~rx_ctl & rx_ctl_d) + ] + self.sync += [ + source.stb.eq(rx_ctl), + source.sop.eq(sop), + source.data.eq(rx_data) + ] + self.comb += source.eop.eq(eop) + + +class LiteEthPHYRGMIICRG(Module, AutoCSR): + def __init__(self, clock_pads, pads, with_hw_init_reset): + self._reset = CSRStorage() + + # # # + + self.clock_domains.cd_eth_rx = ClockDomain() + self.clock_domains.cd_eth_tx = ClockDomain() + + + # RX + dcm_reset = Signal() + dcm_locked = Signal() + + timer = WaitTimer(1024) + fsm = FSM(reset_state="DCM_RESET") + self.submodules += timer, fsm + + fsm.act("DCM_RESET", + dcm_reset.eq(1), + timer.wait.eq(1), + If(timer.done, + timer.wait.eq(0), + NextState("DCM_WAIT") + ) + ) + fsm.act("DCM_WAIT", + timer.wait.eq(1), + If(timer.done, + NextState("DCM_CHECK_LOCK") + ) + ) + fsm.act("DCM_CHECK_LOCK", + If(~dcm_locked, + NextState("DCM_RESET") + ) + ) + + clk90_rx = Signal() + clk0_rx = Signal() + clk0_rx_bufg = Signal() + self.specials += Instance("DCM", + i_CLKIN=clock_pads.rx, + i_CLKFB=clk0_rx_bufg, + o_CLK0=clk0_rx, + o_CLK90=clk90_rx, + o_LOCKED=dcm_locked, + i_PSEN=0, + i_PSCLK=0, + i_PSINCDEC=0, + i_RST=dcm_reset + ) + + self.specials += Instance("BUFG", i_I=clk0_rx, o_O=clk0_rx_bufg) + self.specials += Instance("BUFG", i_I=clk90_rx, o_O=self.cd_eth_rx.clk) + + # TX + self.specials += DDROutput(1, 0, clock_pads.tx, ClockSignal("eth_tx")) + self.specials += Instance("BUFG", i_I=self.cd_eth_rx.clk, o_O=self.cd_eth_tx.clk) + + # Reset + if with_hw_init_reset: + reset = Signal() + counter_done = Signal() + self.submodules.counter = counter = Counter(max=512) + self.comb += [ + counter_done.eq(counter.value == 256), + counter.ce.eq(~counter_done), + reset.eq(~counter_done | self._reset.storage) + ] + else: + reset = self._reset.storage + self.comb += pads.rst_n.eq(~reset) + self.specials += [ + AsyncResetSynchronizer(self.cd_eth_tx, reset), + AsyncResetSynchronizer(self.cd_eth_rx, reset), + ] + + +class LiteEthPHYRGMII(Module, AutoCSR): + def __init__(self, clock_pads, pads, with_hw_init_reset=True): + self.dw = 8 + self.submodules.crg = LiteEthPHYRGMIICRG(clock_pads, pads, with_hw_init_reset) + self.submodules.tx = ClockDomainsRenamer("eth_tx")(LiteEthPHYRGMIITX(pads)) + self.submodules.rx = ClockDomainsRenamer("eth_rx")(LiteEthPHYRGMIIRX(pads)) + self.sink, self.source = self.tx.sink, self.rx.source