gd25q128 flash写失败

程序是用verilog写的,可以读数据,写程序是先擦除,延时tse,再写入,但是读出来是ff,应该是没有写成功,然后我把程序中擦除部分删掉了直接写入数据,就可以读出来正确的数据
这是我用在线逻辑分析仪看的一部分波形,看起来时序应该没问题,但是擦除延时后面的程序波形逻辑分析仪显示不出来

img

下擦除命令后,不能简单延时,要通过读取状态来判断是否操作完成

这里放一下写程序的代码,希望各位大佬能帮忙看一下,也是根据网上的程序改的

module flash_write(

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

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

    //output
    output  [31:0]      dout        ,
    output              dout_vld    
);

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

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

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

//信号定义

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

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

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

    reg                     rdsr_wip        ;
    reg                     rdsr_wel        ;
    reg         [2:0]       flag            ;
    
    reg         [7:0]       tx_data         ;
    reg                     tx_req          ;
    
    reg         [31:0]      data            ;              
    reg                     data_vld        ;

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

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

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

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

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

//输出    

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

    always  @(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin
            tx_req <= 1'b0;
        end
        else if(s_idle2s_cmd)begin
            tx_req <= 1'b1;
        end
        else if(s_cmd2s_dela | s_addr2s_dela | s_data2s_dela)begin
            tx_req <= 1'b0;
        end
        else
            tx_req <=tx_req;
    end
//data    data_mask
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            data <= 0;           
        end 
        else if(m_rdsr2m_idle & ~rdsr_wip)begin //PP completed
            data <=wr_data;
        end
        else if(m_rdsr2m_idle & rdsr_wip)begin //PP failed
            data <=32'b0;
        end  
    end

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

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

endmodule


可能是你在写入数据之前,擦除了错误的位置或者没有对应的地址进行擦除。在诊断时序问题时,建议通过逻辑分析仪提取和比对信号波形,确保每个时钟周期内的信号值都符合预期。

另外,请检查你使用的擦除算法是否正确,并且每个操作的延时时间是否足够。如果延时不够可能会导致 FPGA 写入失败。最好参考 FPGA 芯片的相关文档,以确保所使用的擦除方法和写入方案是正确的。

如果以上方法不能解决问题,建议您尝试将问题范围进一步缩小到某个特定模块,以定位问题所在。如果你还无法解决问题,可以向特定技术社区或 FPGA 厂家寻求帮助,以获得更准确的解决方案。

根据您提供的信息,可能有以下几种原因导致无法正确写入数据:

1.擦除操作存在问题:如果擦除操作没有正确执行,可能会导致数据写入失败。建议检查擦除操作是否正确,并尝试增加擦除操作的延时时间,以确保擦除完成。

2.写入操作存在问题:如果写入操作没有正确执行,可能会导致数据写入失败。建议检查写入操作是否正确,并尝试增加写入操作的延时时间,以确保数据写入完成。

3.时序问题:时序问题可能会导致写入操作失败。建议检查时序是否正确,并尝试根据实际情况进行调整。

对于无法正常读取的现象,可能有以下几种原因:

1.写入操作失败:如果写入操作没有成功,可能导致数据无法正确读取。建议检查写入操作是否正确执行,并尝试增加写入操作的延时时间。

2.读取操作存在问题:如果读取操作没有正确执行,可能会导致数据无法正确读取。建议检查读取操作是否正确,并尝试增加读取操作的延时时间。

3.时序问题:时序问题可能会导致读取操作失败。建议检查时序是否正确,并尝试根据实际情况进行调整。

针对无法显示出擦除延时后面的程序波形,可能是由于逻辑分析仪的采样率不足或者采样时机不对导致的。建议检查逻辑分析仪的设置和采样时机,并尝试调整采样参数,以确保正确显示波形。

综上所述,建议您仔细检查写入和读取操作的时序和参数,并根据实际情况进行调整。如果问题仍然存在,建议使用其他工具或方法进行调试,以确定问题所在。

可以借鉴下

`timescale 1ns/1ns
 
 module file_ctrl;
 
 reg           clk;         
 reg            rst_n;
 
 reg  [7:0]  data_in;                //定义的数据输入寄存器
 reg  [7:0]  data_out;                        
 reg  [7:0]  cnt;
 
 integer  file_rd;                //定义数据读指针    
 integer  file_wr;                //定义数据写指针
 
 initial begin                        //定义时钟    
     clk = 1'b1;
     forever
     #10
     clk = ~clk;                
 end    
 
 initial begin                        //定义复位
     rst_n = 1'b0;
     #21
     rst_n = 1'b1;
 end
 
 initial begin                        //打开读取和写入的文件,这里的路径要对    
     file_rd = $fopen("E:/Project/file_ctrl/sim/tb/data_in.txt","r");
     file_wr = $fopen("E:/Project/file_ctrl/sim/tb/data_out.txt","w");
 end    
         
 always@(posedge clk or negedge rst_n)    
 begin
     if(rst_n == 1'b0)begin
         data_in <= 8'd0;        
         cnt <= 8'd0;
     end
     else if(cnt < 10)begin
         $fscanf(file_rd,"%h",data_in);            //读取每行    
         cnt <= cnt +1;        
         $fwrite(file_wr,"%h\n",    data_in);      //换行写入    
     end
     else begin
         data_in <= 8'd0;
         $fclose(file_rd);                        //关闭文件
         $fclose(file_wr);        
         cnt <= 8'd11;
     end
 end    
 
 
 
 endmodule

以下答案由GPT-4.5Plus大模型(语音)与博主A努力中.......共同编写:

阅读这个帖子,主要问题也是gd25q128闪存的擦除和编程时序引起的。

根据帖子的代码和描述,这里有几点建议:

  1. 擦除延时太短。gd25q128的典型块擦除时间是2毫秒左右,而帖子中的延时只有0.01毫秒,这远远不够。应将擦除延时增加至少在1-2毫秒。
  2. 没有检查擦除状态。应在擦除后读取状态寄存器,确保擦除完成后才执行下一步操作。否则如果擦除未完成就进行编程,会导致失败或写入旧数据。
  3. 未考虑擦除的扰动影响。块擦除会擦除整个扇区,这会影响同一扇区内其他块的编程和读取。应该先擦除所有需要擦除的块,然后再进行编程,避免互相影响。
  4. 缺乏错误处理。在编程和读取期间,应该检测并处理错误,确保操作成功完成。否则出错后续操作将失败或异常。

所以,总体的改进建议如下:

  1. 增加擦除延时,典型值2毫秒。
  2. 擦除后立即读取状态,确认擦除完成。
  3. 先完成所有擦除,后进行编程,避免同一扇区的互相影响。
  4. 在所有操作中添加状态和错误检查,并有相应的错误处理流程。
  5. 如果条件允许,推荐使用驱动芯片的专用SPI闪存控制器,它可以自动管理上述时序和错误处理,简化设计难度。

要解决这个问题,您可以:

  1. 增加更长的擦除延时,确保擦除操作完成后再进行下一步。典型的擦除时间可以查阅芯片的datasheet。

  2. 在擦除后立即读取状态寄存器,确认擦除是否完成,然后再进行下一步操作。例如:

verilog
erase_flash();    // 擦除操作
#tse;             // 延时

status = read_status_reg();  // 读取状态寄存器
while (status == BUSY) begin // 等待擦除完成
    #1;
    status = read_status_reg(); 
end 

write_flash(data);   // 擦除完成后写入数据

  1. 使用 FPGA 的闪存控制器 IP 核来完成闪存擦除和编程。这些 IP 核可以自动处理擦除延时和状态检查,简化设计难度。