spi_IF.v 模块为上层的系统模块, 这个模块 主要是接收系统的命令, 同时向下面的模块(spi_cmd.v) 发送spi 的指令。 这个模块的主要时钟是sys_clk ,通过fifo 和 异步握手方式和spi_cmd.v 模块通讯。 通过接收上层模块的指令,得到相应的操作命令。
参考文章:
SPI 通讯协议 及 SPI 相关工程 详解
spi_IF.v 结构图
spi_IF.v 模块主要是从上层模块 接收 几个按键的信息:在当前的例子中会接收:
1)spi flash DEV-ID ,
2)向 spi flash 中的 0x80_0000 地址 写入 128 bytes 的数据 , 数据为 0x00 – 0x7f
3)向 spi flash 中的 0x80_0000 地址 读取 128 bytes 的数据。
4)擦除 地址为 0x80_0000 的扇区
除此之外, spi_IF.v 的状态机 还可以接收 上层模块传递下来的 uart 信息,在uart 模块中, 可以产生各种不同的spi 命令(不但可以控制spi flash, 也可以用来控制其他的spi 设备)。
代码介绍:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Fraser Innovation Inc
// Engineer: WilliamG
//
// Create Date: 04/21/2021 02:23:43 PM
// Design Name:
// Module Name: spi_IF.v
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module spi_IF #
(
SIM_MODE = "FALSE"
)
(
input sys_clk, // 系统时钟
input pb_rd_id, // PB7作为读id
input pb_wr, // PB4作为写数据 128Byte , 地址为80_0000, 数据 00 -- 127
input pb_rd, // PB6作为读数据 128Byte , 地址为80_0000, 数据 00 -- 127
input pb_erase, // PB2作为擦除扇区指令, 地址为80_0000,
output spi_cmd_done, // transaction ok , 当前整个操作结束标志
input spi_rd, // 读信号,读fifo中 由 spi 接收到的数据
output [7:0] spi_dout, // 读fifo中 由 spi 接收到的数据
output spi_empty, // fifo empty 标志
output reg uart_fifo_rd = 0, // 读信号,读uart 接收fifo 中的数据
input [7:0] uart_fifo_dout, // 读uart 接收fifo 中的数据
input uart_fifo_empty, // uart fifo empty 标志
input uart_done, // uart 接收一个操作结束, 所有命令,数据已经在uart fifo 中
input phy_clk, // SPI时钟
output FLASH_CLK, // SPI时钟
output FLASH_CS, // SPI片选
output FLASH_MOSI, // SPI主器件输出从器件输入
input FLASH_MISO, // SPI主器件输入从器件输出
input rst_n
);
//=======================================================================================
localparam PROG_CMD = 8'h01;
localparam ERASE_CMD = 8'h02;
wire [7:0] wr_addr[2:0];
wire [7:0] rd_addr[2:0];
wire [7:0] er_addr[2:0];
assign wr_addr[2] = (SIM_MODE == "TRUE") ? 8'h10 : 8'h80;
assign wr_addr[1] = (SIM_MODE == "TRUE") ? 8'h00 : 8'h00;
assign wr_addr[0] = (SIM_MODE == "TRUE") ? 8'h00 : 8'h00;
assign rd_addr[2] = (SIM_MODE == "TRUE") ? 8'h10 : 8'h80;
assign rd_addr[1] = (SIM_MODE == "TRUE") ? 8'h00 : 8'h00;
assign rd_addr[0] = (SIM_MODE == "TRUE") ? 8'h00 : 8'h00;
assign er_addr[2] = (SIM_MODE == "TRUE") ? 8'h10 : 8'h80;
assign er_addr[1] = (SIM_MODE == "TRUE") ? 8'h00 : 8'h00;
assign er_addr[0] = (SIM_MODE == "TRUE") ? 8'h00 : 8'h00;
//=======================================================================================
wire spi_cmd_ack;
reg [1:0] sys_cmd_ack = 0;
always @ (posedge sys_clk or negedge rst_n)
if(!rst_n) sys_cmd_ack <= 0;
else sys_cmd_ack <= {sys_cmd_ack[0], spi_cmd_ack};
wire pos_cmd_ack = (sys_cmd_ack == 2'b01) ? 1'b1 : 1'b0;
reg sys_cmd_vld = 0;
reg [7:0] sys_cmd_vld_r = 0;
always @ (posedge sys_clk )
if(sys_cmd_vld) sys_cmd_vld_r <= 8'hff;
else sys_cmd_vld_r <= {sys_cmd_vld_r[6:0], 1'b0};
wire spi_cmd_vld = sys_cmd_vld_r[7];
//=======================================================================================
(* mark_debug = "true" *)reg [17:0] spi_din = 0;
(* mark_debug = "true" *)reg spi_wr = 0;
reg rd_dev_state_en = 0;
wire [7:0] rd_dev_state; // flash 05 state register
reg spi_cmd_done_r = 1;
reg [7:0] curr_cmd = 0;
reg [7:0] test_cnt = 0;
(* mark_debug = "true" *)reg [4:0] cmd_st = 0;
always @ (posedge sys_clk)
if(!rst_n)
begin
uart_fifo_rd <= 0;
rd_dev_state_en <= 0;
spi_cmd_done_r <= 1;
sys_cmd_vld <= 0;
spi_wr <= 0;
cmd_st <= 0;
end
else case (cmd_st)
0:
begin
uart_fifo_rd <= 0;
curr_cmd <= 0;
spi_cmd_done_r <= 1;
sys_cmd_vld <= 0;
spi_wr <= 0;
test_cnt <= test_cnt + 1;
if(&test_cnt)
cmd_st <= 1;
end
1:
begin
rd_dev_state_en <= 0;
test_cnt <= 0;
curr_cmd <= 0;
sys_cmd_vld <= 0;
spi_wr <= 0;
if(pb_rd_id) //PB5作为读id
cmd_st <= 2;
else if(pb_wr) //PB2作为写数据 128Byte , 地址为80_0000, 数据 00 -- 127
begin
curr_cmd <= PROG_CMD;
cmd_st <= 5;
end
else if(pb_erase) //PB1作为擦除扇区指令, 地址为80_0000,
begin
curr_cmd <= ERASE_CMD;
cmd_st <= 5;
end
else if(pb_rd) //PB3作为读数据 128Byte , 地址为80_0000, 数据 00 -- 127
cmd_st <= 22;
else if(uart_done)
cmd_st <= 26;
end
2: //read flash dev id
begin
spi_cmd_done_r <= 0;
spi_din <= 8'h9f; // command
spi_wr <= 1;
cmd_st <= 3;
end
3:
begin
spi_din <= 18'h2_0003;
spi_wr <= 1;
sys_cmd_vld <= 1;
cmd_st <= 4;
end
4:
begin
spi_wr <= 0;
sys_cmd_vld <= 0;
if(pos_cmd_ack)
cmd_st <= 31;
end
5: // write 06 register : write enable 写数据 128Byte , 地址为80_0000, 数据 00 -- 127
begin
spi_cmd_done_r <= 0;
spi_din <= 8'h06; // command write enable
spi_wr <= 1;
cmd_st <= 6;
end
6:
begin
spi_din <= 18'h2_0000;
spi_wr <= 1;
sys_cmd_vld <= 1;
cmd_st <= 7;
end
7:
begin
spi_wr <= 0;
sys_cmd_vld <= 0;
if(pos_cmd_ack)
begin
if(curr_cmd == PROG_CMD)
cmd_st <= 8;
else
cmd_st <= 18;
end
end
8: // write data into flash , 1:0x02 register (write command); 2: flash address 0x80_0000; 3: write data 0- 255
begin
spi_din <= 8'h02; // command write enable
spi_wr <= 1;
test_cnt <= 2;
cmd_st <= 9;
end
9: // write flash address 0x80_0000
begin
if(test_cnt == 0)
cmd_st <= 10;
else
begin
spi_din <= wr_addr[test_cnt];
spi_wr <= 1;
test_cnt <= test_cnt - 1;
end
end
10: // write data 0 - 127
begin
spi_din <= 0;
spi_wr <= 1;
test_cnt <= 1;
cmd_st <= 11;
end
11: // write data 0 - 127
begin
spi_din <= spi_din + 1;
spi_wr <= 1;
test_cnt <= test_cnt + 1;
if(test_cnt == 127)
cmd_st <= 12;
end
12:
begin
test_cnt <= 0;
spi_din <= 18'h2_0000; // no read data
spi_wr <= 1;
sys_cmd_vld <= 1;
cmd_st <= 13;
end
13:
begin
spi_wr <= 0;
sys_cmd_vld <= 0;
if(pos_cmd_ack)
cmd_st <= 14;
end
14: // read status register
begin
test_cnt <= test_cnt + 1;
if(test_cnt == 128)
begin
spi_din <= 8'h05; // read status register
spi_wr <= 1;
cmd_st <= 15;
end
end
15:
begin
test_cnt <= 0;
spi_din <= 18'h2_0001;
spi_wr <= 1;
sys_cmd_vld <= 1;
rd_dev_state_en <= 1;
cmd_st <= 16;
end
16:
begin
spi_wr <= 0;
sys_cmd_vld <= 0;
if(pos_cmd_ack)
cmd_st <= 17;
end
17:
begin
rd_dev_state_en <= 0;
if(rd_dev_state[0])
cmd_st <= 14;
else cmd_st <= 31;
end
18: // erase_cmd
begin
spi_din <= 8'h20; // erase command
spi_wr <= 1;
test_cnt <= 2;
cmd_st <= 19;
end
19: // erase_sector
begin
if(test_cnt == 0)
cmd_st <= 20;
else
begin
spi_din <= er_addr[test_cnt]; // erase sector 0x80_0000
spi_wr <= 1;
test_cnt <= test_cnt - 1;
end
end
20:
begin
test_cnt <= 0;
spi_din <= 18'h2_0000;
spi_wr <= 1;
sys_cmd_vld <= 1;
cmd_st <= 21;
end
21:
begin
spi_wr <= 0;
sys_cmd_vld <= 0;
if(pos_cmd_ack)
cmd_st <= 14;
end
22: //read flash
begin
spi_din <= 8'h03; // read command
spi_wr <= 1;
test_cnt <= 2;
cmd_st <= 23;
end
23: // read start address
begin
spi_cmd_done_r <= 0;
if(test_cnt == 0)
cmd_st <= 24;
else
begin
spi_din <= rd_addr[test_cnt]; // erase sector 0x80_0000
spi_wr <= 1;
test_cnt <= test_cnt - 1;
end
end
24:
begin
test_cnt <= 0;
spi_din <= 18'h2_0080;
spi_wr <= 1;
sys_cmd_vld <= 1;
cmd_st <= 25;
end
25:
begin
spi_wr <= 0;
sys_cmd_vld <= 0;
if(pos_cmd_ack)
cmd_st <= 31;
end
26: // uart rev interface
begin
if(!uart_fifo_empty)
begin
uart_fifo_rd <= 1;
cmd_st <= 27;
end
end
27:
begin
spi_wr <= 0;
uart_fifo_rd <= 0;
cmd_st <= 28;
end
28:
begin
if(uart_fifo_empty)
begin
spi_din <= {2'b10, 8'h00, uart_fifo_dout};
spi_wr <= 1;
sys_cmd_vld <= 1;
cmd_st <= 29;
end
else
begin
spi_cmd_done_r <= 0;
spi_din <= uart_fifo_dout;
spi_wr <= 1;
uart_fifo_rd <= 1;
cmd_st <= 27;
end
end
29:
begin
spi_wr <= 0;
sys_cmd_vld <= 0;
if(pos_cmd_ack)
cmd_st <= 31;
end
/*
28: // disable write
begin
spi_din <= 8'h04; // write disable
spi_wr <= 1;
cmd_st <= 29;
end
29:
begin
spi_din <= 18'h2_0000;
spi_wr <= 1;
sys_cmd_vld <= 1;
cmd_st <= 30;
end
30:
begin
spi_wr <= 0;
sys_cmd_vld <= 0;
if(pos_cmd_ack)
cmd_st <= 31;
end
*/
31:
begin
spi_cmd_done_r <= 1;
cmd_st <= 1;
end
default : cmd_st <= 0;
endcase
assign spi_cmd_done = spi_cmd_done_r;
spi_cmd #(.SPI_MODE ("FLASH") )
spi_cmd_inst
(
.phy_clk (phy_clk), //主时钟
.spi_cs (FLASH_CS), //SPI时钟
.spi_clk (FLASH_CLK), //SPI片选
.spi_mosi (FLASH_MOSI), //SPI主器件输出从器件输入
.spi_miso (FLASH_MISO), //SPI主器件输入从器件输出
.sys_clk (sys_clk),
.spi_din (spi_din),
.spi_wr (spi_wr),
.spi_rd (spi_rd),
.spi_dout (spi_dout),
.spi_empty (spi_empty),
.spi_cmd_vld (spi_cmd_vld),
.spi_cmd_ack (spi_cmd_ack),
.rd_dev_state_en (rd_dev_state_en), // for spi flash
.rd_dev_state (rd_dev_state), // read spi flash 05 state register, this value cannot be written to fifo
.rst_n (rst_n)
);
//=======================================================================================
endmodule
接口介绍
input sys_clk, // 系统时钟 input pb_rd_id, // 按键中提取信号:作为读id input pb_wr, // 按键中提取信号:作为写数据 128Byte , 地址为80_0000, 数据 00 -- 127 input pb_rd, // 按键中提取信号:作为读数据 128Byte , 地址为80_0000, 数据 00 -- 127 input pb_erase, // 按键中提取信号:作为擦除扇区指令, 地址为80_0000, output spi_cmd_done, // transaction ok , 当前整个操作结束标志 input spi_rd, // 读信号,读fifo(spi_fifo_rd)中 由 spi_phy.v 接收到的数据 output [7:0] spi_dout, // 读fifo(spi_fifo_rd)中 由 spi_phy.v 接收到的数据 output spi_empty, // fifo empty 标志(spi_fifo_rd) output reg uart_fifo_rd = 0, // 读信号,读uart 接收fifo 中的数据 input [7:0] uart_fifo_dout, // 读uart 接收fifo 中的数据 input uart_fifo_empty, // uart fifo empty 标志 input uart_done, // uart 接收一个操作结束, 所有命令,数据已经在uart fifo 中 input phy_clk, // SPI工作时钟 output FLASH_CLK, // SPI时钟 output FLASH_CS, // SPI片选 output FLASH_MOSI, // SPI主器件输出从器件输入 input FLASH_MISO, // SPI主器件输入从器件输出 input rst_n
与spi_cmd.v 交互的异步握手逻辑
wire spi_cmd_ack; reg [1:0] sys_cmd_ack = 0; always @ (posedge sys_clk or negedge rst_n) if(!rst_n) sys_cmd_ack <= 0; else sys_cmd_ack <= {sys_cmd_ack[0], spi_cmd_ack}; wire pos_cmd_ack = (sys_cmd_ack == 2'b01) ? 1'b1 : 1'b0;
reg sys_cmd_vld = 0; reg [7:0] sys_cmd_vld_r = 0; always @ (posedge sys_clk ) if(sys_cmd_vld) sys_cmd_vld_r <= 8'hff; else sys_cmd_vld_r <= {sys_cmd_vld_r[6:0], 1'b0}; wire spi_cmd_vld = sys_cmd_vld_r[7]; //=======================================================================================
状态机逻辑:
状态机0:初始化所有变量状态,等待上电时所有fifo 复位结束, 跳转状态1
状态机1:空闲状态,等待上层模块的相关spi 操作,如果:
按键读取spi flash ID命令, 跳转状态2
按键写数据命令, 跳转状态5
按键擦除命令, 跳转状态5
按键读数据, 跳转状态22
uart 接收到spi 命令, 跳转状态26
// 读取spi flash ID
状态机2:发送spi flash ID 读命令,向spi_fifo_wr 中写入 (18’h0_009f)跳转状态3
状态机3:发送spi flash 读长度3,结束命令,向spi_fifo_wr 中写入 (18’h2_0003)跳转状态4
状态机4:等待下层模块发送结束, 跳转状态31
// 设置spi flash 06 寄存器 (打开写操作)
状态机5:写spi flash 06 寄存器,write enable 寄存器打开, 允许对spi flash 进行写(编程),擦除等操作, 跳转状态6
状态机6:没有需要从spif flash 读的数据,结束标志,跳转状态7
状态机7:f当前的操作(transaction) 结束后,如果是写(编程)操作, 跳转8; 如果是擦除操作,跳转18
// 写(编程) 操作
状态机8:写(编程)操作, 02 命令字,跳转9
状态机9:连续写3次spi flash 地址, 跳转 10
状态机10:写 0x00 数据到spi flash 中 ,跳转11
状态机11:写0x01 – 0x7f 数据到 spi flash 中, 跳转12
状态机12:没有数据需要读取, 发送结束标志。 跳转13
状态机13:等待当前的操作(transaction) 结束,即 spi_phy.v 完全将数据发送到spi 设备时,跳转14
// 读spi flash 05 寄存器 (读取flash 的状态寄存器内容)
状态机14:读spi flash 05 (状态寄存器)操作(transaction), 先等待128个clock (没有必要快速读取05 寄存器, spi flash 需要一定的反映时间,然后spi flash 设备才能更新05 状态寄存器),跳转15
状态机15:发送结束标志,从spi flash 中准备读取 1 byte数据, 跳转16
状态机16:等待操作(transaction)结束,跳转17
状态机17:检查05 状态寄存器的数据,如果 bit[0] == 1,编程或者擦除等命令还没有结束,重新检查05寄存器, 跳转14; 如果 bit[0] == 0, 跳转31。
// 擦除操作
状态机18:发送擦除命令,跳转19
状态机19:连续发送3bytes 的地址到spi flash, 跳转20
状态机20:发送结束标志,没有数据需要从spi flash 中读取。 跳转21
状态机21:等待操作(transaction) 结束,跳转14。 (检查05 状态寄存器)
// 读spi flash 操作
状态机22:发送读spi flash 命令, 跳转23
状态机23:连续发送3bytes 的地址数据到spi flash,跳转24
状态机24:发送结束标志,并且需要读取128 bytes的数据,跳转25
状态机25:等待当前的操作(transaction)结束,跳转31
// 从uart 端 接收的相关spi 操作(可能是任何一种spi 操作)
状态机26:如果uart 接收fifo 不是为空,从uart fifo 中读取一个数据,跳转27
状态机27:读信号结束,跳转28
状态机28:如果uart fifo 为空,表示当前从fifo 里读取的最后一个数据为结束标志,发送这个数据(包括读取spi flash 的长度),跳转29。如果uart fifo 不为空,继续读取uart fifo 中的数据,跳转27
状态机29:等待当前操作结束(transaction),跳转31
// 结束当前操作
状态机31:通知上层模块,当前的操作(transaction)结束,跳转1
总结
到此,所有关于spi 的操作,介绍完毕。 上层的应用(按键发送相关的命令, 或者 uart 发送相关的spi 命令)只需要发送命令即可,上层模块不再关心具体的spi 操作细节。



