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' 的流程。

(完)

相关推荐
轻口味1 小时前
命名空间与模块化概述
开发语言·前端·javascript
吉大一菜鸡1 小时前
FPGA学习(基于小梅哥Xilinx FPGA)学习笔记
笔记·学习·fpga开发
森旺电子2 小时前
51单片机仿真摇号抽奖机源程序 12864液晶显示
单片机·嵌入式硬件·51单片机
晓纪同学2 小时前
QT-简单视觉框架代码
开发语言·qt
威桑2 小时前
Qt SizePolicy详解:minimum 与 minimumExpanding 的区别
开发语言·qt·扩张策略
飞飞-躺着更舒服2 小时前
【QT】实现电子飞行显示器(简易版)
开发语言·qt
明月看潮生2 小时前
青少年编程与数学 02-004 Go语言Web编程 16课题、并发编程
开发语言·青少年编程·并发编程·编程与数学·goweb
明月看潮生2 小时前
青少年编程与数学 02-004 Go语言Web编程 17课题、静态文件
开发语言·青少年编程·编程与数学·goweb
Java Fans2 小时前
C# 中串口读取问题及解决方案
开发语言·c#
盛派网络小助手2 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#