新版FM音源プログラム (19)

今回からは実行サイクル数の検討に入ります。
まずは、ARMv6-M アーキテクチャの Cortex-M0 である NXP LPC1114、Cypress PSoC4 から始めます。
アセンブリ言語で書いたスロット (オペレータ) 計算プログラムをコンパイルして得られたオブジェクトを逆アセンブルしたものを下に示します。
(2018 年 3 月 3 日追記: 変更後のブログラムの逆アセンブル・リストに差し替えました)

00000000 <calc_slot>:
   0:   b530            push    {r4, r5, lr}
   2:   6802            ldr     r2, [r0, #0]
   4:   6843            ldr     r3, [r0, #4]
   6:   6884            ldr     r4, [r0, #8]
   8:   68c5            ldr     r5, [r0, #12]
   a:   18d2            adds    r2, r2, r3
   c:   8a03            ldrh    r3, [r0, #16]
   e:   6042            str     r2, [r0, #4]
  10:   18a4            adds    r4, r4, r2
  12:   0a24            lsrs    r4, r4, #8
  14:   4023            ands    r3, r4
  16:   6942            ldr     r2, [r0, #20]
  18:   5eeb            ldrsh   r3, [r5, r3]
  1a:   8a45            ldrh    r5, [r0, #18]
  1c:   b22d            sxth    r5, r5
  1e:   435a            muls    r2, r3
  20:   13d4            asrs    r4, r2, #15
  22:   8b03            ldrh    r3, [r0, #24]
  24:   8244            strh    r4, [r0, #18]
  26:   192d            adds    r5, r5, r4
  28:   436b            muls    r3, r5
  2a:   6083            str     r3, [r0, #8]
  2c:   69c2            ldr     r2, [r0, #28]
  2e:   6a03            ldr     r3, [r0, #32]
  30:   8e85            ldrh    r5, [r0, #52]   ; 0x34
  32:   18d2            adds    r2, r2, r3
  34:   6202            str     r2, [r0, #32]
  36:   436c            muls    r4, r5
  38:   18a4            adds    r4, r4, r2
  3a:   8d83            ldrh    r3, [r0, #44]   ; 0x2c
  3c:   6a85            ldr     r5, [r0, #40]   ; 0x28
  3e:   0a24            lsrs    r4, r4, #8
  40:   4023            ands    r3, r4
  42:   6b02            ldr     r2, [r0, #48]   ; 0x30
  44:   5eeb            ldrsh   r3, [r5, r3]
  46:   435a            muls    r2, r3
  48:   13d2            asrs    r2, r2, #15
  4a:   85c2            strh    r2, [r0, #46]   ; 0x2e
  4c:   3038            adds    r0, #56 ; 0x38
  4e:   3902            subs    r1, #2
  50:   dcd7            bgt.n   2 <calc_slot+0x2>
  52:   bd30            pop     {r4, r5, pc}

これは PSoC4 用に gcc を使ってコンパイルした結果です。 当然ですが、16 ビット Thumb 命令のみを使用しています。
ソースはアセンブリ言語で書いてあるので、処理系によらずコンパイル (アセンブル) 結果は同じになるはずですが、ARM/Keil の armcc を使った場合とでは、「subs r1, #2」命令だけが異なります。
LPC1114 用に armcc でコンパイルした結果では下のようになります。

1be:    1e89        subs    r1, r1, #2

これは、16 ビット Thumb 命令の範囲でも、イミディエイト定数値のビット幅によって 2 つのエンコーディングが存在しているためです。
デスティネーション・レジスタとソース・レジスタが同一で、イミディエイト定数が 8 ビット幅のもの

    SUBS    <Rdn>, #<imm8>

と、デスティネーションとソースに別のレジスタを指定できるけれど、イミディエイト定数が 3 ビット幅に限られるもの

    SUBS    <Rd>, <Rn>, #<imm3>

とがあります。
「subs r1, r1, #2」と書いた場合に、「デスティネーションとソースが同じ」、「イミディエイト定数値が 3 ビット以下」という両方の条件が当てはまるため、gcc と armcc とでは解釈が分かれるようです。
スロット計算プログラムのループ 1 回あたりで、

  • ブランチ命令 (3 サイクル) — 1 個
  • ロード/ストア命令 (2 サイクル) — 21 個
  • その他の命令 (1 サイクル) — 18 個

の命令があるので、単純にサイクル数を合計すると、

3 + (2 × 21) + 18 = 63

となります。 (2018 年 3 月 3 日追記: 条件ブランチ命令の実行サイクル数を「2」と誤っていたのを「3」に訂正しました)
Cortex-M0 ではロード/ストア命令を連続させることによるサイクル数の削減効果はないので、フラッシュ・レイテンシがゼロの場合には、この計算上の「63」サイクルという値に近くなることが期待されます。
LPC1114 と PSoC4 の場合の結果を再掲します。
(2018 年 3 月 3 日追記: 変更後のプログラムでの測定結果に差し替えました)

μVision V5.21.1.0
(MDK-Lite V5.21a, armcc.exe v5.06 update 3)

cycle
per
2slot
評価ボード名 搭載チップ名 アーキテクチャ clock
[MHz]
flash
latency
64 (チップ単体) LPC1114 ARMv6-M
Cortex-M0
20 0
84 (チップ単体) LPC1114 ARMv6-M
Cortex-M0
48 2

PSoC Creator 4.0 Update 1 (gcc 4.9.3)
Inline Assembly Language で記述

cycle
per
2slot
評価ボード名 搭載チップ名 アーキテクチャ clock
[MHz]
flash
latency
64 PSoC 4200
Prototyping
Kit
CY8C4245 ARMv6-M
Cortex-M0
24 0
67 PSoC 4200
Prototyping
Kit
CY8C4245 ARMv6-M
Cortex-M0
48 1

フラッシュ・レイテンシがゼロの場合のサイクル数は LPC1114、PSoC4 ともに「64」となっています。 単純な計算値よりも 1 サイクル多くなっていますが、ほぼ同じと言えます。
LPC1114 の場合、フラッシュ・レイテンシが「2」でサイクル数が「86」と大きく悪化しています。
これには LPC1114 フラッシュ・コントローラ機構が大きく関わっています。
CPU 側からの命令フェッチ要求に応じて、フラッシュ側では、そのアドレスを含む 128 ビット幅 (16 バイト幅) のコードをバッファに読み出します。
続く命令フェッチ要求に対して、そのアドレスに対するコードがフラッシュ・バッファ上にすでに存在している場合には、新たにフラッシュを読み出しすことなく、そのバッファ内のコードを読み出します。
バッファ上にないアドレスのコードのフェッチが要求された場合には、そのアドレスを含む 128 ビット幅で新たにフラッシュから読み出し、バッファの内容を置き換えます。
これは「プリフェッチ」ではなく、フェッチ要求が生じてからフラッシュを読み込むので、レイテンシが隠蔽されることなく、レイテンシがもろに表面に現れます。
2012 年 12 月 10 日の LPC1114 を使った SD カード LPCM プレイヤーの記事 (→こちら) でこのレイテンシ (ウェイト・サイクル) の影響をとらえた波形写真を示しています。
この記事では、コアクロックを 36 MHz に選んでいるので、レイテンシは 1 で、8 命令 (16 バイト) 実行するごとにレイテンシにより GPIO を使ったソフト SPI のクロック波形が 1 クロック分延びています。
コアクロック周波数が 48 MHz の場合にはレイテンシは 2 にする必要があります。
armcc ではアセンブラ記述で「align 16」として、gcc では関数のアトリビュートとして 16 バイト・アラインを指定しているので、calc_slot() 関数は 16 バイト境界から始まります。
関数の先頭アドレスから、オフセット 0x02 〜 0x51 の範囲がループ内部となっており、その中には 6 個の 16 バイト・ブロックが含まれています。
つまり、48 MHz クロックでオフセット 0x02 〜 0x51 の範囲を 1 回実行すると、フラッシュから 6 回読み出され、ウェイト・サイクルとしては 6 × 2 = 12 サイクルが消費されることになります。
さらには、フラッシュ・メモリ上のサイン波テーブルの読み出しを 2 スロット分で 2 回実行しているので、その分の影響があります。
LPC1114 の資料には明確に書いていないので、はっきりしないのですが、フラッシュ・バッファの振る舞いがフラッシュ・メモリ上の命令 (I-Code) だけではなく、フラッシュ・メモリ上のデータ (D-Code) にも適用されると仮定します。
そうすると、フラッシュ中のサイン波テーブル・データをアクセスするために生じるウェイト・サイクル 2 × 2 = 4 サイクルと、テーブル・データを読み出したために破棄された命令コードの再読み込みのためのウェイトサイクル 2 × 2 = 4 サイクルとが加算され、合計すると 8 サイクル余分にウェイト・サイクルが消費されます。
命令フェッチの分と合算すると、12 + 8 = 20 サイクルとなり、ウェイトサイクルなしの「64」に加算すると 84 となり、実測値と一致します。
PSoC 4200 のデータシートには、「Flash Accelerator」を内蔵し、フラッシュはコアクロック 24 MHz まではウェイト・サイクル 0、コアクロック周波数 48 MHz まではウェイト・サイクル 1 で動作すると記述されていますが、それ以上の詳しい説明はありません。
PSoC 4200 の実測値では、0 ウェイトで 64 クロック、1 ウェイトで 67 クロックなので、命令コード・フェッチについては「キャッシュ」が効いて、ウェイト・タイムがほぼ隠蔽されていると思われ、サイン波テーブル・アクセス 2 回分の 2 ウェイト・サイクル程度が上乗せされているだけのように見えます。