寫verilog第一步肯定需要將輸入輸出端口,常量等信息補齊全;
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
);
設置SPI_ADDR_WIDTH標記SPI傳輸數據命令的寄存器地址值寬度,SPI_CMD_WIDTH變量標記SPI傳輸數據的整體寬度。
輸入輸出變量中,clk時鐘信號和rst復位信號都是必備的系統信號;除去SPI接口的4根數據線外,還有輸入的24位cmd_data,將需要發送的數據從這個端口傳遞給SPI處理;輸出的8位read_data,將讀取到的寄存器數據輸出便于做后續處理;以及控制信號,en控制SPI的工作速率,ready指示SPI發送工作的開始,sink_vld指示SPI發送讀取工作的結束。
上一篇文章談到,我們將整個SPI的發送讀取分為5個狀態,
SPI狀態機的5種狀態
現在我們需要捋順每個狀態跳轉的條件;IDLE空閑狀態跳轉到WRITE_ADDR寫地址狀態,說明此時需要發送SPI數據,所以ready信號是跳轉條件;從上圖可以看到,WRITE_ADDR寫地址狀態可以跳轉到WRITE_DATA狀態或READ狀態,而決定條件是SPI的命令是讀取命令還是寫入命令,這取決于SPI寫入數據的MSB(最高位);WRITE_DATA狀態和READ狀態跳轉回IDLE空閑狀態的條件是需要發送的數據已經發送完畢或需要讀取的數據已經讀取完畢。
另外,在WRITE_ADDR寫地址狀態,WRITE_DATA狀態和READ狀態里面需要用到計數器,記錄當前已經發送或讀取的數據量,作為跳出該狀態的判斷依據之一。
由于這部分的狀態機比較簡單,所以第一版我采用了一段式狀態機。為了便于理解SPI_CLK的產生,我選擇使用分頻操作生成SPI_CLK,但其實更推薦的方式是使用MMCM,PLL等方式產生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