反転の反転なんてするから

以前、マイコンのCRC計算回路とDMAを組み合わせてCRC計算をする話を書いた。

CRC演算は時間がかかるからDMA

とりあえず成功したのだが、その後に困ったことが起きた。


というのも、CRC計算回路内部では右送り(MSBファースト)、ビックエンディアンで計算しているのよね。

以前、このBlogでCRC演算の右送り・左送りの話は紹介している。

CRCの計算で一般的なのは、右のビットをMSB、左のビットをLSBと考えて計算する方法。

除数(0x104C11DB7)のMSB・LSBを反転させた値を使いながら、データを右ビットシフトしながら計算するので、右送りと呼ばれる。

(思っていたCRC32とは少し違う)

普通は一番左のビットがMSB、右のビットをLSBと言う。

右送りはデータを右シフトしながら、すなわちLSB側から順番に計算することなので、このマイコンの説明書ではLSBファーストと書かれていた。

ところが、このマイコンのCRC計算回路は左送りが標準となっている。けど、右送りのCRCがないわけでもないし。


解せないのがバイトオーダーがビックエンディアンということ。

このマイコンのプロセッサーはリトルエンディアンなのに、なぜかCRC計算回路はビックエンディアンになっている。

バイトオーダーとは、複数バイトのデータ、例えば32bitのデータを格納するときのバイト順を表している。

ビックエンディアンは1バイト目にMSBが含まれるバイトが、リトルエンディアンは1バイト目にLSBが含まれるバイトが格納される。

16進数で0x12345678というデータを 12 34 56 78と並べるのがビックエンディアン、78 56 34 12と並べるのがリトルエンディアンである。

このあたりはプロセッサーによるんだけど、リトルエンディアンのプロセッサーが主流のよう。

というわけで、メモリ上に12 34 56 78 9A BC DE F0 と格納されたデータを、4バイトずつ読み込むと 0x78563412, 0xF0DECB9A と解釈される。

ところが標準設定のままCRC計算回路に 0x78563412 を転送すると、78 56 34 12 という順番で、各々左送りでCRC計算することになる。

複数バイト一括でCRC計算回路に転送すると、データの順番が逆になっちゃってるんだよね。


ただ、左送り、ビックエンディアンが実際に使いたいCRCの計算方式とは限らないので、

CRC計算回路の前後で、バイト順反転、ビット順反転をできるようになっている。

右送り・リトルエンディアンの場合は、入力・出力ともに、バイト順反転、ビット単位でビット順反転 を有効にすればよい。

ビット順を反転して入力して左送りで計算すれば、右送りでCRC計算した結果のビット順反転になるので、また反転すればよいということらしい。

左送り・リトルエンディアンの場合は、入力側のみバイト順反転にすればよいはず。


というわけで、正しく設定をすればどうってことはないわけだが、1つ問題があった。

それがCRC計算の初期値だ。

CRC計算の初期値としては 0x00000000 と 0xFFFFFFFF がよく使われる。

世間的には0xFFFFFFFFの方がよく使われているようだが、うちの製品群では専ら0x00000000ですね。

いずれにせよ初期値を任意に設定できる仕組みを付けておけばよい。

その機能を使って、一連のCRC演算を分割して行おうとした。

初期値0x00000000からCRC演算を0バイト目からNバイト目まで計算して、そのCRC(ビット反転なし)が0xEDB88320だったとする。

次に初期値0xEDB88320からN+1バイト目からMバイト目までCRCを計算する、そうして求まるCRCは0~Mバイト目のデータのCRC値になる。


ところがこうやって計算すると、結果が合わないんだよね。

それでおかしいなぁってデバッガで調べてみたら、初期値に値を設定して、なにも計算せずに読み出すとビット順が反転していることに気づいた。

すなわち初期値0xEDB88320をセットしたら、途端に結果が0x04C11DB7になっているということだ。

それで過去の同様の実装例を調べたら、途中の計算では出力側のバイト順反転、ビット単位でビット順反転を無効化して、

最後の計算のときだけ、バイト順反転、ビット単位でビット順反転を有効にするという方法を取っていた。

こんな使い分けをしてるなんて今まで気づいてなかったけど、そうだったんだね。1発計算と同じだと思っていた。


ちなみに初期値を0x00000000 と 0xFFFFFFFF をセットするのであれば、ビット順が反転でも何の問題もない。

だってビット順を反転しても0x00000000 と 0xFFFFFFFFだからね。だから右送り・左送りともにこの値を初期値に設定すればよい。

典型的なケースでは問題にならないことから、マイコンのマニュアルにもはっきりとは書かれていなかった。

回路構成を読み解くと、なぜこうなるかはわかるようになってたけど、ちょっと微妙ですね。


そもそも、なんでリトルエンディアンのプロセッサーにビックエンディアン・左送りが標準のCRC演算回路を載せてるんだって話だけど。

マイコンのマニュアルには、CRC16では左送りが標準っぽいことが書かれているけど、僕が知る限りではCRC16も右送りが主流だと思うんだけどね。

左送り+ビックエンディアンというのは、最初のバイトのMSBから順番にという点では一貫性があるけど、

いや、それなら右送り+リトルエンディアンで、最後のバイトのLSBからっていう方を選べよと思うんだが、ようわからん。

まぁ前後で反転すればよいというアプローチはその通りだと思うんだけど。