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」形式を選べば自動的に変換されてセーブされます。