S25FL系列FLASH读写的FPGA实现

文章目录

实现思路

建议读者先对 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' 的流程。

(完)

相关推荐
落落落sss12 分钟前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
简单.is.good29 分钟前
【测试】接口测试与接口自动化
开发语言·python
乌恩大侠1 小时前
【Xcode Command Line Tools】安装指南
macos·fpga开发·c
Yvemil71 小时前
MQ 架构设计原理与消息中间件详解(二)
开发语言·后端·ruby
程序员是干活的1 小时前
私家车开车回家过节会发生什么事情
java·开发语言·软件构建·1024程序员节
我是陈泽1 小时前
一行 Python 代码能实现什么丧心病狂的功能?圣诞树源代码
开发语言·python·程序员·编程·python教程·python学习·python教学
优雅的小武先生1 小时前
QT中的按钮控件和comboBox控件和spinBox控件无法点击的bug
开发语言·qt·bug
虽千万人 吾往矣1 小时前
golang gorm
开发语言·数据库·后端·tcp/ip·golang
创作小达人1 小时前
家政服务|基于springBoot的家政服务平台设计与实现(附项目源码+论文+数据库)
开发语言·python
郭二哈1 小时前
C++——list
开发语言·c++·list