IchigoJam R でハードウェアCRC計算を試す

# はじめに

IchigoJam R では、GD32VF103CBT6 というCPUが使われています。
(出典: [IchigoJam BASIC 1.5β1、USBキーボード対応 RISC-V版 IchigoJam Rβ 出荷スタート！ #KidsIT #IchigoJam / 福野泰介の一日一創 / Create every day by Taisuke Fukuno](https://fukuno.jig.jp/3103))

ここでこのCPUのManualとData Sheetがダウンロードできます。
[GD32VF103CBT6](https://www.gigadevice.com/microcontroller/gd32vf103cbt6/)

このUser Manualを見ると、CRCを計算するハードウェアが入っているらしいことがわかりました。
(8. CRC calculation unit (CRC))
そこで、これを試してみることにしました。

# 引数の確認

まずはどのような引数が渡されているのか確認します。
IchigoJam R のマシン語では、レジスタR10～R17に関数の引数が渡され、かつ自由に使えるようです。
(出典: [IchigoJam Rβでも輝くWS2812B、RISC-Vマシン語で10ナノ秒単位で制御する #riscv #asm #IchigoJam / 福野泰介の一日一創 / Create every day by Taisuke Fukuno](https://fukuno.jig.jp/3111))

IchigoJamの仕様より、第2引数 R11 と第3引数 R12 のどちらかにROMの0番地のアドレス、
どちらかにRAMの0番地相当のアドレスが入るはずですが、どっちがどっちだったか記憶が怪しいです。
そこで、それぞれの値を出力させてみます。

```
10 ' ﾋｷｽｳ ｶｸﾆﾝ
20 POKE#700,#B7,#2F,#07,#E0,#93,#26,#05,#02
30 POKE#708,#91,#E2,#B2,#85,#7D,#89,#33,#D5
40 POKE#710,#A5,#00,#82,#80,#20,#28,#00,#D3
50 POKE#718,#11,#46,#1F,#23,#18,#40,#C1,#40
60 POKE#720,#08,#46,#70,#47
70 A=USR(#700,0):B=USR(#700,16)
80 C=USR(#700,32):D=USR(#700,48)
90 ?"2: #";HEX$(B,4);HEX$(A,4)
100 ?"3: #";HEX$(D,4);HEX$(C,4)
```

<details><summary>アセンブリコード</summary><div>

```
IF M0 GOTO @LEGACY
MODE RV32C
R13 = R10 < 32
IF R13 GOTO @PRINT_R11
R11 = R12
@PRINT_R11
R10 &= #1f
R10 = R11 >> R10
RET
MODE M0
@LEGACY
R0 - 32
IF CC GOTO @PRINT_R1
R1 = R2
@PRINT_R1
R3 = #1f
R0 &= R3
R1 >>= R0
R0 = R1
RET
```

</div></details>

実行結果

```
2: #20000A2C
3: #20000010
```

あれ…？どっちもSRAMのアドレス…？

従来のIchigoJamにも対応させておいたので、1.4.1 で実行してみます。

```
2: #0FFFFD80
3: #00005800
```

どうやら第2引数がRAMのアドレスのようです。

# 設定の確認

User Manual の 1.2. System architecture にある Figure 1-1 を参照すると、
CRC計算のモジュールは AHB Peripherals に含まれるようです。
関連しそうなレジスタを探すと、
5.3.2. Clock configuration register 0 (`RCU_CFG0`) にAHBのクロック速度を決める部分 `AHBPSC` があり、
5.3.6. AHB enable register (`RCU_AHBEN`) にCRC計算用のクロックを供給するかを決める部分 `CRCEN` があるようです。
そこで、これらの値を読んでみます。

```
10 ' print AHBPSC and CRCEN
20 POKE#700,#91,#45,#C2,#05,#93,#E5,#15,#02
30 POKE#708,#B2,#05,#C8,#41,#11,#81,#3D,#89
40 POKE#710,#D0,#49,#09,#82,#41,#8A,#51,#8D
50 POKE#718,#82,#80
60 V=USR(#700,0)
70 ?"AHBPSC=`";BIN$(V&#F,4)
80 ?"CRCEN = ";(V&#10)>>4
```

<details><summary>アセンブリコード</summary><div>

```
MODE RV32C
' R11 = #40021000
R11 = #4
R11 <<= 16
R11 = R11 | #21
R11 <<= 12
' R10 = RCU_CFG0
R10 = [R11 + #04]L
' extract AHBPSC
R10 >>= 4
R10 &= #f
' R12 = RCU_AHBEN
R12 = [R11 + #14]L
' extract CRCEN
R12 >>= 2
R12 &= #10
' merge and return
R10 |= R12
RET
```

</div></details>

実行結果

```
AHBPSC=`0000
CRCEN = 0
```

AHBのクロックは `CK_SYS` に、CRC計算用のクロックはオフになっているようです。

# CRCの値の取得？

`CRCEN` を1にし、CRC計算モジュールに4バイトの値を書き込みます。
とりあえず `DE AD BE EF` (リトルエンディアンで解釈すると `0xefbeadde`) を入れてみます。
なお、`USR`の第2引数は0を入れるとリセットしない、0以外を入れるとリセットする、としました。

```
10 ' CRC test
20 POKE#700,#21,#46,#22,#06,#B2,#95,#11,#46
30 POKE#708,#42,#06,#13,#66,#16,#02,#32,#06
40 POKE#710,#54,#4A,#93,#E6,#06,#04,#54,#CA
50 POKE#718,#09,#67,#3A,#96,#09,#C5,#08,#46
60 POKE#720,#13,#65,#15,#00,#08,#C6,#08,#42
70 POKE#728,#C8,#C1,#88,#41,#08,#C2,#08,#42
80 POKE#730,#88,#C5,#82,#80
90 LET[0],#ADDE,#EFBE
100 X=USR(#700,1)
110 ?"before: #";HEX$([3],4);HEX$([2],4)
120 ?"after : #";HEX$([5],4);HEX$([4],4)
```

<details><summary>アセンブリコード</summary><div>

```
MODE RV32C
' R11 += #800
R12 = 8
R12 <<= 8
R11 += R12
' R12 = #40021000
R12 = #4
R12 <<= 16
R12 = R12 | #21
R12 <<= 12
' R13 = RCU_AHBEN
R13 = [R12 + #14]L
' turn on CRCEN
R13 = R13 | #40
[R12 + #14]L = R13
' R12 += #2000 (R12 = #40023000)
R14 = #2000
R12 += R14
' reset CRC if R10 is non-zero
IF !R10 GOTO @NO_RESET
R10 = [R12 + #08]L
R10 = R10 | 1
[R12 + #08]L = R10
@NO_RESET
' read and store CRC_DATA
R10 = [R12 + #00]L
[R11 + 4]L = R10
' put [0]:[1] to CRC_DATA
R10 = [R11 + 0]L
[R12 + #00]L = R10
' read and store CRC_DATA
R10 = [R12 + #00]L
[R11 + 8]L = R10
' return
RET
```

</div></details>

実行結果

```
before: #FFFFFFFF
after : #EDE062E3
```

`de ad be ef` のCRC-32は `7c9ca35a` のはずなので、値が一致しません。

[From Hex, CRC-32 Checksum - CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Hex%28%27Auto%27%29CRC-32_Checksum%28%29&input=ZGVhZGJlZWY)

これはいったい何を求めているのでしょうか？

ちなみに、CRCを求めるのには 4 AHB clock cycles かかるとされていますが、
今回データを書き込んだ直後(次の命令)で値を読み出しても一定の値が得られました。
`AHBPSC` に `` `1010`` を書き込み、AHBのクロックを8分の1にしても、一定の値が得られました。
(AHBのクロックを下げると、それに比例してシリアル通信のボーレートも下がるようでした)
したがって、あまり気にしなくてよさそうだと考えられます。

# 何を求めているのかの解析

## 多項式のチェック

User Manual を見ると、CRCの計算は「Fixed polynomial: 0x4C11DB7」を用いて行われるようです。
これは普通のCRC-32の計算でも使われる値のようです。

## ゼロが並んだデータを入れる

ここで、試しにデータとして `00 00 00 00` を入れてみました。

```
90 LET[0],#0000,#0000
```

実行結果

```
before: #FFFFFFFF
after : #C704DD7B
```

ちなみに、`00 00 00 00` のCRC-32は `2144df1c` です。

[From Hex, CRC-32 Checksum - CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Hex%28%27Auto%27%29CRC-32_Checksum%28%29&input=MDAwMDAwMDA)

何か特徴的な値かもしれないと思い、`C704DD7B` でググると、以下のページが見つかりました。

[python - How to get ethernet magical number 0xC704DD7B from zlib crc32 calculator - Stack Overflow](https://stackoverflow.com/questions/59953944/how-to-get-ethernet-magical-number-0xc704dd7b-from-zlib-crc32-calculator)

このページによると、`C704DD7B` は`00 00 00 00`のCRC-32のビットの順番を反転し、ビットNOTを取った値のようです。
この「ビットの順番を反転し、ビットNOTを取る」処理を入れると、確かにこの値が得られました。

[From Hex, 7 more - CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Hex%28%27Auto%27%29CRC-32_Checksum%28%29From_Hex%28%27Auto%27%29To_Binary%28%27Space%27,8%29Reverse%28%27Character%27%29From_Binary%28%27Space%27,8%29NOT%28%29To_Hex%28%27None%27,0%29&input=MDAwMDAwMDA)

さらに、もう1回 `00 00 00 00` を書き込み、`00` が8個並んだデータのCRCを計算させます。

```
X=USR(#700,0):GOTO 120
```

実行結果

```
after : #6904BB59
```

これは、`00` が8個並んだデータのCRC-32のビットの順番を反転し、ビットNOTを取った値と一致しました。

[From Hex, 7 more - CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Hex%28%27Auto%27%29CRC-32_Checksum%28%29From_Hex%28%27Auto%27%29To_Binary%28%27Space%27,8%29Reverse%28%27Character%27%29From_Binary%28%27Space%27,8%29NOT%28%29To_Hex%28%27None%27,0%29&input=MDAwMDAwMDAwMDAwMDAwMA)

すなわち、逆にCRC計算モジュールの出力のビットの順番を反転し、ビットNOTを取れば、
`00`が並んだデータのCRC-32が得られそうです。

## 1ビットだけ立ててみる

`00`が並んだデータのCRC-32が得られそうになったので、次は入力データのうち1ビットだけを1にしてみました。

```
90 LET[0],#0000,#8000
```

実行結果

```
before: #FFFFFFFF
after : #61E2E066
```

調べた結果、これは `01 00 00 00` のCRC-32のビットの順番を反転し、ビットNOTを取った値と一致しました。
入力したデータは `00 00 00 80` なので、入力からビットの順番が反転していそうなことがわかります。

## deadbeefに戻してみる

`de ad be ef` について、

1. 入力のビットの順番を反転する
2. CRC-32を求める
3. 得られた値のビットの順番を反転し、ビットNOTを取る

という処理を行うと、結果は `ede062e3` となり、最初のCRC計算モジュールの出力と一致しました。

[From Hex, 10 more - CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Hex%28%27Auto%27%29To_Binary%28%27Space%27,8%29Reverse%28%27Character%27%29From_Binary%28%27Space%27,8%29CRC-32_Checksum%28%29From_Hex%28%27Auto%27%29To_Binary%28%27Space%27,8%29Reverse%28%27Character%27%29From_Binary%28%27Space%27,8%29NOT%28%29To_Hex%28%27None%27,0%29&input=ZGVhZGJlZWY)

## 複数のブロックを入れてみる

`de ad be ef` に続いて `12 34 56 78` をCRC計算モジュールに入力し、出力を見ます。

```
LET[0],#3412,#7856
X=USR(#700,0):GOTO 120
```

実行結果

```
after : #06C89E49
```

これは `de ad be ef 12 34 56 78` を4バイトずつビットの順番を反転させたもののCRC-32を求め、
その結果のビットの順番を反転し、ビットNOTを取った値と一致しました。

[Subsection, 12 more - CyberChef](https://gchq.github.io/CyberChef/#recipe=Subsection%28%27%28%5B0-9a-fA-F%5D%7B8%7D%29%27,true,true,false%29From_Hex%28%27Auto%27%29To_Binary%28%27Space%27,8%29Reverse%28%27Character%27%29From_Binary%28%27Space%27,8%29Merge%28%29CRC-32_Checksum%28%29From_Hex%28%27Auto%27%29To_Binary%28%27Space%27,8%29Reverse%28%27Character%27%29From_Binary%28%27Space%27,8%29NOT%28%29To_Hex%28%27None%27,0%29&input=ZGVhZGJlZWYxMjM0NTY3OA)

入力のビットの順番の反転は、入力全体ではなく、4バイトずつでいいようです。

## 結論

このCRC計算モジュールは、入力の4バイトについて

1. 入力のビットの順番を反転する
2. CRC-32を求める
3. 結果のビットの順番を反転し、ビットNOTをとる

という処理を行った結果を返すらしいことがわかりました。

# 入力を一気に入れてみる

出力は入力の直後の命令で得られるらしいことがわかりましたが、入力を一度に大量に入れるとどうなるでしょう？
User Manual より 32-bit input buffer があるらしいので、これを超えるように4バイトの値4個を一気に入れてみます。

```
10 ' CRC test (many)
20 POKE#700,#21,#46,#22,#06,#B2,#95,#11,#46
30 POKE#708,#42,#06,#13,#66,#16,#02,#32,#06
40 POKE#710,#54,#4A,#93,#E6,#06,#04,#54,#CA
50 POKE#718,#09,#67,#3A,#96,#09,#C5,#08,#46
60 POKE#720,#13,#65,#15,#00,#08,#C6,#08,#42
70 POKE#728,#88,#C9,#88,#41,#D4,#41,#98,#45
80 POKE#730,#DC,#45,#08,#C2,#14,#C2,#18,#C2
90 POKE#738,#1C,#C2,#08,#42,#C8,#C9,#82,#80
100 LET[0],#ADDE,#EFBE,#3412,#7856,#BC9A,#F0DE,#C011,#EEFF
110 X=USR(#700,1)
120 ?"before: #";HEX$([9],4);HEX$([8],4)
130 ?"after : #";HEX$([11],4);HEX$([10],4)
```

<details><summary>アセンブリコード</summary><div>

```
MODE RV32C
' R11 += #800
R12 = 8
R12 <<= 8
R11 += R12
' R12 = #40021000
R12 = #4
R12 <<= 16
R12 = R12 | #21
R12 <<= 12
' R13 = RCU_AHBEN
R13 = [R12 + #14]L
' turn on CRCEN
R13 = R13 | #40
[R12 + #14]L = R13
' R12 += #2000 (R12 = #40023000)
R14 = #2000
R12 += R14
' reset CRC if R10 is non-zero
IF !R10 GOTO @NO_RESET
R10 = [R12 + #08]L
R10 = R10 | 1
[R12 + #08]L = R10
@NO_RESET
' read and store CRC_DATA
R10 = [R12 + #00]L
[R11 + #10]L = R10
' put [0]-[7] to CRC_DATA
R10 = [R11 + #0]L
R13 = [R11 + #4]L
R14 = [R11 + #8]L
R15 = [R11 + #C]L
[R12 + #00]L = R10
[R12 + #00]L = R13
[R12 + #00]L = R14
[R12 + #00]L = R15
' read and store CRC_DATA
R10 = [R12 + #00]L
[R11 + #14]L = R10
' return
RET
```

</div></details>

実行結果

```
before: #FFFFFFFF
after : #3CAB05DC
```

期待通りの値が得られました。

[Subsection, 12 more - CyberChef](https://gchq.github.io/CyberChef/#recipe=Subsection%28%27%28%5B0-9a-fA-F%5D%7B8%7D%29%27,true,true,false%29From_Hex%28%27Auto%27%29To_Binary%28%27Space%27,8%29Reverse%28%27Character%27%29From_Binary%28%27Space%27,8%29Merge%28%29CRC-32_Checksum%28%29From_Hex%28%27Auto%27%29To_Binary%28%27Space%27,8%29Reverse%28%27Character%27%29From_Binary%28%27Space%27,8%29NOT%28%29To_Hex%28%27None%27,0%29&input=ZGVhZGJlZWYxMjM0NTY3ODlhYmNkZWYwMTFjMGZmZWU)

# 短い入力を入れてみる

CRC計算モジュールの入力レジスタについて、
User Manual では「This register has to be accessed by word (32-bit).」となっていますが、
これに反して1バイトや2バイトのアクセスをするとどうなるでしょう？

```
10 ' CRC test (short)
20 POKE#700,#21,#46,#22,#06,#B2,#95,#11,#46
30 POKE#708,#42,#06,#13,#66,#16,#02,#32,#06
40 POKE#710,#54,#4A,#93,#E6,#06,#04,#54,#CA
50 POKE#718,#09,#67,#3A,#96,#93,#76,#85,#00
60 POKE#720,#89,#E6,#14,#46,#93,#E6,#16,#00
70 POKE#728,#14,#C6,#14,#42,#D4,#C1,#94,#41
80 POKE#730,#13,#77,#15,#00,#01,#C7,#23,#00
90 POKE#738,#D6,#00,#01,#A8,#13,#77,#25,#00
100 POKE#740,#01,#C7,#23,#10,#D6,#00,#11,#A0
110 POKE#748,#14,#C2,#14,#42,#94,#C5,#82,#80
120 LET[0],#ADDE,#EFBE
130 FOR I=0 TO 2
140 X=USR(#700,1<<I)
150 ?"before: #";HEX$([3],4);HEX$([2],4)
160 ?"after : #";HEX$([5],4);HEX$([4],4)
170 NEXT
```

<details><summary>アセンブリコード</summary><div>

```
MODE RV32C
' R11 += #800
R12 = 8
R12 <<= 8
R11 += R12
' R12 = #40021000
R12 = #4
R12 <<= 16
R12 = R12 | #21
R12 <<= 12
' R13 = RCU_AHBEN
R13 = [R12 + #14]L
' turn on CRCEN
R13 = R13 | #40
[R12 + #14]L = R13
' R12 += #2000 (R12 = #40023000)
R14 = #2000
R12 += R14
' reset CRC if R10 & 8 is zero
R13 = R10 & #8
IF R13 GOTO @NO_RESET
R13 = [R12 + #08]L
R13 = R13 | 1
[R12 + #08]L = R13
@NO_RESET
' read and store CRC_DATA
R13 = [R12 + #00]L
[R11 + 4]L = R13
' put [0] to CRC_DATA
R13 = [R11 + 0]L
R14 = R10 & #1
IF !R14 GOTO @NOT_1BYTE
[R12 + #00] = R13
GOTO @AFTER_FEED
@NOT_1BYTE
R14 = R10 & #2
IF !R14 GOTO @NOT_2BYTE
[R12 + #00]W = R13
GOTO @AFTER_FEED
@NOT_2BYTE
[R12 + #00]L = R13
@AFTER_FEED
' read and store CRC_DATA
R13 = [R12 + #00]L
[R11 + 8]L = R13
' return
RET
```

</div></details>

引数の最下位ビットが立っている場合(例えば、1)は1バイトでアクセスし、
そうでなく、引数の最下位から2番目のビットが立っている場合(例えば、2)は2バイトでアクセスします。
また、引数の最下位から4番目のビットが立ってい**ない**場合はCRC計算モジュールをリセットします。

実行結果

```
before: #FFFFFFFF
after : #6211BC8C
before: #FFFFFFFF
after : #CDD76B4F
before: #FFFFFFFF
after : #EDE062E3
```

調査の結果、1バイトでアクセスした場合は `de de de de` について処理をした結果、
2バイトでアクセスした場合は `de ad de ad` について処理をした結果と一致することがわかりました。

残念ながら、1バイトや2バイトでアクセスしても4バイトのデータが処理され、端数のデータの処理には使えないようです。

# CRC-32を求めてみる

CRC計算モジュールの性質がわかったので、これを用いてCRC-32を求めてみます。
CRC-32の求め方は

1. 入力を4バイトずつに区切り、それぞれビットの順番を反転してCRC計算モジュールに入力する
2. CRC計算モジュールの出力のビットの順番を反転し、ビットNOTをとる

です。

```
10 ' CRC-32 ｦ ﾓﾄﾒﾙ
20 POKE#700,#7D,#71,#06,#C0,#21,#46,#22,#06
30 POKE#708,#B2,#95,#2E,#C2,#11,#46,#42,#06
40 POKE#710,#13,#66,#16,#02,#32,#06,#54,#4A
50 POKE#718,#93,#E6,#06,#04,#54,#CA,#09,#67
60 POKE#720,#3A,#96,#32,#C4,#09,#C5,#08,#46
70 POKE#728,#13,#65,#15,#00,#08,#C6,#08,#42
80 POKE#730,#C8,#C1,#88,#41,#21,#28,#22,#46
90 POKE#738,#08,#C2,#08,#42,#01,#28,#92,#45
100 POKE#740,#13,#45,#F5,#FF,#88,#C5,#82,#40
110 POKE#748,#41,#61,#82,#80,#81,#45,#01,#46
120 POKE#750,#93,#76,#15,#00,#13,#47,#F6,#01
130 POKE#758,#B3,#96,#E6,#00,#D5,#8D,#05,#81
140 POKE#760,#05,#06,#93,#26,#06,#02,#ED,#F6
150 POKE#768,#2E,#85,#82,#80
160 LET[0],#ADDE,#EFBE
170 X=USR(#700,1)
180 ?"before: #";HEX$([3],4);HEX$([2],4)
190 ?"after : #";HEX$([5],4);HEX$([4],4)
200 LET[0],#3412,#7856
210 X=USR(#700,0)
220 ?"after2: #";HEX$([5],4);HEX$([4],4)
```

<details><summary>アセンブリコード</summary><div>

```
MODE RV32C
SP += -1
PUSH R1, 0
' R11 += #800
R12 = 8
R12 <<= 8
R11 += R12
PUSH R11, 1
' R12 = #40021000
R12 = #4
R12 <<= 16
R12 = R12 | #21
R12 <<= 12
' R13 = RCU_AHBEN
R13 = [R12 + #14]L
' turn on CRCEN
R13 = R13 | #40
[R12 + #14]L = R13
' R12 += #2000 (R12 = #40023000)
R14 = #2000
R12 += R14
PUSH R12, 2
' reset CRC if R10 is non-zero
IF !R10 GOTO @NO_RESET
R10 = [R12 + #08]L
R10 = R10 | 1
[R12 + #08]L = R10
@NO_RESET
' read and store CRC_DATA
R10 = [R12 + #00]L
[R11 + 4]L = R10
' put [0]:[1] to CRC_DATA
R10 = [R11 + 0]L
GOSUB @REVERSE_BITS
POP R12, 2
[R12 + #00]L = R10
' read and store CRC_DATA
R10 = [R12 + #00]L
GOSUB @REVERSE_BITS
POP R11, 1
R10 = R10 ^ -1
[R11 + 8]L = R10
' return
POP R1, 0
SP += 1
RET

@REVERSE_BITS
R11 = 0
R12 = 0
@REVERSE_LOOP
R13 = R10 & 1
R14 = R12 ^ #1F
R13 = R13 << R14
R11 |= R13
R10 >>= 1
R12 += 1
R13 = R12 < 32
IF R13 GOTO @REVERSE_LOOP
R10 = R11
RET
```

</div></details>

実行結果

```
before: #FFFFFFFF
after : #7C9CA35A
after2: #B1C0FA66
```

正しく `de ad be ef` および `de ad be ef 12 34 56 78` のCRC-32が求まりました。

# 結論

IchigoJam R (GD32VF103CBT6) のCRC計算モジュールを用い、CRC-32を求めることができました。
ただし、ビットの順番を反転させる操作が必要な上、データの長さが4バイトの倍数でないと求めることができないようです。
端数の処理のためにソフトウェアでCRC-32の計算を実装するなら、
全部ソフトウェアで計算したほうが面倒が少なくていいかもしれないですね。

# おわりに

IchigoJam R の販売ページはこちらです。

[【β版】IchigoJam 組み立て済完成品 R | Programming Club Net...](https://hello002.stores.jp/items/5fdc3daff0b10819906d9c74)

IchigoJamはjig.jpの登録商標です。