diff --git a/liteeth/mac/crc.py b/liteeth/mac/crc.py index 9f3b0eb..7e37b5c 100644 --- a/liteeth/mac/crc.py +++ b/liteeth/mac/crc.py @@ -1,98 +1,78 @@ # # This file is part of LiteEth. # -# Copyright (c) 2015-2021 Florent Kermarrec +# Copyright (c) 2015-2024 Florent Kermarrec # Copyright (c) 2015 Sebastien Bourdeauducq # Copyright (c) 2021 David Sawatzke # Copyright (c) 2017 whitequark # Copyright (c) 2018 Felix Held # SPDX-License-Identifier: BSD-2-Clause -from functools import reduce -from operator import xor -from collections import OrderedDict from math import ceil +from litex.gen import * + from liteeth.common import * from litex.gen.genlib.misc import chooser, WaitTimer # MAC CRC Engine ----------------------------------------------------------------------------------- -class LiteEthMACCRCEngine(Module): - """Cyclic Redundancy Check Engine +class LiteEthMACCRCEngine(LiteXModule): + """ + Cyclic Redundancy Check (CRC) Engine using an asynchronous LFSR. - Compute next CRC value from last CRC value and data input using - an optimized asynchronous LFSR. + This module calculates the next CRC value based on the previous CRC value and the current data input. + The CRC calculation is optimized for speed and resource efficiency. Parameters ---------- - data_width : int - Width of the data bus. width : int - Width of the CRC. + The bit width of the data bus and CRC value. 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. + The polynomial used for the CRC calculation, specified as an integer (e.g., 0x04C11DB7 for IEEE 802.3). """ def __init__(self, data_width, width, polynom): - self.data = Signal(data_width) - self.last = Signal(width) - self.next = Signal(width) + self.data = Signal(data_width) # Data (Input). + self.crc_prev = Signal(width) # CRC Previous (Input). + self.crc_next = Signal(width) # CRC Next (Output). # # # - def _optimize_eq(l): - """ - remove an even numbers of XORs with the same bit - replace an odd number of XORs with a single 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 + # Determine bits affected by the polynom. + polynom_taps = [bit for bit in range(width) if (1 << bit) & polynom] - # compute and optimize the parallel implementation of the CRC's LFSR - taps = [x for x in range(width) if (1 << x) & polynom] - 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 j+1 in taps: - curval[j] += feedback - curval[j] = _optimize_eq(curval[j]) - curval.insert(0, feedback) + # Prepare the list for CRC calculation through LFSR. + crc_bits = [[("state", i)] for i in range(width)] + for n in range(data_width): + feedback = crc_bits.pop(-1) + [("din", n)] + for pos in range(width - 1): + if (pos + 1) in polynom_taps: + crc_bits[pos] += feedback + crc_bits[pos] = self.optimize_xors(crc_bits[pos]) + crc_bits.insert(0, feedback) - # implement logic + # Calculate the next CRC value based on XOR operations. for i in range(width): xors = [] - for t, n in curval[i]: + for t, n in crc_bits[i]: if t == "state": - xors += [self.last[n]] + xors += [self.crc_prev[n]] elif t == "din": xors += [self.data[n]] - self.comb += self.next[i].eq(reduce(xor, xors)) + self.comb += self.crc_next[i].eq(Reduce("XOR", xors)) + + @staticmethod + def optimize_xors(bits): + """Return items with odd occurrences for XOR optimization.""" + from collections import Counter + return [bit for bit, count in Counter(bits).items() if count % 2 == 1] # MAC CRC32 ---------------------------------------------------------------------------------------- @ResetInserter() @CEInserter() -class LiteEthMACCRC32(Module): +class LiteEthMACCRC32(LiteXModule): """IEEE 802.3 CRC Implement an IEEE 802.3 CRC generator/checker. @@ -106,52 +86,50 @@ class LiteEthMACCRC32(Module): ---------- data : in Data input. - last_be : in - Valid byte in data input (optional). + be : in + Data byte enable (optional, defaults to full word). value : out CRC value (used for generator). error : out CRC error (used for checker). """ width = 32 - polynom = 0x04C11DB7 + polynom = 0x04c11db7 init = 2**width-1 - check = 0xC704DD7B + 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.data = Signal(data_width) + self.be = Signal(data_width//8, reset=2**data_width//8 - 1) + self.value = Signal(self.width) + self.error = Signal() # # # - self.comb += [ - 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)] + # Create a CRC Engine for each byte segment. + # Ex for a 32-bit Data-Path, we create 4 engines: 8, 16, 24 and 32-bit engines. + engines = [] + for n in range(data_width//8): + engines.append(LiteEthMACCRCEngine((n + 1)*8, self.width, self.polynom)) self.submodules += engines + # Register Full-Word CRC Engine (last one). 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)] + self.sync += reg.eq(engines[-1].crc_next) -# MAC CRC Inserter --------------------------------------------------------------------------------- + # Select CRC Engine/Result. + for n in range(data_width//8): + self.comb += [ + engines[n].data.eq(self.data[:(n + 1)*8]), + engines[n].crc_prev.eq(reg), + If(self.be[n], + self.value.eq(reverse_bits(~engines[n].crc_next)), + self.error.eq(engines[n].crc_next != self.check), + ) + ] -class LiteEthMACCRCInserter(Module): +# MAC CRC32 Inserter ------------------------------------------------------------------------------- + +class LiteEthMACCRC32Inserter(LiteXModule): """CRC Inserter Append a CRC at the end of each packet. @@ -168,22 +146,26 @@ class LiteEthMACCRCInserter(Module): source : out Packet data with CRC. """ - def __init__(self, crc_class, description): - self.sink = sink = stream.Endpoint(description) + def __init__(self, description): + self.sink = sink = stream.Endpoint(description) self.source = source = stream.Endpoint(description) # # # - dw = len(sink.data) - assert dw in [8, 32, 64] - crc = crc_class(dw) - fsm = FSM(reset_state="IDLE") - self.submodules += crc, fsm + # Parameters. + data_width = len(sink.data) + ratio = 32//data_width + assert data_width in [8, 32, 64] - # crc packet checksum - crc_packet = Signal(crc.width) - last_be = Signal().like(sink.last_be) + # Signals. + crc_packet = Signal(32) + last_be = Signal(data_width//8) + # CRC32 Generator. + self.crc = crc = LiteEthMACCRC32(data_width) + + # FSM. + self.fsm = fsm = FSM(reset_state="IDLE") fsm.act("IDLE", crc.reset.eq(1), sink.ready.eq(1), @@ -192,35 +174,36 @@ class LiteEthMACCRCInserter(Module): NextState("COPY"), ) ) + self.comb += [ + crc.data.eq(sink.data), + crc.be.eq(sink.last_be), + ] 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 + # 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)], - # If the whole crc value fits in the last sink paket, signal the - # end. This also means the next state is idle - If((dw == 64) & (sink.last_be <= 0xF), + crc.value)[:data_width])) for e in range(data_width//8)], + # If the whole crc value fits in the last sink packet, signal the end. This also + # means the next state is idle + If((data_width == 64) & (sink.last_be <= 0xf), source.last.eq(1), - source.last_be.eq(sink.last_be << (dw//8 - 4)) + source.last_be.eq(sink.last_be << (data_width//8 - 4)) ), ).Else( crc.ce.eq(sink.valid & source.ready), ), If(sink.valid & sink.last & source.ready, - If((dw == 64) & (sink.last_be <= 0xF), + If((data_width == 64) & (sink.last_be <= 0xf), NextState("IDLE"), ).Else( NextValue(crc_packet, crc.value), - If(dw == 64, + If(data_width == 64, NextValue(last_be, sink.last_be >> 4), ).Else ( NextValue(last_be, sink.last_be), @@ -229,16 +212,17 @@ class LiteEthMACCRCInserter(Module): ) ) ) - ratio = crc.width//dw if ratio > 1: - cnt = Signal(max=ratio, reset=ratio-1) + cnt = Signal(max=ratio, reset=ratio-1) cnt_done = Signal() fsm.act("CRC", source.valid.eq(1), chooser(crc_packet, cnt, source.data, reverse=True), If(cnt_done, source.last.eq(1), - If(source.ready, NextState("IDLE")) + If(source.ready, + NextState("IDLE") + ) ) ) self.comb += cnt_done.eq(cnt == 0) @@ -255,18 +239,15 @@ class LiteEthMACCRCInserter(Module): 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")) + source.data.eq(crc_packet[-(e+1)*8:])) for e in range(data_width//8)], + If(source.ready, + NextState("IDLE") + ) ) +# MAC CRC32 Checker -------------------------------------------------------------------------------- -class LiteEthMACCRC32Inserter(LiteEthMACCRCInserter): - def __init__(self, description): - LiteEthMACCRCInserter.__init__(self, LiteEthMACCRC32, description) - -# MAC CRC Checker ---------------------------------------------------------------------------------- - -class LiteEthMACCRCChecker(Module): +class LiteEthMACCRC32Checker(LiteXModule): """CRC Checker Check CRC at the end of each packet. @@ -286,7 +267,7 @@ class LiteEthMACCRCChecker(Module): error : out Pulses every time a CRC error is detected. """ - def __init__(self, crc_class, description): + def __init__(self, description): self.sink = sink = stream.Endpoint(description) self.source = source = stream.Endpoint(description) @@ -294,17 +275,16 @@ class LiteEthMACCRCChecker(Module): # # # - dw = len(sink.data) - assert dw in [8, 32, 64] - crc = crc_class(dw) - self.submodules += crc - ratio = ceil(crc.width/dw) + # Parameters. + data_width = len(sink.data) + ratio = ceil(32/data_width) + assert data_width in [8, 32, 64] - fifo = ResetInserter()(stream.SyncFIFO(description, ratio + 1)) - self.submodules += fifo + # CRC32 Checker. + self.crc = crc = LiteEthMACCRC32(data_width) - fsm = FSM(reset_state="RESET") - self.submodules += fsm + # FIFO. + self.fifo = fifo = ResetInserter()(stream.SyncFIFO(description, ratio + 1)) fifo_in = Signal() fifo_out = Signal() @@ -320,6 +300,8 @@ class LiteEthMACCRCChecker(Module): self.sink.ready.eq(fifo_in), ] + # FSM. + self.fsm = fsm = FSM(reset_state="RESET") fsm.act("RESET", crc.reset.eq(1), fifo.reset.eq(1), @@ -327,7 +309,7 @@ class LiteEthMACCRCChecker(Module): ) self.comb += [ crc.data.eq(sink.data), - crc.last_be.eq(sink.last_be), + crc.be.eq(sink.last_be), ] fsm.act("IDLE", If(sink.valid & sink.ready, @@ -335,37 +317,37 @@ class LiteEthMACCRCChecker(Module): NextState("COPY") ) ) - last_be = Signal().like(sink.last_be) + last_be = Signal().like(sink.last_be) crc_error = Signal() + self.comb += fifo.source.connect(source, omit={"valid", "ready", "last", "last_be"}) fsm.act("COPY", fifo.source.ready.eq(fifo_out), source.valid.eq(sink.valid & fifo_full), - source.payload.eq(fifo.source.payload), - If(dw <= 32, + If(data_width <= 32, source.last.eq(sink.last), source.last_be.eq(sink.last_be), - # For dw == 64 bit, we need to look wether the last word contains only the crc value or both crc and data + # For data_width == 64 bit, we need to look wether the last word contains only the crc value or both crc and data # In the latter case, the last word also needs to be output # In both cases, last_be needs to be adjusted for the new end position ).Elif(sink.last_be & 0xF, source.last.eq(sink.last), - source.last_be.eq(sink.last_be << (dw//8 - 4)), + source.last_be.eq(sink.last_be << (data_width//8 - 4)), ).Else( NextValue(last_be, sink.last_be >> 4), NextValue(crc_error, crc.error), ), - # `source.error` has a width > 1 for dw > 8, but since the crc error + # `source.error` has a width > 1 for data_width > 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 & sink.last, dw//8)), + source.error.eq(sink.error | Replicate(crc.error & sink.last, data_width//8)), self.error.eq(sink.valid & sink.last & crc.error), If(sink.valid & sink.ready, crc.ce.eq(1), - # Can only happen for dw == 64 + # Can only happen for data_width == 64 If(sink.last & (sink.last_be > 0xF), NextState("COPY_LAST"), ).Elif(sink.last, @@ -375,17 +357,12 @@ class LiteEthMACCRCChecker(Module): ) # If the last sink word contains both data and the crc value, shift out - # the last value here. Can only happen for dw == 64 + # the last value here. Can only happen for data_width == 64 fsm.act("COPY_LAST", - fifo.source.connect(source), - source.error.eq(fifo.source.error | Replicate(crc_error, dw//8)), + fifo.source.connect(source, keep={"valid", "ready", "last"}), + source.error.eq(fifo.source.error | Replicate(crc_error, data_width//8)), source.last_be.eq(last_be), If(source.valid & source.ready, NextState("RESET") ) ) - - -class LiteEthMACCRC32Checker(LiteEthMACCRCChecker): - def __init__(self, description): - LiteEthMACCRCChecker.__init__(self, LiteEthMACCRC32, description)