VerilogでSPI通信
前置き
Verilogの勉強としてSPI通信のIFを作成する。
SPI通信は送受信が一つのクロックで同時に行われる。
実装が難しそうな気がするが頑張る。
SPIのプロトコル
通信線は全部で4本。
No | 名称 | 用途 |
---|---|---|
1 | SPICLK | クロック |
2 | CSn | チップセレクト、負論理 |
3 | MISO | マスターからスレーブへの信号 |
4 | MOSI | スレーブからマスターへの信号 |
チップセレクトが負論理。
MISO、MOSIは正論理。
ただしデータの取得タイミングはデバイスによって異なり、クロックの立上がりor立下りいずれかとなる。
加えてデフォルト状態でHかLかもデバイスにより異なる。
さらにLSb(bitなのであえて小文字) FirstかMSb FIrstかもデバイスによって頃なる。
従って、SPIの通信は8通りのプロトコル(モード)がある。
Mode0と2における受信信号とクロックのタイミングが謎。
実装の進め方
方針
今回はMode3かつ、MSb Firstに対応させる。
Mode3はSPICLKがidle時に0、立下りで信号送受信する。
クロックはパラメータで可変にする。
*いずれパラメーターの設定で各モード切り替えられるようにしたい。
手順
スタート信号を受信すると、送信データをバッファからコピーする。
信号とクロックの送信を開始する。
クロックをカウントする。
一定カウント毎にクロックを反転する。
一定カウント毎に信号を送信する。
一定カウント毎に受信信号を受信バッファに入力。
既定のデータ数の送受信後に受信バッファを出力用バッファに代入。
初期化。
クロックを反転する時間間隔はデータの送受信タイミングの2倍。
ソースコード
Verilog-HDL
module spi_msb( input CLK, input RST, input START, input[7:0] TXDATA, input RXSIG, output reg TXSIG = 0, output reg[7:0] RXDATA, output reg SPICLK = 1//Mode3 ); reg[7:0] txbuff = 8'b0; reg[7:0] rxbuff = 8'b0; reg spien = 0; reg[8:0] clkCnt = 9'b0; reg[3:0] dataCnt = 4'b0; parameter CLKFLAG = 9'd125;//CLK/(SPICLK*2) ex)50MHz/(200kHz*2) parameter DATAFLAG = 9'd250;//CLK/SPICLK ex)50MHz/200kHz*2 always@(posedge CLK or negedge RST)begin if(!RST)begin txbuff <= 8'b0; rxbuff <= 8'b0; spien <= 0; SPICLK <= 1;//Mode3 clkCnt <= 9'b0; end else begin if(spien)begin clkCnt <= clkCnt + 9'b1; if(clkCnt == CLKFLAG)begin SPICLK <= ~SPICLK; if(!SPICLK)begin rxbuff[7:0] <= {rxbuff[6:0],1'b0}; rxbuff[0] <= RXSIG; dataCnt <= dataCnt + 4'b1; end end else if(clkCnt == DATAFLAG)begin if(dataCnt < 4'b1000)begin clkCnt <= 9'b0; SPICLK <= ~SPICLK; TXSIG <= txbuff[7]; txbuff <= {txbuff[6:0], 1'b0}; end else begin RXDATA <= rxbuff; txbuff <= 8'b0; rxbuff <= 8'b0; spien <= 0; clkCnt <= 9'b0; dataCnt <= 4'b0; SPICLK <= 1;//Mode3 end end end else if(START)begin txbuff <= TXDATA; rxbuff <= 8'b0; spien <= 1; clkCnt <= 9'b0; dataCnt <= 4'b0; SPICLK <= 0;//Mode3 TXSIG <= txbuff[7]; txbuff <= {txbuff[6:0], 1'b0}; end end end endmodule
テストベンチ
`timescale 1 ns/ 100 ps module spi_msb_vlg_tst(); // constants // general purpose registers reg eachvec; // test vector input registers reg CLK; reg RST; reg RXSIG; reg START; reg [7:0] TXDATA; // wires wire [7:0] RXDATA; wire SPICLK; wire TXSIG; // assign statements (if any) spi_msb i1 ( // port map - connection between master ports and signals/registers .CLK(CLK), .RST(RST), .RXDATA(RXDATA), .RXSIG(RXSIG), .SPICLK(SPICLK), .START(START), .TXDATA(TXDATA), .TXSIG(TXSIG) ); always #10 CLK = ~CLK; initial begin CLK = 1; RST = 1; START = 0; TXDATA = 8'b01010011; RXSIG = 0; #200 START = 1; #100 RXSIG = 1; START = 0; #100 RXSIG = 1;//1bit #5000 RXSIG = 0;//2bit #5000 RXSIG = 0;//3bit #5000 RXSIG = 1;//4bit #5000 RXSIG = 1;//5bit #5000 RXSIG = 0;//6bit #5000 RXSIG = 0;//7bit #5000 RXSIG = 1;//8bit #5000 RXSIG = 0;//default $display("Running testbench"); end always begin @eachvec; end endmodule
気づき
if文を入れ子構造にしている場合でも、条件式を判定するタイミングは同じ。
if(条件1)begin 式1 if(条件2)begin 式2 end end
としている場合、条件1と条件2の判定は同じ同一のタイミングで行われる。
例えば以下において、SPICLK = 1
の時にclkCnt
が100になると、
SPICLK
は0になり、jikkou
は1になる(はず)。
if(clkCnt == 100)begin SPICK <= 0; if(SPICLK == 1)begin jikkou <= 1; end end
通常のプログラミング言語は上から順次演算されるが、HDLは条件式の中が同時に判定されるために、このような結果になる。
if... else...
の場合も、条件式は同時に判定される(はず)。