diff --git a/liteeth/mac/__init__.py b/liteeth/mac/__init__.py index 7dd33c9..430386f 100644 --- a/liteeth/mac/__init__.py +++ b/liteeth/mac/__init__.py @@ -20,9 +20,10 @@ class LiteEthMAC(Module, AutoCSR): ntxslots = 2, hw_mac = None, timestamp = None, - full_memory_we = False): + full_memory_we = False, + sys_data_path = True): assert interface in ["crossbar", "wishbone", "hybrid"] - self.submodules.core = LiteEthMACCore(phy, dw, endianness, with_preamble_crc) + self.submodules.core = LiteEthMACCore(phy, dw, endianness, with_preamble_crc, sys_data_path) self.csrs = [] if interface == "crossbar": self.submodules.crossbar = LiteEthMACCrossbar(dw) diff --git a/liteeth/mac/core.py b/liteeth/mac/core.py index 9929b5e..282e9b6 100644 --- a/liteeth/mac/core.py +++ b/liteeth/mac/core.py @@ -7,7 +7,7 @@ # SPDX-License-Identifier: BSD-2-Clause from liteeth.common import * -from liteeth.mac import gap, preamble, crc, padding, last_be +from liteeth.mac import gap, preamble, crc, padding, last_be, endian_converter from liteeth.phy.model import LiteEthPHYModel from migen.genlib.cdc import PulseSynchronizer @@ -17,16 +17,32 @@ from litex.soc.interconnect.stream import BufferizeEndpoints, DIR_SOURCE, DIR_SI # MAC Core ----------------------------------------------------------------------------------------- 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)) + def __init__(self, phy, dw, + endianness = "big", + with_preamble_crc = True, + sys_data_path = True, + with_padding = True): + core_dw = dw + + if core_dw < phy.dw: + raise ValueError("Core data width({}) must be larger than PHY data width({})".format(core_dw, phy.dw)) rx_pipeline = [phy] tx_pipeline = [phy] + if sys_data_path: + # The pipeline for dw>8 only works for little endian + self.data_path_converter(tx_pipeline, rx_pipeline, core_dw, phy.dw, "little") + cd_tx = cd_rx = "sys" + dw = core_dw + else: + cd_tx = "eth_tx" + cd_rx = "eth_rx" + dw = phy.dw + # Interpacket gap - tx_gap_inserter = gap.LiteEthMACGap(phy.dw) - self.submodules += ClockDomainsRenamer("eth_tx")(tx_gap_inserter) + tx_gap_inserter = gap.LiteEthMACGap(dw) + self.submodules += ClockDomainsRenamer(cd_tx)(tx_gap_inserter) tx_pipeline += [tx_gap_inserter] # Preamble / CRC @@ -36,65 +52,85 @@ class LiteEthMACCore(Module, AutoCSR): self._preamble_crc = CSRStatus(reset=1) elif with_preamble_crc: self._preamble_crc = CSRStatus(reset=1) - self.preamble_errors = CSRStatus(32) - self.crc_errors = CSRStatus(32) # 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) + preamble_inserter = preamble.LiteEthMACPreambleInserter(dw) + preamble_checker = preamble.LiteEthMACPreambleChecker(dw) + self.submodules += ClockDomainsRenamer(cd_tx)(preamble_inserter) + self.submodules += ClockDomainsRenamer(cd_rx)(preamble_checker) + tx_pipeline += [preamble_inserter] + rx_pipeline += [preamble_checker] + self.submodules.ps_preamble_error = PulseSynchronizer(cd_rx, "sys") + + + # Preamble error counter + self.preamble_errors = CSRStatus(32) + self.comb += self.ps_preamble_error.i.eq(preamble_checker.error), + self.sync += If(self.ps_preamble_error.o, + self.preamble_errors.status.eq(self.preamble_errors.status + 1)), # CRC insert/check - crc32_inserter = BufferizeEndpoints({"sink": DIR_SINK})(crc.LiteEthMACCRC32Inserter(eth_phy_description(phy.dw))) - crc32_checker = BufferizeEndpoints({"sink": DIR_SINK})(crc.LiteEthMACCRC32Checker(eth_phy_description(phy.dw))) - self.submodules += ClockDomainsRenamer("eth_tx")(crc32_inserter) - self.submodules += ClockDomainsRenamer("eth_rx")(crc32_checker) + crc32_inserter = BufferizeEndpoints({"sink": DIR_SINK})(crc.LiteEthMACCRC32Inserter(eth_phy_description(dw))) + crc32_checker = BufferizeEndpoints({"sink": DIR_SINK})(crc.LiteEthMACCRC32Checker(eth_phy_description(dw))) + self.submodules += ClockDomainsRenamer(cd_tx)(crc32_inserter) + self.submodules += ClockDomainsRenamer(cd_rx)(crc32_checker) - tx_pipeline += [preamble_inserter, crc32_inserter] - rx_pipeline += [preamble_checker, crc32_checker] + tx_pipeline += [crc32_inserter] + rx_pipeline += [crc32_checker] - # Error counters - self.submodules.ps_preamble_error = PulseSynchronizer("eth_rx", "sys") - self.submodules.ps_crc_error = PulseSynchronizer("eth_rx", "sys") - self.comb += [ - self.ps_preamble_error.i.eq(preamble_checker.error), - self.ps_crc_error.i.eq(crc32_checker.error), - ] - self.sync += [ - If(self.ps_preamble_error.o, - self.preamble_errors.status.eq(self.preamble_errors.status + 1)), - If(self.ps_crc_error.o, - self.crc_errors.status.eq(self.crc_errors.status + 1)), - ] + # CRC error counter + self.crc_errors = CSRStatus(32) + self.submodules.ps_crc_error = PulseSynchronizer(cd_rx, "sys") + self.comb += self.ps_crc_error.i.eq(crc32_checker.error), + self.sync += If(self.ps_crc_error.o, + self.crc_errors.status.eq(self.crc_errors.status + 1)), # 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) + padding_inserter = padding.LiteEthMACPaddingInserter(dw, 60) + padding_checker = padding.LiteEthMACPaddingChecker(dw, 60) + self.submodules += ClockDomainsRenamer(cd_tx)(padding_inserter) + self.submodules += ClockDomainsRenamer(cd_rx)(padding_checker) tx_pipeline += [padding_inserter] rx_pipeline += [padding_checker] + if sys_data_path: + # Since the pipeline only works for little endian when dw > 8, + # convert to big endian if necessary + if endianness == "big" and dw != 8: + tx_converter = endian_converter.LiteEthMACEndianConverter(dw) + rx_converter = endian_converter.LiteEthMACEndianConverter(dw) + self.submodules += tx_converter, rx_converter + tx_pipeline += [tx_converter] + rx_pipeline += [rx_converter] + else: + self.data_path_converter(tx_pipeline, rx_pipeline, core_dw, phy.dw, endianness) + + # Graph + self.submodules.tx_pipeline = stream.Pipeline(*reversed(tx_pipeline)) + self.submodules.rx_pipeline = stream.Pipeline(*rx_pipeline) + + self.sink, self.source = self.tx_pipeline.sink, self.rx_pipeline.source + + def data_path_converter(self, tx_pipeline, rx_pipeline, dw, phy_dw, endianness): # Delimiters if dw != 8: - tx_last_be = last_be.LiteEthMACTXLastBE(phy.dw) - rx_last_be = last_be.LiteEthMACRXLastBE(phy.dw) + 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: + if dw != phy_dw: reverse = endianness == "big" tx_converter = stream.StrideConverter( description_from = eth_phy_description(dw), - description_to = eth_phy_description(phy.dw), + description_to = eth_phy_description(phy_dw), reverse = reverse) rx_converter = stream.StrideConverter( - description_from = eth_phy_description(phy.dw), + description_from = eth_phy_description(phy_dw), description_to = eth_phy_description(dw), reverse = reverse) self.submodules += ClockDomainsRenamer("eth_tx")(tx_converter) @@ -108,9 +144,3 @@ class LiteEthMACCore(Module, AutoCSR): self.submodules += tx_cdc, rx_cdc tx_pipeline += [tx_cdc] rx_pipeline += [rx_cdc] - - # Graph - self.submodules.tx_pipeline = stream.Pipeline(*reversed(tx_pipeline)) - self.submodules.rx_pipeline = stream.Pipeline(*rx_pipeline) - - self.sink, self.source = self.tx_pipeline.sink, self.rx_pipeline.source diff --git a/liteeth/mac/crc.py b/liteeth/mac/crc.py index ed057d8..f891123 100644 --- a/liteeth/mac/crc.py +++ b/liteeth/mac/crc.py @@ -102,8 +102,10 @@ class LiteEthMACCRC32(Module): Attributes ---------- - d : in + data : in Data input. + last_be : in + Valid byte in data input (optional). value : out CRC value (used for generator). error : out @@ -114,22 +116,36 @@ class LiteEthMACCRC32(Module): init = 2**width-1 check = 0xC704DD7B def __init__(self, data_width): + dw = data_width//8 + self.data = Signal(data_width) + self.last_be = Signal(dw) self.value = Signal(self.width) self.error = Signal() + # Add a separate last_be signal, to maintain backwards compatability + last_be = Signal(data_width//8) # # # - 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) + If(self.last_be != 0, + last_be.eq(self.last_be) + ).Else( + last_be.eq(2**(dw-1))) ] + # Since the data can end at any byte end, indicated by `last_be` + # maintain separate engines for each 8 byte increment in the data word + engines = [LiteEthMACCRCEngine((e+1)*8, self.width, self.polynom) for e in range(dw)] + self.submodules += engines + + reg = Signal(self.width, reset=self.init) + self.sync += reg.eq(engines[-1].next) + self.comb += [engines[e].data.eq(self.data[:(e+1)*8]) for e in range(dw)], + self.comb += [engines[e].last.eq(reg) for e in range(dw)] + self.comb += [If(last_be[e], + self.value.eq(reverse_bits(~engines[e].next)), + self.error.eq(engines[e].next != self.check)) + for e in range(dw)] # MAC CRC Inserter --------------------------------------------------------------------------------- @@ -146,9 +162,9 @@ class LiteEthMACCRCInserter(Module): Attributes ---------- sink : in - Packets octets without CRC. + Packet data without CRC. source : out - Packets octets with CRC. + Packet data with CRC. """ def __init__(self, crc_class, description): self.sink = sink = stream.Endpoint(description) @@ -157,10 +173,15 @@ class LiteEthMACCRCInserter(Module): # # # dw = len(sink.data) + assert dw in [8, 32] crc = crc_class(dw) fsm = FSM(reset_state="IDLE") self.submodules += crc, fsm + # crc packet checksum + crc_packet = Signal(crc.width) + last_be = Signal().like(sink.last_be) + fsm.act("IDLE", crc.reset.eq(1), sink.ready.eq(1), @@ -172,9 +193,23 @@ class LiteEthMACCRCInserter(Module): fsm.act("COPY", crc.ce.eq(sink.valid & source.ready), crc.data.eq(sink.data), + crc.last_be.eq(sink.last_be), sink.connect(source), source.last.eq(0), + source.last_be.eq(0), + If(sink.last, + # Fill the empty space of the last data word with the + # beginning of the crc value + [If(sink.last_be[e], + source.data.eq(Cat(sink.data[:(e+1)*8], + crc.value)[:dw])) for e in range(dw//8)], + ).Else( + crc.ce.eq(sink.valid & source.ready), + ), + If(sink.valid & sink.last & source.ready, + NextValue(crc_packet, crc.value), + NextValue(last_be, sink.last_be), NextState("CRC"), ) ) @@ -184,7 +219,7 @@ class LiteEthMACCRCInserter(Module): cnt_done = Signal() fsm.act("CRC", source.valid.eq(1), - chooser(crc.value, cnt, source.data, reverse=True), + chooser(crc_packet, cnt, source.data, reverse=True), If(cnt_done, source.last.eq(1), If(source.ready, NextState("IDLE")) @@ -202,6 +237,9 @@ class LiteEthMACCRCInserter(Module): source.valid.eq(1), source.last.eq(1), source.data.eq(crc.value), + source.last_be.eq(last_be), + [If(last_be[e], + source.data.eq(crc_packet[-(e+1)*8:])) for e in range(dw//8)], If(source.ready, NextState("IDLE")) ) @@ -225,9 +263,9 @@ class LiteEthMACCRCChecker(Module): Attributes ---------- sink : in - Packet octets with CRC. + Packet data with CRC. source : out - Packet octets without CRC and "error" set to 0 + Packet data without CRC and "error" set to 0 on last when CRC OK / set to 1 when CRC KO. error : out Pulses every time a CRC error is detected. @@ -241,6 +279,7 @@ class LiteEthMACCRCChecker(Module): # # # dw = len(sink.data) + assert dw in [8, 32] crc = crc_class(dw) self.submodules += crc ratio = crc.width//dw @@ -268,8 +307,13 @@ class LiteEthMACCRCChecker(Module): source.last.eq(sink.last), fifo.source.ready.eq(fifo_out), source.payload.eq(fifo.source.payload), + source.last_be.eq(sink.last_be), - source.error.eq(sink.error | crc.error), + # `source.error` has a width > 1 for dw > 8, but since the crc error + # applies to the whole ethernet packet, all the bytes are marked as + # containing an error. This way later reducing the data width + # doesn't run into issues with missing the error + source.error.eq(sink.error | Replicate(crc.error, dw//8)), self.error.eq(source.valid & source.last & crc.error), ] @@ -278,7 +322,10 @@ class LiteEthMACCRCChecker(Module): fifo.reset.eq(1), NextState("IDLE"), ) - self.comb += crc.data.eq(sink.data) + self.comb += [ + crc.data.eq(sink.data), + crc.last_be.eq(sink.last_be), + ] fsm.act("IDLE", If(sink.valid & sink.ready, crc.ce.eq(1), diff --git a/liteeth/mac/endian_converter.py b/liteeth/mac/endian_converter.py new file mode 100644 index 0000000..64739ec --- /dev/null +++ b/liteeth/mac/endian_converter.py @@ -0,0 +1,18 @@ +# +# This file is part of LiteEth. +# +# Copyright (c) 2021 David Sawatzke +# SPDX-License-Identifier: BSD-2-Clause + +from liteeth.common import * + +class LiteEthMACEndianConverter(Module): + def __init__(self, dw): + self.sink = sink = stream.Endpoint(eth_phy_description(dw)) + self.source = source = stream.Endpoint(eth_phy_description(dw)) + self.comb += [ + sink.connect(source), + source.data.eq(reverse_bytes(sink.data)), + source.last_be.eq(reverse_bits(sink.last_be)), + source.error.eq(reverse_bits(sink.error)), + ] diff --git a/liteeth/mac/padding.py b/liteeth/mac/padding.py index 2432873..ac9f644 100644 --- a/liteeth/mac/padding.py +++ b/liteeth/mac/padding.py @@ -14,12 +14,14 @@ from liteeth.common import * class LiteEthMACPaddingInserter(Module): def __init__(self, dw, padding): + assert dw in [8, 16, 32, 64] self.sink = sink = stream.Endpoint(eth_phy_description(dw)) self.source = source = stream.Endpoint(eth_phy_description(dw)) # # # padding_limit = math.ceil(padding/(dw/8))-1 + last_be = 2**((padding-1)%(dw//8)) counter = Signal(16) counter_done = Signal() @@ -33,8 +35,14 @@ class LiteEthMACPaddingInserter(Module): If(sink.last, If(~counter_done, source.last.eq(0), + source.last_be.eq(0), NextState("PADDING") - ).Else( + ).Elif((counter == padding_limit) & (last_be > sink.last_be), + # If the right amount of data words are transmitted, but + # too few bytes, transmit more bytes of the word. The + # formerly "unused" bytes get transmitted as well + source.last_be.eq(last_be) + ). Else( NextValue(counter, 0), ) ) @@ -42,7 +50,9 @@ class LiteEthMACPaddingInserter(Module): ) fsm.act("PADDING", source.valid.eq(1), - source.last.eq(counter_done), + If(counter_done, + source.last_be.eq(last_be), + source.last.eq(1)), source.data.eq(0), If(source.valid & source.ready, NextValue(counter, counter + 1), diff --git a/liteeth/mac/preamble.py b/liteeth/mac/preamble.py index 54f5a95..061b41c 100644 --- a/liteeth/mac/preamble.py +++ b/liteeth/mac/preamble.py @@ -25,13 +25,16 @@ class LiteEthMACPreambleInserter(Module): Preamble, SFD, and packet octets. """ def __init__(self, dw): + assert dw in [8, 16, 32, 64] self.sink = stream.Endpoint(eth_phy_description(dw)) self.source = stream.Endpoint(eth_phy_description(dw)) # # # preamble = Signal(64, reset=eth_preamble) - count = Signal(max=(64//dw)-1, reset_less=True) + # For 64 bits, `count` doesn't need to change. But migen won't create a + # signal with a width of 0 bits, so add an unused bit for 64 bit path + count = Signal(max=(64//dw) if dw != 64 else 2, reset_less=True) self.submodules.fsm = fsm = FSM(reset_state="IDLE") fsm.act("IDLE", self.sink.ready.eq(1), @@ -81,7 +84,7 @@ class LiteEthMACPreambleChecker(Module): Pulses every time a preamble error is detected. """ def __init__(self, dw): - assert dw == 8 + assert dw in [8, 16, 32, 64] self.sink = sink = stream.Endpoint(eth_phy_description(dw)) self.source = source = stream.Endpoint(eth_phy_description(dw)) @@ -89,10 +92,12 @@ class LiteEthMACPreambleChecker(Module): # # # + preamble = Signal(64, reset=eth_preamble) self.submodules.fsm = fsm = FSM(reset_state="PREAMBLE") fsm.act("PREAMBLE", sink.ready.eq(1), - If(sink.valid & ~sink.last & (sink.data == (eth_preamble >> 56)), + # Match to end of preamble + If(sink.valid & ~sink.last & (sink.data == preamble[-dw:]), NextState("COPY") ), If(sink.valid & sink.last, self.error.eq(1))