FPGA 版 FM 音源 (61) -- FPGA 版 EG (5)

EG コントロール・モジュール「opl3_EG_ctrl()」を回路図で表現したものを下に示します。

図の上半分は AR / DR / RR のレート・パラメタのマルチプレクサおよび Rof (レート・オフセット) の処理回路です。
図の下半分は EG アキュムレータ値とサステイン・レベル (SL) との比較回路で、実際は EG アキュムレータ・モジュール「opl3_EG_acc()」に含まれています。

図では省略していますが、EG パラメタ AR / DR / SL / RR / EGT には入力レジスタが設けてあります。
EG ステート・マシンについては、回路図で描くと面倒なので省略してあります。 
EG ステート・マシンの状態遷移図を左に示します。
遷移条件を表にしたものを下に示します。
各行が遷移前の状態、各列が遷移後の状態です。
「(なし)」と書いてある欄については、その組み合わせの遷移はないことを示しています。
表の対角要素は状態遷移しない条件を表しますが、複雑になるので空欄にしてあります。

次状態→
↓前状態
MuteAttackDecaySustainRelease
MuteKON↑(なし)(なし)(なし)
Attack(なし)acc=0dB(なし)KON↓
Decayacc=95dBKON↑  (egt=1)
& (acc=SL)
KON↓
| ((egt=0)
& (acc=SL))
Sustain(なし)(なし)(なし)KON↓
Releaseacc=95dBKON↑(なし)(なし)
Verilog で書いたものを下に示します。
"opl3_EG_ctrl.v"

//
// opl3_EG_ctrl.v : OPL3 EG control module
//
// 2017/01/12 Created by pcm1723
//
module opl3_EG_ctrl(
//
// Input
//
    reset, // async reset
    m_clk, // master clock
    slot_clk_en, // slot clock enable
    clk_en,      // clock enable from prescaler
    eg_state_in, // EG state read data from slot mem.
    eq_0dB,  // EG peak (0 dB) flag
    eq_95dB, // EG bottom (-95 dB) flag
    eq_slx,  // EG crossing Sustain Level flag
    kon_rise, // rising  (positive) edge of KON
    kon_fall, // falling (negative) edge of KON
    egt, // EGT(EG Type) parameter
    ar,  // Attack  Rate parameter
    dr,  // Decay   Rate parameter
    rr,  // Release Rate parameter
    rof, // Rate OFfset  parameter
//
// Output
//
    rate,  // actual Rate output
    rof_1, // Rof fraction b1
    rof_0, // Rof fraction b0
    acc_clk_en, // EG acc clock enable
    rate_0,  // (RATE == 0 ) flag
    rate13,  // (RATE == 13) flag
    rate15,  // (RATE == 15) flag
    hi_rate, // (RATE >= 13) flag
    A,  // Attack  state indicator
    D,  // Decay   state indicator
    R,  // Release state indicator
    M,  // Mute    state indicator
    S,  // Sustain state indicator
    eg_state_out // EG state write data to slot mem.
  );
// Input port
  input        reset;
  input        m_clk;
  input        slot_clk_en;
  input        clk_en;      // clock enable from prescaler
  input  [2:0] eg_state_in;
  input        kon_rise;
  input        kon_fall;
  input        egt;
  input  [3:0] ar;
  input  [3:0] dr;
  input  [3:0] rr;
  input  [3:0] rof;
  input        eq_0dB;
  input        eq_95dB;
  input        eq_slx;
// Output port
  output [3:0] rate;
  output       rof_1;
  output       rof_0;
  output       rate_0;
  output       rate15;
  output       rate13;
  output       hi_rate;
  output       acc_clk_en; // EG acc clock enable output
  output       A;
  output       D;
  output       S;
  output       R;
  output       M;
  output [2:0] eg_state_out;
//
// EG state definition
  localparam EG_MUTE    = 3'b000;
  localparam EG_ATTACK  = 3'b001;
  localparam EG_DECAY   = 3'b010;
  localparam EG_SUSTAIN = 3'b110;
  localparam EG_RELEASE = 3'b100;
// wires
  wire [3:0] adder4; // 4-bit adder
  wire       carry;  // carry from 4-bit adder  
  wire [3:0] rate_mux;   // AR/DR/RR mux
  wire [3:0] rof_masked; // Rof masked by (~RATE0)
  wire       to_A; // transition condition
  wire       to_D; // transition condition
  wire       to_S; // transition condition
  wire       to_R; // transition condition
  wire       to_M; // transition condition
  wire       trans; // transition condition
// regs
  reg  [3:0] ar_reg;
  reg  [3:0] dr_reg;
  reg  [3:0] rr_reg;
  reg  [3:0] rof_reg;
  reg        egt_reg;
  reg        kon_rise_reg;
  reg        kon_fall_reg;
// EG state machine  
  reg  [2:0] eg_state;
//
// EG state input register
  always @(posedge m_clk or posedge reset)
    begin
      if (reset) // async reset
        begin
          eg_state <= EG_MUTE;
        end
      else if (slot_clk_en) // slot clock timing
        begin
          eg_state <= eg_state_in;
        end
      else // hold
        begin
          eg_state <= eg_state;
        end // if (reset) ...
    end // always @() ...
//	 
//	input registers for slot parameter
// (AR / DR / RR / Rof / EGT / KON_rise / KON_fall)
  always @(posedge m_clk or posedge reset)
    begin
      if (reset) // async reset
        begin
          ar_reg  <= 4'h0;
          dr_reg  <= 4'h0;
          rr_reg  <= 4'h0;
          rof_reg <= 4'h0;
          egt_reg <= 1'b0;
          kon_rise_reg <= 1'b0;
          kon_fall_reg <= 1'b0;
        end
      else if (slot_clk_en) // slot clock timing
        begin
          ar_reg  <= ar;
          dr_reg  <= dr;
          rr_reg  <= rr;
          rof_reg <= rof;
          egt_reg <= egt;
          kon_rise_reg <= kon_rise;
          kon_fall_reg <= kon_fall;
        end
      else // hold
        begin
          ar_reg  <= ar_reg;
          dr_reg  <= dr_reg;
          rr_reg  <= rr_reg;
          rof_reg <= rof_reg;
          egt_reg <= egt_reg;
          kon_rise_reg <= kon_rise_reg;
          kon_fall_reg <= kon_fall_reg;
        end // if (reset) ...
    end // always @() ...
// AR / DR /RR mux
  assign rate_mux = ( ( {4{A}} & ar_reg) | ( {4{D}} & dr_reg) | ( {4{R}} & rr_reg) );
// RATE0 detect
  assign rate_0 = ( rate_mux == 0 );
// Rof masking for (rate == 0)
  assign rof_masked = ( {4{~rate_0}} & rof_reg );
//Rof[1], Rof[0] masking for (rate == 15)
  assign {rof_1, rof_0} = ( {2{~rate15}} & rof_masked[1:0] );
// actual_rate = rate + rof
  assign {carry, adder4} = (rate_mux + rof_masked[3:2]);
// saturate rate to 15 if carry
  assign rate = ( {4{carry}} | adder4 );
// (rate == 15) flag
  assign rate15 = ( rate == 15 );
// (rate == 13) flag
  assign rate13 = ( rate == 13 );
// hi-rate (rate >= 13 flag)
  assign hi_rate = ( rate >= 13 );
// EG state decode
  assign A = (eg_state == EG_ATTACK);
  assign D = (eg_state == EG_DECAY);
  assign S = (eg_state == EG_SUSTAIN);
  assign R = (eg_state == EG_RELEASE);
  assign M = ( ~(A | D | S | R) ); // Mute state
// transition conditions
  assign to_A = ( (~A) & kon_rise_reg );
  assign to_D = ( A & eq_0dB );
  assign to_S = ( D & eq_slx );
  assign to_R = ( (~(R | M)) & kon_fall_reg );
  assign to_M = ( (D | R) & eq_95dB );
  assign trans = ( to_A | to_D | to_S | to_R | to_M );
// disable EG acc clock when state transition occurred
  assign acc_clk_en = ( M | ((~trans) & clk_en) );
// next state of EG state machine
  assign eg_state_out = ( to_A ? EG_ATTACK  : // highest priority
                        ( to_R ? EG_RELEASE :
                        ( to_M ? EG_MUTE    :
                        ( to_D ? EG_DECAY   :
                        ( to_S ? ( egt_reg ? EG_SUSTAIN : EG_RELEASE ) :
               /* else */ eg_state )))));
endmodule

EG ステートについては、スロット・メモリから読み出したデータを EG ステート・レジスタにラッチし、組み合わせロジックで状態遷移を計算した結果をスロット・メモリへ書き出しています。
状態遷移が発生した場合には、acc_clk_en 信号を強制的に下げて、EG アキュムレータ値がホールドされるようにします。
逆に、ミュート状態 (出力レベルが -95 dB 以下) では acc_clk_en 信号を上げて、最大減衰量 96 dB に相当する値 9'b1_1111_1111 が毎回 EG アキュムレータにロードされるようにしています。