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

armcc のエンベデッド・アセンブリ関数で書いた「アキュムレータ機能」付きのスロット計算関数「acc_calc_slot()」の Cortex-M3 版のリストを下に示します。

#include "slot.h"

#define DEF_OFS(x) _##x equ __cpp(offsetof(op_prop_t, x))

//
// armcc embedded assembly language function
//
__asm int32_t acc_calc_slot(op_prop_t *o_p, int num_slot)
{ 
; define offset of op_prop_t struct members
DEF_OFS(ph_inc)
DEF_OFS(ph_acc)
DEF_OFS(mod_in)
DEF_OFS(stab_p)
DEF_OFS(ind_mask)
DEF_OFS(op_out)
DEF_OFS(ol_lin)
DEF_OFS(mod_mul)
DEF_OFS(L_vol)
DEF_OFS(R_vol)

; op_prop_t struct size
S       equ     __cpp(sizeof(op_prop_t))
    
; phase accum. shift amount 
acc_sft equ     __cpp(PH_ACC_FRAC_BITS-1)

; L/R volume shift amount
vol_sft equ     __cpp(LR_VOL_SHIFT)
;
; function entry point
;
    align   16
    push    {r4, r5, r6, r7, r8, r9, lr} ; save registers
    mov     r8,#0               ; clear r8 = acc_R
    mov     r9,r8               ; clear r9 = acc_L
loop
;
; series FM modulator slot
;
    ldr     r2,[r0,#_ph_inc]    ; r2 = ph_inc
    ldr     r3,[r0,#_ph_acc]    ; r3 = ph_acc
    ldr     r4,[r0,#_mod_in]    ; r4 = mod_in
    ldr     r5,[r0,#_stab_p]    ; r5 = stab_p
    adds    r2, r2,r3           ; r2 = ph_inc + ph_acc
    ldrh    r3,[r0,#_ind_mask]  ; r3 = ind_mask
    str     r2,[r0,#_ph_acc]    ; update ph_acc
    adds    r4, r4,r2           ; r4 = ph_inc + ph_acc + mod_in (= phh)
    and     r3, r4,lsr #acc_sft ; r3 & (r4 >> 8)
    ldr     r2,[r0,#_ol_lin]    ; r2 = ol_lin
    ldrsh   r3,[r5,r3]          ; r3 = *(int16_t *)(stab_p + r3)
;
; 2-tap FIR filter for feedback
;
    ldrsh   r5,[r0,#_op_out]    ; r5 = op_out (= prev_out)
    muls    r2, r3,r2           ; r2 = r3 * ol_lin
    asrs    r4, r2,#15          ; r4 = (r2 >> 15) (= out)
    ldrh    r3,[r0,#_mod_mul]   ; r3 = mod_mul
    strh    r4,[r0,#_op_out]    ; op_out = out
    adds    r5, r5,r4           ; r5 = out + prev_out
    muls    r3, r5,r3           ; r3 = r5 * mod_mul
    str     r3,[r0,#_mod_in]    ; mod_in = r3
;
;  series FM carrier slot
;
    ldr     r2,[r0,#_ph_inc+S]  ; r2 = ph_inc
    ldr     r3,[r0,#_ph_acc+S]  ; r3 = ph_acc
    ldrh    r5,[r0,#_mod_mul+S] ; r5 = mod_mul
    adds    r2, r2,r3           ; r2 = ph_inc + ph_acc
    str     r2,[r0,#_ph_acc+S]  ; update ph_acc
    mla     r2, r4, r5, r2      ; r4 = (r4 * r5) + r2
    ldrh    r3,[r0,#_ind_mask+S] ; r3 = ind_mask
    ldr     r5,[r0,#_stab_p+S]  ; r5 = stab_p
    and     r3, r2,lsr #acc_sft ; r3 & (r2 >> 8)
    ldr     r2,[r0,#_ol_lin+S]  ; r2 = ol_lin
    ldrsh   r3,[r5,r3]          ; r3 = *(int16_t *)(stab_p + r3)
    muls    r2, r3,r2           ; r2 = r3 * ol_lin
    asrs    r2, r2,#15          ; r2 >>= 15 (= out)
    ldrh    r6,[r0,#_L_vol+S]   ; r6 = L_vol1
    ldrh    r7,[r0,#_L_vol+2+S] ; r7 = L_vol0
    ldrh    r3,[r0,#_R_vol+S]   ; r3 = R_vol1
    ldrh    r5,[r0,#_R_vol+2+S] ; r5 = R_vol0
    strh    r2,[r0,#_op_out+S]  ; op_out = out
    mla     r8, r2, r3, r8      ; acc_R += (op_out1*R_vol1)
    mla     r8, r4, r5, r8      ; acc_R += (op_out0*R_vol0)
    mla     r9, r2, r6, r9      ; acc_L += (op_out1*L_vol1)
    mla     r9, r4, r7, r9      ; acc_L += (op_out0*L_vol0)
    adds    r0, r0,#(2*S)       ; advance to next slot pair
    subs    r1, r1,#2           ; decrement loop counter
    bgt     loop                ; more to do
; post scaling and saturate to 16 bit
    ssat    r1, #16, r9, asr #vol_sft ; L-ch
    ssat    r0, #16, r8, asr #vol_sft ; R-ch
    bfi     r0, r1,#16,#16      ; result[31:16] = acc_L[15:0]
    pop     {r4, r5, r6, r7, r8, r9, pc}        ; return
} // __asm void calc_slot()

これは、まず、もとの calc_slot() 関数のループ内でオペレータ 0 の出力を R4 に残して、スロット 1 の計算が終了して結果が得られるまで保持するように途中のレジスタ割り付けを修正しました。
この段階では追加の命令/計算サイクル数の増加はありません。 
その後、SIMD (Single Instruction Multiple Data) 命令のない Cortex-M3 で、Cortex-M4 での SIMD 計算を「バラ」す形で以下の命令を追加しました。

  • ldrh   r6,[r0,#_L_vol+S] — 2 cycle
  • ldrh   r7,[r0,#_L_vol+2+S] — 1 cycle
  • ldrh   r3,[r0,#_R_vol+S] — 1 cycle
  • ldrh   r5,[r0,#_R_vol+2+S] — 1 cycle
  • mla   r8, r2, r3, r8 — 2 cycle
  • mla   r8, r4, r5, r8 — 2 cycle
  • mla   r9, r2, r6, r9 — 2 cycle
  • mla   r9, r4, r7, r9 — 2 cycle

追加された 8 個の命令によるループ 1 回 (スロット 2 個) 当たりの追加サイクル数は「13」となります。 実測値でも 13 サイクルの増加でした。
(STM32VL-Discovery (STM32F100RB, 24 MHz, flash latency=0), NSLOT=128 の条件で、アキュムレータ処理なしで 47 サイクル、アキュムレータ処理ありで 60 サイクル)
最初の 4 つの ldr 命令は、サイクル数を削減するために連続して置いています。
アキュムレーションする段階で、R0 〜 R7 まで使ってしまうので、R_ch/L_ch のアキュムレータとしては R8/R9 を割り当てています。
R8/R9 は 16 ビット Thumb 命令の演算命令では直接アクセスできませんが、MLA 命令は 32 ビット Thumb2 命令であり、レジスタ指定ビット数が 4 ビットなので、R8/R9 でも問題なくアクセスできます。
ただし、Cortex-M3 では MLA 命令の実行に 2 サイクルかかってしまいます。 (Cortex-M4 では 1 サイクル)