はじめに
IchigoJam R では、GD32VF103CBT6 というCPUが使われています。
(出典: IchigoJam BASIC 1.5β1、USBキーボード対応 RISC-V版 IchigoJam Rβ 出荷スタート! #KidsIT #IchigoJam / 福野泰介の一日一創 / Create every day by Taisuke Fukuno)
ここでこのCPUのManualとData Sheetがダウンロードできます。
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)
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)
アセンブリコード
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
実行結果
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
)>>4
アセンブリコード
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
実行結果
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)
アセンブリコード
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
実行結果
before: #FFFFFFFF
after : #EDE062E3
de ad be ef
のCRC-32は 7c9ca35a
のはずなので、値が一致しません。
From Hex, CRC-32 Checksum - CyberChef
これはいったい何を求めているのでしょうか?
ちなみに、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
何か特徴的な値かもしれないと思い、C704DD7B
でググると、以下のページが見つかりました。
python - How to get ethernet magical number 0xC704DD7B from zlib crc32 calculator - Stack Overflow
このページによると、C704DD7B
は00 00 00 00
のCRC-32のビットの順番を反転し、ビットNOTを取った値のようです。
この「ビットの順番を反転し、ビットNOTを取る」処理を入れると、確かにこの値が得られました。
さらに、もう1回 00 00 00 00
を書き込み、00
が8個並んだデータのCRCを計算させます。
X=USR(#700,0):GOTO 120
実行結果
after : #6904BB59
これは、00
が8個並んだデータのCRC-32のビットの順番を反転し、ビットNOTを取った値と一致しました。
すなわち、逆に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
について、
- 入力のビットの順番を反転する
- CRC-32を求める
- 得られた値のビットの順番を反転し、ビットNOTを取る
という処理を行うと、結果は ede062e3
となり、最初のCRC計算モジュールの出力と一致しました。
複数のブロックを入れてみる
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
入力のビットの順番の反転は、入力全体ではなく、4バイトずつでいいようです。
結論
このCRC計算モジュールは、入力の4バイトについて
- 入力のビットの順番を反転する
- CRC-32を求める
- 結果のビットの順番を反転し、ビット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)
アセンブリコード
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
実行結果
before: #FFFFFFFF
after : #3CAB05DC
期待通りの値が得られました。
Subsection, 12 more - CyberChef
短い入力を入れてみる
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
アセンブリコード
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
引数の最下位ビットが立っている場合(例えば、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の求め方は
- 入力を4バイトずつに区切り、それぞれビットの順番を反転してCRC計算モジュールに入力する
- 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)
アセンブリコード
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
実行結果
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...
IchigoJamはjig.jpの登録商標です。