ソフト S/PDIF トランスミッタ (10)

今回はオーディオ・データの BMC エンコード・プログラムの本体についてです。
DMA 機能を持つ Cortex-M0 プロセッサとして、手持ちの Nuvoton NUC120LE3AN についてもプログラムを作ってみたところ、Cortex-M3/M4 用と同じプログラム記述では効率がよくなかったので、プログラムの一部を M0 用と M3/M4 用とで違う記述にしてあります。
ちなみに (DDA によるサイン波発生部も含んで) CPU 負荷率は 84 MHz クロックの STM32F401 で 6.2 %、50 MHz クロックの NUC120LE3AN で 20.3 % でした。
第一引数に 16ビット・オーディオ・データの配列を取り、第二引数にサブフレーム数を取る関数
spdif_encode_subframe_data()
のリストを下に示します。

// encode audio data for specified number of subframe(s)
uint16_t spdif_encode_subframe_data(int16_t dat[], uint16_t nsf)
{
  int i;
  uint16_t bmc_L, bmc_H, aux; // bitmap of biphase mark code
  const uint32_t BMC_LSB_MASK = (1U << 15);
  register volatile uint16_t *p; // TX buffer pointer
  
  p = spdif_txind.ptr; // get S/PDIF TX buffer pointer
  for (i = 0; i < nsf; i++) { // for subframe(s)
// encode LSByte (d[0:7])  of audio data
    bmc_L  = bmc8b[(uint8_t) dat[i]];       
// encode MSByte (d[8:15]) of audio data
    bmc_H  = bmc8b[(uint8_t)(dat[i] >> 8)]; 
#if (__CORTEX_M >= 0x03) // Cortex-M3/M4 processor ?
// invert bmc of d[0:7] if (0 == bmc_H[15])
    bmc_L ^= (~((int16_t)bmc_H) >> 16);   
// gimmick for Cortex-M3/M4 processor (ARMv7-M arch) to avoid conditional
    aux    = (BMC_1000_0000 ^ (((uint32_t)((int16_t)bmc_L)) >> 17));
#else // Cortex-M0 processor (ARMv6-M arch)
// invert bmc of d[0:7] if (0 == bmc_H[15])
    bmc_L ^= ((bmc_H >> 15) + 0xFFFFU);   
// straightforward implementation for Cortex-M0
    if (BMC_LSB_MASK & bmc_L) { // detect "1 start" 
      aux = BMC_0000_0000;      // no need to adjust parity
    } else {
      aux = BMC_1000_0000;      // adjust parity
    } // if (BMC_LSB_MASK & bmc_L) { ...
#endif      
// SPDIF TX buffer update for this subframe data  
// VUCP (4 bit) and preamble (4 bit) not changed (pointed by *(p))
// AUX (4 bit) + audio ext (4 bit) for parity correction
    *(p + 1)  = aux;   
    *(p + 2)  = bmc_L; // audio data d[0:7]  (8 bit)
    *(p + 3)  = bmc_H; // audio data d[8:15] (8 bit)
    p        += 4;     // advance TX buffer pointer by 4
  } // for (i = 0; 
// advance S/PDIF TX buffer pointer
  spdif_txind.ptr   = p;         
// 4 halfword (64 bit) used per subframe
  spdif_txind.room -= (4 * nsf); 
  return(spdif_txind.room);
} // uint16_t spdif_encode_subframe_data()

2 ch ステレオ・データは 2 サブフレーム分を占めますから、ステレオ・データひとつ当たり 2 サブフレームを指定することになります。
出力側との同期のために、下のような構造体を使用しています。 詳しい説明は後の記事に回すことにして、ここでは簡単に述べます。

// S/PDIF TX buffer index structure
typedef struct tag_spdif_txind_t {
// room for ouput data (count in terms of halfword)
  volatile uint16_t room; 
// S/PDIF TX buffer pointer
  volatile uint16_t *ptr; 
} spdif_txind_t;

static spdif_txind_t spdif_txind;

「spdif_txind.room」の値がゼロのときは出力バッファに「空き」がなく、データ発生側が待つ必要があることを示しています。
「spdif_txind.room」の値がゼロでないときは出力バッファに「空き」があり、その値はハーフワード (16 ビット・ワード)単位で数えた空きの要素数になっています。
「spdif_txind.room」の値は spdif_encode_subframe_data() 関数を呼び出す側ですでにチェック済みとして、関数の中ではチェックしていません。
「spdif_txind.ptr」は出力バッファの中の (空いていて) 書き込み可能なメモリへのポインタです。
関数の最後の部分で、「spdif_txind.room」の値と「spdif_txind.ptr」の値が更新されます。
前回の記事で述べたように、オーディオ・データの MS バイト側の符号化パターン (変数 bmc_H に格納される) は「0 終わり」パターンなので、変換テーブル bmc8b[] から読み出した後に値をいじる必要はありません。
オーディオ・データの LS バイト側の符号化パターン (変数 bmc_L に格納される) については bmc_H の b15 の値により論理反転が必要になりますが、ちょっと技巧的な方法で実現しています。
パリティを調整するパターン (変数 aux に格納される) については M3/M4 用についてはさらに技巧的な方法、M0 に対しては素直な条件分岐で実現しています。
Cortex-M3/M4 用のプログラムを逆アセンブルし、コメントを追加したリストを下に示します。
Keil/ARM MDK-ARM Lite V4.53.0.0 コンパイラを使い、最適化オプションは「-O2」を指定してあります。

08002518 <spdif_encode_subframe_data>:
 8002518: b5f0        push    {r4, r5, r6, r7, lr}
 800251a: f8df c058   ldr.w   ip, [pc, #88]  ; 8002574
 800251e: 2300        movs    r3, #0
 8002520: f8df e054   ldr.w   lr, [pc, #84]  ; 8002578
 8002524: f24b 3633   movw    r6, #45875     ; 0xb333
 8002528: f8dc 2004   ldr.w   r2, [ip, #4]
 800252c: e014        b.n     8002558
 ;
 ; r0 = &(dat[0]), r1 = nsf,   r2 = p,       r3 = i,
 ; r4 = bmc_L,     r5 = bmc_H, r6 = #0xb333, r7 = aux,
 ; r12(ip) = &spdif_txind.room, r14(lr) = &(bmc8b[0])
 ;
 ; (note: r4,r5,r6,r7 used for misc. purpose)
 ;
 800252e: f830 5013   ldrh.w  r5, [r0, r3, lsl #1]
 8002532: b2ec        uxtb    r4, r5
 8002534: f3c5 2507   ubfx    r5, r5, #8, #8
 8002538: f83e 4014   ldrh.w  r4, [lr, r4, lsl #1]
 800253c: f83e 5015   ldrh.w  r5, [lr, r5, lsl #1]
 8002540: b22f        sxth    r7, r5
 8002542: 43ff        mvns    r7, r7
 8002544: ea84 4427   eor.w   r4, r4, r7, asr #16
 8002548: b227        sxth    r7, r4
 800254a: ea86 4757   eor.w   r7, r6, r7, lsr #17
 800254e: 8057        strh    r7, [r2, #2]
 8002550: 8094        strh    r4, [r2, #4]
 8002552: 80d5        strh    r5, [r2, #6]
 8002554: 3208        adds    r2, #8
 
 8002556: 1c5b        adds    r3, r3, #1
 8002558: 428b        cmp     r3, r1
 800255a: dbe8        blt.n   800252e
 800255c: f8cc 2004   str.w   r2, [ip, #4]
 8002560: f8bc 0000   ldrh.w  r0, [ip]
 8002564: 4249        negs    r1, r1
 8002566: eb00 0081   add.w   r0, r0, r1, lsl #2
 800256a: f8ac 0000   strh.w  r0, [ip]
 800256e: f8bc 0000   ldrh.w  r0, [ip]
 8002572: bdf0        pop     {r4, r5, r6, r7, pc}
 
 8002574: 20000000    .word   0x20000000
 8002578: 20000c9c    .word   0x20000c9c

0x800252e から 0x8002554 までが i に関する for ループの内側の部分で、bmc_L に対する補正は

 8002540: b22f        sxth    r7, r5
 8002542: 43ff        mvns    r7, r7
 8002544: ea84 4427   eor.w   r4, r4, r7, asr #16

の 3 命令、aux に対する補正は

 8002548: b227        sxth    r7, r4
 800254a: ea86 4757   eor.w   r7, r6, r7, lsr #17

の 2 命令にコンパイルされています。
Cortex-M0 用のプログラムを逆アセンブルし、コメントを追加したリストを下に示します。
GCC 4.5.2 コンパイラを使い、最適化オプションは「-Os」を指定してあります。

00000570 <spdif_encode_subframe_data>:
     570: b5f0        push   {r4, r5, r6, r7, lr}
     572: 4b13        ldr    r3, [pc, #76]  (5c0)
     574: 685c        ldr    r4, [r3, #4]
     576: 2300        movs   r3, #0
     578: 1c22        adds   r2, r4, #0
     57a: e015        b.n    5a8 
;
; r0 = &(dat[0]),  r1 = nsf,                 r2 = p,     
; r3 = i,          r4 = &(spdif_txind.room), r5 = bmc_H, 
; r6 = bmc_L,      r7 = aux
;
; (note: r5,r6,r7 used for misc. purpose)
; 
     
     57c: 8807        ldrh   r7, [r0, #0]
     57e: 4d11        ldr    r5, [pc, #68]  (5c4)
     580: b2fe        uxtb   r6, r7
     582: 0a3f        lsrs   r7, r7, #8
     584: 007f        lsls   r7, r7, #1
     586: 0076        lsls   r6, r6, #1
     588: 5b76        ldrh   r6, [r6, r5]
     58a: 5b7d        ldrh   r5, [r7, r5]
     58c: 0bef        lsrs   r7, r5, #15
     58e: 3f01        subs   r7, #1
     590: 407e        eors   r6, r7
     592: 0437        lsls   r7, r6, #16
     594: d501        bpl.n  59a
     596: 4f0c        ldr    r7, [pc, #48]  (5c8)
     598: e000        b.n    59c
     59a: 4f0c        ldr    r7, [pc, #48]  (5cc)
     59c: 8017        strh   r7, [r2, #0]
     59e: 3301        adds   r3, #1
     5a0: 80d6        strh   r6, [r2, #6]
     5a2: 3002        adds   r0, #2
     5a4: 8095        strh   r5, [r2, #4]
     5a6: 3208        adds   r2, #8
     
     5a8: 428b        cmp    r3, r1
     5aa: d1e7        bne.n  57c
     5ac: 4a04        ldr    r2, [pc, #16]  (5c0)
     5ae: 0099        lsls   r1, r3, #2
     5b0: 8810        ldrh   r0, [r2, #0]
     5b2: 00db        lsls   r3, r3, #3
     5b4: 1a41        subs   r1, r0, r1
     5b6: 8011        strh   r1, [r2, #0]
     5b8: 18e4        adds   r4, r4, r3
     5ba: 8810        ldrh   r0, [r2, #0]
     5bc: 6054        str    r4, [r2, #4]
     5be: bdf0        pop    {r4, r5, r6, r7, pc}
     
     5c0: 20000078    .word  0x20000078
     5c4: 20000124    .word  0x20000124
     5c8: 0000cccc    .word  0x0000cccc
     5cc: 0000b333    .word  0x0000b333

0x57c から 0x5a6 までが i に関する for ループの内側の部分です。
主に Thumb 命令しか使えない ARMv6-M アーキテクチャの Cortex-M0 では、多くの命令で自由に使えるのは R0 〜 R7 の 8 個のレジスタに限られるという制約があり、また、ARM/Thumb2 命令にはあるシフト付きオペランドが使えないので、どうしてもステップ数が多くなります。
それでも bmc_L に対する補正は 3 命令におさまっています。

     58c: 0bef        lsrs   r7, r5, #15
     58e: 3f01        subs   r7, #1
     590: 407e        eors   r6, r7