LoginSignup
1
0

More than 1 year has passed since last update.

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

Posted at

はじめに

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&#10)>>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

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

From Hex, 7 more - CyberChef

さらに、もう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

すなわち、逆に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

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

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バイトについて

  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)

アセンブリコード
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の求め方は

  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)

アセンブリコード
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の登録商標です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0