文章目录
- 实现思路
- 具体实现
- 子模块实现
- top模块
- 测试
- Something
实现思路
建议读者先对 S25FL-S 系列 FLASH 进行了解,我之前的博文中有详细介绍。
笔者的芯片具体型号为 S25FL256SAGNFI00,存储容量 256Mb,增强高性能 EHPLC,4KB 与 64KB 混合 Sector 的存储阵列,256 Byte 的 Page Programming Buffer 大小,最高支持 133MHz,无硬复位 RESET# 引脚。
为简单起见,采用 SDR 时钟模式;为了兼顾读写速度,采用 Quad mode;同时考虑到 Quad Page Programming 地址只能通过 SI 单线传输,因此读、写 FLASH 分别采用 Quad Output Read、Quad Page Programming,以实现时序格式的统一,简化编程。
由于 S25FL-S 在 SCK 上升沿锁存数据,在 SCK 下降沿转换数据,因此主控端应在 SCK 下降沿转换数据,在 SCK 上升沿锁存数据。
由于写 FLASH 需要先进行写使能以及擦除操作,而擦除操作需要检查 WIP bit(SR1[0]);要使用 Quad 读写模式,需要置位 Quad bit(CR1[1]);要判断地址映射类型和四元读模式下的 Dummy 长度,需要实现读写寄存器。因此需要实现以下功能:写使能 WREN、写失能 WRDI、写寄存器 WRR、清除状态寄存器 CLSR、读状态寄存器 RDSR1/RDSR2、读配置寄存器 RDCR、擦除操作(扇区擦除 4SE、批量擦除 BE)、四元编程操作 4QPP、Quad Output Read 操作 4QOR 等。
为每一种功能单独写一个模块当然也是可行的思路,但过于繁杂;观察到在时序层面上述指令可以归类为简单的 5 种:单 8bit 指令(如 WREN、WRDI、CLSR、BE 等)、写寄存器(8bit 指令后跟随 1~4Byte 数据,SI 单线传输,如 WRR、ABWR、BRWR 等,甚至 8bit 指令 + 4Byte 地址的 4SE 也可归于此类)、读寄存器(8bit 指令(SI)后跟随 1~4Byte 输出(SO),如 RDSR1、RDSR2、RDCR1、ABRD、BRRD 等)、四元写 FLASH (8bit 指令(SI)+ 32bit 地址(SI)+ 1~256Byte 数据(IO0~IO3写),如 4QPP)、四元读 FLASH (8bit 指令(SI)+ 32bit 地址(SI)+ xbit Dummy + xByte 数据(IO0~IO3读回),如 4QOR)。
因此可以首先实现以上几个基础模块,然后根据需要在上层模块中用状态机控制几个基础模块的运行。
具体实现
由于本示例实现中每个子模块都涉及 FLASH_IO 这组 inout 线的操作,因此有注意事项如下:
每个 FPGA 管脚上都要有 IBUF、OBUF 或 IOBUF,input/output 管脚上 IBUF/OBUF 会自动生成,而 inout 管脚需要用户编写,要么用 IOBUF,要么直接用 link? xx_OBUF : 1’bz 这种形式(其实后者也是生成了一个 OBUF 和一个 IBUF)。
对于每个 FPGA 管脚,只能由一个 OBUF 驱动,因此如果多个子模块要用 inout 操作同一根线,会出问题(这种情况下 vivado 会自动生成 IBUF,导致模块大部分逻辑无效化,进而在综合后整个模块被优化掉,即使强制关闭 IBUF/OBUF 自动插入功能,也会因为多个 OBUF 驱动同一管脚而综合失败)。
因此子模块不能再保有 inout,而是通过操作顶层模块的 IOBUF 实现数据读写,具体实现方式为:子模块关于 FLASH_IO 的接口设计为两个单向接口(FLASH_IO_IBUF、FLASH_IO_OBUF),并给出何时使能 O_BUF 的 link 信号;顶层模块根据状态仲裁接通哪路子模块,并根据对应的 link 决定驱动方向。
子模块实现
- 单条指令
/* * file : flash_instruction.v* author : 今朝无言* Lab : WHU-EIS-LMSWE* date : 2023-11-15* version : v2.0* description : 单条 8bit 指令,从而支持诸如 WREN、WRDI、Bulk Erase 等指令* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.*/
`default_nettype none
module flash_instruction(
input wire clk,
input wire rst_n,output wire FLASH_SCK,
output reg FLASH_nCS,output reg [3:0] link,
output reg [3:0] FLASH_IO_OBUF,
input wire [3:0] FLASH_IO_IBUF,//usr interface
input wire send_en, //上升沿有效
input wire [7:0] instruction,
output reg busy
);reg FLASH_nCS = 1'b1;
assign FLASH_SCK = FLASH_nCS? 1'b1 : clk; //SPI mode 3reg [3:0] link = 4'h0;
reg [3:0] FLASH_IO_OBUF = 4'hf;//--------------------------------------------------
wire send_en_pe;
reg send_en_d0;
reg send_en_d1;always @(posedge clk) beginsend_en_d0 <= send_en;send_en_d1 <= send_en_d0;
endassign send_en_pe = send_en_d0 & (~send_en_d1);//--------------------FSM---------------------------
localparam S_IDLE = 8'h01;
localparam S_COMMAND = 8'h02;
localparam S_STOP = 8'h04;reg [7:0] state = S_IDLE;
reg [7:0] next_state;reg [2:0] cnt = 3'd0;always @(posedge clk or negedge rst_n) beginif(~rst_n) beginstate <= S_IDLE;endelse beginstate <= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(send_en_pe) beginnext_state <= S_COMMAND;endelse beginnext_state <= S_IDLE;endendS_COMMAND: beginif(cnt >= 3'd7) beginnext_state <= S_STOP;endelse beginnext_state <= S_COMMAND;endendS_STOP: beginnext_state <= S_IDLE;enddefault: beginnext_state <= S_IDLE;endendcase
end//FLASH_nCS
always @(negedge clk) begincase(state)S_COMMAND: beginFLASH_nCS <= 1'b0;enddefault: beginFLASH_nCS <= 1'b1;endendcase
end//cnt
always @(posedge clk) begincase(state)S_IDLE: begincnt <= 3'd0;endS_COMMAND: beginif(~FLASH_nCS) begincnt <= cnt + 1'b1;endelse begincnt <= 3'd0;endendS_STOP: begincnt <= 3'd0;enddefault: begincnt <= cnt;endendcase
end//FLASH_IO_OBUF
always @(negedge clk) begin //在SCK下降沿转换数据case(state)S_COMMAND: beginFLASH_IO_OBUF[0] <= instruction[3'd7-cnt]; //首先移出MSBFLASH_IO_OBUF[3:1] <= 3'b111;enddefault: beginFLASH_IO_OBUF <= 4'hf;endendcase
end//link
always @(negedge clk) begincase(state)S_COMMAND: beginlink <= 4'b1101;//指令阶段,SO应维持高阻,WP#、HOLD#应拉高;//而WP#、HOLD#内部有上拉电阻,因此IO1~IO3可以直接释放掉//不过为保险起见,这里还是强制拉高IO2/IO3,而IO1可以释放掉enddefault: beginlink <= 4'h0;endendcase
end//busy
always @(*) begincase(state)S_IDLE: beginbusy <= 1'b0;endS_COMMAND, S_STOP: beginbusy <= 1'b1;enddefault: beginbusy <= 1'b0;endendcase
endendmodule
- 读寄存器
/* * file : flash_RDR.v* author : 今朝无言* Lab : WHU-EIS-LMSWE* date : 2023-11-15* version : v2.0* description : 读寄存器,支持1~4Byte读取,从而支持对SR1、SR2、CR1、ABR、BAR等寄存器的读取* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.*/
`default_nettype none
module flash_RDR(
input wire clk,
input wire rst_n,output wire FLASH_SCK,
output reg FLASH_nCS,output reg [3:0] link,
output reg [3:0] FLASH_IO_OBUF,
input wire [3:0] FLASH_IO_IBUF,//usr interface
input wire read_en, //上升沿有效
input wire [7:0] instruction,input wire [3:0] Register_Len, //寄存器长度,1/2/4 Byte
output reg [31:0] Reg, //低位对齐。即1Byte的寄存器占用Reg[7:0],4Byte的寄存器占用Reg[31:0]output reg busy
);reg FLASH_nCS = 1'b1;
assign FLASH_SCK = FLASH_nCS? 1'b1 : clk; //SPI mode 3reg [3:0] link = 4'h0;
reg [3:0] FLASH_IO_OBUF = 4'hf;wire read_en_pe;
reg read_en_d0;
reg read_en_d1;always @(posedge clk) beginread_en_d0 <= read_en;read_en_d1 <= read_en_d0;
endassign read_en_pe = read_en_d0 & (~read_en_d1);//--------------------FSM---------------------------
localparam S_IDLE = 8'h01;
localparam S_COMMAND = 8'h02;
localparam S_RDR = 8'h04;
localparam S_STOP = 8'h08;reg [7:0] state = S_IDLE;
reg [7:0] next_state;reg [2:0] cnt = 3'd0; //Byte内bit计数
reg [3:0] cnt_Byte = 4'd0;always @(posedge clk or negedge rst_n) beginif(~rst_n) beginstate <= S_IDLE;endelse beginstate <= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(read_en_pe) beginnext_state <= S_COMMAND;endelse beginnext_state <= S_IDLE;endendS_COMMAND: beginif(cnt >= 3'd7) beginnext_state <= S_RDR;endelse beginnext_state <= S_COMMAND;endendS_RDR: beginif(cnt >= 3'd7 && cnt_Byte >= Register_Len - 1'b1) beginnext_state <= S_STOP;endelse beginnext_state <= S_RDR;endendS_STOP: beginnext_state <= S_IDLE;enddefault: beginnext_state <= S_IDLE;endendcase
end//FLASH_nCS
always @(negedge clk) begincase(state)S_COMMAND, S_RDR: beginFLASH_nCS <= 1'b0;enddefault: beginFLASH_nCS <= 1'b1;endendcase
end//cnt
always @(posedge clk) begincase(state)S_IDLE: begincnt <= 3'd0;endS_COMMAND, S_RDR: begin //将cnt设计为3bit位宽,可实现模8加if(~FLASH_nCS) begincnt <= cnt + 1'b1;endelse begincnt <= 3'd0;endendS_STOP: begincnt <= 3'd0;enddefault: begincnt <= cnt;endendcase
end//cnt_Byte
always @(posedge clk) begincase(state)S_RDR: beginif(cnt==3'd7) begincnt_Byte <= cnt_Byte + 1'b1;endelse begincnt_Byte <= cnt_Byte;endenddefault: begincnt_Byte <= 4'd0;endendcase
end//FLASH_IO_OBUF
always @(negedge clk) begin //在SCK下降沿转换数据case(state)S_COMMAND: beginFLASH_IO_OBUF[0] <= instruction[3'd7-cnt]; //首先移出MSBFLASH_IO_OBUF[3:1] <= 3'b111;enddefault: beginFLASH_IO_OBUF <= 4'hf;endendcase
end//link
always @(negedge clk) begincase(state)S_COMMAND: beginlink <= 4'b1101;endS_RDR: beginlink <= 4'h0;enddefault: beginlink <= 4'h0;endendcase
end//read reg
wire SO = FLASH_IO_IBUF[1];
always @(posedge clk or negedge rst_n) begin //须在SCK上升沿锁存数据if(~rst_n) beginReg <= 32'd0;endelse begincase(state)S_RDR: beginReg <= {Reg[30:0], SO}; //移位寄存来自SO的值enddefault: beginReg <= Reg;endendcaseend
end//busy
always @(*) begincase(state)S_IDLE: beginbusy <= 1'b0;enddefault: beginbusy <= 1'b1;endendcase
endendmodule
- 写寄存器
/* * file : flash_WRR.v* author : 今朝无言* Lab : WHU-EIS-LMSWE* date : 2023-11-15* version : v2.0* description : 写寄存器,支持 1Byte ~ 4Byte 的写入,* 从而支持对 SR1、CR1、ABR、BAR 等寄存器的写入操作,* 以及Sector Erase擦除命令* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.*/
`default_nettype none
module flash_WRR(
input wire clk,
input wire rst_n,output wire FLASH_SCK,
output reg FLASH_nCS,output reg [3:0] link,
output reg [3:0] FLASH_IO_OBUF,
input wire [3:0] FLASH_IO_IBUF,//usr interface
input wire send_en, //上升沿有效
input wire [7:0] instruction,input wire [3:0] Register_Len, //寄存器长度,1/2/4 Byte
input wire [7:0] Byte1,
input wire [7:0] Byte2,
input wire [7:0] Byte3,
input wire [7:0] Byte4,output reg busy
);
//使用示例:对于单写SR1寄存器,令Reg_Len=1,并在Byte1给出要写入SR1的值;
//对于写CR1,需要用到2Byte的形式,令Reg_Len=2,Byte1=SR1,Byte2=CR1;
//对于Autiboot Reister,Len=4,Byte1~4分别为ABR[31:24]、ABR[23:16]、ABR[15:8]、ABR[7:0];
//其余写寄存器指令依此类推
//甚至对于4SE擦除操作,Byte1~4可直接用作Sector地址使用reg FLASH_nCS = 1'b1;
assign FLASH_SCK = FLASH_nCS? 1'b1 : clk; //SPI mode 3reg [3:0] link = 4'h0;
reg [3:0] FLASH_IO_OBUF = 4'hf;wire send_en_pe;
reg send_en_d0;
reg send_en_d1;always @(posedge clk) beginsend_en_d0 <= send_en;send_en_d1 <= send_en_d0;
endassign send_en_pe = send_en_d0 & (~send_en_d1);//--------------------FSM---------------------------
localparam S_IDLE = 8'h01;
localparam S_COMMAND = 8'h02;
localparam S_WRR = 8'h04;
localparam S_STOP = 8'h08;reg [7:0] state = S_IDLE;
reg [7:0] next_state;reg [2:0] cnt = 3'd0; //Byte内bit计数
reg [3:0] cnt_Byte = 4'd0;always @(posedge clk or negedge rst_n) beginif(~rst_n) beginstate <= S_IDLE;endelse beginstate <= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(send_en_pe) beginnext_state <= S_COMMAND;endelse beginnext_state <= S_IDLE;endendS_COMMAND: beginif(cnt >= 3'd7) beginnext_state <= S_WRR;endelse beginnext_state <= S_COMMAND;endendS_WRR: beginif(cnt >= 3'd7 && cnt_Byte >= Register_Len - 1'b1) beginnext_state <= S_STOP;endelse beginnext_state <= S_WRR;endendS_STOP: beginnext_state <= S_IDLE;enddefault: beginnext_state <= S_IDLE;endendcase
end//FLASH_nCS
always @(negedge clk) begincase(state)S_COMMAND, S_WRR: beginFLASH_nCS <= 1'b0;enddefault: beginFLASH_nCS <= 1'b1;endendcase
end//cnt
always @(posedge clk) begincase(state)S_IDLE: begincnt <= 3'd0;endS_COMMAND, S_WRR: begin //将cnt设计为3bit位宽,可实现模8加if(~FLASH_nCS) begincnt <= cnt + 1'b1;endelse begincnt <= 3'd0;endendS_STOP: begincnt <= 3'd0;enddefault: begincnt <= cnt;endendcase
end//cnt_Byte
always @(posedge clk) begincase(state)S_WRR: beginif(cnt==3'd7) begincnt_Byte <= cnt_Byte + 1'b1;endelse begincnt_Byte <= cnt_Byte;endenddefault: begincnt_Byte <= 4'd0;endendcase
end//FLASH_IO_OBUF
always @(negedge clk) begin //在SCK下降沿转换数据case(state)S_COMMAND: beginFLASH_IO_OBUF[0] <= instruction[3'd7-cnt]; //首先移出MSBFLASH_IO_OBUF[3:1] <= 3'b111;endS_WRR: begincase(cnt_Byte)4'd0: FLASH_IO_OBUF[0] <= Byte1[3'd7-cnt];4'd1: FLASH_IO_OBUF[0] <= Byte2[3'd7-cnt];4'd2: FLASH_IO_OBUF[0] <= Byte3[3'd7-cnt];4'd3: FLASH_IO_OBUF[0] <= Byte4[3'd7-cnt];default: FLASH_IO_OBUF[0] <= 1'b1;endcaseFLASH_IO_OBUF[3:1] <= 3'b111;enddefault: beginFLASH_IO_OBUF <= 4'hf;endendcase
end//link
always @(negedge clk) begincase(state)S_COMMAND, S_WRR: beginlink <= 4'b1101;enddefault: beginlink <= 4'h0;endendcase
end//busy
always @(*) begincase(state)S_IDLE: beginbusy <= 1'b0;enddefault: beginbusy <= 1'b1;endendcase
endendmodule
- Page Programming
/* * file : flash_4QPP.v* author : 今朝无言* Lab : WHU-EIS-LMSWE* date : 2023-11-16* version : v2.0* description : 实现 4QPP 指令,32bit Addr,Quad Page Programming* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.*/
`default_nettype none
module flash_4QPP(
input wire clk, //S25FL256SAGNFI00 在 4QPP 下最大支持 80M
input wire rst_n,output wire FLASH_SCK,
output reg FLASH_nCS,output reg [3:0] link,
output reg [3:0] FLASH_IO_OBUF,
input wire [3:0] FLASH_IO_IBUF,//usr interface
input wire program_start, //上升沿有效input wire [31:0] addr, //起始地址,可以是任意字节地址,但建议是 Page 起始地址,S25FL256SAGNFI00 的 Page 大小为 256Byte
input wire [9:0] Byte_Len, //一次写多少字节数据,Page Programming 只能在当前 Page 内进行写入,超出的将被忽略,建议一次写一整个 Pageoutput wire data_rd_clk, //读数据的驱动时钟,若使用FIFO请用这个时钟,是clk的二分频时钟
output reg data_rden, //读数据请求,可用作 FIFO 的 rden,FIFO 应采用 First Word Fall Through
input wire [7:0] data, //字节数据output reg busy
);localparam instruction = 8'h34; //4QPP的指令码为 0x34reg FLASH_nCS = 1'b1;
assign FLASH_SCK = FLASH_nCS? 1'b1 : clk; //SPI mode 3reg [3:0] link = 4'h0;
reg [3:0] FLASH_IO_OBUF = 4'hf;wire program_start_pe;
reg program_start_d0;
reg program_start_d1;always @(posedge clk) beginprogram_start_d0 <= program_start;program_start_d1 <= program_start_d0;
endassign program_start_pe = program_start_d0 & (~program_start_d1);clkdiv #(.N(2))
clkdiv_2(.clk_in (clk),.clk_out (data_rd_clk)
);//--------------------FSM---------------------------
localparam S_IDLE = 8'h01;
localparam S_COMMAND = 8'h02;
localparam S_ADDR = 8'h04;
localparam S_QUAD_WR = 8'h08;
localparam S_STOP = 8'h10;reg [7:0] state = S_IDLE;
reg [7:0] next_state;reg [2:0] cnt = 3'd0; //Byte内bit计数
reg [9:0] cnt_Byte = 10'd0;always @(posedge clk or negedge rst_n) beginif(~rst_n) beginstate <= S_IDLE;endelse beginstate <= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(program_start_pe) beginnext_state <= S_COMMAND;endelse beginnext_state <= S_IDLE;endendS_COMMAND: beginif(cnt >= 3'd7) beginnext_state <= S_ADDR;endelse beginnext_state <= S_COMMAND;endendS_ADDR: beginif(cnt >= 3'd7 && cnt_Byte >= 4'd3) beginnext_state <= S_QUAD_WR;endelse beginnext_state <= S_ADDR;endendS_QUAD_WR: beginif(cnt >= 3'd4 && (Byte_Len == 10'd0 || cnt_Byte >= Byte_Len - 1'b1)) begin //Len=0时视作Len=1next_state <= S_STOP;endelse beginnext_state <= S_QUAD_WR;endendS_STOP: beginnext_state <= S_IDLE;enddefault: beginnext_state <= S_IDLE;endendcase
end//FLASH_nCS
always @(negedge clk) begincase(state)S_COMMAND, S_ADDR, S_QUAD_WR: beginFLASH_nCS <= 1'b0;enddefault: beginFLASH_nCS <= 1'b1;endendcase
end//cnt
always @(posedge clk) begincase(state)S_IDLE: begincnt <= 3'd0;endS_COMMAND, S_ADDR: beginif(~FLASH_nCS) begincnt <= cnt + 1'b1;endelse begincnt <= 3'd0;endendS_QUAD_WR: beginif(~FLASH_nCS) begincnt <= cnt + 3'd4; //Quad WR 阶段一次传送4bitendelse begincnt <= 3'd0;endendS_STOP: begincnt <= 3'd0;enddefault: begincnt <= cnt;endendcase
end//cnt_Byte
always @(posedge clk) begincase(state)S_ADDR: beginif(cnt==3'd7) beginif(cnt_Byte >= 16'd3) begincnt_Byte <= 10'd0;endelse begincnt_Byte <= cnt_Byte + 1'b1;endendelse begincnt_Byte <= cnt_Byte;endendS_QUAD_WR: beginif(cnt==3'd4) begincnt_Byte <= cnt_Byte + 1'b1;endelse begincnt_Byte <= cnt_Byte;endenddefault: begincnt_Byte <= 10'd0;endendcase
end//link
always @(negedge clk) begincase(state)S_COMMAND, S_ADDR: beginlink <= 4'b1101;endS_QUAD_WR: beginlink <= 4'b1111;enddefault: beginlink <= 4'h0;endendcase
end//FLASH_IO_OBUF
always @(negedge clk) begin //在SCK下降沿转换数据case(state)S_COMMAND: beginFLASH_IO_OBUF[0] <= instruction[3'd7-cnt]; //首先移出MSBFLASH_IO_OBUF[3:1] <= 3'b111;endS_ADDR: begincase(cnt_Byte[3:0])4'd0: FLASH_IO_OBUF[0] <= addr[5'd31-cnt];4'd1: FLASH_IO_OBUF[0] <= addr[5'd23-cnt];4'd2: FLASH_IO_OBUF[0] <= addr[5'd15-cnt];4'd3: FLASH_IO_OBUF[0] <= addr[5'd7-cnt];default: FLASH_IO_OBUF[0] <= 1'b1;endcaseFLASH_IO_OBUF[3:1] <= 3'b111;endS_QUAD_WR: begincase(cnt)4'd0: FLASH_IO_OBUF[3:0] <= data[7:4];4'd4: FLASH_IO_OBUF[3:0] <= data[3:0];default: FLASH_IO_OBUF[3:0] <= 4'hf;endcaseenddefault: beginFLASH_IO_OBUF <= 4'hf;endendcase
end//data_rden
always @(posedge clk) begincase(state)S_QUAD_WR: begindata_rden <= 1'b1;enddefault: begindata_rden <= 1'b0;endendcase
end//busy
always @(*) begincase(state)S_IDLE: beginbusy <= 1'b0;enddefault: beginbusy <= 1'b1;endendcase
endendmodule
- 读 FLASH 主存储器
/* * file : flash_4QOR.v* author : 今朝无言* Lab : WHU-EIS-LMSWE* date : 2023-11-17* version : v2.0* description : 4QOR读flash,32bit Addr,Quad Output Read* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.*/
`default_nettype none
module flash_4QOR(
input wire clk,
input wire rst_n,output wire FLASH_SCK,
output reg FLASH_nCS,output reg [3:0] link,
output reg [3:0] FLASH_IO_OBUF,
input wire [3:0] FLASH_IO_IBUF,//usr interface
input wire read_start, //上升沿有效input wire [31:0] addr, //起始地址,可以是任意字节地址
input wire [31:0] Byte_Len, //一次读多少字节数据,读取过程中flash会自动地址+1,达到最大地址后将从0x00地址继续读取output wire data_wr_clk, //写数据的驱动时钟,若使用FIFO请用这个时钟,是clk的二分频时钟
output reg data_wren, //wren,可用作 FIFO 的 wren
output reg [7:0] data, //读到的字节数据output reg busy,//LC
input wire [1:0] LC //LC bit(CR1[7:6])
);
//LC确定Dummy的长度,对于HPLC和PLC,在Quad Output Read下表现一致,
//都没有mode字段(mode len=0),除LC=11对应dummy len=0外(最大支持50MHz),其余都是dummy len=8localparam instruction = 8'h6C; //4QOR的指令码为 0x6Creg FLASH_nCS = 1'b1;
assign FLASH_SCK = FLASH_nCS? 1'b1 : clk; //SPI mode 3reg [3:0] link = 4'h0;
reg [3:0] FLASH_IO_OBUF = 4'hf;wire read_start_pe;
reg read_start_d0;
reg read_start_d1;always @(posedge clk) beginread_start_d0 <= read_start;read_start_d1 <= read_start_d0;
endassign read_start_pe = read_start_d0 & (~read_start_d1);clkdiv #(.N(2))
clkdiv_2(.clk_in (clk),.clk_out (data_wr_clk)
);//--------------------FSM---------------------------
localparam S_IDLE = 8'h01;
localparam S_COMMAND = 8'h02;
localparam S_ADDR = 8'h04;
localparam S_DUMMY = 8'h08;
localparam S_QUAD_RD = 8'h10;
localparam S_STOP = 8'h20;reg [7:0] state = S_IDLE;
reg [7:0] next_state;reg [2:0] cnt = 3'd0; //Byte内bit计数
reg [31:0] cnt_Byte = 32'd0;always @(posedge clk or negedge rst_n) beginif(~rst_n) beginstate <= S_IDLE;endelse beginstate <= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(read_start_pe) beginnext_state <= S_COMMAND;endelse beginnext_state <= S_IDLE;endendS_COMMAND: beginif(cnt >= 3'd7) beginnext_state <= S_ADDR;endelse beginnext_state <= S_COMMAND;endendS_ADDR: beginif(cnt >= 3'd7 && cnt_Byte >= 4'd3) begincase(LC) //根据LC判断Dummy的长度2'b11: beginnext_state <= S_QUAD_RD;end2'b00, 2'b01, 2'b10: beginnext_state <= S_DUMMY;enddefault: ;endcaseendelse beginnext_state <= S_ADDR;endendS_DUMMY: beginif(cnt >= 3'd7) beginnext_state <= S_QUAD_RD;endelse beginnext_state <= S_DUMMY;endendS_QUAD_RD: beginif(cnt >= 3'd4 && (Byte_Len == 32'd0 || cnt_Byte >= Byte_Len - 1'b1)) begin //Len=0时视作Len=1next_state <= S_STOP;endelse beginnext_state <= S_QUAD_RD;endendS_STOP: beginif(cnt>=1) begin //维持在STOP两个clk,以保持data和wren保持一个wr_clknext_state <= S_IDLE;endelse beginnext_state <= S_STOP;endenddefault: beginnext_state <= S_IDLE;endendcase
end//FLASH_nCS
always @(negedge clk) begincase(state)S_COMMAND, S_ADDR, S_DUMMY, S_QUAD_RD: beginFLASH_nCS <= 1'b0;enddefault: beginFLASH_nCS <= 1'b1;endendcase
end//cnt
always @(posedge clk) begincase(state)S_IDLE: begincnt <= 3'd0;endS_COMMAND, S_ADDR: beginif(~FLASH_nCS) begincnt <= cnt + 1'b1;endelse begincnt <= 3'd0;endendS_DUMMY: beginif(cnt >= 3'd7) begin //这里设置Bummy长度;由于4QOR只有0/8的Dummy长度,因该case实际可以和上面合并cnt <= 3'd0;endelse begincnt <= cnt + 1'b1;endendS_QUAD_RD: begincnt <= cnt + 3'd4; //Quad RD 阶段一次读回4bitendS_STOP: begincnt <= 3'd1;enddefault: begincnt <= cnt;endendcase
end//cnt_Byte
always @(posedge clk) begincase(state)S_ADDR: beginif(cnt==3'd7) beginif(cnt_Byte >= 32'd3) begincnt_Byte <= 32'd0;endelse begincnt_Byte <= cnt_Byte + 1'b1;endendelse begincnt_Byte <= cnt_Byte;endendS_DUMMY: begincnt_Byte <= 32'd0;endS_QUAD_RD: beginif(cnt==3'd4) begincnt_Byte <= cnt_Byte + 1'b1;endelse begincnt_Byte <= cnt_Byte;endenddefault: begincnt_Byte <= 32'd0;endendcase
end//link
always @(negedge clk) begincase(state)S_COMMAND, S_ADDR: beginlink <= 4'b1101;endS_DUMMY, S_QUAD_RD: begin //为防止主控端与flash端的驱动器冲突,Dummy期间主控端应释放总线link <= 4'b0000;enddefault: beginlink <= 4'h0;endendcase
end//FLASH_IO_OBUF
always @(negedge clk) begin //在SCK下降沿转换数据case(state)S_COMMAND: beginFLASH_IO_OBUF[0] <= instruction[3'd7-cnt]; //首先移出MSBFLASH_IO_OBUF[3:1] <= 3'b111;endS_ADDR: begincase(cnt_Byte[3:0])4'd0: FLASH_IO_OBUF[0] <= addr[5'd31-cnt];4'd1: FLASH_IO_OBUF[0] <= addr[5'd23-cnt];4'd2: FLASH_IO_OBUF[0] <= addr[5'd15-cnt];4'd3: FLASH_IO_OBUF[0] <= addr[5'd7-cnt];default: FLASH_IO_OBUF[0] <= 1'b1;endcaseFLASH_IO_OBUF[3:1] <= 3'b111;enddefault: beginFLASH_IO_OBUF <= 4'hf;endendcase
end//data_tmp
reg [7:0] data_tmp;
always @(posedge clk) begin //须在SCK上升沿锁存数据case(state)S_QUAD_RD: begincase(cnt)3'd0: begindata_tmp[7:4] <= FLASH_IO_IBUF;end3'd4: begindata_tmp[3:0] <= FLASH_IO_IBUF;enddefault: begindata_tmp <= data_tmp;endendcaseenddefault: begindata_tmp <= data_tmp;endendcase
end//data_wren & data
reg data_wren_buf;
reg [7:0] data_buf;
always @(posedge clk) begincase(state)S_QUAD_RD: beginif(cnt==0 && cnt_Byte>=1) begindata_wren_buf <= 1'b1;data_buf <= data_tmp;endelse begindata_wren_buf <= data_wren_buf;data_buf <= data_buf;endendS_STOP: begin //S_STOP时锁存输出最后一个数据if(cnt==0) begindata_wren_buf <= 1'b1;data_buf <= data_tmp;endelse begindata_wren_buf <= data_wren_buf;data_buf <= data_buf;endenddefault: begindata_wren_buf <= 1'b0;data_buf <= 8'd0;endendcase
endalways @(posedge data_wr_clk) begin //同步到data_wr_clk时钟域data_wren <= data_wren_buf;data <= data_buf;
end//busy
always @(*) begincase(state)S_IDLE: beginbusy <= 1'b0;enddefault: beginbusy <= 1'b1;endendcase
endendmodule
top模块
- FLASH_top.v
/* * file : FLASH_top.v* author : 今朝无言* Lab : WHU-EIS-LMSWE* date : 2023-11-18* version : v2.0* description : S25FL256SAGNFI00 的读写控制,实现 SDR 时钟模式下的 Quad 读写模式* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.*/
`default_nettype none
module FLASH_top(
input wire clk,
input wire rst_n,output reg FLASH_SCK,
output reg FLASH_nCS,
inout wire [3:0] FLASH_IO,//----------------user interface---------------------
//wr FLASH
input wire WR_req, //Page Programminginput wire [31:0] WR_addr, //起始编程地址,对于S25FL256S,可用地址为0~1FFFFFF(25bit)
input wire [9:0] WR_Byte_Len, //编程字节数,单次只能在一个Page里进行写入(256Byte Programming Buffer Size)
// 最好一次写一个完整的Page(低8位地址为0,Len=256)output wire data_rd_clk, //读wFIFO的时钟
output wire data_rden, //读wFIFO的使能信号
input wire [7:0] data_PP, //从wFIFO读到的数据,将写入FLASH//rd FLASH
input wire RD_req,
input wire [1:0] LC, //LC bits, CR1[7:6]input wire [31:0] RD_addr, //起始读取地址
input wire [31:0] RD_Byte_Len, //读取字节数output wire data_wr_clk, //写rFIFO的clk
output wire data_wren, //写rFIFO的使能信号
output wire [7:0] data_4QOR, //从FLASH读到的数据//WREN/WRDI/CLSR/RESET
input wire WREN_req, //置位WEL bit
input wire WRDI_req, //复位WEL bit
input wire CLSR_req, //清空SR1,只复位P_ERR、E_ERR这两个bit
input wire RESET_req, //软复位//erase
input wire bulk_erase_req, //批量擦除input wire sector_erase_req, //Sector擦除,一次擦除一个标准Sector(64KB)
input wire [31:0] sector_erase_addr, //低16位直接置零即可//RD SR1/CR1/SR2/BAR/ABR
input wire rd_SR1_req, //Status Register 1
output reg [7:0] SR1_rd,input wire rd_CR1_req, //Configuration Register
output reg [7:0] CR1_rd,input wire rd_SR2_req, //Status Register 2
output reg [7:0] SR2_rd,input wire rd_BAR_req, //Bank Address Register
output reg [7:0] BAR_rd,input wire rd_ABR_req, //Autoboot Register
output reg [31:0] ABR_rd,//WR SR1/CR1/BAR/ABR
input wire wr_SR1_req, //发起WR_SR1只需要给入SR1
input wire wr_CR1_req, //发起WR_CR1请求时,要同时给入SR1、CR1两个值
input wire [7:0] SR1_wr,
input wire [7:0] CR1_wr,input wire wr_BAR_req,
input wire [7:0] BAR_wr,input wire wr_ABR_req,
input wire [31:0] ABR_wr,output reg busy,//debug
output reg [3:0] link,
output reg [3:0] FLASH_IO_OBUF,
output wire [3:0] FLASH_IO_IBUF,
output reg [23:0] state
);
//注意,为避免操作冲突,所有req信号请最多同时启用一个(本模块已经做了优先编码)
//所有req高电平有效,请发起req后检测busy,若busy=H,则置低req,避免重复读写
//所有req均应在busy=L时才可发起//---------------------------------COMMAND----------------------------------------
localparam I_WREN = 8'h06; //置位WEL
localparam I_WRDI = 8'h04; //复位WEL
localparam I_CLSR = 8'h30; //复位P_ERR、E_ERR
localparam I_RESET = 8'hF0; //软复位localparam I_WRR = 8'h01; //写SR1、CR1
localparam I_RDSR1 = 8'h05; //读SR1
localparam I_RDSR2 = 8'h07; //读SR2
localparam I_RDCR1 = 8'h35; //读CR1localparam I_RDABR = 8'h14; //读Autoboot Register
localparam I_WRABR = 8'h15; //写ABRlocalparam I_RDBAR = 8'h16; //读Bank Address Register
localparam I_WRBAR = 8'h17; //写BARlocalparam I_BE = 8'h60; //bulk erase
localparam I_SE = 8'hDC; //4SE,Erase 64KB Sector (4-byte address)localparam I_4QPP = 8'h34; //Quad Page Programming (4-byte address)
localparam I_4QOR = 8'h6C; //Quad Output Read (4-byte address)
//4QPP、4QOR的指令码在子模块里写好了,这里只是罗列一下,除此之外的指令码都在本模块内用到//----------------------------------SPI x4----------------------------------------
reg [3:0] link = 4'h0;
reg [3:0] FLASH_IO_OBUF = 4'hf;
wire [3:0] FLASH_IO_IBUF;genvar i;
generatefor(i=0; i<4; i=i+1) beginIOBUF IOBUF_FLASH_IO( //IOBUF由一个IBUF和一个OBUF组成,.O (FLASH_IO_IBUF[i]), //O为IBUF的输出.IO (FLASH_IO[i]), //IO为OBUF的输出、IBUF的输入.I (FLASH_IO_OBUF[i]), //I为OBUF的输入.T (~link[i]) //T为OBUF的三态门使能,低电平有效);end
endgenerateassign FLASH_IO_IBUF1 = FLASH_IO_IBUF;
assign FLASH_IO_IBUF2 = FLASH_IO_IBUF;
assign FLASH_IO_IBUF3 = FLASH_IO_IBUF;
assign FLASH_IO_IBUF4 = FLASH_IO_IBUF;
assign FLASH_IO_IBUF5 = FLASH_IO_IBUF;//********重要**********
//注意,每个FPGA管脚上都要有IBUF、OBUF或IOBUF,input/output管脚上IBUF/OBUF会自动生成,
//而inout管脚需要用户编写,要么用IOBUF,要么直接用 link? xx_OBUF : 1'bz 这种形式(其实后者也是生成了一个OBUF和一个IBUF)
//对于每个FPGA管脚,只能由一个OBUF驱动,因此如果多个子模块要用inout操作同一根线,会出问题
//(这种情况下vivado会自动生成IBUF,导致模块大部分逻辑无效化,进而在综合后整个模块被优化掉,
// 即使强制关闭IBUF/OBUF自动插入功能,也会因为多个OBUF驱动同一管脚而综合失败)
//因此子模块不能再保有inout,而是通过操作顶层模块的IOBUF实现数据读写
//**********************//--------------------------------几个子模块--------------------------------------
//---------------单条8bit指令发送模块---------------
wire FLASH_SCK_1;
wire FLASH_nCS_1;wire [3:0] link1;
wire [3:0] FLASH_IO_OBUF1;
wire [3:0] FLASH_IO_IBUF1;reg start_1;
reg [7:0] instruction_1;
wire busy_1;flash_instruction flash_instruction_inst(.clk (clk),.rst_n (rst_n),.FLASH_SCK (FLASH_SCK_1),.FLASH_nCS (FLASH_nCS_1),.link (link1),.FLASH_IO_OBUF (FLASH_IO_OBUF1),.FLASH_IO_IBUF (FLASH_IO_IBUF1),//usr interface.send_en (start_1),.instruction (instruction_1),.busy (busy_1)
);//-------------写寄存器指令,支持1~4Byte-------------
wire FLASH_SCK_2;
wire FLASH_nCS_2;wire [3:0] link2;
wire [3:0] FLASH_IO_OBUF2;
wire [3:0] FLASH_IO_IBUF2;reg start_2;
reg [7:0] instruction_2;
wire busy_2;reg [3:0] Register_Len_WRR;
reg [7:0] WRR_Byte1, WRR_Byte2, WRR_Byte3, WRR_Byte4;flash_WRR flash_WRR_inst(.clk (clk),.rst_n (rst_n),.FLASH_SCK (FLASH_SCK_2),.FLASH_nCS (FLASH_nCS_2),.link (link2),.FLASH_IO_OBUF (FLASH_IO_OBUF2),.FLASH_IO_IBUF (FLASH_IO_IBUF2),//usr interface.send_en (start_2),.instruction (instruction_2),.Register_Len (Register_Len_WRR),.Byte1 (WRR_Byte1),.Byte2 (WRR_Byte2),.Byte3 (WRR_Byte3),.Byte4 (WRR_Byte4),.busy (busy_2)
);//------------------读寄存器------------------
wire FLASH_SCK_3;
wire FLASH_nCS_3;wire [3:0] link3;
wire [3:0] FLASH_IO_OBUF3;
wire [3:0] FLASH_IO_IBUF3;reg start_3;
reg [7:0] instruction_3;
wire busy_3;reg [3:0] Register_Len_RDR;
wire [31:0] RDR_Reg;flash_RDR flash_RDR_inst(.clk (clk),.rst_n (rst_n),.FLASH_SCK (FLASH_SCK_3),.FLASH_nCS (FLASH_nCS_3),.link (link3),.FLASH_IO_OBUF (FLASH_IO_OBUF3),.FLASH_IO_IBUF (FLASH_IO_IBUF3),//usr interface.read_en (start_3),.instruction (instruction_3),.Register_Len (Register_Len_RDR),.Reg (RDR_Reg),.busy (busy_3)
);//---------------Page Programming---------------
wire FLASH_SCK_4;
wire FLASH_nCS_4;wire [3:0] link4;
wire [3:0] FLASH_IO_OBUF4;
wire [3:0] FLASH_IO_IBUF4;reg start_4;
wire busy_4;reg [31:0] addr_PP;
reg [9:0] Byte_Len_PP;wire data_rd_clk;
wire data_rden;
wire [7:0] data_PP;flash_4QPP flash_4QPP_inst(.clk (clk),.rst_n (rst_n),.FLASH_SCK (FLASH_SCK_4),.FLASH_nCS (FLASH_nCS_4),.link (link4),.FLASH_IO_OBUF (FLASH_IO_OBUF4),.FLASH_IO_IBUF (FLASH_IO_IBUF4),//usr interface.program_start (start_4),.addr (addr_PP),.Byte_Len (Byte_Len_PP),.data_rd_clk (data_rd_clk), //读wFIFO,将数据写入FLASH.data_rden (data_rden),.data (data_PP), //从wFIFO读到的数据.busy (busy_4)
);//-------------------read flash-------------------
wire FLASH_SCK_5;
wire FLASH_nCS_5;wire [3:0] link5;
wire [3:0] FLASH_IO_OBUF5;
wire [3:0] FLASH_IO_IBUF5;reg start_5;
wire busy_5;reg [31:0] addr_4QOR;
reg [31:0] Byte_Len_4QOR;wire data_wr_clk;
wire data_wren;
wire [7:0] data_4QOR;wire [1:0] LC;flash_4QOR flash_4QOR_inst(.clk (clk),.rst_n (rst_n),.FLASH_SCK (FLASH_SCK_5),.FLASH_nCS (FLASH_nCS_5),.link (link5),.FLASH_IO_OBUF (FLASH_IO_OBUF5),.FLASH_IO_IBUF (FLASH_IO_IBUF5),//usr interface.read_start (start_5),.addr (addr_4QOR),.Byte_Len (Byte_Len_4QOR),.data_wr_clk (data_wr_clk), //读FLASH并将数据写入rFIFO.data_wren (data_wren),.data (data_4QOR), //写到rFIFO的数据.busy (busy_5),//LC.LC (LC) //LC bit(CR1[7:6])
);//--------------------------------通道仲裁--------------------------------------
localparam M_NONE = 8'h01;
localparam M_instruction = 8'h02;
localparam M_WRR = 8'h04;
localparam M_RDR = 8'h08;
localparam M_PP = 8'h10;
localparam M_4QOR = 8'h20;reg [7:0] module_arb = M_NONE;
reg submodule_busy;always @(*) begincase(module_arb)M_NONE: beginsubmodule_busy <= 1'b0;FLASH_SCK <= 1'b1;FLASH_nCS <= 1'b1;link <= 4'h0;FLASH_IO_OBUF <= 4'hf;endM_instruction: beginsubmodule_busy <= busy_1;FLASH_SCK <= FLASH_SCK_1;FLASH_nCS <= FLASH_nCS_1;link <= link1;FLASH_IO_OBUF <= FLASH_IO_OBUF1;endM_WRR: beginsubmodule_busy <= busy_2;FLASH_SCK <= FLASH_SCK_2;FLASH_nCS <= FLASH_nCS_2;link <= link2;FLASH_IO_OBUF <= FLASH_IO_OBUF2;endM_RDR: beginsubmodule_busy <= busy_3;FLASH_SCK <= FLASH_SCK_3;FLASH_nCS <= FLASH_nCS_3;link <= link3;FLASH_IO_OBUF <= FLASH_IO_OBUF3;endM_PP: beginsubmodule_busy <= busy_4;FLASH_SCK <= FLASH_SCK_4;FLASH_nCS <= FLASH_nCS_4;link <= link4;FLASH_IO_OBUF <= FLASH_IO_OBUF4;endM_4QOR: beginsubmodule_busy <= busy_5;FLASH_SCK <= FLASH_SCK_5;FLASH_nCS <= FLASH_nCS_5;link <= link5;FLASH_IO_OBUF <= FLASH_IO_OBUF5;enddefault: beginsubmodule_busy <= 1'b0;FLASH_SCK <= 1'b1;FLASH_nCS <= 1'b1;link <= 4'h0;FLASH_IO_OBUF <= 4'hf;endendcase
end//----------------------------------FSM----------------------------------------
localparam S_IDLE = 24'h000001;
localparam S_ARB = 24'h000002; //仲裁对哪一个req进行响应
localparam S_WAIT = 24'h000004; //等待子模块工作完成
localparam S_STOP = 24'h000008;localparam S_WREN = 24'h000010; //执行WREN指令,置位WEL bit
localparam S_WRDI = 24'h000020; //执行WRDI指令,复位WEL bit
localparam S_CLSR = 24'h000040; //执行CLSR,复位P_ERR、E_ERR bit
localparam S_BE = 24'h000080; //Bulk Eraselocalparam S_WRSR1 = 24'h000100; //写Status Register 1
localparam S_WRCR1 = 24'h000200; //写Configurate Register 1
localparam S_WRBAR = 24'h000400; //写Bank Address Register
localparam S_WRABR = 24'h000800; //写Autoboot Register
localparam S_SE = 24'h001000; //Sector Eraselocalparam S_RDSR1 = 24'h002000; //读SR1
localparam S_RDSR2 = 24'h004000; //读SR2
localparam S_RDCR1 = 24'h008000; //读CR1
localparam S_RDBAR = 24'h010000; //读Bank Address Register
localparam S_RDABR = 24'h020000; //读Autoboot Registerlocalparam S_4QPP = 24'h040000; //Page Programming
localparam S_4QOR = 24'h080000; //Quad Output Readlocalparam S_RESET = 24'h100000; //flash software resetreg [23:0] state = S_IDLE;
reg [23:0] next_state;always @(posedge clk or negedge rst_n) beginif(~rst_n) beginstate <= S_IDLE;endelse beginstate <= next_state;end
endwire [16:0] all_req;
reg [16:0] all_req_buf;
assign all_req = {RESET_req, WREN_req, WRDI_req, CLSR_req, bulk_erase_req, sector_erase_req,rd_SR1_req, rd_CR1_req, rd_SR2_req, rd_BAR_req, rd_ABR_req,wr_SR1_req, wr_CR1_req, wr_BAR_req, wr_ABR_req,WR_req, RD_req};always @(posedge clk) beginall_req_buf <= all_req;
endalways @(*) begincase(state)S_IDLE: beginnext_state <= S_ARB;endS_ARB: begincasex(all_req_buf)17'b1_xxxx_xxxx_xxxx_xxxx: next_state <= S_RESET;17'b0_1xxx_xxxx_xxxx_xxxx: next_state <= S_WREN;17'b0_01xx_xxxx_xxxx_xxxx: next_state <= S_WRDI;17'b0_001x_xxxx_xxxx_xxxx: next_state <= S_CLSR;17'b0_0001_xxxx_xxxx_xxxx: next_state <= S_BE;17'b0_0000_1xxx_xxxx_xxxx: next_state <= S_SE;17'b0_0000_01xx_xxxx_xxxx: next_state <= S_RDSR1;17'b0_0000_001x_xxxx_xxxx: next_state <= S_RDCR1;17'b0_0000_0001_xxxx_xxxx: next_state <= S_RDSR2;17'b0_0000_0000_1xxx_xxxx: next_state <= S_RDBAR;17'b0_0000_0000_01xx_xxxx: next_state <= S_RDABR;17'b0_0000_0000_001x_xxxx: next_state <= S_WRSR1;17'b0_0000_0000_0001_xxxx: next_state <= S_WRCR1;17'b0_0000_0000_0000_1xxx: next_state <= S_WRBAR;17'b0_0000_0000_0000_01xx: next_state <= S_WRABR;17'b0_0000_0000_0000_001x: next_state <= S_4QPP;17'b0_0000_0000_0000_0001: next_state <= S_4QOR;default: next_state <= S_ARB;endcaseendS_RESET, S_WREN, S_WRDI, S_CLSR, S_BE, S_SE,S_RDSR1, S_RDCR1, S_RDSR2, S_RDBAR, S_RDABR,S_WRSR1, S_WRCR1, S_WRBAR, S_WRABR,S_4QPP, S_4QOR: beginif(submodule_busy) beginnext_state <= S_WAIT;endelse beginnext_state <= state;endendS_WAIT: beginif(~submodule_busy) beginnext_state <= S_STOP;endelse beginnext_state <= S_WAIT;endendS_STOP: beginnext_state <= S_IDLE;enddefault: beginnext_state <= S_IDLE;endendcase
endreg [3:0] update_register = 4'd0; //在RD REG操作中判断要更新哪一个Reg
//1:SR1, 2:CR1, 3:SR2, 4:BAR, 5:ABRalways @(posedge clk) begincase(state)S_IDLE: beginmodule_arb <= M_NONE;start_1 <= 1'b0;start_2 <= 1'b0;start_3 <= 1'b0;start_4 <= 1'b0;start_5 <= 1'b0;update_register <= 4'd0;endS_ARB: beginmodule_arb <= M_NONE;start_1 <= 1'b0;start_2 <= 1'b0;start_3 <= 1'b0;start_4 <= 1'b0;start_5 <= 1'b0;endS_RESET: beginmodule_arb <= M_instruction;start_1 <= 1'b1;instruction_1 <= I_RESET;endS_WREN: beginmodule_arb <= M_instruction;start_1 <= 1'b1;instruction_1 <= I_WREN;endS_WRDI: beginmodule_arb <= M_instruction;start_1 <= 1'b1;instruction_1 <= I_WRDI;endS_CLSR: beginmodule_arb <= M_instruction;start_1 <= 1'b1;instruction_1 <= I_CLSR;endS_BE: beginmodule_arb <= M_instruction;start_1 <= 1'b1;instruction_1 <= I_BE;endS_SE: beginmodule_arb <= M_WRR;start_2 <= 1'b1;instruction_2 <= I_SE;Register_Len_WRR <= 4'd4;WRR_Byte1 <= sector_erase_addr[31:24];WRR_Byte2 <= sector_erase_addr[23:16];WRR_Byte3 <= sector_erase_addr[15:8];WRR_Byte4 <= sector_erase_addr[7:0];endS_RDSR1: beginmodule_arb <= M_RDR;start_3 <= 1'b1;instruction_3 <= I_RDSR1;Register_Len_RDR <= 4'd1;update_register <= 4'd1;endS_RDCR1: beginmodule_arb <= M_RDR;start_3 <= 1'b1;instruction_3 <= I_RDCR1;Register_Len_RDR <= 4'd1;update_register <= 4'd2;endS_RDSR2: beginmodule_arb <= M_RDR;start_3 <= 1'b1;instruction_3 <= I_RDSR2;Register_Len_RDR <= 4'd1;update_register <= 4'd3;endS_RDBAR: beginmodule_arb <= M_RDR;start_3 <= 1'b1;instruction_3 <= I_RDBAR;Register_Len_RDR <= 4'd1;update_register <= 4'd4;endS_RDABR: beginmodule_arb <= M_RDR;start_3 <= 1'b1;instruction_3 <= I_RDABR;Register_Len_RDR <= 4'd4;update_register <= 4'd5;endS_WRSR1: beginmodule_arb <= M_WRR;start_2 <= 1'b1;instruction_2 <= I_WRR;Register_Len_WRR <= 4'd1;WRR_Byte1 <= SR1_wr;WRR_Byte2 <= 8'd0;WRR_Byte3 <= 8'd0;WRR_Byte4 <= 8'd0;endS_WRCR1: beginmodule_arb <= M_WRR;start_2 <= 1'b1;instruction_2 <= I_WRR;Register_Len_WRR <= 4'd2;WRR_Byte1 <= SR1_wr;WRR_Byte2 <= CR1_wr;WRR_Byte3 <= 8'd0;WRR_Byte4 <= 8'd0;endS_WRBAR: beginmodule_arb <= M_WRR;start_2 <= 1'b1;instruction_2 <= I_WRBAR;Register_Len_WRR <= 4'd1;WRR_Byte1 <= BAR_wr;WRR_Byte2 <= 8'd0;WRR_Byte3 <= 8'd0;WRR_Byte4 <= 8'd0;endS_WRABR: beginmodule_arb <= M_WRR;start_2 <= 1'b1;instruction_2 <= I_WRABR;Register_Len_WRR <= 4'd4;WRR_Byte1 <= ABR_wr[31:24];WRR_Byte2 <= ABR_wr[23:16];WRR_Byte3 <= ABR_wr[15:8];WRR_Byte4 <= ABR_wr[7:0];endS_4QPP: beginmodule_arb <= M_PP;start_4 <= 1'b1;addr_PP <= WR_addr;Byte_Len_PP <= WR_Byte_Len;endS_4QOR: beginmodule_arb <= M_4QOR;start_5 <= 1'b1;addr_4QOR <= RD_addr;Byte_Len_4QOR <= RD_Byte_Len;endS_WAIT: beginstart_1 <= 1'b0;start_2 <= 1'b0;start_3 <= 1'b0;start_4 <= 1'b0;start_5 <= 1'b0;endS_STOP: beginmodule_arb <= M_NONE;case(update_register)4'd1: SR1_rd <= RDR_Reg[7:0];4'd2: CR1_rd <= RDR_Reg[7:0];4'd3: SR2_rd <= RDR_Reg[7:0];4'd4: BAR_rd <= RDR_Reg[7:0];4'd5: ABR_rd <= RDR_Reg;default: ;endcaseenddefault: beginmodule_arb <= M_NONE;start_1 <= 1'b0;start_2 <= 1'b0;start_3 <= 1'b0;start_4 <= 1'b0;start_5 <= 1'b0;endendcase
endalways @(*) begincase(state)S_IDLE, S_ARB: beginbusy <= 1'b0;enddefault: beginbusy <= 1'b1;endendcase
endendmodule
测试
编写测试代码如下,并下载到板子进行测试(注意,我的板子上的 FLASH 的 QUAD bit(CR1[1])已经被置位了,所以这里只执行了擦除、写入、读取流程,如果你的不是,需要多加一个 WRR 步骤)
// FLASH 测试(主存读写测试)
`default_nettype none
module test_flash_mainMemory(
input wire clk_sys, //OXCO_10Moutput wire FLASH_nCS,
inout wire [3:0] FLASH_IO,input wire [3:0] Key,
output wire [3:0] LED
);wire clk_100M;
wire clk_flash;
wire clk_1k;
wire clk_1Hz;reg rst_n = 1'b1;clk_wiz_0 clk_wiz(.clk_in1 (clk_sys),.clk_out1 (clk_100M), .reset (1'b0), .locked ()
);clkdiv #(.N(3))
clkdiv_flash(.clk_in (clk_100M),.clk_out (clk_flash) //测试发现50M下寄存器写操作可能出现错误,因此降为33M
);clkdiv #(.N(1000_00))
clkdiv_1k(.clk_in (clk_100M),.clk_out (clk_1k)
);clkdiv #(.N(100_000_000))
clkdiv_1Hz(.clk_in (clk_100M),.clk_out (clk_1Hz)
);wire usrdone;
set_CCLK set_CCLK_inst(.usrcclk (FLASH_SCK),.usrdone (usrdone),.cfgclk (),.cfgmclk (),.eos ()
);assign usrdone = clk_1Hz;//-------------------------------------FLASH------------------------------------------------------
wire FLASH_SCK;
wire FLASH_nCS;
wire [3:0] FLASH_IO;//wr FLASH
reg WR_req = 1'b0; //Page Programmingreg [31:0] WR_addr = 32'd0; //起始编程地址,对于S25FL256S,可用地址为0~1FFFFFF(25bit)
reg [9:0] WR_Byte_Len = 10'd1; //编程字节数wire data_rd_clk; //读wFIFO的时钟
wire data_rden; //读wFIFO的使能信号
reg [7:0] data_PP = 8'd0; //从wFIFO读到的数据,将写入FLASH//rd FLASH
reg RD_req = 1'b0;
reg [1:0] LC = 2'b00; //LC bits, CR1[7:6]reg [31:0] RD_addr = 32'd0; //起始读取地址
reg [31:0] RD_Byte_Len = 32'd1; //读取字节数wire data_wr_clk; //写rFIFO的clk
wire data_wren; //写rFIFO的使能信号
wire [7:0] data_4QOR; //从FLASH读到的数据//WREN/WRDI/CLSR/RESET
reg WREN_req = 1'b0; //置位WEL bit
reg WRDI_req = 1'b0; //复位WEL bit
reg CLSR_req = 1'b0; //清空SR1,只复位P_ERR、E_ERR这两个bit
reg RESET_req = 1'b0; //软复位//erase
reg bulk_erase_req = 1'b0; //批量擦除reg sector_erase_req = 1'b0; //Sector擦除,一次擦除一个标准Sector(64KB)
reg [31:0] sector_erase_addr = 32'd0; //低16位直接置零即可//RD SR1/CR1/SR2/BAR/ABR
reg rd_SR1_req = 1'b0; //Status Register 1
wire [7:0] SR1_rd;reg rd_CR1_req = 1'b0; //Configuration Register
wire [7:0] CR1_rd;reg rd_SR2_req = 1'b0; //Status Register 2
wire [7:0] SR2_rd;reg rd_BAR_req = 1'b0; //Bank Address Register
wire [7:0] BAR_rd;reg rd_ABR_req = 1'b0; //Autoboot Register
wire [31:0] ABR_rd;//WR SR1/CR1/BAR/ABR
reg wr_SR1_req = 1'b0; //发起WR_SR1只需要给入SR1
reg wr_CR1_req = 1'b0; //发起WR_CR1请求时,要同时给入SR1、CR1两个值
reg [7:0] SR1_wr = 8'd0;
reg [7:0] CR1_wr;reg wr_BAR_req = 1'b0;
reg [7:0] BAR_wr;reg wr_ABR_req = 1'b0;
reg [31:0] ABR_wr;wire busy;FLASH_top FLASH_top_inst(.clk (clk_flash),.rst_n (rst_n),.FLASH_SCK (FLASH_SCK),.FLASH_nCS (FLASH_nCS),.FLASH_IO (FLASH_IO),//----------------user interface---------------------//wr FLASH.WR_req (WR_req),.WR_addr (WR_addr),.WR_Byte_Len (WR_Byte_Len),.data_rd_clk (data_rd_clk),.data_rden (data_rden),.data_PP (data_PP),//rd FLASH.RD_req (RD_req),.LC (LC),.RD_addr (RD_addr),.RD_Byte_Len (RD_Byte_Len),.data_wr_clk (data_wr_clk),.data_wren (data_wren),.data_4QOR (data_4QOR),//WREN/WRDI/CLSR/RESET.WREN_req (WREN_req),.WRDI_req (WRDI_req),.CLSR_req (CLSR_req),.RESET_req (RESET_req),//erase.bulk_erase_req (bulk_erase_req),.sector_erase_req (sector_erase_req),.sector_erase_addr (sector_erase_addr),//RD SR1/CR1/SR2/BAR/ABR.rd_SR1_req (rd_SR1_req),.SR1_rd (SR1_rd),.rd_CR1_req (rd_CR1_req),.CR1_rd (CR1_rd),.rd_SR2_req (rd_SR2_req),.SR2_rd (SR2_rd),.rd_BAR_req (rd_BAR_req),.BAR_rd (BAR_rd),.rd_ABR_req (rd_ABR_req),.ABR_rd (ABR_rd),//WR SR1/CR1/BAR/ABR.wr_SR1_req (wr_SR1_req),.wr_CR1_req (wr_CR1_req),.SR1_wr (SR1_wr),.CR1_wr (CR1_wr),.wr_BAR_req (wr_BAR_req),.BAR_wr (BAR_wr),.wr_ABR_req (wr_ABR_req),.ABR_wr (ABR_wr),.busy (busy),//debug.link (link),.FLASH_IO_OBUF (FLASH_IO_OBUF),.FLASH_IO_IBUF (FLASH_IO_IBUF),.state (state)
);//debug
wire [3:0] link;
wire [3:0] FLASH_IO_OBUF;
wire [3:0] FLASH_IO_IBUF;
wire [23:0] state;//-----------------------------test------------------------------------
wire PPS_pe;
reg PPS_d0;
reg PPS_d1;reg PPS_pe_d1;
reg PPS_pe_d2;assign PPS_pe = PPS_d0 & (~PPS_d1);reg [7:0] cnt = 8'd0;always @(posedge clk_flash) beginPPS_d0 <= clk_1k;PPS_d1 <= PPS_d0;if(PPS_pe) beginif(cnt==1 || cnt==11) beginif(SR1_rd[1]) begin //检查WELcnt <= cnt + 1'b1;endelse begincnt <= cnt;endendelse if(cnt==3 || cnt==13) beginif(~SR1_rd[0]) begin //检查WIPcnt <= cnt + 1'b1;endelse begincnt <= cnt;endendelse begincnt <= cnt + 1'b1;endendPPS_pe_d1 <= PPS_pe;PPS_pe_d2 <= PPS_pe_d1;
endlocalparam WR_RD_ADDR = 32'h0100_0000;reg [7:0] data_PP_tmp = 8'd0;
always @(posedge data_rd_clk) beginif(data_rden) begindata_PP_tmp <= data_PP_tmp + 1'b1;endelse begindata_PP_tmp <= data_PP_tmp;end
endalways @(posedge clk_100M) begincase(cnt)//---------------erase-------------------------8'd0: WREN_req <= PPS_pe_d2;8'd1: rd_SR1_req <= PPS_pe_d2;8'd2: beginsector_erase_req <= PPS_pe_d2;sector_erase_addr <= WR_RD_ADDR;end8'd3: rd_SR1_req <= PPS_pe_d2;8'd4: rd_CR1_req <= PPS_pe_d2;//------------wr main mem----------------------8'd10: WREN_req <= PPS_pe_d2;8'd11: rd_SR1_req <= PPS_pe_d2;8'd12: beginWR_req <= PPS_pe_d2;WR_addr <= WR_RD_ADDR;WR_Byte_Len <= 10'd16;data_PP <= data_PP_tmp;end8'd13: rd_SR1_req <= PPS_pe_d2;//--------------get LC--------------------------8'd20: rd_CR1_req <= PPS_pe_d2;8'd21: LC <= CR1_rd[7:6];//------------rd main mem----------------------8'd30: beginRD_req <= PPS_pe_d2;RD_addr <= WR_RD_ADDR;RD_Byte_Len <= 10'd16;enddefault: ;endcase
end//-----------------------------ILA------------------------------------
ila_test ila(.clk (clk_100M),.probe0 (cnt),.probe1 (busy),.probe2 (FLASH_SCK),.probe3 (FLASH_nCS),.probe4 (link),.probe5 (FLASH_IO_IBUF),.probe6 (SR1_rd),.probe7 (CR1_rd),.probe8 (data_rd_clk),.probe9 (data_rden),.probe10 (data_PP),.probe11 (data_wr_clk),.probe12 (data_wren),.probe13 (data_4QOR)
);endmodule
用户控制 CCLK 主要用到 STARTUPE2 原语,我这里封装为了一个代码模块,具体可看这篇博文
/* * file : set_CCLK.v* author : 今朝无言* Lab : WHU-EIS-LMSWE* date : 2023-11-02* version : v1.0* description : 使用原语设置CCLK* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.*/
`default_nettype none
module set_CCLK(
input wire usrcclk,
input wire usrdone,output wire cfgclk,
output wire cfgmclk,
output wire eos
);//-------------------STARTUPE2---------------------
STARTUPE2 #(.PROG_USR ("FALSE"),.SIM_CCLK_FREQ (0.0)
)
STARTUPE2_inst(.CFGCLK (cfgclk),.CFGMCLK (cfgmclk),.EOS (eos),.PREQ (),.CLK (0),.GSR (0),.GTS (0),.KEYCLEARB (1),.PACK (1),.USRCCLKO (usrcclk),.USRCCLKTS (0),.USRDONEO (usrdone),.USRDONETS (0)
);endmodule
在该测试代码中,循环向 FLASH 写入自增 1 的数据,然后观察从 FLASH 读取到的数据,如下
可以看到读取到正确的数据。
Something
在测试 FLASH 读写中踩到了好多坑,主要是写入/擦除操作方面的(写寄存器、写主存、擦除等),记录如下:
-
WREN 操作后,WEL bit 不是立即置位的,如果执行 WREN 后立即执行写寄存器、擦除、写主存等操作,都会失败(这些操作都需要写使能位 WEL 为高才能执行)。精细测量发现在执行 WREN 后约 800us ,WEL 才被置位,且这个时间不是很固定,因此强烈建议在执行 WREN 后,周期检查 WEL bit,待 WEL=1 后再执行擦除、写入操作。
-
WRR 命令执行后,若只存在把某位(某些位)从 0 置 1 的操作,则执行非常快(小于 1ms);而如果存在把某些位从 1 置 0 的操作时,设备会陷入长时间的忙碌状态(WIP=1),测试表明约 383ms。若在 WIP=1 的状态执行新的写入、擦除操作时,这些指令都会被忽略。因此在执行 WRR 后也需要检查 WIP,待 WIP=0 后才能退回空闲状态。即写寄存器应当遵循 ‘WREN -> check WEL -> WR Reg -> check WIP -> return IDLE’ 的流程。
-
Erase、Page Program 等操作执行后时间也很长,也应当遵循 ‘WREN -> check WEL -> Erase/PP -> check WIP -> return IDLE’ 的流程。
(完)