FPGA 版 FM 音源 (65) -- FPGA 版 EG (9)

今回は対数 — リニア値変換モジュール「opl3_lb2lin()」についてです。
OPL3 では、サイン波 ROM や EG の出力は「真数」(リニア値) ではなく、 2 を底とする「対数」で表現されていて、対数値同士の加算後に対数 — リニア変換を行なうことによりハードウェア乗算器を用いることなしに乗算を実現しています。
この 2 を底とする対数値を勝手に lb 値と名づけ、リニア値への変換モジュールに「lb2lin」という名前を付けてあります。
lb 値については 2008 年 1 月 8 日の記事 (→こちら) で定義しています。
EG 出力の lb 値は、整数部 4 ビット、小数部 5 ビットの計 9 ビットですが、サイン波 ROM 出力は整数部 4 ビット、小数部 8 ビットの計 12 ビットになっています。 さらに、符号ビットがあり、

符号 1 ビット + 振幅の絶対値 12 ビット

の計 13 ビットになっています。
したがって、EG 出力の 9 ビットの下に 3 ビットのゼロを補って合計 12 ビットにしてからサイン波 ROM の出力と足し合わせる必要があります。
ここでは EG 出力と対数 — リニア変換回路は直結ですが、EG 出力に合わせてビット数を削ることはせず、フル機能の変換回路に対し EG 出力に下位のゼロを補って入力しています。
変換回路のブロック図を下に示します。

lb 値の整数部 4 ビットについては、リニア値を「x」、lb 値を「y」とすると、

   x = 2-y = 2-s · 2-f

となります。 ここで、「s」は y の整数部、「f」は y の小数部とします。
2-s は s ビットの右シフトで実現できることが分かります。 これが上の図の下側にある「バレル・シフタ」部分です。
整数部 4 ビットなので、0 〜 15 ビットのシフトになります。
2-f については簡単な回路では求められないので、「変換テーブル」を ROM として作成しておき、そのアドレスに小数部のビットを入力して求めます。
小数部 8 ビットなので ROM も 8 ビット・アドレスの 256 ワード構成になります。
ROM のデータ・ビット幅は 11 ビットですが、出力レベル 0 dB に対応する出力は 4084 で、12 ビット幅の数値となっています。
これは、出力レベル 0 dB 〜 -6 dB (整数部 = 0 でシフトなし) については LSB にゼロを補って、全ビット幅は 12 ビット、その中で有効ビット幅は 11 ビットとすることで実現しています。
ROM に書き込む値の上限と下限としては、単純に

2-0 = 1.0

2-(255/256) = 0.5014

ではなく、2-1/256 のスケーリングを行なって、

2-1/256 · 2-0 = 2-1/256 = 0.9973

2-1/256 · 2-(255/256) = 2-(256/256) = 0.5

としています。
11 ビット固定小数点での表現に直すと、

2048 · 0.9973 = 2042 = 0x7fa = 11'b111_1111_1010

2048 · 0.5 = 1024 = 0x400 = 11'b100_0000_0000

となります。 出力レベル 0 dB では、この値が 2 倍されますから、上限値は 2 · 2042 = 4084 となります。
この表現では、ROM 出力データの b10 が常に「1」となりますから、ROM データ幅を 10 ビットとして、b10 は定数の「1」に置き換えることができます。
後に示す Verilog プログラムでは、ROM データ自体は 11 ビット幅で作っておいて、回路では ROM 出力データの b10 は使用せず、固定の「1」を使っています。
lb — リニア変換回路では、最後に符号ビットから「1 の補数」方式により負の数を作り出しています。 したがって、「正のゼロ」と「負のゼロ」のふたつのゼロが存在します。
リニア値に変換後は 1 の補数であることは考慮されず、加算などでは「普通の数」、つまり 2 の補数として扱われます。
変換テーブル ROM に用いている内部 RAM ブロック「M4K」では、常にアドレス入力を D-FF でラッチする形式でしか利用できないので、lb 値の整数部 4 ビットに対しても D-FF を挿入し、バレル・シフタ部でのタイミングが揃うようにしています。
「opl3_lb2lin()」の Verilog ソースを下に示します。
"opl3_lb2lin.v"

//
// opl3_lb2lin.v : convert 1-bit sign + 12-bit log2(magnitude) number to 16-bit linear
//
// 2017/01/16 Modified for Megawizard genereted ROM
// 2013/12/02 Created by pcm1723 (for testbench simulation only)
//
module opl3_lb2lin(reset, m_clk, clk_en, 
                   sign, lb,
                   lin_out);
//
  parameter SIGNED = 1; // signed / unsigned flag
// Input port                   
  input         reset;   // reset for simulation
  input         m_clk;   // master clock
  input         clk_en;  // clock enable
  input         sign;    // sign bit (don't care if unsigned)
  input  [11:0] lb;      // log2(magnutide) data (12-bit)
// Output port  
  output [15:0] lin_out; // 1's comp. linear value (16-bit)
  
// ROM output
  wire   [10:0] lb2lin;

// lb integer part buffer
  reg    [11:8] lb_reg;

// barrel shifter
  wire   [11:0] shifter;
  
// instantiate lb_to_lin ROM
  lb2lintab11	lb2lin_inst(
    .address( lb[7:0] ), // 8-bit address (latched)
    .clken  ( clk_en ),  // clock enable
    .clock  ( m_clk ),   // master clock
    .q      ( lb2lin )   // 11-bit output (not latched)
  );

// barrel shifter
  assign shifter = ( {1'b1, lb2lin[9:0], 1'b0} >> lb_reg[11:8] );
  
  generate
    if (SIGNED)
      begin
// 1's complementer
        assign lin_out = { {4{sign}}, ( {12{sign}} ^ shifter ) };
      end
    else
      begin
// unsigned
        assign lin_out = { 4'b0000, shifter };
      end
  endgenerate	 
  
// input register						 
  always @(posedge m_clk or posedge reset)
    begin
      if (reset) // async reset
        begin
          lb_reg <= 4'h0;
        end
      else if (clk_en) 
        begin
          lb_reg <= lb[11:8];
        end
      else // hold
        begin
          lb_reg <= lb_reg;
        end
    end // always
    
endmodule // opl3_lb2lin()

今回の EG 出力直結のように、符号ビットを使用しない場合には、「SIGNED」パラメタを「0」にすると 1 の補数器を省略するようになっています。
バレル・シフタについてはシフト演算子を用いた場合と、8 / 4 / 2 / 1 ビットシフトに「バラして」記述した場合とで LE (ロジック・エレメント) の使用量に変化がなかったので、簡単のためシフト演算子を使っています。
変換テーブル ROM はメガウィザードを用いて作成しました。 出力されたファイル等については省略します。
ROM の内容について、.mif 形式で下に示します。

%
  file: "lb2lintab.mif"
  table for 8-bit lb fraction to 11-bit linear conversion
  (256 entries)
%

depth=256;
width=11;
address_radix=hex;
data_radix=hex;
content
begin
--      +0   +1   +2   +3   +4   +5   +6   +7
--    ---- ---- ---- ---- ---- ---- ---- ----
000 : 07fa 07f5 07ef 07ea 07e4 07df 07da 07d4 ;
008 : 07cf 07c9 07c4 07bf 07b9 07b4 07ae 07a9 ;
010 : 07a4 079f 0799 0794 078f 078a 0784 077f ;
018 : 077a 0775 0770 076a 0765 0760 075b 0756 ;
020 : 0751 074c 0747 0742 073d 0738 0733 072e ;
028 : 0729 0724 071f 071a 0715 0710 070b 0706 ;
030 : 0702 06fd 06f8 06f3 06ee 06e9 06e5 06e0 ;
038 : 06db 06d6 06d2 06cd 06c8 06c4 06bf 06ba ;
040 : 06b5 06b1 06ac 06a8 06a3 069e 069a 0695 ;
048 : 0691 068c 0688 0683 067f 067a 0676 0671 ;
050 : 066d 0668 0664 065f 065b 0657 0652 064e ;
058 : 0649 0645 0641 063c 0638 0634 0630 062b ;
060 : 0627 0623 061e 061a 0616 0612 060e 0609 ;
068 : 0605 0601 05fd 05f9 05f5 05f0 05ec 05e8 ;
070 : 05e4 05e0 05dc 05d8 05d4 05d0 05cc 05c8 ;
078 : 05c4 05c0 05bc 05b8 05b4 05b0 05ac 05a8 ;
080 : 05a4 05a0 059c 0599 0595 0591 058d 0589 ;
088 : 0585 0581 057e 057a 0576 0572 056f 056b ;
090 : 0567 0563 0560 055c 0558 0554 0551 054d ;
098 : 0549 0546 0542 053e 053b 0537 0534 0530 ;
0a0 : 052c 0529 0525 0522 051e 051b 0517 0514 ;
0a8 : 0510 050c 0509 0506 0502 04ff 04fb 04f8 ;
0b0 : 04f4 04f1 04ed 04ea 04e7 04e3 04e0 04dc ;
0b8 : 04d9 04d6 04d2 04cf 04cc 04c8 04c5 04c2 ;
0c0 : 04be 04bb 04b8 04b5 04b1 04ae 04ab 04a8 ;
0c8 : 04a4 04a1 049e 049b 0498 0494 0491 048e ;
0d0 : 048b 0488 0485 0482 047e 047b 0478 0475 ;
0d8 : 0472 046f 046c 0469 0466 0463 0460 045d ;
0e0 : 045a 0457 0454 0451 044e 044b 0448 0445 ;
0e8 : 0442 043f 043c 0439 0436 0433 0430 042d ;
0f0 : 042a 0428 0425 0422 041f 041c 0419 0416 ;
0f8 : 0414 0411 040e 040b 0408 0406 0403 0400 ;
end;

ただし、ModelSim シミュレータはサードパーティー・ツールなので、.mif 形式のデータを理解できません。 
ModelSim でシミュレーションを行なうなら、あらかじめ ROM 内容を .mif から hex 形式に変換しておく必要があります。
変換については、.mif ファイルを Quartus II で開いた場合に自動的に呼び出される mif エディタで、ファイルをセーブするダイアログで「hex」形式を選べば自動的に変換されてセーブされます。