FPGA 版 FM 音源 (25) -- EG シミュレーション (1)
約 3 年間放置していた「FPGA 版 FM 音源」を再開しました。
アタックとディケイのみを実装した EG を Verilog-HDL で書いて「ModelSim」でシミュレーションを行い、以前の記事の C プログラムでの実現と同様の結果を得ました。
アタック/ディケイ・タイムが長くてシミュレーションでは確認が困難な領域については、実際に FPGA 上に「ハード」をインプリメントして確認しました。
Altera の (無印) Cyclone をターゲットにして、 EG 部分は 105 ロジック・セルとなりました。
具体的には、CQ 出版社の DESIGN WAVE MAGAZINE 2003 年 10 月号付属の FRK1C3 基板 (EP1C3T100 使用) を使いました。
ModelSim の WAVE 画面のキャプチャを下に示します。 (クリックで拡大します)
AR (アタック・レート) と DR (ディケイ・レート) を同じ値に設定してあり、AR/DR = 7 の Rof = 0 から AR/DR = 15 の Rof = 3 までをシミュレーションしています。
上下中央やや上のシアン色のトレースが EG 出力値 (減衰量の対数) を ModelSim の「アナログ波形」表示機能を使って表示したもので、中央やや下の赤色のトレースが EG 出力をリニア値に変換した結果のアナログ波形表示です。
赤色のトレースの下が AR/DR の値で、その下が Rof の値です。
アタック/ディケイ・タイムの長い領域についてはゲート・タイムが十分ではなく、ディケイの途中でエンベロープが強制的に落とされています。
下に AR/DR = 14、Rof = 1 の部分を拡大して、ディジタル・データの数値が読める状態にしたものを示します。 (クリックで拡大します)
各トレースは、上から順に、
- クロック
- ゲート信号
- (EG 外部回路の)ゲート・カウンタ値
- EG 出力値「eg_out」
- リニア値に変換した「lin_out」
となっており、一番下のトレースがシフト量「shift_amount」です。
アタック部分の eg_out、eg_lin、shift_amount は、2011 年 1 月 31 日の記事 (→こちら) の表と完全に一致しています。
レート・マルチプライア内のフリーラン・オクタル・カウンタの位相と、ゲート信号のタイミングとの関係により、この数値の系列は変化しますが、他の位相については確認していません。
最後に、EG 部分の Verilog-HDL ソースを下に示します。
これは主にレート・マルチプライアによるシフト量/クロック間引き機能の検証のためで、アタック/ディケイしか実装していないこともあり、コントロール部分については「動けばいい」という程度のレベルになっています。
本来は AR/DR = 0 の場合にはエンベロープの更新を停止 (アタック/ディケイ・タイム無限大) しなければなりませんが、それも実装していません。
// OPL3 EG (Attack / Decay only) // 2013/12/04 Created by pcm1723 // module opl3_eg(reset, m_clk, clk_en, gate, egt, rof, ar, dr, sl, rr, eg_out); input reset; // reset for simulation input m_clk; // master clock input clk_en; // clock enable input gate; // GATE signal input egt; // EG type input [1:0] rof; // Rof (0..3) input [3:0] ar; // Attack Rate (0..15) input [3:0] dr; // Decay Rate (0..15) input [3:0] sl; // Sustain Level (0..15) input [3:0] rr; // Release Rate (0..15) output [8:0] eg_out; // EG output reg [2:0] rm_cnt; // rate multiplier octal counter reg [8:0] eg_acc; // EG accumulator wire [8:0] eg_inc; // EG increment reg gate_dly; // delay element for GATE wire [3:0] rate; // AR/DR/RR muxed RATE wire [1:0] shift_amount; // shift amount for eg_inc reg [11:0] psc_cnt; // prescaler conter reg [11:0] psc_cmp_reg; // prescaler compare register wire [11:0] psc_mask; // prescaler compare mask reg decay_state; reg mute_state; // AR/DR/RR rate selector assign rate = ( decay_state ? dr : ar); assign eg_out = eg_acc; // start of Attack assign A_trig = (gate & (~gate_dly)); // start of Decay (EG acc zero detection) assign D_trig = (gate & ~(| eg_acc)); // mute condition detection (level < -94.5 dB) assign below_94dB = (decay_state & (&eg_acc[8:4])); // (0 != eg_acc) indication assign acc_non_zero = (A_trig | ( | eg_acc )); // lo rate prescaler enable flag (rate < 12) assign psc_mode = ~( & rate[3:2]); // rate flags assign rate_eq_13 = (13 == rate); assign rate_eq_14 = (14 == rate); assign rate_eq_15 = (15 == rate); assign hi_rate = (rate_eq_15 | rate_eq_14 | rate_eq_13); assign lo_rate = ~hi_rate; // rate multiplier output assign eg_sft_sel = ( (rm_cnt[1]) ? (rof[0] & rm_cnt[2]) : (rof[1]) ); assign eg_clk_en = (hi_rate | eg_sft_sel | (~rm_cnt[0])); // EG increment shift amount function [1:0] shift_calc; input lo_rate; input [1:0] rate; input eg_sft_sel; case ({lo_rate, rate, eg_sft_sel}) 4'b0_01_0 : shift_calc = 3; 4'b0_01_1 : shift_calc = 2; 4'b0_10_0 : shift_calc = 2; 4'b0_10_1 : shift_calc = 1; 4'b0_11_0 : shift_calc = 1; 4'b0_11_1 : shift_calc = 1; default : shift_calc = 3; endcase endfunction assign shift_amount = shift_calc(lo_rate, rate[1:0], eg_sft_sel); // EG increment shifter // fixed pattern for DR/RR // EG feedback (inverted) for AR assign eg_inc = ( ( (decay_state) ? (9'b0000_01000) // for Decay : {3'b111, ~eg_acc} // for Attack ) >> shift_amount ); // prescaler compare function [11:0] psc_mask_calc; input [3:0] rate; case (rate) 1 : psc_mask_calc = 11'b11111111111; // care all 2 : psc_mask_calc = 11'b01111111111; // omit b10 3 : psc_mask_calc = 11'b00111111111; // omit b10..9 4 : psc_mask_calc = 11'b00011111111; // omit b10..8 5 : psc_mask_calc = 11'b00001111111; // omit b10..7 6 : psc_mask_calc = 11'b00000111111; // omit b10..6 7 : psc_mask_calc = 11'b00000011111; // omit b10..5 8 : psc_mask_calc = 11'b00000001111; // omit b10..4 9 : psc_mask_calc = 11'b00000000111; // omit b10..3 10 : psc_mask_calc = 11'b00000000011; // omit b10..2 11 : psc_mask_calc = 11'b00000000001; // omit b10..1 default : psc_mask_calc = 11'b00000000000; // ignore all endcase endfunction assign psc_mask = psc_mask_calc(rate); assign psc_match = ~( | (psc_mask & (psc_cnt ^ psc_cmp_reg))); assign psc_clk_en = (psc_match); // EG accumulator always @(posedge reset or posedge m_clk) begin if (reset) eg_acc <= 9'b1111_11111; else if (clk_en & (decay_state | acc_non_zero)) begin if ((~A_trig) & (mute_state | (~gate))) eg_acc <= 9'b1111_11111 ; else if (eg_clk_en & psc_clk_en) eg_acc <= ( (A_trig & rate_eq_15) ? (9'b0000_00000) : (eg_acc + eg_inc) ); end end // always // rate multiplier octal counter (free running) always @(posedge reset or posedge m_clk) begin if (reset) rm_cnt <= 0; else if (clk_en & psc_clk_en) begin rm_cnt <= (rm_cnt + 1); end end // always // clock prescaler counter for EG acc (free running) always @(posedge reset or posedge m_clk) begin if (reset) psc_cnt <= 0; else if (clk_en) begin psc_cnt <= (psc_cnt + 1); end end // always // clock prescaler compare register always @(posedge reset or posedge m_clk) begin if (reset) psc_cmp_reg <= 0; else if (clk_en & (A_trig | (D_trig & ~decay_state))) begin psc_cmp_reg <= psc_cnt; // remember counter value end end // always // gate signal delay element always @(posedge reset or posedge m_clk) begin if (reset) gate_dly <= 0; else if (clk_en) gate_dly <= gate; end // always // decay state always @(posedge reset or posedge m_clk) begin if (reset) decay_state <= 0; else if (clk_en) begin if (~gate) decay_state <= 0; else if (D_trig) decay_state <= 1; end // if (clk_en) end // always // mute state always @(posedge reset or posedge m_clk) begin if (reset) mute_state <= 0; else if (clk_en) begin if (A_trig) // exit mute state at attack mute_state <= 0; else if (below_94dB) // mute condition mute_state <= 1; end // if (clk_en) end // always endmodule // opl3_eg()