サイクルタイムを測定しよう (8) [PSoC]
これまで、実行サイクル時間を自身のタイマで測定してきました。 この方法を使うと、測定結果を出力するためのインターフェイスが必要になります。 今回は、外部の周波数カウンタを使って、純粋にループの周期を測定します。
実験回路
ループの周期を測定するためには、ループ内に外部信号を出力する命令を入れて、この外部信号の周期を測定してループの周期とします。 外部信号は、 Control Register の Pulse 出力を使用しており、レジスタへの書込み操作だけでパルスを発生させることができます。 このパルスは高速であるため、人間の目では直接観測できません。 そこで、 1000 サイクル周期の PWM コンポーネントを二つ使って百万分の一分周器を構成し、 LED を点滅させて人間用のモニタとしています。
メインループ
メインループは、以下のようになっています。 main() 関数では、メインループの体裁を残していますが、実際には、このループは使用しません。 コンポーネントの初期化の後、 func9() 関数を呼び出すと、 func9() 内の無限ループに入り、 main() には戻ってきません。
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"命令を挿入し、プログラムの配置位置を制御しています。 文 "*reg = val;" で Control Register を叩き、パルスを発生させます。 Control Register のアドレスと書き込むべき値は、この関数への引数としてレジスタを介して渡されます。 これで、 Control Register を叩くときに余計なコストがかからなくなります。
void func9(reg8 *reg, uint8 val) __attribute__((aligned(32))); void func9(reg8 *reg, uint8 val) { __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_1:\n" ); 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:" ); *reg = val; } }
"nop" を全く入れない場合には、以下のように2命令のループになりました。 すべての命令がプリフェッチバッファに入るため、純粋な命令実行時間が見えます。
35 .L2: 29:.\main.c **** for (;;) { 43:.\main.c **** *reg = val; 42 0000 0170 strb r1, [r0] 44:.\main.c **** } 44 0002 FDE7 b .L2
測定結果
測定結果は、以下のようになりました。 それぞれの数値は、 "nop" によるサイクル数を減じた上記の2命令の正味実行時間を示しています。 "nop" を全く入れない場合の所要サイクル数は、8サイクルでした。
分岐先 | ||||||||||||
00 | 02 | 04 | 06 | 08 | 0A | 0C | 0E | 10 | 12 | 14 | ||
分岐命令 | 02 | 8 | ||||||||||
04 | 8 | 8 | ||||||||||
06 | 8 | 8 | 8 | |||||||||
08 | 8 | 8 | 8 | 8 | ||||||||
0A | 8 | 8 | 8 | 9 | 8 | |||||||
0C | 8 | 8 | 8 | 9 | 8 | 8 | ||||||
0E | 8 | 8 | 8 | 9 | 8 | 8 | 8 | |||||
10 | 8 | 8 | 8 | 9 | 8 | 8 | 8 | 8 | ||||
12 | 8 | 8 | 8 | 9 | 8 | 8 | 8 | 9 | 8 | |||
14 | 8 | 8 | 8 | 9 | 8 | 8 | 8 | 9 | 8 | 8 | ||
16 | 8 | 8 | 8 | 9 | 8 | 8 | 8 | 9 | 8 | 8 | 8 | |
18 | 8 | 8 | 9 | 8 | 8 | 8 | 9 | 8 | 8 | 8 | ||
1A | 8 | 9 | 8 | 8 | 8 | 9 | 8 | 8 | 8 | |||
1C | 9 | 8 | 8 | 8 | 9 | 8 | 8 | 8 | ||||
1E | 8 | 8 | 8 | 9 | 8 | 8 | 8 | |||||
20 | 8 | 8 | 9 | 8 | 8 | 8 | ||||||
22 | 8 | 9 | 8 | 8 | 8 | |||||||
24 | 9 | 8 | 8 | 8 | ||||||||
26 | 8 | 8 | 8 | |||||||||
28 | 8 | 8 | ||||||||||
2A | 8 |
分岐先が近い場合には、プリフェッチの影響が見えません。 それ以外では分岐先アドレスの下3ビットが 110 になっている場合にサイクル数が1サイクルだけ伸びています。 分岐先のアドレスを気にしたプログラムを作成すると、サイクル数の節約が出来るようです。 普通は、しないけどね。
プロジェクトアーカイブ
この記事で作成したプロジェクトは、このファイルの拡張子を "zip" に変更すると再現できるようになります。
コメント 0