Merge pull request #158 from enjoy-digital/crc_cleanup

CRC cleanups.
This commit is contained in:
enjoy-digital 2024-03-26 12:41:30 +01:00 committed by GitHub
commit c1dc02093d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 122 additions and 145 deletions

View File

@ -1,98 +1,78 @@
#
# This file is part of LiteEth.
#
# Copyright (c) 2015-2021 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2015-2024 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2015 Sebastien Bourdeauducq <sb@m-labs.hk>
# Copyright (c) 2021 David Sawatzke <d-git@sawatzke.dev>
# Copyright (c) 2017 whitequark <whitequark@whitequark.org>
# Copyright (c) 2018 Felix Held <felix-github@felixheld.de>
# 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.be = Signal(data_width//8, reset=2**data_width//8 - 1)
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.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):
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,7 +212,6 @@ class LiteEthMACCRCInserter(Module):
)
)
)
ratio = crc.width//dw
if ratio > 1:
cnt = Signal(max=ratio, reset=ratio-1)
cnt_done = Signal()
@ -238,7 +220,9 @@ class LiteEthMACCRCInserter(Module):
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,
@ -337,35 +319,35 @@ class LiteEthMACCRCChecker(Module):
)
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)