lib.crc,docs/stdlib/crc: improve consistency with the rest of documentation.

This commit is contained in:
Catherine 2024-04-10 03:11:33 +00:00
parent c68f9e43f9
commit b6f51d269e
4 changed files with 228 additions and 233 deletions

View file

@ -1,155 +1,114 @@
"""
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
"""
import operator
from ... import *
__all__ = ["Algorithm", "Parameters", "Processor", "catalog"]
class Algorithm:
"""
Settings for a CRC algorithm, excluding data width.
"""Essential parameters for cyclic redundancy check computation.
The parameter set is based on the Williams model from
"A Painless Guide to CRC Error Detection Algorithms":
http://www.ross.net/crc/download/crc_v3.txt
The parameter set is based on the Williams model from `"A Painless Guide to CRC Error Detection
Algorithms" <http://www.ross.net/crc/download/crc_v3.txt>`_.
For a reference of standard CRC parameter sets, refer to:
* `reveng`_'s catalogue, which uses an identical parameterisation,
* `crcmod`_'s list of predefined functions, but remove the leading '1'
from the polynominal, XOR the "Init-value" with "XOR-out" to obtain
``initial_crc``, and where "Reversed" is True, set both ``reflect_input``
and ``reflect_output`` to True,
* `CRC Zoo`_, which contains only polynomials; use the "explicit +1"
form of polynomial but remove the leading '1'.
* `reveng`_'s catalogue, which uses an identical parameterisation;
* `crcmod`_'s list of predefined functions, but remove the leading '1' from the polynominal,
XOR the "Init-value" with "XOR-out" to obtain :py:`initial_crc`, and where "Reversed" is
:py:`True`, set both :py:`reflect_input` and :py:`reflect_output` to :py:`True`;
* `CRC Zoo`_, which contains only polynomials; use the "explicit +1" form of polynomial but
remove the leading '1'.
.. _reveng: https://reveng.sourceforge.io/crc-catalogue/all.htm
.. _crcmod: https://crcmod.sourceforge.net/crcmod.predefined.html
.. _CRC Zoo: https://users.ece.cmu.edu/~koopman/crc/
Many commonly used CRC algorithms are available in the
:py:mod:`~amaranth.lib.crc.catalog` module, which includes
all entries in the `reveng`_ catalogue.
Many commonly used CRC algorithms are available in the :py:mod:`~amaranth.lib.crc.catalog`
module, which includes all entries in the `reveng catalogue <reveng_>`_.
To create a :py:class:`Parameters` instance, call the :py:class:`Algorithm`
object with the required data width, which defaults to 8 bits.
The essential parameters on their own cannot be used to perform CRC computation, and must be
combined with a specific data word width. This can be done using :py:`algo(data_width)`, which
returns a :class:`Parameters` object.
Parameters
----------
crc_width : int
crc_width : :class:`int`
Bit width of CRC word. Also known as "width" in the Williams model.
polynomial : int
CRC polynomial to use, ``crc_width`` bits long, without the implicit
``x**crc_width`` term. Polynomial is always specified with the highest
order terms in the most significant bit positions; use
``reflect_input`` and ``reflect_output`` to perform a least
polynomial : :class:`int`
CRC polynomial to use, :py:`crc_width` bits long, without the implicit :py:`x ** crc_width`
term. Polynomial is always specified with the highest order terms in the most significant
bit positions; use :py:`reflect_input` and :py:`reflect_output` to perform a least
significant bit first computation.
initial_crc : int
Initial value of CRC register at reset. Most significant bit always
corresponds to the highest order term in the CRC register.
reflect_input : bool
If True, the input data words are bit-reflected, so that they are
processed least significant bit first.
reflect_output : bool
If True, the output CRC is bit-reflected, so the least-significant bit
of the output is the highest-order bit of the CRC register.
Note that this reflection is performed over the entire CRC register;
for transmission you may want to treat the output as a little-endian
multi-word value, so for example the reflected 16-bit output 0x4E4C
would be transmitted as the two octets 0x4C 0x4E, each transmitted
least significant bit first.
xor_output : int
The output CRC will be the CRC register XOR'd with this value, applied
after any output bit-reflection.
initial_crc : :class:`int`
Initial value of CRC register at reset. Most significant bit always corresponds to
the highest order term in the CRC register.
reflect_input : :class:`bool`
If :py:`True`, the input data words are bit-reflected, so that they are processed least
significant bit first.
reflect_output : :class:`bool`
If :py:`True`, the output CRC is bit-reflected, so that the least-significant bit of
the output is the highest-order bit of the CRC register. Note that this reflection is
performed over the entire CRC register; for transmission you may want to treat the output
as a little-endian multi-word value, so for example the reflected 16-bit output :py:`0x4E4C`
would be transmitted as the two octets :py:`0x4C, 0x4E`, each transmitted least significant
bit first.
xor_output : :class:`int`
The output CRC will be the CRC register XOR'd with this value, applied after any output
bit-reflection.
"""
def __init__(self, *, crc_width, polynomial, initial_crc, reflect_input,
reflect_output, xor_output):
self.crc_width = int(crc_width)
self.polynomial = int(polynomial)
self.initial_crc = int(initial_crc)
self.crc_width = operator.index(crc_width)
self.polynomial = operator.index(polynomial)
self.initial_crc = operator.index(initial_crc)
self.reflect_input = bool(reflect_input)
self.reflect_output = bool(reflect_output)
self.xor_output = int(xor_output)
self.xor_output = operator.index(xor_output)
if self.crc_width <= 0:
raise ValueError("crc_width must be greater than 0")
if not 0 <= self.polynomial < 2 ** self.crc_width:
raise ValueError("polynomial must be between 0 and 2**crc_width - 1")
if not 0 <= self.initial_crc < 2 ** self.crc_width:
raise ValueError("initial_crc must be between 0 and 2**crc_width - 1")
if not 0 <= self.xor_output < 2 ** self.crc_width:
raise ValueError("xor_output must be between 0 and 2**crc_width - 1")
if not self.crc_width > 0:
raise ValueError("CRC width must be greater than 0")
if self.polynomial not in range(2 ** self.crc_width):
raise ValueError("Polynomial must be between 0 and (2 ** crc_width - 1)")
if self.initial_crc not in range(2 ** self.crc_width):
raise ValueError("Initial CRC must be between 0 and (2 ** crc_width - 1)")
if self.xor_output not in range(2 ** self.crc_width):
raise ValueError("XOR output must be between 0 and (2 ** crc_width - 1)")
def __call__(self, data_width=8):
"""
Constructs a :py:class:`Parameters` instance from this
:py:class:`Algorithm` with the specified ``data_width``.
"""Combine these essential parameters with a data word width to form complete parameters.
Parameters
----------
data_width : int
Bit width of data words, default 8.
Returns
-------
:class:`Parameters`
:py:`Parameters(self, data_width)`
"""
return Parameters(self, data_width)
def __repr__(self):
return f"Algorithm(crc_width={self.crc_width}," \
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" reflect_input={self.reflect_input}," \
f" reflect_output={self.reflect_output}," \
f" xor_output=0x{self.xor_output:0{self.crc_width//4}x})"
return (f"Algorithm(crc_width={self.crc_width}, "
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"reflect_input={self.reflect_input}, "
f"reflect_output={self.reflect_output}, "
f"xor_output=0x{self.xor_output:0{self.crc_width // 4}x})")
class Parameters:
"""
Full set of parameters for a CRC computation.
"""Complete parameters for cyclic redundancy check computation.
Contains the settings from :py:class:`Algorithm` and additionally
``data_width``. Refer to :py:class:`Algorithm` for details of what each
parameter means and how to construct them.
Contains the essential :class:`Algorithm` parameters, plus the data word width.
From this class, you can directly compute CRCs with the
:py:meth:`~Parameters.compute` method, or construct a hardware module with
the :py:meth:`~Parameters.create` method.
A :class:`Parameters` object can be used to directly compute CRCs using
the :meth:`~Parameters.compute` method, or to construct a hardware module using
the :meth:`~Parameters.create` method.
Parameters
----------
algorithm : Algorithm
CRC algorithm to use. Specifies the CRC width, polynomial,
initial value, whether to reflect the input or output words,
and any output XOR.
data_width : int
algorithm : :class:`Algorithm`
CRC algorithm to use. Specifies the CRC width, polynomial, initial value, whether to
reflect the input or output words, and any output XOR.
data_width : :class:`int`
Bit width of data words.
"""
def __init__(self, algorithm, data_width=8):
@ -159,16 +118,12 @@ class Parameters:
self._reflect_input = algorithm.reflect_input
self._reflect_output = algorithm.reflect_output
self._xor_output = algorithm.xor_output
self._data_width = int(data_width)
if self._data_width <= 0:
raise ValueError("data_width must be greater than 0")
self.data_width = operator.index(data_width)
if not self.data_width > 0:
raise ValueError("Data width must be greater than 0")
@property
def algorithm(self):
"""
Returns an :py:class:`Algorithm` with the CRC settings from this
instance.
"""
return Algorithm(
crc_width=self._crc_width,
polynomial=self._polynomial,
@ -178,13 +133,11 @@ class Parameters:
xor_output=self._xor_output)
def residue(self):
"""
Compute the residue value for this CRC, which is the value left in the
CRC register after processing any valid codeword.
"""
# Residue is computed by initialising to (possibly reflected)
# xor_output, feeding crc_width worth of 0 bits, then taking
# the (possibly reflected) output without any XOR.
"""Obtain the residual value left in the CRC register after processing a valid trailing CRC.
A trailing CRC data word is also known as a codeword."""
# Residue is computed by initialising to (possibly reflected) xor_output, feeding crc_width
# worth of 0 bits, then taking the (possibly reflected) output without any XOR.
if self._reflect_output:
init = self._reflect(self._xor_output, self._crc_width)
else:
@ -193,179 +146,164 @@ class Parameters:
algo.initial_crc = init
algo.reflect_input = False
algo.xor_output = 0
return algo(data_width=self._crc_width).compute([0])
def create(self):
"""
Returns a ``Processor`` configured with these parameters.
"""
return Processor(self)
return algo(self._crc_width).compute([0])
def compute(self, data):
"""
Computes and returns the CRC of all data words in ``data``.
"""Compute the CRC of all data words in :py:`data`.
Parameters
----------
data : iterable of integers
The CRC is computed over this complete set of data.
Each item is an integer of bitwidth equal to ``data_width``.
data : iterable of :class:`int`
Data words, each of which is :py:`data_width` bits wide.
"""
# Precompute some constants we use every iteration.
word_max = (1 << self._data_width) - 1
top_bit = 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
word_max = (1 << self.data_width) - 1
top_bit = 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
# Implementation notes:
# We always compute most-significant bit first, which means the
# polynomial and initial value may be used as-is, and the reflect_in
# 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 its MSbit lines up with the MSbit of the previous
# CRC value. When the CRC width is smaller than the word width, this
# would normally truncate data bits.
# Instead, we shift the initial CRC left by the data width, and the
# 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.
# We always compute most-significant bit first, which means the polynomial and initial
# value may be used as-is, and the reflect_in 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
# its MSbit lines up with the MSbit of the previous CRC value. When the CRC width is smaller
# than the word width, this would normally truncate data bits. Instead, we shift the initial
# CRC left by the data width, and the 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:
if not 0 <= word <= word_max:
raise ValueError(f"data word must be between 0 and {word_max - 1}")
if self._reflect_input:
word = self._reflect(word, self._data_width)
word = self._reflect(word, self.data_width)
crc ^= word << self._crc_width
for _ in range(self._data_width):
for _ in range(self.data_width):
if crc & top_bit:
crc = (crc << 1) ^ poly_shifted
else:
crc <<= 1
crc &= crc_mask
crc >>= self._data_width
crc >>= self.data_width
if self._reflect_output:
crc = self._reflect(crc, self._crc_width)
crc ^= self._xor_output
return crc
def create(self):
"""Create a hardware CRC generator with these parameters.
Returns
-------
:class:`Processor`
:py:`Processor(self)`
"""
return Processor(self)
@staticmethod
def _reflect(word, n):
"""
Bitwise-reflects an n-bit word ``word``.
"""
"""Bitwise-reflect an :py:`n`-bit word :py:`word`."""
return int(f"{word:0{n}b}"[::-1], 2)
def _matrices(self):
"""
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.
"""Compute the F and G matrices for parallel CRC computation.
The matrices are ordered least-significant-bit first; in other words
the first entry, with index (0, 0), corresponds to the effect of the
least-significant bit of the input on the least-significant bit of the
output.
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.
For convenience of implementation, both matrices are returned
transposed: the first index is the input bit, and the second index is
the corresponding output bit.
The matrices are ordered least-significant-bit first; in other words the first entry, with
index (0, 0), corresponds to the effect of the least-significant bit of the input on
the least-significant bit of the output.
The matrices are used to select which bits are XORd together to compute
each bit i of 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.
For convenience of implementation, both matrices are returned transposed: the first index
is the input bit, and the second index is the corresponding output bit.
These matrices are not affected by ``initial_crc``, ``reflect_input``,
``reflect_output``, or ``xor_output``.
The matrices are used to select which bits are XORd together to compute each bit i of
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 = []
g = []
algo = self.algorithm
algo.reflect_input = algo.reflect_output = False
algo.xor_output = 0
crc = Parameters(algo, self._data_width)
crc = Parameters(algo, self.data_width)
for i in range(self._crc_width):
crc._initial_crc = 2 ** i
w = crc.compute([0])
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
w = crc.compute([2 ** i])
g.append([int(x) for x in reversed(f"{w:0{self._crc_width}b}")])
return f, g
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):
"""
Cyclic redundancy check (CRC) processor module.
"""Hardware cyclic redundancy check generator.
This module generates CRCs from an input data stream, which can be used
to validate an existing CRC or generate a new CRC. It is configured by
the :py:class:`Parameters` class, which can handle most forms of CRCs.
Refer to that class's documentation for a description of the parameters.
This module generates CRCs from an input data stream, which can be used to validate an existing
CRC or generate a new CRC. It is configured by the :class:`Parameters` class, which can handle
most types of CRCs.
The CRC value is updated on any clock cycle where ``valid`` is asserted,
with the updated value available on the ``crc`` output on the subsequent
clock cycle. The latency is therefore one clock cycle, and the throughput
is one data word per clock cycle.
The CRC value is updated on any clock cycle where :py:`valid` is asserted, with the updated
value available on the :py:`crc` output on the subsequent clock cycle. The latency is therefore
one clock cycle, and the throughput is one data word per clock cycle.
The CRC is reset to its initial value whenever ``start`` is asserted.
``start`` and ``valid`` may be asserted on the same clock cycle, in which
case a new CRC computation is started with the current value of ``data``.
The CRC is reset to its initial value whenever :py:`start` is asserted. :py:`start` and
:py:`valid` may be asserted on the same clock cycle, in which case a new CRC computation is
started with the current value of `data`.
With ``data_width=1``, a classic bit-serial CRC is implemented for the
given polynomial in a Galois-type shift register. For larger values of
``data_width``, a similar architecture computes every new bit of the
CRC in parallel.
When :py:`data_width` is 1, a classic bit-serial CRC is implemented for the given polynomial
in a Galois-type shift register. For larger values of :py:`data_width`, a similar architecture
computes every new bit of the CRC in parallel.
The ``match_detected`` output may be used to validate data with a trailing
CRC (also known as a codeword). If the most recently processed word(s) form
the valid CRC of all the previous data since ``start`` was asserted, the
CRC register will always take on a fixed value known as the residue. The
``match_detected`` output indicates whether the CRC register currently
contains this residue.
The :py:`match_detected` output may be used to validate data with a trailing CRC (also known as
a codeword). If the most recently processed data word(s) form the valid CRC of all the previous
data words since :py:`start` was asserted, the CRC register will always take on a fixed value
known as the :meth:`residue <Parameters.residue>`. The :py:`match_detected` output indicates
whether the CRC register currently contains this residue.
Parameters
----------
parameters : Parameters
CRC parameters.
parameters : :class:`Parameters`
Parameters used for computation.
Attributes
----------
start : Signal(), in
Assert to indicate the start of a CRC computation, re-initialising
the CRC register to the initial value. May be asserted simultaneously
with ``valid`` or by itself.
Assert to indicate the start of a CRC computation, re-initialising the CRC register to
the initial value. May be asserted simultaneously with :py:`valid` or by itself.
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
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
Registered CRC output value, updated one clock cycle after ``valid``
becomes asserted.
Registered CRC output value, updated one clock cycle after :py:`valid` becomes asserted.
match_detected : Signal(), out
Asserted if the current CRC value indicates a valid codeword has been
received.
Asserted if the current CRC value indicates a valid codeword has been received.
"""
def __init__(self, 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}"
.format(parameters))
self._crc_width = parameters._crc_width
self._data_width = parameters._data_width
self._data_width = parameters.data_width
self._polynomial = parameters._polynomial
self._initial_crc = Const(parameters._initial_crc, self._crc_width)
self._reflect_input = parameters._reflect_input

View file

@ -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: 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()
.. _reveng catalogue: https://reveng.sourceforge.io/crc-catalogue/all.htm
See the documentation for the :mod:`~amaranth.lib.crc` module for examples.
"""
from . import Algorithm

View file

@ -1,10 +1,74 @@
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__
The following pre-defined CRC algorithms are available:
.. autoclass:: Parameters
CRC computation
===============
.. autoclass:: Processor()
Predefined algorithms
=====================
The following predefined CRC algorithms are available:
.. toctree::

View file

@ -1,4 +1,4 @@
Predefined CRC Algorithms
#########################
Algorithm catalog
#################
.. automodule:: amaranth.lib.crc.catalog