From 45b9730786560893e7cc8eefa3409911be811d15 Mon Sep 17 00:00:00 2001 From: Adam Greig Date: Sun, 30 Jan 2022 19:28:10 +0000 Subject: [PATCH] Implement RFC 6: CRC Generator See amaranth-lang/rfcs#6 and #681. --- amaranth/lib/crc/__init__.py | 426 ++++++++++++++++ amaranth/lib/crc/catalog.py | 918 +++++++++++++++++++++++++++++++++++ docs/changes.rst | 7 + docs/stdlib.rst | 1 + docs/stdlib/crc.rst | 11 + docs/stdlib/crc/catalog.rst | 4 + tests/test_lib_crc.py | 350 +++++++++++++ 7 files changed, 1717 insertions(+) create mode 100644 amaranth/lib/crc/__init__.py create mode 100644 amaranth/lib/crc/catalog.py create mode 100644 docs/stdlib/crc.rst create mode 100644 docs/stdlib/crc/catalog.rst create mode 100644 tests/test_lib_crc.py diff --git a/amaranth/lib/crc/__init__.py b/amaranth/lib/crc/__init__.py new file mode 100644 index 0000000..282a57a --- /dev/null +++ b/amaranth/lib/crc/__init__.py @@ -0,0 +1,426 @@ +""" +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 * + +__all__ = ["Algorithm", "Parameters", "Processor", "catalog"] + + +class Algorithm: + """ + Settings for a CRC algorithm, excluding data width. + + 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: https://reveng.sourceforge.io/crc-catalogue/all.htm + .. _crcmod: http://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. + + To create a :py:class:`Parameters` instance, call the :py:class:`Algorithm` + object with the required data width, which defaults to 8 bits. + + Parameters + ---------- + crc_width : 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 + 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. + """ + 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.reflect_input = bool(reflect_input) + self.reflect_output = bool(reflect_output) + self.xor_output = int(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") + + def __call__(self, data_width=8): + """ + Constructs a :py:class:`Parameters` instance from this + :py:class:`Algorithm` with the specified ``data_width``. + + Parameters + ---------- + data_width : int + Bit width of data words, default 8. + """ + 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})" + + +class Parameters: + """ + Full set of parameters for a CRC 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. + + 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. + + 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 + Bit width of data words. + """ + def __init__(self, algorithm, data_width=8): + self._crc_width = algorithm.crc_width + self._polynomial = algorithm.polynomial + self._initial_crc = algorithm.initial_crc + 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") + + @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, + initial_crc=self._initial_crc, + reflect_input=self._reflect_input, + reflect_output=self._reflect_output, + 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. + if self._reflect_output: + init = self._reflect(self._xor_output, self._crc_width) + else: + init = self._xor_output + algo = self.algorithm + 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) + + def compute(self, data): + """ + Computes and returns the CRC of all data words in ``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``. + """ + # 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 + + # 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. + + crc = self._initial_crc << self._data_width + for word in data: + if not 0 <= word <= word_max: + raise ValueError("data word must be between 0 and {}".format(word_max - 1)) + + if self._reflect_input: + word = self._reflect(word, self._data_width) + + crc ^= word << self._crc_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 + if self._reflect_output: + crc = self._reflect(crc, self._crc_width) + + crc ^= self._xor_output + return crc + + @staticmethod + def _reflect(word, n): + """ + Bitwise-reflects an n-bit word ``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. + + 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. + + 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 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 ``initial_crc``, ``reflect_input``, + ``reflect_output``, or ``xor_output``. + """ + f = [] + g = [] + algo = self.algorithm + algo.reflect_input = algo.reflect_output = False + algo.xor_output = 0 + 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): + 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})" + + +class Processor(Elaboratable): + """ + Cyclic redundancy check (CRC) processor module. + + 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. + + 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 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``. + + 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. + + 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. + + Parameters + ---------- + parameters : Parameters + CRC parameters. + + 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. + data : Signal(data_width), in + Data word to add to CRC when ``valid`` is asserted. + valid : Signal(), in + Assert when ``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. + match_detected : Signal(), out + 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, " + "not {!r}" + .format(parameters)) + self._crc_width = parameters._crc_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 + self._reflect_output = parameters._reflect_output + self._xor_output = parameters._xor_output + self._matrix_f, self._matrix_g = parameters._matrices() + self._residue = parameters.residue() + + self.start = Signal() + self.data = Signal(self._data_width) + self.valid = Signal() + self.crc = Signal(self._crc_width) + self.match_detected = Signal() + + def elaborate(self, platform): + m = Module() + + crc_reg = Signal(self._crc_width, reset=self._initial_crc.value) + data_in = Signal(self._data_width) + + # Optionally bit-reflect input words. + if self._reflect_input: + m.d.comb += data_in.eq(self.data[::-1]) + else: + m.d.comb += data_in.eq(self.data) + + # Optionally bit-reflect and then XOR the output. + if self._reflect_output: + m.d.comb += self.crc.eq(crc_reg[::-1] ^ self._xor_output) + else: + m.d.comb += self.crc.eq(crc_reg ^ self._xor_output) + + # Compute next CRC state. + source = Mux(self.start, self._initial_crc, crc_reg) + with m.If(self.valid): + for i in range(self._crc_width): + bit = 0 + for j in range(self._crc_width): + if self._matrix_f[j][i]: + bit ^= source[j] + for j in range(self._data_width): + if self._matrix_g[j][i]: + bit ^= data_in[j] + m.d.sync += crc_reg[i].eq(bit) + with m.Elif(self.start): + m.d.sync += crc_reg.eq(self._initial_crc) + + # Check for residue match, indicating a valid codeword. + if self._reflect_output: + m.d.comb += self.match_detected.eq(crc_reg[::-1] == self._residue) + else: + m.d.comb += self.match_detected.eq(crc_reg == self._residue) + + return m + + +# Imported after Algorithm is defined to prevent circular imports. +from . import catalog diff --git a/amaranth/lib/crc/catalog.py b/amaranth/lib/crc/catalog.py new file mode 100644 index 0000000..d5debe7 --- /dev/null +++ b/amaranth/lib/crc/catalog.py @@ -0,0 +1,918 @@ +""" +This module contains a catalog of predefined CRC algorithms. + +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() + +""" + +from . import Algorithm + +# Note: The trailing `#:` gives Sphinx an empty documentation string for each +# constant, allowing it to be documented with `automodule` (which otherwise +# ignores undocumented module constants) and also preventing it from using +# the Algorithm docstring if otherwise forced to document the constants. + +CRC3_GSM = Algorithm( + crc_width=3, + polynomial=0x3, + initial_crc=0x0, + reflect_input=False, + reflect_output=False, + xor_output=0x7) #: + +CRC3_ROHC = Algorithm( + crc_width=3, + polynomial=0x3, + initial_crc=0x7, + reflect_input=True, + reflect_output=True, + xor_output=0x0) #: + +CRC4_G_704 = CRC4_ITU = Algorithm( + crc_width=4, + polynomial=0x3, + initial_crc=0x0, + reflect_input=True, + reflect_output=True, + xor_output=0x0) #: + +CRC4_INTERLAKEN = Algorithm( + crc_width=4, + polynomial=0x3, + initial_crc=0xf, + reflect_input=False, + reflect_output=False, + xor_output=0xf) #: + +CRC5_EPC_C1G2 = CRC5_EPC = Algorithm( + crc_width=5, + polynomial=0x09, + initial_crc=0x09, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC5_G_704 = CRC5_ITU = Algorithm( + crc_width=5, + polynomial=0x15, + initial_crc=0x00, + reflect_input=True, + reflect_output=True, + xor_output=0x00) #: + +CRC5_USB = Algorithm( + crc_width=5, + polynomial=0x05, + initial_crc=0x1f, + reflect_input=True, + reflect_output=True, + xor_output=0x1f) #: + +CRC6_CDMA2000_A = Algorithm( + crc_width=6, + polynomial=0x27, + initial_crc=0x3f, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC6_CDMA2000_B = Algorithm( + crc_width=6, + polynomial=0x07, + initial_crc=0x3f, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC6_DARC = Algorithm( + crc_width=6, + polynomial=0x19, + initial_crc=0x00, + reflect_input=True, + reflect_output=True, + xor_output=0x00) #: + +CRC6_G_704 = CRC6_ITU = Algorithm( + crc_width=6, + polynomial=0x03, + initial_crc=0x00, + reflect_input=True, + reflect_output=True, + xor_output=0x00) #: + +CRC6_GSM = Algorithm( + crc_width=6, + polynomial=0x2f, + initial_crc=0x00, + reflect_input=False, + reflect_output=False, + xor_output=0x3f) #: + +CRC7_MMC = Algorithm( + crc_width=7, + polynomial=0x09, + initial_crc=0x00, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC7_ROHC = Algorithm( + crc_width=7, + polynomial=0x4f, + initial_crc=0x7f, + reflect_input=True, + reflect_output=True, + xor_output=0x00) #: + +CRC7_UMTS = Algorithm( + crc_width=7, + polynomial=0x45, + initial_crc=0x00, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC8_AUTOSAR = Algorithm( + crc_width=8, + polynomial=0x2f, + initial_crc=0xff, + reflect_input=False, + reflect_output=False, + xor_output=0xff) #: + +CRC8_BLUETOOTH = Algorithm( + crc_width=8, + polynomial=0xa7, + initial_crc=0x00, + reflect_input=True, + reflect_output=True, + xor_output=0x00) #: + +CRC8_CDMA2000 = Algorithm( + crc_width=8, + polynomial=0x9b, + initial_crc=0xff, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC8_DARC = Algorithm( + crc_width=8, + polynomial=0x39, + initial_crc=0x00, + reflect_input=True, + reflect_output=True, + xor_output=0x00) #: + +CRC8_DVB_S2 = Algorithm( + crc_width=8, + polynomial=0xd5, + initial_crc=0x00, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC8_GSM_A = Algorithm( + crc_width=8, + polynomial=0x1d, + initial_crc=0x00, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC8_GSM_B = Algorithm( + crc_width=8, + polynomial=0x49, + initial_crc=0x00, + reflect_input=False, + reflect_output=False, + xor_output=0xff) #: + +CRC8_HITAG = Algorithm( + crc_width=8, + polynomial=0x1d, + initial_crc=0xff, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC8_I_432_1 = CRC8_ITU = Algorithm( + crc_width=8, + polynomial=0x07, + initial_crc=0x00, + reflect_input=False, + reflect_output=False, + xor_output=0x55) #: + +CRC8_I_CODE = Algorithm( + crc_width=8, + polynomial=0x1d, + initial_crc=0xfd, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC8_LTE = Algorithm( + crc_width=8, + polynomial=0x9b, + initial_crc=0x00, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC8_MAXIM_DOW = CRC8_MAXIM = Algorithm( + crc_width=8, + polynomial=0x31, + initial_crc=0x00, + reflect_input=True, + reflect_output=True, + xor_output=0x00) #: + +CRC8_MIFARE_MAD = Algorithm( + crc_width=8, + polynomial=0x1d, + initial_crc=0xc7, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC8_NRSC_5 = Algorithm( + crc_width=8, + polynomial=0x31, + initial_crc=0xff, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC8_OPENSAFETY = Algorithm( + crc_width=8, + polynomial=0x2f, + initial_crc=0x00, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC8_ROHC = Algorithm( + crc_width=8, + polynomial=0x07, + initial_crc=0xff, + reflect_input=True, + reflect_output=True, + xor_output=0x00) #: + +CRC8_SAE_J1850 = Algorithm( + crc_width=8, + polynomial=0x1d, + initial_crc=0xff, + reflect_input=False, + reflect_output=False, + xor_output=0xff) #: + +CRC8_SMBUS = Algorithm( + crc_width=8, + polynomial=0x07, + initial_crc=0x00, + reflect_input=False, + reflect_output=False, + xor_output=0x00) #: + +CRC8_TECH_3250 = CRC8_AES = CRC8_ETU = Algorithm( + crc_width=8, + polynomial=0x1d, + initial_crc=0xff, + reflect_input=True, + reflect_output=True, + xor_output=0x00) #: + +CRC8_WCDMA = Algorithm( + crc_width=8, + polynomial=0x9b, + initial_crc=0x00, + reflect_input=True, + reflect_output=True, + xor_output=0x00) #: + +CRC10_ATM = CRC10_I_610 = Algorithm( + crc_width=10, + polynomial=0x233, + initial_crc=0x000, + reflect_input=False, + reflect_output=False, + xor_output=0x000) #: + +CRC10_CDMA2000 = Algorithm( + crc_width=10, + polynomial=0x3d9, + initial_crc=0x3ff, + reflect_input=False, + reflect_output=False, + xor_output=0x000) #: + +CRC10_GSM = Algorithm( + crc_width=10, + polynomial=0x175, + initial_crc=0x000, + reflect_input=False, + reflect_output=False, + xor_output=0x3ff) #: + +CRC11_FLEXRAY = Algorithm( + crc_width=11, + polynomial=0x385, + initial_crc=0x01a, + reflect_input=False, + reflect_output=False, + xor_output=0x000) #: + +CRC11_UMTS = Algorithm( + crc_width=11, + polynomial=0x307, + initial_crc=0x000, + reflect_input=False, + reflect_output=False, + xor_output=0x000) #: + +CRC12_CDMA2000 = Algorithm( + crc_width=12, + polynomial=0xf13, + initial_crc=0xfff, + reflect_input=False, + reflect_output=False, + xor_output=0x000) #: + +CRC12_DECT = Algorithm( + crc_width=12, + polynomial=0x80f, + initial_crc=0x000, + reflect_input=False, + reflect_output=False, + xor_output=0x000) #: + +CRC12_GSM = Algorithm( + crc_width=12, + polynomial=0xd31, + initial_crc=0x000, + reflect_input=False, + reflect_output=False, + xor_output=0xfff) #: + +CRC12_UMTS = CRC12_3GPP = Algorithm( + crc_width=12, + polynomial=0x80f, + initial_crc=0x000, + reflect_input=False, + reflect_output=True, + xor_output=0x000) #: + +CRC13_BBC = Algorithm( + crc_width=13, + polynomial=0x1cf5, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC14_DARC = Algorithm( + crc_width=14, + polynomial=0x0805, + initial_crc=0x0000, + reflect_input=True, + reflect_output=True, + xor_output=0x0000) #: + +CRC14_GSM = Algorithm( + crc_width=14, + polynomial=0x202d, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x3fff) #: + +CRC15_CAN = Algorithm( + crc_width=15, + polynomial=0x4599, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC15_MPT1327 = Algorithm( + crc_width=15, + polynomial=0x6815, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0001) #: + +CRC16_ARC = CRC16_IBM = Algorithm( + crc_width=16, + polynomial=0x8005, + initial_crc=0x0000, + reflect_input=True, + reflect_output=True, + xor_output=0x0000) #: + +CRC16_CDMA2000 = Algorithm( + crc_width=16, + polynomial=0xc867, + initial_crc=0xffff, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_CMS = Algorithm( + crc_width=16, + polynomial=0x8005, + initial_crc=0xffff, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_DDS_110 = Algorithm( + crc_width=16, + polynomial=0x8005, + initial_crc=0x800d, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_DECT_R = Algorithm( + crc_width=16, + polynomial=0x0589, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0001) #: + +CRC16_DECT_X = Algorithm( + crc_width=16, + polynomial=0x0589, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_DNP = Algorithm( + crc_width=16, + polynomial=0x3d65, + initial_crc=0x0000, + reflect_input=True, + reflect_output=True, + xor_output=0xffff) #: + +CRC16_EN_13757 = Algorithm( + crc_width=16, + polynomial=0x3d65, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0xffff) #: + +CRC16_GENIBUS = CRC16_DARC = CRC16_EPC = CRC16_EPC_C1G2 = CRC16_I_CODE = Algorithm( + crc_width=16, + polynomial=0x1021, + initial_crc=0xffff, + reflect_input=False, + reflect_output=False, + xor_output=0xffff) #: + +CRC16_GSM = Algorithm( + crc_width=16, + polynomial=0x1021, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0xffff) #: + +CRC16_IBM_3740 = CRC16_AUTOSAR = CRC16_CCITT_FALSE = Algorithm( + crc_width=16, + polynomial=0x1021, + initial_crc=0xffff, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_IBM_SDLC = CRC16_ISO_HDLC = CRC16_ISO_IEC_14443_3_B = CRC16_X25 = Algorithm( + crc_width=16, + polynomial=0x1021, + initial_crc=0xffff, + reflect_input=True, + reflect_output=True, + xor_output=0xffff) #: + +CRC16_ISO_IEC_14443_3_A = Algorithm( + crc_width=16, + polynomial=0x1021, + initial_crc=0xc6c6, + reflect_input=True, + reflect_output=True, + xor_output=0x0000) #: + +CRC16_KERMIT = CRC16_BLUETOOTH = CRC16_CCITT = CRC16_CCITT_TRUE = CRC16_V_41_LSB = Algorithm( + crc_width=16, + polynomial=0x1021, + initial_crc=0x0000, + reflect_input=True, + reflect_output=True, + xor_output=0x0000) #: + +CRC16_LJ1200 = Algorithm( + crc_width=16, + polynomial=0x6f63, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_M17 = Algorithm( + crc_width=16, + polynomial=0x5935, + initial_crc=0xffff, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_MAXIM_DOW = CRC16_MAXIM = Algorithm( + crc_width=16, + polynomial=0x8005, + initial_crc=0x0000, + reflect_input=True, + reflect_output=True, + xor_output=0xffff) #: + +CRC16_MCRF4XX = Algorithm( + crc_width=16, + polynomial=0x1021, + initial_crc=0xffff, + reflect_input=True, + reflect_output=True, + xor_output=0x0000) #: + +CRC16_MODBUS = Algorithm( + crc_width=16, + polynomial=0x8005, + initial_crc=0xffff, + reflect_input=True, + reflect_output=True, + xor_output=0x0000) #: + +CRC16_NRSC_5 = Algorithm( + crc_width=16, + polynomial=0x080b, + initial_crc=0xffff, + reflect_input=True, + reflect_output=True, + xor_output=0x0000) #: + +CRC16_OPENSAFETY_A = Algorithm( + crc_width=16, + polynomial=0x5935, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_OPENSAFETY_B = Algorithm( + crc_width=16, + polynomial=0x755b, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_PROFIBUS = CRC16_IEC_61158_2 = Algorithm( + crc_width=16, + polynomial=0x1dcf, + initial_crc=0xffff, + reflect_input=False, + reflect_output=False, + xor_output=0xffff) #: + +CRC16_RIELLO = Algorithm( + crc_width=16, + polynomial=0x1021, + initial_crc=0xb2aa, + reflect_input=True, + reflect_output=True, + xor_output=0x0000) #: + +CRC16_SPI_FUJITSU = CRC16_AUG_CCITT = Algorithm( + crc_width=16, + polynomial=0x1021, + initial_crc=0x1d0f, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_T10_DIF = Algorithm( + crc_width=16, + polynomial=0x8bb7, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_TELEDISK = Algorithm( + crc_width=16, + polynomial=0xa097, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_TMS37157 = Algorithm( + crc_width=16, + polynomial=0x1021, + initial_crc=0x89ec, + reflect_input=True, + reflect_output=True, + xor_output=0x0000) #: + +CRC16_UMTS = CRC16_BUYPASS = CRC16_VERIFONE = Algorithm( + crc_width=16, + polynomial=0x8005, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC16_USB = Algorithm( + crc_width=16, + polynomial=0x8005, + initial_crc=0xffff, + reflect_input=True, + reflect_output=True, + xor_output=0xffff) #: + +CRC16_XMODEM = CRC16_ACORN = CRC16_LTE = CRC16_V_41_MSB = CRC16_ZMODEM = Algorithm( + crc_width=16, + polynomial=0x1021, + initial_crc=0x0000, + reflect_input=False, + reflect_output=False, + xor_output=0x0000) #: + +CRC17_CAN_FD = Algorithm( + crc_width=17, + polynomial=0x1685b, + initial_crc=0x00000, + reflect_input=False, + reflect_output=False, + xor_output=0x00000) #: + +CRC21_CAN_FD = Algorithm( + crc_width=21, + polynomial=0x102899, + initial_crc=0x000000, + reflect_input=False, + reflect_output=False, + xor_output=0x000000) #: + +CRC24_BLE = Algorithm( + crc_width=24, + polynomial=0x00065b, + initial_crc=0x555555, + reflect_input=True, + reflect_output=True, + xor_output=0x000000) #: + +CRC24_FLEXRAY_A = Algorithm( + crc_width=24, + polynomial=0x5d6dcb, + initial_crc=0xfedcba, + reflect_input=False, + reflect_output=False, + xor_output=0x000000) #: + +CRC24_FLEXRAY_B = Algorithm( + crc_width=24, + polynomial=0x5d6dcb, + initial_crc=0xabcdef, + reflect_input=False, + reflect_output=False, + xor_output=0x000000) #: + +CRC24_INTERLAKEN = Algorithm( + crc_width=24, + polynomial=0x328b63, + initial_crc=0xffffff, + reflect_input=False, + reflect_output=False, + xor_output=0xffffff) #: + +CRC24_LTE_A = Algorithm( + crc_width=24, + polynomial=0x864cfb, + initial_crc=0x000000, + reflect_input=False, + reflect_output=False, + xor_output=0x000000) #: + +CRC24_LTE_B = Algorithm( + crc_width=24, + polynomial=0x800063, + initial_crc=0x000000, + reflect_input=False, + reflect_output=False, + xor_output=0x000000) #: + +CRC24_OPENPGP = Algorithm( + crc_width=24, + polynomial=0x864cfb, + initial_crc=0xb704ce, + reflect_input=False, + reflect_output=False, + xor_output=0x000000) #: + +CRC24_OS_9 = Algorithm( + crc_width=24, + polynomial=0x800063, + initial_crc=0xffffff, + reflect_input=False, + reflect_output=False, + xor_output=0xffffff) #: + +CRC30_CDMA = Algorithm( + crc_width=30, + polynomial=0x2030b9c7, + initial_crc=0x3fffffff, + reflect_input=False, + reflect_output=False, + xor_output=0x3fffffff) #: + +CRC31_PHILIPS = Algorithm( + crc_width=31, + polynomial=0x04c11db7, + initial_crc=0x7fffffff, + reflect_input=False, + reflect_output=False, + xor_output=0x7fffffff) #: + +CRC32_AIXM = Algorithm( + crc_width=32, + polynomial=0x814141ab, + initial_crc=0x00000000, + reflect_input=False, + reflect_output=False, + xor_output=0x00000000) #: + +CRC32_AUTOSAR = Algorithm( + crc_width=32, + polynomial=0xf4acfb13, + initial_crc=0xffffffff, + reflect_input=True, + reflect_output=True, + xor_output=0xffffffff) #: + +CRC32_BASE91_D = Algorithm( + crc_width=32, + polynomial=0xa833982b, + initial_crc=0xffffffff, + reflect_input=True, + reflect_output=True, + xor_output=0xffffffff) #: + +CRC32_BZIP2 = CRC32_AAL5 = CRC32_DECT_B = Algorithm( + crc_width=32, + polynomial=0x04c11db7, + initial_crc=0xffffffff, + reflect_input=False, + reflect_output=False, + xor_output=0xffffffff) #: + +CRC32_CD_ROM_EDC = Algorithm( + crc_width=32, + polynomial=0x8001801b, + initial_crc=0x00000000, + reflect_input=True, + reflect_output=True, + xor_output=0x00000000) #: + +CRC32_CKSUM = CRC32_POSIX = Algorithm( + crc_width=32, + polynomial=0x04c11db7, + initial_crc=0x00000000, + reflect_input=False, + reflect_output=False, + xor_output=0xffffffff) #: + +CRC32_ISCSI = CRC32_BASE91_C = CRC32_CASTAGNOLI = CRC32_INTERLAKEN = Algorithm( + crc_width=32, + polynomial=0x1edc6f41, + initial_crc=0xffffffff, + reflect_input=True, + reflect_output=True, + xor_output=0xffffffff) #: + +CRC32_ISO_HDLC = CRC32_ADCCP = CRC32_V_42 = CRC32_XZ = CRC32_PKZIP = CRC32_ETHERNET = Algorithm( + crc_width=32, + polynomial=0x04c11db7, + initial_crc=0xffffffff, + reflect_input=True, + reflect_output=True, + xor_output=0xffffffff) #: + +CRC32_JAMCRC = Algorithm( + crc_width=32, + polynomial=0x04c11db7, + initial_crc=0xffffffff, + reflect_input=True, + reflect_output=True, + xor_output=0x00000000) #: + +CRC32_MEF = Algorithm( + crc_width=32, + polynomial=0x741b8cd7, + initial_crc=0xffffffff, + reflect_input=True, + reflect_output=True, + xor_output=0x00000000) #: + +CRC32_MPEG_2 = Algorithm( + crc_width=32, + polynomial=0x04c11db7, + initial_crc=0xffffffff, + reflect_input=False, + reflect_output=False, + xor_output=0x00000000) #: + +CRC32_XFER = Algorithm( + crc_width=32, + polynomial=0x000000af, + initial_crc=0x00000000, + reflect_input=False, + reflect_output=False, + xor_output=0x00000000) #: + +CRC40_GSM = Algorithm( + crc_width=40, + polynomial=0x0004820009, + initial_crc=0x0000000000, + reflect_input=False, + reflect_output=False, + xor_output=0xffffffffff) #: + +CRC64_ECMA_182 = Algorithm( + crc_width=64, + polynomial=0x42f0e1eba9ea3693, + initial_crc=0x0000000000000000, + reflect_input=False, + reflect_output=False, + xor_output=0x0000000000000000) #: + +CRC64_GO_ISO = Algorithm( + crc_width=64, + polynomial=0x000000000000001b, + initial_crc=0xffffffffffffffff, + reflect_input=True, + reflect_output=True, + xor_output=0xffffffffffffffff) #: + +CRC64_MS = Algorithm( + crc_width=64, + polynomial=0x259c84cba6426349, + initial_crc=0xffffffffffffffff, + reflect_input=True, + reflect_output=True, + xor_output=0x0000000000000000) #: + +CRC64_REDIS = Algorithm( + crc_width=64, + polynomial=0xad93d23594c935a9, + initial_crc=0x0000000000000000, + reflect_input=True, + reflect_output=True, + xor_output=0x0000000000000000) #: + +CRC64_WE = Algorithm( + crc_width=64, + polynomial=0x42f0e1eba9ea3693, + initial_crc=0xffffffffffffffff, + reflect_input=False, + reflect_output=False, + xor_output=0xffffffffffffffff) #: + +CRC64_XZ = CRC64_ECMA = Algorithm( + crc_width=64, + polynomial=0x42f0e1eba9ea3693, + initial_crc=0xffffffffffffffff, + reflect_input=True, + reflect_output=True, + xor_output=0xffffffffffffffff) #: + +CRC82_DARC = Algorithm( + crc_width=82, + polynomial=0x0308c0111011401440411, + initial_crc=0x000000000000000000000, + reflect_input=True, + reflect_output=True, + xor_output=0x000000000000000000000) #: diff --git a/docs/changes.rst b/docs/changes.rst index f27ec26..261f6c1 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -35,6 +35,9 @@ Implemented RFCs .. _RFC 3: https://amaranth-lang.org/rfcs/0003-enumeration-shapes.html .. _RFC 4: https://amaranth-lang.org/rfcs/0004-const-castable-exprs.html .. _RFC 5: https://amaranth-lang.org/rfcs/0005-remove-const-normalize.html +.. _RFC 6: https://amaranth-lang.org/rfcs/0006-stdlib-crc.html +.. _RFC 8: https://amaranth-lang.org/rfcs/0008-aggregate-extensibility.html +.. _RFC 9: https://amaranth-lang.org/rfcs/0009-const-init-shape-castable.html .. _RFC 8: https://amaranth-lang.org/rfcs/0008-aggregate-extensibility.html .. _RFC 9: https://amaranth-lang.org/rfcs/0009-const-init-shape-castable.html .. _RFC 10: https://amaranth-lang.org/rfcs/0010-move-repl-to-value.html @@ -44,6 +47,9 @@ Implemented RFCs * `RFC 3`_: Enumeration shapes * `RFC 4`_: Constant-castable expressions * `RFC 5`_: Remove Const.normalize +* `RFC 6`_: CRC generator +* `RFC 8`_: Aggregate extensibility +* `RFC 9`_: Constant initialization for shape-castable objects * `RFC 8`_: Aggregate extensibility * `RFC 9`_: Constant initialization for shape-castable objects * `RFC 10`_: Move Repl to Value.replicate @@ -79,6 +85,7 @@ Standard library changes * Added: :mod:`amaranth.lib.enum`. (`RFC 3`_) * Added: :mod:`amaranth.lib.data`. (`RFC 1`_) +* Added: :mod:`amaranth.lib.crc`. (`RFC 6`_) Toolchain changes diff --git a/docs/stdlib.rst b/docs/stdlib.rst index 2140381..48a7930 100644 --- a/docs/stdlib.rst +++ b/docs/stdlib.rst @@ -12,4 +12,5 @@ Standard library stdlib/data stdlib/coding stdlib/cdc + stdlib/crc stdlib/fifo diff --git a/docs/stdlib/crc.rst b/docs/stdlib/crc.rst new file mode 100644 index 0000000..96e8757 --- /dev/null +++ b/docs/stdlib/crc.rst @@ -0,0 +1,11 @@ +Cyclic redundancy checks +######################## + +.. automodule:: amaranth.lib.crc + :special-members: __call__ + +The following pre-defined CRC algorithms are available: + +.. toctree:: + + crc/catalog diff --git a/docs/stdlib/crc/catalog.rst b/docs/stdlib/crc/catalog.rst new file mode 100644 index 0000000..1f8a575 --- /dev/null +++ b/docs/stdlib/crc/catalog.rst @@ -0,0 +1,4 @@ +Predefined CRC Algorithms +######################### + +.. automodule:: amaranth.lib.crc.catalog diff --git a/tests/test_lib_crc.py b/tests/test_lib_crc.py new file mode 100644 index 0000000..db06442 --- /dev/null +++ b/tests/test_lib_crc.py @@ -0,0 +1,350 @@ +# amaranth: UnusedElaboratable=no + +import unittest + +from amaranth.sim import * +from amaranth.lib.crc import Algorithm, Processor, catalog + + +# Subset of catalogue CRCs used to test the hardware CRC implementation, +# as testing all algorithms takes a long time for little benefit. +# Selected to ensure coverage of CRC width, initial value, reflection, and +# XOR output. +CRCS = [ + "CRC3_GSM", "CRC4_INTERLAKEN", "CRC5_USB", "CRC8_AUTOSAR", "CRC8_BLUETOOTH", + "CRC8_I_432_1", "CRC12_UMTS", "CRC15_CAN", "CRC16_ARC", "CRC16_IBM_3740", + "CRC17_CAN_FD", "CRC21_CAN_FD", "CRC24_FLEXRAY_A", "CRC32_AUTOSAR", + "CRC32_BZIP2", "CRC32_ISO_HDLC", "CRC40_GSM" +] + +# All catalogue CRCs with their associated check values and residues from +# the reveng catalogue. Used to verify our computation of the check and +# residue values. +CRC_CHECKS = { + "CRC3_GSM": (0x4, 0x2), + "CRC3_ROHC": (0x6, 0x0), + "CRC4_G_704": (0x7, 0x0), + "CRC4_ITU": (0x7, 0x0), + "CRC4_INTERLAKEN": (0xb, 0x2), + "CRC5_EPC_C1G2": (0x00, 0x00), + "CRC5_EPC": (0x00, 0x00), + "CRC5_G_704": (0x07, 0x00), + "CRC5_ITU": (0x07, 0x00), + "CRC5_USB": (0x19, 0x06), + "CRC6_CDMA2000_A": (0x0d, 0x00), + "CRC6_CDMA2000_B": (0x3b, 0x00), + "CRC6_DARC": (0x26, 0x00), + "CRC6_G_704": (0x06, 0x00), + "CRC6_ITU": (0x06, 0x00), + "CRC6_GSM": (0x13, 0x3a), + "CRC7_MMC": (0x75, 0x00), + "CRC7_ROHC": (0x53, 0x00), + "CRC7_UMTS": (0x61, 0x00), + "CRC8_AUTOSAR": (0xdf, 0x42), + "CRC8_BLUETOOTH": (0x26, 0x00), + "CRC8_CDMA2000": (0xda, 0x00), + "CRC8_DARC": (0x15, 0x00), + "CRC8_DVB_S2": (0xbc, 0x00), + "CRC8_GSM_A": (0x37, 0x00), + "CRC8_GSM_B": (0x94, 0x53), + "CRC8_HITAG": (0xb4, 0x00), + "CRC8_I_432_1": (0xa1, 0xac), + "CRC8_ITU": (0xa1, 0xac), + "CRC8_I_CODE": (0x7e, 0x00), + "CRC8_LTE": (0xea, 0x00), + "CRC8_MAXIM_DOW": (0xa1, 0x00), + "CRC8_MAXIM": (0xa1, 0x00), + "CRC8_MIFARE_MAD": (0x99, 0x00), + "CRC8_NRSC_5": (0xf7, 0x00), + "CRC8_OPENSAFETY": (0x3e, 0x00), + "CRC8_ROHC": (0xd0, 0x00), + "CRC8_SAE_J1850": (0x4b, 0xc4), + "CRC8_SMBUS": (0xf4, 0x00), + "CRC8_TECH_3250": (0x97, 0x00), + "CRC8_AES": (0x97, 0x00), + "CRC8_ETU": (0x97, 0x00), + "CRC8_WCDMA": (0x25, 0x00), + "CRC10_ATM": (0x199, 0x000), + "CRC10_I_610": (0x199, 0x000), + "CRC10_CDMA2000": (0x233, 0x000), + "CRC10_GSM": (0x12a, 0x0c6), + "CRC11_FLEXRAY": (0x5a3, 0x000), + "CRC11_UMTS": (0x061, 0x000), + "CRC12_CDMA2000": (0xd4d, 0x000), + "CRC12_DECT": (0xf5b, 0x000), + "CRC12_GSM": (0xb34, 0x178), + "CRC12_UMTS": (0xdaf, 0x000), + "CRC12_3GPP": (0xdaf, 0x000), + "CRC13_BBC": (0x04fa, 0x0000), + "CRC14_DARC": (0x082d, 0x0000), + "CRC14_GSM": (0x30ae, 0x031e), + "CRC15_CAN": (0x059e, 0x0000), + "CRC15_MPT1327": (0x2566, 0x6815), + "CRC16_ARC": (0xbb3d, 0x0000), + "CRC16_IBM": (0xbb3d, 0x0000), + "CRC16_CDMA2000": (0x4c06, 0x0000), + "CRC16_CMS": (0xaee7, 0x0000), + "CRC16_DDS_110": (0x9ecf, 0x0000), + "CRC16_DECT_R": (0x007e, 0x0589), + "CRC16_DECT_X": (0x007f, 0x0000), + "CRC16_DNP": (0xea82, 0x66c5), + "CRC16_EN_13757": (0xc2b7, 0xa366), + "CRC16_GENIBUS": (0xd64e, 0x1d0f), + "CRC16_DARC": (0xd64e, 0x1d0f), + "CRC16_EPC": (0xd64e, 0x1d0f), + "CRC16_EPC_C1G2": (0xd64e, 0x1d0f), + "CRC16_I_CODE": (0xd64e, 0x1d0f), + "CRC16_GSM": (0xce3c, 0x1d0f), + "CRC16_IBM_3740": (0x29b1, 0x0000), + "CRC16_AUTOSAR": (0x29b1, 0x0000), + "CRC16_CCITT_FALSE": (0x29b1, 0x0000), + "CRC16_IBM_SDLC": (0x906e, 0xf0b8), + "CRC16_ISO_HDLC": (0x906e, 0xf0b8), + "CRC16_ISO_IEC_14443_3_B": (0x906e, 0xf0b8), + "CRC16_X25": (0x906e, 0xf0b8), + "CRC16_ISO_IEC_14443_3_A": (0xbf05, 0x0000), + "CRC16_KERMIT": (0x2189, 0x0000), + "CRC16_BLUETOOTH": (0x2189, 0x0000), + "CRC16_CCITT": (0x2189, 0x0000), + "CRC16_CCITT_TRUE": (0x2189, 0x0000), + "CRC16_V_41_LSB": (0x2189, 0x0000), + "CRC16_LJ1200": (0xbdf4, 0x0000), + "CRC16_M17": (0x772b, 0x0000), + "CRC16_MAXIM_DOW": (0x44c2, 0xb001), + "CRC16_MAXIM": (0x44c2, 0xb001), + "CRC16_MCRF4XX": (0x6f91, 0x0000), + "CRC16_MODBUS": (0x4b37, 0x0000), + "CRC16_NRSC_5": (0xa066, 0x0000), + "CRC16_OPENSAFETY_A": (0x5d38, 0x0000), + "CRC16_OPENSAFETY_B": (0x20fe, 0x0000), + "CRC16_PROFIBUS": (0xa819, 0xe394), + "CRC16_IEC_61158_2": (0xa819, 0xe394), + "CRC16_RIELLO": (0x63d0, 0x0000), + "CRC16_SPI_FUJITSU": (0xe5cc, 0x0000), + "CRC16_AUG_CCITT": (0xe5cc, 0x0000), + "CRC16_T10_DIF": (0xd0db, 0x0000), + "CRC16_TELEDISK": (0x0fb3, 0x0000), + "CRC16_TMS37157": (0x26b1, 0x0000), + "CRC16_UMTS": (0xfee8, 0x0000), + "CRC16_BUYPASS": (0xfee8, 0x0000), + "CRC16_VERIFONE": (0xfee8, 0x0000), + "CRC16_USB": (0xb4c8, 0xb001), + "CRC16_XMODEM": (0x31c3, 0x0000), + "CRC16_ACORN": (0x31c3, 0x0000), + "CRC16_LTE": (0x31c3, 0x0000), + "CRC16_V_41_MSB": (0x31c3, 0x0000), + "CRC16_ZMODEM": (0x31c3, 0x0000), + "CRC17_CAN_FD": (0x04f03, 0x00000), + "CRC21_CAN_FD": (0x0ed841, 0x000000), + "CRC24_BLE": (0xc25a56, 0x000000), + "CRC24_FLEXRAY_A": (0x7979bd, 0x000000), + "CRC24_FLEXRAY_B": (0x1f23b8, 0x000000), + "CRC24_INTERLAKEN": (0xb4f3e6, 0x144e63), + "CRC24_LTE_A": (0xcde703, 0x000000), + "CRC24_LTE_B": (0x23ef52, 0x000000), + "CRC24_OPENPGP": (0x21cf02, 0x000000), + "CRC24_OS_9": (0x200fa5, 0x800fe3), + "CRC30_CDMA": (0x04c34abf, 0x34efa55a), + "CRC31_PHILIPS": (0x0ce9e46c, 0x4eaf26f1), + "CRC32_AIXM": (0x3010bf7f, 0x00000000), + "CRC32_AUTOSAR": (0x1697d06a, 0x904cddbf), + "CRC32_BASE91_D": (0x87315576, 0x45270551), + "CRC32_BZIP2": (0xfc891918, 0xc704dd7b), + "CRC32_AAL5": (0xfc891918, 0xc704dd7b), + "CRC32_DECT_B": (0xfc891918, 0xc704dd7b), + "CRC32_CD_ROM_EDC": (0x6ec2edc4, 0x00000000), + "CRC32_CKSUM": (0x765e7680, 0xc704dd7b), + "CRC32_POSIX": (0x765e7680, 0xc704dd7b), + "CRC32_ISCSI": (0xe3069283, 0xb798b438), + "CRC32_BASE91_C": (0xe3069283, 0xb798b438), + "CRC32_CASTAGNOLI": (0xe3069283, 0xb798b438), + "CRC32_INTERLAKEN": (0xe3069283, 0xb798b438), + "CRC32_ISO_HDLC": (0xcbf43926, 0xdebb20e3), + "CRC32_ADCCP": (0xcbf43926, 0xdebb20e3), + "CRC32_V_42": (0xcbf43926, 0xdebb20e3), + "CRC32_XZ": (0xcbf43926, 0xdebb20e3), + "CRC32_PKZIP": (0xcbf43926, 0xdebb20e3), + "CRC32_ETHERNET": (0xcbf43926, 0xdebb20e3), + "CRC32_JAMCRC": (0x340bc6d9, 0x00000000), + "CRC32_MEF": (0xd2c22f51, 0x00000000), + "CRC32_MPEG_2": (0x0376e6e7, 0x00000000), + "CRC32_XFER": (0xbd0be338, 0x00000000), + "CRC40_GSM": (0xd4164fc646, 0xc4ff8071ff), + "CRC64_ECMA_182": (0x6c40df5f0b497347, 0x0000000000000000), + "CRC64_GO_ISO": (0xb90956c775a41001, 0x5300000000000000), + "CRC64_MS": (0x75d4b74f024eceea, 0x0000000000000000), + "CRC64_REDIS": (0xe9c6d914c4b8d9ca, 0x0000000000000000), + "CRC64_WE": (0x62ec59e3f1a4f00a, 0xfcacbebd5931a992), + "CRC64_XZ": (0x995dc9bbdf1939fa, 0x49958c9abd7d353f), + "CRC64_ECMA": (0x995dc9bbdf1939fa, 0x49958c9abd7d353f), + "CRC82_DARC": (0x09ea83f625023801fd612, 0x000000000000000000000), +} + + +class CRCTestCase(unittest.TestCase): + def test_checks(self): + """ + Verify computed check values and residues match catalogue entries. + """ + for name in dir(catalog): + if name.startswith("CRC"): + crc = getattr(catalog, name)(data_width=8) + check, residue = CRC_CHECKS[name] + assert crc.compute(b"123456789") == check + assert crc.residue() == residue + + def test_repr(self): + algorithm = catalog.CRC8_AUTOSAR + assert repr(algorithm) == "Algorithm(crc_width=8, polynomial=0x2f," \ + " initial_crc=0xff, reflect_input=False, reflect_output=False," \ + " xor_output=0xff)" + + params = algorithm(data_width=8) + assert repr(params) == "Parameters(Algorithm(crc_width=8," \ + " polynomial=0x2f, initial_crc=0xff, reflect_input=False," \ + " reflect_output=False, xor_output=0xff), data_width=8)" + + def test_processor_typecheck(self): + with self.assertRaises(TypeError): + proc = Processor(12) + + def test_algorithm_range_checks(self): + with self.assertRaises(ValueError): + Algorithm(crc_width=0, polynomial=0x3, initial_crc=0x0, + reflect_input=False, reflect_output=False, xor_output=0x7) + with self.assertRaises(ValueError): + Algorithm(crc_width=3, polynomial=0x8, initial_crc=0x0, + reflect_input=False, reflect_output=False, xor_output=0x7) + with self.assertRaises(ValueError): + Algorithm(crc_width=3, polynomial=0x3, initial_crc=0x8, + reflect_input=False, reflect_output=False, xor_output=0x7) + with self.assertRaises(ValueError): + Algorithm(crc_width=3, polynomial=0x3, initial_crc=0x0, + reflect_input=False, reflect_output=False, xor_output=0x8) + + def test_parameter_range_checks(self): + with self.assertRaises(ValueError): + catalog.CRC8_AUTOSAR(data_width=0) + with self.assertRaises(ValueError): + crc = catalog.CRC8_AUTOSAR() + crc.compute([3, 4, 256]) + + def test_crc_bytes(self): + """ + Verify CRC generation by computing the check value for each CRC + in the catalogue with byte-sized inputs. + """ + for name in CRCS: + crc = getattr(catalog, name)(data_width=8).create() + check = CRC_CHECKS[name][0] + + def process(): + for word in b"123456789": + yield crc.start.eq(word == b"1") + yield crc.data.eq(word) + yield crc.valid.eq(1) + yield + yield crc.valid.eq(0) + yield + self.assertEqual((yield crc.crc), check) + + sim = Simulator(crc) + sim.add_sync_process(process) + sim.add_clock(1e-6) + sim.run() + + def test_crc_words(self): + """ + Verify CRC generation for non-byte-sized data by computing a check + value for 1, 2, 4, 16, 32, and 64-bit inputs. + """ + # We can't use the catalogue check value since it requires 8-bit + # inputs, so we'll instead use an input of b"12345678". + data = b"12345678" + # Split data into individual bits. When input is reflected, we have + # to reflect each byte first, then form the input words, then let + # the CRC module reflect those words, to get the same effective input. + bits = "".join(f"{x:08b}" for x in data) + bits_r = "".join(f"{x:08b}"[::-1] for x in data) + + for name in CRCS: + for m in (1, 2, 4, 16, 32, 64): + algo = getattr(catalog, name) + crc = algo(data_width=m).create() + # Use a SoftwareCRC with byte inputs to compute new checks. + swcrc = algo(data_width=8) + check = swcrc.compute(data) + # Chunk input bits into m-bit words, reflecting if needed. + if algo.reflect_input: + d = [bits_r[i : i+m][::-1] for i in range(0, len(bits), m)] + else: + d = [bits[i : i+m] for i in range(0, len(bits), m)] + words = [int(x, 2) for x in d] + + def process(): + yield crc.start.eq(1) + yield + yield crc.start.eq(0) + for word in words: + yield crc.data.eq(word) + yield crc.valid.eq(1) + yield + yield crc.valid.eq(0) + yield + self.assertEqual((yield crc.crc), check) + + sim = Simulator(crc) + sim.add_sync_process(process) + sim.add_clock(1e-6) + sim.run() + + def test_crc_match(self): + """Verify match_detected output detects valid codewords.""" + for name in CRCS: + algo = getattr(catalog, name) + n = algo.crc_width + m = 8 if n % 8 == 0 else 1 + crc = algo(data_width=m).create() + check = CRC_CHECKS[name][0] + + if m == 8: + # For CRCs which are multiples of one byte wide, we can easily + # append the correct checksum in bytes. + check_b = check.to_bytes(n // 8, "little" if algo.reflect_output else "big") + words = b"123456789" + check_b + else: + # For other CRC sizes, use single-bit input data. + if algo.reflect_output: + check_b = check.to_bytes((n + 7)//8, "little") + if not algo.reflect_input: + # For cross-endian CRCs, flip the CRC bits separately. + check_b = bytearray(int(f"{x:08b}"[::-1], 2) for x in check_b) + else: + shift = 8 - (n % 8) + check_b = (check << shift).to_bytes((n + 7)//8, "big") + # No catalogue CRCs have ref_in but not ref_out. + codeword = b"123456789" + check_b + words = [] + for byte in codeword: + if algo.reflect_input: + words += [int(x) for x in f"{byte:08b}"[::-1]] + else: + words += [int(x) for x in f"{byte:08b}"] + words = words[:72 + n] + + def process(): + yield crc.start.eq(1) + yield + yield crc.start.eq(0) + for word in words: + yield crc.data.eq(word) + yield crc.valid.eq(1) + yield + yield crc.valid.eq(0) + yield + self.assertTrue((yield crc.match_detected)) + + sim = Simulator(crc) + sim.add_sync_process(process) + sim.add_clock(1e-6) + sim.run()