Menu Close

SPI 通讯协议(5)SPI FLASH (verilog) 工程解析 (spi_cmd.v)

spi_phy.v 的上层模块是spi_cmd.v.  spi_cmd.v模块主要是负责命令(指令)的调度。 上层的系统模块可以将相关的spi 命令(指令)写入到spi_fifo_wr (ip 核)中, spi_cmd.v 模块通过读取spi_fifo_wr 的内容,得到相关的命令(指令),然后产生spi_phy.v 所需要的相关控制信号, 数据将通过spi_phy.v 模块发送到其他外设(spi 设备); 同时 ,从spi_phy.v 模块中读取到的数据,也会被存储到spi_cmd.v 模块中的spi_fifo_rd (ip核)中, 供spi_cmd.v 模块之上的系统模块调用。

参考文章:

SPI 通讯协议 及 SPI 相关工程 详解

 

spi_cmd.v 模块介绍

 

spi_cmd.v 模块 下层连接spi_phy.v,上层模块为SPI_IF.V  。  spi_cmd.v 和 spi_phy.v 模块都是使用的phy_clk 时钟的, 他们的上层模块将使用系统时钟(sys_clk)。使用fifo 作为两个时钟域的接口是比较适合的。spi_cmd.v 模块 负责将上层模块的指令(fifo 中) 正确的发送给spi_phy.v 模块, 同时将spi_phy.v 模块中读到的数据写入(spi_fifo_rd) 中, 供上层模块读取。

代码介绍:


 

 

模块接口介绍

 


在本模块接口中,上层模块的spi 命令主要是通过fifo 进行交互的, 这样可以隔离不同的时钟域。

spi_cmd_vld, spi_cmd_ack 这对信号用于通知上层和本层模块:命令是否可以执行, 命令是否执行完毕, 多是多时钟周期的信号, 然后对这些信号采集上升沿 , 就可以实现时钟域转换了。

rd_dev_state_en, rd_dev_state 这对信号用于对spi flash 的05 寄存器进行读取, 当然也可以使用spi_fifo_rd 进行读取, 因为05寄存器需要反复读取,作为上层模块的某些固定操作,可以使用这对信号直接读取,不再使用spi_fifo_rd 通道了。

 

 

spi_cmd_vld, spi_cmd_ack  信号转换

 

reg [1:0] phy_cmd_vld = 0;
always @ (posedge phy_clk or negedge rst_n)
if(!rst_n) phy_cmd_vld <= 0;
else phy_cmd_vld <= {phy_cmd_vld[0], spi_cmd_vld};

wire pos_cmd_vld = (phy_cmd_vld == 2’b01) ? 1’b1 : 1’b0;

对上层模块spi_cmd_vld 信号(sys_clk 时钟域)在phy 时钟域下采样,获得上升沿 pos_cmd_vld 供本层模块使用。这也是异步时钟域信号转换的一种方法。

reg phy_cmd_ack = 0;
reg [7:0] phy_cmd_ack_r = 0;
always @ (posedge phy_clk )
if(phy_cmd_ack) phy_cmd_ack_r <= 8’hff;
else phy_cmd_ack_r <= {phy_cmd_ack_r[6:0], 1’b0};

assign spi_cmd_ack = phy_cmd_ack_r[7];

本层模块产生的单沿信号 phy_cmd_ack 进行多时钟周期延迟,产生长周期(phy_clk)  信号,供上层模块异步采样。

命令写fifo 的数据结构

 

这里我们设计了一个spi_fifo_wr 数据结构, 这个fifo  数据的宽度为18bit, 不同的spi 操作(transaction)可能需要很多条命令才能完成。举例: spi flash 写操作: 02 + addr[23:16] + addr[15:08] + addr[07:00] + data0 + data1 + data2 + data3 + ……

命令格式:

1)所有命令都是8bit的, bit[17] 都必须 为 0, bit[16:08] 是多少,不重要, 也不关心。

2)所有命令都必须有且只有一条读操作的长度(RD LENGTH) ,作为最后一条指令 ,同时也是本次操作的结束标志 bit[17] 必须为 1; bit[16:00] 代表 读取数据(bytes)次数,如果不需要读取bit[16:00] = 0, 最多可以读出 17’h 1_1111_1111 这么多个byes 的数据。

3)  所有写操作 (不需要从spi flash 设备中读取数据) ,最后一条指令必须是 18’h2_0000_0000 。 (结束标志, 读取长度 0 byte)。

4)如果有读spi 的操作 ,最后一条指令必须是 (18’h2_0000_0000 | RD LENGTH) 。 (结束标志, 读取长度 RD LENGTH bytes)。

5)fifo中只能有一个操作(transaction) ,每个操作可能有多个命令。例如:02 + addr[23:16] + addr[15:08] + addr[07:00] + data0 + data1 + data2 + data3 。 向spi flash 某个地址写入 4 个数据,这里就有8 个命令  +  结束命令 (18’h2_0000_0000) 共同组成一个操作(transaction)。

 

命令举例:

1) 在 0x30_0000 地址 连续写 0x01, 0x02,0x03, 0x04  4个bytes 数据

操作(transaction)1:  // 打开spi flash 写有效

18’h0_0006

18’h2_0000

操作(transaction)2:  // 在 0x30_0000 地址 连续写 0x01, 0x02,0x03, 0x04  4个bytes 数据

18’h0_0002

18’h0_0030

18’h0_0000

18’h0_0000

18’h0_0001

18’h0_0002

18’h0_0003

18’h0_0004

18’h2_0000

 

操作(transaction)3:  // 连续读取05 寄存器,直到 状态寄存器返回的数据 最低位 为 0

18’h0_0005

18’h2_0001

 

2) 从spi flash 读取DEV ID 3 bytes

18’h0_009f

18’h2_0003

3) 从 0x40_0000 读取 11个数据

18’h0_0003

18’h0_0040

18’h0_0000

18’h0_0000

18’h2_000b

 

状态机解析:

状态机主要用于读取spi_fifo_wr 中的数据,同时将相关的命令发给spi_phy.v

状态0:空闲状态, 等待 fifo中填满一条操作(transaction),上层会发送一个操作准备好的信号,经过时钟域转换得到pos_cmd_vld, 这时,准备读取spi_fifo_wr 中的数据了,跳转状态1

状态1:如果fifo 不是空, 开始读取spi_fifo_wr , 跳转状态2

状态2:由于使用的是标准的fifo , 所以需要等待一个时钟周期,才能得到fifo里的数据。 跳转状态3 

状态3:将fifo中读取到的数据发送给spi_phy.v  同时发送 flash_din_rdy 信号给spi_phy.v 模块 ; 读取spi_fifo_wr 中的下一个数据, 跳转状态4

状态4:跳转状态5

状态5:如果spi_phy.v 完成数据的发送, 那就看一下最新从fifo中读取到的数据格式。

如果从fifo中读到的数据有结束标志(bit 17 == 1),

if(RD LENGTH == 0),   跳转状态 7

else  连续发送 8  个时钟, 这时的 flash_din 是多少已经不重要了, spi 控制器 只要连续发送 时钟, 数据就会从miso 线上返回。 跳转状态 6

如果从fifo中读到的数据结束标志(bit 17 == 0) , 表示还有命令需要发送, 发送当前的数据到spi_phy.v  , 从fifo中读下一个数据,跳转状态 4

状态6:准备开始接收 miso 线过来的数据 spi_rev_en = 1。 同时spi_cnt 计数器 加一。

if(RD LENGTH == spi_cnt),   跳转状态 7

else  继续发送 时钟。

状态7:所有操作(transcation 结束) ,通知上层模块 本次操作结束(phy_cmd_ack = 1) . 返回状态0

 

rd_dev_state_en,  rd_dev_state  信号操作

 


将spi_phy.v 读取到的数据赋值给 rd_dev_state。 同时 将 rd_dev_state_en 同步到phy_clk 时钟域上。

接下来就是 spi_fifo_rd (IP ) 的写入操作了, 如果不希望 05 状态寄存器写入 fifo 使用:

rx_fifo_we <= (SPI_MODE == “FLASH”) ? ~rd_dev_state_en_phy : 1’b1;

如果认为单独提取 05 状态 不是非常必要,上层可以读取fifo ,得到 05 寄存器的状态 可以设置:

rx_fifo_we <= 1’b1;

目前工程中是将上层模块某些固定的操作中05 寄存器的读取,单独使用, 而不是写入到fifo中。

SPI 数据读取fifo

 


 

spi 接收fifo 主要用于从spi_phy.v 的miso 线上接收数据,经过 串行到并行的decode ,得到 8bit 的接收数据。

always @ (posedge phy_clk)

if(flash_dout_vld & spi_rev_en)

begin

    rx_fifo_din <= flash_dout;

    rx_fifo_we <= (SPI_MODE == “FLASH”) ? ~rd_dev_state_en_phy : 1’b1;

// rx_fifo_we <= 1’b1;

end

else rx_fifo_we <= 0;

其中 flash_dout, flash_dout_vld 是从flahs_phy.v 模块传输过来的, 同时配合本层状态机逻辑 spi_rev_en  (接收数据 允许)。 最终得到 rx_fifo_din, rx_fifo_we 这两个fifo 接口上的信号。

spi_fifo_rd  使用的的是标准fifo, 8bit 输入, 8bit输出, 没有相关的数据结构,即:所有数据都是从spi_phy.v 接收来的。 上层模块只需要读取这个fifo , 就可以得到相应的数据了。

 

仿真波形

spi_fifo_wr  fifo中输入 18’h0_009f,  18’h2_0003 。  发送9f 命令给spi flash , 从flash 中得到相应的read-id 信息。

 

 

总结

到此, spi_cmd.v 模块介绍完毕, 大家可以注意到,这个模块本身(配合spi_phy.v)  不但可以和spi flash (外设)通讯, 也可以连接其他种类的spi (外设)。 这个模块本身是和spi flash 无关的, 当然,spi flash 只是其中的一种应用罢了。

 

Posted in FPGA, FPGA 教材教案, IP开发, RISC-V IPcore设计, RISC-V 教材教案, RISC-V架构, 元器件, 存储器, 教材与教案, 文章, 资料区