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 を対向させてテストしたシミュレーション結果を下に示します。 (図をクリックすると拡大します)
スレーブ側のマスター・クロック周期は 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 の補数) を転送しています。