S25FL系列FLASH读写的FPGA实现

文章目录

  • 实现思路
  • 具体实现
    • 子模块实现
    • top模块
  • 测试
  • Something

实现思路

  建议读者先对 S25FL-S 系列 FLASH 进行了解,我之前的博文中有详细介绍。

  笔者的芯片具体型号为 S25FL256SAGNFI00,存储容量 256Mb,增强高性能 EHPLC,4KB 与 64KB 混合 Sector 的存储阵列,256 Byte 的 Page Programming Buffer 大小,最高支持 133MHz,无硬复位 RESET# 引脚。

  为简单起见,采用 SDR 时钟模式;为了兼顾读写速度,采用 Quad mode;同时考虑到 Quad Page Programming 地址只能通过 SI 单线传输,因此读、写 FLASH 分别采用 Quad Output Read、Quad Page Programming,以实现时序格式的统一,简化编程。

  由于 S25FL-S 在 SCK 上升沿锁存数据,在 SCK 下降沿转换数据,因此主控端应在 SCK 下降沿转换数据,在 SCK 上升沿锁存数据

  由于写 FLASH 需要先进行写使能以及擦除操作,而擦除操作需要检查 WIP bit(SR1[0]);要使用 Quad 读写模式,需要置位 Quad bit(CR1[1]);要判断地址映射类型和四元读模式下的 Dummy 长度,需要实现读写寄存器。因此需要实现以下功能:写使能 WREN、写失能 WRDI、写寄存器 WRR、清除状态寄存器 CLSR、读状态寄存器 RDSR1/RDSR2、读配置寄存器 RDCR、擦除操作(扇区擦除 4SE、批量擦除 BE)、四元编程操作 4QPP、Quad Output Read 操作 4QOR 等。

  为每一种功能单独写一个模块当然也是可行的思路,但过于繁杂;观察到在时序层面上述指令可以归类为简单的 5 种:单 8bit 指令(如 WREN、WRDI、CLSR、BE 等)、写寄存器(8bit 指令后跟随 1~4Byte 数据,SI 单线传输,如 WRR、ABWR、BRWR 等,甚至 8bit 指令 + 4Byte 地址的 4SE 也可归于此类)、读寄存器(8bit 指令(SI)后跟随 1~4Byte 输出(SO),如 RDSR1、RDSR2、RDCR1、ABRD、BRRD 等)、四元写 FLASH (8bit 指令(SI)+ 32bit 地址(SI)+ 1~256Byte 数据(IO0~IO3写),如 4QPP)、四元读 FLASH (8bit 指令(SI)+ 32bit 地址(SI)+ xbit Dummy + xByte 数据(IO0~IO3读回),如 4QOR)。

  因此可以首先实现以上几个基础模块,然后根据需要在上层模块中用状态机控制几个基础模块的运行。

具体实现

  由于本示例实现中每个子模块都涉及 FLASH_IO 这组 inout 线的操作,因此有注意事项如下:

  每个 FPGA 管脚上都要有 IBUF、OBUF 或 IOBUF,input/output 管脚上 IBUF/OBUF 会自动生成,而 inout 管脚需要用户编写,要么用 IOBUF,要么直接用 link? xx_OBUF : 1’bz 这种形式(其实后者也是生成了一个 OBUF 和一个 IBUF)。

  对于每个 FPGA 管脚,只能由一个 OBUF 驱动,因此如果多个子模块要用 inout 操作同一根线,会出问题(这种情况下 vivado 会自动生成 IBUF,导致模块大部分逻辑无效化,进而在综合后整个模块被优化掉,即使强制关闭 IBUF/OBUF 自动插入功能,也会因为多个 OBUF 驱动同一管脚而综合失败)。

 因此子模块不能再保有 inout,而是通过操作顶层模块的 IOBUF 实现数据读写,具体实现方式为:子模块关于 FLASH_IO 的接口设计为两个单向接口(FLASH_IO_IBUF、FLASH_IO_OBUF),并给出何时使能 O_BUF 的 link 信号;顶层模块根据状态仲裁接通哪路子模块,并根据对应的 link 决定驱动方向。

子模块实现

  • 单条指令
/* 
 * file			: flash_instruction.v
 * author		: 今朝无言
 * Lab			: WHU-EIS-LMSWE
 * date			: 2023-11-15
 * version		: v2.0
 * description	: 单条 8bit 指令,从而支持诸如 WREN、WRDI、Bulk Erase 等指令
 * Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.
 */
`default_nettype none
module flash_instruction(
input	wire			clk,
input	wire			rst_n,

output	wire			FLASH_SCK,
output	reg				FLASH_nCS,

output	reg		[3:0]	link,
output	reg		[3:0]	FLASH_IO_OBUF,
input	wire	[3:0]	FLASH_IO_IBUF,

//usr interface
input	wire			send_en,		//上升沿有效
input	wire	[7:0]	instruction,
output	reg				busy
);

reg		FLASH_nCS	= 1'b1;
assign	FLASH_SCK	= FLASH_nCS? 1'b1 : clk;	//SPI mode 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
  • 读寄存器
/* 
 * 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
  • 写寄存器
/* 
 * 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
/* 
 * 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 主存储器
/* 
 * 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
/* 
 * 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 步骤)

// 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 原语,我这里封装为了一个代码模块,具体可看这篇博文

/* 
 * 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’ 的流程。

(完)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/189781.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java中static、final、static final的区别

文章目录 finalstaticstatic final final final可以修饰&#xff1a;属性&#xff0c;方法&#xff0c;类&#xff0c;局部变量&#xff08;方法中的变量&#xff09; final修饰的属性的初始化可以在编译期&#xff0c;也可以在运行期&#xff0c;初始化后不能被改变。 final修…

nginx配置文件的简单结构

nginx的配置文件&#xff08;nginx.conf&#xff09;整体上可分为三个部分&#xff1a;全局块、events块、http块 区域职责全局块配置和nginx运行相关的全局配置events块配置和网络连接相关的配置http块配置代理、缓存、日志记录、虚拟主机等配置在http块中&#xff0c;可以包含…

python:傅里叶分析,傅里叶变换 FFT

使用python进行傅里叶分析&#xff0c;傅里叶变换 FFT 的一些关键概念的引入&#xff1a; 1.1.离散傅里叶变换&#xff08;DFT&#xff09; 离散傅里叶变换(discrete Fourier transform) 傅里叶分析方法是信号分析的最基本方法&#xff0c;傅里叶变换是傅里叶分析的核心&…

摆脱无用代码的负担:TreeShaking 的魔力

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【教学类-06-12】20231126 (一)如何让加减乘除题目从小到大排序(以1-20之间加法为例,做正序排列用)

结果展示 优化后 优化前 背景需求&#xff1a; 生成列表 单独抽取显示题目排序方法 存在问题: 我希望 00 01 02……这样排序&#xff0c;但是实际上&#xff0c;除了第一个加数会从小到大排序&#xff0c;第二个被加数的第十位数和个位数都会从小到大排序&#xff0c;也就是…

NeoPreference延伸:为SharedPreferences配置项生成配置页面

代码地址&#xff1a;https://github.com/Nagi1225/NeoPreference.git 最初在开发NeoPreference这个SharedPreferences工具的时候&#xff0c;就期望完成三个目标&#xff1a; 代码简洁&#xff0c;新增配置项的时候一行代码&#xff08;最多两行&#xff09;&#xff1b;读写…

线程的常用方法-wait和notify以及线程的结束方式

再复习一下Java中的线程的状态图 wait和sleep的区别是&#xff1a;wait需要先持有锁&#xff08;wait需要再synchronized代码块中执行&#xff09;&#xff0c;执行后会让出锁。而sleep不需要先持有锁&#xff0c;执行后也不会释放锁&#xff08;有锁的话抱着锁睡觉&#xff09…

SpringBoot 环境使用 Redis + AOP + 自定义注解实现接口幂等性

目录 一、前言二、主流实现方案介绍2.1、前端按钮做加载状态限制&#xff08;必备&#xff09;2.2、客户端使用唯一标识符2.3、服务端通过检测请求参数进行幂等校验&#xff08;本文使用&#xff09; 三、代码实现3.1、POM3.2、application.yml3.3、Redis配置类3.4、自定义注解…

基于Haclon的标签旋转项目案例

项目要求&#xff1a; 图为HALCON附图“25interleaved_exposure_04”&#xff0c;里面为旋转的二维码标签&#xff0c;请将其旋转到水平位置。 项目知识&#xff1a; 在HALCON中进行图像平移和旋转通常有以下步骤&#xff1a; &#xff08;1&#xff09;通过hom_mat2d_ident…

jQuery_03 dom对象和jQuery对象的互相转换

dom对象和jQuery对象 dom对象 jQuery对象 在一个文件中同时存在两种对象 dom对象: 通过js中的document对象获取的对象 或者创建的对象 jQuery对象: 通过jQuery中的函数获取的对象。 为什么使用dom或jQuery对象呢&#xff1f; 目的是 要使用dom对象的函数或者属性 以及呢 要…

<JavaEE> 线程的五种创建方法 和 查看线程的两种方式

目录 一、线程的创建方法 1.1 继承 Thread -> 重写 run 方法 1.2 使用匿名内部类 -> 继承 Thread -> 重写 run 方法 1.3 实现 Runnable 接口 -> 重写 run 方法 1.4 使用匿名内部类 -> 实现 Runnable 接口 -> 重写 run 方法 1.5 使用 lambda 表达式 二…

Self Distillation 自蒸馏论文解读

paper&#xff1a;Be Your Own Teacher: Improve the Performance of Convolutional Neural Networks via Self Distillation official implementation&#xff1a; https://github.com/luanyunteng/pytorch-be-your-own-teacher 前言 知识蒸馏作为一种流行的压缩方法&#…

五种多目标优化算法(MOGWO、MOLPB、MOJS、NSGA3、MOPSO)求解微电网多目标优化调度(MATLAB代码)

一、多目标优化算法简介 &#xff08;1&#xff09;多目标灰狼优化算法MOGWO 多目标应用&#xff1a;基于多目标灰狼优化算法MOGWO求解微电网多目标优化调度&#xff08;MATLAB代码&#xff09;-CSDN博客 &#xff08;2&#xff09;多目标学习者行为优化算法MOLPB 多目标学习…

ps5ps4游戏室如何计时?计费系统怎么查看游戏时间以及收费如何管理

ps5ps4游戏室如何计时&#xff1f;计费系统怎么查看游戏时间以及收费如何管理 1、ps5ps4游戏室如何计时&#xff1f; 下图以佳易王计时计费软件V17.9为例说明 在开始计时的时候&#xff0c;只需点 开始计时按钮&#xff0c;那么开台时间和使用的时间长度项目显示在屏幕上&am…

如何判断一个题目用“贪心/动态规划“还是用“BFS/DFS”方法解决

1 总结 1.1 贪心、动态规划和BFS/DFS题解的关系 一般能使用贪心、动态规划解决一个问题时&#xff0c;使用BFS&#xff0c;DFS也能解决这个题&#xff0c;但是反之不能成立。 1.2 2 贪心 -> BFS/DFS 2.1 跳跃游戏1和3的异同 这两道题&#xff0c;“跳跃游戏”&#xf…

靡靡之音 天籁之声 ——Adobe Audition

上一期讲到了和Pr配合使用的字幕插件Arctime Pro的相关介绍。相信还记得的小伙伴应该记得我还提到过一个软件叫做Au。 当人们对字幕需求的逐渐满足&#xff0c;我们便开始追求更高层次的享受&#xff0c;当视觉享受在进步&#xff0c;听觉享受想必也不能被落下&#xff01; Au即…

Flutter桌面应用开发之毛玻璃效果

目录 效果实现方案依赖库支持平台实现步骤注意事项话题扩展 毛玻璃效果&#xff1a;毛玻璃效果是一种模糊化的视觉效果&#xff0c;常用于图像处理和界面设计中。它可以通过在图像或界面元素上应用高斯模糊来实现。使用毛玻璃效果可以增加图像或界面元素的柔和感&#xff0c;同…

一、深入简出串口(USRT)通信——基本概念。

一、前言 串口到底是什么&#xff1f;简单来说一句话就可以解释&#xff0c;串口就是一种通信协议。 看到这里可能大家会觉得你这不是放屁么&#xff0c;说了跟没说一样。所以这里做前言来描述&#xff0c;大家要先对通信协议有一个下意识地认识才能在学习串口的时候不至于迷茫…

spring循环依赖

Bean的生命周期 这里不会对Bean的生命周期进行详细的描述&#xff0c;只描述一下大概的过程。 Bean的生命周期指的就是&#xff1a;在Spring中&#xff0c;Bean是如何生成的&#xff1f; 被Spring管理的对象叫做Bean。Bean的生成步骤如下&#xff1a; Spring扫描class得到Bean…

yolo系列中的一些评价指标说明

文章目录 一. 混淆矩阵二. 准确度(Accuracy)三. 精确度(Precision)四. 召回率(Recall)五. F1-score六. P-R曲线七. AP八. mAP九. mAP0.5十. mAP[0.5:0.95] 一. 混淆矩阵 TP (True positives)&#xff1a;被正确地划分为正例的个数&#xff0c;即实际为正例且被分类器划分为正例…