# amaranth: UnusedElaboratable=no import unittest import concurrent.futures 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 for_each_crc_concurrent(self, f): with concurrent.futures.ProcessPoolExecutor() as executor: futures = {executor.submit(f, crc) for crc in CRCS} for future in concurrent.futures.as_completed(futures): future.result() @staticmethod def perform_test_crc_bytes(name): crc = getattr(catalog, name)(data_width=8).create() check = CRC_CHECKS[name][0] async def testbench(ctx): for word in b"123456789": ctx.set(crc.start, word == b"1") ctx.set(crc.data, word) ctx.set(crc.valid, 1) await ctx.tick() ctx.set(crc.valid, 0) await ctx.tick() assert ctx.get(crc.crc) == check sim = Simulator(crc) sim.add_testbench(testbench) sim.add_clock(1e-6) sim.run() def test_crc_bytes(self): """ Verify CRC generation by computing the check value for each CRC in the catalogue with byte-sized inputs. """ self.for_each_crc_concurrent(self.perform_test_crc_bytes) @staticmethod def perform_test_crc_words(name): # 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 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] async def testbench(ctx): ctx.set(crc.start, 1) await ctx.tick() ctx.set(crc.start, 0) for word in words: ctx.set(crc.data, word) ctx.set(crc.valid, 1) await ctx.tick() ctx.set(crc.valid, 0) await ctx.tick() assert ctx.get(crc.crc) == check sim = Simulator(crc) sim.add_testbench(testbench) 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. """ self.for_each_crc_concurrent(self.perform_test_crc_words) @staticmethod def perform_test_crc_match(name): 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] async def testbench(ctx): ctx.set(crc.start, 1) await ctx.tick() ctx.set(crc.start, 0) for word in words: ctx.set(crc.data, word) ctx.set(crc.valid, 1) await ctx.tick() ctx.set(crc.valid, 0) await ctx.tick() assert ctx.get(crc.match_detected) sim = Simulator(crc) sim.add_testbench(testbench) sim.add_clock(1e-6) sim.run() def test_crc_match(self): """Verify match_detected output detects valid codewords.""" self.for_each_crc_concurrent(self.perform_test_crc_match)