新版FM音源プログラム (18)
アセンブリ言語によるスロット (オペレータ) 計算プログラムでは、ARMv6-M (Cortex-M0) の 16 ビット Thumb 命令を主に使うこととし、ARMv7-M (Cortex-M3/M4) に対しては一部を 32 ビット Thumb2 命令で記述することにより効率化しています。
以下に、その「一部分」を示します。
#if defined(__thumb2__) // ARMv7-M (Cortex-M3/M4) " ands r3, r3,r4,lsr #8 \n\t" #else // ARMv6-M (Cortex-M0) " lsrs r4, r4,#8 \n\t" " ands r3, r3,r4 \n\t" #endif " ldrsh r3,[r5,r3] \n\t"
これは、フェーズ・アキュムレータ (ph_acc) の値からシフトおよび AND によってサイン波テーブルアクセスのためのバイト・オフセットを切り出す部分です。
Thumb 命令の範囲では、シフト命令 lsrs と AND 命令 ands の 2 命令必要ですが、Thumb2 命令では ands 命令の「シフト付きオペランド」でシフト命令の役割を果たすことができます。
#if defined(__thumb2__) // ARMv7-M (Cortex-M3/M4) " ldrsh r5,[r0,%[op_out]] \n\t" #else // ARMv6-M (Cortex-M0) " ldrh r5,[r0,%[op_out]] \n\t" " sxth r5, r5 \n\t" #endif
これは、オペレータ出力 (op_out) を読み出している部分ですが、Thumb 命令には符号付きハーフワード (int16_t) 型のデータを 32 ビットに符号拡張して、定数オフセット・アドレッシングで読み出す ldrsh 命令がないため、まず、1 命令目で、「ゼロ拡張」である ldrh 命令で int16_t データを読み出し、2 命令目の sxth で 16 ビットデータを 32 ビットに符号拡張しています。
Thumb2 命令には定数オフセットの ldrsh 命令があるので、(命令長は 32 ビットになりますが) それを使います。
ここは、「レジスタ・オフセット形式」の ldrsh 命令は Thumb にもあるので、
" movs r5,%[op_out] \n\t" " ldrsh r5,[r0,r5] \n\t"
としても (レジスタを 1 個使ってしまいますが) 実現できます。
#if defined(__thumb2__) // ARMv7-M (Cortex-M3/M4) " mla r4, r4, r5, r2 \n\t" #else // ARMv6-M (Cortex-M0) " muls r4, r5,r4 \n\t" " adds r4, r4,r2 \n\t" #endif
これはキャリア・オペレータのフェーズ・アキュムレータの計算で、モジュレータ側からのモジュレーションを掛けている部分です。
r2 には (ph_acc + ph_inc) の値、r4 にはモジュレータの op_out の値、R5 にはモジュレーションありかなしかを決める mod_mul の値が入っています。
r4 = (r4 * r5) + r2 = (op_out * mod_mul) + (ph_acc + ph_inc)
を計算しています。
Thumb 命令では「積和命令」がないので、乗算の muls 命令と、加算の adds 命令の 2 命令で実現しています。
Thumb2 命令では「Multiply Accumulate 」命令ひとつ
mla rd, rn, rm, ra
で
rd = (rn * rm) + ra
という計算が実行できます。
Cortex-M3 の場合には、mla 命令の実行サイクル数は 2 で、Thumb 命令の muls / adds の 2 命令バージョンと変わりありませんが、Cortex-M4 の場合には実行サイクル数 1 となって、少し得をします。