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

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

#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-r7, lr}         ; save registers
    movs    r6,#0               ; clear r6 = acc_R
    mov     r7,r6               ; clear r7 = 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)
    lsrs    r4, r4,#acc_sft     ; r4 >> 8
    ands    r3, r3,r4           ; r3 & r4
    ldr     r2,[r0,#_ol_lin]    ; r2 = ol_lin
    ldrsh   r3,[r5,r3]          ; r3 = *(int16_t *)(stab_p+r3)
;
; 2-tap FIR filter for feedback
;
    ldrh    r5,[r0,#_op_out]    ; r5 = op_out (= prev_out)
    sxth    r5, r5              ; sign extend
    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
    muls    r5, r4,r5           ; r4 = out * mod_mul
    adds    r2, r5,r2           ; r2 = ph_inc+ph_acc+mod (= phh)
    ldrh    r3,[r0,#_ind_mask+S] ; r3 = ind_mask
    ldr     r5,[r0,#_stab_p+S]  ; r5 = stab_p
    lsrs    r2, r2,#acc_sft     ; r2 >> 8
    ands    r3, r2,r3           ; r3 & r2
    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)
    strh    r2,[r0,#_op_out+S]  ; op_out = out
;
; accumulator
;
; r0 = *op_prop, r1 = num_slot
; r2 = op_out1,  r4 = op_out0
; r6 = acc_R,    r7 = acc_L
; r3, r5 = free
;
    ldr     r3,[r0,#_R_vol+S]   ; r3 = R_vol0:R_vol1
    lsrs    r5, r3, #16         ; r5 = R_vol0
    uxth    r3, r3              ; r3 = R_vol1
    muls    r3, r2,r3           ; r3 *= op_out1
    muls    r5, r4,r5           ; r5 *= op_out0
    adds    r6, r6,r3           ; acc_R += r3
    adds    r6, r6,r5           ; acc_R += r5
    ldr     r3,[r0,#_L_vol+S]   ; r3 = L_vol0:L_vol1
    lsrs    r5, r3, #16         ; r5 = L_vol0
    uxth    r3, r3              ; r3 = L_vol1
    muls    r3, r2,r3           ; r3 *= op_out1
    muls    r5, r2,r5           ; r5 *= op_out0
    adds    r7, r7,r3           ; acc_L += r3
    adds    r7, r7,r5           ; acc_L += r5
;				
    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
;
    ldr     r3,=0x7fff       ; r3 = 0x00007fff
    mvns    r4, r3           ; r4 = 0xffff8000
;                            ; saturate acc_R
    asrs    r6, r6, #vol_sft ; acc_R post scaling
    bmi     neg_r            ; branch if negative
    cmp     r6, r3           ; compare to 0x00007fff
    blt     sat_l            ; no pos overflow
    movs    r6, r3           ; r6 = 0x00007fff
    b       sat_l
;		
neg_r
    cmp     r6, r4           ; compare to 0xffff8000
    bge     sat_l            ; no neg overflow
    movs    r6, r4           ; r6 = 0xffff8000
sat_l                        ; saturate acc_L		
    asrs    r7, r7, #vol_sft ; acc_L post scaling
    bmi     neg_l            ; branch if negative
    cmp     r7, r3           ; compare to 0x00007fff
    blt     pack             ; no pos overflow
    movs    r7, r3           ; r7 = 0x00007fff
    b       pack
;		
neg_l
    cmp     r7, r4           ; compare to 0xffff8000
    bge     pack             ; no neg overflow
    movs    r7, r4           ; r7 = 0xffff8000
pack                     ; pack (acc_L:acc_R) to r0
    lsls    r7, r7, #16  ; acc_L in top halfword of r7
    uxth    r0, r6       ; acc_R in bottom halfword of r0
    orrs    r0, r0,r7    ; combine them
    pop     {r4-r7, pc}  ; return
} // __asm void calc_slot()

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

  • ldr     r3,[r0,#_R_vol+S] — 2 cycle
  • lsrs    r5, r3, #16 — 1 cycle
  • uxth   r3, r3     — 1 cycle
  • muls   r3, r2,r3 — 1 cycle
  • muls   r5, r4,r5 — 1 cycle
  • adds   r6, r6,r3 — 1 cycle
  • adds   r6, r6,r5 — 1 cycle
  • ldr     r3,[r0,#_L_vol+S] — 2 cycle
  • lsrs    r5, r3, #16 — 1 cycle
  • uxth   r3, r3     — 1 cycle
  • muls   r3, r2,r3 — 1 cycle
  • muls   r5, r2,r5 — 1 cycle
  • adds   r7, r7,r3 — 1 cycle
  • adds   r7, r7,r5 — 1 cycle

追加された 14 個の命令によるループ 1 回 (スロット 2 個) 当たりの追加サイクル数は「16」となります。 実測値でも 16 サイクルの増加でした。
(LPC1114 (Cortex-M0), 20 MHz, flash wait=0, NSLOT=64 の条件で、アキュムレータ処理なしで 63 サイクル、アキュムレータ処理ありで 79 サイクル)
「アキュムレータ」として R6 / R7 を使っています。
最初の ldr/lsrs/uxth 命令でパックされたボリューム値 (L_vol / R_vol) をロード/アンパックしています。
Cortex-M0 では、ldr 命令を連続させてもクロック数は削減されないので、データが必要な時期になってからデータをロードしています。
Cortex-M0 では積和命令がないので、個別の muls/adds 命令を組み合わせて積和演算を構成しています。
「飽和演算命令」も Cortex-M0 にはないので、「プログラム」によって -32768 〜 +32767 への「クリッピング」を行なっています。