SCLK周波数の切り替えやCPOL・CPHAモード変更、CSイネーブル後のSCLK動作開始までのタイミング変更、送受信データのビット数変更などに対応させ、多くのデバイスで対応できるようにした。
SPI通信の概要
SCLKで同期をとってMOSIとMISOでデータのやり取りを行う通信。I2C通信と同じくらいメジャーな通信規格。おもにセンサー-マイコン間や基板上のIC間といった短い距離の通信インターフェースとして使われる。くわしくは別途参照。SPI通信(マスター)のverilogソースコード
`timescale 1ns / 1ps
module SPI_IP#(
parameter DATA_WIDTH = 32,
parameter DATA_WIDTH_INIT = 0,
parameter CS_SCLK_TIME = 10, //clock count
parameter SLAVE_NUM = 1,
parameter SLAVE_NUM_INIT = 0
)
(
input wire CLK_in,
input wire NRESET_in,
output wire SCLK_out,
output reg [SLAVE_NUM-1:0] CS_out,
output wire MOSI_out,
input wire MISO_in,
input wire [31:0] DATA_BIT_NUM_in,
input wire [DATA_WIDTH -1:0] SEND_DATA_in,
output reg [DATA_WIDTH -1:0] RECE_DATA_out,
input wire [SLAVE_NUM -1:0] DEVICE_SELECT_in, //1=enable(CS)
input wire [31:0] SCLK_PERIOD_in, // Edge width time( double is SCK period)
input wire [1:0] POL_PHA_in, // bit0=POL(0:SCLK idles low.1:SCLK idles high), bit1=PHA(0:Latches on neg edge,1:Latches on posi edge)
input wire START_in,
output reg [31:0] STATUS_out
);
//----------
// Resister
//----------
reg SCLK_reg;
reg [2:0] MISO_ff3_reg;
reg start_ff = 1'b0;
reg start_ris_reg;
reg [31:0] SCLK_PERIOD_reg;
reg [31:0] DATA_BIT_NUM_reg;
reg [31:0] CS_counter_reg;
reg [31:0] SCLK_PERIOD_counter_reg;
reg [31:0] SCLK_edge_counter_reg;
reg [DATA_WIDTH -1:0] SEND_DATA_shift_reg;
reg [DATA_WIDTH -1:0] RECE_DATA_shift_reg;
reg done_reg;
reg busy_reg;
assign SCLK_out = (POL_PHA_in[0] == 1'b0) ? SCLK_reg : ~SCLK_reg;
//--------------------
// Remove metastable
//--------------------
always @( posedge CLK_in, negedge NRESET_in )
begin
if( NRESET_in == 1'b0 )
MISO_ff3_reg <= 3'b0;
else
MISO_ff3_reg <= { MISO_ff3_reg[1], MISO_ff3_reg[0], MISO_in };
end
//---------------
// State Machine
//---------------
reg [5:0] cur, nxt;
localparam IDLE = 5'b0_0001,
DATA_SET = 5'b0_0010,
SCLK_ON_WAIT = 5'b0_0100,
SEND_AND_RECEIVE= 5'b0_1000,
FIN = 5'b1_0000;
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
cur <= IDLE;
else
cur <= nxt;
end
always @*
begin
case ( cur )
IDLE:
if( start_ris_reg == 1'b1 )
nxt = DATA_SET;
else
nxt = IDLE;
DATA_SET:
nxt = SCLK_ON_WAIT;
SCLK_ON_WAIT:
if( CS_counter_reg >= CS_SCLK_TIME )
nxt = SEND_AND_RECEIVE;
else
nxt = SCLK_ON_WAIT;
SEND_AND_RECEIVE:
if( SCLK_edge_counter_reg == DATA_BIT_NUM_in && SCLK_PERIOD_counter_reg == SCLK_PERIOD_reg - 32'h0000_0001 )
nxt = FIN;
else
nxt = SEND_AND_RECEIVE;
FIN:
nxt = IDLE;
default:
nxt = IDLE;
endcase
end
//--------------------------
// Detect Start Rising Edge
//--------------------------
always @( posedge CLK_in, negedge NRESET_in )
begin
if( NRESET_in == 1'b0 )
begin
start_ff <= 1'b0;
start_ris_reg <= 1'b0;
end
else
begin
start_ff <=START_in;
start_ris_reg <= ~start_ff & START_in;
end
end
//----------------------
// Data set on register
//----------------------
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
begin
SCLK_PERIOD_reg <= 32'h0000_0000;
DATA_BIT_NUM_reg <= 32'h0000_0000;
end
else if ( nxt == DATA_SET )
begin
SCLK_PERIOD_reg <= SCLK_PERIOD_in;
DATA_BIT_NUM_reg <= DATA_BIT_NUM_in;
end
else
begin
SCLK_PERIOD_reg <= SCLK_PERIOD_reg;
DATA_BIT_NUM_reg <= DATA_BIT_NUM_reg;
end
end
//------------
// CS control
//------------
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
CS_counter_reg <= 32'h0000_0000;
else if ( nxt == SCLK_ON_WAIT )
CS_counter_reg <= CS_counter_reg + 32'h0000_0001;
else
CS_counter_reg <= 32'h0000_0000;
end
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
CS_out <= ~SLAVE_NUM_INIT;
else if ( cur == DATA_SET && nxt == SCLK_ON_WAIT )
CS_out <= ~DEVICE_SELECT_in;
else
CS_out <= CS_out;
end
//-----------
// Make SCLK
//-----------
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
SCLK_PERIOD_counter_reg <= 32'h0000_0000;
else if ( SCLK_PERIOD_counter_reg == SCLK_PERIOD_reg - 32'h0000_0001 )
SCLK_PERIOD_counter_reg <= 32'h0000_0000;
else if ( nxt == SEND_AND_RECEIVE )
SCLK_PERIOD_counter_reg <= SCLK_PERIOD_counter_reg + 32'h0000_0001;
else
SCLK_PERIOD_counter_reg <= 32'h0000_0000;
end
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
SCLK_reg <= 1'b0;
else if ( cur == SCLK_ON_WAIT && nxt == SEND_AND_RECEIVE )
SCLK_reg <= ~SCLK_reg;
else if ( SCLK_PERIOD_counter_reg == SCLK_PERIOD_reg - 32'h0000_0001 && nxt == SEND_AND_RECEIVE )
SCLK_reg <= ~SCLK_reg;
else if ( nxt == FIN )
SCLK_reg <= 1'b0;
else
SCLK_reg <= SCLK_reg;
end
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
SCLK_edge_counter_reg <= 32'h0000_0000;
else if ( POL_PHA_in[1] == 1'b0 && SCLK_out == 1'b0 && SCLK_PERIOD_counter_reg == SCLK_PERIOD_reg - 32'h0000_0001 && nxt == SEND_AND_RECEIVE )
SCLK_edge_counter_reg <= SCLK_edge_counter_reg + 32'h0000_0001;
else if ( POL_PHA_in[1] == 1'b0 && SCLK_out == 1'b0 && cur == SCLK_ON_WAIT && nxt == SEND_AND_RECEIVE )
SCLK_edge_counter_reg <= SCLK_edge_counter_reg + 32'h0000_0001;
else if ( POL_PHA_in[1] == 1'b1 && SCLK_out == 1'b1 && SCLK_PERIOD_counter_reg == SCLK_PERIOD_reg - 32'h0000_0001 && nxt == SEND_AND_RECEIVE )
SCLK_edge_counter_reg <= SCLK_edge_counter_reg + 32'h0000_0001;
else if ( POL_PHA_in[1] == 1'b1 && SCLK_out == 1'b1 && (cur == SCLK_ON_WAIT && nxt == SEND_AND_RECEIVE) )
SCLK_edge_counter_reg <= SCLK_edge_counter_reg + 32'h0000_0001;
else if ( nxt != SEND_AND_RECEIVE )
SCLK_edge_counter_reg <= 32'h0000_0000;
else
SCLK_edge_counter_reg <= SCLK_edge_counter_reg;
end
//-----------
// Send data
//-----------
assign MOSI_out = SEND_DATA_shift_reg[DATA_WIDTH -1];
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
SEND_DATA_shift_reg <= DATA_WIDTH_INIT;
else if ( nxt == DATA_SET && cur == IDLE )
SEND_DATA_shift_reg <= SEND_DATA_in << (DATA_WIDTH - DATA_BIT_NUM_in);
else if ( POL_PHA_in[1] == 1'b0 && SCLK_out == 1'b1 && SCLK_PERIOD_counter_reg == SCLK_PERIOD_reg - 32'h0000_0001 && nxt == SEND_AND_RECEIVE )
SEND_DATA_shift_reg <= {SEND_DATA_shift_reg[DATA_WIDTH -2:0],1'b0};
else if ( POL_PHA_in[1] == 1'b1 && SCLK_out == 1'b0 && SCLK_PERIOD_counter_reg == SCLK_PERIOD_reg - 32'h0000_0001 && nxt == SEND_AND_RECEIVE )
SEND_DATA_shift_reg <= {SEND_DATA_shift_reg[DATA_WIDTH -2:0],1'b0};
else if ( nxt == IDLE )
SEND_DATA_shift_reg <= DATA_WIDTH_INIT;
else
SEND_DATA_shift_reg <= SEND_DATA_shift_reg;
end
//--------------
// Receive data
//--------------
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
RECE_DATA_shift_reg <= DATA_WIDTH_INIT;
else if( nxt == IDLE )
RECE_DATA_shift_reg <= DATA_WIDTH_INIT;
else if ( POL_PHA_in[1] == 1'b0 && SCLK_out == 1'b0 && SCLK_PERIOD_counter_reg == SCLK_PERIOD_reg - 32'h0000_0001 && nxt == SEND_AND_RECEIVE )
RECE_DATA_shift_reg <= {RECE_DATA_shift_reg[DATA_WIDTH -2:0], MISO_ff3_reg[2]};
else if ( POL_PHA_in[1] == 1'b0 && SCLK_out == 1'b0 && cur == SCLK_ON_WAIT && nxt == SEND_AND_RECEIVE )
RECE_DATA_shift_reg <= {RECE_DATA_shift_reg[DATA_WIDTH -2:0], MISO_ff3_reg[2]};
else if ( POL_PHA_in[1] == 1'b1 && SCLK_out == 1'b1 && SCLK_PERIOD_counter_reg == SCLK_PERIOD_reg - 32'h0000_0001 && nxt == SEND_AND_RECEIVE )
RECE_DATA_shift_reg <= {RECE_DATA_shift_reg[DATA_WIDTH -2:0], MISO_ff3_reg[2]};
else if ( POL_PHA_in[1] == 1'b1 && SCLK_out == 1'b1 && (cur == SCLK_ON_WAIT && nxt == SEND_AND_RECEIVE) )
RECE_DATA_shift_reg <= {RECE_DATA_shift_reg[DATA_WIDTH -2:0], MISO_ff3_reg[2]};
else
RECE_DATA_shift_reg <= RECE_DATA_shift_reg;
end
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
RECE_DATA_out <= DATA_WIDTH_INIT;
else if ( nxt == FIN )
RECE_DATA_out <= RECE_DATA_shift_reg;
else
RECE_DATA_out <= RECE_DATA_out;
end
//--------
// Status
//--------
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
done_reg <= 1'b0;
else if( nxt == FIN )
done_reg <= 1'b1;
else if( start_ris_reg == 1'b1 )
done_reg <= 1'b0;
else
done_reg <= done_reg;
end
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
busy_reg <= 1'b0;
else if( nxt == DATA_SET )
busy_reg <= 1'b1;
else if( nxt == IDLE )
busy_reg <= 1'b0;
else
busy_reg <= busy_reg;
end
always @( posedge CLK_in, negedge NRESET_in )
begin
if ( NRESET_in == 1'b0 )
STATUS_out <= 32'h0000_0000;
else
STATUS_out <= {24'h00_0000, done_reg, busy_reg, nxt};
end
endmodule
パラメータについて
DATA_WIDTH:SPI通信で送受信するデータのビット幅を指定DATA_WIDTH_INIT:上記パラメータで指定されたビット幅を持つレジスタのリセット用。ほんとは32’h0000というように記述しなければならないが、VIVADOではなぜか動作がおかしくなるので0と記述してある。(こことか読めばbit幅のパラメータ化がなんとなくわかるはず。http://www.darwin.esys.tsukuba.ac.jp/home/ohyou/verilog/parameter)
CS_SCLK_TIME:CSが立ち下がってからSCLKが動作し始めるまでの時間を指定。クロックカウントの値で指定する。
SLAVE_NUM:本SPIマスター回路につながるスレーブデバイスの数を指定。ここで指定した本数のCS出力信号が生成される。
SLAVE_NUM_INIT:DATA_WIDTH_INITと同様の存在。
入出力について
CLK_in:クロックNRESET_in :リセット(負論理)
SCLK_out:SPI通信のSCLK信号
CS_out:SPI信号のCS信号
MOSI_out:SPI信号のMOSI信号
MISO_in:SPI信号のMISO信号
DATA_BIT_NUM_in:SPI通信で送受信するデータのビット数を入力
SEND_DATA_in:SPI通信で送信したいデータを入力
RECE_DATA_out:SPI通信で受信したデータが出力される
DEVICE_SELECT_in:SPI通信を行うデバイスを入力。1を立てたビットのCSがLOWになる
SCLK_PERIOD_in:SCLKの周期の1/2を入力。クロックカウントの値で入力する。(例:CLKが100MHzでここの値を10にしたとき、SCLKの周期は0.2μsとなる)
POL_PHA_in:CPOL、CPHAの設定を入力。SCLKは0bit目が0ならIDLE LOW、1ならIDLE HIGH。1bit目が0なら立下りエッジでデータをラッチ、1なら立ち上がりエッジでデータをラッチする。
START_in:SPI通信をスタートするとき1を入力する。回路内ではこの信号の立ち上がりエッジで動作するため、再度通信をスタートさせる場合は一度0にした後1にしてやる必要がある。
STATUS_out:ステータスを出力する。0-5bit:現在のステートマシンの位置、6bit:busy(1なら通信中)、7bit:done(1ならSPI通信終了している)
動作について
回路が動き始めるとまずはIDLE状態になる。START_inの立ち上がりエッジを検出すると、DATA_SET状態になりSCLK_PERIOD_in、DATA_BIT_NUM_in、SEND_DATA_inの内容が内部レジスタに取り込まれ、DEVICE_SELECT_inで1が立っているbitと同じbitのCS_outがLOWになる。
その後、SCLK_ON_WAIT状態になり、パラメータCS_SCLK_TIMEで設定したクロックカウント分だけ待機する。
待機後、SEND_AND_RECEIVE状態になり、SCLK_PERIOD_in で設定したSCLK周期でDATA_BIT_NUM_inで設定したbit数ぶんの通信を行いFIN状態になり、IDLE状態に戻る。
DATA_BIT_NUM_in、SEND_DATA_in、DEVICE_SELECT_in、SCLK_PERIOD_in、POL_PHA_inといった入力信号はSTART_inの立ち上がりエッジまでには確定しておくこと。
テストベンチのソースコード
`timescale 1ns / 1ps
module SPI_IP_SIM_0;
localparam PERIOD = 8;
localparam END = 1000;
reg CLK_in;
reg NRESET_in;
reg MISO_in;
reg [31:0] DATA_BIT_NUM_in;
reg [31:0] SEND_DATA_in;
reg DEVICE_SELECT_in;
reg START_in;
reg [31:0] SCLK_PERIOD_in;
reg [1:0] POL_PHA_in;
SPI_IP #() SPI_IP
(
.CLK_in(CLK_in),
.NRESET_in(NRESET_in),
.SCLK_out(),
.CS_out(),
.MOSI_out(),
.MISO_in(MISO_in),
.DATA_BIT_NUM_in(DATA_BIT_NUM_in),
.SEND_DATA_in(SEND_DATA_in),
.DEVICE_SELECT_in(DEVICE_SELECT_in),
.RECE_DATA_out(),
.STATUS_out(),
.START_in(START_in),
.SCLK_PERIOD_in(SCLK_PERIOD_in),
.POL_PHA_in(POL_PHA_in)
);
always
begin
CLK_in = 0; #(PERIOD/2);
CLK_in = 1; #(PERIOD/2);
end
always
begin
MISO_in = 0; #(59);
MISO_in = 1; #(53);
end
initial
begin
NRESET_in = 1;
#(PERIOD*3) NRESET_in = 0;
#(PERIOD*3) DATA_BIT_NUM_in = 0;
SEND_DATA_in = 0;
DEVICE_SELECT_in = 0;
START_in = 0;
SCLK_PERIOD_in = 0;
POL_PHA_in = 0;
#(PERIOD*3) NRESET_in = 1;
#(PERIOD*3) POL_PHA_in = 2'b10;
#(PERIOD*3) DATA_BIT_NUM_in = 8;
SEND_DATA_in = 8'b10011011;
DEVICE_SELECT_in = 1;
SCLK_PERIOD_in = 10;
#(PERIOD*3) START_in = 1;
#(PERIOD*3) START_in = 0;
#(2000) START_in = 1;
#(PERIOD*3) START_in = 0;
#(PERIOD * END);
$stop;
end
endmodule
シミュレーション結果
立ち上がりでデータラッチ |
また読み取りデータは1010_1001であり、読み取り後RECE_DATA_outから0xA9(1001_0101)が出力されていることがわかる。
立ち下がりでデータラッチ |
また読み取りデータは1001_0101であり、読み取り後RECE_DATA_outから0x95(1001_0101)が出力されていることがわかる。
0 件のコメント:
コメントを投稿