ぐだぐだ低レベルプログラミング(21) GD32VF103のサイクルカウンタ辺の実装

Joseph Halfmoon

「RISC-V原典」を読んだので(といって全部きちんとじゃないですが)、再び、RISC-Vのアセンブラと戯れてみたいと思います。最初の疑問は、「図3.3 ゼロ・レジスタであるx0を利用したRISC-Vの32個の疑似命令」という表に掲げられている rdinstret, rdcycle, rdtimeという3つの「疑似命令」についてです。x86の場合の rdtsc と似たもの共、と言えば「アセンブラ関係者」の方はお分かりと思います。

※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら

こやつらは、サイクルカウンタとか、パフォーマンスカウンタとか言われる一族のモノであります。主として機械語レベルでのチューニングなどで使われることが多いので、その辺に触れる機会がないとお付き合い願う必要はあまりないかと思います。RISC-Vに限らず、x86にもArmにも皆類似の命令群があるのですが、どこでも実装に依存する部分が大きい部分じゃないかと思います。

狭義のサイクルカウンタは、ある期間の命令実行にかかるCPUサイクル数をカウントするためのもの

といっていいかと思います。パフォーマンスカウンタ、といった場合、上記よりは広い意味となり、

ある命令実行期間中に発生した各種のイベントをカウントする

ことになります。イベントとは、何命令実行完了したとかCPU関係のものもあれば、メモリリードの回数とか、キャッシュミスの回数とか、メモリに関わるもの、割り込み回数など割り込みに関わるものなどもあります。そのイベントの一つにCPUサイクル数も含まれると。x86などでは、狭義のサイクルカウンタへのアクセスのためには、よく使われるrdtsc命令などという専用命令が準備されています。広義の方のパフォーマンスカウンタの方は仕様が大きすぎて1命令で済むようなものではなく(実装により異なりますが、数百種類のイベントを識別可能な複数のカウンタが存在)巨大なものです。Armの場合はオプショナルなコープロセッサといった位置づけだった筈。それぞれにまた闇があり。閑話休題。

さて、RISC-Vにも同様の機構が存在可能なのでありますが、

  • 3種のカウンタについては疑似命令まで決まっている
  • その他の拡張カウントについては、実装依存

になっているようです。ただし、疑似命令まで決まっている3種のカウンタについても、実装必須というわけでないようです。三種疑似命令は以下のとおり、

  1. rdinstret[h]
  2. rdcycle[h]
  3. rdtime[h]

どの命令も後ろに[h]が付いているのは、どれも64ビットのカウンタであるのですが、hなしの時下32ビットを読み込み、hありのとき上32ビットを読み込みの2つの命令に分かれているためです。

最初のrdinstretは、read instruction retired counterだと思います。実行済命令カウンタの読み込み。上位のスーパスカラ機などでは、実行開始したけれども途中で止めた、といったケースが頻発する筈なので、「実行完了」した命令数を数えないと実際の仕事に釣り合いません。GD32VF103の場合は、スーパスカラじゃない筈なので、単に処理した命令数という理解で良いような気がします。

次のrdcycleは、単純にCPUの実行サイクルを数えるもの。スーパスカラじゃないRISCの場合、通常、1命令=1サイクルである(スーパスカラなら、1サイクルに複数命令走ったりもする)筈ですが、「諸般の事情で」1命令が複数サイクルに伸びることもままあるので、サイクル数は別に数えないとなりません。

最後のrdtimeは、CPUサイクルとは別の実時間タイマという定義です。昨今のCPUクロックは、早くなったり遅くなったり、止まったりする筈なので、それとは別に実時間で動くタイマがないと本当の処理時間が分からないためか、と思います。

上記3つの疑似命令は、RISC-Vが定める制御レジスタの空間である CSR空間のレジスタの読み取り命令を「覚えやすく」してくれます。たとえば、rdinstret疑似命令は、実際には、

csrrs    rd, 0xC02, x0
などというあまり覚えられそうにないコードに展開されます。さて、
この命令走らせれば、カウンタの値が読めるんだからいいじゃん。
と浅墓にも、以前、ちょっと動かしてみあたことがあったのです。GD32VF103の場合、ソフトの書き方にもよりますが、単純にCSR空間のレジスタを読み出すだけでは、それらしい値が返ってこないです。それどころか、
rdtime
に関しては、トラップが発生しました。GD32VF103の場合、INSTRETのカウンタと、CYCLEカウンタは存在するようですが、rdtimeで読める実時間タイマは実装されていないようです。まあ、周辺回路として、タイマ類はいろいろあるので、その中のどれかを使って、ってことでしょうか。
残りのINSTRETと、CYCLEについては、値が読めるし、走らせ方によっては読み取れる数字が違うことがあるのだけれど、どうも私が書いたテストプログラムの中では、

カウントしていない

です。今回、RISC-V原典よみましたが、その辺のからくりについては何もかいていません。しかし、Bumblebee core datasheetをよくよく眺めたところ判明しました(手元のこのPDFはフォントのせいで途轍もなく読みずらいので避けてたのですが)。GD32VF103の実装によるものだと思います。
mcountinhibit
という別なCSRレジスタが実装されており、ここのそれぞれのカウンタに対応する「ビットを立てる」とカウントが止まるのでした。カウントさせるためには、以下のようにこのレジスタのビットをクリアせねばなりません。
write_csr(0x320, 0); //mcountinhibit

(10/10追記:ここから怪しいです。実際にdelay_1msの中を確認したら何もしていません。要再調査)さらに言うと、便利なのでついつい使っている。systick.h内のdelay_1msルーチン(下参照)を呼ぶと、上記のビットが立つ!ようでした。もともとこのようなインヒビット機能付きの実装になっているのは、消費電力を少しでも下げたいという要求にそったもののようです。遅延ルーチンに入ると「忘れずに」インヒビットしてくれちゃうみたいです。それでテストプログラムではカウンタが止まったままだったです。(怪しい部分終わり)

#include "systick.h"
~途中略~
delay_1ms(1000);

なお、インヒビットするには、CSRアドレス0x320に0でなく、5をかけばINSTRETとCYCLEに相当するフラグが立ちます。

またまた、ちゃんとデータシート読めや!という話。

ぐだぐだ低レベルプログラミング(20) RISC-V、nop、mv、li?? へ戻る

ぐだぐだ低レベルプログラミング(22) GD32VF103 サイクルカウンタ実測例 へ進む