5 min read

Cyclic Redundancy Implemented Logically

This article presents a first-order logic implementation of generalised Cyclic Redundancy Check summing using Prolog. The predicates make use of tabling for performance.

Decomposition

CRC calculation combines several computational steps:

  • Polynomial degree
  • Bit reversal
  • Check summing by left shifting
  • Check summing by right shifting
  • Optional inversion

The following sections dig into some of the details though not all, nor precisely explain how the pieces fit together. View the final result online.

Polynomial Degree

Binary polynomials have a binary degree.

Every valid polynomial has a degree. The degree \(d\) of binary polynomial \(p\) is the power of two which holds \(p\in\left[2^d, 2^{d+1}\right)\). Note the inclusive-exclusive boundaries. For example, the degree equivalence for polynomial \(107_{16}\) \[ 2^8\leq107_{16}<2^9\equiv256\leq107_{16}<512 \] holds as true and hence has a degree of \(d=8\).

How does that translate logically? The logical expression poly_deg_ holds for all polynomials Poly of degree Deg; see predicate below. Any given polynomial cannot by definition have more than one degree; it cannot simultaneously span two \(\left[2^d, 2^{d+1}\right)\) ranges.

poly_deg_(Poly, Deg) :-
    Low is 1 << Deg,
    Low =< Poly,
    High is Low << 1,
    Poly < High.

The predicate’s term ordering matters at least for performance. It computes the lower degree boundary by shifting and then computes the higher boundary by shifting the lower boundary by one—doubling it.

A tabled implementation for poly_deg unifies Poly and Deg for degrees 8, 16, 24, 32 and 64, as follows.

:- table poly_deg/2.

poly_deg(Poly, Deg) :-
    deg(Deg),
    poly_deg_(Poly, Deg),
    !.

deg(8).
deg(16).
deg(24).
deg(32).
deg(64).

Bit Reversal

CRC calculation requires a ‘bit reversal’ operation over a given span of bits, or math \(A=({}_{0}A, \ldots, {}_{j-1}A)\) for integer \(A\) of \(j\) bits. The reverse bits equal the mirror image of the original; integer \(1\) reversed in 16 bits becomes \(8000_{16}\) for instance.

In Prolog by accumulation:

rbit(Shift, Int, Reverse) :-
    rbit_(Shift, Int, 0, _Int, Reverse).

rbit_(0, Int, Reverse, Int, Reverse) :- !.
rbit_(Shift, Int0, Reverse0, Int, Reverse) :-
    succ(Shift_, Shift),
    Reverse_ is (Reverse0 << 1) \/ (Int0 /\ 1),
    Int_ is Int0 >> 1,
    rbit_(Shift_, Int_, Reverse_, Int, Reverse).

Arity-3 rbit/3 predicate throws away the residual. Any bit values lying outside the shifting span disappear; they do not appear in the residual and the predicate discards them. The order of the sub-terms is not very important, except for failures. Placing succ first ensures that recursive shifting fails if Shift is not a positive integer; it triggers an exception if not actually an integer.

Left Carry

The implementation takes care of CRC truncation when shifting left. The same issue does not exist when shifting right. Right shifts implicitly discard the least-significant bit but not so with leftwise shifting. Shifting left in Prolog expands the integer indefinitely, not just up to the platform’s native integer size. The SWI edition of Prolog WIELEMAKER et al. (2012) utilises GNU’s multi-precision arithmetic library (GMP) to represent all numbers including integers. Integer bit-width grows to any giant size, limited only by the size of the memory available. Prolog lets integers left-shift millions of times—the result is a very large integer.

Shifting with carry is a basic arithmetic operation. CPU cores typically equip register shift with carry operations. The operation performs a bit shift and moves the overflow bit to the carry flag; subsequent machine operations test the carry bit. CRC computation requires the same operation but over arbitrary widths. A ‘shift left with carry’ using higher-level arithmetic needs a width whereas right does not. The least-significant bit automatically disappears on right-shift. Left shifting applies a mask and consequently requires a bit vector width—the degree \(Deg\) of the polynomial in the bit_left/4 predicate below. Contrast with the corresponding bit_right/3.

bit_left(Deg, Int0, Bit, Int) :-
    Bit is getbit(Int0, Deg - 1),
    Int is (Int0 << 1) /\ ((1 << Deg) - 1).

bit_right(Int0, Bit, Int) :-
    Bit is getbit(Int0, 0),
    Int is Int0 >> 1.

Usage

Put it all together and the result makes arbitrary CRC calculation easy. First, load the library; this assumes that the Prolog system finds the pack already installed—install it using pack_install(canny_tudor) otherwise. The following session demonstrates how to use the pack.

1 ?- [library(canny/crc)].
true.

2 ?- crc(crc-16-mcrf4xx, A),
     crc(A, `123456789`, B),
     crc_property(B, check(C)),
     format('~16R~n', [C]).
6F91
A = crc(69665, 65535, [reverse]),
B = crc(69665, 28561, [reverse]),
C = 28561.

The crc/2 predicate sets up an initial CRC at \(A\) using a predefined term, in this example using CRC-16/MCRF4xx. The first argument specifies a predefined CRC computation with the correct polynomial, initial value and any necessary options such as bit reversal and inversion value. Next, the arity-3 crc/3 mutates the initial CRC to \(B\) by feeding in a list of codes. Backticks around an atom generate a list of character codes as a useful Prolog shorthand. The updated CRC unifies at \(B\). The second argument is byte code or a list of byte codes. Finally, crc_property/2 extracts the CRC’s checksum for comparison at \(C\); the same predicate unifies with other interesting values belonging to a CRC accumulator. Correct result! \[ 28561_{10}={6F91}_{16} \]

Conclusions

The solution makes use of tabling for performance reasons. Enhancing of ‘polynomial degree’ is an important optimisation. The computation is not itself difficult or time-consuming, but it commonly proves useful for bit-width and bit-masking calculations. Having a quick method for joining a polynomial and its degree simplifies the number of arguments since the implementation can easily access the degree given the polynomial at any point within the evaluation stack. The polynomial automatically infers its degree and thereby its bit width. Degree and bit-width are one and the same. The implementation only needs to pass the polynomial without its degree.

The crc/3 predicate generates on-the-fly the polynomial lookups but then tabulates the results dynamically in order to avoid repeated computation of identical terms, called ‘tabled execution’ in Prolog or tabling for short. Tabling instrumentation applies to predicates check_left/3 and check_right/3 as well as poly_deg/2.

References

WIELEMAKER, JAN, TOM SCHRIJVERS, MARKUS TRISKA, and TORBJÖRN LAGER. 2012. SWI-Prolog 12: 67–96. https://doi.org/10.1017/s1471068411000494.