文章目录
实现思路
建议读者先对 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 决定驱动方向。
子模块实现
- 单条指令
verilog
/*
* 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 3
reg [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) begin
send_en_d0 <= send_en;
send_en_d1 <= send_en_d0;
end
assign 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) begin
if(~rst_n) begin
state <= S_IDLE;
end
else begin
state <= next_state;
end
end
always @(*) begin
case(state)
S_IDLE: begin
if(send_en_pe) begin
next_state <= S_COMMAND;
end
else begin
next_state <= S_IDLE;
end
end
S_COMMAND: begin
if(cnt >= 3'd7) begin
next_state <= S_STOP;
end
else begin
next_state <= S_COMMAND;
end
end
S_STOP: begin
next_state <= S_IDLE;
end
default: begin
next_state <= S_IDLE;
end
endcase
end
//FLASH_nCS
always @(negedge clk) begin
case(state)
S_COMMAND: begin
FLASH_nCS <= 1'b0;
end
default: begin
FLASH_nCS <= 1'b1;
end
endcase
end
//cnt
always @(posedge clk) begin
case(state)
S_IDLE: begin
cnt <= 3'd0;
end
S_COMMAND: begin
if(~FLASH_nCS) begin
cnt <= cnt + 1'b1;
end
else begin
cnt <= 3'd0;
end
end
S_STOP: begin
cnt <= 3'd0;
end
default: begin
cnt <= cnt;
end
endcase
end
//FLASH_IO_OBUF
always @(negedge clk) begin //在SCK下降沿转换数据
case(state)
S_COMMAND: begin
FLASH_IO_OBUF[0] <= instruction[3'd7-cnt]; //首先移出MSB
FLASH_IO_OBUF[3:1] <= 3'b111;
end
default: begin
FLASH_IO_OBUF <= 4'hf;
end
endcase
end
//link
always @(negedge clk) begin
case(state)
S_COMMAND: begin
link <= 4'b1101;
//指令阶段,SO应维持高阻,WP#、HOLD#应拉高;
//而WP#、HOLD#内部有上拉电阻,因此IO1~IO3可以直接释放掉
//不过为保险起见,这里还是强制拉高IO2/IO3,而IO1可以释放掉
end
default: begin
link <= 4'h0;
end
endcase
end
//busy
always @(*) begin
case(state)
S_IDLE: begin
busy <= 1'b0;
end
S_COMMAND, S_STOP: begin
busy <= 1'b1;
end
default: begin
busy <= 1'b0;
end
endcase
end
endmodule
- 读寄存器
verilog
/*
* 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 3
reg [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) begin
read_en_d0 <= read_en;
read_en_d1 <= read_en_d0;
end
assign 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) begin
if(~rst_n) begin
state <= S_IDLE;
end
else begin
state <= next_state;
end
end
always @(*) begin
case(state)
S_IDLE: begin
if(read_en_pe) begin
next_state <= S_COMMAND;
end
else begin
next_state <= S_IDLE;
end
end
S_COMMAND: begin
if(cnt >= 3'd7) begin
next_state <= S_RDR;
end
else begin
next_state <= S_COMMAND;
end
end
S_RDR: begin
if(cnt >= 3'd7 && cnt_Byte >= Register_Len - 1'b1) begin
next_state <= S_STOP;
end
else begin
next_state <= S_RDR;
end
end
S_STOP: begin
next_state <= S_IDLE;
end
default: begin
next_state <= S_IDLE;
end
endcase
end
//FLASH_nCS
always @(negedge clk) begin
case(state)
S_COMMAND, S_RDR: begin
FLASH_nCS <= 1'b0;
end
default: begin
FLASH_nCS <= 1'b1;
end
endcase
end
//cnt
always @(posedge clk) begin
case(state)
S_IDLE: begin
cnt <= 3'd0;
end
S_COMMAND, S_RDR: begin //将cnt设计为3bit位宽,可实现模8加
if(~FLASH_nCS) begin
cnt <= cnt + 1'b1;
end
else begin
cnt <= 3'd0;
end
end
S_STOP: begin
cnt <= 3'd0;
end
default: begin
cnt <= cnt;
end
endcase
end
//cnt_Byte
always @(posedge clk) begin
case(state)
S_RDR: begin
if(cnt==3'd7) begin
cnt_Byte <= cnt_Byte + 1'b1;
end
else begin
cnt_Byte <= cnt_Byte;
end
end
default: begin
cnt_Byte <= 4'd0;
end
endcase
end
//FLASH_IO_OBUF
always @(negedge clk) begin //在SCK下降沿转换数据
case(state)
S_COMMAND: begin
FLASH_IO_OBUF[0] <= instruction[3'd7-cnt]; //首先移出MSB
FLASH_IO_OBUF[3:1] <= 3'b111;
end
default: begin
FLASH_IO_OBUF <= 4'hf;
end
endcase
end
//link
always @(negedge clk) begin
case(state)
S_COMMAND: begin
link <= 4'b1101;
end
S_RDR: begin
link <= 4'h0;
end
default: begin
link <= 4'h0;
end
endcase
end
//read reg
wire SO = FLASH_IO_IBUF[1];
always @(posedge clk or negedge rst_n) begin //须在SCK上升沿锁存数据
if(~rst_n) begin
Reg <= 32'd0;
end
else begin
case(state)
S_RDR: begin
Reg <= {Reg[30:0], SO}; //移位寄存来自SO的值
end
default: begin
Reg <= Reg;
end
endcase
end
end
//busy
always @(*) begin
case(state)
S_IDLE: begin
busy <= 1'b0;
end
default: begin
busy <= 1'b1;
end
endcase
end
endmodule
- 写寄存器
verilog
/*
* 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 3
reg [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) begin
send_en_d0 <= send_en;
send_en_d1 <= send_en_d0;
end
assign 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) begin
if(~rst_n) begin
state <= S_IDLE;
end
else begin
state <= next_state;
end
end
always @(*) begin
case(state)
S_IDLE: begin
if(send_en_pe) begin
next_state <= S_COMMAND;
end
else begin
next_state <= S_IDLE;
end
end
S_COMMAND: begin
if(cnt >= 3'd7) begin
next_state <= S_WRR;
end
else begin
next_state <= S_COMMAND;
end
end
S_WRR: begin
if(cnt >= 3'd7 && cnt_Byte >= Register_Len - 1'b1) begin
next_state <= S_STOP;
end
else begin
next_state <= S_WRR;
end
end
S_STOP: begin
next_state <= S_IDLE;
end
default: begin
next_state <= S_IDLE;
end
endcase
end
//FLASH_nCS
always @(negedge clk) begin
case(state)
S_COMMAND, S_WRR: begin
FLASH_nCS <= 1'b0;
end
default: begin
FLASH_nCS <= 1'b1;
end
endcase
end
//cnt
always @(posedge clk) begin
case(state)
S_IDLE: begin
cnt <= 3'd0;
end
S_COMMAND, S_WRR: begin //将cnt设计为3bit位宽,可实现模8加
if(~FLASH_nCS) begin
cnt <= cnt + 1'b1;
end
else begin
cnt <= 3'd0;
end
end
S_STOP: begin
cnt <= 3'd0;
end
default: begin
cnt <= cnt;
end
endcase
end
//cnt_Byte
always @(posedge clk) begin
case(state)
S_WRR: begin
if(cnt==3'd7) begin
cnt_Byte <= cnt_Byte + 1'b1;
end
else begin
cnt_Byte <= cnt_Byte;
end
end
default: begin
cnt_Byte <= 4'd0;
end
endcase
end
//FLASH_IO_OBUF
always @(negedge clk) begin //在SCK下降沿转换数据
case(state)
S_COMMAND: begin
FLASH_IO_OBUF[0] <= instruction[3'd7-cnt]; //首先移出MSB
FLASH_IO_OBUF[3:1] <= 3'b111;
end
S_WRR: begin
case(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;
endcase
FLASH_IO_OBUF[3:1] <= 3'b111;
end
default: begin
FLASH_IO_OBUF <= 4'hf;
end
endcase
end
//link
always @(negedge clk) begin
case(state)
S_COMMAND, S_WRR: begin
link <= 4'b1101;
end
default: begin
link <= 4'h0;
end
endcase
end
//busy
always @(*) begin
case(state)
S_IDLE: begin
busy <= 1'b0;
end
default: begin
busy <= 1'b1;
end
endcase
end
endmodule
- Page Programming
verilog
/*
* 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 内进行写入,超出的将被忽略,建议一次写一整个 Page
output 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的指令码为 0x34
reg FLASH_nCS = 1'b1;
assign FLASH_SCK = FLASH_nCS? 1'b1 : clk; //SPI mode 3
reg [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) begin
program_start_d0 <= program_start;
program_start_d1 <= program_start_d0;
end
assign 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) begin
if(~rst_n) begin
state <= S_IDLE;
end
else begin
state <= next_state;
end
end
always @(*) begin
case(state)
S_IDLE: begin
if(program_start_pe) begin
next_state <= S_COMMAND;
end
else begin
next_state <= S_IDLE;
end
end
S_COMMAND: begin
if(cnt >= 3'd7) begin
next_state <= S_ADDR;
end
else begin
next_state <= S_COMMAND;
end
end
S_ADDR: begin
if(cnt >= 3'd7 && cnt_Byte >= 4'd3) begin
next_state <= S_QUAD_WR;
end
else begin
next_state <= S_ADDR;
end
end
S_QUAD_WR: begin
if(cnt >= 3'd4 && (Byte_Len == 10'd0 || cnt_Byte >= Byte_Len - 1'b1)) begin //Len=0时视作Len=1
next_state <= S_STOP;
end
else begin
next_state <= S_QUAD_WR;
end
end
S_STOP: begin
next_state <= S_IDLE;
end
default: begin
next_state <= S_IDLE;
end
endcase
end
//FLASH_nCS
always @(negedge clk) begin
case(state)
S_COMMAND, S_ADDR, S_QUAD_WR: begin
FLASH_nCS <= 1'b0;
end
default: begin
FLASH_nCS <= 1'b1;
end
endcase
end
//cnt
always @(posedge clk) begin
case(state)
S_IDLE: begin
cnt <= 3'd0;
end
S_COMMAND, S_ADDR: begin
if(~FLASH_nCS) begin
cnt <= cnt + 1'b1;
end
else begin
cnt <= 3'd0;
end
end
S_QUAD_WR: begin
if(~FLASH_nCS) begin
cnt <= cnt + 3'd4; //Quad WR 阶段一次传送4bit
end
else begin
cnt <= 3'd0;
end
end
S_STOP: begin
cnt <= 3'd0;
end
default: begin
cnt <= cnt;
end
endcase
end
//cnt_Byte
always @(posedge clk) begin
case(state)
S_ADDR: begin
if(cnt==3'd7) begin
if(cnt_Byte >= 16'd3) begin
cnt_Byte <= 10'd0;
end
else begin
cnt_Byte <= cnt_Byte + 1'b1;
end
end
else begin
cnt_Byte <= cnt_Byte;
end
end
S_QUAD_WR: begin
if(cnt==3'd4) begin
cnt_Byte <= cnt_Byte + 1'b1;
end
else begin
cnt_Byte <= cnt_Byte;
end
end
default: begin
cnt_Byte <= 10'd0;
end
endcase
end
//link
always @(negedge clk) begin
case(state)
S_COMMAND, S_ADDR: begin
link <= 4'b1101;
end
S_QUAD_WR: begin
link <= 4'b1111;
end
default: begin
link <= 4'h0;
end
endcase
end
//FLASH_IO_OBUF
always @(negedge clk) begin //在SCK下降沿转换数据
case(state)
S_COMMAND: begin
FLASH_IO_OBUF[0] <= instruction[3'd7-cnt]; //首先移出MSB
FLASH_IO_OBUF[3:1] <= 3'b111;
end
S_ADDR: begin
case(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;
endcase
FLASH_IO_OBUF[3:1] <= 3'b111;
end
S_QUAD_WR: begin
case(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;
endcase
end
default: begin
FLASH_IO_OBUF <= 4'hf;
end
endcase
end
//data_rden
always @(posedge clk) begin
case(state)
S_QUAD_WR: begin
data_rden <= 1'b1;
end
default: begin
data_rden <= 1'b0;
end
endcase
end
//busy
always @(*) begin
case(state)
S_IDLE: begin
busy <= 1'b0;
end
default: begin
busy <= 1'b1;
end
endcase
end
endmodule
- 读 FLASH 主存储器
verilog
/*
* 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=8
localparam instruction = 8'h6C; //4QOR的指令码为 0x6C
reg FLASH_nCS = 1'b1;
assign FLASH_SCK = FLASH_nCS? 1'b1 : clk; //SPI mode 3
reg [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) begin
read_start_d0 <= read_start;
read_start_d1 <= read_start_d0;
end
assign 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) begin
if(~rst_n) begin
state <= S_IDLE;
end
else begin
state <= next_state;
end
end
always @(*) begin
case(state)
S_IDLE: begin
if(read_start_pe) begin
next_state <= S_COMMAND;
end
else begin
next_state <= S_IDLE;
end
end
S_COMMAND: begin
if(cnt >= 3'd7) begin
next_state <= S_ADDR;
end
else begin
next_state <= S_COMMAND;
end
end
S_ADDR: begin
if(cnt >= 3'd7 && cnt_Byte >= 4'd3) begin
case(LC) //根据LC判断Dummy的长度
2'b11: begin
next_state <= S_QUAD_RD;
end
2'b00, 2'b01, 2'b10: begin
next_state <= S_DUMMY;
end
default: ;
endcase
end
else begin
next_state <= S_ADDR;
end
end
S_DUMMY: begin
if(cnt >= 3'd7) begin
next_state <= S_QUAD_RD;
end
else begin
next_state <= S_DUMMY;
end
end
S_QUAD_RD: begin
if(cnt >= 3'd4 && (Byte_Len == 32'd0 || cnt_Byte >= Byte_Len - 1'b1)) begin //Len=0时视作Len=1
next_state <= S_STOP;
end
else begin
next_state <= S_QUAD_RD;
end
end
S_STOP: begin
if(cnt>=1) begin //维持在STOP两个clk,以保持data和wren保持一个wr_clk
next_state <= S_IDLE;
end
else begin
next_state <= S_STOP;
end
end
default: begin
next_state <= S_IDLE;
end
endcase
end
//FLASH_nCS
always @(negedge clk) begin
case(state)
S_COMMAND, S_ADDR, S_DUMMY, S_QUAD_RD: begin
FLASH_nCS <= 1'b0;
end
default: begin
FLASH_nCS <= 1'b1;
end
endcase
end
//cnt
always @(posedge clk) begin
case(state)
S_IDLE: begin
cnt <= 3'd0;
end
S_COMMAND, S_ADDR: begin
if(~FLASH_nCS) begin
cnt <= cnt + 1'b1;
end
else begin
cnt <= 3'd0;
end
end
S_DUMMY: begin
if(cnt >= 3'd7) begin //这里设置Bummy长度;由于4QOR只有0/8的Dummy长度,因该case实际可以和上面合并
cnt <= 3'd0;
end
else begin
cnt <= cnt + 1'b1;
end
end
S_QUAD_RD: begin
cnt <= cnt + 3'd4; //Quad RD 阶段一次读回4bit
end
S_STOP: begin
cnt <= 3'd1;
end
default: begin
cnt <= cnt;
end
endcase
end
//cnt_Byte
always @(posedge clk) begin
case(state)
S_ADDR: begin
if(cnt==3'd7) begin
if(cnt_Byte >= 32'd3) begin
cnt_Byte <= 32'd0;
end
else begin
cnt_Byte <= cnt_Byte + 1'b1;
end
end
else begin
cnt_Byte <= cnt_Byte;
end
end
S_DUMMY: begin
cnt_Byte <= 32'd0;
end
S_QUAD_RD: begin
if(cnt==3'd4) begin
cnt_Byte <= cnt_Byte + 1'b1;
end
else begin
cnt_Byte <= cnt_Byte;
end
end
default: begin
cnt_Byte <= 32'd0;
end
endcase
end
//link
always @(negedge clk) begin
case(state)
S_COMMAND, S_ADDR: begin
link <= 4'b1101;
end
S_DUMMY, S_QUAD_RD: begin //为防止主控端与flash端的驱动器冲突,Dummy期间主控端应释放总线
link <= 4'b0000;
end
default: begin
link <= 4'h0;
end
endcase
end
//FLASH_IO_OBUF
always @(negedge clk) begin //在SCK下降沿转换数据
case(state)
S_COMMAND: begin
FLASH_IO_OBUF[0] <= instruction[3'd7-cnt]; //首先移出MSB
FLASH_IO_OBUF[3:1] <= 3'b111;
end
S_ADDR: begin
case(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;
endcase
FLASH_IO_OBUF[3:1] <= 3'b111;
end
default: begin
FLASH_IO_OBUF <= 4'hf;
end
endcase
end
//data_tmp
reg [7:0] data_tmp;
always @(posedge clk) begin //须在SCK上升沿锁存数据
case(state)
S_QUAD_RD: begin
case(cnt)
3'd0: begin
data_tmp[7:4] <= FLASH_IO_IBUF;
end
3'd4: begin
data_tmp[3:0] <= FLASH_IO_IBUF;
end
default: begin
data_tmp <= data_tmp;
end
endcase
end
default: begin
data_tmp <= data_tmp;
end
endcase
end
//data_wren & data
reg data_wren_buf;
reg [7:0] data_buf;
always @(posedge clk) begin
case(state)
S_QUAD_RD: begin
if(cnt==0 && cnt_Byte>=1) begin
data_wren_buf <= 1'b1;
data_buf <= data_tmp;
end
else begin
data_wren_buf <= data_wren_buf;
data_buf <= data_buf;
end
end
S_STOP: begin //S_STOP时锁存输出最后一个数据
if(cnt==0) begin
data_wren_buf <= 1'b1;
data_buf <= data_tmp;
end
else begin
data_wren_buf <= data_wren_buf;
data_buf <= data_buf;
end
end
default: begin
data_wren_buf <= 1'b0;
data_buf <= 8'd0;
end
endcase
end
always @(posedge data_wr_clk) begin //同步到data_wr_clk时钟域
data_wren <= data_wren_buf;
data <= data_buf;
end
//busy
always @(*) begin
case(state)
S_IDLE: begin
busy <= 1'b0;
end
default: begin
busy <= 1'b1;
end
endcase
end
endmodule
top模块
- FLASH_top.v
verilog
/*
* 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 Programming
input 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; //读CR1
localparam I_RDABR = 8'h14; //读Autoboot Register
localparam I_WRABR = 8'h15; //写ABR
localparam I_RDBAR = 8'h16; //读Bank Address Register
localparam I_WRBAR = 8'h17; //写BAR
localparam 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;
generate
for(i=0; i<4; i=i+1) begin
IOBUF 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
endgenerate
assign 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 @(*) begin
case(module_arb)
M_NONE: begin
submodule_busy <= 1'b0;
FLASH_SCK <= 1'b1;
FLASH_nCS <= 1'b1;
link <= 4'h0;
FLASH_IO_OBUF <= 4'hf;
end
M_instruction: begin
submodule_busy <= busy_1;
FLASH_SCK <= FLASH_SCK_1;
FLASH_nCS <= FLASH_nCS_1;
link <= link1;
FLASH_IO_OBUF <= FLASH_IO_OBUF1;
end
M_WRR: begin
submodule_busy <= busy_2;
FLASH_SCK <= FLASH_SCK_2;
FLASH_nCS <= FLASH_nCS_2;
link <= link2;
FLASH_IO_OBUF <= FLASH_IO_OBUF2;
end
M_RDR: begin
submodule_busy <= busy_3;
FLASH_SCK <= FLASH_SCK_3;
FLASH_nCS <= FLASH_nCS_3;
link <= link3;
FLASH_IO_OBUF <= FLASH_IO_OBUF3;
end
M_PP: begin
submodule_busy <= busy_4;
FLASH_SCK <= FLASH_SCK_4;
FLASH_nCS <= FLASH_nCS_4;
link <= link4;
FLASH_IO_OBUF <= FLASH_IO_OBUF4;
end
M_4QOR: begin
submodule_busy <= busy_5;
FLASH_SCK <= FLASH_SCK_5;
FLASH_nCS <= FLASH_nCS_5;
link <= link5;
FLASH_IO_OBUF <= FLASH_IO_OBUF5;
end
default: begin
submodule_busy <= 1'b0;
FLASH_SCK <= 1'b1;
FLASH_nCS <= 1'b1;
link <= 4'h0;
FLASH_IO_OBUF <= 4'hf;
end
endcase
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 Erase
localparam 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 Erase
localparam 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 Register
localparam S_4QPP = 24'h040000; //Page Programming
localparam S_4QOR = 24'h080000; //Quad Output Read
localparam S_RESET = 24'h100000; //flash software reset
reg [23:0] state = S_IDLE;
reg [23:0] next_state;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
state <= S_IDLE;
end
else begin
state <= next_state;
end
end
wire [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) begin
all_req_buf <= all_req;
end
always @(*) begin
case(state)
S_IDLE: begin
next_state <= S_ARB;
end
S_ARB: begin
casex(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;
endcase
end
S_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: begin
if(submodule_busy) begin
next_state <= S_WAIT;
end
else begin
next_state <= state;
end
end
S_WAIT: begin
if(~submodule_busy) begin
next_state <= S_STOP;
end
else begin
next_state <= S_WAIT;
end
end
S_STOP: begin
next_state <= S_IDLE;
end
default: begin
next_state <= S_IDLE;
end
endcase
end
reg [3:0] update_register = 4'd0; //在RD REG操作中判断要更新哪一个Reg
//1:SR1, 2:CR1, 3:SR2, 4:BAR, 5:ABR
always @(posedge clk) begin
case(state)
S_IDLE: begin
module_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;
end
S_ARB: begin
module_arb <= M_NONE;
start_1 <= 1'b0;
start_2 <= 1'b0;
start_3 <= 1'b0;
start_4 <= 1'b0;
start_5 <= 1'b0;
end
S_RESET: begin
module_arb <= M_instruction;
start_1 <= 1'b1;
instruction_1 <= I_RESET;
end
S_WREN: begin
module_arb <= M_instruction;
start_1 <= 1'b1;
instruction_1 <= I_WREN;
end
S_WRDI: begin
module_arb <= M_instruction;
start_1 <= 1'b1;
instruction_1 <= I_WRDI;
end
S_CLSR: begin
module_arb <= M_instruction;
start_1 <= 1'b1;
instruction_1 <= I_CLSR;
end
S_BE: begin
module_arb <= M_instruction;
start_1 <= 1'b1;
instruction_1 <= I_BE;
end
S_SE: begin
module_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];
end
S_RDSR1: begin
module_arb <= M_RDR;
start_3 <= 1'b1;
instruction_3 <= I_RDSR1;
Register_Len_RDR <= 4'd1;
update_register <= 4'd1;
end
S_RDCR1: begin
module_arb <= M_RDR;
start_3 <= 1'b1;
instruction_3 <= I_RDCR1;
Register_Len_RDR <= 4'd1;
update_register <= 4'd2;
end
S_RDSR2: begin
module_arb <= M_RDR;
start_3 <= 1'b1;
instruction_3 <= I_RDSR2;
Register_Len_RDR <= 4'd1;
update_register <= 4'd3;
end
S_RDBAR: begin
module_arb <= M_RDR;
start_3 <= 1'b1;
instruction_3 <= I_RDBAR;
Register_Len_RDR <= 4'd1;
update_register <= 4'd4;
end
S_RDABR: begin
module_arb <= M_RDR;
start_3 <= 1'b1;
instruction_3 <= I_RDABR;
Register_Len_RDR <= 4'd4;
update_register <= 4'd5;
end
S_WRSR1: begin
module_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;
end
S_WRCR1: begin
module_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;
end
S_WRBAR: begin
module_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;
end
S_WRABR: begin
module_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];
end
S_4QPP: begin
module_arb <= M_PP;
start_4 <= 1'b1;
addr_PP <= WR_addr;
Byte_Len_PP <= WR_Byte_Len;
end
S_4QOR: begin
module_arb <= M_4QOR;
start_5 <= 1'b1;
addr_4QOR <= RD_addr;
Byte_Len_4QOR <= RD_Byte_Len;
end
S_WAIT: begin
start_1 <= 1'b0;
start_2 <= 1'b0;
start_3 <= 1'b0;
start_4 <= 1'b0;
start_5 <= 1'b0;
end
S_STOP: begin
module_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: ;
endcase
end
default: begin
module_arb <= M_NONE;
start_1 <= 1'b0;
start_2 <= 1'b0;
start_3 <= 1'b0;
start_4 <= 1'b0;
start_5 <= 1'b0;
end
endcase
end
always @(*) begin
case(state)
S_IDLE, S_ARB: begin
busy <= 1'b0;
end
default: begin
busy <= 1'b1;
end
endcase
end
endmodule
测试
编写测试代码如下,并下载到板子进行测试(注意,我的板子上的 FLASH 的 QUAD bit(CR1[1])已经被置位了,所以这里只执行了擦除、写入、读取流程,如果你的不是,需要多加一个 WRR 步骤)
verilog
// FLASH 测试(主存读写测试)
`default_nettype none
module test_flash_mainMemory(
input wire clk_sys, //OXCO_10M
output 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 Programming
reg [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) begin
PPS_d0 <= clk_1k;
PPS_d1 <= PPS_d0;
if(PPS_pe) begin
if(cnt==1 || cnt==11) begin
if(SR1_rd[1]) begin //检查WEL
cnt <= cnt + 1'b1;
end
else begin
cnt <= cnt;
end
end
else if(cnt==3 || cnt==13) begin
if(~SR1_rd[0]) begin //检查WIP
cnt <= cnt + 1'b1;
end
else begin
cnt <= cnt;
end
end
else begin
cnt <= cnt + 1'b1;
end
end
PPS_pe_d1 <= PPS_pe;
PPS_pe_d2 <= PPS_pe_d1;
end
localparam WR_RD_ADDR = 32'h0100_0000;
reg [7:0] data_PP_tmp = 8'd0;
always @(posedge data_rd_clk) begin
if(data_rden) begin
data_PP_tmp <= data_PP_tmp + 1'b1;
end
else begin
data_PP_tmp <= data_PP_tmp;
end
end
always @(posedge clk_100M) begin
case(cnt)
//---------------erase-------------------------
8'd0: WREN_req <= PPS_pe_d2;
8'd1: rd_SR1_req <= PPS_pe_d2;
8'd2: begin
sector_erase_req <= PPS_pe_d2;
sector_erase_addr <= WR_RD_ADDR;
end
8'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: begin
WR_req <= PPS_pe_d2;
WR_addr <= WR_RD_ADDR;
WR_Byte_Len <= 10'd16;
data_PP <= data_PP_tmp;
end
8'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: begin
RD_req <= PPS_pe_d2;
RD_addr <= WR_RD_ADDR;
RD_Byte_Len <= 10'd16;
end
default: ;
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 原语,我这里封装为了一个代码模块,具体可看这篇博文
verilog
/*
* 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' 的流程。
(完)