サイクルタイムを測定しよう (9) [PSoC]
これまで、単純な分岐命令に着目して、サイクルタイムが長くなる場合と短くなる場合について調べてきました。 今回は、いわゆるサブルーチンコールのサイクルタイムについて調べてみました。
メインループ
実験には、以下のようなメインループを使用しました。 前回と同じように、 func9() 関数を呼び出したら、このメインループにはもどってきません。
int main(void) __attribute__((aligned(32))); int main(void) { CyGlobalIntEnable; /* Enable global interrupts. */ /* Place your initialization/startup code here (e.g. MyInst_Start()) */ PWM_1_Start(); PWM_2_Start(); for (;;) { /* Place your application code here. */ func9(CR1_Control_PTR, 1u); } }
被測定ループ
今回もループの周期を外部周波数カウンタで測定して、サイクルタイムを計測します。 ループ中の "nop" 命令の数を調節して、関数を呼び出す分岐命令のアドレスを変化させます。
void func9(reg8 *reg, uint8 val) __attribute__((aligned(32))); void func9(reg8 *reg, uint8 val) { for (;;) { __ASM( "nop\n" // 10 "nop\n" // 9 "nop\n" // 8 "nop\n" // 7 "nop\n" // 6 "nop\n" // 5 "nop\n" // 4 "nop\n" // 3 "nop\n" // 2 "nop\n" // 1 ".label_2:" ); func1_0(); *reg = val; } }
呼び出される関数は、この例では "func1_0()" となっていますが、飛び先を変える事で、分岐先のアドレスを変化させます。 ループの先頭アドレスは固定されていますので、ループの分岐先に起因する要因を除外する事ができます。
呼び出される関数
呼び出される関数は、以下のように "nop" 命令が並んだ構造になっていますが、これは "nop" の数を調整するためのものではありません。
void func1(void) __attribute__((aligned(32))); void func1(void) { __ASM( "func1_0: nop\n" // 8 "func1_1: nop\n" // 7 "func1_2: nop\n" // 6 "func1_3: nop\n" // 5 "func1_4: nop\n" // 4 "func1_5: nop\n" // 3 "func1_6: nop\n" // 2 "func1_7: nop\n" // 1 "func1_8:\n" // 0 ); } void func1_0(void); void func1_1(void); void func1_2(void); void func1_3(void); void func1_4(void); void func1_5(void); void func1_6(void); void func1_7(void); void func1_8(void);
func1() 関数は、独立した関数なのですが、内部に複数のラベルを定義する事で、飛び込み先の異なる複数の関数となるように細工されています。 例えば、 func1_0() を呼んだ場合はアドレス00に分岐し、 func1_4() を呼んだ場合はアドレス08に分岐します。 この部分のコンパイル結果は、以下のようになっています。
74 func1: 80 0000 C046 func1_0: nop 81 0002 C046 func1_1: nop 82 0004 C046 func1_2: nop 83 0006 C046 func1_3: nop 84 0008 C046 func1_4: nop 85 000a C046 func1_5: nop 86 000c C046 func1_6: nop 87 000e C046 func1_7: nop 88 func1_8: 94 0010 7047 bx lr
実験結果
"nop" の数を変える事で分岐命令のアドレスを、関数の名前を変える事で分岐先を、それぞれ変化させてサイクルタイムを測定しました。 それぞれのサイクルタイムからは、実行された "nop" 命令の数だけサイクルタイムを減じて、サブルーチンコールに起因する違いが見えるようにしています。
分岐先 | ||||||||||
00 | 02 | 04 | 06 | 08 | 0A | 0C | 0E | 10 | ||
分岐命令 | 06 | 19 | 19 | 20 | 21 | 19 | 19 | 20 | 21 | 16 |
08 | 19 | 19 | 20 | 21 | 19 | 19 | 20 | 21 | 16 | |
0A | 20 | 20 | 21 | 22 | 20 | 20 | 21 | 22 | 19 | |
0C | 21 | 21 | 22 | 23 | 21 | 21 | 22 | 23 | 16 | |
0E | 21 | 21 | 22 | 23 | 21 | 21 | 22 | 23 | 16 | |
10 | 21 | 21 | 22 | 23 | 21 | 21 | 22 | 23 | 16 | |
12 | 22 | 22 | 23 | 24 | 22 | 22 | 23 | 24 | 21 | |
14 | 21 | 21 | 22 | 23 | 21 | 21 | 22 | 23 | 16 | |
16 | 21 | 21 | 22 | 23 | 21 | 21 | 22 | 23 | 16 | |
18 | 21 | 21 | 22 | 23 | 21 | 21 | 22 | 23 | 16 | |
1A | 22 | 22 | 23 | 24 | 22 | 22 | 23 | 24 | 21 |
特徴的なのは、分岐先が "10" になっているとサイクルタイムが短くなっている点です。 このアドレスには、サブルーチンから戻るための命令が配置されています。 そのため、分岐先が分岐命令であった場合の特別なルールが働いたのではないかと推測しています。
また、分岐先のアドレスに応じてサイクルタイムが変化しているのがわかります。 アドレスの下3ビットが 110 である場合にサイクルタイムが長くなるというのは、前回までに判明したサイクルタイムが分岐先アドレスに依存しているのと合致します。 サブルーチンの分岐先についても例外ではなさそうです。
一方、分岐命令のアドレスについても依存関係が見られます。 こちらはアドレスの下3ビットが 010 であった場合にサイクルタイムが長くなっています。
この原因を考えた所、サブルーチンコールから戻ってきた時のアドレスに依存しているのではないかと推測しました。 サブルーチンコールに使用される分岐命令は4バイトです。 そのため、分岐命令のアドレス下3ビットが 010 であった場合、戻りアドレスの下3ビットは 110 となり、前回までの実験結果と一致します。
つまり、ここでも分岐先のアドレスに依存してサイクルタイムが長くなるという現象として説明できます。 分岐命令のアドレスに関連してサイクルタイムが伸びる条件は、いずれの場合も分岐先アドレスの下3ビットが 110 となる場合であると説明できます。
プロジェクトアーカイブ
この記事で作成したプロジェクトは、このファイルの拡張子を "zip" に変更すると再現できるようになります。
コメント 0