寫verilog第一步肯定需要將輸入輸出端口,常量等信息補(bǔ)齊全;
module spi_ctrl
#(
parameter SPI_ADDR_WIDTH = 16,
parameter SPI_CMD_WIDTH = 24,
parameter SPI_IDLE = 0
)
(
/* System signal */
input clk ,
input rst ,
/* User Data */
input [23:0] cmd_data ,
output reg [ 7:0] read_data ,
input en ,
input ready ,
output reg sink_vld ,
/* SPI interface */
output reg spi_clk ,
output reg spi_enb ,
output reg spi_di ,
input spi_do
);
設(shè)置SPI_ADDR_WIDTH標(biāo)記SPI傳輸數(shù)據(jù)命令的寄存器地址值寬度,SPI_CMD_WIDTH變量標(biāo)記SPI傳輸數(shù)據(jù)的整體寬度。
輸入輸出變量中,clk時(shí)鐘信號(hào)和rst復(fù)位信號(hào)都是必備的系統(tǒng)信號(hào);除去SPI接口的4根數(shù)據(jù)線外,還有輸入的24位cmd_data,將需要發(fā)送的數(shù)據(jù)從這個(gè)端口傳遞給SPI處理;輸出的8位read_data,將讀取到的寄存器數(shù)據(jù)輸出便于做后續(xù)處理;以及控制信號(hào),en控制SPI的工作速率,ready指示SPI發(fā)送工作的開始,sink_vld指示SPI發(fā)送讀取工作的結(jié)束。
上一篇文章談到,我們將整個(gè)SPI的發(fā)送讀取分為5個(gè)狀態(tài),
SPI狀態(tài)機(jī)的5種狀態(tài)
現(xiàn)在我們需要捋順每個(gè)狀態(tài)跳轉(zhuǎn)的條件;IDLE空閑狀態(tài)跳轉(zhuǎn)到WRITE_ADDR寫地址狀態(tài),說明此時(shí)需要發(fā)送SPI數(shù)據(jù),所以ready信號(hào)是跳轉(zhuǎn)條件;從上圖可以看到,WRITE_ADDR寫地址狀態(tài)可以跳轉(zhuǎn)到WRITE_DATA狀態(tài)或READ狀態(tài),而決定條件是SPI的命令是讀取命令還是寫入命令,這取決于SPI寫入數(shù)據(jù)的MSB(最高位);WRITE_DATA狀態(tài)和READ狀態(tài)跳轉(zhuǎn)回IDLE空閑狀態(tài)的條件是需要發(fā)送的數(shù)據(jù)已經(jīng)發(fā)送完畢或需要讀取的數(shù)據(jù)已經(jīng)讀取完畢。
另外,在WRITE_ADDR寫地址狀態(tài),WRITE_DATA狀態(tài)和READ狀態(tài)里面需要用到計(jì)數(shù)器,記錄當(dāng)前已經(jīng)發(fā)送或讀取的數(shù)據(jù)量,作為跳出該狀態(tài)的判斷依據(jù)之一。
由于這部分的狀態(tài)機(jī)比較簡(jiǎn)單,所以第一版我采用了一段式狀態(tài)機(jī)。為了便于理解SPI_CLK的產(chǎn)生,我選擇使用分頻操作生成SPI_CLK,但其實(shí)更推薦的方式是使用MMCM,PLL等方式產(chǎn)生SPI_CLK。
localparam SPI_DATA_WIDTH = SPI_CMD_WIDTH - SPI_ADDR_WIDTH;
assign flag_write_addr_update = (cnt < SPI_ADDR_WIDTH && spi_clk == 1'b0) ? 1'b1 : 1'b0;
assign flag_write_addr_hold = (cnt < SPI_ADDR_WIDTH) ? 1'b1 : 1'b0 ;
assign flag_data_update = (cnt < SPI_DATA_WIDTH && spi_clk == 1'b0) ? 1'b1 : 1'b0 ;
assign flag_data_hold = (cnt < SPI_DATA_WIDTH) ? 1'b1 : 1'b0 ;
always @ (posedge clk or posedge rst)
begin
if (rst)
begin
spi_clk <= SPI_IDLE ;
spi_enb <= 1'b1 ;
spi_di <= 1'b0 ;
read_data <= 'd0 ;
sink_vld <= 1'b0 ;
state <= IDLE ;
cmd_data_r <= 'd0 ;
cnt <= 'd0 ;
flag_read <= 1'b0 ;
end
else if (en)
begin
case (state)
IDLE:
begin
if (ready)
begin
state <= WRITE_ADDR ;
spi_enb <= 1'b0 ;
cmd_data_r <=cmd_data ;
cnt <= 'd0 ;
flag_read <= !cmd_data[23] ;
end
sink_vld <= 1'b0;
spi_di <= 1'b0;
spi_enb <= 1'b1;
end
WRITE_ADDR:
begin
spi_enb <= 1'b0;
if (flag_write_addr_update)
begin
spi_di <= cmd_data_r[23] ;
cmd_data_r <= {cmd_data_r[22:0], 1'b0} ;
spi_clk <= 1'b1 ;
cnt <= cnt + 8'd1 ;
end
else if (flag_write_addr_hold)
begin
spi_clk <= 1'b0;
end
else
begin
if (flag_read)
state <= READ ;
else
state <= WRITE_DATA ;
cnt <= 'd0 ;
spi_clk <= 1'b0 ;
end
end
WRITE_DATA:
begin
if (flag_data_update)
begin
spi_di <= cmd_data_r[23] ;
cmd_data_r