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 3reg		[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) beginsend_en_d0	<= send_en;send_en_d1	<= send_en_d0;
endassign	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) beginif(~rst_n) beginstate	<= S_IDLE;endelse beginstate	<= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(send_en_pe) beginnext_state	<= S_COMMAND;endelse beginnext_state	<= S_IDLE;endendS_COMMAND: beginif(cnt >= 3'd7) beginnext_state	<= S_STOP;endelse beginnext_state	<= S_COMMAND;endendS_STOP: beginnext_state	<= S_IDLE;enddefault: beginnext_state	<= S_IDLE;endendcase
end//FLASH_nCS
always @(negedge clk) begincase(state)S_COMMAND: beginFLASH_nCS	<= 1'b0;enddefault: beginFLASH_nCS	<= 1'b1;endendcase
end//cnt
always @(posedge clk) begincase(state)S_IDLE: begincnt		<= 3'd0;endS_COMMAND: beginif(~FLASH_nCS) begincnt		<= cnt + 1'b1;endelse begincnt		<= 3'd0;endendS_STOP: begincnt		<= 3'd0;enddefault: begincnt		<= cnt;endendcase
end//FLASH_IO_OBUF
always @(negedge clk) begin		//在SCK下降沿转换数据case(state)S_COMMAND: beginFLASH_IO_OBUF[0]	<= instruction[3'd7-cnt];	//首先移出MSBFLASH_IO_OBUF[3:1]	<= 3'b111;enddefault: beginFLASH_IO_OBUF	<= 4'hf;endendcase
end//link
always @(negedge clk) begincase(state)S_COMMAND: beginlink	<= 4'b1101;//指令阶段,SO应维持高阻,WP#、HOLD#应拉高;//而WP#、HOLD#内部有上拉电阻,因此IO1~IO3可以直接释放掉//不过为保险起见,这里还是强制拉高IO2/IO3,而IO1可以释放掉enddefault: beginlink	<= 4'h0;endendcase
end//busy
always @(*) begincase(state)S_IDLE: beginbusy	<= 1'b0;endS_COMMAND, S_STOP: beginbusy	<= 1'b1;enddefault: beginbusy	<= 1'b0;endendcase
endendmodule
  • 读寄存器
/* * 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 3reg		[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) beginread_en_d0	<= read_en;read_en_d1	<= read_en_d0;
endassign	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) beginif(~rst_n) beginstate	<= S_IDLE;endelse beginstate	<= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(read_en_pe) beginnext_state	<= S_COMMAND;endelse beginnext_state	<= S_IDLE;endendS_COMMAND: beginif(cnt >= 3'd7) beginnext_state	<= S_RDR;endelse beginnext_state	<= S_COMMAND;endendS_RDR: beginif(cnt >= 3'd7 && cnt_Byte >= Register_Len - 1'b1) beginnext_state	<= S_STOP;endelse beginnext_state	<= S_RDR;endendS_STOP: beginnext_state	<= S_IDLE;enddefault: beginnext_state	<= S_IDLE;endendcase
end//FLASH_nCS
always @(negedge clk) begincase(state)S_COMMAND, S_RDR: beginFLASH_nCS	<= 1'b0;enddefault: beginFLASH_nCS	<= 1'b1;endendcase
end//cnt
always @(posedge clk) begincase(state)S_IDLE: begincnt		<= 3'd0;endS_COMMAND, S_RDR: begin		//将cnt设计为3bit位宽,可实现模8加if(~FLASH_nCS) begincnt		<= cnt + 1'b1;endelse begincnt		<= 3'd0;endendS_STOP: begincnt		<= 3'd0;enddefault: begincnt		<= cnt;endendcase
end//cnt_Byte
always @(posedge clk) begincase(state)S_RDR: beginif(cnt==3'd7) begincnt_Byte	<= cnt_Byte + 1'b1;endelse begincnt_Byte	<= cnt_Byte;endenddefault: begincnt_Byte	<= 4'd0;endendcase
end//FLASH_IO_OBUF
always @(negedge clk) begin		//在SCK下降沿转换数据case(state)S_COMMAND: beginFLASH_IO_OBUF[0]	<= instruction[3'd7-cnt];	//首先移出MSBFLASH_IO_OBUF[3:1]	<= 3'b111;enddefault: beginFLASH_IO_OBUF	<= 4'hf;endendcase
end//link
always @(negedge clk) begincase(state)S_COMMAND: beginlink	<= 4'b1101;endS_RDR: beginlink	<= 4'h0;enddefault: beginlink	<= 4'h0;endendcase
end//read reg
wire	SO	= FLASH_IO_IBUF[1];
always @(posedge clk or negedge rst_n) begin	//须在SCK上升沿锁存数据if(~rst_n) beginReg		<= 32'd0;endelse begincase(state)S_RDR: beginReg		<= {Reg[30:0], SO};		//移位寄存来自SO的值enddefault: beginReg		<= Reg;endendcaseend
end//busy
always @(*) begincase(state)S_IDLE: beginbusy	<= 1'b0;enddefault: beginbusy	<= 1'b1;endendcase
endendmodule
  • 写寄存器
/* * 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 3reg		[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) beginsend_en_d0	<= send_en;send_en_d1	<= send_en_d0;
endassign	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) beginif(~rst_n) beginstate	<= S_IDLE;endelse beginstate	<= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(send_en_pe) beginnext_state	<= S_COMMAND;endelse beginnext_state	<= S_IDLE;endendS_COMMAND: beginif(cnt >= 3'd7) beginnext_state	<= S_WRR;endelse beginnext_state	<= S_COMMAND;endendS_WRR: beginif(cnt >= 3'd7 && cnt_Byte >= Register_Len - 1'b1) beginnext_state	<= S_STOP;endelse beginnext_state	<= S_WRR;endendS_STOP: beginnext_state	<= S_IDLE;enddefault: beginnext_state	<= S_IDLE;endendcase
end//FLASH_nCS
always @(negedge clk) begincase(state)S_COMMAND, S_WRR: beginFLASH_nCS	<= 1'b0;enddefault: beginFLASH_nCS	<= 1'b1;endendcase
end//cnt
always @(posedge clk) begincase(state)S_IDLE: begincnt		<= 3'd0;endS_COMMAND, S_WRR: begin		//将cnt设计为3bit位宽,可实现模8加if(~FLASH_nCS) begincnt		<= cnt + 1'b1;endelse begincnt		<= 3'd0;endendS_STOP: begincnt		<= 3'd0;enddefault: begincnt		<= cnt;endendcase
end//cnt_Byte
always @(posedge clk) begincase(state)S_WRR: beginif(cnt==3'd7) begincnt_Byte	<= cnt_Byte + 1'b1;endelse begincnt_Byte	<= cnt_Byte;endenddefault: begincnt_Byte	<= 4'd0;endendcase
end//FLASH_IO_OBUF
always @(negedge clk) begin		//在SCK下降沿转换数据case(state)S_COMMAND: beginFLASH_IO_OBUF[0]	<= instruction[3'd7-cnt];	//首先移出MSBFLASH_IO_OBUF[3:1]	<= 3'b111;endS_WRR: begincase(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;endcaseFLASH_IO_OBUF[3:1]	<= 3'b111;enddefault: beginFLASH_IO_OBUF	<= 4'hf;endendcase
end//link
always @(negedge clk) begincase(state)S_COMMAND, S_WRR: beginlink	<= 4'b1101;enddefault: beginlink	<= 4'h0;endendcase
end//busy
always @(*) begincase(state)S_IDLE: beginbusy	<= 1'b0;enddefault: beginbusy	<= 1'b1;endendcase
endendmodule
  • 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 内进行写入,超出的将被忽略,建议一次写一整个 Pageoutput	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的指令码为 0x34reg		FLASH_nCS	= 1'b1;
assign	FLASH_SCK	= FLASH_nCS? 1'b1 : clk;	//SPI mode 3reg		[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) beginprogram_start_d0	<= program_start;program_start_d1	<= program_start_d0;
endassign	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) beginif(~rst_n) beginstate	<= S_IDLE;endelse beginstate	<= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(program_start_pe) beginnext_state	<= S_COMMAND;endelse beginnext_state	<= S_IDLE;endendS_COMMAND: beginif(cnt >= 3'd7) beginnext_state	<= S_ADDR;endelse beginnext_state	<= S_COMMAND;endendS_ADDR: beginif(cnt >= 3'd7 && cnt_Byte >= 4'd3) beginnext_state	<= S_QUAD_WR;endelse beginnext_state	<= S_ADDR;endendS_QUAD_WR: beginif(cnt >= 3'd4 && (Byte_Len == 10'd0 || cnt_Byte >= Byte_Len - 1'b1)) begin	//Len=0时视作Len=1next_state	<= S_STOP;endelse beginnext_state	<= S_QUAD_WR;endendS_STOP: beginnext_state	<= S_IDLE;enddefault: beginnext_state	<= S_IDLE;endendcase
end//FLASH_nCS
always @(negedge clk) begincase(state)S_COMMAND, S_ADDR, S_QUAD_WR: beginFLASH_nCS	<= 1'b0;enddefault: beginFLASH_nCS	<= 1'b1;endendcase
end//cnt
always @(posedge clk) begincase(state)S_IDLE: begincnt		<= 3'd0;endS_COMMAND, S_ADDR: beginif(~FLASH_nCS) begincnt		<= cnt + 1'b1;endelse begincnt		<= 3'd0;endendS_QUAD_WR: beginif(~FLASH_nCS) begincnt		<= cnt + 3'd4;	//Quad WR 阶段一次传送4bitendelse begincnt		<= 3'd0;endendS_STOP: begincnt		<= 3'd0;enddefault: begincnt		<= cnt;endendcase
end//cnt_Byte
always @(posedge clk) begincase(state)S_ADDR: beginif(cnt==3'd7) beginif(cnt_Byte >= 16'd3) begincnt_Byte	<= 10'd0;endelse begincnt_Byte	<= cnt_Byte + 1'b1;endendelse begincnt_Byte	<= cnt_Byte;endendS_QUAD_WR: beginif(cnt==3'd4) begincnt_Byte	<= cnt_Byte + 1'b1;endelse begincnt_Byte	<= cnt_Byte;endenddefault: begincnt_Byte	<= 10'd0;endendcase
end//link
always @(negedge clk) begincase(state)S_COMMAND, S_ADDR: beginlink	<= 4'b1101;endS_QUAD_WR: beginlink	<= 4'b1111;enddefault: beginlink	<= 4'h0;endendcase
end//FLASH_IO_OBUF
always @(negedge clk) begin		//在SCK下降沿转换数据case(state)S_COMMAND: beginFLASH_IO_OBUF[0]	<= instruction[3'd7-cnt];	//首先移出MSBFLASH_IO_OBUF[3:1]	<= 3'b111;endS_ADDR: begincase(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;endcaseFLASH_IO_OBUF[3:1]	<= 3'b111;endS_QUAD_WR: begincase(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;endcaseenddefault: beginFLASH_IO_OBUF	<= 4'hf;endendcase
end//data_rden
always @(posedge clk) begincase(state)S_QUAD_WR: begindata_rden	<= 1'b1;enddefault: begindata_rden	<= 1'b0;endendcase
end//busy
always @(*) begincase(state)S_IDLE: beginbusy	<= 1'b0;enddefault: beginbusy	<= 1'b1;endendcase
endendmodule
  • 读 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=8localparam	instruction		= 8'h6C;	//4QOR的指令码为 0x6Creg		FLASH_nCS	= 1'b1;
assign	FLASH_SCK	= FLASH_nCS? 1'b1 : clk;	//SPI mode 3reg		[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) beginread_start_d0	<= read_start;read_start_d1	<= read_start_d0;
endassign	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) beginif(~rst_n) beginstate	<= S_IDLE;endelse beginstate	<= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(read_start_pe) beginnext_state	<= S_COMMAND;endelse beginnext_state	<= S_IDLE;endendS_COMMAND: beginif(cnt >= 3'd7) beginnext_state	<= S_ADDR;endelse beginnext_state	<= S_COMMAND;endendS_ADDR: beginif(cnt >= 3'd7 && cnt_Byte >= 4'd3) begincase(LC)		//根据LC判断Dummy的长度2'b11: beginnext_state	<= S_QUAD_RD;end2'b00, 2'b01, 2'b10: beginnext_state	<= S_DUMMY;enddefault: ;endcaseendelse beginnext_state	<= S_ADDR;endendS_DUMMY: beginif(cnt >= 3'd7) beginnext_state	<= S_QUAD_RD;endelse beginnext_state	<= S_DUMMY;endendS_QUAD_RD: beginif(cnt >= 3'd4 && (Byte_Len == 32'd0 || cnt_Byte >= Byte_Len - 1'b1)) begin	//Len=0时视作Len=1next_state	<= S_STOP;endelse beginnext_state	<= S_QUAD_RD;endendS_STOP: beginif(cnt>=1) begin	//维持在STOP两个clk,以保持data和wren保持一个wr_clknext_state	<= S_IDLE;endelse beginnext_state	<= S_STOP;endenddefault: beginnext_state	<= S_IDLE;endendcase
end//FLASH_nCS
always @(negedge clk) begincase(state)S_COMMAND, S_ADDR, S_DUMMY, S_QUAD_RD: beginFLASH_nCS	<= 1'b0;enddefault: beginFLASH_nCS	<= 1'b1;endendcase
end//cnt
always @(posedge clk) begincase(state)S_IDLE: begincnt		<= 3'd0;endS_COMMAND, S_ADDR: beginif(~FLASH_nCS) begincnt		<= cnt + 1'b1;endelse begincnt		<= 3'd0;endendS_DUMMY: beginif(cnt >= 3'd7) begin	//这里设置Bummy长度;由于4QOR只有0/8的Dummy长度,因该case实际可以和上面合并cnt		<= 3'd0;endelse begincnt		<= cnt + 1'b1;endendS_QUAD_RD: begincnt		<= cnt + 3'd4;	//Quad RD 阶段一次读回4bitendS_STOP: begincnt		<= 3'd1;enddefault: begincnt		<= cnt;endendcase
end//cnt_Byte
always @(posedge clk) begincase(state)S_ADDR: beginif(cnt==3'd7) beginif(cnt_Byte >= 32'd3) begincnt_Byte	<= 32'd0;endelse begincnt_Byte	<= cnt_Byte + 1'b1;endendelse begincnt_Byte	<= cnt_Byte;endendS_DUMMY: begincnt_Byte		<= 32'd0;endS_QUAD_RD: beginif(cnt==3'd4) begincnt_Byte	<= cnt_Byte + 1'b1;endelse begincnt_Byte	<= cnt_Byte;endenddefault: begincnt_Byte		<= 32'd0;endendcase
end//link
always @(negedge clk) begincase(state)S_COMMAND, S_ADDR: beginlink	<= 4'b1101;endS_DUMMY, S_QUAD_RD: begin	//为防止主控端与flash端的驱动器冲突,Dummy期间主控端应释放总线link	<= 4'b0000;enddefault: beginlink	<= 4'h0;endendcase
end//FLASH_IO_OBUF
always @(negedge clk) begin		//在SCK下降沿转换数据case(state)S_COMMAND: beginFLASH_IO_OBUF[0]	<= instruction[3'd7-cnt];	//首先移出MSBFLASH_IO_OBUF[3:1]	<= 3'b111;endS_ADDR: begincase(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;endcaseFLASH_IO_OBUF[3:1]	<= 3'b111;enddefault: beginFLASH_IO_OBUF	<= 4'hf;endendcase
end//data_tmp
reg		[7:0]	data_tmp;
always @(posedge clk) begin	//须在SCK上升沿锁存数据case(state)S_QUAD_RD: begincase(cnt)3'd0: begindata_tmp[7:4]	<= FLASH_IO_IBUF;end3'd4: begindata_tmp[3:0]	<= FLASH_IO_IBUF;enddefault: begindata_tmp	<= data_tmp;endendcaseenddefault: begindata_tmp	<= data_tmp;endendcase
end//data_wren & data
reg				data_wren_buf;
reg		[7:0]	data_buf;
always @(posedge clk) begincase(state)S_QUAD_RD: beginif(cnt==0 && cnt_Byte>=1) begindata_wren_buf	<= 1'b1;data_buf		<= data_tmp;endelse begindata_wren_buf	<= data_wren_buf;data_buf		<= data_buf;endendS_STOP: begin		//S_STOP时锁存输出最后一个数据if(cnt==0) begindata_wren_buf	<= 1'b1;data_buf		<= data_tmp;endelse begindata_wren_buf	<= data_wren_buf;data_buf		<= data_buf;endenddefault: begindata_wren_buf	<= 1'b0;data_buf		<= 8'd0;endendcase
endalways @(posedge data_wr_clk) begin		//同步到data_wr_clk时钟域data_wren	<= data_wren_buf;data		<= data_buf;
end//busy
always @(*) begincase(state)S_IDLE: beginbusy	<= 1'b0;enddefault: beginbusy	<= 1'b1;endendcase
endendmodule

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 Programminginput	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;		//读CR1localparam	I_RDABR	= 8'h14;		//读Autoboot Register
localparam	I_WRABR	= 8'h15;		//写ABRlocalparam	I_RDBAR	= 8'h16;		//读Bank Address Register
localparam	I_WRBAR	= 8'h17;		//写BARlocalparam	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;
generatefor(i=0; i<4; i=i+1) beginIOBUF 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
endgenerateassign	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 @(*) begincase(module_arb)M_NONE: beginsubmodule_busy	<= 1'b0;FLASH_SCK		<= 1'b1;FLASH_nCS		<= 1'b1;link			<= 4'h0;FLASH_IO_OBUF	<= 4'hf;endM_instruction: beginsubmodule_busy	<= busy_1;FLASH_SCK		<= FLASH_SCK_1;FLASH_nCS		<= FLASH_nCS_1;link			<= link1;FLASH_IO_OBUF	<= FLASH_IO_OBUF1;endM_WRR: beginsubmodule_busy	<= busy_2;FLASH_SCK		<= FLASH_SCK_2;FLASH_nCS		<= FLASH_nCS_2;link			<= link2;FLASH_IO_OBUF	<= FLASH_IO_OBUF2;endM_RDR: beginsubmodule_busy	<= busy_3;FLASH_SCK		<= FLASH_SCK_3;FLASH_nCS		<= FLASH_nCS_3;link			<= link3;FLASH_IO_OBUF	<= FLASH_IO_OBUF3;endM_PP: beginsubmodule_busy	<= busy_4;FLASH_SCK		<= FLASH_SCK_4;FLASH_nCS		<= FLASH_nCS_4;link			<= link4;FLASH_IO_OBUF	<= FLASH_IO_OBUF4;endM_4QOR: beginsubmodule_busy	<= busy_5;FLASH_SCK		<= FLASH_SCK_5;FLASH_nCS		<= FLASH_nCS_5;link			<= link5;FLASH_IO_OBUF	<= FLASH_IO_OBUF5;enddefault: beginsubmodule_busy	<= 1'b0;FLASH_SCK		<= 1'b1;FLASH_nCS		<= 1'b1;link			<= 4'h0;FLASH_IO_OBUF	<= 4'hf;endendcase
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 Eraselocalparam	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 Eraselocalparam	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 Registerlocalparam	S_4QPP		= 24'h040000;		//Page Programming
localparam	S_4QOR		= 24'h080000;		//Quad Output Readlocalparam	S_RESET		= 24'h100000;		//flash software resetreg		[23:0]	state	= S_IDLE;
reg		[23:0]	next_state;always @(posedge clk or negedge rst_n) beginif(~rst_n) beginstate	<= S_IDLE;endelse beginstate	<= next_state;end
endwire	[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) beginall_req_buf		<= all_req;
endalways @(*) begincase(state)S_IDLE: beginnext_state	<= S_ARB;endS_ARB: begincasex(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;endcaseendS_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: beginif(submodule_busy) beginnext_state	<= S_WAIT;endelse beginnext_state	<= state;endendS_WAIT: beginif(~submodule_busy) beginnext_state	<= S_STOP;endelse beginnext_state	<= S_WAIT;endendS_STOP: beginnext_state	<= S_IDLE;enddefault: beginnext_state	<= S_IDLE;endendcase
endreg		[3:0]	update_register	= 4'd0;		//在RD REG操作中判断要更新哪一个Reg
//1:SR1, 2:CR1, 3:SR2, 4:BAR, 5:ABRalways @(posedge clk) begincase(state)S_IDLE: beginmodule_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;endS_ARB: beginmodule_arb			<= M_NONE;start_1				<= 1'b0;start_2				<= 1'b0;start_3				<= 1'b0;start_4				<= 1'b0;start_5				<= 1'b0;endS_RESET: beginmodule_arb			<= M_instruction;start_1				<= 1'b1;instruction_1		<= I_RESET;endS_WREN: beginmodule_arb			<= M_instruction;start_1				<= 1'b1;instruction_1		<= I_WREN;endS_WRDI: beginmodule_arb			<= M_instruction;start_1				<= 1'b1;instruction_1		<= I_WRDI;endS_CLSR: beginmodule_arb			<= M_instruction;start_1				<= 1'b1;instruction_1		<= I_CLSR;endS_BE: beginmodule_arb			<= M_instruction;start_1				<= 1'b1;instruction_1		<= I_BE;endS_SE: beginmodule_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];endS_RDSR1: beginmodule_arb			<= M_RDR;start_3				<= 1'b1;instruction_3		<= I_RDSR1;Register_Len_RDR	<= 4'd1;update_register		<= 4'd1;endS_RDCR1: beginmodule_arb			<= M_RDR;start_3				<= 1'b1;instruction_3		<= I_RDCR1;Register_Len_RDR	<= 4'd1;update_register		<= 4'd2;endS_RDSR2: beginmodule_arb			<= M_RDR;start_3				<= 1'b1;instruction_3		<= I_RDSR2;Register_Len_RDR	<= 4'd1;update_register		<= 4'd3;endS_RDBAR: beginmodule_arb			<= M_RDR;start_3				<= 1'b1;instruction_3		<= I_RDBAR;Register_Len_RDR	<= 4'd1;update_register		<= 4'd4;endS_RDABR: beginmodule_arb			<= M_RDR;start_3				<= 1'b1;instruction_3		<= I_RDABR;Register_Len_RDR	<= 4'd4;update_register		<= 4'd5;endS_WRSR1: beginmodule_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;endS_WRCR1: beginmodule_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;endS_WRBAR: beginmodule_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;endS_WRABR: beginmodule_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];endS_4QPP: beginmodule_arb		<= M_PP;start_4			<= 1'b1;addr_PP			<= WR_addr;Byte_Len_PP		<= WR_Byte_Len;endS_4QOR: beginmodule_arb		<= M_4QOR;start_5			<= 1'b1;addr_4QOR		<= RD_addr;Byte_Len_4QOR	<= RD_Byte_Len;endS_WAIT: beginstart_1			<= 1'b0;start_2			<= 1'b0;start_3			<= 1'b0;start_4			<= 1'b0;start_5			<= 1'b0;endS_STOP: beginmodule_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: ;endcaseenddefault: beginmodule_arb		<= M_NONE;start_1			<= 1'b0;start_2			<= 1'b0;start_3			<= 1'b0;start_4			<= 1'b0;start_5			<= 1'b0;endendcase
endalways @(*) begincase(state)S_IDLE, S_ARB: beginbusy	<= 1'b0;enddefault: beginbusy	<= 1'b1;endendcase
endendmodule

测试

  编写测试代码如下,并下载到板子进行测试(注意,我的板子上的 FLASH 的 QUAD bit(CR1[1])已经被置位了,所以这里只执行了擦除、写入、读取流程,如果你的不是,需要多加一个 WRR 步骤)

// FLASH 测试(主存读写测试)
`default_nettype none
module test_flash_mainMemory(
input	wire			clk_sys,	//OXCO_10Moutput	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 Programmingreg		[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) beginPPS_d0		<= clk_1k;PPS_d1		<= PPS_d0;if(PPS_pe) beginif(cnt==1 || cnt==11) beginif(SR1_rd[1]) begin		//检查WELcnt		<= cnt + 1'b1;endelse begincnt		<= cnt;endendelse if(cnt==3 || cnt==13) beginif(~SR1_rd[0]) begin	//检查WIPcnt		<= cnt + 1'b1;endelse begincnt		<= cnt;endendelse begincnt		<= cnt + 1'b1;endendPPS_pe_d1	<= PPS_pe;PPS_pe_d2	<= PPS_pe_d1;
endlocalparam	WR_RD_ADDR	= 32'h0100_0000;reg		[7:0]	data_PP_tmp	= 8'd0;
always @(posedge data_rd_clk) beginif(data_rden) begindata_PP_tmp		<= data_PP_tmp + 1'b1;endelse begindata_PP_tmp		<= data_PP_tmp;end
endalways @(posedge clk_100M) begincase(cnt)//---------------erase-------------------------8'd0: WREN_req		<= PPS_pe_d2;8'd1: rd_SR1_req	<= PPS_pe_d2;8'd2: beginsector_erase_req	<= PPS_pe_d2;sector_erase_addr	<= WR_RD_ADDR;end8'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: beginWR_req			<= PPS_pe_d2;WR_addr			<= WR_RD_ADDR;WR_Byte_Len		<= 10'd16;data_PP			<= data_PP_tmp;end8'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: beginRD_req			<= PPS_pe_d2;RD_addr			<= WR_RD_ADDR;RD_Byte_Len		<= 10'd16;enddefault: ;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.hqwc.cn/news/219305.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言-内存函数详解

文章目录 1. memcpy使用和模拟实现2. memmove使用和模拟实现3. memset函数的使用4. memcmp函数的使用 1. memcpy使用和模拟实现 返回类型和参数&#xff1a; void * memcpy ( void * destination, const void * source, size_t num );1.函数memcpy从source的位置开始向后复制…

***Linux常用命令及解释

1、查看Linux的版本信息 1.1、uname -a 1.2、cat /etc/issue 1.3、cat /proc/version 1.4、hostnamectl 通过使用hostnamectl命令&#xff0c;可以查询和更改系统主机名&#xff0c;并且还可以查看Linux的发行版和内核版本。 2、删除文件 3、修改目录权限 4、解压文件 5、…

stm32 42步进电机 上位机示例

脉冲到底是个啥东西&#xff1f;步进电机一直说发脉冲 步进电机通过接收脉冲信号来实现精确的位置控制。脉冲是一种短暂的电信号&#xff0c;它的变化可以触发步进电机转动一定的角度或步进。步进电机控制系统会根据输入的脉冲信号来精确定位和控制步进电机的转动&#xff0c;每…

HTTP状态码:404 Not Found错误之谜

文章目录 HTTP 404 Not Found错误 404出现形式导致 HTTP 404 错误的原因&#xff1f;推荐阅读 HTTP 404 Not Found 错误 404&#xff0c;也称为“HTTP 404 Not Found”&#xff0c;是当无法找到所请求的资源时 Web 服务器返回的HTTP 状态代码。 简单来说&#xff0c;这意味着…

【Spring篇】spring核心——AOP面向切面编程

目录 想要彻底理解AOP&#xff0c;我觉得你的先要了解框架的模块化思想&#xff0c;为此先记录框架在讲AOP 什么是java框架&#xff1f;为什么要出现框架&#xff1f; 我总结以下七点来讲述和帮助理解java框架思想 什么是AOP&#xff1f; 如何理解上面这句话呢&#xff1…

提示工程-Prompt Engineering

提示工程 提示工程 1、概述 Prompt Engineering&#xff1a; 提示工程 通过自然语言&#xff08;英语、汉语等&#xff09;来给AI下达指示&#xff0c;从而让AI完成你指定给他的工作的过程都可以称之为提示工程。&#xff08;面向自然语言编程&#xff09; 提示词要素 指令&…

华硕V4050E笔记本安装Win10不识别硬盘解决方法

笔记本硬件参数&#xff1a; ASUS VivoBook14 V4050E 型 号 V4050EP1135-0DAKXQ2X10 制造日期 2020-12 12M C P U 11th Gen Intel(R)Core(TM)i5-1135G72.4GHz 2.42GHz 4核心 8线程 L2&#xff1a;5MB L3&#xff1a;8MB 内 存 16.0GB &#xff08;8Gb X2 320…

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

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

BeanUtil的正确使用方式

shigen日更文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 在实际的开发中&#xff0c;我们常常会用到工具类去拷贝对象的属性&#xff0c;将一个对象的属性转换成另外一个…

【21年扬大真题】编写程序,通过指针p的改变,实现一维数组的输入及逆序输出

【21年扬大真题】编写程序&#xff0c;通过指针p的改变&#xff0c;实现一维数组的输入及逆序输出 例如&#xff0c;输入为1,2,3,4,5,6,7&#xff1b; 输出为7,6,5,4,3,2,1 法一&#xff1a;不改变原数组&#xff0c;仅逆序打印输出 #define _CRT_SECURE_NO_WARNINGS #includ…

Linux服务器SSH客户端断开后保持程序继续运行的方法

目录 1. nohup 命令&#xff1a; 2. tmux 或 screen&#xff1a; 3 final shell 断开后服务器如何继续执行令&#xff1f; 方法一&#xff1a;使用 nohup 命令 方法二&#xff1a;将命令放在后台执行 4 你可以使用 jobs 命令查看当前终端中正在后台运行的任务 &#xff…

rsyslog出现Unit rsyslog.service is masked不可用问题解决

博主在测试将日志发送到日志服务器的功能时遇到了rsyslog服务不可用的问题&#xff0c;具体来说&#xff0c;就是执行systemctl restart rsyslog或者 service rsyslog restart命令时&#xff0c;出现了标题中所述的Unit rsyslog.service is masked问题。网上查找了很多资料&…