新版FM音源プログラム (23)
armcc のエンベデッド・アセンブリ関数で書いた「アキュムレータ機能」付きのスロット計算関数「acc_calc_slot()」のリストを下に示します。 (2018 年 3 月 23 日追記: #if により各種アーキテクチャに対応させているのをやめて、Cortex-M4 専用のソースとしました)
#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, lr} ; save registers
movs r7,#0 ; clear r7 = acc_R
mov r8,r7 ; clear r8 = 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)
ldr r5,[r0,#_R_vol+S] ; r5 = R volume
ldr r6,[r0,#_L_vol+S] ; r6 = L volume
muls r2, r3,r2 ; r2 = r3 * ol_lin
asrs r2, r2,#15 ; r2 >>= 15 (= out)
bfi r2, r4,#16,#16 ; r2[31:16] = r4[15:0]
str r2,[r0,#_op_out+S] ; op_out = out
smlad r7, r2, r5, r7 ; acc_R += (op_out0*R_vol0) + (op_out1*R_vol1)
smlad r8, r2, r6, r8 ; acc_L += (op_out0*L_vol0) + (op_out1*L_vol1)
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, r8, asr #vol_sft ; L-ch
ssat r0, #16, r7, asr #vol_sft ; R-ch
; pack two 16-bit halfwords to single 32-bit word
bfi r0, r1,#16,#16 ; result[31:16] = acc_L[15:0]
pop {r4, r5, r6, r7, r8, pc} ; return
} // __asm void calc_slot()これは、まず、もとの calc_slot() 関数のループ内でオペレータ 0 の出力を R4 に残して、スロット 1 の計算が終了して結果が得られるまで保持するように途中のレジスタ割り付けを修正しました。
この段階では追加の命令/計算サイクル数の増加はありません。 その後、以下の命令を追加しました。
追加された 5 個の命令によるループ 1 回 (スロット 2 個) 当たりの追加サイクル数は「5」となります。 実測値でも 5 サイクルの増加でした。
最初の 2 つの ldr 命令は、ldr を連続させてサイクル数を削減するために、(必要なタイミングより早い時期の) スロット 1 の出力の計算のための ldr/ldrsh 命令の直後に置いています。
実際に必要な時期まで値を保持するために、新たにレジスタ R6 を割り付けています。
ふたつのハーフワード値をひとつのワード・レジスタにパッキングするために、ARMv7E-M の「PKHBT/PKHTB」(PacK Halfword Bottom Top/PacK Halfword Top Bottom) 命令ではなく、Cortex-M3 でも実行可能な「BFI」(Bit Field Insert) 命令を使っています。
オペレータの計算および L_vol/R_vol の値の保持のためレジスタ R6 までを使用しているので、R_ch/L_ch のアキュムレータとして、R7/R8 を使っています。 R8 は 16 ビット Thumb 命令の演算命令では直接アクセスできませんが、SMLAD 命令は 32 ビット Thumb2 命令であり、レジスタ指定ビット数が 4 ビットなので、R8 でも問題なくアクセスできます。