FPGA 版 FM 音源 (74) -- FPGA 版 EG (18)

OPL3 のスロット・パラメタ・レジスタ・アレイは、アレイひとつ当たり 256 バイトのアドレス空間を占めています。 アレイ 0 とアレイ 1 とのふたつのアレイがあるので、合計では 512 バイトとなります。
これをホスト・プロセッサのアドレス空間にそのまま割り付けてしまうと、ホストの貴重なリソースを大量に消費することになります。
そのため、OPL3 ではアレイひとつに対して一組のアドレス・レジスタ/データ・レジスタの 2 バイトのみをホストのアドレス空間に割り付け、「間接アドレシング」によりホストから OPL3 のレジスタ・アレイにアクセスする方式を取っています。
アドレス/データの区別のための「A0」信号と、アレイ 0/1 の区別のための「A1」信号との計 2 本のアドレス線で計 4 バイトのレジスタを区別します。
データの入出力は 8 ビット・パラレルで行なわれるので、これをそのまま踏襲すると信号線の数が多くなります。
現在のマイコンには多くの場合 SPI (Serial Peripheral Interface) モジュールが組み込まれているので、FPGA 版ではホストとのインターフェースには SPI を使って信号線の数を減らすことにしました。
レジスタ・アレイ 0/1 の区別は独立した信号線を使い、SPI ではアレイ内の 256 アドレスを指定する 8 ビットを上位バイトに、データの 8 ビットを下位バイトとして連結して合計 16 ビットを一回の SPI 転送でやり取りします。
FPGA 側には、FPGA のマスター・クロックによる同期回路で、スレーブ・モードの SPI として動作するモジュールを用意しました。 マイコン側ではマスター・モードの SPI を動作させます。
スレーブ側は SPI の SCK に同期する (非同期) 回路ではなく、いわゆる「ダブル・ラッチ」により入力信号をマスター・クロックに同期化させているので、SCK の周波数は FPGA マスター・クロックの 1/2 以下にする必要があります。
スレーブ・モード SPI の回路を下に示します。

転送パラメタは、16 ビット転送、モード 0 に決め打ちで、簡単のため「ビット・カウンタ」も設けてありません。
また、簡単のため「(パラレル) 出力レジスタ」も設けておらず、シリ-パラ変換のためのシフトレジスタ出力をそのまま出してあります。 したがって、次の SPI 転送が始まると前回の受信結果は失われてしまいますから、次の SPI 転送開始までに受信データを引き取る必要があります。
16 ビット転送なのでシフトレジスタは 16 個の FF で構成されますが、上の回路図では、その内の 3 個しか示してありません。
入力信号のダブル・ラッチのため信号遅延が生じ、SCK 周波数が高いとマスター SPI が MISO 信号をサンプルするタイミングに間に合わなくなります。
このスレーブ SPI では MISO から出力するためのデータのロードは行なっておらず、前回の転送でシフトレジスタに残ったデータをそのまま送り返すだけで実用性はありませんが、ロジック・シミュレーション目的のために MISO 出力を付けてあります。
ホスト側からは FPGA 側のスロット・レジスタ・アレイの内容を読み出すことは不可ですが、FPGA 側のビジー・ステータスなどの出力信号をホストのマスター SPI の MISO 入力に接続しておき、そのステータスを読み出すことは可能です。
Verilog ソースを下に示します。

//
// spi16s.v : 16-bit SPI
//            (slave, mode0, CPHA=0, CPOL=0)
//
// 2017/03/03 Created by pcm1723
//

module spi16s(
// input
  reset,    // async. reset
  m_clk,    // master clock
  spi_sck,  // SPI SCK  (Serial ClocK)
  spi_mosi, // SPI MOSI (Master In Slave Out)
  spi_nss,  // SPI nSS  (Slave Select)
// output
  spi_miso,   // SPI MISO (Master Out Slave In)
  spi_addr,   // address part
  spi_data,   // data part
  spi_valid   // write trigger  
);
//
  input        reset;
  input        m_clk;
  input        spi_sck;
  input        spi_mosi;
  input        spi_nss;
//
  output       spi_miso;
  output [7:0] spi_addr;
  output [7:0] spi_data;
  output       spi_valid;
//
// wires
  wire         sck_rise;
  wire         spi_miso;
// regs
  reg    [2:0] sck_z;  // double latch and 1 m_clk delay
  reg    [1:0] nss_z;  // double latch
  reg    [1:0] mosi_z; // double latch         
  reg   [15:0] sr;     // 16-bit shift register
//
  assign spi_miso = sr[15];   // MISO
  assign spi_addr = sr[15:8]; // address part
  assign spi_data = sr[7:0];  // data part
  assign spi_valid = (nss_z[1]); // SPI received data valid
// SCK rising edge detection
  assign sck_rise = ( (~sck_z[2]) & ( sck_z[1]) & (~nss_z[1]) ); 
  
// input capture by double latch
  always @(posedge m_clk or posedge reset) 
    begin
      if (reset) // async. reset
        begin
          sck_z  <= 3'b000;
          nss_z  <= 2'b11;
          mosi_z <= 2'b00;
        end
      else 
        begin
          sck_z  <= {sck_z[1:0], spi_sck};
          nss_z  <= {nss_z[0],   spi_nss};
          mosi_z <= {mosi_z[0],  spi_mosi};
        end // if
    end // always 

    
// SPI shift register
  always @(posedge m_clk or posedge reset) 
    begin
      if (reset) // async. reset
        begin
          sr <= 16'h0000;
        end
      else if (sck_rise) // 1st edge
        begin // 1-bit shift
          sr <= {sr[14:0], mosi_z[1]};
        end // if
    end // always
endmodule // spi16s()

マスターモードの SPI を対向させてテストしたシミュレーション結果を下に示します。 (図をクリックすると拡大します)

opl3_spi16_ms_test.png

スレーブ側のマスター・クロック周期は 58 ns (周波数は約 17.2 MHz)、マスター SPI の SCK 周期は 188 ns (周波数は約 5.3 MHz) とし、両者の周波数比は約 3.2 としてスレーブ側の MISO 出力がマスター側で正しくサンプルできるようにしてあります。
上の「縮小版」では 16 ビット転送 1 回分だけを示してありますが、「拡大版」では 2 回分を示してあります。
最初の転送では、スレーブ側のシフトレジスタの内容を 0x0000 に初期化したあと、マスター SPI から 0xa595 を転送し、2 回目はマスター側から 0x5a6a (0xa595 の 1 の補数) を転送しています。