8bit SPIスレーブの実装
- FPGA4FUNのSPIスレーブVerilogコードがあまり実用的でなかったので書き直したメモです。
fpga4funのSPIコードでは、CS(SSEL)信号やSCK、MOSIの状態を3bitのシフトレジスタにコピーし、立ち上がり・立ち下がりを観測している様です。
// sync SCK to the FPGA clock using a 3-bits shift register reg [2:0] SCKr; always @(posedge clk) SCKr <= {SCKr[1:0], SCK}; wire SCK_risingedge = (SCKr[2:1]==2'b01); // now we can detect SCK rising edges wire SCK_fallingedge = (SCKr[2:1]==2'b10); // and falling edges // same thing for SSEL reg [2:0] SSELr; always @(posedge clk) SSELr <= {SSELr[1:0], SSEL}; wire SSEL_active = ~SSELr[1]; // SSEL is active low wire SSEL_startmessage = (SSELr[2:1]==2'b10); // message starts at falling edge wire SSEL_endmessage = (SSELr[2:1]==2'b01); // message stops at rising edge // and for MOSI reg [1:0] MOSIr; always @(posedge clk) MOSIr <= {MOSIr[0], MOSI}; wire MOSI_data = MOSIr[1];
なんとも半端ない努力ですが *1 、SPIの転送速度がFPGAクロックに支配されてしまいます。
そこで、マスターから送出されるSCKに同期するようにしたものです。要はただの8bitシフトレジスタです。
- SCLKをFPGAクロックに依存しないようにした
- SPIでマスターから受け取った8bitデータをエコーバックするVerilogコード。
- 使用ロジックエレメントは20LEほど。(ALTERA MAXII CPLD)
軽い説明:
マスタからの8bitデータをMOSI端子からSCKクロックに同期して受け取ります。
1バイト分受け取ると byte_received = 1 になります。マスタから受け取った8bitデータはシフトレジスタ:byte_data_receivedに格納されています
byte_received が1になったら、送信用レジスタ:byte_data_sent にbyte_data_receivedをそのままコピーします。これによって次の8bitサイクルでマスタにデータがエコーバックされます。
全二重通信なのでマスタはスレーブデータを受け取るために0xffなどのダミーデータを送る必要はありません。スレーブからのデータを受け取りつつ、次のデータを送信できます。
このデータは次々回の1バイト送受信のときにスレーブから返ってくるはずです。
module SPI_slave(SCK, MOSI, MISO, SSEL, MOSI_GET_DATA); input SCK, SSEL, MOSI; output MISO; output [7:0] MOSI_GET_DATA; wire byte_received; // high when a byte has been received reg [7:0] byte_data_received; reg [2:0] bitcnt; always @(posedge SCK)begin if(SSEL) begin //if(CS = 1) bitcnt <= 3'b000; end else begin bitcnt <= bitcnt + 3'b001; end; end // implement a shift-left register (since we receive the data MSB first) always @(posedge SCK) byte_data_received <= {byte_data_received[6:0], MOSI}; assign byte_received = (~SSEL) && (bitcnt==3'b111); assign MOSI_GET_DATA = byte_data_received; reg [7:0] byte_data_sent; always @(negedge SCK) if(~SSEL)begin if(byte_received) begin byte_data_sent <= byte_data_received; end else begin byte_data_sent <= {byte_data_sent[6:0], 1'b0}; end; end assign MISO = byte_data_sent[7]; endmodule
*1:これはSPIの極性(CPHA、CPOL)の変更に柔軟に対応するためみたい。でもこのコードだと、動作可能SPIクロックは速くてもメインクロックの3分の1になってしまう