S25FL系列FLASH读写的FPGA实现

news2024/11/28 9:28:00

文章目录

  • 实现思路
  • 具体实现
    • 子模块实现
    • 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’ 的流程。

(完)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快速幂算法详解(C++实现)

文章目录 1. 什么是快速幂2. 暴力求解代码实现缺陷分析 3. 优化一&#xff1a;取模运算的性质4. 优化二&#xff1a;快速幂算法的核心思想5. 终极优化&#xff1a;位运算优化6. 源码 这篇文章我们来一起学习一个算法——快速幂算法。 1. 什么是快速幂 顾名思义&#xff0c;快速…

中海油“海安杯”一站到底知识竞赛真的很有特色

中海油“海安杯”一站到底知识竞赛规格高&#xff0c;赛制复杂&#xff0c;天纵知识竞赛系统为此次知识竞赛提供了软件支持。本次竞赛设置选手区和擂台区两个区域。比赛共分为五个轮次&#xff0c;五个轮次选手区所有参赛选手均需答题。 第一轮&#xff1a;“脱颖而出” 所有参…

叠加原理(superposition principle)

叠加原理&#xff08;superposition principle&#xff09;指对线性系统而言&#xff0c;两个或多个输入产生的输出&#xff0c;等于这几个输入单独引起的输出的和&#xff0c;即输入的叠加等于各输入单独引起的输出的叠加。 例如&#xff0c;如果输入产生的输出是&#xff0c;…

B树与B+树的对比

B树&#xff1a; m阶B树的核心特性&#xff1a; 树中每个节点至多有m棵子树&#xff0c;即至多含有m-1个关键字根节点的子树数属于[2, m]&#xff0c;关键字数属于[1, m-1]&#xff0c;其他节点的子树数属于 [ ⌈ m 2 ⌉ , m ] [\lceil \frac{m}{2}\rceil, m] [⌈2m​⌉,m]&am…

Spring的依赖注入,依赖注入的基本原则,依赖注入的优势

文章目录 Spring的依赖注入依赖注入的基本原则依赖注入有什么优势查找定位操作与应用代码完全无关。有哪些不同类型的依赖注入实现方式&#xff1f;构造器依赖注入和 Setter方法注入的区别 Spring的依赖注入 控制反转IoC是一个很大的概念&#xff0c;可以用不同的方式来实现。…

电子学会C/C++编程等级考试2022年09月(二级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:统计误差范围内的数 统计一个整数序列中与指定数字m误差范围小于等于X的数的个数。 时间限制:5000 内存限制:65536输入 输入包含三行: 第一行为N,表示整数序列的长度(N <= 100); 第二行为N个整数,整数之间以一个空格分…

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

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

定长子网划分和变长子网划分问题_二叉树解法_通俗易懂_配考研真题

引入:定长子网划分和变长子网划分的基本概念 定长子网划分和变长子网划分的基本概念 目前常用的子网划分&#xff0c;是基于CIDR的子网划分&#xff0c;也就是将给定的CIDR地址块划分为若干个较小的CIDR地址块。 定长子网划分: 使用同一个子网掩码来划分子网&#xff0c;因…

使用VC++设计程序对一幅256级灰度图像进行全局固定阈值分割、自适应阈值分割

图像分割–全局固定阈值分割、自适应阈值分割 获取源工程可访问gitee可在此工程的基础上进行学习。 该工程的其他文章&#xff1a; 01- 一元熵值、二维熵值 02- 图像平移变换&#xff0c;图像缩放、图像裁剪、图像对角线镜像以及图像的旋转 03-邻域平均平滑算法、中值滤波算法、…

线性表,也是Java中数组的知识点!

线性表定义&#xff1a; 由n (n≥0)个数据特性相同的元素构成的有限序列称为线性表&#xff0c;(n0)的时候被称为空表。 线性表的顺序表示 线性表的顺序存储又被称为顺序表 优点 无需为表示表中元素之间的逻辑关系而增加额外的存储空间可以随意读取任意位置的元素 缺点 插入…

Autosar MCAL-RH850P1HC-MCAL配置环境搭建

文章目录 前言下载安装包软件安装安装SIP包安装MCAL文件配置工程配置生成代码测试静态代码路径总结前言 对于RH850P1HC,官网有免费的MCAL,但官网的MCAL没有CAN模块(原厂反馈为Bosch IP,CAN Driver他们没有),也没有FEE模块。如果需要,可以找第三方软件公司,如ETAS.虽然M…

机器学习:攻击方法FGSM系列

任务 FGSM I-FGSM MI-FGSM Ensemble Attack 攻击评价指标 准确率越低表明攻击越好 数据 预训练模型 BaseLine 实践

SpringBoot——LiteFlow引擎框架

优质博文&#xff1a;IT-BLOG-CN 一、LiteFlow 简介 LiteFlow是一个轻量且强大的国产规则引擎框架&#xff0c;可用于复杂的组件化业务的编排领域。帮助系统变得更加丝滑且灵活。利用LiteFlow&#xff0c;你可以将瀑布流式的代码&#xff0c;转变成以组件为核心概念的代码结构…

计算机组成原理-Cache的基本概念和原理

文章目录 存储系统存在的问题Cache的工作原理局部性原理性能分析例题界定何为局部部分问题总结 存储系统存在的问题 增加Cache层来缓和CPU和主存的工作速度矛盾 Cache的工作原理 启动某个程序后&#xff0c;将程序的代码从辅存中取出放入内存中&#xff0c;再从内存中将代码…

nginx反向代理解决跨域前端实践

需求实现 本地请求百度的一个搜索接口&#xff0c;用nginx代理解决跨域思路&#xff1a;前端和后端都用nginx代理到同一个地址8080&#xff0c;这样访问接口就不存在跨域限制 本地页面 查询一个百度搜索接口&#xff0c;运行在http://localhost:8035 index.js const path …

Kanna库代码示例

编写一个使用Kanna库的网络爬虫程序。以下是代码的详细解释&#xff1a; swift import Kanna // 创建一个对象 let proxy Proxy(host: ") // 创建一个Kanna对象 let kanna Kanna(proxy: proxy) // 创建一个请求对象 let request Request(url: "") // 使用…

AIGC ChatGPT4总结Linux Shell命令集合

在Linux中,Shell命令的数量非常庞大,因为Linux提供了各种各样的命令来处理系统任务。这些命令包括GNU核心工具集、系统命令、shell内置命令以及通过安装获得的第三方应用程序命令。以下是一些常见的Linux命令分类及其示例,但请注意,这不是一个全面的列表,因为列出所有命令…

typeof,instanceof

1.typeof typeof运算符返回的结果是以小写的字符串表示的变量的类型 2.instanceof instanceof运算符用于判断右边构造函数的原型对象是否在左边对象的原型链上 let arr[]let obj{}let datenew Dateconsole.log(arr instanceof Array)console.log(arr instanceof Object)conso…

算法竞赛备赛进阶之状态压缩训练

状态压缩 状态压缩DP是一种暴力的算法&#xff0c;它需要遍历每个状态&#xff0c;而每个状态是多个事件的集合。这种算法通常用于小规模问题的求解&#xff0c;因为它的复杂度是指数级别的。 状态压缩DP的两个基本特征包括问题的数据规模特别小&#xff0c;可以通过2的阶乘次…

显示器校准软件BetterDisplay Pro mac中文版介绍

BetterDisplay Pro mac是一款显示器校准软件&#xff0c;可以帮助用户调整显示器的颜色和亮度&#xff0c;以获得更加真实、清晰和舒适的视觉体验。 BetterDisplay Pro mac软件特点 - 显示器校准&#xff1a;可以根据不同的需求和环境条件调整显示器的颜色、亮度和对比度等参数…