SPI协议读取FLASH【FPGA】

2023-05-16

SPI协议读取FLASH【FPGA】

      • 一、SPI协议
        • 1、SPI简介
        • 2、SPI物理层
        • 3、SPI协议层
          • CPOL/CPHA 及通讯模式
        • 4、 SPI 基本通讯过程
        • 5、 通讯的起始和停止信号
        • 6、数据有效性
      • 二、Flash
        • 1、状态寄存器
          • 1、WIP(正在写入)
          • 2、WEL(写使能锁存器)
          • 3、BP(块保护)
          • 4、SRWD(状态寄存器写保护)
        • 2、Flash运行的模式
          • 1、Active Power Mode
          • 2、Stand-by Power Mode
        • 3、操作指令
          • 1、Write Enable(WREN-0x06)
          • 2、Write Disable(WRDI-0x04)
          • 3、Read Identification(RDID-0x9F)
          • 4、Read State Register(RDSR-0x05)
          • 5、Write State Register(WRSR-0x01)
          • 6、Read Data Byte(READ-0x03)
          • 7、Read Data Bytes at Higher Speed (FAST_READ-0x0B)
          • 8、Page Program(PP-0x02)
          • 9、Sector Erase(SE-0xD8)
          • 10、Bulk Erase(BE-0xC7)
      • 三、SPI_FLASH
        • spi_master
        • flash_read
        • flash_write
        • flash_ctrl
      • 四、参考
      • 五、源码

一、SPI协议

1、SPI简介

SPI(Serial Peripheral Interface,串行外围设备接口)通讯协议,是一种同步串行接口技术,
是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输
优点:是支持全双工通信,通讯方式较为简单,且相对数据传输速率较快;
缺点:是没有指定的流控制,没有应答机制确认数据是否接收

2、SPI物理层

SPI 通讯设备的通讯模式是主从通讯模式,通讯双方有主从之分(一主一从和一主多从)
(1) SCK (Serial Clock):时钟信号线,用于同步通讯数据。由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不同,两个设备之间通讯时,通讯速率受限于低速设备。
(2) MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,数据方向由主机到从机。
(3) MISO (Master Input,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,数据方向由从机到主机。
(4) CS(Chip Select):片选信号线,也称为 CS_N,以下用 CS_N 表示。当有多个 SPI 从设备与 SPI 主机相连时,设备的其它信号线 SCK、MOSI 及 MISO 同时并联到相同的 SPI总线上,即无论有多少个从设备,都共同使用这 3 条总线;而每个从设备都有独立的这一条 CS_N 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而 SPI协议中没有设备地址,它使用 CS_N 信号线来寻址,当主机要选择从设备时,把该从设备的 CS_N 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI 通讯。所以 SPI 通讯以 CS_N 线置低电平为开始信号,以 CS_N 线被拉高作为结束信号。

3、SPI协议层

CPOL/CPHA 及通讯模式

SPI 通讯协议一共有四种通讯模式,模式 0、模式 1、模式 2 以及模式 3,这 4 种模式(0、3模式比较常用)
分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义,其中
CPOL 参数规定了空闲状态(CS_N 为高电平,设备未被选中)时 SCK 时钟信号的电平状态,(0:低 1:高)
CPHA 规定了数据采样是在 SCK 时钟的奇数边沿还是偶数边沿。(0:奇数边沿 1:偶数边沿)

模式 0:CPOL= 0,CPHA=0。空闲状态时 SCK 串行时钟为低电平;数据采样在 SCK时钟的奇数边沿,
本模式中,奇数边沿为上升沿;数据更新在 SCK 时钟的偶数边沿,本模式中,偶数边沿为下降沿。
模式 1:CPOL= 0,CPHA=1。空闲状态时 SCK 串行时钟为低电平;数据采样在 SCK时钟的偶数边沿,
本模式中,偶数边沿为下降沿;数据更新在 SCK 时钟的奇数边沿,本模式中,偶数边沿为上升沿。
模式 2:CPOL= 1,CPHA=0。空闲状态时 SCK 串行时钟为高电平;数据采样在 SCK时钟的奇数边沿,
本模式中,奇数边沿为下降沿;数据更新在 SCK 时钟的偶数边沿,本模式中,偶数边沿为上升沿。
模式 3:CPOL= 1,CPHA=1。空闲状态时 SCK 串行时钟为高电平;数据采样在 SCK时钟的偶数边沿,
本模式中,偶数边沿为上升沿;数据更新在 SCK 时钟的奇数边沿,本模式中,偶数边沿为下降沿。
在这里插入图片描述
红线对应的为数据采样点

4、 SPI 基本通讯过程

在这里插入图片描述
MOSI 与 MISO 的信号只在 CS_N 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。

5、 通讯的起始和停止信号

在图 46-6 中的标号①处,CS_N 信号线由高变低,是 SPI 通讯的起始信号。CS_N 是每个从机各自独占的信号线,当从机在自己的 CS_N 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。
在图中的标号⑥处,CS_N 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。

6、数据有效性

数据传输时,MSB 先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI 通讯设备之间使用同样的协定,一般都会采图 46-6 中的 MSB 先行模式,先发送数据的最高位。
观察图中的②③④⑤标号处,MOSI 及 MISO 的数据在 SCK 的下降沿期间变化输出,在 SCK 的上升沿时被采样。即在 SCK 的上升沿时刻,MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数“0”。在其它时刻,数据无效,MOSI 及 MISO为下一次表示数据做准备。SPI 每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。

二、Flash

1、状态寄存器

在这里插入图片描述

1、WIP(正在写入)

高电平“1” 当前Flash还在工作(写状态寄存器、写入数据、擦除数据)
低电平“0” 当前Flash处于空闲的状态,可以处理指令。

2、WEL(写使能锁存器)

高电平“1” 内部启用锁存设置,可以接收输入的指令
低电平“0” 除了写使能指令,其他指令一律不接收

3、BP(块保护)

①块保护(BP2、BP1、BP0)位是非易失性的。
②定义了需要保护数据的区域(只读),防止被指令擦除
③只能使用写状态寄存器指令修改
④当所有的BP(BP2、BP1、BP0)都为0的时候,才能执行BE(全擦除)指令

4、SRWD(状态寄存器写保护)

状态寄存器写保护位与写保护W(芯片的引脚)共同决定状态寄存器BP2、BP1、BP0为的读写属性。
写保护W引脚为低电平“0”的时候,SRWD才能决定BP的只读属性。
写保护W引脚为低电平“1”的时候,BP2、BP1、BP0为只读属性。
高电平“1” BP位只读
低电平“0” BP位可读可写

2、Flash运行的模式

1、Active Power Mode

处于该状态下,说明flash正处于工作的状态(CS拉低、CS拉高之后一小段时间以内,都会处于该状态)。

2、Stand-by Power Mode

处于该模式下,flash能够处理输入的指令,并且芯片的功耗也会降低。

3、操作指令

1、Write Enable(WREN-0x06)

写使能指令用于设置状态寄存器的WEL位,将该位置“1”,后续很多的指令都需要先写入该指令之后,才能被正确的识别。
在这里插入图片描述

2、Write Disable(WRDI-0x04)

写失能指令设置状态寄存器的WEL位,将该位置“0”
在以下的条件下,也会将WEL位置“0”
①上电
②执行WRDI指令完成
③执行WRSR(写状态寄存器)指令完成
④执行PP指令完成
⑤执行SE指令完成
⑥执行BE指令完成
在这里插入图片描述

3、Read Identification(RDID-0x9F)

可以直接发送该指令获取设备的信息(3个字节),不需要提前发送WREN指令。
发送完该指令之后,Flash立即进入Stand-by Power Mode,能够立即接收下一次指令。
在这里插入图片描述

4、Read State Register(RDSR-0x05)

状态寄存器可以在任何时候被读取,包括Flash处于Stand-by Power Mode,都可以被读取。
在发送一个指令之前,最好先读取状态寄存器的WIP(正在写入)位的值。
发送完该指令之后,Flash会一直发送状态寄存器的值 ,直到将片选信号拉高。
在这里插入图片描述

5、Write State Register(WRSR-0x01)

更新状态寄存器的值,发送该指令需要先发送WREN(写使能)指令。
在写入状态寄存器值的时候,依然可以读取状态寄存器WIP(正在写入)值,判断写入是否完成。
该指令写完之后,将WEL位值“0”。
在这里插入图片描述

6、Read Data Byte(READ-0x03)

发送该指令之后,后续紧跟3个字节的地址信息(扇区地址、页地址、字节地址),之后每次回传一次数据,就会在当前的地址基础上加1,直到片选CS信号拉高。
数据在时钟下降沿更新输出。输出数据的频率最大20MHz,所以时钟设置为20MHz。
理论上,该指令能够将整个Flash全部读完。
当Flash处于擦除、写入数据、写周期的状态时,任何的READ指令都会被拒绝。
在这里插入图片描述

7、Read Data Bytes at Higher Speed (FAST_READ-0x0B)
8、Page Program(PP-0x02)

写入该指令之前,需要先写入WREN指令,使能写的功能。
写入指令之后,需要写入3字节的地址信息,再传输需要存储的数据。
如果写入的数据个数大于了256(一页),那么就会覆盖掉最先存入的数据(在当前页循环存入)。
将内存中的值又由1变为0
执行完该指令之后,将状态寄存器的WEL置为“0”。
在这里插入图片描述

9、Sector Erase(SE-0xD8)

在写入指令之前,需要先写入WREN指令,使能写锁存器。
扇区擦除,将指定扇区的全部数据置“1”
完成该指令之后,状态寄存器WEL的值会置“0”
执行该指令的过程,可以读取状态寄存器的WIP的值,判断是否擦除完成
在这里插入图片描述

10、Bulk Erase(BE-0xC7)

在执行指令之前,需要先执行WREN指令,将整个Flash的所有数据置为“1”
完成该指令之后,状态寄存器WEL的值会置“0”
执行该指令的过程,可以读取状态寄存器的WIP的值,判断是否擦除完成
在这里插入图片描述

三、SPI_FLASH

spi_master

module spi_master(
    input           clk     ,
    input           rst_n   ,

    input           req     ,
    input   [7:0]   din     ,
    output  [7:0]   dout    ,
    output          done    ,

    output          cs_n    ,
    output          mosi    ,
    input           miso    ,
    output          sclk        
      
);

//参数定义
    localparam  SCLK_PERIOD = 16,   
                SCLK_FALL   = 4 ,
                SCLK_RISE   = 12;
//信号定义
    reg [ 3:0]  cnt_sclk        ;
    wire        add_cnt_sclk    ;
    wire        end_cnt_sclk    ; 

    reg [ 3:0]  cnt_bit         ;
    wire        add_cnt_bit     ;
    wire        end_cnt_bit     ;

    reg         spi_sclk        ;
    reg         spi_mosi        ;
    reg [7:0]   rx_data         ;

//计数器
 
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt_sclk <= 0; 
        end
        else if(add_cnt_sclk) begin
            if(end_cnt_sclk)
                cnt_sclk <= 0; 
            else
                cnt_sclk <= cnt_sclk+1 ;
       end
    end
    assign add_cnt_sclk = (req);
    assign end_cnt_sclk = add_cnt_sclk  && cnt_sclk == (SCLK_PERIOD)-1 ;
    
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt_bit <= 0; 
        end
        else if(add_cnt_bit) begin
            if(end_cnt_bit)
                cnt_bit <= 0; 
            else
                cnt_bit <= cnt_bit+1 ;
       end
    end
    assign add_cnt_bit = (end_cnt_sclk);
    assign end_cnt_bit = add_cnt_bit  && cnt_bit == (8)-1 ;

//spi_sclk 模式3     
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            spi_sclk <= 1'b1;
        end
        else if(cnt_sclk == SCLK_FALL-1)begin
            spi_sclk <= 1'b0;
        end
        else if(cnt_sclk == SCLK_RISE-1)begin
            spi_sclk <= 1'b1;
        end
    end
//发送数据
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            spi_mosi <= 1'b0;
        end
        else if(cnt_sclk == SCLK_FALL-1)begin
            spi_mosi <= din[7-cnt_bit];
        end
    end
//接收数据
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_data <= 0;
        end
        else if(cnt_sclk == SCLK_RISE-1)begin
            rx_data[7-cnt_bit] <= miso;
        end
    end

//输出
    assign sclk = spi_sclk;
    assign mosi = spi_mosi;
    assign cs_n = ~req;
    assign done = end_cnt_bit;
    assign dout = rx_data;

endmodule 


flash_read

module flash_read(

    input               clk         ,
    input               rst_n       ,
    
    //key
    input               rd_id       ,
    input               rd_data     ,
    
    input   [23:0]      rd_addr     ,//flash读地址

    //spi_master
    output              trans_req   ,
    output  [7:0]       tx_dout     ,
    input   [7:0]       rx_din      ,
    input               trans_done  ,

    //output
    output  [47:0]      dout        ,
    output  [5:0]       dout_mask   ,
    output              dout_vld    
);

/*********  工程注释        ****************

M25P16 Flash读控制器,实现读数据和读存储器的ID。

**********  注释结束    ****************/

//状态机参数定义
    localparam  IDLE = 4'b0001,
                RDID = 4'b0010,//读器件ID
                RDDA = 4'b0100,//读数据字节
                DONE = 4'b1000;
//Flash命令参数定义
    localparam  CMD_RDID = 8'h9F,
                CMD_RDDA = 8'h03;

//信号定义

    reg     [3:0]       state_c     ;
    reg     [3:0]       state_n     ;

    reg     [3:0]       cnt_byte    ;
    wire                add_cnt_byte;
    wire                end_cnt_byte;
    reg     [3:0]       byte_num    ;

    reg     [7:0]       tx_data     ;
    reg                 tx_req      ;
    
    reg                 flag        ;//读数据、读ID标志

    wire                idle2rdid   ; 
    wire                rdid2done   ; 
    wire                idle2rdda   ; 
    wire                rdda2done   ; 
    wire                done2idle   ; 
    
    reg     [31:0]      rx_data	/* synthesis keep */;//串并转换寄存器
    reg     [47:0]      data        ;
    reg     [5:0]       data_mask   ;
    reg                 data_vld    ;

//状态机
    
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            state_c <= IDLE ;
        end
        else begin
            state_c <= state_n;
       end
    end
    
    always @(*) begin 
        case(state_c)  
            IDLE :begin
                if(idle2rdid)
                    state_n = RDID ;
                else if(idle2rdda)
                    state_n = RDDA ;
                else 
                    state_n = state_c ;
            end
            RDID :begin
                if(rdid2done)
                    state_n = DONE ;
                else 
                    state_n = state_c ;
            end
            RDDA :begin
                if(rdda2done)
                    state_n = DONE ;
                else 
                    state_n = state_c ;
            end
            DONE :begin
                if(done2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            default : state_n = IDLE ;
        endcase
    end
    
    assign idle2rdid = state_c==IDLE && (rd_id  );
    assign rdid2done = state_c==RDID && (end_cnt_byte);
    assign idle2rdda = state_c==IDLE && (rd_data);
    assign rdda2done = state_c==RDDA && (end_cnt_byte);
    assign done2idle = state_c==DONE && (1'b1);
    
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt_byte <= 0; 
        end
        else if(add_cnt_byte) begin
            if(end_cnt_byte)
                cnt_byte <= 0; 
            else
                cnt_byte <= cnt_byte+1 ;
       end
    end
    assign add_cnt_byte = (state_c != IDLE && trans_done);
    assign end_cnt_byte = add_cnt_byte  && cnt_byte == (byte_num)-1 ;
        
    always  @(*)begin
        if(state_c == RDID)
            byte_num = 4;
        else  
            byte_num = 8;   //至少5B CMD + 3 ADDR + X DATA
    end

//输出
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            tx_req <= 1'b0;
        end
        else if(idle2rdid | idle2rdda)begin
            tx_req <= 1'b1;
        end
        else if(rdid2done | rdda2done)begin
            tx_req <= 1'b0;
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            tx_data <= 0;
        end
        else if(state_c == RDID)begin
            case(cnt_byte)
                0:tx_data <= CMD_RDID;  
                default:tx_data <= 0; 
            endcase 
        end
        else if(state_c == RDDA)begin
            case(cnt_byte)
                0:tx_data <= CMD_RDDA; 
                1:tx_data <= rd_addr[23:16];
                2:tx_data <= rd_addr[15:8];
                3:tx_data <= rd_addr[7:0];
                default:tx_data <= 0; 
            endcase 
        end
    end

    //rx_data
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rx_data <= 0;
        end
        else if(state_c == RDID && trans_done)begin
            rx_data <= {rx_data[23:0],rx_din};
        end
        else if(state_c == RDDA && trans_done)begin
            rx_data <= {rx_data[23:0],rx_din};
        end
    end

    //data  
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            data <= 0;
        end
        else if(state_c == DONE && ~flag)begin   //读ID
            data <= {4'd0,rx_data[23:20],4'd0,rx_data[19:16],   //2 0
                     4'd0,rx_data[15:12],4'd0,rx_data[11:8],    //2 0
                     4'd0,rx_data[7:4],4'd0,rx_data[3:0]};      //1 5 
        end 
        else if(state_c == DONE && flag)begin //读数据
            data <= {"R","D",16'd0,4'd0,rx_data[7:4],4'd0,rx_data[3:0]};
        end 
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            data_mask <= 0;
        end
        else if(state_c == DONE && ~flag)begin
            data_mask <= 6'b00_0000;
        end
        else if(state_c == DONE && flag)begin
            data_mask <= 6'b00_1100;
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            data_vld <= 0;
        end
        else begin
            data_vld <= state_c == DONE; 
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            flag <= 1'b0;
        end
        else if(idle2rdda)begin
            flag <= 1'b1;
        end
        else if(idle2rdid)begin
            flag <= 1'b0;
        end
    end

//输出
    assign tx_dout =tx_data;
    assign trans_req = tx_req;

    assign dout = data;
    assign dout_mask = data_mask;
    assign dout_vld = data_vld;

endmodule 



flash_write

module flash_write(

    input               clk         ,
    input               rst_n       ,
    
    //key
    input               write       ,
    input   [ 7:0]      wr_data     ,//写入的数据
    input   [23:0]      wr_addr     ,//flash写地址

    //spi_master
    output              trans_req   ,
    output  [7:0]       tx_dout     ,
    input   [7:0]       rx_din      ,
    input               trans_done  ,

    //output
    output  [47:0]      dout        ,
    output  [5:0]       dout_mask   ,
    output              dout_vld    
);

//参数定义
    //主状态机状态参数
    localparam  M_IDLE = 5'b0_0001,
                M_WREN = 5'b0_0010,//主机发写使能序列
                M_SE   = 5'b0_0100,//主机发扇区擦除序列
                M_RDSR = 5'b0_1000,//主机发读状态寄存器序列
                M_PP   = 5'b1_0000;//主机发页编程序列
    //从状态机状态参数
    localparam  S_IDLE = 5'b0_0001,
                S_CMD  = 5'b0_0010,//发送命令
                S_ADDR = 5'b0_0100,//发送地址
                S_DATA = 5'b0_1000,//发送数据、接收数据
                S_DELA = 5'b1_0000;//延时 拉高片选信号

    localparam  CMD_WREN = 8'h06,//写使能命令
                CMD_SE   = 8'hD8,//擦除命令
                CMD_RDSR = 8'h05,//读状态寄存器命令
                CMD_PP   = 8'h02;//页编程命令

    parameter   TIME_DELAY = 16,//发完WREN、SE、PP序列 拉高片选
                TIME_SE    = 150_000_000,//扇区擦除3s
                TIME_PP    = 25_000,//页编程5ms
                TIME_RDSR  = 2000;//读状态寄存器 最大2000次

//信号定义

    reg         [4:0]       m_state_c       ;
    reg         [4:0]       m_state_n       ;
    reg         [4:0]       s_state_c       ;
    reg         [4:0]       s_state_n       ;

    reg         [3:0]       cnt0            ;
    wire                    add_cnt0        ;
    wire                    end_cnt0        ;
    reg         [3:0]       xx              ;

    reg         [31:0]      cnt1            ;
    wire                    add_cnt1        ;
    wire                    end_cnt1        ;
	reg			[31:0]      yy				;

    reg                     rdsr_wip        ;
    reg                     rdsr_wel        ;
    reg         [2:0]       flag            ;
    
    reg         [7:0]       tx_data         ;
    reg                     tx_req          ;
    
    reg         [47:0]      data            ;
    reg         [5:0]       data_mask       ;
    reg                     data_vld        ;

    wire                    m_idle2m_wren   ; 
    wire                    m_wren2m_se     ; 
    wire                    m_se2m_rdsr     ; 
    wire                    m_rdsr2m_wren   ; 
    wire                    m_rdsr2m_pp     ; 
    wire                    m_wren2m_pp     ; 
    wire                    m_rdsr2m_idle   ; 
    wire                    m_pp2m_rdsr     ; 

    wire                    s_idle2s_cmd    ; 
    wire                    s_cmd2s_addr    ; 
    wire                    s_cmd2s_data    ; 
    wire                    s_cmd2s_dela    ; 
    wire                    s_addr2s_data   ; 
    wire                    s_addr2s_dela   ; 
    wire                    s_data2s_dela   ; 
    wire                    s_dela2s_idle   ; 

//状态机设计
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            m_state_c <= M_IDLE ;
        end
        else begin
            m_state_c <= m_state_n;
       end
    end
    
    always @(*) begin 
        case(m_state_c)  
            M_IDLE :begin
                if(m_idle2m_wren)
                    m_state_n = M_WREN ;
                else 
                    m_state_n = m_state_c ;
            end
            M_WREN :begin
                if(m_wren2m_se)
                    m_state_n = M_SE ;
                else if(m_wren2m_pp)
                    m_state_n = M_PP ;
                else 
                    m_state_n = m_state_c ;
            end
            M_SE :begin
                if(m_se2m_rdsr)
                    m_state_n = M_RDSR ;
                else 
                    m_state_n = m_state_c ;
            end
            M_RDSR :begin
                if(m_rdsr2m_wren)
                    m_state_n = M_WREN ;
                else if(m_rdsr2m_pp)
                    m_state_n = M_PP ;
                else if(m_rdsr2m_idle)
                    m_state_n = M_IDLE ;
                else 
                    m_state_n = m_state_c ;
            end
            M_PP :begin
                if(m_pp2m_rdsr)
                    m_state_n = M_RDSR ;
                else 
                    m_state_n = m_state_c ;
            end
            default : m_state_n = M_IDLE ;
        endcase
    end
    
    assign m_idle2m_wren = m_state_c==M_IDLE && (write);
    assign m_wren2m_se   = m_state_c==M_WREN && (s_dela2s_idle && flag[0]);
    assign m_se2m_rdsr   = m_state_c==M_SE   && (s_dela2s_idle);
    assign m_rdsr2m_wren = m_state_c==M_RDSR && (s_dela2s_idle && ~rdsr_wel && ~rdsr_wip && flag[1]);
    assign m_rdsr2m_pp   = m_state_c==M_RDSR && (s_dela2s_idle && rdsr_wel && ~rdsr_wip && flag[1]);
    assign m_wren2m_pp   = m_state_c==M_WREN && (s_dela2s_idle && flag[1]);
    assign m_rdsr2m_idle = m_state_c==M_RDSR && (s_dela2s_idle && flag[2]);
    assign m_pp2m_rdsr   = m_state_c==M_PP   && (s_dela2s_idle);
    
 //从状态机设计
        
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            s_state_c <= S_IDLE ;
        end
        else begin
            s_state_c <= s_state_n;
       end
    end
    
    always @(*) begin 
        case(s_state_c)  
            S_IDLE :begin
                if(s_idle2s_cmd)
                    s_state_n = S_CMD ;
                else 
                    s_state_n = s_state_c ;
            end
            S_CMD :begin
                if(s_cmd2s_addr)
                    s_state_n = S_ADDR ;
                else if(s_cmd2s_data)
                    s_state_n = S_DATA ;
                else if(s_cmd2s_dela)
                    s_state_n = S_DELA ;
                else 
                    s_state_n = s_state_c ;
            end
            S_ADDR :begin
                if(s_addr2s_data)
                    s_state_n = S_DATA ;
                else if(s_addr2s_dela)
                    s_state_n = S_DELA ;
                else 
                    s_state_n = s_state_c ;
            end
            S_DATA :begin
                if(s_data2s_dela)
                    s_state_n = S_DELA ;
                else 
                    s_state_n = s_state_c ;
            end
            S_DELA :begin
                if(s_dela2s_idle)
                    s_state_n = S_IDLE ;
                else 
                    s_state_n = s_state_c ;
            end
            default : s_state_n = S_IDLE ;
        endcase
    end
    
    assign s_idle2s_cmd  = s_state_c==S_IDLE && (m_state_c != M_IDLE);
    assign s_cmd2s_addr  = s_state_c==S_CMD  && (trans_done && (m_state_c == M_SE || m_state_c == M_PP));
    assign s_cmd2s_data  = s_state_c==S_CMD  && (trans_done && m_state_c == M_RDSR);
    assign s_cmd2s_dela  = s_state_c==S_CMD  && (trans_done && m_state_c == M_WREN);
    assign s_addr2s_data = s_state_c==S_ADDR && (end_cnt0 && (m_state_c == M_RDSR || m_state_c == M_PP));
    assign s_addr2s_dela = s_state_c==S_ADDR && (end_cnt0 && m_state_c == M_SE);
    assign s_data2s_dela = s_state_c==S_DATA && (end_cnt0 && m_state_c == M_PP || m_state_c == M_RDSR && end_cnt1);
    assign s_dela2s_idle = s_state_c==S_DELA && (end_cnt1);
    
    //计数器设计
    
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt0 <= 0; 
        end
        else if(add_cnt0) begin
            if(end_cnt0)
                cnt0 <= 0; 
            else
                cnt0 <= cnt0+1 ;
       end
    end
    assign add_cnt0 = (m_state_c != M_RDSR && trans_done);
    assign end_cnt0 = add_cnt0 && cnt0 == (xx)-1 ;
    
    always  @(*)begin
        if(s_state_c == S_CMD)  //发命令1字节
            xx = 1;
        else if(s_state_c == S_ADDR)    //发地址3字节
            xx = 3;
        else /*if(s_state_c == S_DATA)*/ //发数据1字节
            xx = 4;
    end
    
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt1 <= 0; 
        end
        else if(add_cnt1) begin
            if(end_cnt1)
                cnt1 <= 0; 
            else
                cnt1 <= cnt1+1 ;
       end
    end
    assign add_cnt1 = (s_state_c == S_DELA || m_state_c == M_RDSR && s_state_c == S_DATA && trans_done);
    assign end_cnt1 = add_cnt1  && (cnt1 == (yy)-1 || trans_done && ~rdsr_wip);
    
    always  @(*)begin
        if((m_state_c == M_WREN || m_state_c == M_RDSR) && s_state_c == S_DELA)  //发完写使能延时
            yy = TIME_DELAY;
        else if(m_state_c == M_SE)//发完擦除延时
            yy = TIME_SE;
        else if(m_state_c == M_PP)//发完页编程延时
            yy = TIME_PP;
        else if(m_state_c == M_RDSR && s_state_c == S_DATA)//读状态寄存器值 yy 次
            yy = TIME_RDSR;
	     else 
		    yy = TIME_DELAY;
    end

//rdsr_wel rdsr_wip
    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            rdsr_wel <= 1'b0;
            rdsr_wip <= 1'b0;
        end
        else if(m_state_c == M_RDSR && s_state_c == S_DATA && trans_done)begin
            rdsr_wel <= rx_din[1];
            rdsr_wip <= rx_din[0];
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            flag <= 3'b000;
        end
        else if(m_idle2m_wren)begin
            flag <= 3'b001;
        end
        else if(m_se2m_rdsr)begin
            flag <= 3'b010;
        end
        else if(m_pp2m_rdsr)begin
            flag <= 3'b100;
        end
    end

//输出    

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            tx_data <= 0;
        end
        else if(m_state_c == M_WREN)begin
            tx_data <= CMD_WREN;
        end
        else if(m_state_c == M_SE)begin
            case(s_state_c)
                S_CMD :tx_data <= CMD_SE;
                S_ADDR:tx_data <= wr_addr[23-cnt0*8 -:8];
                default:tx_data <= 0;
            endcase 
        end
        else if(m_state_c == M_RDSR)begin   //在读状态寄存器时,可以发一次命令,也可以连续发命令
            tx_data <= CMD_RDSR;
        end 
        else if(m_state_c == M_PP)begin 
            case(s_state_c)
                S_CMD :tx_data <= CMD_PP;
                S_ADDR:tx_data <= wr_addr[23-cnt0*8 -:8];
                S_DATA:tx_data <= wr_data + cnt0;
                default:tx_data <= 0;
            endcase 
        end 
    end

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            tx_req <= 1'b0;
        end
        else if(s_idle2s_cmd)begin
            tx_req <= 1'b1;
        end
        else if(s_cmd2s_dela | s_addr2s_dela | s_data2s_dela)begin
            tx_req <= 1'b0;
        end
    end
//data    data_mask
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            data <= 0;
            data_mask <= 0;
        end 
        else if(m_rdsr2m_idle & ~rdsr_wip)begin //PP completed
            data <= {"P","P",16'd0,4'd0,wr_data[7:4],4'd0,wr_data[3:0]};
            data_mask <= 6'b001100;
        end
        else if(m_rdsr2m_idle & rdsr_wip)begin //PP failed
            data <= {"P","P",8'd0,"ERR"};
            data_mask <= 6'b001000;
        end  
    end

//data_vld   ,
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            data_vld <= 0;
        end 
        else begin 
            data_vld <= m_rdsr2m_idle;
        end 
    end

//输出
    assign tx_dout = tx_data;
    assign trans_req = tx_req;
    assign dout_vld = data_vld;
    assign dout_mask = data_mask;
    assign dout = data;

endmodule


flash_ctrl

module flash_ctrl(

    input               clk         ,
    input               rst_n       ,
    
    input   [2:0]       key         ,
    input   [ 7:0]      wr_din      ,//写入的数据
    input   [23:0]      rw_addr     ,//flash读写地址
    
    output              trans_req   ,
    output  [7:0]       tx_dout     ,
    input   [7:0]       rx_din      ,
    input               trans_done  ,

    output  [47:0]      dout        ,
    output  [5:0]       dout_mask   ,
    output              dout_vld     

);

//信号定义
    
    wire                 wr_req         ; 
    wire     [7:0]       wr_dout        ; 

    wire     [47:0]      wr_data        ;
    wire     [5:0]       wr_data_mask   ;
    wire                 wr_data_vld    ;

    wire                 rd_req         ; 
    wire     [7:0]       rd_dout        ; 
 
    wire     [47:0]      rd_data        ;
    wire     [5:0]       rd_data_mask   ;
    wire                 rd_data_vld    ;

//模块例化
flash_write u_write(
    /*input               */.clk         (clk           ),
    /*input               */.rst_n       (rst_n         ),
    /*input               */.write       (key[2]        ),
    /*input   [ 7:0]      */.wr_data     (wr_din        ),//写入的数据
    /*input   [23:0]      */.wr_addr     (rw_addr       ),//flash写地址
    /*output              */.trans_req   (wr_req        ),
    /*output  [7:0]       */.tx_dout     (wr_dout       ),
    /*input   [7:0]       */.rx_din      (rx_din        ),
    /*input               */.trans_done  (trans_done    ),
    /*output  [47:0]      */.dout        (wr_data       ),
    /*output  [5:0]       */.dout_mask   (wr_data_mask  ),
    /*output              */.dout_vld    (wr_data_vld   )
);
 flash_read u_read(
    /*input               */.clk         (clk           ),
    /*input               */.rst_n       (rst_n         ),
    /*input               */.rd_id       (key[0]        ),
    /*input               */.rd_data     (key[1]        ),
    /*input   [23:0]      */.rd_addr     (rw_addr       ),//flash读地址
    /*output              */.trans_req   (rd_req        ),
    /*output  [7:0]       */.tx_dout     (rd_dout       ),
    /*input   [7:0]       */.rx_din      (rx_din        ),
    /*input               */.trans_done  (trans_done    ),
    /*output  [47:0]      */.dout        (rd_data       ),
    /*output  [5:0]       */.dout_mask   (rd_data_mask  ),
    /*output              */.dout_vld    (rd_data_vld   )
);

    assign trans_req = rd_req | wr_req;
    assign tx_dout = {8{wr_req}} & wr_dout | {8{rd_req}} & rd_dout;
    assign dout_vld = wr_data_vld | rd_data_vld;
    assign dout = {48{wr_data_vld}} & wr_data 
                | {48{rd_data_vld}} & rd_data;
    assign dout_mask = {6{wr_data_vld}} & wr_data_mask
                     | {6{rd_data_vld}} & rd_data_mask;
endmodule 


四、参考

【FPGA】FPGA基于spi的flash读写

五、源码

https://github.com/IvanXiang/FPGA_SPI_FLASH

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SPI协议读取FLASH【FPGA】 的相关文章

  • fastjson用java转json时间的格式化

    一 项目中需求遇到需要接收其他应用数据 xff0c 通过 64 RequestBody注解接收参数后 xff0c 到本地利用fastJson把json格式化 需要注意一下几点 xff1a 需要在调用JSON toJSONString 的时候
  • 工具类里面调用service接口或者mapper接口

    我们在开发中经常会遇到需要将一些频繁进行的操作抽取封装到工具类中 xff0c springboot不支持注入静态属性 所以在工具类中使用 64 Autowired或者其他注解自动注入会失败 xff0c 才用如下方法这可以避免注入失败 spa
  • windows下停止【kill】nginx命令

    杀死nginx taskkill fi 34 imagename eq nginx EXE 34 f taskkill f t im nginx exe stop bat taskkill f t im nginx exe pause
  • 基本类型对应的缓冲池

    基本类型对应的缓冲池如下 xff1a boolean values true and false all byte values short values between 128 and 127 int values between 128
  • 记录JVM中Eden区、Survivor from区和Survivor to区及Minor GC和Major GC的理解

    仅做学习笔记 JVM中Eden区 Survivor from区和Survivor to区 本文主要根据 深入理解JVM 中内存回收策略 xff0c 主要关注如下五个方面 xff1a 1 xff1a Eden区分配 2 xff1a 大对象直接
  • ubuntu下修改python默认版本的方法

    Ubuntu安装之后会面临多个python版本共存的问题 xff08 python2和python3 xff09 xff0c 但是有时候安装其他库的时候会安装在默认的python版本环境下 xff08 比如 xff0c 通常默认版本是pyt
  • C++:C语言实现HTTP的GET和POST请求

    https www cnblogs com diligenceday p 6255788 html
  • Linux C/C++ UDP Socket 网络通信

    Python微信订餐小程序课程视频 https edu csdn net course detail 36074 Python实战量化交易理财系统 https edu csdn net course detail 35475 昨晚 Vv 让
  • Xcode工程创建多个target

    Xcode工程创建多个target 小菜本人有时候会在一个Xcode工程中新建多个包含main函数的 m文件用于用于学习Objective C xff0c 于是要用到target这个东西 target对应于一个可运行文件和一些编译配置 点击
  • JLINK简介

    一 什么是JLINK JLINK是一个兼容JTAG的仿真器 xff0c 作用是烧入程序和Debug 二 JLINK是如何处理数据的 xff1f 1 PC端应用程序将数据以某种协议格式 xff0c 通过USB接口发送给J Link 2 J L
  • 蓝桥杯单片机-DS1302时钟模块

    一 简介 1 采用SPI三线接口通信 xff08 SCK SDA RST xff09 上升沿数据被写入DS1302 xff0c 下降沿被读出 二 应用 1 在ds1302 c文件中定义三个数组 unsigned char code READ
  • 蓝桥杯单片机-定时器

    一 简介 有三个寄存器与定时器相关 xff08 TMOD xff0c TCON xff0c 数值设置寄存器TH TL xff09 1 定时器工作方式设置寄存器TMOD GATE 门控制位 GATE 61 0时 定时器 计数器启动与停止仅受T
  • 蓝桥杯单片机-NE555模块

    一 简介 1 NE555在开发板中用于输出频率可变 xff0c 占空比不变的方波 2 NE555是纯硬件的设计 xff0c 通过电位器RB3可改变其信号输出频率 不需要编程实现其功能 考点 xff1a 使用定时器的计数模式测量NE555输出
  • C语言学习笔记(基于单片机)

    目录 一 关键字部分 static code const extern bit sbit sft struct xff08 结构体 xff09 1 结构体的初始化 2 结构体的赋值 3 应用 data idata pdata xdata 与
  • 蓝桥杯单片机-赛前总结

    目录 一 省赛中开发平台涉及的模块 xff1a 1 IIC驱动 2 DS1302驱动 3 onewire驱动 4 定时器读取NE555频率 二 一些功能性操作 1 外部中断 2 矩阵按键 3 PWM输出 4 毫秒延时函数 三 需要注意的一些
  • 相互依赖的so库,在编译时如何解耦

    有时候 xff0c 我们写的程序 xff0c 会涉及到相互引用的问题 比如frameworks av media libstagefright下的这个libstagefright xff0c 被frameworks av media lib
  • boost库学习总结

    第一次使用boost库是因为网络编程 xff0c 由于时间比较紧 xff0c 没有时间每个库都学 xff0c 所以前期想找个专门的boost库网络教程 xff08 以前自己就用过socket写过 xff0c 但是为了跨平台 xff0c 而且
  • C51单片机判断点亮的是奇数位还是偶数位

    学单片机的时候想到一个问题 xff1a 如何判断单片机点亮的LED灯是奇数位还是偶数位 xff1f 在网上搜了一圈没找到 xff0c 于是打算自己写一个 单片机型号 xff1a AT89C52 xff0c LED为低电平有效接法 xff0c
  • Linux学习笔记——如何在交叉编译时使用共享库

    0 前言 在较为复杂的项目中会利用到交叉编译得到的共享库 xff08 so文件 xff09 在这种情况下便会产生以下疑问 xff0c 例如 xff1a 1 交叉编译时的共享库是否需要放置于目标板中 xff0c 如果需要放置在哪个目录中 2
  • 实例变量(instance var)与属性(@property)的关系

    实例变量 instance var 与属性 64 property 的关系 Objective C 2 0之后 xff0c 声明一个 64 property name自动产生一个实例变量 xff0c 名为 name xff0c 因此省去实例

随机推荐

  • VS+CUDA+Matlab环境配置经验 (达到用matlab使用nvcc进行gpu并行加速)

    前前后后配了一个多星期的环境 xff0c 终于完成了 xff0c 梳理一下我的经验给大家分享也留作以后参考 一 版本兼容性问题 一开始安装的时候我并不懂 xff0c 没有什么顺序和版本的概念 xff0c 所以各种组件不能相互支持 首先明确
  • 华为无线WiFi配置802.1x认证

    一 拓扑 xff1a 二 简介 xff1a 本篇主要介绍华为交换机设备配合Windows server 2019配置的802 1x 43 NPS协同做的有线网络认证 xff08 可跟做 xff09 现有的华为6605无线AC配置 xff1a
  • 寝室一伙计

    我们寝室一伙计 xff0c 昨天晚上睡觉的时候 xff0c 说梦话 xff0c 笑 xff0c 磨牙 xff0c 还有吃奶 xff0c 等等等等 xff0c 我服了
  • Qt学习笔记(五)重定向

    实时获取程序中qt所输出的信息 xff0c 并显示到QTextBrowser上 1 在main文件中添加以下内容 xx为你的界面类名 xx clk 61 NULL void myMessageOutput QtMsgType type co
  • 串口通信中接收数据时延迟处理与缓存处理的解决方案(C#)

    利用串口进行通信 xff0c 当发送方 xff08 A xff09 将数据写入串口后 xff0c 通过无线或有线方式将数据传送给接收方 xff08 B xff09 xff0c B通过调用串口读方法comm read 参数 即可将数据读出 原
  • robomaster电控究极学习教程(以哨兵为例)------一、串口dma和遥控器

    robomaster电控究极学习教程 xff08 以哨兵为例 xff09 一 串口dma和遥控器 文章目录 robomaster电控究极学习教程 xff08 以哨兵为例 xff09 一 串口dma和遥控器 一 串口DMA的作用二 步骤1 c
  • 无人机架构

    无人机架构 基本设计需求 xff0c 如 xff1a xff08 1 xff09 慎思规划和反应式行为 xff1b xff08 2 xff09 容许不确定性 xff1b xff08 3 xff09 考虑危险 xff1b xff08 4 xf
  • 第八课 C++中的__LINE__宏

    在C 43 43 编程中 xff0c 我们有时候需要知道当前源代码的行号 xff0c 这时候就可以使用 LINE 宏 本文将介绍如何使用 LINE 宏 xff0c 以及它的使用示例 LINE 宏简介 LINE 是C 43 43 中的一个预定
  • 介绍@dynamic的用法

    介绍 64 dynamic的用法 Objective C 2 0提供了属性 64 property xff0c 可以让编译器自动生成setter和getter方法 如果不想编译器自作主张生成这些setter和getter方法 xff0c 则
  • OkHttpUtils的使用

    OkHttpUtils是一个非常好的网络协议框架 xff0c 它是在OkHttp的基础上进行了二次封装 要使用这个类首先下载jar包 xff0c 如下 xff1a http download csdn net download xxdw19
  • 十进制整数转为十六进制整数(C++实现)

    一 代码功能 xff1a 输入一个十进制整数 xff0c 将其转化为十六进制整数并输出 二 源码 include lt iostream gt include lt cstring gt include lt cmath gt using
  • 我的四轴专用PID参数整定方法及原理---超长文慎入(转)

    给四轴调了好久的PID xff0c 总算是调好了 xff0c 现分享PID参数整定的心得给大家 xff0c 还请大家喷的时候手下留情 首先说明一下 xff0c 这篇文章的主旨并不是直接教你怎么调 xff0c 而是告诉你这么调有什么道理 xf
  • 简单的TCP客户端发包工具

    一 TCP介绍 先放这里有时间在写 xff0c 最近在写DuiLib相关的使用内容 xff0c 这部分大家凑活着看 二 程序截图 下载链接 链接 xff1a https pan baidu com s 1MzNUzwd7WwBat6vNMc
  • C++常用头文件汇总

    之前说过的头文件这就来了 1 首先是本人最喜欢也是最最方便的万能头文件 xff0c 顾名思义 xff0c 不管是天上飘的还是地下埋的 xff0c 只要不是不对的头文件它都包含 xff0c 除了本篇第14个头文件 xff1a include
  • 基于I2C/SPI的温湿度采集与OLED显示

    基于I2C SPI的温湿度采集与OLED显示 一 AHT20温湿度采集1 I2C2 温湿度采集代码效果 二 OLED显示1 显示学号姓名2 诗句显示 三 总结四 参考 一 AHT20温湿度采集 1 I2C 解释什么是 软件I2C 和 硬件I
  • 游戏客户端编程

    游戏客户端编程 1 代码实现服务器连接发送按钮 2 显示消息3 发送信息4 播放背景音乐5 变换游戏背景图片6 参考 1 代码实现 服务器连接 span class token keyword private span span class
  • 卷积神经网络实现表情识别

    卷积神经网络实现表情识别 CNN人脸表情识别图片预处理原本效果处理后效果 图片数据集效果 CNN人脸识别创建模型归一化与数据增强创建网络 摄像头人脸识别图片识别 参考 CNN人脸表情识别 图片预处理 span class token key
  • IIC协议及其工程【FPGA】

    IIC协议及其工程 FPGA 一 IIC协议1 IIC简介2 IIC中相关的术语1 应答信号 xff08 ACK xff09 2 无应答信号 xff08 NOACK xff09 3 虚写3 IIC的时序 二 EEPROM1 时钟频率2 起始
  • char *与char []的区别

    其实 xff0c 只要记住一点就能很好区分char 和char xff1a char 定义的是一个指向字符串的指针 xff08 注意 xff1a C语言中没有对应字符串的内置类型或者类类型 xff09 xff0c 而char 就是C语言中的
  • SPI协议读取FLASH【FPGA】

    SPI协议读取FLASH FPGA 一 SPI协议1 SPI简介2 SPI物理层3 SPI协议层CPOL CPHA 及通讯模式 4 SPI 基本通讯过程5 通讯的起始和停止信号6 数据有效性 二 Flash1 状态寄存器1 WIP xff0