基于FPGA的内存128M flash芯片控制器设计



By
jonson
19 4 月 24
0
comment

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、”行侠仗义”栏里获取其他感兴趣的资源,或者一起煮酒言欢。

今天给大侠带来基于FPGA的内存128M flash芯片控制器设计,话不多说,上货。

设计原理及思路

FLASH闪存 闪存的英文名称是”Flash Memory”,一般简称为”Flash”,它属于内存器件的一种,是一种不挥发性( Non-Volatile )内存。

闪存的物理特性与常见的内存有根本性的差异:目前各类 DDR 、 SDRAM 或者 RDRAM 都属于挥发性内存,只要停止电流供应内存中的数据便无法保持,因此每次电脑开机都需要把数据重新载入内存;闪存在没有电流供应的条件下也能够长久地保持数据,其存储特性相当于硬盘,这项特性正是闪存得以成为各类便携型数字设备的存储介质的基础。

本次设计使用的是 W25Q128FV 内存128M的flash芯片,大家可以自己在官网上下载器件手册。在这里为了方便,也提供给各位,需要使用的可以在公众号内部回复“W25Q128FV手册资料”,各位可以根据实际项目应用灵活设计。

这款flash芯片的的存储是一个扇区4KB,一个扇区可以存256个字,一个字是8位,一个块是64KB,一共有256个块组成一个存储flash内存。

在下面的讲解中,将主要讲实现一下字节的读写,本次设计使用的协议是SPI协议,这个芯片支持QSPI,双端口SPI等。flash有三个状态寄存器,每一个状态寄存器的每一位都有各自的功能。大家可以具体的看器件手册,首先给大家简单的讲一下第一个状态寄存器。

这个状态寄存器第一位是可读、忙和不忙的标志位,大家可以在我们的设计中判断芯片是否忙和不忙来是否进行下一步的操作。第二位是一个写标志的信号,当写使能打开的时候它为1,只有它为1的时候我们才可以进行写,值得一说的不管是页操作,还是擦除等命令后都会使这个标志位变成0。然后前面的命令算的上的是保护命令,具体有使用的逻辑功能。

在flash中,写数据前先要擦除数据(想要擦除的地方),然后进行写,如果没有用过的flash芯片的话那么可以不用擦除,因为flash掉电不丢失数据。

设计思路大概是先读出器件厂商和芯片ID,然后写命令,写使能打开,页操作写入数据(值得说明的是我们FLASH是新的所以没进行擦除命令,建议擦除—关闭写使能 — 打开写使能),然后读第一个寄存器判断芯片的第一位是否忙,不忙然后进行读操作之后再数码管上显示出我们写入的数据。

部分操作命令如下:

我们的发送格式为在时钟的上升沿写入命令,在时钟的下降沿读出命令,用的是标准的SPI协议,端口IO0,和IO1,都是单向的。

写使能时序:

读使能时序:

其他的时序在这里就不分别列举出来了,大家可以参考器件手册。

设计架构

本次的设计是用一个FSM控制器来控制发送什么命令,flash模块判断FSM发送过来的state信号来选择应该执行什么操作,当命令写入或者读出后,会发送一个flag_done命令,这个命令让我们判断上个指令是否完成,如果完成后FAM将发送下一个命令。总体架构图如下:

设计代码

顶层模块 flash_top 代码:

module flash_top(clk , rst_n, q0,  q1, sclk, cs, seg, sel);
  input clk, rst_n;  input q0;  output q1;  output  sclk;  output  cs;  output [5:0] sel;  output  [7:0] seg;  wire [7:0] command;  wire [23:0] addr;  wire [2:0] state;  wire [7:0] data;  wire [23:0] show_data;  wire flag_done;      flash flash_dut(    .clk(clk) ,    .rst_n(rst_n),    .q0(q0),    .q1(q1),    .sclk(sclk),    .cs(cs),    .command(command),    .addr(addr),    .state(state),    .data(data),    .show_data(show_data),    .flag_done(flag_done)  );    fsm fsm_dut(    .clk(clk),    .rst_n(rst_n),    .flag_done(flag_done),    .command(command),    .addr(addr),     .state(state),    .data(data)  );    seg seg_dut(    .clk(clk),    .rst_n(rst_n),    .sel(sel),    .seg7(seg),    .data_in(show_data)  );

endmodule 

设计模块 fsm 代码:

module fsm(clk, rst_n, flag_done, command, addr, state, data);
  input clk, rst_n;  input flag_done;   //输入标志位  output reg [7:0] command;   //输出命令  output reg [23:0] addr;     //输出地址  output reg [2:0] state;    //输出状态模式  output reg [7:0] data;     //输出写入数据    reg [2:0] state_s;  reg [20:0] count;  always @ (posedge clk)    if(!rst_n)      begin        state_s <= 0;        data <= 8'd0;        addr <= 24'd0;        command <= 8'd0;        state <= 0;        count <= 0;      end    else      case (state_s)        0  :  begin              if(count < 200)    //延迟一段时间                count <= count + 1;              else                begin       //发送读厂商ID的命令                  command <= 8'h90;                  addr <= 24'd0;                  state <= 1;                  count <= 1;                end              if(flag_done)   //检查是否完成                state_s <= 1;            end                1  :  begin              if(count < 200)  //延迟一段时间                count <= count + 1;              else                begin    //写使能                  command <= 8'h06;                  state <= 3;                  count <= 0;                end              if(flag_done)  //检查是否完成                state_s <= 2;            end                2  :  begin              if(count < 200)   //延迟一段时间                count <= count + 1;              else                begin    //页操作                  command <= 8'h02;                  addr <= 24'd0;                  state <= 4;                  data <= 8'haa;                  count <= 0;                end              if(flag_done)   //检查是否完成                state_s <= 3;            end                3  :  begin              if(count < 200)   //延迟一段时间                count <= count + 1;              else                begin    //读寄存器                  command <= 8'h05;                  count <= 0;                  state <= 5;                end              if(flag_done)   //检查是否完成                state_s <= 4;            end                    4  :  begin               if(count < 200)    //延迟一段时间                count <= count + 1;              else                    begin     //读数据                  command <= 8'h03;                  addr <= 24'd0;                  state <= 2;                  count <= 0;                end            end                default: state_s <= 0;      endcase      endmodule

中间模块flash代码:

module flash (clk , rst_n, q0,  q1, sclk, cs, command, addr, state, data, show_data, flag_done);    input clk, rst_n;          input q0;  output reg q1;  output reg sclk;  output reg cs;  input [7:0] command;      //输入命令  input [23:0] addr;      //地址  input [2:0] state;      //状态  input [7:0] data;        //数据  output reg [23:0] show_data;   //显示  output reg flag_done;    //命令完成标志
  reg [5:0] count;  reg [5:0] cnt;  reg [31:0] temp;  reg [15:0] d;  reg [5:0] count_s;  reg [7:0] dou;  reg [39:0] xie;  reg [7:0] r_reg;    always @ (posedge clk)    if(!rst_n)      begin        sclk <= 1;        count_s <= 0;      end    else if(cs)      begin        count_s <= 0;        sclk <= 1;      end    else      begin          if(count_s == 25 - 1)   //产生1M的时钟            begin              count_s <= 0;              sclk <= ~sclk;            end        else          count_s <= count_s + 1;      end        reg [1:0] signle_s;    //边沿检测电路  always @ (posedge clk or negedge rst_n)    if(!rst_n)      begin        signle_s <= 2'b11;      end    else      begin        signle_s[0] <= sclk;        signle_s[1] <= signle_s[0];      end    assign pose_dge = signle_s[0] && ~signle_s[1];  //上升沿脉冲  assign nege_dge = ~signle_s[0] && signle_s[1];  //下降沿脉冲     reg [1:0] s;  reg [1:0] s1,s2,s3,s4;  always @ (posedge clk or negedge rst_n)    if(!rst_n)      begin        q1 <= 0;        count <= 0;         cs <= 1;        temp <= 0;        d <= 0;        cnt <= 0;        s <= 0;        s1 <= 0;        s2 <= 0;        s3 <= 0;        flag_done <= 0;        s4  <= 0;      end    else      begin      if (state == 1)      //state == 1进入读芯片的厂商和ID        case (s)          0:  begin  cs <= 0;  temp <= {command,addr}; s <= 1; end                  1  : begin              if(nege_dge)    //下降沿发送数据                begin                  if(count < 32)                      begin                      q1 <= temp[31];                      count <= count + 1;                      temp <= {temp[30:0],temp[31]};                    end                  else                     begin                      count <= 0;                      s <= 2;                    end                end              else                q1 <= q1;            end                      2  :  begin              if(pose_dge)    //上升沿采集数据                begin                  if(count < 16)                    begin                      count <= count + 1;                      d <= {d[14:0],q0};                    end                  else                    begin                      s <= 3;                      cs <= 1;                      count <= 0;                      flag_done <= 1;                      show_data <= d;                    end                end              else                begin                  s <= 2;                end            end                        3  :   begin                flag_done <= 0;              end                    endcase              else if(state == 2)      //state == 2进入读模式      case (s1)        0:  begin  cs <= 0;  temp <= {command,addr}; s1 <= 1; end              1  :begin            if(nege_dge)              begin                if(count < 32)                  begin                    q1 <= temp[31];                    count <= count + 1;                    temp <= {temp[30:0],temp[31]};                  end                else                   begin                    count <= 0;                    s1 <= 2;                  end              end            else              q1 <= q1;          end                  2  :  begin            if(pose_dge)              begin                if(count < 8)                  begin                    count <= count + 1;                    dou <= {dou[6:0],q0};                    s1 <= 2;                  end                else                  begin                    s1 <= 3;                    cs <= 1;                    count <= 0;                    flag_done <= 1;                    show_data <= dou;                  end              end            else              begin                s1 <= 2;              end          end                    3  :   begin              flag_done <= 0;            end      endcase          else if(state == 3)      //state == 3 进入写使能模式      case (s2)        0:  begin  cs <= 0;  temp <= {command,addr}; s2 <= 1; end              1  :begin            if(nege_dge)              begin                if(count < 8)                  begin                    q1 <= temp[31];                    count <= count + 1;                    temp <= {temp[30:0],temp[31]};                  end                else                   begin                    count <= 0;                    s2 <= 2;                    cs <= 1;                    flag_done <= 1;                  end              end            else              q1 <= q1;          end            2  : flag_done <= 0;    endcase          else if(state == 4)      //state == 4 进入页写操作      case (s3)        0:  begin  cs <= 0;  xie <= {command,addr,data}; s3 <= 1; end              1  :begin            if(nege_dge)              begin                if(count < 40)                  begin                    q1 <= xie[39];                    count <= count + 1;                    xie <= {xie[38:0],xie[39]};                  end                else                   begin                    count <= 0;                    s3 <= 2;                    cs <= 1;                    flag_done <= 1;                  end              end            else              q1 <= q1;          end            2  : flag_done <= 0;          endcase          else if(state == 5)      //state == 5 进入读第一个状态寄存器操作      case (s4)        0:  begin  cs <= 0;  r_reg <= command; s4 <= 1; end              1  :begin            if(nege_dge)              begin                if(count < 8)                  begin                    q1 <= r_reg[7];                    count <= count + 1;                    r_reg <= {r_reg[6:0],r_reg[7]};                  end                else                   begin                    count <= 0;                    s4 <= 2;                  end              end            else              q1 <= q1;          end            2  :  begin              if(pose_dge)                begin                  if(count < 8)                    begin                      count <= count + 1;                      d <= {d[14:0],q0};                    end                  else                    begin                      cs <= 1;                      count <= 0;                      if(!d[8]) //判断BUSY位忙不忙,不忙进入下个状态                        begin                          flag_done <= 1;                          s4 <= 3;                        end                      else    //忙继续读第一个寄存器                        s4 <= 0;                    end                end              else                begin                  s4 <= 2;                end            end            3  : flag_done <= 0;          endcase          end    endmodule 

数码管模块seg代码:

module seg(clk,rst_n,sel,seg7,data_in);
  input clk;  input rst_n;  input  [23:0] data_in;  output reg [5:0] sel;  output reg [7:0] seg7;    parameter s0 = 3'b000;  parameter s1 = 3'b001;  parameter s2 = 3'b010;  parameter s3 = 3'b011;  parameter s4 = 3'b100;  parameter s5 = 3'b101;    `define T1ms  50_000  //`define T1ms  5  reg [15:0] count;  reg  flag;  always @ (posedge clk or negedge rst_n)    if(!rst_n)      begin        count <= 16'd0;        flag <= 1;      end    else      if(count == (`T1ms / 2 - 1))        begin          count <= 16'd0;          flag <= ~ flag;        end      else        begin          count <= count + 1'b1;        end    reg [2:0] state;   reg [3:0] num;    always @ (posedge flag or negedge rst_n)    if(!rst_n)      begin        sel <= 3'b0;        state <= 3'b0;        num <= 4'b0;      end    else      begin        case (state)          s0:begin              state <= s1;              sel <= 6'b011111;              num <= data_in[23:20];            end          s1:begin              state <= s2;              sel <= 6'b101111;              num <= data_in[19:16];            end          s2:begin              state <= s3;              sel <= 6'b110111;              num <= data_in[15:12];            end          s3:begin                            state <= s4;              sel <= 6'b111011;              num <= data_in[11:8];                            end          s4:begin              state <= s5;              sel <= 6'b111101;              num <= data_in[7:4];            end          s5:begin              state <= s0;              sel <= 6'b111110;              num <= data_in[3:0];            end          default:state <= s0;        endcase      end  
  always @ (*)      begin        case (num)          0:seg7 = 8'b1100_0000;          1:seg7 = 8'b1111_1001;          2:seg7 = 8'b1010_0100;          3:seg7 = 8'b1011_0000;          4:seg7 = 8'b1001_1001;          5:seg7 = 8'b1001_0010;          6:seg7 = 8'b1000_0010;          7:seg7 = 8'b1111_1000;          8:seg7 = 8'b1000_0000;          9:seg7 = 8'b1001_0000;          10:seg7 = 8'b1000_1000;          11:seg7 = 8'b1000_0011;          12:seg7 = 8'b1100_0110;          13:seg7 = 8'b1010_0001;          14:seg7 = 8'b1000_0110;          15:seg7 = 8'b1000_1110;          default:;        endcase      endendmodule

SignalTap 采集图

图中显示的和我们的设计一样,发送的各个命令也是一样的,我们写入的是AA然后接收的也是AA,设计正确。

发表回复