lib.crc,docs/stdlib/crc: improve consistency with the rest of documentation.
This commit is contained in:
parent
c68f9e43f9
commit
b6f51d269e
|
@ -1,155 +1,114 @@
|
||||||
"""
|
import operator
|
||||||
The :mod:`amaranth.lib.crc` module provides utilities for computing cyclic
|
|
||||||
redundancy checks (CRCs) in software and in hardware.
|
|
||||||
|
|
||||||
CRCs are specified using the :py:class:`Algorithm` class, which contains
|
|
||||||
settings for CRC width, polynomial, initial value, input/output reflection, and
|
|
||||||
output XOR. Many commonly used CRC algorithms are available in the
|
|
||||||
:py:mod:`~amaranth.lib.crc.catalog` module, while most other CRC designs can be
|
|
||||||
accommodated by manually constructing :py:class:`Algorithm`.
|
|
||||||
|
|
||||||
Call the :py:class:`Algorithm` with a ``data_width`` to obtain a
|
|
||||||
:py:class:`Parameters` class, which fully defines a CRC computation. The
|
|
||||||
:py:class:`Parameters` class provides the :py:meth:`~Parameters.compute` method
|
|
||||||
to perform software computations, and the :py:meth:`~Parameters.create` method
|
|
||||||
to create a hardware CRC module, :py:class:`Processor`.
|
|
||||||
|
|
||||||
.. code-block::
|
|
||||||
|
|
||||||
# Create a predefined CRC16-CCITT hardware module, using the default
|
|
||||||
# 8-bit data width (in other words, bytes).
|
|
||||||
from amaranth.lib.crc.catalog import CRC16_CCITT
|
|
||||||
crc = m.submodules.crc = CRC16_CCITT().create()
|
|
||||||
|
|
||||||
# Create a custom CRC algorithm, specify the data width explicitly,
|
|
||||||
# and use it to compute a CRC value in software.
|
|
||||||
from amaranth.lib.crc import Algorithm
|
|
||||||
algo = Algorithm(crc_width=16, polynomial=0x1021, initial_crc=0xffff,
|
|
||||||
reflect_input=False, reflect_output=False,
|
|
||||||
xor_output=0x0000)
|
|
||||||
assert algo(data_width=8).compute(b"123456789") == 0x29b1
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ... import *
|
from ... import *
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["Algorithm", "Parameters", "Processor", "catalog"]
|
__all__ = ["Algorithm", "Parameters", "Processor", "catalog"]
|
||||||
|
|
||||||
|
|
||||||
class Algorithm:
|
class Algorithm:
|
||||||
"""
|
"""Essential parameters for cyclic redundancy check computation.
|
||||||
Settings for a CRC algorithm, excluding data width.
|
|
||||||
|
|
||||||
The parameter set is based on the Williams model from
|
The parameter set is based on the Williams model from `"A Painless Guide to CRC Error Detection
|
||||||
"A Painless Guide to CRC Error Detection Algorithms":
|
Algorithms" <http://www.ross.net/crc/download/crc_v3.txt>`_.
|
||||||
http://www.ross.net/crc/download/crc_v3.txt
|
|
||||||
|
|
||||||
For a reference of standard CRC parameter sets, refer to:
|
For a reference of standard CRC parameter sets, refer to:
|
||||||
|
|
||||||
* `reveng`_'s catalogue, which uses an identical parameterisation,
|
* `reveng`_'s catalogue, which uses an identical parameterisation;
|
||||||
* `crcmod`_'s list of predefined functions, but remove the leading '1'
|
* `crcmod`_'s list of predefined functions, but remove the leading '1' from the polynominal,
|
||||||
from the polynominal, XOR the "Init-value" with "XOR-out" to obtain
|
XOR the "Init-value" with "XOR-out" to obtain :py:`initial_crc`, and where "Reversed" is
|
||||||
``initial_crc``, and where "Reversed" is True, set both ``reflect_input``
|
:py:`True`, set both :py:`reflect_input` and :py:`reflect_output` to :py:`True`;
|
||||||
and ``reflect_output`` to True,
|
* `CRC Zoo`_, which contains only polynomials; use the "explicit +1" form of polynomial but
|
||||||
* `CRC Zoo`_, which contains only polynomials; use the "explicit +1"
|
remove the leading '1'.
|
||||||
form of polynomial but remove the leading '1'.
|
|
||||||
|
|
||||||
.. _reveng: https://reveng.sourceforge.io/crc-catalogue/all.htm
|
.. _reveng: https://reveng.sourceforge.io/crc-catalogue/all.htm
|
||||||
.. _crcmod: https://crcmod.sourceforge.net/crcmod.predefined.html
|
.. _crcmod: https://crcmod.sourceforge.net/crcmod.predefined.html
|
||||||
.. _CRC Zoo: https://users.ece.cmu.edu/~koopman/crc/
|
.. _CRC Zoo: https://users.ece.cmu.edu/~koopman/crc/
|
||||||
|
|
||||||
Many commonly used CRC algorithms are available in the
|
Many commonly used CRC algorithms are available in the :py:mod:`~amaranth.lib.crc.catalog`
|
||||||
:py:mod:`~amaranth.lib.crc.catalog` module, which includes
|
module, which includes all entries in the `reveng catalogue <reveng_>`_.
|
||||||
all entries in the `reveng`_ catalogue.
|
|
||||||
|
|
||||||
To create a :py:class:`Parameters` instance, call the :py:class:`Algorithm`
|
The essential parameters on their own cannot be used to perform CRC computation, and must be
|
||||||
object with the required data width, which defaults to 8 bits.
|
combined with a specific data word width. This can be done using :py:`algo(data_width)`, which
|
||||||
|
returns a :class:`Parameters` object.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
crc_width : int
|
crc_width : :class:`int`
|
||||||
Bit width of CRC word. Also known as "width" in the Williams model.
|
Bit width of CRC word. Also known as "width" in the Williams model.
|
||||||
polynomial : int
|
polynomial : :class:`int`
|
||||||
CRC polynomial to use, ``crc_width`` bits long, without the implicit
|
CRC polynomial to use, :py:`crc_width` bits long, without the implicit :py:`x ** crc_width`
|
||||||
``x**crc_width`` term. Polynomial is always specified with the highest
|
term. Polynomial is always specified with the highest order terms in the most significant
|
||||||
order terms in the most significant bit positions; use
|
bit positions; use :py:`reflect_input` and :py:`reflect_output` to perform a least
|
||||||
``reflect_input`` and ``reflect_output`` to perform a least
|
|
||||||
significant bit first computation.
|
significant bit first computation.
|
||||||
initial_crc : int
|
initial_crc : :class:`int`
|
||||||
Initial value of CRC register at reset. Most significant bit always
|
Initial value of CRC register at reset. Most significant bit always corresponds to
|
||||||
corresponds to the highest order term in the CRC register.
|
the highest order term in the CRC register.
|
||||||
reflect_input : bool
|
reflect_input : :class:`bool`
|
||||||
If True, the input data words are bit-reflected, so that they are
|
If :py:`True`, the input data words are bit-reflected, so that they are processed least
|
||||||
processed least significant bit first.
|
significant bit first.
|
||||||
reflect_output : bool
|
reflect_output : :class:`bool`
|
||||||
If True, the output CRC is bit-reflected, so the least-significant bit
|
If :py:`True`, the output CRC is bit-reflected, so that the least-significant bit of
|
||||||
of the output is the highest-order bit of the CRC register.
|
the output is the highest-order bit of the CRC register. Note that this reflection is
|
||||||
Note that this reflection is performed over the entire CRC register;
|
performed over the entire CRC register; for transmission you may want to treat the output
|
||||||
for transmission you may want to treat the output as a little-endian
|
as a little-endian multi-word value, so for example the reflected 16-bit output :py:`0x4E4C`
|
||||||
multi-word value, so for example the reflected 16-bit output 0x4E4C
|
would be transmitted as the two octets :py:`0x4C, 0x4E`, each transmitted least significant
|
||||||
would be transmitted as the two octets 0x4C 0x4E, each transmitted
|
bit first.
|
||||||
least significant bit first.
|
xor_output : :class:`int`
|
||||||
xor_output : int
|
The output CRC will be the CRC register XOR'd with this value, applied after any output
|
||||||
The output CRC will be the CRC register XOR'd with this value, applied
|
bit-reflection.
|
||||||
after any output bit-reflection.
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, *, crc_width, polynomial, initial_crc, reflect_input,
|
def __init__(self, *, crc_width, polynomial, initial_crc, reflect_input,
|
||||||
reflect_output, xor_output):
|
reflect_output, xor_output):
|
||||||
self.crc_width = int(crc_width)
|
self.crc_width = operator.index(crc_width)
|
||||||
self.polynomial = int(polynomial)
|
self.polynomial = operator.index(polynomial)
|
||||||
self.initial_crc = int(initial_crc)
|
self.initial_crc = operator.index(initial_crc)
|
||||||
self.reflect_input = bool(reflect_input)
|
self.reflect_input = bool(reflect_input)
|
||||||
self.reflect_output = bool(reflect_output)
|
self.reflect_output = bool(reflect_output)
|
||||||
self.xor_output = int(xor_output)
|
self.xor_output = operator.index(xor_output)
|
||||||
|
|
||||||
if self.crc_width <= 0:
|
if not self.crc_width > 0:
|
||||||
raise ValueError("crc_width must be greater than 0")
|
raise ValueError("CRC width must be greater than 0")
|
||||||
if not 0 <= self.polynomial < 2 ** self.crc_width:
|
if self.polynomial not in range(2 ** self.crc_width):
|
||||||
raise ValueError("polynomial must be between 0 and 2**crc_width - 1")
|
raise ValueError("Polynomial must be between 0 and (2 ** crc_width - 1)")
|
||||||
if not 0 <= self.initial_crc < 2 ** self.crc_width:
|
if self.initial_crc not in range(2 ** self.crc_width):
|
||||||
raise ValueError("initial_crc must be between 0 and 2**crc_width - 1")
|
raise ValueError("Initial CRC must be between 0 and (2 ** crc_width - 1)")
|
||||||
if not 0 <= self.xor_output < 2 ** self.crc_width:
|
if self.xor_output not in range(2 ** self.crc_width):
|
||||||
raise ValueError("xor_output must be between 0 and 2**crc_width - 1")
|
raise ValueError("XOR output must be between 0 and (2 ** crc_width - 1)")
|
||||||
|
|
||||||
def __call__(self, data_width=8):
|
def __call__(self, data_width=8):
|
||||||
"""
|
"""Combine these essential parameters with a data word width to form complete parameters.
|
||||||
Constructs a :py:class:`Parameters` instance from this
|
|
||||||
:py:class:`Algorithm` with the specified ``data_width``.
|
|
||||||
|
|
||||||
Parameters
|
Returns
|
||||||
----------
|
-------
|
||||||
data_width : int
|
:class:`Parameters`
|
||||||
Bit width of data words, default 8.
|
:py:`Parameters(self, data_width)`
|
||||||
"""
|
"""
|
||||||
return Parameters(self, data_width)
|
return Parameters(self, data_width)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Algorithm(crc_width={self.crc_width}," \
|
return (f"Algorithm(crc_width={self.crc_width}, "
|
||||||
f" polynomial=0x{self.polynomial:0{self.crc_width//4}x}," \
|
f"polynomial=0x{self.polynomial:0{self.crc_width // 4}x}, "
|
||||||
f" initial_crc=0x{self.initial_crc:0{self.crc_width//4}x}," \
|
f"initial_crc=0x{self.initial_crc:0{self.crc_width // 4}x}, "
|
||||||
f" reflect_input={self.reflect_input}," \
|
f"reflect_input={self.reflect_input}, "
|
||||||
f" reflect_output={self.reflect_output}," \
|
f"reflect_output={self.reflect_output}, "
|
||||||
f" xor_output=0x{self.xor_output:0{self.crc_width//4}x})"
|
f"xor_output=0x{self.xor_output:0{self.crc_width // 4}x})")
|
||||||
|
|
||||||
|
|
||||||
class Parameters:
|
class Parameters:
|
||||||
"""
|
"""Complete parameters for cyclic redundancy check computation.
|
||||||
Full set of parameters for a CRC computation.
|
|
||||||
|
|
||||||
Contains the settings from :py:class:`Algorithm` and additionally
|
Contains the essential :class:`Algorithm` parameters, plus the data word width.
|
||||||
``data_width``. Refer to :py:class:`Algorithm` for details of what each
|
|
||||||
parameter means and how to construct them.
|
|
||||||
|
|
||||||
From this class, you can directly compute CRCs with the
|
A :class:`Parameters` object can be used to directly compute CRCs using
|
||||||
:py:meth:`~Parameters.compute` method, or construct a hardware module with
|
the :meth:`~Parameters.compute` method, or to construct a hardware module using
|
||||||
the :py:meth:`~Parameters.create` method.
|
the :meth:`~Parameters.create` method.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
algorithm : Algorithm
|
algorithm : :class:`Algorithm`
|
||||||
CRC algorithm to use. Specifies the CRC width, polynomial,
|
CRC algorithm to use. Specifies the CRC width, polynomial, initial value, whether to
|
||||||
initial value, whether to reflect the input or output words,
|
reflect the input or output words, and any output XOR.
|
||||||
and any output XOR.
|
data_width : :class:`int`
|
||||||
data_width : int
|
|
||||||
Bit width of data words.
|
Bit width of data words.
|
||||||
"""
|
"""
|
||||||
def __init__(self, algorithm, data_width=8):
|
def __init__(self, algorithm, data_width=8):
|
||||||
|
@ -159,16 +118,12 @@ class Parameters:
|
||||||
self._reflect_input = algorithm.reflect_input
|
self._reflect_input = algorithm.reflect_input
|
||||||
self._reflect_output = algorithm.reflect_output
|
self._reflect_output = algorithm.reflect_output
|
||||||
self._xor_output = algorithm.xor_output
|
self._xor_output = algorithm.xor_output
|
||||||
self._data_width = int(data_width)
|
self.data_width = operator.index(data_width)
|
||||||
if self._data_width <= 0:
|
if not self.data_width > 0:
|
||||||
raise ValueError("data_width must be greater than 0")
|
raise ValueError("Data width must be greater than 0")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def algorithm(self):
|
def algorithm(self):
|
||||||
"""
|
|
||||||
Returns an :py:class:`Algorithm` with the CRC settings from this
|
|
||||||
instance.
|
|
||||||
"""
|
|
||||||
return Algorithm(
|
return Algorithm(
|
||||||
crc_width=self._crc_width,
|
crc_width=self._crc_width,
|
||||||
polynomial=self._polynomial,
|
polynomial=self._polynomial,
|
||||||
|
@ -178,13 +133,11 @@ class Parameters:
|
||||||
xor_output=self._xor_output)
|
xor_output=self._xor_output)
|
||||||
|
|
||||||
def residue(self):
|
def residue(self):
|
||||||
"""
|
"""Obtain the residual value left in the CRC register after processing a valid trailing CRC.
|
||||||
Compute the residue value for this CRC, which is the value left in the
|
|
||||||
CRC register after processing any valid codeword.
|
A trailing CRC data word is also known as a codeword."""
|
||||||
"""
|
# Residue is computed by initialising to (possibly reflected) xor_output, feeding crc_width
|
||||||
# Residue is computed by initialising to (possibly reflected)
|
# worth of 0 bits, then taking the (possibly reflected) output without any XOR.
|
||||||
# xor_output, feeding crc_width worth of 0 bits, then taking
|
|
||||||
# the (possibly reflected) output without any XOR.
|
|
||||||
if self._reflect_output:
|
if self._reflect_output:
|
||||||
init = self._reflect(self._xor_output, self._crc_width)
|
init = self._reflect(self._xor_output, self._crc_width)
|
||||||
else:
|
else:
|
||||||
|
@ -193,179 +146,164 @@ class Parameters:
|
||||||
algo.initial_crc = init
|
algo.initial_crc = init
|
||||||
algo.reflect_input = False
|
algo.reflect_input = False
|
||||||
algo.xor_output = 0
|
algo.xor_output = 0
|
||||||
return algo(data_width=self._crc_width).compute([0])
|
return algo(self._crc_width).compute([0])
|
||||||
|
|
||||||
def create(self):
|
|
||||||
"""
|
|
||||||
Returns a ``Processor`` configured with these parameters.
|
|
||||||
"""
|
|
||||||
return Processor(self)
|
|
||||||
|
|
||||||
def compute(self, data):
|
def compute(self, data):
|
||||||
"""
|
"""Compute the CRC of all data words in :py:`data`.
|
||||||
Computes and returns the CRC of all data words in ``data``.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
data : iterable of integers
|
data : iterable of :class:`int`
|
||||||
The CRC is computed over this complete set of data.
|
Data words, each of which is :py:`data_width` bits wide.
|
||||||
Each item is an integer of bitwidth equal to ``data_width``.
|
|
||||||
"""
|
"""
|
||||||
# Precompute some constants we use every iteration.
|
# Precompute some constants we use every iteration.
|
||||||
word_max = (1 << self._data_width) - 1
|
word_max = (1 << self.data_width) - 1
|
||||||
top_bit = 1 << (self._crc_width + self._data_width - 1)
|
top_bit = 1 << (self._crc_width + self.data_width - 1)
|
||||||
crc_mask = (1 << (self._crc_width + self._data_width)) - 1
|
crc_mask = (1 << (self._crc_width + self.data_width)) - 1
|
||||||
poly_shifted = self._polynomial << self._data_width
|
poly_shifted = self._polynomial << self.data_width
|
||||||
|
|
||||||
# Implementation notes:
|
# Implementation notes:
|
||||||
# We always compute most-significant bit first, which means the
|
# We always compute most-significant bit first, which means the polynomial and initial
|
||||||
# polynomial and initial value may be used as-is, and the reflect_in
|
# value may be used as-is, and the reflect_in and reflect_out values have their usual sense.
|
||||||
# and reflect_out values have their usual sense.
|
# However, when computing word-at-a-time and MSbit-first, we must align the input word so
|
||||||
# However, when computing word-at-a-time and MSbit-first, we must align
|
# its MSbit lines up with the MSbit of the previous CRC value. When the CRC width is smaller
|
||||||
# the input word so its MSbit lines up with the MSbit of the previous
|
# than the word width, this would normally truncate data bits. Instead, we shift the initial
|
||||||
# CRC value. When the CRC width is smaller than the word width, this
|
# CRC left by the data width, and the data word left by the crc width, lining up their
|
||||||
# would normally truncate data bits.
|
# MSbits no matter the relation between the two widths. The new CRC is then shifted right by
|
||||||
# Instead, we shift the initial CRC left by the data width, and the
|
# the data width before output.
|
||||||
# data word left by the crc width, lining up their MSbits no matter
|
|
||||||
# the relation between the two widths.
|
|
||||||
# The new CRC is then shifted right by the data width before output.
|
|
||||||
|
|
||||||
crc = self._initial_crc << self._data_width
|
crc = self._initial_crc << self.data_width
|
||||||
for word in data:
|
for word in data:
|
||||||
if not 0 <= word <= word_max:
|
if not 0 <= word <= word_max:
|
||||||
raise ValueError(f"data word must be between 0 and {word_max - 1}")
|
raise ValueError(f"data word must be between 0 and {word_max - 1}")
|
||||||
|
|
||||||
if self._reflect_input:
|
if self._reflect_input:
|
||||||
word = self._reflect(word, self._data_width)
|
word = self._reflect(word, self.data_width)
|
||||||
|
|
||||||
crc ^= word << self._crc_width
|
crc ^= word << self._crc_width
|
||||||
for _ in range(self._data_width):
|
for _ in range(self.data_width):
|
||||||
if crc & top_bit:
|
if crc & top_bit:
|
||||||
crc = (crc << 1) ^ poly_shifted
|
crc = (crc << 1) ^ poly_shifted
|
||||||
else:
|
else:
|
||||||
crc <<= 1
|
crc <<= 1
|
||||||
crc &= crc_mask
|
crc &= crc_mask
|
||||||
|
|
||||||
crc >>= self._data_width
|
crc >>= self.data_width
|
||||||
if self._reflect_output:
|
if self._reflect_output:
|
||||||
crc = self._reflect(crc, self._crc_width)
|
crc = self._reflect(crc, self._crc_width)
|
||||||
|
|
||||||
crc ^= self._xor_output
|
crc ^= self._xor_output
|
||||||
return crc
|
return crc
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
"""Create a hardware CRC generator with these parameters.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`Processor`
|
||||||
|
:py:`Processor(self)`
|
||||||
|
"""
|
||||||
|
return Processor(self)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _reflect(word, n):
|
def _reflect(word, n):
|
||||||
"""
|
"""Bitwise-reflect an :py:`n`-bit word :py:`word`."""
|
||||||
Bitwise-reflects an n-bit word ``word``.
|
|
||||||
"""
|
|
||||||
return int(f"{word:0{n}b}"[::-1], 2)
|
return int(f"{word:0{n}b}"[::-1], 2)
|
||||||
|
|
||||||
def _matrices(self):
|
def _matrices(self):
|
||||||
"""
|
"""Compute the F and G matrices for parallel CRC computation.
|
||||||
Computes the F and G matrices for parallel CRC computation, treating
|
|
||||||
the CRC as a linear time-invariant system described by the state
|
|
||||||
relation x(t+1) = F.x(i) + G.u(i), where x(i) and u(i) are column
|
|
||||||
vectors of the bits of the CRC register and input word, F is the n-by-n
|
|
||||||
matrix relating the old state to the new state, and G is the n-by-m
|
|
||||||
matrix relating the new data to the new state, where n is the CRC
|
|
||||||
width and m is the data word width.
|
|
||||||
|
|
||||||
The matrices are ordered least-significant-bit first; in other words
|
Computes the F and G matrices for parallel CRC computation, treating the CRC as a linear
|
||||||
the first entry, with index (0, 0), corresponds to the effect of the
|
time-invariant system described by the state relation x(t+1) = F.x(i) + G.u(i), where x(i)
|
||||||
least-significant bit of the input on the least-significant bit of the
|
and u(i) are column vectors of the bits of the CRC register and input word, F is the n-by-n
|
||||||
output.
|
matrix relating the old state to the new state, and G is the n-by-m matrix relating the new
|
||||||
|
data to the new state, where n is the CRC width and m is the data word width.
|
||||||
|
|
||||||
For convenience of implementation, both matrices are returned
|
The matrices are ordered least-significant-bit first; in other words the first entry, with
|
||||||
transposed: the first index is the input bit, and the second index is
|
index (0, 0), corresponds to the effect of the least-significant bit of the input on
|
||||||
the corresponding output bit.
|
the least-significant bit of the output.
|
||||||
|
|
||||||
The matrices are used to select which bits are XORd together to compute
|
For convenience of implementation, both matrices are returned transposed: the first index
|
||||||
each bit i of the new state: if F[j][i] is set then bit j of the old
|
is the input bit, and the second index is the corresponding output bit.
|
||||||
state is included in the XOR, and if G[j][i] is set then bit j of the
|
|
||||||
new data is included.
|
|
||||||
|
|
||||||
These matrices are not affected by ``initial_crc``, ``reflect_input``,
|
The matrices are used to select which bits are XORd together to compute each bit i of
|
||||||
``reflect_output``, or ``xor_output``.
|
the new state: if F[j][i] is set then bit j of the old state is included in the XOR, and
|
||||||
|
if G[j][i] is set then bit j of the new data is included.
|
||||||
|
|
||||||
|
These matrices are not affected by :py:`initial_crc`, :py:`reflect_input`,
|
||||||
|
:py:`reflect_output`, or :py:`xor_output`.
|
||||||
"""
|
"""
|
||||||
f = []
|
f = []
|
||||||
g = []
|
g = []
|
||||||
algo = self.algorithm
|
algo = self.algorithm
|
||||||
algo.reflect_input = algo.reflect_output = False
|
algo.reflect_input = algo.reflect_output = False
|
||||||
algo.xor_output = 0
|
algo.xor_output = 0
|
||||||
crc = Parameters(algo, self._data_width)
|
crc = Parameters(algo, self.data_width)
|
||||||
for i in range(self._crc_width):
|
for i in range(self._crc_width):
|
||||||
crc._initial_crc = 2 ** i
|
crc._initial_crc = 2 ** i
|
||||||
w = crc.compute([0])
|
w = crc.compute([0])
|
||||||
f.append([int(x) for x in reversed(f"{w:0{self._crc_width}b}")])
|
f.append([int(x) for x in reversed(f"{w:0{self._crc_width}b}")])
|
||||||
for i in range(self._data_width):
|
for i in range(self.data_width):
|
||||||
crc._initial_crc = 0
|
crc._initial_crc = 0
|
||||||
w = crc.compute([2 ** i])
|
w = crc.compute([2 ** i])
|
||||||
g.append([int(x) for x in reversed(f"{w:0{self._crc_width}b}")])
|
g.append([int(x) for x in reversed(f"{w:0{self._crc_width}b}")])
|
||||||
return f, g
|
return f, g
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Parameters({self.algorithm!r}, data_width={self._data_width})"
|
return f"Parameters({self.algorithm!r}, data_width={self.data_width})"
|
||||||
|
|
||||||
|
|
||||||
class Processor(Elaboratable):
|
class Processor(Elaboratable):
|
||||||
"""
|
"""Hardware cyclic redundancy check generator.
|
||||||
Cyclic redundancy check (CRC) processor module.
|
|
||||||
|
|
||||||
This module generates CRCs from an input data stream, which can be used
|
This module generates CRCs from an input data stream, which can be used to validate an existing
|
||||||
to validate an existing CRC or generate a new CRC. It is configured by
|
CRC or generate a new CRC. It is configured by the :class:`Parameters` class, which can handle
|
||||||
the :py:class:`Parameters` class, which can handle most forms of CRCs.
|
most types of CRCs.
|
||||||
Refer to that class's documentation for a description of the parameters.
|
|
||||||
|
|
||||||
The CRC value is updated on any clock cycle where ``valid`` is asserted,
|
The CRC value is updated on any clock cycle where :py:`valid` is asserted, with the updated
|
||||||
with the updated value available on the ``crc`` output on the subsequent
|
value available on the :py:`crc` output on the subsequent clock cycle. The latency is therefore
|
||||||
clock cycle. The latency is therefore one clock cycle, and the throughput
|
one clock cycle, and the throughput is one data word per clock cycle.
|
||||||
is one data word per clock cycle.
|
|
||||||
|
|
||||||
The CRC is reset to its initial value whenever ``start`` is asserted.
|
The CRC is reset to its initial value whenever :py:`start` is asserted. :py:`start` and
|
||||||
``start`` and ``valid`` may be asserted on the same clock cycle, in which
|
:py:`valid` may be asserted on the same clock cycle, in which case a new CRC computation is
|
||||||
case a new CRC computation is started with the current value of ``data``.
|
started with the current value of `data`.
|
||||||
|
|
||||||
With ``data_width=1``, a classic bit-serial CRC is implemented for the
|
When :py:`data_width` is 1, a classic bit-serial CRC is implemented for the given polynomial
|
||||||
given polynomial in a Galois-type shift register. For larger values of
|
in a Galois-type shift register. For larger values of :py:`data_width`, a similar architecture
|
||||||
``data_width``, a similar architecture computes every new bit of the
|
computes every new bit of the CRC in parallel.
|
||||||
CRC in parallel.
|
|
||||||
|
|
||||||
The ``match_detected`` output may be used to validate data with a trailing
|
The :py:`match_detected` output may be used to validate data with a trailing CRC (also known as
|
||||||
CRC (also known as a codeword). If the most recently processed word(s) form
|
a codeword). If the most recently processed data word(s) form the valid CRC of all the previous
|
||||||
the valid CRC of all the previous data since ``start`` was asserted, the
|
data words since :py:`start` was asserted, the CRC register will always take on a fixed value
|
||||||
CRC register will always take on a fixed value known as the residue. The
|
known as the :meth:`residue <Parameters.residue>`. The :py:`match_detected` output indicates
|
||||||
``match_detected`` output indicates whether the CRC register currently
|
whether the CRC register currently contains this residue.
|
||||||
contains this residue.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
parameters : Parameters
|
parameters : :class:`Parameters`
|
||||||
CRC parameters.
|
Parameters used for computation.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
start : Signal(), in
|
start : Signal(), in
|
||||||
Assert to indicate the start of a CRC computation, re-initialising
|
Assert to indicate the start of a CRC computation, re-initialising the CRC register to
|
||||||
the CRC register to the initial value. May be asserted simultaneously
|
the initial value. May be asserted simultaneously with :py:`valid` or by itself.
|
||||||
with ``valid`` or by itself.
|
|
||||||
data : Signal(data_width), in
|
data : Signal(data_width), in
|
||||||
Data word to add to CRC when ``valid`` is asserted.
|
Data word to add to CRC when :py:`valid` is asserted.
|
||||||
valid : Signal(), in
|
valid : Signal(), in
|
||||||
Assert when ``data`` is valid to add the data word to the CRC.
|
Assert when :py:`data` is valid to add the data word to the CRC.
|
||||||
crc : Signal(crc_width), out
|
crc : Signal(crc_width), out
|
||||||
Registered CRC output value, updated one clock cycle after ``valid``
|
Registered CRC output value, updated one clock cycle after :py:`valid` becomes asserted.
|
||||||
becomes asserted.
|
|
||||||
match_detected : Signal(), out
|
match_detected : Signal(), out
|
||||||
Asserted if the current CRC value indicates a valid codeword has been
|
Asserted if the current CRC value indicates a valid codeword has been received.
|
||||||
received.
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, parameters):
|
def __init__(self, parameters):
|
||||||
if not isinstance(parameters, Parameters):
|
if not isinstance(parameters, Parameters):
|
||||||
raise TypeError("Algorithmn parameters must be of type Parameters, "
|
raise TypeError("Algorithm parameters must be of a Parameters instance, "
|
||||||
"not {!r}"
|
"not {!r}"
|
||||||
.format(parameters))
|
.format(parameters))
|
||||||
self._crc_width = parameters._crc_width
|
self._crc_width = parameters._crc_width
|
||||||
self._data_width = parameters._data_width
|
self._data_width = parameters.data_width
|
||||||
self._polynomial = parameters._polynomial
|
self._polynomial = parameters._polynomial
|
||||||
self._initial_crc = Const(parameters._initial_crc, self._crc_width)
|
self._initial_crc = Const(parameters._initial_crc, self._crc_width)
|
||||||
self._reflect_input = parameters._reflect_input
|
self._reflect_input = parameters._reflect_input
|
||||||
|
|
|
@ -1,17 +1,10 @@
|
||||||
"""
|
"""
|
||||||
This module contains a catalog of predefined CRC algorithms.
|
This module contains a catalog of predefined CRC algorithms, retrieved from the `reveng catalogue`_
|
||||||
|
on 2023-05-25.
|
||||||
|
|
||||||
All entries are from `reveng`_, accessed on 2023-05-25.
|
.. _reveng catalogue: https://reveng.sourceforge.io/crc-catalogue/all.htm
|
||||||
|
|
||||||
.. _reveng: https://reveng.sourceforge.io/crc-catalogue/all.htm
|
|
||||||
|
|
||||||
To use an entry, call it with an optional ``data_width`` which defaults
|
|
||||||
to 8. For example:
|
|
||||||
|
|
||||||
.. code-block::
|
|
||||||
|
|
||||||
crc8 = m.submodules.crc8 = crc.catalog.CRC8_AUTOSAR().create()
|
|
||||||
|
|
||||||
|
See the documentation for the :mod:`~amaranth.lib.crc` module for examples.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from . import Algorithm
|
from . import Algorithm
|
||||||
|
|
|
@ -1,10 +1,74 @@
|
||||||
Cyclic redundancy checks
|
Cyclic redundancy checks
|
||||||
########################
|
########################
|
||||||
|
|
||||||
.. automodule:: amaranth.lib.crc
|
.. py:module:: amaranth.lib.crc
|
||||||
|
|
||||||
|
The :mod:`amaranth.lib.crc` module provides facilities for computing cyclic redundancy checks (CRCs)
|
||||||
|
in software and in hardware.
|
||||||
|
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
The essentials of a CRC computation are specified with an :class:`Algorithm` object, which defines
|
||||||
|
CRC width, polynomial, initial value, input/output reflection, and output XOR. Many commonly used
|
||||||
|
CRC algorithms are available in the :py:mod:`~amaranth.lib.crc.catalog` module, while most other
|
||||||
|
CRC designs can be accommodated by manually constructing an :class:`Algorithm`.
|
||||||
|
|
||||||
|
An :class:`Algorithm` is specialized for a particular data width to obtain :class:`Parameters`,
|
||||||
|
which fully define a CRC computation. :meth:`Parameters.compute` computes a CRC in software, while
|
||||||
|
:meth:`Parameters.create` creates a :class:`Processor` that computes a CRC in hardware.
|
||||||
|
|
||||||
|
|
||||||
|
Examples
|
||||||
|
========
|
||||||
|
|
||||||
|
.. testsetup::
|
||||||
|
|
||||||
|
from amaranth import *
|
||||||
|
|
||||||
|
m = Module()
|
||||||
|
|
||||||
|
.. testcode::
|
||||||
|
|
||||||
|
from amaranth.lib.crc import Algorithm
|
||||||
|
from amaranth.lib.crc.catalog import CRC16_CCITT, CRC16_USB
|
||||||
|
|
||||||
|
|
||||||
|
# Compute a CRC in hardware using the predefined CRC16-CCITT algorithm and the data word
|
||||||
|
# width of 8 bits (in other words, computing it over bytes).
|
||||||
|
m.submodules.crc16_ccitt = crc16_ccitt = CRC16_CCITT().create()
|
||||||
|
|
||||||
|
# Compute a CRC in hardware using the predefined CRC16-USB algorithm and the data word
|
||||||
|
# width of 32 bits.
|
||||||
|
m.submodules.crc16_usb = crc16_usb = CRC16_USB(32).create()
|
||||||
|
|
||||||
|
# Compute a CRC in software using a custom CRC algorithm and explicitly specified data word
|
||||||
|
# width.
|
||||||
|
algo = Algorithm(crc_width=16, polynomial=0x1021, initial_crc=0xffff,
|
||||||
|
reflect_input=False, reflect_output=False, xor_output=0x0000)
|
||||||
|
assert algo(data_width=8).compute(b"123456789") == 0x29b1
|
||||||
|
|
||||||
|
|
||||||
|
Algorithms and parameters
|
||||||
|
=========================
|
||||||
|
|
||||||
|
.. autoclass:: Algorithm
|
||||||
:special-members: __call__
|
:special-members: __call__
|
||||||
|
|
||||||
The following pre-defined CRC algorithms are available:
|
.. autoclass:: Parameters
|
||||||
|
|
||||||
|
|
||||||
|
CRC computation
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. autoclass:: Processor()
|
||||||
|
|
||||||
|
|
||||||
|
Predefined algorithms
|
||||||
|
=====================
|
||||||
|
|
||||||
|
The following predefined CRC algorithms are available:
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Predefined CRC Algorithms
|
Algorithm catalog
|
||||||
#########################
|
#################
|
||||||
|
|
||||||
.. automodule:: amaranth.lib.crc.catalog
|
.. automodule:: amaranth.lib.crc.catalog
|
||||||
|
|
Loading…
Reference in a new issue