FPGA 版 FM 音源 (13) -- YMF262 測定 (5)

WS = 0、つまり、普通の正弦波を発生する状態の波形発生部の概念的な回路構成を下の図に示します。
「サイン波 ROM」として 1/4 周期、つまり「アドレス入力」が 8 ビット (256 エントリ) のものを使い、ビット 8 の値でアドレス入力を反転することで 1/2 周期分の働きをさせています。

波形発生部へは、フェーズ・ジェネレータの 19 ビット・アキュムレータの上位 10 ビットを入力します。
符号ビットは、入力をそのまま出力し、最終的には lb - リニア値変換部で負の数に変換します。
このため、たとえば WS = 2 の「全波整流」のような波形の場合には、符号出力を「ゼロ」に固定するだけで、波形の前半も後半も正の方向に振れる波形を簡単に発生できます。
また、ROM 出力の最上位ビット (-48 dB) が「1」となるのは、アドレス入力が全ビット「0」の場合だけなので、ROM 出力は 11 ビット幅とし、-48 dB のビットはロジック・ゲートで生成して ROM 容量を節約することもできます。
上の回路の構成と、前回の lb - リニア値変換部を組み合わせて、フルスケール正弦波を出力する C プログラムを次に示します。

// OPL3 sin (WS=0) simulator
// 2011/01/11

#include <stdio.h>
#include <math.h>

int sin_tab[1024];
int lb2lin_tab[256];

//
// lb-to-lin conversion with sign
//
int lb2lin(int sign, int lb)
{
  int lin, sft;
  sft = (lb / 256); // integer  part of lb value
  lb  = (lb % 256); // fraction part of lb value
// force 0 if too small 
  lin = ( (15 < sft) ? 0 : (lb2lin_tab[lb] >> sft) ); 
  return( sign ? (~lin) : lin ); // 1's complementer
} // int lb2lin()

//
// generate lb-to-lin table for 8 bit fraction part of lb
//
void gen_lb2lin_tab( void )
{
  int i;
  double v;
  for (i = 0; i < 256; i++) { // for 8 bit fraction
//  (2 ** (-x)) = exp(-log(2) * x)  
    v = 2048 * exp(-log(2) * (i + 1) / 256.0);
// rounding and shift left 1 bit
    lb2lin_tab[i] = (int)(v + 0.5) * 2; 
  } // for (i = 0; ...
} // void gen_lb2lin_tab()

//
// generate sine table
//
void gen_sin_tab( void )
{
  int i;
  double phi;
  for (i = 0; i < 512; i++) { // for 8 bit fraction
    phi = M_PI * (2*i + 1) / (2.0 * 512);
    sin_tab[i] = -256 * log(sin(phi))/log(2) + 0.5; 
  } // for (i = 0; ...
} // void gen_sin_tab()

void main( void )
{
  int i, s, d, k, xor_mask, op_out;
  gen_lb2lin_tab();
  gen_sin_tab();
  for (i = 0; i < 1024; i++) {
    s = 0x01 & (i >> 9); // extract sign bit (b9)
    d = 0x01 & (i >> 8); // extract direction bit (b8)
    k = 0xff & i;        // extract lower 8 bits for index
    xor_mask  = -d;      // compute ex-or mask bit pattern
    xor_mask &= 0xff;    // for sine table indexing
// sine table look up and convert to linear value    
    op_out = lb2lin(s, sin_tab[k ^ xor_mask]); 
    printf("%6d %6d %d %d %3d 0x%.2x %3d\r\xa", i, op_out, 
                     s, d,  k, xor_mask, (k ^ xor_mask) ); 
  } // for (i = 0; ...
} // void main()

このプログラム出力と、実際に YMF262 の出力をキャプチャした結果とは、サイン波の 1 周期 1024 サンプルすべてで一致することを確認しました。
ちなみに、オペレータ出力を完全にゼロに絞ることは不可能なので、2 オペレータ・モードでも、純粋に 1 つのオペレータ出力だけを測定することはできません。
そのため、「並列アルゴリズム」を選び、ふたつのオペレータの設定を全く同一にして、単純に 1 オペレータ出力の 2 倍が出力される状態でキャプチャしています。
上のプログラムの「op_out」変数は 1 オペレータ分なので、2 倍した値をプリントするように変更してキャプチャ結果と比較しています。