• FPGA零基础学习:SPI 协议驱动设计


    本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。

    系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。后续会陆续更新 Xilinx 的 Vivado、ISE 及相关操作软件的开发的相关内容,学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性的培训诱导,真正的去学习去实战应用。话不多说,上货。

    SPI 协议驱动设计

    作者:郝旭帅  校对:陆辉

    本篇实现基于叁芯智能科技的SANXIN -B01 FPGA开发板,以下为配套的教程,如有入手开发板,可以登录官方淘宝店购买,还有配套的学习视频。

    SANXIN-B01 Verilog教程-郝旭帅团队

    SPI是串行外设接口(Serial Peripheral Interface)的缩写。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议。

    SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,中间靠三线或者四线连接(三线时为单向传输或者数据线双向传输)。所有基于SPI的设备共有的,它们是MISO、MOSI、SCLK、CS。

    MISO– Master Input Slave Output,主设备数据输入,从设备数据输出。

    MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入。

    SCLK – Serial Clock,时钟信号,由主设备产生。

    CS – Chip Select,从设备使能信号,由主设备控制。

    cs是从芯片是否被主芯片选中的控制信号,也就是说只有片选信号为预先规定的使能信号时(高电位或低电位),主芯片对此从芯片的操作才有效。这就使在同一条总线上连接多个spi设备成为可能。

    通讯是通过数据交换完成的,由sclk提供时钟脉冲,mosi、miso则基于此脉冲完成数据传输。数据输出通过 mosi线,数据在时钟上升沿或下降沿时改变,在紧接着的下降沿或上升沿被读取。完成一位数据传输,输入也使用同样原理。因此,至少需要N次时钟信号的改变(上沿和下沿为一次),才能完成N位数据的传输。

    spi通信有四种不同的模式,不同的从设备可能在出厂时就已经配置为某种模式。通信的双方必须是工作在同一模式下,所以我们可以对主设备的spi模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式。

    mode0:CPOL=0,CPHA=0;

    mode1:CPOL=0,CPHA=1;

    mode2:CPOL=1,CPHA=0;

    mode3:CPOL=1,CPHA=1;

    时钟极性CPOL是用来配置SCLK在空闲时,应该处于的状态;时钟相位CPHA用来配置在第几个边沿进行采样。

    CPOL=0,表示在空闲状态时,时钟SCLK为低电平。

    CPOL=1,表示在空闲状态时,时钟SCLK为高电平。

    CPHA=0,表示数据采样是在第1个边沿。

    CPHA=1,表示数据采样是在第2个边沿。

    即:

    CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。

    CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。

    CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。

    CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。

    • 硬件简介

    FLASH闪存 的英文名称是"Flash Memory",一般简称为"Flash",它属于内存器件的一种,是一种非易失性( Non-Volatile )内存。

    在开发板上有一块flash(M25P16),用来保存FPGA的硬件配置信息,也可以用来存储用户的应用程序或数据。

    M25P16是一款带有写保护机制和高速SPI总线访问的2M字节串行Flash存储器,该存储器主要特点:2M字节的存储空间,分32个扇区,每个扇区256页,每页256字节;能单个扇区擦除和整片擦除;每扇区擦写次数保证10万次、数据保存期限至少20年。

    C(serial clock:串行时钟)为D和Q提供了数据输入或者输出的时序。D的数据总是在C的上升沿被采样。Q的数据 在C的下降沿被输出。

    (Chip Select:芯片选择端),当输入为低时,该芯片被选中,可以允许进行读写操作。当输入为高时,该芯片被释放,不能够进行操作。

    对于H——o——l——d——和W——, 为保持功能和硬件写保护功能,在本设计中不使用此管脚,在硬件设计时,这两个管脚全部被拉高了,即全部失效。

    flash采用spi的通信协议,flash当做从机。serial clcok等效于spi中的sclk,chip select等效于spi中的cs,D等效于spi中的mosi,Q等效于spi中的miso。

    flash可以支持mode0和mode3,这两种模式中,都是在时钟的上升沿采样,在时钟的下降沿发送数据。

    flash的每一页都可以被写入,但是写入只能是把1改变为0。擦除可以把0改变为1。所以在正常写入数据之前,都要将flash进行擦除。

    flash的命令表如下:

    下面介绍几个常用的命令。

    RDID(Read Identification :读ID):发送命令RDID(9F),然后接收第1个字节的memory type(20H),第二个字节的memory capacity(15H)。后续的字节暂不关心。

    WREN(Write Enable :写使能):在任何写或者擦除的命令之前,都必须首先打开写使能。打开写使能为发送命令WREN(06h)。

    RDSR(Read Status Register:读状态寄存器):发送命令RDSR(05h),然后返回一个字节的状态值。 

    状态寄存器的格式如下:

    WIP(Write In Progress bit)表示flash内部是否正在进行内部操作,写和擦除都会导致flash内部进行一段时间的工作,在内部工作期间,外部的命令会被忽略,所以在进行任何命令之前,都需要查看flash内部是否正在工作。WIP为1时,表示flash内部正在工作;WIP为0时,表示flash内部没有在工作。

    READ(Read DATA Bytes:读数据):发送命令READ(03H),后续发送3个字节的地址,然后就可以接收数据,内部的地址会不断递增。一个读命令就可以把整个flash全部读完。

    PP(Page Program :页编写):发送命令PP(02H),接着发送3个字节的地址,然后发送数据即可。切记所写的数据不能超过本页的地址范围。

    SE(Sector Erase :扇区擦除):发送命令SE(D8H),接着发送3个字节的地址。

    BE(Bulk Erase:整片擦除):发送命令BE(C7H)。

    关于flash的其他的介绍,可以参考03_芯片手册->FLASH->M25P16.pdf。

    • 设计要求

    设计flash(M25P16)控制器。

    • 设计分析

    根据M25P16的数据手册得知,其接口为spi接口,且支持模式0和模式3,本设计中选择模式0。

    输入时序图如下:

    输出时序如下:

    时序图中所对应的符号说明:

    根据输入和输出的时序图以及参数表,将SPI的时钟的频率定为10MHz。

    在设计中,FPGA作为主机,M25P16作为从机。

    • 架构设计和信号说明

    此模块命名为m25p16_drive。

    二级模块(分模块)(第一页)

    二级模块(分模块)(第二页)

    设计中,各个命令单独写出控制器,通过多路选择器选择出对应的命令,然后控制spi_8bit_drive将数据按照spi的协议发送出去。各个命令的脉冲通过ctrl模块进行控制各个命令控制器,写入的数据首先写入到写缓冲区,读出的数据读出后写入到读缓冲区。

    暂不分配的端口,在应用时都是由上游模块进行控制,本设计测试时,编写上游模块进行测试。

    各个模块的功能,和连接线的功能在各个模块设计中说明。

    • spi_8bit_drive设计实现

    本模块负责将8bit的并行数据按照spi协议发送出去,以及负责按照spi协议接收数据,将接收的数据(8bit)并行传输给各个模块。

    spi_send_en为发送数据使能信号(脉冲信号),spi_send_data为所要发送数据,spi_send_done为发送完成信号(脉冲信号)。

    spi_read_en为接收数据使能信号(脉冲信号),spi_read_data为所接收的数据,spi_read_done为接收完成信号(脉冲信号)。

    module spi_8bit_drive (  input   wire              clk,  input   wire              rst_n,  input   wire              spi_send_en,  input   wire  [7:0]       spi_send_data,  output  reg               spi_send_done,  input   wire              spi_read_en,  output  reg   [7:0]       spi_read_data,  output  reg               spi_read_done,  output  wire              spi_sclk,  output  wire              spi_mosi,  input   wire              spi_miso);  reg           [8:0]       send_data_buf;  reg           [3:0]       send_cnt;  reg                       rec_en;  reg                       rec_en_n;  reg           [3:0]       rec_cnt;  reg           [7:0]       rec_data_buf;  always @ (negedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      send_data_buf <= 9'd0;    else      if (spi_send_en == 1'b1)        send_data_buf <= {spi_send_data, 1'b0};      else        send_data_buf <= send_data_buf;  end  always @ (negedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      send_cnt <= 4'd8;    else      if (spi_send_en == 1'b1)        send_cnt <= 4'd0;      else        if (send_cnt < 4'd8)          send_cnt <= send_cnt + 1'b1;        else          send_cnt <= send_cnt;  end  assign spi_mosi = send_data_buf[8 - send_cnt];  assign spi_sclk = (send_cnt < 4'd8 || rec_en_n == 1'b1) ? clk : 1'b0;  always @ (negedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      spi_send_done <= 1'b0;    else      if (send_cnt == 4'd7)        spi_send_done <= 1'b1;      else        spi_send_done <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rec_en <= 1'b0;    else      if (spi_read_en == 1'b1)        rec_en <= 1'b1;      else        if (rec_cnt ==  4'd7)          rec_en <= 1'b0;        else          rec_en <= rec_en;  end  always @ (negedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rec_en_n <= 1'b0;    else      rec_en_n <= rec_en;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rec_data_buf <= 8'd0;    else      if (rec_en == 1'b1)        rec_data_buf <= {rec_data_buf[6:0], spi_miso};      else        rec_data_buf <=rec_data_buf;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rec_cnt <= 4'd0;    else      if (rec_en == 1'b1)        rec_cnt <= rec_cnt + 1'b1;      else        rec_cnt <= 4'd0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      spi_read_done <= 1'b0;    else      if (rec_cnt == 4'd8)        spi_read_done <= 1'b1;      else        spi_read_done <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      spi_read_data <= 8'd0;    else      if (rec_cnt == 4'd8)        spi_read_data <= rec_data_buf;      else        spi_read_data <= spi_read_data;  endendmodule

    在发送逻辑控制中,全部的信号采用下降沿驱动。利用外部给予的spi_send_en作为启动信号,启动send_cnt。send_cnt在不发送数据时为8,发送数据时,从0到7。

    在接收逻辑中,全部的信号采用上升沿驱动。利用外部给予的spi_read_en作为启动信号,启动rec_en,经过移位接收数据。

    在spi_sclk输出时,采用组合逻辑。由于设计采用spi的模式0,故而spi_sclk不发送或者接收数据时为0,接收数据时为时钟信号。因为要求为模式0,所以在接收数据时,spi_sclk的输出不能够先有下降沿,即要求spi_sclk的控制信号不能由上升沿信号驱动,所以将rec_en同步到下降沿的rec_en_n。

    仿真代码为:

    `timescale 1ns/1psmodule spi_8bit_drive_tb;  reg               clk;  reg               rst_n;  reg               spi_send_en;  reg     [7:0]     spi_send_data;  wire              spi_send_done;  reg               spi_read_en;  wire    [7:0]     spi_read_data;  wire              spi_read_done;  wire              spi_sclk;  wire              spi_mosi;  reg               spi_miso;  spi_8bit_drive spi_8bit_drive_inst(      .clk              (clk),      .rst_n            (rst_n),      .spi_send_en      (spi_send_en),      .spi_send_data    (spi_send_data),      .spi_send_done    (spi_send_done),      .spi_read_en      (spi_read_en),      .spi_read_data    (spi_read_data),      .spi_read_done    (spi_read_done),      .spi_sclk         (spi_sclk),      .spi_mosi         (spi_mosi),      .spi_miso         (spi_miso)  );  initial clk = 1'b0;  always # 50 clk = ~clk;  initial begin    rst_n = 1'b0;    spi_send_en = 1'b0;    spi_send_data = 8'd0;    spi_read_en = 1'b0;    spi_miso = 1'b0;    # 201    rst_n = 1'b1;    # 200    @ (posedge clk);    # 2;    spi_send_en = 1'b1;    spi_send_data = {$random} % 256;    @ (posedge clk);    # 2;    spi_send_en = 1'b0;    spi_send_data = 8'd0;    @ (posedge spi_send_done);    # 2000    @ (posedge clk);    # 2;    spi_read_en = 1'b1;    @ (posedge clk);    # 2;    spi_read_en = 1'b0;    @ (posedge spi_read_done);    # 200    $stop;  end  always @ (negedge clk) spi_miso <= {$random} % 2;endmodule

    在仿真中,将时钟设置为10MHz。

    所有的信号采用上升沿驱动。发送一个8bit的随机数值,接收一个8bit的随机数值。

    spi_miso信号为从机下降沿驱动信号。

    通过RTL仿真,可以看出发送和接收全部正常。

    • mux7_1设计实现

    本模块负责将7个命令模块发出的命令(写使能、写数据和读使能)经过选择发送给spi_8bit_drive模块。

    module mux7_1 (  input     wire          rdsr_send_en,  input     wire    [7:0] rdsr_send_data,  input     wire          rdsr_read_en,  input     wire          pp_send_en,  input     wire    [7:0] pp_send_data,  input     wire          wren_send_en,  input     wire    [7:0] wren_send_data,  input     wire          be_send_en,  input     wire    [7:0] be_send_data,  input     wire          se_send_en,  input     wire    [7:0] se_send_data,  input     wire          rdid_send_en,  input     wire    [7:0] rdid_send_data,  input     wire          rdid_read_en,  input     wire          read_send_en,  input     wire    [7:0] read_send_data,  input     wire          read_read_en,  input     wire    [2:0] mux_sel,                   output    reg           spi_send_en,  output    reg     [7:0] spi_send_data,  output    reg           spi_read_en);  always @ * begin    case (mux_sel)      3'd0    : begin         spi_send_en = rdsr_send_en;         spi_send_data = rdsr_send_data;         spi_read_en = rdsr_read_en;       end      3'd1    : begin         spi_send_en = pp_send_en;         spi_send_data = pp_send_data;         spi_read_en = 1'b0;       end      3'd2    : begin         spi_send_en = wren_send_en;         spi_send_data = wren_send_data;         spi_read_en = 1'b0;       end      3'd3    : begin         spi_send_en = be_send_en;         spi_send_data = be_send_data;         spi_read_en = 1'b0;       end      3'd4    : begin         spi_send_en = se_send_en;         spi_send_data = se_send_data;         spi_read_en = 1'b0;       end      3'd5    : begin         spi_send_en = rdid_send_en;         spi_send_data = rdid_send_data;         spi_read_en = rdid_read_en;       end      3'd6    : begin         spi_send_en = read_send_en;         spi_send_data = read_send_data;         spi_read_en = read_read_en;       end      default : begin         spi_send_en = 1'b0;         spi_send_data = 8'd0;         spi_read_en = 1'b0;       end    endcase  endendmodule

    在设计中,有的命令模块不需要进行读取(pp和se等等),此时将输出的读使能信号输出为低电平。

    • be设计实现

    该模块接收到be_en(整片擦除的脉冲信号)信号后,发送对应的使能和数据,等待发送完成脉冲。发送完成后,输出擦除完成的脉冲。

    module be (  input     wire            clk,  input     wire            rst_n,  input     wire            be_en,  output    reg             be_done,  output    reg             be_send_en,  output    wire    [7:0]   be_send_data,  input     wire            spi_send_done);  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      be_send_en <= 1'b0;    else      be_send_en <= be_en;  end  assign be_send_data = 8'hc7;  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      be_done <= 1'b0;    else      be_done <= spi_send_done;  endendmodule

    整片擦除的命令为8’hc7。

    • wren设计实现

    该模块接收到wren_en(打开flash内部的写使能的脉冲信号)信号后,发送对应的使能和数据,等待发送完成脉冲。发送完成后,输出擦除完成的脉冲。

    module wren (  input     wire            clk,  input     wire            rst_n,  input     wire            wren_en,  output    reg             wren_done,  output    reg             wren_send_en,  output    wire    [7:0]   wren_send_data,  input     wire            spi_send_done);  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      wren_send_en <= 1'b0;    else      wren_send_en <= wren_en;  end  assign wren_send_data = 8'h06;  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      wren_done <= 1'b0;    else      wren_done <= spi_send_done;  endendmodule

    打开flash内部写使能的命令码为8’h06。

    • se设计实现

    该模块接收到se_en(擦除扇区的写使能的脉冲信号)信号后,发送对应的使能和数据,等待发送完成脉冲。发送完成后,接着发送高八位地址,中间八位地址和低八位地址。全部发送完成后,发送se_done信号。

    该模块采用状态机实现。SE_STATE(扇区擦除命令发送)、H_ADDR(高八位地址发送)、M_ADDR(中间八位地址发送)、L_ADDR(低八位地址发送)、SE_DONE(扇区擦除完成)。所有的脉冲信号在未标注的时刻,输出全部为0。

    设计代码为:

    module se (  input   wire                clk,  input   wire                rst_n,  input   wire                se_en,  input   wire      [23:0]    se_addr,  output  reg                 se_done,  output  reg                 se_send_en,  output  reg       [7:0]     se_send_data,  input   wire                spi_send_done);  localparam      SE_STATE    = 5'b00001;  localparam      H_ADDR      = 5'b00010;  localparam      M_ADDR      = 5'b00100;  localparam      L_ADDR      = 5'b01000;  localparam      SE_DONE     = 5'b10000;  reg               [4:0]     c_state;  reg               [4:0]     n_state;  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      c_state <= SE_STATE;    else      c_state <= n_state;  end  always @ * begin    case (c_state)      SE_STATE    :   begin        if (se_en == 1'b0)          n_state = SE_STATE;        else          n_state = H_ADDR;      end      H_ADDR      :   begin        if (spi_send_done == 1'b0)          n_state = H_ADDR;        else          n_state = M_ADDR;      end      M_ADDR      :   begin        if (spi_send_done == 1'b0)          n_state = M_ADDR;        else          n_state = L_ADDR;      end      L_ADDR      :   begin        if (spi_send_done == 1'b0)          n_state = L_ADDR;        else          n_state = SE_DONE;      end      SE_DONE     :   begin        if (spi_send_done == 1'b0)          n_state = SE_DONE;        else          n_state = SE_STATE;      end      default     :     n_state = SE_STATE;    endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      se_send_en <= 1'b0;    else      case (c_state)        SE_STATE    :   begin          if (se_en == 1'b1)            se_send_en <= 1'b1;          else            se_send_en <= 1'b0;        end        H_ADDR      :   begin          if (spi_send_done == 1'b1)            se_send_en <= 1'b1;          else            se_send_en <= 1'b0;        end        M_ADDR      :   begin          if (spi_send_done == 1'b1)            se_send_en <= 1'b1;          else            se_send_en <= 1'b0;        end        L_ADDR      :   begin          if (spi_send_done == 1'b1)            se_send_en <= 1'b1;          else            se_send_en <= 1'b0;        end        SE_DONE     :   begin          se_send_en <= 1'b0;        end        default     :   se_send_en <= 1'b0;      endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      se_send_data <= 8'd0;    else      case (c_state)        SE_STATE    :   begin          if (se_en == 1'b1)            se_send_data <= 8'hd8;          else            se_send_data <= 8'd0;        end        H_ADDR      :   begin          if (spi_send_done == 1'b1)            se_send_data <= se_addr[23:16];          else            se_send_data <= 8'd0;        end        M_ADDR      :   begin          if (spi_send_done == 1'b1)            se_send_data <= se_addr[15:8];          else            se_send_data <= 8'd0;        end        L_ADDR      :   begin          if (spi_send_done == 1'b1)            se_send_data <= se_addr[7:0];          else            se_send_data <= 8'd0;        end        SE_DONE     :   begin          se_send_data <= 8'd0;        end        default     :   se_send_data <= 8'd0;      endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      se_done <= 1'b0;    else      if (c_state == SE_DONE && spi_send_done == 1'b1)        se_done <= 1'b1;      else        se_done <= 1'b0;  endendmodule

    在发送过程中,由于是每8bit发送一次,所以在时序上将看到发送时,每8个脉冲一组,中间会有明显的间隔。

    • pp设计实现

    该模块负责将外部写fifo中的数据写入到flash中。wr_fifo_rd为写fifo的读使能信号,wrdata为从写fifo中读出的数据,wr_len为需要写入flash中数据的长度,wr_addr为写入地址。

    该模块采用状态机实现。PP_STATE(发送pp命令),H_ADDR(发送高八位地址)、M_ADDR(发送中间八位地址),L_ADDR(发送低八位地址)、RDFIFO(读写fifo)、FIFO_WAIT(等待读写fifo的数据输出)、SEND(发送8bit数据)、SEND_WAIT(发送等待,发送完成后判断是否发送完成)。对于所有的脉冲信号,没有赋值的位置,全部赋值为0。

    cnt为记录已经发送的数据个数。

    设计代码为:

    module pp (  input   wire                  clk,  input   wire                  rst_n,  input   wire                  pp_en,  output  reg                   pp_done,  output  reg                   wr_fifo_rd,  input   wire    [7:0]         wrdata,  input   wire    [8:0]         wr_len,  input   wire    [23:0]        wr_addr,  output  reg                   pp_send_en,  output  reg     [7:0]         pp_send_data,  input   wire                  spi_send_done);  localparam      PP_STATE    = 8'b0000_0001;  localparam      H_ADDR      = 8'b0000_0010;  localparam      M_ADDR      = 8'b0000_0100;  localparam      L_ADDR      = 8'b0000_1000;  localparam      RDFIFO      = 8'b0001_0000;  localparam      FIFO_WAIT   = 8'b0010_0000;  localparam      SEND        = 8'b0100_0000;  localparam      SEND_WAIT   = 8'b1000_0000;  reg             [7:0]         c_state;  reg             [7:0]         n_state;  reg             [8:0]         cnt;  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      c_state <= PP_STATE;    else      c_state <= n_state;  end  always @ * begin    case (c_state)      PP_STATE        :   begin        if (pp_en == 1'b0)          n_state = PP_STATE;        else          n_state = H_ADDR;      end      H_ADDR          :   begin        if (spi_send_done == 1'b1)          n_state = M_ADDR;        else          n_state = H_ADDR;      end      M_ADDR          :   begin        if (spi_send_done == 1'b1)          n_state = L_ADDR;        else          n_state = M_ADDR;      end      L_ADDR          :   begin        if (spi_send_done == 1'b1)          n_state = RDFIFO;        else          n_state = L_ADDR;      end      RDFIFO          :   begin        if (spi_send_done == 1'b1)          n_state = FIFO_WAIT;        else          n_state = RDFIFO;      end      FIFO_WAIT       :   begin        n_state = SEND;      end      SEND            :   begin        n_state = SEND_WAIT;      end      SEND_WAIT       :   begin        if (spi_send_done == 1'b1)          if (cnt == wr_len)            n_state = PP_STATE;          else            n_state = FIFO_WAIT;        else          n_state = SEND_WAIT;      end      default       :   n_state = PP_STATE;    endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      pp_send_en <= 1'b0;    else      case (c_state)        PP_STATE      :   begin          if  (pp_en == 1'b1)            pp_send_en <= 1'b1;          else            pp_send_en <= 1'b0;        end        H_ADDR        :   begin          if (spi_send_done == 1'b1)            pp_send_en <= 1'b1;          else            pp_send_en <= 1'b0;        end        M_ADDR        :   begin          if (spi_send_done == 1'b1)            pp_send_en <= 1'b1;          else            pp_send_en <= 1'b0;        end        L_ADDR        :   begin          if (spi_send_done == 1'b1)            pp_send_en <= 1'b1;          else            pp_send_en <= 1'b0;        end        SEND          : begin          pp_send_en <= 1'b1;        end        default       :   pp_send_en <= 1'b0;      endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      pp_send_data <= 8'd0;    else      case (c_state)        PP_STATE      :   begin          if  (pp_en == 1'b1)            pp_send_data <= 8'h02;          else            pp_send_data <= 8'd0;        end        H_ADDR        :   begin          if (spi_send_done == 1'b1)            pp_send_data <= wr_addr[23:16];          else            pp_send_data <= 8'd0;        end        M_ADDR        :   begin          if (spi_send_done == 1'b1)            pp_send_data <= wr_addr[15:8];          else            pp_send_data <= 8'd0;        end        L_ADDR        :   begin          if (spi_send_done == 1'b1)            pp_send_data <= wr_addr[7:0];          else            pp_send_data <= 8'd0;        end        SEND          : begin          pp_send_data <= wrdata;        end        default       :   pp_send_data <= 8'd0;      endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      pp_done <= 1'b0;    else      if (c_state == SEND_WAIT && spi_send_done == 1'b1 && cnt == wr_len)        pp_done <= 1'b1;      else        pp_done <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      cnt <= 9'd0;    else      if ((c_state == RDFIFO && spi_send_done == 1'b1) || (c_state == SEND_WAIT && spi_send_done == 1'b1 && cnt < wr_len))        cnt <= cnt + 1'b1;      else        if (c_state == PP_STATE)          cnt <= 9'd0;        else          cnt <= cnt;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      wr_fifo_rd <= 1'b1;    else      if ((c_state == RDFIFO && spi_send_done == 1'b1) || (c_state == SEND_WAIT && spi_send_done == 1'b1 && cnt < wr_len))        wr_fifo_rd <= 1'b1;      else        wr_fifo_rd <= 1'b0;  endendmodule
    • rdsr设计实现

    本模块的功能为读取m25p16的状态寄存器,主要检测状态寄存器的最低位(WIP)。

    WIP(write in progress :正在进行写进程),该bie位表示了flash内部是否在进行写进程。如果处于写进程时,flash忽略外部所有的命令,所以建议在执行任何命令前,首先进行检测该位。1表示正在写进程中,0表示不处于写进程。

    如果检测到正在写进程中,进行延迟1ms,然后再次读取该位状态。直到写进程结束。

    本模块采用状态机设计实现。ILDE(发送读状态寄存器命令)、RDSRSTATE(发送读使能)、WIP(判断wip位)、 DELAY1ms(延迟1ms)。cnt为延迟1ms的计数器。

    设计代码为:

    module rdsr (  input   wire                    clk,  input   wire                    rst_n,  input   wire                    rdsr_en,  output  reg                     rdsr_done,  output  reg                     rdsr_send_en,  output  reg       [7:0]         rdsr_send_data,  input   wire                    spi_send_done,  output  reg                     rdsr_read_en,  input   wire      [7:0]         spi_read_data,  input   wire                    spi_read_done);  parameter     T_1ms           =       50_000;  localparam    IDLE            =       4'b0001;  localparam    RDSRSTATE       =       4'b0010;  localparam    WIP             =       4'b0100;  localparam    DELAY1ms        =       4'b1000;  reg               [3:0]         c_state;  reg               [3:0]         n_state;  reg               [15:0]        cnt;  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      c_state <= IDLE;    else      c_state <= n_state;  end  always @ * begin    case (c_state)      IDLE          :   begin        if (rdsr_en == 1'b0)          n_state = IDLE;        else          n_state = RDSRSTATE;      end      RDSRSTATE     :   begin        if (spi_send_done == 1'b1)          n_state = WIP;        else          n_state = RDSRSTATE;      end      WIP           :   begin        if (spi_read_done == 1'b0)          n_state = WIP;        else          if (spi_read_data[0] == 1'b0)            n_state = IDLE;          else            n_state = DELAY1ms;      end      DELAY1ms      :   begin        if (cnt < T_1ms - 1'b1)          n_state = DELAY1ms;        else          n_state = RDSRSTATE;      end      default       :   n_state = IDLE;    endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      cnt <= 16'd0;    else      if (c_state == DELAY1ms && cnt < T_1ms - 1'b1)        cnt <= cnt + 1'b1;      else        cnt <= 16'd0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdsr_done <= 1'b0;    else      if (c_state == WIP && spi_read_done == 1'b1 && spi_read_data[0] == 1'b0)        rdsr_done <= 1'b1;      else        rdsr_done <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdsr_send_data <= 8'd0;    else      if ((c_state == IDLE && rdsr_en == 1'b1) || (c_state == DELAY1ms && cnt == T_1ms - 1'b1))        rdsr_send_data <= 8'h05;      else        rdsr_send_data <= 8'd0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdsr_send_en <= 1'b0;    else      if ((c_state == IDLE && rdsr_en == 1'b1) || (c_state == DELAY1ms && cnt == T_1ms - 1'b1))        rdsr_send_en <= 1'b1;      else        rdsr_send_en <= 1'd0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdsr_read_en <= 1'b0;    else      if (c_state == RDSRSTATE && spi_send_done == 1'b1)        rdsr_read_en <= 1'b1;      else        rdsr_read_en <= 1'b0;  endendmodule
    • rdid设计实现

    该模块负责读取flash的ID(2015),验证ID的正确性。

    该模块采用状态机的方式实现。IDLE(等待读取ID的命令)、IDSTATE1(读取高八位ID)、IDSTATE2(读取中间八位ID)、IDSTATE3(读取低八位ID)、ID_CHECK(检测ID的正确性)。

    状态转移图如下:

    设计代码为:

    module rdid (  input     wire                  clk,  input     wire                  rst_n,  input     wire                  rdid_en,  output    reg                   rdid_done,  output    reg                   rdid_send_en,  output    reg     [7:0]         rdid_send_data,  input     wire                  spi_send_done,  output    reg                   rdid_read_en,  input     wire                  spi_read_done,  input     wire    [7:0]         spi_read_data);  localparam        IDLE          = 5'b00001;  localparam        IDSTATE1      = 5'b00010;  localparam        IDSTATE2      = 5'b00100;  localparam        IDSTATE3      = 5'b01000;  localparam        ID_CHECK      = 5'b10000;  reg               [4:0]         c_state;  reg               [4:0]         n_state;  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      c_state <= IDLE;    else      c_state <= n_state;  end  always @ * begin    case (c_state)      IDLE          :     begin        if (rdid_en == 1'b1)          n_state = IDSTATE1;        else          n_state = IDLE;      end      IDSTATE1      :     begin        if (spi_send_done == 1'b1)          n_state = IDSTATE2;        else          n_state = IDSTATE1;      end      IDSTATE2      :     begin        if (spi_read_done == 1'b1 && spi_read_data == 8'h20)          n_state = IDSTATE3;        else          n_state = IDSTATE2;      end      IDSTATE3      :     begin        if (spi_read_done == 1'b1 && spi_read_data == 8'h20)          n_state = ID_CHECK;        else          n_state = IDSTATE3;      end      ID_CHECK      :     begin        if (spi_read_done == 1'b1 && spi_read_data == 8'h15)          n_state = IDLE;        else          n_state = ID_CHECK;      end      default       :   n_state = IDLE;    endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdid_send_data <= 8'd0;    else      if (c_state == IDLE && rdid_en == 1'b1)        rdid_send_data <= 8'h9f;      else        rdid_send_data <= 8'd0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdid_send_en <= 1'b0;    else      if (c_state == IDLE && rdid_en == 1'b1)        rdid_send_en <= 1'b1;      else        rdid_send_en <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdid_read_en <= 1'b0;    else      if ((c_state == IDSTATE1 && spi_send_done == 1'b1) || (c_state == IDSTATE2 && spi_read_done == 1'b1 && spi_read_data == 8'h20) || (c_state == IDSTATE3 && spi_read_done == 1'b1 && spi_read_data == 8'h20))        rdid_read_en <= 1'b1;      else        rdid_read_en <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdid_done <= 1'b0;    else      if (c_state == ID_CHECK && spi_read_done == 1'b1 && spi_read_data == 8'h15)        rdid_done <= 1'b1;      else        rdid_done <= 1'b0;  endendmodule
    • read_ctrl设计实现

    该模块负责将flash的数据读出,写入到输出缓存中。

    该模块采用状态机实现。RD_STATE(等待读命令)、H_ADDR(发送高八位地址)、M_ADDR(发送中间八位地址)、L_ADDR(发送低八位地址)、RDDATA(读取数据)、WRFIFO(将读出的数据写入到FIFO中)、CHECK_LEN(判断读取的长度)。

    状态转移图如下:

    设计代码为:

    module read_ctrl (  input     wire                clk,  input     wire                rst_n,  input     wire                read_en,  input     wire    [23:0]      rd_addr,  input     wire    [8:0]       rd_len,  output    reg     [7:0]       rddata,  output    reg                 rd_fifo_wr,  output    reg                 read_done,  output    reg                 read_send_en,  output    reg     [7:0]       read_send_data,  input     wire                spi_send_done,  output    reg                 read_read_en,  input     wire                spi_read_done,  input     wire    [7:0]       spi_read_data);  localparam        RD_STATE    = 7'b000_0001;  localparam        H_ADDR      = 7'b000_0010;  localparam        M_ADDR      = 7'b000_0100;  localparam        L_ADDR      = 7'b000_1000;  localparam        RDDATA      = 7'b001_0000;  localparam        WRFIFO      = 7'b010_0000;  localparam        CHECK_LEN   = 7'b100_0000;  reg               [6:0]       c_state;  reg               [6:0]       n_state;  reg               [8:0]       cnt;  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      c_state <= RD_STATE;    else      c_state <= n_state;  end  always @ * begin    case (c_state)      RD_STATE      :   begin        if (read_en == 1'b1)          n_state = H_ADDR;        else          n_state = RD_STATE;      end      H_ADDR        :   begin        if (spi_send_done == 1'b1)          n_state = M_ADDR;        else          n_state = H_ADDR;      end      M_ADDR        :   begin        if (spi_send_done == 1'b1)          n_state = L_ADDR;        else          n_state = M_ADDR;      end      L_ADDR        :   begin        if (spi_send_done == 1'b1)          n_state = RDDATA;        else          n_state = L_ADDR;      end      RDDATA        :   begin        if (spi_send_done == 1'b1)          n_state = WRFIFO;        else          n_state = RDDATA;      end      WRFIFO        :   begin        if (spi_read_done == 1'b1)          n_state = CHECK_LEN;        else          n_state = WRFIFO;      end      CHECK_LEN     :   begin        if (cnt == rd_len)          n_state = RD_STATE;        else          n_state = WRFIFO;      end      default       :   n_state = RD_STATE;    endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      cnt <= 9'd0;    else      if ((c_state == RDDATA && spi_send_done == 1'b1) || (c_state == CHECK_LEN && cnt < rd_len))        cnt <= cnt + 1'b1;      else        if (c_state == RD_STATE)          cnt <= 9'd0;        else            cnt <= cnt;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      read_read_en <= 1'b0;    else      if ((c_state == RDDATA && spi_send_done == 1'b1) || (c_state == CHECK_LEN && cnt < rd_len))        read_read_en <= 1'b1;      else        read_read_en <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      read_done <= 1'b0;    else      if (c_state == CHECK_LEN && cnt == rd_len)        read_done <= 1'b1;      else        read_done <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rd_fifo_wr <= 1'b0;    else      if (c_state == WRFIFO && spi_read_done == 1'b1)        rd_fifo_wr <= 1'b1;      else        rd_fifo_wr <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rddata <= 8'd0;    else      if (c_state == WRFIFO && spi_read_done == 1'b1)        rddata <= spi_read_data;      else        rddata <= 8'd0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      read_send_en <= 1'b0;    else      case (c_state)        RD_STATE      :   begin          if  (read_en == 1'b1)            read_send_en <= 1'b1;          else            read_send_en <= 1'b0;        end        H_ADDR        :   begin          if (spi_send_done == 1'b1)            read_send_en <= 1'b1;          else            read_send_en <= 1'b0;        end        M_ADDR        :   begin          if (spi_send_done == 1'b1)            read_send_en <= 1'b1;          else            read_send_en <= 1'b0;        end        L_ADDR        :   begin          if (spi_send_done == 1'b1)            read_send_en <= 1'b1;          else            read_send_en <= 1'b0;        end        default       :   read_send_en <= 1'b0;      endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      read_send_data <= 8'd0;    else      case (c_state)        RD_STATE      :   begin          if  (read_en == 1'b1)            read_send_data <= 8'h03;          else            read_send_data <= 8'd0;        end        H_ADDR        :   begin          if (spi_send_done == 1'b1)            read_send_data <= rd_addr[23:16];          else            read_send_data <= 8'd0;        end        M_ADDR        :   begin          if (spi_send_done == 1'b1)            read_send_data <= rd_addr[15:8];          else            read_send_data <= 8'd0;        end        L_ADDR        :   begin          if (spi_send_done == 1'b1)            read_send_data <= rd_addr[7:0];          else            read_send_data <= 8'd0;        end        default       :   read_send_data <= 8'd0;      endcase  endendmodule
    • wr_fifo和rd_fifo调用

    两个fifo的宽度设置为8,深度设置为256,同步fifo,带有复位。

    • ctrl设计实现

    该模块根据外部的命令,按照m25p16的执行规则,进行控制各个模块的执行。

    该模块采用状态机实现。INIT_RDSR(读WIP),INIT_RDID(读ID),INIT_ID(判断ID),WIP(读WIP),WIP_DONE(等待WIP),IDLE(空闲状态),**STATE(执行对应的命令),**WREN(打开flash的写使能)。在进行任何命令前,都检查wip。

    状态转移图如下:

    在不同的状态,mux_sel选择对应的命令通过。

    drive_busy只有在IDLE状态才是低电平。

    spi_cs_n信号, DLE状态为高电平、WIP_DONE(INIT_RDID)中spi_read_done信号为高时 (保证能够多次读取状态寄存器)、在其他状态发生切换时,spi_cs_n 为高电平,否则为低电平。

    设计代码为:

    module ctrl (  input     wire                clk,  input     wire                rst_n,  input     wire                flag_be,  input     wire                flag_se,  input     wire                flag_wr,  input     wire                flag_rd,  input     wire    [23:0]      addr,  input     wire    [8:0]       len,  output    wire                drive_busy,  output    reg                 spi_cs_n,  input     wire                spi_read_done,  output    reg                 rdsr_en,  input     wire                rdsr_done,  output    reg                 wren_en,  input     wire                wren_done,  output    reg                 pp_en,  output    reg     [23:0]      wr_addr,  output    reg     [8:0]       wr_len,  input     wire                pp_done,  output    reg                 be_en,  input     wire                be_done,  output    reg                 se_en,  output    reg     [23:0]      se_addr,  input     wire                se_done,  output    reg                 rdid_en,  input     wire                rdid_done,  output    reg                 read_en,  output    reg     [23:0]      rd_addr,  output    reg     [8:0]       rd_len,  input     wire                read_done,  output    reg     [2:0]       mux_sel);  localparam        INIT_RDSR       =   13'b0000_0000_00001;  localparam        INIT_RDID       =   13'b0000_0000_00010;  localparam        INIT_ID         =   13'b0000_0000_00100;  localparam        WIP             =   13'b0000_0000_01000;  localparam        WIP_DONE        =   13'b0000_0000_10000;  localparam        IDLE            =   13'b0000_0001_00000;  localparam        RDSTATE         =   13'b0000_0010_00000;  localparam        PPWREN          =   13'b0000_0100_00000;  localparam        PPSTATE         =   13'b0000_1000_00000;  localparam        SEWREN          =   13'b0001_0000_00000;  localparam        SESTATE         =   13'b0010_0000_00000;  localparam        BEWREN          =   13'b0100_0000_00000;  localparam        BESTATE         =   13'b1000_0000_00000;  reg               [12:0]      c_state;  reg               [12:0]      n_state;  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      c_state <= INIT_RDSR;    else      c_state <= n_state;  end  always @ * begin    case (c_state)      INIT_RDSR         :   begin        n_state = INIT_RDID;      end      INIT_RDID         :   begin        if (rdsr_done == 1'b1)          n_state = INIT_ID;        else          n_state = INIT_RDID;      end      INIT_ID           :   begin        if (rdid_done == 1'b1)          n_state = WIP;        else          n_state = INIT_ID;      end      WIP               :   begin        n_state = WIP_DONE;      end      WIP_DONE          :   begin        if (rdsr_done == 1'b1)          n_state = IDLE;        else          n_state = WIP_DONE;      end      IDLE              :   begin        if (flag_wr == 1'b1)          n_state = PPWREN;        else          if (flag_rd == 1'b1)            n_state = RDSTATE;          else            if (flag_be == 1'b1)              n_state = BEWREN;            else              if (flag_se == 1'b1)                n_state = SEWREN;              else                n_state = IDLE;      end      RDSTATE           :   begin        if (read_done == 1'b1)          n_state = WIP;        else          n_state = RDSTATE;      end      PPWREN            :   begin        if (wren_done == 1'b1)          n_state = PPSTATE;        else          n_state = PPWREN;      end      PPSTATE           :   begin        if (pp_done == 1'b1)          n_state = WIP;        else          n_state = PPSTATE;      end      SEWREN            :   begin        if (wren_done == 1'b1)          n_state = SESTATE;        else          n_state = SEWREN;      end      SESTATE           :   begin        if (se_done == 1'b1)          n_state = WIP;        else          n_state = SESTATE;      end      BEWREN            :   begin        if (wren_done == 1'b1)          n_state = BESTATE;        else          n_state = BEWREN;      end      BESTATE           :   begin        if (be_done == 1'b1)          n_state = WIP;        else          n_state = BESTATE;      end      default     :   n_state = INIT_RDSR;    endcase  end  assign drive_busy = (c_state != IDLE || flag_be == 1'b1 || flag_rd == 1'b1 || flag_se == 1'b1 || flag_wr == 1'b1);  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      spi_cs_n <= 1'b1;    else      case (c_state)        INIT_RDSR       :     spi_cs_n <= 1'b1;        INIT_RDID       :     if (spi_read_done == 1'b1)                                spi_cs_n <= 1'b1;                              else                                spi_cs_n <= 1'b0;        INIT_ID         :     if (rdid_done == 1'b1)                                  spi_cs_n <= 1'b1;                              else                                spi_cs_n <= 1'b0;        WIP             :     spi_cs_n <= 1'b1;        WIP_DONE        :     if (spi_read_done == 1'b1)                                spi_cs_n <= 1'b1;                              else                                spi_cs_n <= 1'b0;        IDLE            :     spi_cs_n <= 1'b1;        RDSTATE         :     if (read_done == 1'b1)                                spi_cs_n <= 1'b1;                              else                                spi_cs_n <= 1'b0;        PPWREN          :     if (wren_done == 1'b1)                                spi_cs_n <= 1'b1;                              else                                spi_cs_n <= 1'b0;        PPSTATE         :     if (pp_done == 1'b1)                                spi_cs_n <= 1'b1;                              else                                spi_cs_n <= 1'b0;        SEWREN          :     if (wren_done == 1'b1)                                spi_cs_n <= 1'b1;                              else                                spi_cs_n <= 1'b0;        SESTATE         :     if (se_done == 1'b1)                                spi_cs_n <= 1'b1;                              else                                spi_cs_n <= 1'b0;        BEWREN          :     if (wren_done == 1'b1)                                spi_cs_n <= 1'b1;                              else                                spi_cs_n <= 1'b0;        BESTATE         :     if (be_done == 1'b1)                                spi_cs_n <= 1'b1;                              else                                spi_cs_n <= 1'b0;        default         :     spi_cs_n <= 1'b1;      endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdsr_en <= 1'b0;    else      if (c_state == INIT_RDSR || c_state == WIP)        rdsr_en <= 1'b1;      else        rdsr_en <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      wren_en <= 1'b0;    else      if (c_state == IDLE && (flag_be == 1'b1 || flag_se == 1'b1 || flag_wr == 1'b1))        wren_en <= 1'b1;      else        wren_en <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      pp_en <= 1'b0;    else      if (c_state == PPWREN && wren_done == 1'b1)        pp_en <= 1'b1;      else        pp_en <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      wr_len <= 9'd0;    else      if (c_state == IDLE && flag_wr == 1'b1)        wr_len <= len;      else        wr_len <= wr_len;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      wr_addr <= 24'd0;    else      if (c_state == IDLE && flag_wr == 1'b1)        wr_addr <= addr;      else        wr_addr <= wr_addr;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      be_en <= 1'b0;    else      if (c_state == BEWREN && wren_done == 1'b1)        be_en <= 1'b1;      else        be_en <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      se_en <= 1'b0;    else      if (c_state == SEWREN && wren_done == 1'b1)        se_en <= 1'b1;      else        se_en <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      se_addr <= 24'd0;    else      if (c_state == IDLE && flag_se == 1'b1)        se_addr <= addr;      else        se_addr <= se_addr;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdid_en <= 1'b0;    else      if (c_state == INIT_RDID && rdsr_done == 1'b1)        rdid_en <= 1'b1;      else        rdid_en <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rd_len <= 9'd0;    else      if (c_state == IDLE && flag_rd == 1'b1)        rd_len <= len;      else        rd_len <= rd_len;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rd_addr <= 24'd0;    else      if (c_state == IDLE && flag_rd == 1'b1)        rd_addr <= addr;      else        rd_addr <= rd_addr;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      read_en <= 1'b0;    else      if (c_state == IDLE && flag_rd == 1'b1)        read_en <= 1'b1;      else        read_en <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      mux_sel <= 3'd0;    else      case (c_state)        INIT_RDSR       :   mux_sel <= 3'd0;        INIT_RDID       :   mux_sel <= 3'd0;        INIT_ID         :   mux_sel <= 3'd5;        WIP             :   mux_sel <= 3'd0;        WIP_DONE        :   mux_sel <= 3'd0;        IDLE            :   mux_sel <= 3'd0;        RDSTATE         :   mux_sel <= 3'd6;        PPWREN          :   mux_sel <= 3'd2;        PPSTATE         :   mux_sel <= 3'd1;        SEWREN          :   mux_sel <= 3'd2;        SESTATE         :   mux_sel <= 3'd4;        BEWREN          :   mux_sel <= 3'd2;        BESTATE         :   mux_sel <= 3'd3;        default         :   mux_sel <= 3'd0;      endcase  endendmodule
    • m25p16_drive设计实现

    本模块负责连接所有二级模块,实现所有的功能。

    module m25p16_drive (  input     wire                  clk,  input     wire                  rst_n,  input     wire                  wrfifo_wr,  input     wire    [7:0]         wrfifo_data,  input     wire                  flag_be,  input     wire                  flag_se,  input     wire                  flag_wr,  input     wire                  flag_rd,  input     wire    [23:0]        addr,  input     wire    [8:0]         len,  input     wire                  rdfifo_rd,  output    wire    [7:0]         rdfifo_rdata,  output    wire                  rdfifo_rdempty,  output    wire                  drive_busy,  output    wire                  spi_cs_n,  output    wire                  spi_sclk,  output    wire                  spi_mosi,  input     wire                  spi_miso);  wire                            spi_send_en;  wire              [7:0]         spi_send_data;  wire                            spi_send_done;  wire                            spi_read_en;  wire              [7:0]         spi_read_data;  wire                            spi_read_done;  wire                            rdsr_send_en;  wire              [7:0]         rdsr_send_data;  wire                            rdsr_read_en;  wire                            pp_send_en;  wire              [7:0]         pp_send_data;  wire                            wren_send_en;  wire              [7:0]         wren_send_data;  wire                            be_send_en;  wire              [7:0]         be_send_data;  wire                            se_send_en;  wire              [7:0]         se_send_data;  wire                            rdid_send_en;  wire              [7:0]         rdid_send_data;  wire                            rdid_read_en;  wire                            read_send_en;  wire              [7:0]         read_send_data;  wire                            read_read_en;  wire              [2:0]         mux_sel;  wire                            be_en;  wire                            be_done;  wire                            wren_en;  wire                            wren_done;  wire                            se_en;  wire              [23:0]        se_addr;  wire                            se_done;  wire                            pp_en;  wire                            pp_done;  wire                            wr_fifo_rd;  wire              [7:0]         wrdata;  wire              [8:0]         wr_len;  wire              [23:0]        wr_addr;  wire                            rdsr_en;  wire                            rdsr_done;  wire                            rdid_en;  wire                            rdid_done;  wire                            read_en;  wire              [23:0]        rd_addr;  wire              [8:0]         rd_len;  wire              [7:0]         rddata;  wire                            rd_fifo_wr;  wire                            read_done;  ctrl ctrl_inst(      .clk            (clk),      .rst_n          (rst_n),      .flag_be        (flag_be),      .flag_se        (flag_se),      .flag_wr        (flag_wr),      .flag_rd        (flag_rd),      .addr           (addr),      .len            (len),      .drive_busy     (drive_busy),      .spi_cs_n       (spi_cs_n),      .spi_read_done  (spi_read_done),      .rdsr_en        (rdsr_en),      .rdsr_done      (rdsr_done),      .wren_en        (wren_en),      .wren_done      (wren_done),      .pp_en          (pp_en),      .wr_addr        (wr_addr),      .wr_len         (wr_len),      .pp_done        (pp_done),      .be_en          (be_en),      .be_done        (be_done),      .se_en          (se_en),      .se_addr        (se_addr),      .se_done        (se_done),      .rdid_en        (rdid_en),      .rdid_done      (rdid_done),      .read_en        (read_en),      .rd_addr        (rd_addr),      .rd_len         (rd_len),      .read_done      (read_done),      .mux_sel        (mux_sel)    );  rd_fifo  rd_fifo_inst (      .aclr           ( ~rst_n ),      .clock          ( clk ),      .data           ( rddata ),      .rdreq          ( rdfifo_rd ),      .wrreq          ( rd_fifo_wr ),      .empty          ( rdfifo_rdempty ),      .q              ( rdfifo_rdata )    );  wr_fifo  wr_fifo_inst (      .aclr           ( ~rst_n ),      .clock          ( clk ),      .data           ( wrfifo_data ),      .rdreq          ( wr_fifo_rd ),      .wrreq          ( wrfifo_wr ),      .q              ( wrdata )    );  read_ctrl read_ctrl_inst(      .clk              (clk),      .rst_n            (rst_n),      .read_en          (read_en),      .rd_addr          (rd_addr),      .rd_len           (rd_len),      .rddata           (rddata),      .rd_fifo_wr       (rd_fifo_wr),      .read_done        (read_done),      .read_send_en     (read_send_en),      .read_send_data   (read_send_data),      .spi_send_done    (spi_send_done),      .read_read_en     (read_read_en),      .spi_read_done    (spi_read_done),      .spi_read_data    (spi_read_data)    );  rdid rdid_inst(      .clk              (clk),      .rst_n            (rst_n),      .rdid_en          (rdid_en),      .rdid_done        (rdid_done),      .rdid_send_en     (rdid_send_en),      .rdid_send_data   (rdid_send_data),      .spi_send_done    (spi_send_done),      .rdid_read_en     (rdid_read_en),      .spi_read_done    (spi_read_done),      .spi_read_data    (spi_read_data)    );  rdsr rdsr_inst(      .clk              (clk),      .rst_n            (rst_n),      .rdsr_en          (rdsr_en),      .rdsr_done        (rdsr_done),      .rdsr_send_en     (rdsr_send_en),      .rdsr_send_data   (rdsr_send_data),      .spi_send_done    (spi_send_done),      .rdsr_read_en     (rdsr_read_en),      .spi_read_data    (spi_read_data),      .spi_read_done    (spi_read_done)    );  pp pp_inst(      .clk              (clk),      .rst_n            (rst_n),      .pp_en            (pp_en),      .pp_done          (pp_done),      .wr_fifo_rd       (wr_fifo_rd),      .wrdata           (wrdata),      .wr_len           (wr_len),      .wr_addr          (wr_addr),      .pp_send_en       (pp_send_en),      .pp_send_data     (pp_send_data),      .spi_send_done    (spi_send_done)    );  se se_inst(      .clk              (clk),      .rst_n            (rst_n),      .se_en            (se_en),      .se_addr          (se_addr),      .se_done          (se_done),      .se_send_en       (se_send_en),      .se_send_data     (se_send_data),      .spi_send_done    (spi_send_done)    );  wren wren_inst(      .clk              (clk),      .rst_n            (rst_n),      .wren_en          (wren_en),      .wren_done        (wren_done),      .wren_send_en     (wren_send_en),      .wren_send_data   (wren_send_data),      .spi_send_done    (spi_send_done)    );  be be_inst(      .clk              (clk),      .rst_n            (rst_n),      .be_en            (be_en),      .be_done          (be_done),      .be_send_en       (be_send_en),      .be_send_data     (be_send_data),      .spi_send_done    (spi_send_done)    );  mux7_1 mux7_1_inst(      .rdsr_send_en     (rdsr_send_en),      .rdsr_send_data   (rdsr_send_data),      .rdsr_read_en     (rdsr_read_en),      .pp_send_en       (pp_send_en),      .pp_send_data     (pp_send_data),      .wren_send_en     (wren_send_en),      .wren_send_data   (wren_send_data),      .be_send_en       (be_send_en),      .be_send_data     (be_send_data),      .se_send_en       (se_send_en),      .se_send_data     (se_send_data),      .rdid_send_en     (rdid_send_en),      .rdid_send_data   (rdid_send_data),      .rdid_read_en     (rdid_read_en),      .read_send_en     (read_send_en),      .read_send_data   (read_send_data),      .read_read_en     (read_read_en),      .mux_sel          (mux_sel),      .spi_send_en      (spi_send_en),      .spi_send_data    (spi_send_data),      .spi_read_en      (spi_read_en)    );  spi_8bit_drive spi_8bit_drive_inst(      .clk              (clk),      .rst_n            (rst_n),      .spi_send_en      (spi_send_en),      .spi_send_data    (spi_send_data),      .spi_send_done    (spi_send_done),      .spi_read_en      (spi_read_en),      .spi_read_data    (spi_read_data),      .spi_read_done    (spi_read_done),      .spi_sclk         (spi_sclk),      .spi_mosi         (spi_mosi),      .spi_miso         (spi_miso)  );endmodule
    • RTL仿真

    本次设计涉及到读取flash的id以及状态寄存器,所以在仿真时需要加入仿真模型。仿真模型放在msim的m25p16_sim_module中。m25p16为仿真模型的顶层文件。

    由于读写和擦除的时间较长,RTL仿真中,将只仿真RDSR和RDID,其他的功能测试在板级测试时进行。

    仿真代码如下:

    `timescale 1ns/1psmodule m25p16_drive_tb;  reg             clk;  reg             rst_n;  wire            drive_busy;  wire            spi_cs_n;  wire            spi_sclk;  wire            spi_mosi;  wire            spi_miso;  m25p16_drive m25p16_drive_inst(      .clk              (clk),      .rst_n            (rst_n),      .wrfifo_wr        (1'b0),      .wrfifo_data      (8'd0),      .flag_be          (1'b0),      .flag_se          (1'b0),      .flag_wr          (1'b0),      .flag_rd          (1'b0),      .addr             (24'd0),      .len              (9'd0),      .rdfifo_rd        (1'b0),      .rdfifo_rdata     (),      .rdfifo_rdempty   (),      .drive_busy       (drive_busy),      .spi_cs_n         (spi_cs_n),      .spi_sclk         (spi_sclk),      .spi_mosi         (spi_mosi),      .spi_miso         (spi_miso)    );  m25p16 m25p16_inst(      .c                (spi_sclk),      .data_in          (spi_mosi),      .s                (spi_cs_n),      .w                (1'b1),      .hold             (1'b1),      .data_out         (spi_miso)    );  initial clk = 1'b0;  always # 50 clk = ~clk;  initial begin    rst_n = 1'b0;    # 201    rst_n = 1'b1;    @ (negedge drive_busy);    # 2000    $stop;  endendmodule

    在设置testbench时,注意将所有文件全部添加到文件中。

    选择testbench时,注意选中设置的m25p16_drive_tb。

    利用modelsim仿真,可以得出如下RTL仿真波形。

    读到ID,以及检测WIP都是正确的。

    • 板级测试

    由于m25p16的时序原因,整个设计工作在10MHz(利用PLL产生)。

    在进行测试控制时,对最后一个扇区进行擦除;对最后一个扇区的第一页进行写入数据100个(1至100);对最后一个扇区的第一个进行读取,验证数据是否为1至100。

    测试的控制模块命名为test_ctrl。

    此模块采用状态机实现。WRFIFO(将1至100写入wrfifo中)、SE(扇区擦除)、PP(写入flash)、RD(读出flash)、WAIT_RD(等待读取)、CHECK( 检测读出的数据的正确性)。

    设计代码为:

    module test_ctrl (  input     wire                  clk,  input     wire                  rst_n,  output    reg                   wrfifo_wr,  output    reg     [7:0]         wrfifo_data,  output    reg                   flag_se,  output    reg                   flag_wr,  output    reg                   flag_rd,  input     wire                  drive_busy,  output    reg                   rdfifo_rd,  input     wire    [7:0]         rdfifo_rdata);  localparam      WRFIFO      =     6'b000_001;  localparam      SE          =     6'b000_010;  localparam      PP          =     6'b000_100;  localparam      RD          =     6'b001_000;  localparam      WAIT_RD     =     6'b010_000;  localparam      CHECK       =     6'b100_000;  reg               [5:0]         c_state;  reg               [5:0]         n_state;  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      c_state <= WRFIFO;    else      c_state <= n_state;  end  always @ * begin    case (c_state)      WRFIFO        :   begin        if (wrfifo_data == 8'd100)          n_state = SE;        else          n_state = WRFIFO;      end      SE            :   begin        if (drive_busy == 1'b0)          n_state = PP;        else          n_state = SE;      end      PP            :   begin        if (drive_busy == 1'b0)          n_state = RD;        else          n_state = PP;      end      RD            :   begin        if (drive_busy == 1'b0)          n_state = WAIT_RD;        else          n_state = RD;      end      WAIT_RD       :   begin        if (drive_busy == 1'b0)          n_state = CHECK;        else          n_state = WAIT_RD;      end      CHECK         :   begin        n_state = CHECK;      end      default       :   n_state = WRFIFO;    endcase  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      wrfifo_data <= 8'd0;    else      if (c_state == WRFIFO && wrfifo_data < 8'd100)        wrfifo_data <= wrfifo_data + 1'b1;      else        wrfifo_data <= 8'd0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      wrfifo_wr <= 1'd0;    else      if (c_state == WRFIFO && wrfifo_data < 8'd100)        wrfifo_wr <= 1'd1;      else        wrfifo_wr <= 1'd0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      flag_se <= 1'b0;    else      if (c_state == SE && drive_busy == 1'b0)        flag_se <= 1'b1;      else        flag_se <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      flag_wr <= 1'b0;    else      if (c_state == PP && drive_busy == 1'b0)        flag_wr <= 1'b1;      else          flag_wr <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      flag_rd <= 1'b0;    else      if (c_state == RD && drive_busy == 1'b0)        flag_rd <= 1'b1;      else        flag_rd <= 1'b0;  end  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdfifo_rd <= 1'b0;    else      if (c_state == WAIT_RD && drive_busy == 1'b0)        rdfifo_rd <= 1'b1;      else        if (c_state == CHECK && rdfifo_rdata == 8'd99)          rdfifo_rd <= 1'b0;        else            rdfifo_rd <= rdfifo_rd;  endendmodule

    将test模块设置为顶层。在test模块中,m25p16_drive例化中,对于整片擦除不做控制,对于addr直接指向最后一个扇区的第一页,len指定为100。

    代码为:

    module test (  input     wire                  clk,  input     wire                  rst_n,  output    wire                  spi_cs_n,  output    wire                  spi_sclk,  output    wire                  spi_mosi,  input     wire                  spi_miso);  wire                            wrfifo_wr;  wire              [7:0]         wrfifo_data;  wire                            flag_rd;  wire                            flag_se;  wire                            flag_wr;  wire                            drive_busy;  wire                            rdfifo_rd;  wire              [7:0]         rdfifo_rdata;  wire                            clk_10m;  wire                            pll_locked;  pll_test  pll_test_inst (      .areset             ( ~rst_n ),      .inclk0             ( clk ),      .c0                 ( clk_10m ),      .locked             ( pll_locked )    );  test_ctrl test_ctrl_inst(      .clk                (clk_10m),      .rst_n              (pll_locked),      .wrfifo_wr          (wrfifo_wr),      .wrfifo_data        (wrfifo_data),      .flag_se            (flag_se),      .flag_wr            (flag_wr),      .flag_rd            (flag_rd),      .drive_busy         (drive_busy),      .rdfifo_rd          (rdfifo_rd),      .rdfifo_rdata       (rdfifo_rdata)    );  m25p16_drive m25p16_drive_inst(      .clk              (clk_10m),      .rst_n            (pll_locked),      .wrfifo_wr        (wrfifo_wr),      .wrfifo_data      (wrfifo_data),      .flag_be          (1'b0),      .flag_se          (flag_se),      .flag_wr          (flag_wr),      .flag_rd          (flag_rd),      .addr             (24'hff0000),      .len              (9'd100),      .rdfifo_rd        (rdfifo_rd),      .rdfifo_rdata     (rdfifo_rdata),      .rdfifo_rdempty   (),      .drive_busy       (drive_busy),      .spi_cs_n         (spi_cs_n),      .spi_sclk         (spi_sclk),      .spi_mosi         (spi_mosi),      .spi_miso         (spi_miso)    );endmodule

    由于开发板上的flash是为FPGA进行保存配置信息的,所以管脚都连接在专用管脚上,本次实验需要将这专用管脚配置为普通io。

    右击器件型号,选择device。

    点击device and pin options。

    选择Dual-purpose pins,将其中所有的功能改为普通IO。

    点击ok后,即可进行综合分析。

    连接开发板和PC,打开逻辑分析仪。

    采样时钟选择10MHz(PLL 的c0),采样深度设置为2K。

    观测信号如下图所示。

    首先将wrfifo_wr的触发条件设置为上升沿。点击触发后,按下复位按键。触发后,可以看到写入数据1至100后,然后进行SE命令。

    将rdfifo_rd的触发条件设置为上升沿(将wrfifo_wr触发条件修改为donot care)。点击触发后,按下复位按键。

    通过仿真和下板实测,验证控制器设计正确。

    - End -

    【福利】:QQ交流群173560979,进群备注名字+学校/企业。淘宝店铺:https://shop588964188.taobao.com
    论坛网址:www.sxznfpga.com
    郝旭帅团队制作

    往期推荐

  • 相关阅读:
    vc++编程之在程序中加入网址链接
    VC++编程之对话框贴图
    软考(软件设计师)注意事项(攻略)
    解决SQLite数据库中文乱码问题
    计算机专业中经典书籍(程序猿和大学生必读)
    VC++编程中为程序加入启动画面功能
    动态规划的详细解析(01背包问题)
    动态规划之深入灵魂的解读(非常好)
    UML类图详解
    团队冲刺——第四天
  • 原文地址:https://www.cnblogs.com/sxznfpga/p/13303951.html
Copyright © 2020-2023  润新知