Notes

A fixed shift of the growing prefix

· algorithms

r is the degree of G, and it stays fixed while the prefix grows. The register holds that prefix times x^r, and the value is the CRC only when the prefix is the whole message.

Last time the register stayed rr bits wide, and the loop still read the message followed by rr zeros. Appending them made the input longer and added rr iterations. Those zeros are a fixed factor xrx^{r}. I put that factor into the polynomial the register holds, and the input stays the message.

A heavier state

After kk message bits, MkM_{k} is that prefix. Last time the register held rem⁡G(Mk)\operatorname{rem}_{G}(M_{k}), so rr zeros still had to be shifted in afterwards. The width of that shift is deg⁡G\deg G. It stays fixed while the prefix grows. This time the register holds the prefix already multiplied by that fixed power:

Ck=rem⁡G(xrMk).\boxed{C_{k}=\operatorname{rem}_{G}(x^{r}M_{k}).}

Degree less than rr is part of that remainder, so Ck≡xrMk(modG)C_{k}\equiv x^{r}M_{k}\pmod{G}. It is the bits so far, followed by the same rr zeros. It becomes the CRC when the prefix has grown into the whole message, and not before.

The empty prefix is M0=0M_{0}=0. The register starts at 00, and C0=0C_{0}=0 satisfies the same relation.

The next bit is bk+1b_{k+1}, so the prefix grows by

Mk+1=xMk+bk+1.M_{k+1}=x M_{k}+b_{k+1}.

The register has to apply the same fixed shift to the longer prefix:

xrMk+1=xr(xMk+bk+1)=x(xrMk)+bk+1xr.x^{r}M_{k+1}=x^{r}(x M_{k}+b_{k+1})=x(x^{r}M_{k})+b_{k+1}x^{r}.

The remainder replaces xrMkx^{r}M_{k} by CkC_{k}, and

Ck+1=rem⁡G(xCk+bk+1xr).C_{k+1}=\operatorname{rem}_{G}(x C_{k}+b_{k+1}x^{r}).

Two contributions to xrx^{r}

CkC_{k} still fits in the register. Split off the coefficient of xr−1x^{r-1}:

Ck=tkxr−1+Lk(x),tk=[xr−1]Ck,deg⁡Lk<r−1.C_{k}=t_{k} x^{r-1}+L_{k}(x), \qquad t_{k}=[x^{r-1}]C_{k}, \qquad \deg L_{k}<r-1.

tkt_{k} may be 00. It is that fixed coefficient, the bit msb reads. The leading coefficient of CkC_{k} is whichever highest power is actually nonzero, and it may sit below xr−1x^{r-1}.

One factor of xx lifts the coefficient of xr−1x^{r-1} just past the register,

xCk=tkxr+xLk(x).x C_{k}=t_{k} x^{r}+x L_{k}(x).

The new bit produces the same power, because the state already includes the xrx^{r} that last time was written as trailing zeros. The two coefficients add:

xCk+bk+1xr=xLk(x)+(tk+bk+1)xr.x C_{k}+b_{k+1}x^{r}=x L_{k}(x)+(t_{k}+b_{k+1})x^{r}.

In F2\mathbb{F}_2 that sum of coefficients is an XOR. Call it the feedback,

fk+1=tk+bk+1=[xr−1]Ck+bk+1.f_{k+1}=t_{k}+b_{k+1}=[x^{r-1}]C_{k}+b_{k+1}.

It multiplies xrx^{r}, so it sits one bit past the register: r+1r+1 bits in all. Last time xr≡g(x)x^{r}\equiv g(x), and that congruence is what keeps the step inside the register. The overflow is congruent to gg, which already fits in rr bits. Once tkt_{k} has moved out, the powers below xrx^{r} are

xCk mod xr=xLk(x).x C_{k}\bmod x^{r}=x L_{k}(x).

Since deg⁡Lk<r−1\deg L_{k}<r-1, that product already has degree less than rr. It is the register after (crc_register << 1) & register_mask. Therefore

Ck+1=(xCk mod xr)+fk+1 g(x).C_{k+1}=(x C_{k}\bmod x^{r})+f_{k+1}\,g(x).

The two terms are written in that order. First store xCk mod xrx C_{k}\bmod x^{r}. Then, when fk+1=1f_{k+1}=1, XOR gg. The incoming bit never enters the low end. It only joins tkt_{k} in fk+1f_{k+1}.

When tkt_{k} and bk+1b_{k+1} are both 11, the two coefficients cancel and fk+1=0f_{k+1}=0. The arriving bit is spent on the bit that just left, and gg stays out.

The same step is one multiplication by xx after the new bit has been added at xr−1x^{r-1}. For a polynomial PP of degree less than rr,

TG(P)=rem⁡G(xP).T_{G}(P)=\operatorname{rem}_{G}(x P).

The split is that operator. The coefficient of xr−1x^{r-1} decides whether gg is added, and xP mod xrx P\bmod x^{r} is the rest:

TG(P)=(xP mod xr)+[xr−1]P⋅g(x).\boxed{T_{G}(P)=(x P\bmod x^{r})+[x^{r-1}]P\cdot g(x).}

Adding bk+1b_{k+1} at xr−1x^{r-1} makes that coefficient fk+1f_{k+1}, and the lower part stays LkL_{k}, so

Ck+1=TG(Ck+bk+1xr−1).\boxed{C_{k+1}=T_{G}(C_{k}+b_{k+1}x^{r-1}).}

Example

Take G=10011G=10011, so r=4r=4 and g=0011g=0011. The message is 101101101101, and rem⁡G(x4M)=1110\operatorname{rem}_{G}(x^{4}M)=1110. The register starts at 00000000. These six bits are the whole input.

The first bit is 11. The top is 00, so the feedback is 11. Shifting 00000000 leaves 00000000, and the bit arrives as gg:

0000
^0011
-----
0011

The second bit is 00. The top is 00, the feedback is 00, and 00110011 shifts to 01100110.

The third bit is 11. The top is 00, the feedback is 11, and 01100110 shifts to 11001100:

1100
^0011
-----
1111

The fourth bit is 11, and the top is already 11. The feedback is 1+1=01+1=0. Shifting 11111111 gives 11101110, and gg is not added. This is the cancellation: both sources of x4x^{4} were present, and they removed each other.

The fifth bit is 00. The top of 11101110 is 11, the feedback is 11, the shift is 11001100, and the XOR with 00110011 leaves 11111111.

The sixth bit is 11. The top is 11 again, so the feedback is 00, and the shift takes 11111111 to 11101110.

Every message bit is in, so M6=MM_{6}=M and

C6=rem⁡G(x4M).C_{6}=\operatorname{rem}_{G}(x^{4}M).

The register reads 11101110. The input was the six message bits, and the loop ran six times.

The program

msb is [xr−1]Ck[x^{r-1}]C_{k}. feedback is fk+1f_{k+1}, written with ^. truncated_generator is gg. (crc_register << 1) & register_mask is xCk mod xrx C_{k}\bmod x^{r}. The incoming bit enters only through feedback. The masked shift plus that conditional XOR is TG(Ck+bk+1xr−1)T_{G}(C_{k}+b_{k+1}x^{r-1}).

// Same register as register_crc. The incoming bit is mixed into the outgoing
// msb, so the message is not padded with `width` zeros.
[[nodiscard]] uint32_t feedback_crc(const std::vector<int>& message_bits,
uint32_t generator) {
const int width = polynomial_degree(generator);
uint32_t crc_register = 0;
const uint32_t register_mask = (1U << width) - 1;
const uint32_t truncated_generator = generator & register_mask;
for (const int bit : message_bits) {
const uint32_t msb = (crc_register >> (width - 1)) & 1U;
const uint32_t feedback = msb ^ static_cast<uint32_t>(bit);
crc_register = (crc_register << 1) & register_mask;
if (feedback == 1) {
crc_register ^= truncated_generator;
}
}
return crc_register;
}