はじめに
AXI Timerは標準で32bitのカウンタを内蔵しており、これは100MHz環境では$2^{32} / 100M = 42.96...$と42秒ほどまでしか計測できません。ので、より長い時間のかかるプログラムの実行時間を測定したいとき困ります。ここでは64bitにする方法を説明します。
以下の記事の和訳及び要約です。
Vivado側の操作
AXI Timerをダブルクリック、Enable 64-bit modeにチェックをいれるだけでいいです。AXI Timer IPはもとから内部に2つ、32bitのカウンタを持っています。これらを連結し、64bitのカウンタとして操作できるようにしてくれるのがこのmodeです。
Vitis側の操作
適切に.cからAXI Timerを設定する必要があります。XTmrCtr_GetTime64(&TimerCounterInst)の前に必ず行わないといけない処理は以下のとおりです。
int Status;
Status = XTmrCtr_Initialize(&TimerCounterInst, XTMRCTR_BASEADDRESS);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
XTmrCtr_SetResetValue(&TimerCounterInst, TIMER_CNTR_0, RESET_VALUE);
XTmrCtr_SetResetValue(&TimerCounterInst, TIMER_CNTR_1, RESET_VALUE);
XTmrCtr_SetOptions(&TimerCounterInst, TIMER_CNTR_0, XTC_CASCADE_MODE_OPTION);
XTmrCtr_Reset(&TimerCounterInst, TIMER_CNTR_0);
XTmrCtr_Reset(&TimerCounterInst, TIMER_CNTR_1);
XTmrCtr_Start(&TimerCounterInst, TIMER_CNTR_0);
-
XTmrCtr_Initialize()
内部でハードウェアのテストを行います。Vivado側でAXI Timerを用意していない状況で実行するとここで教えてくれます。 -
XTmrCtr_SetResetValue()
無しでも動きますが、たまに変な値が入っているので0埋めします。 -
XTmrCtr_SetOptions
どのようにタイマーを使用するかをここで指定します。二つのタイマーを一つのものとして扱うのでここではXTC_CASCADE_MODE_OPTIONを指定します。他にもいくつかOPTIONがあり、xtmrctr.hに記載があります。割り込みを発生させるときはXTC_INT_MODE_OPTION | XTC_AUTO_RELOAD_OPTION | XTC_CASCADE_MODE_OPTIONというふうに指定します。末尾に添付するリポジトリのxtmrctr_intr_64bit_example.cに詳しく記載があります。 -
XTmrCtr_Reset()
無しでも動きますが、公式の例では必ず記載があるので倣います。 -
XTmrCtr_Start()
timer0のみで問題ないです。cascade modeのときはtimer1の設定に使うレジスタは無効化されます。
テストで作成した.cは末尾に添付します。
参考
おまけ 実行の様子
32-bit modeでは以下のように42秒を過ぎたところでロールオーバーが発生します。

以上の設定で64-bit modeを有効化すると以下のように40秒以降も正常に計測が可能です。$2^{64}/100M = 42949672.96...$と4000万秒ほど、71万分ほど計測可能です。

また、動作確認のためにsleep()を使うと挙動がおかしくなるので使用は避けましょう。
おまけ2 .cの関数解説
u64 XTmrCtr_GetTime64(XTmrCtr *Tmr){
u32 high1, low, high2;
do {
high1 = XTmrCtr_GetValue(Tmr, TIMER_CNTR_1);
low = XTmrCtr_GetValue(Tmr, TIMER_CNTR_0);
high2 = XTmrCtr_GetValue(Tmr, TIMER_CNTR_1);
} while (high1 != high2);
return (((u64)high1 << 32) | low);
}
AXI Timer内部のtimer0, timerから返ってくる値は32bitです。これを64bitの値として扱うのは.c側なので適切にまとめ上げる必要があります。
また、32bitカウンタ1つのみの状況とは異なり、timer0とtimer1から同時に値を持ってこれないのでtimer0のロールオーバーを考慮する必要があります。timer1から値を取り、timer0から値を取り、これらをつなげて64bit値完成!としてはいけません。timer1から値をとった瞬間にtimer0が0xffffffffから0x00000000に戻っている可能性があり、このときtimer0から0x00000000を貰った瞬間にはtimer1の値は先程より1大きくなっています。このように素直に扱うと値が信用ならないので、以上のようにループを回します。timer0のロールオーバー = 42秒の境目を跨がずに値を取得できたらいいので、timer1から同じ値が連続して回収出来るまでループを回します。ほとんど一発で通りますが、重要な処理です。
double XTmrCtr_GetElapsedTime(u64 delta_clocks){
return delta_clocks / 100000000.0;
}
void XTmrCtr_ShowElapsedTime(double time){
u32 whole = time;
u32 thousandths = (time - whole) * 1000;
xil_printf("%u.%03u sec\n\r", whole, thousandths);
return;
}
経過クロック数から秒数に変換します。私の環境は100MHzで動作しているのでクロック数を100Mで除すると秒数が得られます。xil_printf()に浮動小数点型を渡すことはできないので、小数点の前後でそれぞれ整数に分けて渡す必要があります。
おまけ3 .c全体
timer64bit_example.c
#include "xtmrctr.h"
#include "xil_exception.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xinterrupt_wrap.h"
#include <xil_types.h>
#define XTMRCTR_BASEADDRESS XPAR_XTMRCTR_0_BASEADDR
#define TIMER_CNTR_0 0
#define TIMER_CNTR_1 1
#define RESET_VALUE 0x00000000
XTmrCtr TimerCounterInst; /* The instance of the Timer Counter */
u64 XTmrCtr_GetTime64(XTmrCtr *Tmr){
u32 high1, low, high2;
do {
high1 = XTmrCtr_GetValue(Tmr, TIMER_CNTR_1);
low = XTmrCtr_GetValue(Tmr, TIMER_CNTR_0);
high2 = XTmrCtr_GetValue(Tmr, TIMER_CNTR_1);
} while (high1 != high2);
return (((u64)high1 << 32) | low);
}
double XTmrCtr_GetElapsedTime(u64 delta_clocks){
return delta_clocks / 100000000.0;
}
void XTmrCtr_ShowElapsedTime(double time){
u32 whole = time;
u32 thousandths = (time - whole) * 1000;
xil_printf("%u.%03u sec\n\r", whole, thousandths);
return;
}
int main(void){
int Status;
Status = XTmrCtr_Initialize(&TimerCounterInst, XTMRCTR_BASEADDRESS);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
XTmrCtr_SetResetValue(&TimerCounterInst, TIMER_CNTR_0, RESET_VALUE);
XTmrCtr_SetResetValue(&TimerCounterInst, TIMER_CNTR_1, RESET_VALUE);
XTmrCtr_SetOptions(&TimerCounterInst, TIMER_CNTR_0, XTC_CASCADE_MODE_OPTION);
XTmrCtr_Reset(&TimerCounterInst, TIMER_CNTR_0);
XTmrCtr_Reset(&TimerCounterInst, TIMER_CNTR_1);
XTmrCtr_Start(&TimerCounterInst, TIMER_CNTR_0);
u64 tStart = XTmrCtr_GetTime64(&TimerCounterInst);
for(int i=0; i<10; i++){
for(volatile int j=0; j<(50000000 + 100000*i); j++); // do not use sleep() here !!!!!!
u64 tEnd = XTmrCtr_GetTime64(&TimerCounterInst);
XTmrCtr_ShowElapsedTime(XTmrCtr_GetElapsedTime(tEnd - tStart));
}
}
