Skip to content

Commit 6bc8a23

Browse files
Merge pull request #183 from david-sawatzke/optimize_crc
mac: Only use one CRC engine for Checker
2 parents 5fba3c4 + e6bfbf2 commit 6bc8a23

File tree

4 files changed

+243
-15
lines changed

4 files changed

+243
-15
lines changed

liteeth/mac/crc.py

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ class LiteEthMACCRCEngine(LiteXModule):
2626
2727
Parameters
2828
----------
29+
data_width : int
30+
The bit width of the data bus
2931
width : int
30-
The bit width of the data bus and CRC value.
32+
The bit width of CRC value.
3133
polynom : int
3234
The polynomial used for the CRC calculation, specified as an integer (e.g., 0x04C11DB7 for IEEE 802.3).
3335
"""
@@ -67,6 +69,53 @@ def optimize_xors(bits):
6769
from collections import Counter
6870
return [bit for bit, count in Counter(bits).items() if count % 2 == 1]
6971

72+
def crc_calc(data_width, width, polynom, crc_prev, data):
73+
"""
74+
Calculate the next CRC value. Functionally equivalent to the migen CRCEngine, but as a python function
75+
76+
Parameters
77+
----------
78+
data_width : int
79+
The bit width of data
80+
width : int
81+
The bit width of the CRC value
82+
polynom : int
83+
The polynomial used for the CRC calculation, specified as an integer (e.g., 0x04C11DB7 for IEEE 802.3).
84+
crc_prev : int
85+
The previous CRC value
86+
data : int
87+
The new data word
88+
89+
Returns
90+
-------
91+
int
92+
The next CRC value
93+
"""
94+
# Convert crc_prev into a list of bits (LSB first) for easier bitwise operations
95+
state = [(crc_prev >> i) & 1 for i in range(width)]
96+
97+
# Process each bit of the input data (assumed LSB-first).
98+
for n in range(data_width):
99+
d = (data >> n) & 1
100+
feedback = state[-1] ^ d
101+
state.pop()
102+
103+
# For each remaining bit position (positions 0 .. width-2),
104+
# if the corresponding tap (at bit position pos+1 in the polynomial)
105+
# is active, XOR the feedback into that bit.
106+
for pos in range(width - 1):
107+
if (polynom >> (pos + 1)) & 1:
108+
state[pos] ^= feedback
109+
# Insert the feedback at the beginning of the state (this is equivalent
110+
# to shifting the register and feeding in the new bit).
111+
state.insert(0, feedback)
112+
113+
crc_next = 0
114+
for i, bit in enumerate(state):
115+
if bit:
116+
crc_next |= (1 << i)
117+
return crc_next
118+
70119
# MAC CRC32 ----------------------------------------------------------------------------------------
71120

72121
@ResetInserter()
@@ -131,6 +180,70 @@ def __init__(self, data_width):
131180
)
132181
]
133182

183+
# MAC CRC32 ----------------------------------------------------------------------------------------
184+
185+
@ResetInserter()
186+
@CEInserter()
187+
class LiteEthMACCRC32Check(LiteXModule):
188+
"""IEEE 802.3 CRC
189+
190+
Implement an IEEE 802.3 CRC checker.
191+
192+
Parameters
193+
----------
194+
data_width : int
195+
Width of the data bus.
196+
197+
Attributes
198+
----------
199+
data : in
200+
Data input.
201+
be : in
202+
Data byte enable (optional, defaults to full word).
203+
error : out
204+
CRC error (used for checker).
205+
"""
206+
width = 32
207+
polynom = 0x04c11db7
208+
init = 2**width - 1
209+
check = 0xc704dd7b
210+
def __init__(self, data_width):
211+
self.data = Signal(data_width)
212+
self.be = Signal(data_width//8, reset=2**data_width//8 - 1)
213+
self.value = Signal(self.width)
214+
self.error = Signal()
215+
216+
check_be = [self.check]
217+
for _ in range(1, data_width//8):
218+
check_be.append(crc_calc(8, self.width, self.polynom, check_be[-1], 0))
219+
220+
# # #
221+
222+
# Create a CRC Engine for the data_width
223+
self.submodules.engine = engine = LiteEthMACCRCEngine(
224+
data_width = data_width,
225+
width = self.width,
226+
polynom = self.polynom,
227+
)
228+
229+
# Register Full-Word CRC Engine (last one).
230+
reg = Signal(self.width, reset=self.init)
231+
self.sync += reg.eq(engine.crc_next)
232+
233+
# Select CRC Engine/Result.
234+
self.comb += [
235+
# TODO mask data
236+
engine.data.eq(self.data),
237+
engine.crc_prev.eq(reg),
238+
]
239+
for n in range(data_width//8):
240+
self.comb += [
241+
If(self.be[n],
242+
engine.data.eq(self.data & (2**((n + 1)*8) - 1)),
243+
self.error.eq(engine.crc_next != check_be[-(n + 1)]),
244+
)
245+
]
246+
134247
# MAC CRC32 Inserter -------------------------------------------------------------------------------
135248

136249
class LiteEthMACCRC32Inserter(LiteXModule):
@@ -159,7 +272,7 @@ def __init__(self, description):
159272
# Parameters.
160273
data_width = len(sink.data)
161274
ratio = 32//data_width
162-
assert data_width in [8, 16, 32, 64]
275+
assert data_width in [8, 32, 64]
163276

164277
# Signals.
165278
crc_packet = Signal(32, reset_less=True)
@@ -279,10 +392,10 @@ def __init__(self, description):
279392
# Parameters.
280393
data_width = len(sink.data)
281394
ratio = ceil(32/data_width)
282-
assert data_width in [8, 16, 32, 64]
395+
assert data_width in [8, 32, 64]
283396

284397
# CRC32 Checker.
285-
self.crc = crc = LiteEthMACCRC32(data_width)
398+
self.crc = crc = LiteEthMACCRC32Check(data_width)
286399

287400
# FIFO.
288401
self.fifo = fifo = ResetInserter()(stream.SyncFIFO(description, ratio + 1))

test/test_crc.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#
2+
# This file is part of LiteEth.
3+
#
4+
# Copyright (c) 2025 David Sawatzke <d-git@sawatzke.dev>
5+
# SPDX-License-Identifier: BSD-2-Clause
6+
7+
import unittest
8+
import random
9+
10+
from migen import *
11+
12+
from liteeth.common import *
13+
from liteeth.mac.crc import *
14+
15+
from litex.gen.sim import *
16+
17+
from .test_stream import (
18+
mask_last_be,
19+
StreamPacket,
20+
stream_inserter,
21+
stream_collector,
22+
compare_packets,
23+
)
24+
25+
26+
def get_stream_desc(dw):
27+
return [
28+
("data", dw),
29+
("last_be", dw // 8),
30+
("error", dw // 8),
31+
]
32+
33+
34+
class DUT(Module):
35+
def __init__(self, dw):
36+
self.submodules.inserter = LiteEthMACCRC32Inserter(eth_phy_description(dw))
37+
self.submodules.checker = LiteEthMACCRC32Checker(eth_phy_description(dw))
38+
self.comb += self.inserter.source.connect(self.checker.sink)
39+
40+
41+
# -----------------------------------------------------------------------------
42+
# Simulation Test
43+
# -----------------------------------------------------------------------------
44+
45+
46+
class TestCRC(unittest.TestCase):
47+
def crc_inserter_checker_test(self, dw=32, seed=42, npackets=2, debug_print=False):
48+
prng = random.Random(seed + 5)
49+
50+
dut = DUT(dw)
51+
desc = get_stream_desc(dw)
52+
full_last_be = (1 << (dw // 8)) - 1
53+
54+
packets = []
55+
56+
for n in range(npackets):
57+
header = {}
58+
datas = [prng.randrange(2**8) for _ in range(prng.randrange(dw - 1) + 1)]
59+
packets.append(StreamPacket(datas, header))
60+
61+
recvd_packets = []
62+
run_simulation(
63+
dut,
64+
[
65+
stream_inserter(
66+
dut.inserter.sink,
67+
src=packets,
68+
seed=seed,
69+
debug_print=debug_print,
70+
valid_rand=50,
71+
),
72+
stream_collector(
73+
dut.checker.source,
74+
dest=recvd_packets,
75+
expect_npackets=npackets,
76+
seed=seed,
77+
debug_print=debug_print,
78+
ready_rand=50,
79+
),
80+
],
81+
vcd_name="crc_test_{}bit_seed{}.vcd".format(dw, seed),
82+
)
83+
84+
if not compare_packets(packets, recvd_packets):
85+
86+
print("crc_test_{}bit_seed{}".format(dw, seed))
87+
print(len(packets))
88+
for i in range(len(packets)):
89+
print(i)
90+
print(packets[i].data)
91+
print(recvd_packets[i].data)
92+
assert False
93+
94+
def test_8bit_loopback(self):
95+
for seed in range(42, 48):
96+
with self.subTest(seed=seed):
97+
self.crc_inserter_checker_test(dw=8, seed=seed)
98+
99+
def test_32bit_loopback(self):
100+
for seed in range(42, 48):
101+
with self.subTest(seed=seed):
102+
self.crc_inserter_checker_test(dw=32, seed=seed)
103+
104+
# TODO the 64 bit case has a few issues unrelated to LiteEthMACCRC32Check
105+
# def test_64bit_loopback(self):
106+
# for seed in range(42, 70):
107+
# with self.subTest(seed=seed):
108+
# self.crc_inserter_checker_test(dw=64, seed=seed)
109+
110+
# 16 bit is completely broken
111+
# def test_16bit_loopback(self):
112+
# for seed in range(42, 70):
113+
# with self.subTest(seed=seed):
114+
# self.crc_inserter_checker_test(dw=16, seed=seed)

test/test_packet.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,7 @@
1212
from litex.soc.interconnect.stream import *
1313
from liteeth.packet import *
1414

15-
from .test_stream import StreamPacket, stream_inserter, stream_collector, compare_packets
16-
17-
def mask_last_be(dw, data, last_be):
18-
masked_data = 0
19-
20-
for byte in range(dw // 8):
21-
if 2**byte > last_be:
22-
break
23-
masked_data |= data & (0xFF << (byte * 8))
24-
25-
return masked_data
15+
from .test_stream import StreamPacket, stream_inserter, stream_collector, compare_packets, mask_last_be
2616

2717
class TestPacket(unittest.TestCase):
2818
def loopback_test(self, dw, seed=42, with_last_be=False, debug_print=False):

test/test_stream.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ def grouper(iterable, n, fillvalue=None):
2222
args = [iter(iterable)] * n
2323
return itertools.zip_longest(*args, fillvalue=fillvalue)
2424

25+
def mask_last_be(dw, data, last_be):
26+
masked_data = 0
27+
28+
for byte in range(dw // 8):
29+
if 2**byte > last_be:
30+
break
31+
masked_data |= data & (0xFF << (byte * 8))
32+
33+
return masked_data
34+
2535
class StreamPacket:
2636
def __init__(self, data, params={}):
2737
# Data must be a list of bytes
@@ -251,6 +261,7 @@ def stream_collector(
251261
if (yield source.last) == 1:
252262
read_last = True
253263
if hasattr(source, "last_be") and \
264+
dw != 8 and \
254265
2**byte > (yield source.last_be):
255266
break
256267
collected_bytes += [((data >> (byte * 8)) & 0xFF)]

0 commit comments

Comments
 (0)