モノ創りで国造りを

ハード/ソフト問わず知見をまとめてます

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通りのプロトコル(モード)がある。

f:id:yuji2yuji:20190813171657p:plain

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...の場合も、条件式は同時に判定される(はず)。