FPGA 版 FM 音源 (12) -- YMF262 測定 (4)

YMF262 のエンベローブ・ジェネレータのディケイ/リリースの減衰部分の回路構成を概念的に示すと、下の図のようになると考えられます。
アタックからの遷移や、サステイン・レベルとの比較によるディケイ→リリースの遷移の機能は考慮してありません。
ここで最も重要なのは、EG アキュムレータのビット幅が 23 ビットであるということです。

   RATE = RR * 4 + Rof
によって求めた実効 RATE 値の下位 2 ビットがアキュムレータの「増分」のビットパターンに使われ、上位 4 ビットが「バレル・シフタ」の左シフト量を決定します。
ここで、RATE[2] のような表示は、RATE 値の 2 ビット目を表すものとします。
RATE[5:2] で示されるシフト量は、RATE[5:2] = 1 でシフトなし、RATE[5:2] = 2 で 1 ビット左シフト、RATE[5:2] = 15 で 14 ビット左シフトです。
仕様上、RATE[5:2] = 0 ではエンベロープが変化しないという約束ですから、RATE[5:2] = 0 の場合にはバレル・シフタ出力をゼロに強制することにします。
回路上は、単に、アキュムレータの初期値ゼロから始めて、RATE の値に応じた傾きの lb ドメインでの「直線」を発生させるだけです。
しかし、実際の回路はこれとは違う方式のようです。
後で示す、上の回路をプログラムで実現した場合の出力と、実際の YMF262 出力を比較すると、Rof = 0 の場合には完全に一致します。
それ以外の Rof の場合は、全体のカーブとしては同じですが、サンプル単位で見ると完全には一致せず、たとえば 50 % だけ一致するような形になります。
もう少し調べてから、両者の違いなどを記事にしたいと思っています。

lb 値は、サイン波発生部では小数部のビット幅は 8 ですが、opl4 などのドキュメントにあるように、EG 出力の分解能は 0.1875 dB であり、lb 値の小数部は 5 ビット幅で出力されています。
この「lb 値」から「リニア値」への変換回路は、実験結果から、概念上は次の図のようになっていると考えられます。
右シフトのバレル・シフタの入力には、テーブル出力の 11 ビットと、その下のビットに固定の「0」が入力された形となっています。
lb 値の整数部がバレル・シフタのシフト量を決めており、ここは普通に 0 ならシフトなし、1 なら 1 ビット右シフト、15 なら 15 ビット右シフトになります。

スロット (オペレータ) 出力は 13 ビット幅 (符号 1 ビット + 12 ビット) と言ってきましたが、詳しく調べてみると、有効ビット幅は 11 ビットであることが分かりました。
出力が 0 dB 〜 -6 dB の区間は、LSB が常にゼロとなっており、上位 11 ビットにしか有効な情報が含まれていません。
したがって、lb 値からリニア値への変換テーブルは、「アドレス入力」が 8 ビット幅 (256 エントリ) で、出力ビット幅は 11 ビットであると思われます。
リニア値に変換された値がバレル・シフタ出力に得られますが、これは、まだ振幅の絶対値ですから、符号に応じて負数に変換する必要があります。
以前の記事では、2 の補数を取るものと思っていましたが、実際は図のように、符号に応じてビット反転、つまり 1 の補数を取っているようです。
この方式では、仮想的に -0.5 LSB の位置が「ゼロ」となり、実際の出力値に「ゼロ」を表すビットパターンは存在しないことになります。
最小の振幅は、ゼロではなく (DC オフセットを除外して考えると) +0.5 LSB あるいは -0.5 LSB になります。
この lb - リニア変換部およびディケイ/リリース回路を C プログラムで表現したものを下に示します。

// OPL3 EG (DR/RR) simulator
// 2011/01/10

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

int lb2lin_tab[256];

//
// 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()

//
// 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()

void main( void )
{
  int i, rr, rof, op_out, eg_acc, eg_inc;
  gen_lb2lin_tab();
  rr  = 13; // release rate for 512 samples long
  rof = 0;
  eg_inc = ( (0 == rr) ? 0 : ( (0x04 + rof) << (rr - 1) ) );
  eg_acc = 0;
  for (i = 0; i < 512; i++) {
    op_out  = lb2lin(0, (~0x07) & (eg_acc >> 11)); 
    eg_acc += eg_inc;           
    printf("%6d %6d \r\xa", i, op_out); 
  } // for (i = 0; ...
} // void main()

処理系は PC 上の cygwinx86gcc を使っています。
lb - リニア変換部において、lb が大きい場合に出力を強制ゼロとしているのは、このプログラムでは EG が -96 dB で停止しないで動き続けるために、そのままではラップアラウンドして、不適切なゼロでない値が出現するためです。
実際の回路では、EG も最大の減衰位置で停止するはずであり、サイン波出力との lb 値加算回路でも「飽和演算」されるはずなので、lb - リニア値変換部で特に考慮する必要はないはずです。
このプログラムの出力と、実際にチャプチャした YMF262 の出力とは、Rof = 0 の場合に限り、データを取った範囲においては全て一致しました。
比較したデータは RR = 11 / 13 / 14 /15 (以上 Rof = 0) です。