Menu Close

SPI 通讯协议(6)SPI FLASH (verilog) 工程解析 (spi_IF.v)

spi_IF.v 模块为上层的系统模块, 这个模块 主要是接收系统的命令, 同时向下面的模块(spi_cmd.v) 发送spi 的指令。 这个模块的主要时钟是sys_clk ,通过fifo 和 异步握手方式和spi_cmd.v 模块通讯。 通过接收上层模块的指令,得到相应的操作命令。

参考文章:

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

spi_IF.v 结构图

%title插图%num

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;

%title插图%num

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];
//=======================================================================================

%title插图%num

状态机逻辑:

状态机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

%title插图%num

总结

到此,所有关于spi 的操作,介绍完毕。 上层的应用(按键发送相关的命令,  或者 uart 发送相关的spi 命令)只需要发送命令即可,上层模块不再关心具体的spi 操作细节。

Posted in FPGA, FPGA, FPGA, IP开发, RISC-V, RISC-V IPcore设计, 开发板, 教材与教案, 文章

发表评论

相关链接