野火FPGA进阶(1):基于SPI协议的Flash驱动控制

news2024/10/6 0:36:44

文章目录

    • 第48讲:基于SPI协议的Flash驱动控制
      • 0. 理论部分
      • 1. Flash全擦除实验
        • key_filter
        • flash_be_ctrl
        • spi_flash_be
        • tb_flash_be_ctrl
        • tb_spi_flash_be
      • 2. Flash扇区擦除实验
        • key_filter
        • flash_se_ctrl
        • spi_flash_se
      • 3. 数据读操作
        • key_filter
        • uart_tx
        • flash_read_ctrl
        • spi_flash_read
        • tb_spi_flash_read
      • 4. 数据页写操作
        • key_filter
        • flash_pp_ctrl
        • spi_flash_pp
      • 5. 数据连续写操作

第48讲:基于SPI协议的Flash驱动控制

0. 理论部分

SPI(Serial Peripheral Interface,串行外围设备接口)通讯协议,是Motorola公司提出的一种同步串行接口技术,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输

应用:EEPROM、Flash、RTC、ADC、DSP等

优缺点:全双工通信,通讯方式较为简单,相对数据传输速率较快;没有应答机制确认数据是否接收,在数据可靠性上有一定缺陷(与I2C相比)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述


1. Flash全擦除实验

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

key_filter

`timescale  1ns/1ns

module  key_filter
#(
    parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
    input   wire    sys_clk     ,   //系统时钟50Mhz
    input   wire    sys_rst_n   ,   //全局复位
    input   wire    key_in      ,   //按键输入信号

    output  reg     key_flag        //key_flag为1时表示消抖后检测到按键被按下
                                    //key_flag为0时表示没有检测到按键被按下
);

//reg   define
reg     [19:0]  cnt_20ms    ;   //计数器

//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'b0;
    else    if(key_in == 1'b1)
        cnt_20ms <= 20'b0;
    else    if(cnt_20ms == CNT_MAX && key_in == 1'b0)
        cnt_20ms <= cnt_20ms;
    else
        cnt_20ms <= cnt_20ms + 1'b1;

//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_flag <= 1'b0;
    else    if(cnt_20ms == CNT_MAX - 1'b1)
        key_flag <= 1'b1;
    else
        key_flag <= 1'b0;

endmodule

flash_be_ctrl

`timescale  1ns/1ns

module  flash_be_ctrl
(
    input   wire            sys_clk     ,   //系统时钟,频率50MHz
    input   wire            sys_rst_n   ,   //复位信号,低电平有效
    input   wire            key         ,   //按键输入信号

    output  reg             cs_n        ,   //片选信号
    output  reg             sck         ,   //串行时钟
    output  reg             mosi            //主输出从输入数据
);

//parameter define
parameter   IDLE    =   4'b0001 ,   //初始状态
            WR_EN   =   4'b0010 ,   //写状态
            DELAY   =   4'b0100 ,   //等待状态
            BE      =   4'b1000 ;   //全擦除状态
parameter   WR_EN_INST  =   8'b0000_0110,   //写使能指令
            BE_INST     =   8'b1100_0111;   //全擦除指令

//reg   define
reg     [2:0]   cnt_byte;   //字节计数器
reg     [3:0]   state   ;   //状态机状态
reg     [4:0]   cnt_clk ;   //系统时钟计数器
reg     [1:0]   cnt_sck ;   //串行时钟计数器
reg     [2:0]   cnt_bit ;   //比特计数器

//cnt_clk:系统时钟计数器,用以记录单个字节
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk  <=  5'd0;
    else    if(state != IDLE)
        cnt_clk  <=  cnt_clk + 1'b1;

//cnt_byte:记录输出字节个数和等待时间
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_byte    <=  3'd0;
    else    if((cnt_clk == 5'd31) && (cnt_byte == 3'd6))
        cnt_byte    <=  3'd0;
    else    if(cnt_clk == 31)
        cnt_byte    <=  cnt_byte + 1'b1;

//cnt_sck:串行时钟计数器,用以生成串行时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_sck <=  2'd0;
    else    if((state == WR_EN) && (cnt_byte == 1'b1))
        cnt_sck <=  cnt_sck + 1'b1;
    else    if((state == BE) && (cnt_byte == 3'd5))
        cnt_sck <=  cnt_sck + 1'b1;

//cs_n:片选信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cs_n    <=  1'b1;
    else    if(key == 1'b1)
        cs_n    <=  1'b0;
    else    if((cnt_byte == 3'd2) && (cnt_clk == 5'd31) && (state == WR_EN))
        cs_n    <=  1'b1;
    else    if((cnt_byte == 3'd3) && (cnt_clk == 5'd31) && (state == DELAY))
        cs_n    <=  1'b0;
    else    if((cnt_byte == 3'd6) && (cnt_clk == 5'd31) && (state == BE))
        cs_n    <=  1'b1;

//sck:输出串行时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        sck <=  1'b0;
    else    if(cnt_sck == 2'd0)
        sck <=  1'b0;
    else    if(cnt_sck == 2'd2)
        sck <=  1'b1;

//cnt_bit:高低位对调,控制mosi输出
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <=  3'd0;
    else    if(cnt_sck == 2'd2)
        cnt_bit <=  cnt_bit + 1'b1;

//state:两段式状态机第一段,状态跳转
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  IDLE;
    else
    case(state)
        IDLE:   if(key == 1'b1)
                state   <=  WR_EN;
        WR_EN:  if((cnt_byte == 3'd2) && (cnt_clk == 5'd31))
                state   <=  DELAY;
        DELAY:  if((cnt_byte == 3'd3) && (cnt_clk == 5'd31))
                state   <=  BE;
        BE:     if((cnt_byte == 3'd6) && (cnt_clk == 5'd31))
                state   <=  IDLE;
        default:    state   <=  IDLE;
    endcase

//mosi:两段式状态机第二段,逻辑输出
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        mosi    <=  1'b0;
    else    if((state == WR_EN) && (cnt_byte == 3'd2))
        mosi    <=  1'b0;
    else    if((state == BE) && (cnt_byte == 3'd6))
        mosi    <=  1'b0;
    else    if((state == WR_EN) && (cnt_byte == 3'd1) && (cnt_sck == 5'd0))
        mosi    <=  WR_EN_INST[7 - cnt_bit];    //写使能指令
    else    if((state == BE) && (cnt_byte == 3'd5) && (cnt_sck == 5'd0))
        mosi    <=  BE_INST[7 - cnt_bit];       //全擦除指令

endmodule

spi_flash_be

`timescale  1ns/1ns

module  spi_flash_be
(
    input   wire    sys_clk     ,   //系统时钟,频率50MHz
    input   wire    sys_rst_n   ,   //复位信号,低电平有效
    input   wire    pi_key      ,   //按键输入信号

    output  wire    cs_n        ,   //片选信号
    output  wire    sck         ,   //串行时钟
    output  wire    mosi            //主输出从输入数据
);

//parameter define
parameter   CNT_MAX =   20'd999_999;    //计数器计数最大值

//wire  define
wire    po_key  ;

//------------- key_filter_inst -------------
key_filter
#(
    .CNT_MAX    (CNT_MAX    )   //计数器计数最大值
)
key_filter_inst
(
    .sys_clk    (sys_clk    ),  //系统时钟,频率50MHz
    .sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效
    .key_in     (pi_key     ),  //按键输入信号

    .key_flag   (po_key     )   //消抖后信号
);

//------------- flash_be_ctrl_inst -------------
flash_be_ctrl  flash_be_ctrl_inst
(
    .sys_clk    (sys_clk    ),  //系统时钟,频率50MHz
    .sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效
    .key        (po_key     ),  //按键输入信号

    .sck        (sck        ),  //片选信号
    .cs_n       (cs_n       ),  //串行时钟
    .mosi       (mosi       )   //主输出从输入数据
);

endmodule

tb_flash_be_ctrl

`timescale  1ns/1ns
module  tb_flash_be_ctrl();

//wire  define
wire            cs_n    ;   //Flash片选信号
wire            sck     ;   //Flash串行时钟
wire            mosi    ;   //Flash主输出从输入信号

//reg   define
reg     sys_clk     ;   //模拟时钟信号
reg     sys_rst_n   ;   //模拟复位信号
reg     key         ;   //模拟全擦除触发信号

//时钟、复位信号、模拟按键信号
initial
    begin
        sys_clk     =   1'b1;
        sys_rst_n   <=  1'b0;
        key <=  1'b0;
        #100
        sys_rst_n   <=  1'b1;
        #1000
        key <=  1'b1;
        #20
        key <=  1'b0;
    end

always  #10 sys_clk <=  ~sys_clk;   //模拟时钟,频率50MHz

//写入Flash仿真模型初始值(全F)
defparam memory.mem_access.initfile = "initmemory.txt";

//------------- flash_be_ctrl_inst -------------
flash_be_ctrl  flash_be_ctrl_inst
(
    .sys_clk    (sys_clk    ),  //输入系统时钟,频率50MHz,1bit
    .sys_rst_n  (sys_rst_n  ),  //输入复位信号,低电平有效,1bit
    .key        (key        ),  //按键输入信号,1bit

    .sck        (sck        ),  //输出串行时钟,1bit
    .cs_n       (cs_n       ),  //输出片选信号,1bit
    .mosi       (mosi       )   //输出主输出从输入数据,1bit
);

//------------- memory -------------
m25p16  memory
(
    .c          (sck    ),  //输入串行时钟,频率12.5Mhz,1bit
    .data_in    (mosi   ),  //输入串行指令或数据,1bit
    .s          (cs_n   ),  //输入片选信号,1bit
    .w          (1'b1   ),  //输入写保护信号,低有效,1bit
    .hold       (1'b1   ),  //输入hold信号,低有效,1bit

    .data_out   (       )   //输出串行数据
);

endmodule

tb_spi_flash_be

`timescale  1ns/1ns
module  tb_spi_flash_be();

//wire  define
wire    cs_n;
wire    sck ;
wire    mosi ;

//reg   define
reg     clk     ;
reg     rst_n   ;
reg     key     ;

//时钟、复位信号、模拟按键信号
initial
    begin
        clk =   0;
        rst_n   <=  0;
        key <=  0;
        #100
        rst_n   <=  1;
        #1000
        key <=  1;
        #20
        key <=  0;
    end

always  #10 clk <=  ~clk;

defparam memory.mem_access.initfile = "initmemory.txt";

//-------------spi_flash_erase-------------
spi_flash_be    spi_flash_be_inst
(
    .sys_clk    (clk    ),  //系统时钟,频率50MHz
    .sys_rst_n  (rst_n  ),  //复位信号,低电平有效
    .pi_key     (key    ),  //按键输入信号

    .sck        (sck    ),  //串行时钟
    .cs_n       (cs_n   ),  //片选信号
    .mosi       (mosi   )   //主输出从输入数据
);

m25p16  memory
(
    .c          (sck    ),  //输入串行时钟,频率12.5Mhz,1bit
    .data_in    (mosi   ),  //输入串行指令或数据,1bit
    .s          (cs_n   ),  //输入片选信号,1bit
    .w          (1'b1   ),  //输入写保护信号,低有效,1bit
    .hold       (1'b1   ),  //输入hold信号,低有效,1bit

    .data_out   (       )   //输出串行数据
);

endmodule

2. Flash扇区擦除实验

在这里插入图片描述
在这里插入图片描述

key_filter

`timescale  1ns/1ns

module  key_filter
#(
    parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
    input   wire    sys_clk     ,   //系统时钟50Mhz
    input   wire    sys_rst_n   ,   //全局复位
    input   wire    key_in      ,   //按键输入信号

    output  reg     key_flag        //key_flag为1时表示消抖后检测到按键被按下
                                    //key_flag为0时表示没有检测到按键被按下
);

//reg   define
reg     [19:0]  cnt_20ms    ;   //计数器

//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'b0;
    else    if(key_in == 1'b1)
        cnt_20ms <= 20'b0;
    else    if(cnt_20ms == CNT_MAX && key_in == 1'b0)
        cnt_20ms <= cnt_20ms;
    else
        cnt_20ms <= cnt_20ms + 1'b1;

//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_flag <= 1'b0;
    else    if(cnt_20ms == CNT_MAX - 1'b1)
        key_flag <= 1'b1;
    else
        key_flag <= 1'b0;

endmodule

flash_se_ctrl

`timescale  1ns/1ns

module  flash_se_ctrl
(
    input   wire    sys_clk     ,   //系统时钟,频率50MHz
    input   wire    sys_rst_n   ,   //复位信号,低电平有效
    input   wire    key         ,   //按键输入信号

    output  reg     cs_n        ,   //片选信号
    output  reg     sck         ,   //串行时钟
    output  reg     mosi            //主输出从输入数据
);

//parameter define
parameter   IDLE    =   4'b0001 ,   //初始状态
            WR_EN   =   4'b0010 ,   //写状态
            DELAY   =   4'b0100 ,   //等待状态
            SE      =   4'b1000 ;   //扇区擦除状态
parameter   WR_EN_INST  =   8'b0000_0110,   //写使能指令
            SE_INST     =   8'b1101_1000;   //扇区擦除指令
parameter   SECTOR_ADDR =   8'b0000_0000,   //扇区地址
            PAGE_ADDR   =   8'b0000_0100,   //页地址
            BYTE_ADDR   =   8'b0010_0101;   //字节地址

//reg   define
reg     [3:0]   cnt_byte;   //字节计数器
reg     [3:0]   state   ;   //状态机状态
reg     [4:0]   cnt_clk ;   //系统时钟计数器
reg     [1:0]   cnt_sck ;   //串行时钟计数器
reg     [2:0]   cnt_bit ;   //比特计数器

//cnt_clk:系统时钟计数器,用以记录单个字节
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk  <=  5'd0;
    else    if(state != IDLE)
        cnt_clk  <=  cnt_clk + 1'b1;

//cnt_byte:记录输出字节个数和等待时间
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_byte    <=  4'd0;
    else    if((cnt_clk == 5'd31) && (cnt_byte == 4'd9))
        cnt_byte    <=  4'd0;
    else    if(cnt_clk == 31)
        cnt_byte    <=  cnt_byte + 1'b1;

//cnt_sck:串行时钟计数器,用以生成串行时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_sck <=  2'd0;
    else    if((state == WR_EN) && (cnt_byte == 1'b1))
        cnt_sck <=  cnt_sck + 1'b1;
    else    if((state == SE) && (cnt_byte >= 4'd5) && (cnt_byte <= 4'd8))
        cnt_sck <=  cnt_sck + 1'b1;

//cs_n:片选信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cs_n    <=  1'b1;
    else    if(key == 1'b1)
        cs_n    <=  1'b0;
    else    if((cnt_byte == 4'd2) && (cnt_clk == 5'd31) && (state == WR_EN))
        cs_n    <=  1'b1;
    else    if((cnt_byte == 4'd3) && (cnt_clk == 5'd31) && (state == DELAY))
        cs_n    <=  1'b0;
    else    if((cnt_byte == 4'd9) && (cnt_clk == 5'd31) && (state == SE))
        cs_n    <=  1'b1;

//sck:输出串行时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        sck <=  1'b0;
    else    if(cnt_sck == 2'd0)
        sck <=  1'b0;
    else    if(cnt_sck == 2'd2)
        sck <=  1'b1;

//cnt_bit:高低位对调,控制mosi输出
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <=  3'd0;
    else    if(cnt_sck == 2'd2)
        cnt_bit <=  cnt_bit + 1'b1;

//state:两段式状态机第一段,状态跳转
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  IDLE;
    else
    case(state)
        IDLE:   if(key == 1'b1)
                state   <=  WR_EN;
        WR_EN:  if((cnt_byte == 4'd2) && (cnt_clk == 5'd31))
                state   <=  DELAY;
        DELAY:  if((cnt_byte == 4'd3) && (cnt_clk == 5'd31))
                state   <=  SE;
        SE:     if((cnt_byte == 4'd9) && (cnt_clk == 5'd31))
                state   <=  IDLE;
        default:    state   <=  IDLE;
    endcase

//mosi:两段式状态机第二段,逻辑输出
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        mosi    <=  1'b0;
    else    if((state == WR_EN) && (cnt_byte == 4'd2))
        mosi    <=  1'b0;
    else    if((state == SE) && (cnt_byte == 4'd9))
        mosi    <=  1'b0;
    else    if((state == WR_EN) && (cnt_byte == 4'd1) && (cnt_sck == 5'd0))
        mosi    <=  WR_EN_INST[7 - cnt_bit];  //写使能指令
    else    if((state == SE) && (cnt_byte == 4'd5) && (cnt_sck == 5'd0))
        mosi    <=  SE_INST[7 - cnt_bit];    //扇区擦除指令
    else    if((state == SE) && (cnt_byte == 4'd6) && (cnt_sck == 5'd0))
        mosi    <=  SECTOR_ADDR[7 - cnt_bit];  //扇区地址
    else    if((state == SE) && (cnt_byte == 4'd7) && (cnt_sck == 5'd0))
        mosi    <=  PAGE_ADDR[7 - cnt_bit];    //页地址
    else    if((state == SE) && (cnt_byte == 4'd8) && (cnt_sck == 5'd0))
        mosi    <=  BYTE_ADDR[7 - cnt_bit];    //字节地址

endmodule

spi_flash_se

`timescale  1ns/1ns

module  spi_flash_se
(
    input   wire    sys_clk     ,   //系统时钟,频率50MHz
    input   wire    sys_rst_n   ,   //复位信号,低电平有效
    input   wire    pi_key      ,   //按键输入信号

    output  wire    cs_n        ,   //片选信号
    output  wire    sck         ,   //串行时钟
    output  wire    mosi            //主输出从输入数据
);

//parameter define
parameter   CNT_MAX =   20'd999_999;    //计数器计数最大值

//wire  define
wire    po_key  ;

//------------- key_filter_inst -------------
key_filter
#(
    .CNT_MAX    (CNT_MAX    )   //计数器计数最大值
)
key_filter_inst
(
    .sys_clk    (sys_clk    ),  //系统时钟,频率50MHz
    .sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效
    .key_in     (pi_key     ),  //按键输入信号

    .key_flag   (po_key     )   //消抖后信号
);

//------------- flash_se_ctrl_inst -------------
flash_se_ctrl  flash_se_ctrl_inst
(
    .sys_clk    (sys_clk    ),  //系统时钟,频率50MHz
    .sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效
    .key        (po_key     ),  //按键输入信号

    .sck        (sck        ),  //片选信号
    .cs_n       (cs_n       ),  //串行时钟
    .mosi       (mosi       )   //主输出从输入数据
);

endmodule



3. 数据读操作

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

key_filter

`timescale  1ns/1ns

module  key_filter
#(
    parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
    input   wire    sys_clk     ,   //系统时钟50Mhz
    input   wire    sys_rst_n   ,   //全局复位
    input   wire    key_in      ,   //按键输入信号

    output  reg     key_flag        //key_flag为1时表示消抖后检测到按键被按下
                                    //key_flag为0时表示没有检测到按键被按下
);

//reg   define
reg     [19:0]  cnt_20ms    ;   //计数器

//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'b0;
    else    if(key_in == 1'b1)
        cnt_20ms <= 20'b0;
    else    if(cnt_20ms == CNT_MAX && key_in == 1'b0)
        cnt_20ms <= cnt_20ms;
    else
        cnt_20ms <= cnt_20ms + 1'b1;

//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_flag <= 1'b0;
    else    if(cnt_20ms == CNT_MAX - 1'b1)
        key_flag <= 1'b1;
    else
        key_flag <= 1'b0;

endmodule

uart_tx

`timescale  1ns/1ns

module  uart_tx
#(
    parameter   UART_BPS    =   'd9600,         //串口波特率
    parameter   CLK_FREQ    =   'd50_000_000    //时钟频率
)
(
     input   wire            sys_clk     ,   //系统时钟50MHz
     input   wire            sys_rst_n   ,   //全局复位
     input   wire    [7:0]   pi_data     ,   //模块输入的8bit数据
     input   wire            pi_flag     ,   //并行数据有效标志信号
 
     output  reg             tx              //串转并后的1bit数据
);

//localparam    define
localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ;

//reg   define
reg [12:0]  baud_cnt;
reg         bit_flag;
reg [3:0]   bit_cnt ;
reg         work_en ;

//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            work_en <= 1'b0;
        else    if(pi_flag == 1'b1)
            work_en <= 1'b1;
        else    if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
            work_en <= 1'b0;

//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            baud_cnt <= 13'b0;
        else    if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
            baud_cnt <= 13'b0;
        else    if(work_en == 1'b1)
            baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            bit_flag <= 1'b0;
        else    if(baud_cnt == 13'd1)
            bit_flag <= 1'b1;
        else
            bit_flag <= 1'b0;

//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 4'b0;
    else    if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
        bit_cnt <= 4'b0;
    else    if((bit_flag == 1'b1) && (work_en == 1'b1))
        bit_cnt <= bit_cnt + 1'b1;

//tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk or negedge sys_rst_n)
        if(sys_rst_n == 1'b0)
            tx <= 1'b1; //空闲状态时为高电平
        else    if(bit_flag == 1'b1)
            case(bit_cnt)
                0       : tx <= 1'b0;
                1       : tx <= pi_data[0];
                2       : tx <= pi_data[1];
                3       : tx <= pi_data[2];
                4       : tx <= pi_data[3];
                5       : tx <= pi_data[4];
                6       : tx <= pi_data[5];
                7       : tx <= pi_data[6];
                8       : tx <= pi_data[7];
                9       : tx <= 1'b1;
                default : tx <= 1'b1;
            endcase

endmodule

flash_read_ctrl

`timescale  1ns/1ns

module  flash_read_ctrl(
    input   wire            sys_clk     ,   //系统时钟,频率50MHz
    input   wire            sys_rst_n   ,   //复位信号,低电平有效
    input   wire            key         ,   //按键输入信号
    input   wire            miso        ,   //读出flash数据

    output  reg             sck         ,   //串行时钟
    output  reg             cs_n        ,   //片选信号
    output  reg             mosi        ,   //主输出从输入数据
    output  reg             tx_flag     ,   //输出数据标志信号
    output  wire    [7:0]   tx_data         //输出数据
);

//parameter define
parameter   IDLE    =   3'b001  ,   //初始状态
            READ    =   3'b010  ,   //数据读状态
            SEND    =   3'b100  ;   //数据发送状态

parameter   READ_INST   =   8'b0000_0011;   //读指令
parameter   NUM_DATA    =   16'd100     ;   //读出数据个数
parameter   SECTOR_ADDR =   8'b0000_0000,   //扇区地址
            PAGE_ADDR   =   8'b0000_0100,   //页地址
            BYTE_ADDR   =   8'b0010_0101;   //字节地址
parameter   CNT_WAIT_MAX=   16'd6_00_00 ;

//wire  define
wire    [7:0]   fifo_data_num   ;   //fifo内数据个数
//reg   define
reg     [4:0]   cnt_clk         ;   //系统时钟计数器
reg     [2:0]   state           ;   //状态机状态
reg     [15:0]  cnt_byte        ;   //字节计数器
reg     [1:0]   cnt_sck         ;   //串行时钟计数器
reg     [2:0]   cnt_bit         ;   //比特计数器
reg             miso_flag       ;   //miso提取标志信号
reg     [7:0]   data            ;   //拼接数据
reg             po_flag_reg     ;   //输出数据标志信号
reg             po_flag         ;   //输出数据
reg     [7:0]   po_data         ;   //输出数据
reg             fifo_read_valid ;   //fifo读有效信号
reg     [15:0]  cnt_wait        ;   //等待计数器
reg             fifo_read_en    ;   //fifo读使能
reg     [7:0]   read_data_num   ;   //读出fifo数据个数

//cnt_clk:系统时钟计数器,用以记录单个字节
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk  <=  5'd0;
    else    if(state == READ)
        cnt_clk  <=  cnt_clk + 1'b1;

//cnt_byte:记录输出字节个数和等待时间
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_byte    <=  16'd0;
    else    if((cnt_clk == 5'd31) && (cnt_byte == NUM_DATA + 16'd3))
        cnt_byte    <=  16'd0;
    else    if(cnt_clk == 5'd31)
        cnt_byte    <=  cnt_byte + 1'b1;

//cnt_sck:串行时钟计数器,用以生成串行时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_sck <=  2'd0;
    else    if(state == READ)
        cnt_sck <=  cnt_sck + 1'b1;

//cs_n:片选信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cs_n    <=  1'b1;
    else    if(key == 1'b1)
        cs_n    <=  1'b0;
    else    if((cnt_byte == NUM_DATA + 16'd3) && (cnt_clk == 5'd31) && (state == READ))
        cs_n    <=  1'b1;

//sck:输出串行时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        sck <=  1'b0;
    else    if(cnt_sck == 2'd0)
        sck <=  1'b0;
    else    if(cnt_sck == 2'd2)
        sck <=  1'b1;

//cnt_bit:高低位对调,控制mosi输出
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <=  3'd0;
    else    if(cnt_sck == 2'd2)
        cnt_bit <=  cnt_bit + 1'b1;

//state:两段式状态机第一段,状态跳转
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  IDLE;
    else
    case(state)
        IDLE:   if(key == 1'b1)
                    state   <=  READ;
        READ:   if((cnt_byte == NUM_DATA + 16'd3) && (cnt_clk == 5'd31))
                    state   <=  SEND;
        SEND:   if((read_data_num == NUM_DATA)
                && ((cnt_wait == (CNT_WAIT_MAX - 1'b1))))
                    state   <=  IDLE;
        default:    state   <=  IDLE;
    endcase

//mosi:两段式状态机第二段,逻辑输出
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        mosi    <=  1'b0;
    else    if((state == READ) && (cnt_byte>= 16'd4))
        mosi    <=  1'b0;
    else    if((state == READ) && (cnt_byte == 16'd0) && (cnt_sck == 2'd0))
        mosi    <=  READ_INST[7 - cnt_bit];  //读指令
    else    if((state == READ) && (cnt_byte == 16'd1) && (cnt_sck == 2'd0))
        mosi    <=  SECTOR_ADDR[7 - cnt_bit];  //扇区地址
    else    if((state == READ) && (cnt_byte == 16'd2) && (cnt_sck == 2'd0))
        mosi    <=  PAGE_ADDR[7 - cnt_bit];    //页地址
    else    if((state == READ) && (cnt_byte == 16'd3) && (cnt_sck == 2'd0))
        mosi    <=  BYTE_ADDR[7 - cnt_bit];    //字节地址

//miso_flag:miso提取标志信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        miso_flag   <=  1'b0;
    else    if((cnt_byte >= 16'd4) && (cnt_sck == 2'd1))
        miso_flag   <=  1'b1;
    else
        miso_flag   <=  1'b0;

//data:拼接数据
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data    <=  8'd0;
    else    if(miso_flag == 1'b1)
        data    <=  {data[6:0],miso};

//po_flag_reg:输出数据标志信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_flag_reg <=  1'b0;
    else    if((cnt_bit == 3'd7) && (miso_flag == 1'b1))
        po_flag_reg <=  1'b1;
    else
        po_flag_reg <=  1'b0;

//po_flag:输出数据标志信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_flag <=  1'b0;
    else
        po_flag <=  po_flag_reg;

//po_data:输出数据
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_data <=  8'd0;
    else    if(po_flag_reg == 1'b1)
        po_data <=  data;
    else
        po_data <=  po_data;

//fifo_read_valid:fifo读有效信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        fifo_read_valid <=  1'b0;
    else    if((read_data_num == NUM_DATA)
                && ((cnt_wait == (CNT_WAIT_MAX - 1'b1))))
        fifo_read_valid <=  1'b0;
    else    if(fifo_data_num == NUM_DATA)
        fifo_read_valid <=  1'b1;

//cnt_wait:两数据读取时间间隔
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_wait    <=  16'd0;
    else    if(fifo_read_valid == 1'b0)
        cnt_wait    <=  16'd0;
    else    if(cnt_wait == (CNT_WAIT_MAX - 1'b1))
        cnt_wait    <=  16'd0;
    else    if(fifo_read_valid == 1'b1)
        cnt_wait    <=  cnt_wait + 1'b1;

//fifo_read_en:fifo读使能信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        fifo_read_en <=  1'b0;
    else    if((cnt_wait == (CNT_WAIT_MAX - 1'b1))
                && (read_data_num < NUM_DATA))
        fifo_read_en <=  1'b1;
    else
        fifo_read_en <=  1'b0;

//read_data_num:自fifo中读出数据个数计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        read_data_num <=  8'd0;
    else    if(fifo_read_valid == 1'b0)
        read_data_num <=  8'd0;
    else    if(fifo_read_en == 1'b1)
        read_data_num <=  read_data_num + 1'b1;

//tx_flag
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        tx_flag <=  1'b0;
    else
        tx_flag <=  fifo_read_en;

//-------------fifo_data_inst--------------
fifo_data fifo_data_inst(
    .clock  (sys_clk      ),    //时钟信号
    .data   (po_data      ),    //写数据,8bit
    .wrreq  (po_flag      ),    //写请求
    .rdreq  (fifo_read_en ),    //读请求

    .q      (tx_data      ),    //数据读出,8bit
    .usedw  (fifo_data_num)     //fifo内数据个数
);

endmodule

spi_flash_read

`timescale  1ns/1ns

module  spi_flash_read(
    input   wire    sys_clk     ,   //系统时钟,频率50MHz
    input   wire    sys_rst_n   ,   //复位信号,低电平有效
    input   wire    pi_key      ,   //按键输入信号
    input   wire    miso        ,   //读出flash数据

    output  wire    cs_n        ,   //片选信号
    output  wire    sck         ,   //串行时钟
    output  wire    mosi        ,   //主输出从输入数据
    output  wire    tx              
);

//parameter define
parameter   CNT_MAX     =   20'd999_999     ;   //计数器计数最大值
parameter   UART_BPS    =   14'd9600        ,   //比特率
            CLK_FREQ    =   26'd50_000_000  ;   //时钟频率


//wire  define
wire            po_key  ;   //消抖处理后的按键信号
wire            tx_flag ;   //输入串口发送模块数据标志信号
wire    [7:0]   tx_data ;   //输入串口发送模块数据

//------------- key_filter_inst -------------
key_filter
#(
    .CNT_MAX    (CNT_MAX    )   //计数器计数最大值
)
key_filter_inst
(
    .sys_clk    (sys_clk    ),  //系统时钟,频率50MHz
    .sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效
    .key_in     (pi_key     ),  //按键输入信号

    .key_flag   (po_key     )   //消抖后信号
);

//-------------flash_read_ctrl_inst-------------
flash_read_ctrl  flash_read_ctrl_inst(

    .sys_clk    (sys_clk    ),  //系统时钟,频率50MHz
    .sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效
    .key        (po_key     ),  //按键输入信号
    .miso       (miso       ),  //读出flash数据

    .sck        (sck        ),  //片选信号
    .cs_n       (cs_n       ),  //串行时钟
    .mosi       (mosi       ),  //主输出从输入数据
    .tx_flag    (tx_flag    ),  //输出数据标志信号
    .tx_data    (tx_data    )   //输出数据
);

//-------------uart_tx_inst-------------
uart_tx
#(
    .UART_BPS    (UART_BPS ),         //串口波特率
    .CLK_FREQ    (CLK_FREQ )          //时钟频率
)
uart_tx_inst(
    .sys_clk     (sys_clk  ),   //系统时钟50Mhz
    .sys_rst_n   (sys_rst_n),   //全局复位
    .pi_data     (tx_data  ),   //并行数据
    .pi_flag     (tx_flag  ),   //并行数据有效标志信号
                                
    .tx          (tx       )    //串口发送数据
);

endmodule

tb_spi_flash_read

`timescale  1ns/1ns
module  tb_spi_flash_read();

//wire  define
wire    cs_n;
wire    sck ;
wire    mosi;
wire    miso;
wire    tx  ;

//reg   define
reg     clk     ;
reg     rst_n   ;
reg     key     ;

//时钟、复位信号、模拟按键信号
initial
    begin
        clk =   0;
        rst_n   <=  0;
        key <=  0;
        #100
        rst_n   <=  1;
        #1000
        key <=  1;
        #20
        key <=  0;
    end

always  #10 clk <=  ~clk;

defparam memory.mem_access.initfile = "initM25P16_test.txt";
defparam spi_flash_read_inst.flash_read_ctrl_inst.CNT_WAIT_MAX = 1000;
defparam spi_flash_read_inst.uart_tx_inst.CLK_FREQ = 100000;

//------------- spi_flash_read -------------
spi_flash_read    spi_flash_read_inst(
    .sys_clk    (clk    ),  //input     sys_clk
    .sys_rst_n  (rst_n  ),  //input     sys_rst
    .pi_key     (key    ),  //input     key
    .miso       (miso   ),

    .sck        (sck    ),  //output    sck
    .cs_n       (cs_n   ),  //output    cs_n
    .mosi       (mosi   ),  //output    mosi
    .tx         (tx     )
);

//------------- memory -------------
m25p16  memory (
    .c          (sck    ), 
    .data_in    (mosi   ), 
    .s          (cs_n   ), 
    .w          (1'b1   ), 
    .hold       (1'b1   ), 
    .data_out   (miso   )
);

endmodule



4. 数据页写操作

两种写入方式:页写,连续写

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

key_filter

`timescale  1ns/1ns

module  key_filter
#(
    parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
    input   wire    sys_clk     ,   //系统时钟50Mhz
    input   wire    sys_rst_n   ,   //全局复位
    input   wire    key_in      ,   //按键输入信号

    output  reg     key_flag        //key_flag为1时表示消抖后检测到按键被按下
                                    //key_flag为0时表示没有检测到按键被按下
);

//reg   define
reg     [19:0]  cnt_20ms    ;   //计数器

//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'b0;
    else    if(key_in == 1'b1)
        cnt_20ms <= 20'b0;
    else    if(cnt_20ms == CNT_MAX && key_in == 1'b0)
        cnt_20ms <= cnt_20ms;
    else
        cnt_20ms <= cnt_20ms + 1'b1;

//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_flag <= 1'b0;
    else    if(cnt_20ms == CNT_MAX - 1'b1)
        key_flag <= 1'b1;
    else
        key_flag <= 1'b0;

endmodule

flash_pp_ctrl

`timescale  1ns/1ns

module  flash_pp_ctrl(
    input   wire            sys_clk     ,   //系统时钟,频率50MHz
    input   wire            sys_rst_n   ,   //复位信号,低电平有效
    input   wire            key         ,   //按键输入信号

    output  reg             cs_n        ,   //片选信号
    output  reg             sck         ,   //串行时钟
    output  reg             mosi            //主输出从输入数据
);

//parameter define
parameter   IDLE    =   4'b0001 ,   //初始状态
            WR_EN   =   4'b0010 ,   //写状态
            DELAY   =   4'b0100 ,   //等待状态
            PP      =   4'b1000 ;   //页写状态
parameter   WR_EN_INST      =   8'b0000_0110,   //写使能指令
            PP_INST         =   8'b0000_0010;   //页写指令
parameter   SECTOR_ADDR     =   8'b0000_0000,   //扇区地址
            PAGE_ADDR       =   8'b0000_0100,   //页地址
            BYTE_ADDR       =   8'b0010_0101;   //字节地址
parameter   NUM_DATA        =   8'd100      ;   //页写数据个数(0-99)

//reg   define
reg     [7:0]   cnt_byte        ;   //字节计数器
reg     [3:0]   state           ;   //状态机状态
reg     [4:0]   cnt_clk         ;   //系统时钟计数器
reg     [1:0]   cnt_sck         ;   //串行时钟计数器
reg     [2:0]   cnt_bit         ;   //比特计数器
reg     [7:0]   data            ;   //页写入数据

//cnt_clk:系统时钟计数器,用以记录单个字节
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk  <=  5'd0;
    else    if(state != IDLE)
        cnt_clk  <=  cnt_clk + 1'b1;

//cnt_byte:记录输出字节个数和等待时间
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_byte    <=  8'd0;
    else    if((cnt_clk == 5'd31) && (cnt_byte == NUM_DATA + 8'd9))
        cnt_byte    <=  8'd0;
    else    if(cnt_clk == 5'd31)
        cnt_byte    <=  cnt_byte + 1'b1;

//cnt_sck:串行时钟计数器,用以生成串行时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_sck <=  2'd0;
    else    if((state == WR_EN) && (cnt_byte == 8'd1))
        cnt_sck <=  cnt_sck + 1'b1;
    else    if((state == PP) && (cnt_byte >= 8'd5)
                && (cnt_byte <= NUM_DATA + 8'd9 - 1'b1))
        cnt_sck <=  cnt_sck + 1'b1;

//cs_n:片选信号
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cs_n    <=  1'b1;
    else    if(key == 1'b1)
        cs_n    <=  1'b0;
    else    if((cnt_byte == 8'd2) && (cnt_clk == 5'd31) && (state == WR_EN))
        cs_n    <=  1'b1;
    else    if((cnt_byte == 8'd3) && (cnt_clk == 5'd31) && (state == DELAY))
        cs_n    <=  1'b0;
    else    if((cnt_byte == NUM_DATA + 8'd9) && (cnt_clk == 5'd31) && (state == PP))
        cs_n    <=  1'b1;

//sck:输出串行时钟
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        sck <=  1'b0;
    else    if(cnt_sck == 2'd0)
        sck <=  1'b0;
    else    if(cnt_sck == 2'd2)
        sck <=  1'b1;

//cnt_bit:高低位对调,控制mosi输出
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <=  3'd0;
    else    if(cnt_sck == 2'd2)
        cnt_bit <=  cnt_bit + 1'b1;

//data:页写入数据
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data <=  8'd0;
    else    if((cnt_clk == 5'd31) && ((cnt_byte >= 8'd9)
                && (cnt_byte < NUM_DATA + 8'd9 - 1'b1)))
        data <=  data + 1'b1;

//state:两段式状态机第一段,状态跳转
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  IDLE;
    else
    case(state)
        IDLE:   if(key == 1'b1)
                state   <=  WR_EN;
        WR_EN:  if((cnt_byte == 8'd2) && (cnt_clk == 5'd31))
                state   <=  DELAY;
        DELAY:  if((cnt_byte == 8'd3) && (cnt_clk == 5'd31))
                state   <=  PP;
        PP:     if((cnt_byte == NUM_DATA + 8'd9) && (cnt_clk == 5'd31))
                state   <=  IDLE;
        default:    state   <=  IDLE;
    endcase

//mosi:两段式状态机第二段,逻辑输出
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        mosi    <=  1'b0;
    else    if((state == WR_EN) && (cnt_byte== 8'd2))
        mosi    <=  1'b0;
    else    if((state == PP) && (cnt_byte == NUM_DATA + 8'd9))
        mosi    <=  1'b0;
    else    if((state == WR_EN) && (cnt_byte == 8'd1) && (cnt_sck == 5'd0))
        mosi    <=  WR_EN_INST[7 - cnt_bit];  //写使能指令
    else    if((state == PP) && (cnt_byte == 8'd5) && (cnt_sck == 5'd0))
        mosi    <=  PP_INST[7 - cnt_bit];    //页写指令
    else    if((state == PP) && (cnt_byte == 8'd6) && (cnt_sck == 5'd0))
        mosi    <=  SECTOR_ADDR[7 - cnt_bit];  //扇区地址
    else    if((state == PP) && (cnt_byte == 8'd7) && (cnt_sck == 5'd0))
        mosi    <=  PAGE_ADDR[7 - cnt_bit];    //页地址
    else    if((state == PP) && (cnt_byte == 8'd8) && (cnt_sck == 5'd0))
        mosi    <=  BYTE_ADDR[7 - cnt_bit];    //字节地址
    else    if((state == PP) && ((cnt_byte >= 8'd9)
                && (cnt_byte <= NUM_DATA + 8'd9 - 1'b1)) && (cnt_sck == 5'd0))
        mosi    <=  data[7 - cnt_bit];  //页写入数据

endmodule

spi_flash_pp

`timescale  1ns/1ns

module  spi_flash_pp
(
    input   wire    sys_clk     ,   //系统时钟,频率50MHz
    input   wire    sys_rst_n   ,   //复位信号,低电平有效
    input   wire    pi_key      ,   //按键输入信号

    output  wire    cs_n        ,   //片选信号
    output  wire    sck         ,   //串行时钟
    output  wire    mosi            //主输出从输入数据
);

//parameter define
parameter   CNT_MAX =   20'd999_999;    //计数器计数最大值

//wire  define
wire    po_key  ;

//------------- key_filter_inst -------------
key_filter
#(
    .CNT_MAX    (CNT_MAX    )   //计数器计数最大值
)
key_filter_inst
(
    .sys_clk    (sys_clk    ),  //系统时钟,频率50MHz
    .sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效
    .key_in     (pi_key     ),  //按键输入信号

    .key_flag   (po_key     )   //消抖后信号
);

//------------- flash_pp_ctrl_inst -------------
flash_pp_ctrl  flash_pp_ctrl_inst
(
    .sys_clk    (sys_clk    ),  //系统时钟,频率50MHz
    .sys_rst_n  (sys_rst_n  ),  //复位信号,低电平有效
    .key        (po_key     ),  //按键输入信号
                                
    .sck        (sck        ),  //片选信号
    .cs_n       (cs_n       ),  //串行时钟
    .mosi       (mosi       )   //主输出从输入数据
);

endmodule



5. 数据连续写操作

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/46100.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

专注于元宇宙实际应用方案的企业

元宇宙的话题持续火热。国内互联网大厂正在加大对元宇宙相关技术和应用的研发&#xff0c;元宇宙正在逐步成为创新创业的主战场。企业元宇宙在教育、会展、文创、旅游、博物馆、文化艺术、娱乐、社交、版权、零售等等领域发力&#xff0c;增加客户对企业的认同。 也许未来只需一…

接口测试学习第一天

1. 接口 接口的定义&#xff1a;是指系统或组件之间的交互点&#xff0c;通过这些交互点可以实现数据的交互。&#xff08;数据交互的通道&#xff09; 接口的分类&#xff1a;硬件接口和软件接口&#xff1b;我们这里只关注软件层面的接口&#xff1b; 1.1 接口的类型 接…

LeetCode-28-找出字符串中第一个匹配项的下标

1、KMP算法$$ 解决本问题最简单的方法就是暴力穷举&#xff0c;思路简单但时间复杂度为O(m∗n)O(m*n)O(m∗n)。此处我们仅考虑最优的KMP算法&#xff0c;时间复杂度为O(mn)O(mn)O(mn)。 KMP算法的优化之处在于当我们对比haystackhaystackhaystack和needleneedleneedle时&…

[附源码]计算机毕业设计springboot基于Java酒店管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

亚马逊商品销售数据爬虫分析报告

家电产业和消费者升级悄然地展开。 市场的这种变化使消费者对家用电器的期望不再仅仅是一个简单的功能满足&#xff0c;而是更多的细节体验和技术创新。 通过洞察家用电器的消费特点&#xff0c;有利于确定市场的未来趋势&#xff0c;从而积极应对市场变化。 ▼ 我们围绕亚马逊…

AI智能视频技术在考古工作中的安防应用

文物考古关系着民族文化的传承、历史的记录与保留&#xff0c;工作意义重大。考古发掘工地由于面积大、区域多且分散&#xff0c;以及周边环境复杂&#xff0c;因此安全防护工作开展困难&#xff0c;整体的安全形势不容乐观。 一、考古现场安保面临问题&#xff1a;1、考古遗址…

K - Scholomance Academy Gym - 103202K

题目链接 题意&#xff1a;很长&#xff0c;读了很长时间才懂&#xff1a; 就是给一个物品评分&#xff0c;假设分数大于等于x&#xff0c;就将其判断为正数&#xff0c;否则判断为负数 这样判断肯定会出现一些误判&#xff0c;那么我们将判为负数的正数成为假正数&#xff0…

C. Set or Decrease(二分 + 有两个不确定情况如何二分)

Problem - 1622C - Codeforces 给你一个整数数组a1,a2,...,an和整数k。 在一个步骤中&#xff0c;你可以 选择某个索引i并将ai减少1&#xff08;使aiai-1&#xff09;。 或者选择两个索引i和j&#xff0c;将ai等于aj&#xff08;使aiaj&#xff09;。 为了使数组∑i1nai≤k的…

启动服务提供者报 zookeeper not connected错

今天启动zookeeper的服务提供者后&#xff0c;报 zookeeper not connected错&#xff0c;记录一下解决过程 意思是zookeeper注册中心连接不上&#xff0c;无非两个原因&#xff1a; 第一&#xff1a;zookeeper没有启动好。第二&#xff1a;zookeeper的ip以及端口号配置没配好 …

算法day35|860,406,452

目录 860.柠檬水找零 406.根据身高重建队列 452. 用最少数量的箭引爆气球 860.柠檬水找零 class Solution:def lemonadeChange(self, bills: List[int]) -> bool:five,ten,twenty 0,0,0for bill in bills:#情况一&#xff1a;如果bills是5元&#xff0c;不需要找零if bill…

黑马点评--附近商铺

附近商铺 GEO数据结构 GEO就是Geolocation的简写形式&#xff0c;代表地理坐标。Redis在3.2版本加入了对GEO的支持&#xff0c;允许存储地理坐标消息&#xff0c;帮助我们根据经纬度来检索数据。常见的命令有&#xff1a; GEOADD&#xff1a;添加一个地理空间信息&#xff0…

gcexcel-java-5.2.5 Crack update in 2022-11-28

gcexcel高速 Java Excel 电子表格 API 库 在 Java 应用程序中以编程方式创建、编辑、导入和导出 Excel 电子表格。几乎可以在任何地方部署。 创建、加载、编辑和保存 Excel 电子表格 保存为 .XLSX、PDF、HTML、CSV 和 JSON 基于具有零 Excel 依赖性的 Excel 对象模型 在本地、…

Tomcat负载均衡部署动静分离

NginxTomcat架构拓扑 环境部署 Nginx服务器&#xff1a;IP地址192.168.32.3 Tomcat服务器1&#xff1a;IP地址192.168.32.4 Tomcat服务器2&#xff1a;IP地址192.168.32.5 关闭防火墙&#xff0c;关闭开机自启 systemctl stop firewalld.service setenforce 0Nginx 主机安…

计算机组成原理习题课第三章-1(唐朔飞)

计算机组成原理习题课第三章-1&#xff08;唐朔飞&#xff09; ✨欢迎关注&#x1f5b1;点赞&#x1f380;收藏⭐留言✒ &#x1f52e;本文由京与旧铺原创&#xff0c;csdn首发&#xff01; &#x1f618;系列专栏&#xff1a;java学习 &#x1f4bb;首发时间&#xff1a;&…

高电压放大器与高电流放大器该如何选择使用

虽然电压放大器和电流放大器都属于功率放大器的分支类型&#xff0c;所能起到的效果和作用也都是一致的&#xff0c;不过两者还是细微差别的。经常会有人在后台咨询“电压放大器和电流放大器有什么区别&#xff0c;该如何选择使用”等等&#xff0c;针对这些疑问&#xff0c;今…

zynq实现视频动态字符叠加OSD,提供2套工程源码和技术支持

目录1.网上同行的OSD方案(太low)2.本方案OSD的优势3.HLS实现方案4.OSD延时和资源占用情况5.工程1&#xff1a;zynq7100实现字符叠加6.上板调试验证7.福利&#xff1a;工程源码获取1.网上同行的OSD方案(太low) 视频的字符叠加&#xff0c;简称OSD&#xff0c;是FPGA图像处理的基…

PG::Vegeta1

nmap -Pn -p- -T4 --min-rate1000 192.168.201.73 nmap -Pn -p 22,80 -sCV 192.168.201.73 查看80端口的服务。 没有发现可用的信息&#xff0c;尝试爆破路径。 gobuster dir -e -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-big.txt -u htt…

年薪50W的数字前端设计工程师是做什么的?

近两年&#xff0c;芯片行业大火&#xff0c;行业的发展受到了很大的政策支持&#xff0c;芯片行业不仅发展前景好&#xff0c;薪资待遇也很高&#xff0c;所以不少人纷纷转行IC&#xff0c;那么转行IC岗位该如何选择呢&#xff1f;下面IC修真院就重点为大家来介绍一下数字前端…

【Android App】检查手机连接WiFi信息以及扫描周围WiFi的讲解及实战(附源码和演示 超详细必看)

需要全部代码请点赞关注收藏后评论区留言私信~~~ 一、检查是否连接WiFi以及输出WiFi信息 传统的定位方式不适用于室内的垂直定位&#xff0c;原因如下&#xff1a; &#xff08;1&#xff09;卫星定位要求没有障碍物遮挡&#xff0c;它在户外比较精准&#xff0c;在室内信号就…

【Canvas】js用canvas绘制一个钟表时钟动画效果

学习JavaScript的看过来&#xff0c;有没有兴趣用Canvas画图呢&#xff0c;可以画很多有趣的事物&#xff0c;自由发挥想象&#xff0c;收获多多哦&#xff0c;旋转角度绘图这个重点掌握到了吗&#xff0c;这里有一个例子&#xff0c;如何用canvas画钟表时钟动图效果&#xff0…