下冰雹 · 5月9日

基于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有三个状态寄存器,每一个状态寄存器的每一位都有各自的功能。大家可以具体的看器件手册,首先给大家简单的讲一下第一个状态寄存器。

image.png

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

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

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

部分操作命令如下:

image.png

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

写使能时序:

image.png

读使能时序:

image.png

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

设计架构

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

image.png

设计代码

顶层模块 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
      end
endmodule

SignalTap 采集图

image.png

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

THE END

原文:FPGA技术江湖
作者:The last one

相关文章推荐

更多FPGA干货请关注FPGA的逻辑技术专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
10599
内容数
561
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息