电子说
写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 <= {cmd_data_r[