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) 中, 供上层模块读取。
代码介绍:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Fraser Innovation Inc
// Engineer: WilliamG
//
// Create Date: 2020/06/22 10:58:53
// Design Name:
// Module Name: spi_cmd.v
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
/*
flash command
02h page program
20h subsector erase
d8h sector erase
c7h chip erase
9fh read ID
03h read
06h write enable
04h write disable
05h read status register
*/
module spi_cmd #
(
parameter SPI_MODE = "FLASH"
)
(
input phy_clk,
// phy interface
output spi_cs, //SPI片选
output spi_clk, //SPI时钟
output spi_mosi, //SPI主器件输出从器件输入
input spi_miso, //SPI主器件输入从器件输出
// 系统时钟域
input sys_clk,
input [17:0] spi_din,
input spi_wr,
input spi_rd,
output [7:0] spi_dout,
output spi_empty,
input spi_cmd_vld,
output spi_cmd_ack,
input rd_dev_state_en, // for spi flash
output [7:0] rd_dev_state, // read spi flash 05 state register, this value cannot be written to fifo
input rst_n
);
//=======================================================================================
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;
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];
//=======================================================================================
/*
定义 写fifo ; 18bit:
bit[17] == 0, bit[7:0] 是数据, bit[16:8] 无用;
bit[17] == 1 作为flash 的读标识 和 结束标识。 剩余的bit[16:0] 读spi 的 byte 数量;
举例 : 18'h2_0000 表示当前命令没有需要从flash 读取的数据。
18'h2_0003 表示当前命令需要从flash 读取3个数据。
每次flash 操作 都必须使用 18'b1x_xxxx_xxxx_xxxx_xxxx 作为最后一条命令,
即使没有要从flash读取数据,也要写入fifo 18'h2_0000 作为 本次命令传输结束。
举例:
读 spi flash ID
18'h0_009f // 发送READ-ID 命令
18'h2_0003 // 结束符,从flash 中读取3 bytes 数据
写 spi flash 0x20_0000 0x01,0x02,0x03
18'h0_0006 // write enable command
18'h2_0000 // 结束符,从flash 中读取0 bytes 数据
18'h0_0002 // write enable command
18'h0_0020 // flash 地址 最高8bit
18'h0_0000 // flash 地址 中间8bit
18'h0_0000 // flash 地址 最低8bit
18'h0_0001 // flash 写数据 0x01
18'h0_0002 // flash 写数据 0x02
18'h0_0003 // flash 写数据 0x03
18'h2_0000 // 结束符,从flash 中读取0 bytes 数据
擦除扇区 0x80_0000
18'h0_0020 // erase command
18'h0_0080 // flash 地址 最高8bit
18'h0_0000 // flash 地址 中间8bit
18'h0_0000 // flash 地址 最低8bit
18'h2_0000 // 结束符,从flash 中读取0 bytes 数据
*/
reg wfifo_rd = 0;
wire [17:0] wfifo_dout;
wire wfifo_empty;
SPI_FIFO_WR SPI_FIFO_WR_inst
(
.wr_clk (sys_clk), // input wire wr_clk
.din (spi_din), // input wire [17 : 0] din
.wr_en (spi_wr), // input wire wr_en
.full (), // output wire full
.rd_clk (phy_clk), // input wire rd_clk
.rd_en (wfifo_rd), // input wire rd_en
.dout (wfifo_dout), // output wire [17 : 0] dout
.valid (), // output wire valid
.empty (wfifo_empty), // output wire empty
.rst (!rst_n) // input wire rst
);
//=======================================================================================
reg [31:0] rd_len = 0;
reg spi_rev_en = 0;
reg spi_cs_cmd = 1;
reg [7:0] flash_din = 8'hff;
reg flash_din_rdy = 0;
wire [7:0] flash_dout;
wire flash_dout_vld;
wire flash_data_done;
reg [31:0] spi_cnt = 0;
reg [2:0] spi_st = 0;
always @ (posedge phy_clk or negedge rst_n)
if(!rst_n)
begin
spi_rev_en <= 0;
phy_cmd_ack <= 0;
wfifo_rd <= 0;
flash_din_rdy <= 0;
spi_cs_cmd <= 1;
spi_st <= 0;
end
else case(spi_st)
0:
begin
phy_cmd_ack <= 0;
spi_cs_cmd <= 1;
spi_rev_en <= 0;
spi_cnt <= 0;
flash_din <= 8'hff;
wfifo_rd <= 0;
if(pos_cmd_vld)
begin
spi_st <= 1;
end
end
1:
begin
if(!wfifo_empty)
begin
wfifo_rd <= 1;
spi_st <= 2;
end
end
2:
begin
spi_cs_cmd <= 0;
wfifo_rd <= 0;
spi_st <= 3;
end
3:
begin
flash_din <= wfifo_dout[7:0];
flash_din_rdy <= 1;
wfifo_rd <= 1;
spi_st <= 4;
end
4:
begin
wfifo_rd <= 0;
flash_din_rdy <= 0;
spi_st <= 5;
end
5:
begin
flash_din_rdy <= 0;
if(flash_data_done)
begin
if(wfifo_dout[17])
begin
rd_len <= wfifo_dout[16:0];
spi_cnt <= 1;
flash_din <= 8'hff;
if(wfifo_dout[16:0] == 0) spi_st <= 7;
else
begin
flash_din_rdy <= 1;
spi_st <= 6;
end
end
else
begin
flash_din <= wfifo_dout[7:0];
flash_din_rdy <= 1;
wfifo_rd <= 1;
spi_st <= 4;
end
end
end
6:
begin
spi_rev_en <= 1;
flash_din_rdy <= 0;
if(flash_data_done)
begin
if(spi_cnt == rd_len)
spi_st <= 7;
else
begin
spi_cnt <= spi_cnt + 1;
flash_din <= 8'hff;
flash_din_rdy <= 1;
end
end
end
7:
begin
spi_cs_cmd <= 1;
spi_rev_en <= 0;
flash_din_rdy <= 0;
phy_cmd_ack <= 1;
spi_st <= 0;
end
default : spi_st <= 0;
endcase
reg [7:0] rx_fifo_din = 0;
reg rx_fifo_we = 0;
//=======================================================================================
// 系统上层程序 发送 rd_dev_state_en, 想要得到 rd_dev_state。 从phy_clk 时钟域 转换到 sys_clk 时钟域
reg [7:0] rd_dev_state_r = 0;
always @ (posedge sys_clk)
if(rd_dev_state_en)
rd_dev_state_r <= rx_fifo_din;
else
rd_dev_state_r <= 0;
assign rd_dev_state = rd_dev_state_r;
// 从sys_clk 时钟域 转换到 phy_clk 时钟域
reg rd_dev_state_en_phy = 0;
always @ (posedge phy_clk)
rd_dev_state_en_phy <= rd_dev_state_en;
//=======================================================================================
// 05 寄存器读取的内容,没有被写进fifo, 当然也是可以写入的, 再由上层程序读取。
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;
wire rx_fifo_full;
SPI_FIFO_RD SPI_FIFO_RD_inst
(
.wr_clk (phy_clk), // input wire wr_clk
.din (rx_fifo_din), // input wire [7 : 0] din
.wr_en (rx_fifo_we), // input wire wr_en
.full (rx_fifo_full), // output wire full
.rd_clk (sys_clk), // input wire rd_clk
.rd_en (spi_rd), // input wire rd_en
.dout (spi_dout), // output wire [7 : 0] dout
.valid (), // output wire valid
.empty (spi_empty), // output wire empty
.rst (!rst_n) // input wire rst
);
//=======================================================================================
// spi_phy 驱动模块
spi_phy spi_phy_inst
(
.phy_clk (phy_clk),
.spi_cs_cmd (spi_cs_cmd),
.flash_din (flash_din),
.flash_din_rdy (flash_din_rdy),
.flash_data_done (flash_data_done),
.flash_dout (flash_dout),
.flash_dout_vld (flash_dout_vld),
.spi_cs (spi_cs), // SPI片选
.spi_clk (spi_clk), // SPI 时钟
.spi_mosi (spi_mosi), // SPI主器件输出从器件输入
.spi_miso (spi_miso), // SPI主器件输入从器件输出
.rst_n (rst_n)
);
//=======================================================================================
endmodule
模块接口介绍
input phy_clk, // phy interface 和其他spi 设备连接的pin output spi_cs, //SPI片选 output spi_clk, //SPI时钟 output spi_mosi, //SPI主器件输出从器件输入 input spi_miso, //SPI主器件输入从器件输出 // 系统时钟域 input sys_clk, // 上层模块(系统时钟域) 时钟 input [17:0] spi_din, // 上层模块(系统时钟域) 写入spi_fifo_wr (IP) 的数据 input spi_wr, // 上层模块(系统时钟域) 写入spi_fifo_wr 写信号 input spi_rd, // 上层模块(系统时钟域) 读 spi_fifo_rd 读信号 output [7:0] spi_dout, // 上层模块(系统时钟域) 读 spi_fifo_rd 中的数据(由 spi_phy.v 写入) output spi_empty, // spi_fifo_rd (IP) 空 信号指示 input spi_cmd_vld, // 上层模块(系统时钟域)通知本模块 状态机, 上层命令发送完毕, 本模块可以读取spi_fifo_wr 中的数据,并执行当前spi 操作, 多个系统时钟宽度。 output spi_cmd_ack, // 本模块(phy时钟域)返回给上层模块, 通知当前的命令(指令)已经执行完毕, 等待上层模块发送新的命令,多个phy 时钟宽度。 input rd_dev_state_en, // 上层模块(系统时钟域)读 spi flash (spi 设备) 05 寄存器(stutas register) output [7:0] rd_dev_state, // 上层模块(系统时钟域)05 寄存器 的数据, input rst_n
在本模块接口中,上层模块的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 。 (结束标志, 读取长度 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 信号操作
// 系统上层程序 发送 rd_dev_state_en, 想要得到 rd_dev_state。 从phy_clk 时钟域 转换到 sys_clk 时钟域
reg [7:0] rd_dev_state_r = 0;
always @ (posedge sys_clk)
if(rd_dev_state_en)
rd_dev_state_r <= rx_fifo_din;
else
rd_dev_state_r <= 0;
assign rd_dev_state = rd_dev_state_r;
// 从sys_clk 时钟域 转换到 phy_clk 时钟域
reg rd_dev_state_en_phy = 0;
always @ (posedge phy_clk)
rd_dev_state_en_phy <= rd_dev_state_en;
//=======================================================================================
// 05 寄存器读取的内容,没有被写进fifo, 当然也是可以写入的, 再由上层程序读取。
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;
将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
wire rx_fifo_full;
SPI_FIFO_RD SPI_FIFO_RD_inst
(
.wr_clk (phy_clk), // input wire wr_clk
.din (rx_fifo_din), // input wire [7 : 0] din
.wr_en (rx_fifo_we), // input wire wr_en
.full (rx_fifo_full), // output wire full
.rd_clk (sys_clk), // input wire rd_clk
.rd_en (spi_rd), // input wire rd_en
.dout (spi_dout), // output wire [7 : 0] dout
.valid (), // output wire valid
.empty (spi_empty), // output wire empty
.rst (!rst_n) // input wire rst
);
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 只是其中的一种应用罢了。





