手写一个SPI FLASH 读写擦除控制器


开始之前需要先了解一下SPI通信协议的特点: spi协议

本篇内容要实现FPGA芯片通过SPI总线与flash芯片进行通信,连接如下图所示:
在这里插入图片描述
信号线解释:

  1. Cs_n:片选线,为 FPGA 的输出信号,Flash 的输入信号,低电平有效;
  2. Sck:时钟线,为 FPGA 的输出信号,Flash 的输入信号,Flash 根据该信 号的上升沿锁存 sdi 发来的数据,根据该信号的下降沿通过 sdo 输出数据;
  3. sdi:mosi,数据线,为 FPGA 的输出信号,Flash 的输入信号,FPGA 可通过 sdi总线串行的传输数据到 Flash;
  4. sdo:miso,数据线,为 FPGA 的输入信号,Flash 的输出信号,Flash 可通过 sdo总线串行的传输数据到 FPGA。

flash读写数据的特点

这里以 M25p16 型号的 flash 为例进行介绍。
M25p16 最大的时钟频率为 50Mhz,在此我们进行擦除时采用 12.5Mhz 的时钟,即 sck 的时钟频率为 12.5Mhz。该款 Flash 芯片的存储空间16Mbit,每个地址内存储 1byte(8bit)的数据,共 2M 的存储深度,其地址分为 32 扇区(sector)、每个扇区包含 256 页(page)、每一页包含 256字节(byte),因此该 Flash 芯片需要用到 21 位地址线,加上扩展的 3bit 地址线
一共用到== 24bit 地址线==,具体如下图 3所示。
在这里插入图片描述

1. 扇擦除SE(Sector Erase)

通过前面的项目我们知道,ram存储器的特点是:,在同一个地址写入新的数据时,旧数据会被覆盖。而flash则不同,在对 Flash 进行写入数据时,首先需要将 Flash内部现有的数据进行擦除,Flash 的擦除分为扇区擦除(sector erase)和全擦除(bulk erase)。下面将以扇区擦除为例进行讲解.
在进行 SE 之前需要给出一个写使能指令
(WREN),然后进行扇区擦除,WREN的指令为8’h06。SE时序图如下:
在这里插入图片描述

注意:

  1. 在 WREN 结束后,cs_n 需要被拉高,为了确保 WREN 被 Flash 存储,因此 cs_n 拉高的时间至少需要 100ns(官方文档虽然没写拉高 100ns 但是为了稳定一定要 拉高 cs_n),之后再次拉低 cs_n 为发送 SE 指令做准备。
  2. 由于是扇区擦除,因此给出的地址位只有高 8bit有效,低 16bit 无论是何值都对该扇区的擦除没有影响。

1.1 flash_se 模块设计

1.1.1 信号连接示意图:

在这里插入图片描述

1.1.2 SE状态机

在这里插入图片描述

1.1.3 波形图设计:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.1.4 代码

module  flash_se(
	input	wire 		sclk,
	input	wire 		rst,
	input	wire 		se_flag,
	input	wire [23:0]	se_addr,
	output	wire 		sck,
	output	wire 		cs_n,
	output	wire 		sdi
	);

parameter	IDLE 	= 4'b0001;
parameter	WE 		= 4'b0010;
parameter	DELAY	= 4'b0100;
parameter	SE 		= 4'b1000;

parameter	DIV_END_NUM = 4-1;
parameter 	DELAY_END =16-1;
parameter	WRITE_EN =8'h06;
parameter   SECTOR_E =8'hD8;

reg 	[3:0]	state;
reg 	[7:0]	div_cnt;
reg 	[7:0]	bit_cnt;
reg 	[7:0]	delay_cnt;
reg 			cs_n_r;
reg 			sck_r;
reg 			sdi_r;
reg 	[7:0]	we_inst_shift;
reg 	[31:0]	se_inst_shift;

assign sck = sck_r;
assign cs_n = cs_n_r;
assign sdi = sdi_r;
always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		state <= IDLE;
	end
	else case (state)
		IDLE : if(se_flag == 1'b1 )
				state <= WE;
		WE 	 : if(div_cnt == DIV_END_NUM && bit_cnt == 'd8)
				state <= DELAY;
		DELAY: if (delay_cnt == DELAY_END)
				state <= SE;
		SE   : if(div_cnt == DIV_END_NUM && bit_cnt == 'd32)
				state <= IDLE;
		default : state <= IDLE;
		endcase
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		div_cnt <= 'd0;
	end
	else if(div_cnt == DIV_END_NUM) begin
		div_cnt <='d0;
	end
	else if (state == WE || state == SE) begin
		div_cnt <= div_cnt + 1'b1;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		bit_cnt <= 'd0;
	end
	else if (state == WE && bit_cnt == 'd8 && div_cnt == DIV_END_NUM) begin
		bit_cnt <='d0;
	end
	else if (state == WE && div_cnt == DIV_END_NUM) begin
		bit_cnt <= bit_cnt + 1'b1;
	end
	else if (state == SE && bit_cnt == 'd32 && div_cnt == DIV_END_NUM) begin
		bit_cnt <='d0;
	end
	else if (state == SE && div_cnt == DIV_END_NUM) begin
		bit_cnt <= bit_cnt + 1'b1;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		delay_cnt <='d0;
	end
	else if (state == DELAY) begin
		delay_cnt <= delay_cnt + 1'b1;
	end
	else begin
		delay_cnt <= 'd0;
	end
end


always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		cs_n_r <= 1'b1;
	end
	else if(state == WE && div_cnt == DIV_END_NUM && bit_cnt == 'd8) begin
		cs_n_r <= 1'b1;
	end
	else if (state == IDLE && se_flag == 1'b1) begin
		cs_n_r <= 1'b0;
	end
	else if(state == SE && div_cnt == DIV_END_NUM && bit_cnt == 'd32) begin
		cs_n_r <= 1'b1;
	end
	else if(state == DELAY && delay_cnt == DELAY_END) begin
		cs_n_r <= 1'b0;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		sck_r <= 1'b0;
	end
	else if (state == WE && div_cnt == (DIV_END_NUM >>1) && bit_cnt != 'd8) begin
		sck_r <= 1'b1;
	end
	else if (state == WE && div_cnt == (DIV_END_NUM ) && bit_cnt != 'd8) begin
		sck_r <= 1'b0;
	end
	else if (state == SE && div_cnt == (DIV_END_NUM >>1) && bit_cnt != 'd32) begin
		sck_r <= 1'b1;
	end
	else if (state == SE && div_cnt == (DIV_END_NUM ) && bit_cnt != 'd32) begin
		sck_r <= 1'b0;
	end
end


always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		we_inst_shift <= 'd0;
	end
	else if (se_flag == 1'b1) begin
		we_inst_shift <= WRITE_EN;
	end
	else if(state == WE && div_cnt == DIV_END_NUM) begin
		we_inst_shift <= {we_inst_shift[6:0],1'b0};
	end
end


always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		se_inst_shift <= 'd0;
	end
	else if (se_flag == 1'b1 && state == IDLE) begin
		se_inst_shift <= {SECTOR_E,se_addr};
	end
	else if (state == SE && div_cnt == DIV_END_NUM) begin
		se_inst_shift <= {se_inst_shift[30:0],1'b0};
	end
end

always @*  begin
	if (state == WE) begin
		sdi_r <= we_inst_shift[7];
	end
	else if(state == SE) begin
		sdi_r <= se_inst_shift[31];
	end
	else begin
		sdi_r <= 1'b0;
	end
end


endmodule

2. 页写PP(Page Program)

Flash 页写功能,简称 PP,Page Program 命令 8’h02。同样与擦除指令一样,需要再发送 PP 指令之前,执行 Write EN 操作,当执行完 PP 后 Write EN 会自动复位,所以每次 PP 之前必须发送 Write EN 操作。PP时序图如下:
在这里插入图片描述

2.1 flash_pp模块设计

说明:这里:这里pp进去的256byte数据是提前存在fifo的,因此开始向flash里pp数据的时候需要产生读fifo使能(rd_en)。

Fifo的位宽设置为8,fifo一次读出8bit数据,而sdi传输1bit数据,所以需要考虑fifo读使能拉高的时机以及读出的数据移位的时机

2.1.1 信号连接示意图:

在这里插入图片描述

2.1.2 PP状态机

在这里插入图片描述

2.1.3 波形图设计(WE部分和DELAY部分和上述一致)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
rd_en和rd_data数据移位实现并转串细节:
在这里插入图片描述

2.1.4 代码

module flash_pp(	
	input	wire 		sclk,
	input	wire 		rst,
	input	wire 		pp_flag,
	input	wire [23:0]	pp_addr,
	input	wire 		wr_en,
	input	wire [7:0]	wr_data,
	output	wire 		sck,
	output	wire 		cs_n,
	output	wire 		sdi
	);

parameter	IDLE 	= 4'b0001;
parameter	WE 		= 4'b0010;
parameter	DELAY	= 4'b0100;
parameter	PP 		= 4'b1000;

parameter	DIV_END_NUM = 4-1;
parameter 	DELAY_END =16-1;
parameter	WRITE_EN =8'h06;
parameter   PAGE_P =8'h02;

reg 	[3:0]	state;
reg 	[7:0]	div_cnt;
reg 	[11:0]	bit_cnt;
reg 	[7:0]	delay_cnt;
reg 			cs_n_r;
reg 			sck_r;
reg 			sdi_r;
reg 	[7:0]	we_inst_shift;
reg 	[31:0]	pp_inst_shift;
reg 			read_fifo_en;
reg 			pp_shift_flag;
wire 	[7:0]	read_fifo_data;

assign sck = sck_r;
assign cs_n = cs_n_r;
assign sdi = sdi_r;
always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		state <= IDLE;
	end
	else case (state)
		IDLE : if(pp_flag == 1'b1 )
				state <= WE;
		WE 	 : if(div_cnt == DIV_END_NUM && bit_cnt == 'd8)
				state <= DELAY;
		DELAY: if (delay_cnt == DELAY_END)
				state <= PP;
		PP   : if(div_cnt == DIV_END_NUM && bit_cnt == 'd2080)
				state <= IDLE;
		default : state <= IDLE;
		endcase
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		div_cnt <= 'd0;
	end
	else if(div_cnt == DIV_END_NUM) begin
		div_cnt <='d0;
	end
	else if (state == WE || state == PP) begin
		div_cnt <= div_cnt + 1'b1;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		bit_cnt <= 'd0;
	end
	else if (state == WE && bit_cnt == 'd8 && div_cnt == DIV_END_NUM) begin
		bit_cnt <='d0;
	end
	else if (state == WE && div_cnt == DIV_END_NUM) begin
		bit_cnt <= bit_cnt + 1'b1;
	end
	else if (state == PP && bit_cnt == 'd2080 && div_cnt == DIV_END_NUM) begin
		bit_cnt <='d0;
	end
	else if (state == PP && div_cnt == DIV_END_NUM) begin
		bit_cnt <= bit_cnt + 1'b1;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		delay_cnt <='d0;
	end
	else if (state == DELAY) begin
		delay_cnt <= delay_cnt + 1'b1;
	end
	else begin
		delay_cnt <= 'd0;
	end
end


always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		cs_n_r <= 1'b1;
	end
	else if(state == WE && div_cnt == DIV_END_NUM && bit_cnt == 'd8) begin
		cs_n_r <= 1'b1;
	end
	else if (state == IDLE && pp_flag == 1'b1) begin
		cs_n_r <= 1'b0;
	end
	else if(state == PP && div_cnt == DIV_END_NUM && bit_cnt == 'd2080) begin
		cs_n_r <= 1'b1;
	end
	else if(state == DELAY && delay_cnt == DELAY_END) begin
		cs_n_r <= 1'b0;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		sck_r <= 1'b0;
	end
	else if (state == WE && div_cnt == (DIV_END_NUM >>1) && bit_cnt != 'd8) begin
		sck_r <= 1'b1;
	end
	else if (state == WE && div_cnt == (DIV_END_NUM ) && bit_cnt != 'd8) begin
		sck_r <= 1'b0;
	end
	else if (state == PP && div_cnt == (DIV_END_NUM >>1) && bit_cnt != 'd2080) begin
		sck_r <= 1'b1;
	end
	else if (state == PP && div_cnt == (DIV_END_NUM ) && bit_cnt != 'd2080) begin
		sck_r <= 1'b0;
	end
end


always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		we_inst_shift <= 'd0;
	end
	else if (pp_flag == 1'b1) begin
		we_inst_shift <= WRITE_EN;
	end
	else if(state == WE && div_cnt == DIV_END_NUM) begin
		we_inst_shift <= {we_inst_shift[6:0],1'b0};
	end
end


always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		pp_inst_shift <= 'd0;
	end
	else if (pp_flag == 1'b1 && state == IDLE) begin
		pp_inst_shift <= {PAGE_P,pp_addr};
	end
	else if (state == PP && div_cnt == DIV_END_NUM && bit_cnt < 'd31) begin
		pp_inst_shift <= {pp_inst_shift[30:0],1'b0};
	end
	else if (read_fifo_en == 1'b1) begin
		pp_inst_shift <= {read_fifo_data,24'd0};
	end
	else if (pp_shift_flag == 1'b1 ) begin
		pp_inst_shift <= {pp_inst_shift[30:24],25'd0};
	end
end

always @* begin
	if (bit_cnt >='d31 && bit_cnt <'d2079 && div_cnt == 'd3 && state == PP && bit_cnt[2:0]==3'b111) begin
		read_fifo_en <= 1'b1;
	end
	else begin
		read_fifo_en <= 1'b0;
	end
end

always @* begin
	if (bit_cnt >='d31 && bit_cnt <'d2079 && div_cnt == 'd3 && state == PP && bit_cnt[2:0]!=3'b111) begin
		pp_shift_flag <= 1'b1;
	end
	else begin
		pp_shift_flag <= 1'b0;
	end
end

always @*  begin
	if (state == WE) begin
		sdi_r <= we_inst_shift[7];
	end
	else if(state == PP) begin
		sdi_r <= pp_inst_shift[31];
	end
	else begin
		sdi_r <= 1'b0;
	end
end


fifo_1024x8 fifo_1024x8_inst (
  .clk(sclk), // input clk
  .din(wr_data), // input [7 : 0] din
  .wr_en(wr_en), // input wr_en
  .rd_en(read_fifo_en), // input rd_en
  .dout(read_fifo_data), // output [7 : 0] dout
  .full(full), // output full
  .empty(empty) // output empty
);
endmodule 

3.读flash操作

Read data byte 读数据字节指令,简称 RDB 指令,指令码为 8’h03,读指令可以读取 Flash 地址空间的任何一个byte 数据,不受扇区,页的限制,而且可以连续读取出所有数据。
读数据的必要条件:给出读命令码,然后紧跟 24bit 的起始地址,之后会在 SDO 总线串行输出读出数据,直到 Cs_n 信号拉高中断读出数据操作,否则地址在 flash 内部递增读出数据。
flash_rd操作时序图:
在这里插入图片描述

3.1 flash_rd模块设计

读控制器要能满足以下功能:
给定读起始地址和需要读取的数据长度,控制器自动启动读 flash, 并把读出数据缓存到 fifo 中,以供其他模块读取调用。

3.1.1 信号连接示意图

在这里插入图片描述

3.1.2 波形图设计

sdo是1bit ,而fifo的wr_data为8bit,因此要合理设计sdo的移位标志,与wr_en的标志,实现串转并写入fifo中。
在这里插入图片描述

3.1.3 代码

module flash_rd(
	input	wire 		sclk,
	input	wire 		rst,
	input	wire 		rd_flag,
	input	wire [23:0]	rd_addr,
	input	wire [10:0]	rd_len,
	output	wire 		rd_ready,
	input	wire		rd_en,
	output	wire [7:0]	rd_data,
	output	wire 		sck,
	output	wire 		cs_n,
	input	wire 		sdo,
	output	wire 		sdi
	);

parameter	DIV_END_NUM = 4-1;
parameter	READ_P = 8'h03;

reg	[15:0]	bit_cnt_end;
reg 		cs_n_r,sdi_r,sck_r;
reg [7:0]	div_cnt;
reg [15:0]	bit_cnt;
reg [31:0]	rd_inst_shift;
reg 		shift_flag;
reg 		wr_en;
reg [7:0]	sdo_data_shift;
reg 		flash_read_end;
wire 		full,empty;


assign sdi = sdi_r;
assign sck = sck_r;
assign cs_n = cs_n_r;

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		bit_cnt_end <='d0;
	end
	else if (rd_flag == 1'b1) begin
		bit_cnt_end <= 32 + (rd_len<<3);
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		cs_n_r <= 1'b1;
	end
	else if (div_cnt == DIV_END_NUM && bit_cnt == bit_cnt_end) begin
		cs_n_r <= 1'b1;
	end
	else if (rd_flag == 1'b1) begin
		cs_n_r <= 1'b0;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		div_cnt <='d0;
	end
	else if(div_cnt == DIV_END_NUM) begin
		div_cnt <= 'd0;
	end
	else if (cs_n_r == 1'b0) begin
		div_cnt <= div_cnt + 1'b1;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		bit_cnt <='d0;
	end
	else if (cs_n_r == 1'b0 && div_cnt == DIV_END_NUM && bit_cnt == bit_cnt_end) begin
		bit_cnt <= 'd0;
	end
	else if (cs_n_r == 1'b0 && div_cnt == DIV_END_NUM) begin
		bit_cnt <= bit_cnt + 1'b1;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		sck_r <= 1'b0;
	end
	else if (cs_n_r == 1'b0 && div_cnt == (DIV_END_NUM>>1) && bit_cnt != bit_cnt_end) begin
		sck_r <= 1'b1;
	end
	else if (cs_n_r == 1'b0 && div_cnt == DIV_END_NUM && bit_cnt != bit_cnt_end) begin
		sck_r <= 1'b0;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		rd_inst_shift <='d0;
	end
	else if (rd_flag == 1'b1 && cs_n_r == 1'b1) begin
		rd_inst_shift <= {READ_P,rd_addr};
	end
	else if (cs_n_r == 1'b0 && bit_cnt < 31 && div_cnt== DIV_END_NUM) begin
		rd_inst_shift <= {rd_inst_shift[30:0],1'b0};
	end
end

always @*
	sdi_r <= rd_inst_shift[31];

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		shift_flag <= 1'b0;
	end
	else if (bit_cnt >31 && bit_cnt != bit_cnt_end && div_cnt == (DIV_END_NUM>>1)) begin
		shift_flag <= 1'b1;
	end
	else begin
		shift_flag <= 1'b0;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		wr_en <= 1'b0;
	end
	else if (bit_cnt > 31 && bit_cnt != bit_cnt_end && div_cnt == ((DIV_END_NUM >>1)+1) && bit_cnt[2:0] == 3'b111) begin
		wr_en <= 1'b1;
	end
	else begin
		wr_en <= 1'b0;
	end

end


always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		sdo_data_shift <= 'd0;
	end
	else if (shift_flag == 1'b1) begin
		sdo_data_shift <= {sdo_data_shift[6:0],sdo};
	end
end


always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		flash_read_end <= 1'b0;
	end
	else if(flash_read_end == 1'b1 && empty == 1'b1)begin
		flash_read_end <= 1'b0;
	end
	else if (cs_n_r == 1'b0 && bit_cnt == bit_cnt_end && div_cnt == DIV_END_NUM) begin
		flash_read_end <= 1'b1;
	end
end

assign  rd_ready = flash_read_end & (~empty);


fifo_1024x8 fifo_1024x8_inst (
  .clk(sclk), // input clk
  .din(sdo_data_shift), // input [7 : 0] din
  .wr_en(wr_en), // input wr_en
  .rd_en(rd_en), // input rd_en
  .dout(rd_data), // output [7 : 0] dout
  .full(full), // output full
  .empty(empty) // output empty
);

endmodule 

4.模块整合

到目前为止,我们分别完成了flash_se、flash_pp、flash_rd三个对flash进行操作的模块,如何将他们整合在一起,实现:
我们通过串口给 fpga 发送指令,fpga 解析指令之后,转化为对 flash_se、flash_pp、flash_rd 的控制时序(即fpga能根据接收到的指令产生控制对应模块需要的输入信号),实现对 flash 的读or写or擦除操作。

4.1 state_ctrl模块设计

既然要与上位机串口交互,我们必须制定一个协议,我们制定三种数据包类型,擦除命令包、写命令包、读命令包,当fpga接收到对应的命令包后可以跳转到对应模块。
在这里插入图片描述

4.1.1 信号连接示意图

在这里插入图片描述

4.1.2 状态机转换

在这里插入图片描述

4.1.3 波形图设计

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

4.1.4 代码

关键代码:
在这里插入图片描述

module state_ctrl(
	input	wire 		sclk,
	input	wire 		rst,
	output	reg  		cs_n,
	output	reg 		sck,
	output	reg 		sdi,
	input	wire 		sdo,
	/*************uart rx****************************/
	input	wire 		rx_flag,
	input	wire [7:0]	rx_data,
	/************uart tx ***************************/
	output	wire 		tx_flag,
	output	wire [7:0]	tx_data,
	/************flash se interface*******************/
	output	wire 		se_flag,
	output	wire [23:0]	se_addr,
	input	wire 		se_sck,
	input	wire 		se_cs_n,
	input	wire 		se_sdi,
	/************flash pp interface******************/
	output	wire 		pp_flag,
	output	wire [23:0]	pp_addr,
	output	wire 		wr_en,
	output	wire [7:0]	wr_data,
	input	wire 		pp_sck,
	input	wire 		pp_cs_n,
	input	wire 		pp_sdi,
	/************flash rd interface******************/
	output	wire 		rd_flag,
	output	wire [23:0]	rd_addr,
	output	wire [10:0]	rd_len,
	input	wire 		rd_ready,
	output	wire		rd_en,
	input	wire [7:0]	rd_data,
	input	wire 		rd_sck,
	input	wire 		rd_cs_n,
	output	wire 		rd_sdo,
	input	wire 		rd_sdi
	);
parameter 	IDLE =4'b0001;
parameter	WRITE =4'b0010;
parameter	SERASE=4'b0100;
parameter	READ  =4'b1000;

parameter	DIV_CNT_END = 4340;//52082;

reg [3:0]	state;
reg 		write_end,serase_end,read_end;
reg [15:0]	byte_cnt;
reg [23:0]	flash_addr_shift;
reg 		wr_fifo_en;
reg [7:0]	wr_fifo_data;
reg [15:0]	rd_len_shift;
reg 		rd_flash_req;//rd_flag
reg [15:0]	div_cnt;
reg 		rd_fifo_en;
reg [15:0]	tx_d_cnt;

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		state <= IDLE;
	end
	else case(state)
		IDLE : if(rx_flag == 1'b1 && rx_data == 8'hcc)begin
			state <= WRITE;
		end
		else if(rx_flag == 1'b1 && rx_data == 8'hee) begin
			state <= SERASE;
		end
		else if (rx_flag == 1'b1 && rx_data == 8'hdd) begin
			state <= READ;
		end
		WRITE : if (write_end == 1'b1 ) begin
			state <= IDLE;
		end
		SERASE : if (serase_end == 1'b1 ) begin
			state <= IDLE;
		end
		READ : if (read_end == 1'b1) begin
			state <= IDLE;
		end
		default : state <= IDLE;
	endcase
end

//write state and flash pp
always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		byte_cnt <= 'd0;
	end
	else if (state == IDLE) begin
		byte_cnt <='d0;
	end
	else if (state != IDLE && rx_flag == 1'b1) begin
		byte_cnt <= byte_cnt + 1'b1;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		flash_addr_shift <= 'd0;
	end
	else if (state != IDLE && byte_cnt < 3 && rx_flag == 1'b1  ) begin
		flash_addr_shift <= {flash_addr_shift[15:0],rx_data};
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		wr_fifo_en <= 1'b0;
	end
	else if (state == WRITE && byte_cnt >2 && rx_flag == 1'b1) begin
		wr_fifo_en <= 1'b1;
	end
	else begin
		wr_fifo_en <= 1'b0;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		wr_fifo_data <= 'd0;
	end
	else if (state == WRITE) begin
		wr_fifo_data <= rx_data;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		write_end <=1'b1;
	end
	else if (state == WRITE && byte_cnt == 'd258 && rx_flag == 1'b1) begin
		write_end <= 1'b1;
	end
	else begin
		write_end <= 1'b0;
	end
end

assign pp_flag = write_end;
assign pp_addr = flash_addr_shift;
assign wr_en = wr_fifo_en;
assign wr_data = wr_fifo_data;
//write state end
//serase state start
always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		serase_end <= 1'b0;
	end
	else if (state == SERASE && rx_flag == 1'b1 && byte_cnt == 2) begin
		serase_end <= 1'b1;
	end
	else begin
		serase_end <= 1'b0;
	end
end
assign se_flag = serase_end;
assign se_addr = flash_addr_shift;
//serase state end
//read state start

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		rd_len_shift <='d0;
	end
	else if (state == READ && (byte_cnt == 3 || byte_cnt == 4) && rx_flag == 1'b1 ) begin
		rd_len_shift <= {rd_len_shift[7:0],rx_data};
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		rd_flash_req<= 1'b0;
	end
	else if (state == READ && byte_cnt == 4 && rx_flag == 1'b1 ) begin
		rd_flash_req <= 1'b1;
	end
	else begin
		rd_flash_req <= 1'b0;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		div_cnt <='d0;
	end
	else if(state == READ && rd_ready == 1'b1 && div_cnt == DIV_CNT_END)begin
		div_cnt <= 'd0;
	end
	else if (state == READ && rd_ready == 1) begin
		div_cnt <= div_cnt + 1'b1;
	end
	else if (rd_ready == 1'b0) begin
		div_cnt <='d0;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		rd_fifo_en <= 1'b0;
	end
	else if (state == READ && rd_ready == 1'b1 && div_cnt ==0) begin
		rd_fifo_en <= 1'b1;
	end
	else begin
		rd_fifo_en <= 1'b0;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		tx_d_cnt <='d0;
	end
	else if (state == READ && rd_fifo_en == 1'b1 ) begin
		tx_d_cnt <= tx_d_cnt + 1'b1;
	end
	else if(state != READ) begin
		tx_d_cnt <='d0;
	end
end

always @(posedge sclk or posedge rst) begin
	if (rst == 1'b1) begin
		read_end <= 1'b0;
	end
	else if (state == READ && tx_d_cnt == (rd_len_shift-1)&&rd_fifo_en == 1'b1 ) begin
		read_end <= 1'b1;
	end
	else begin
		read_end <= 1'b0;
	end
end

assign rd_flag = rd_flash_req;
assign rd_addr = flash_addr_shift;
assign rd_len = rd_len_shift[10:0];
assign rd_en = rd_fifo_en;

assign tx_flag = rd_fifo_en;
assign tx_data = rd_data;

//read state  end
//flash spi signal route

always @* begin
	if(state == READ) begin
		sdi <= rd_sdi;
		cs_n <= rd_cs_n;
		sck <= rd_sck;
	end
	else begin
		sdi <= se_sdi | pp_sdi;
		sck <= se_sck | pp_sck;
		cs_n <= se_cs_n & pp_cs_n;
	end
end

assign rd_sdo =sdo ;

endmodule 

5.下板操作:

实现将一个呼吸灯的程序固化到flash中

1.先将flash_ctrl设计的bit文件通过JTAG下载线下载到fpga板子上(保证fpga有读写flash的功能,这是往flash中烧写文件的前提)

2.打开老师提供的fpga_update软件(这个软件是简化版本的,目前只能对flash进行写数据),将呼吸灯的bin文件下载到flash中
在这里插入图片描述
3.烧写完成后,板卡重新上电,发现板子开始执行呼吸灯的程序了。

  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
手写一个监听器,需要创建一个接口,其中包含要监听的事件的方法。此接口将被实现,以便在事件发生时可以执行相应的操作。以下是一个简单的例子: ``` public interface MyEventListener { void onEventTriggered(Event event); } ``` 在这个例子中,我们定义了一个名为 MyEventListener 的接口,它有一个方法 onEventTriggered,该方法接收一个 Event 对象作为参数。这个接口将由实现它的类来实现,并在事件发生时执行相应的操作。 要使用这个监听器,我们需要创建一个事件类,并在事件发生时通知监听器。这可以通过调用 MyEventListener 接口的 onEventTriggered 方法来实现。 ``` public class Event { private List<MyEventListener> listeners = new ArrayList<>(); public void addListener(MyEventListener listener) { listeners.add(listener); } public void removeListener(MyEventListener listener) { listeners.remove(listener); } public void trigger() { for (MyEventListener listener : listeners) { listener.onEventTriggered(this); } } } ``` 在这个例子中,我们创建了一个名为 Event 的类,它具有添加、移除和触发监听器的方法。当事件发生时,我们可以调用 trigger 方法来通知所有监听器。 现在我们可以创建一个实现 MyEventListener 接口的类,并将其添加到 Event 对象的监听器列表中。例如: ``` public class MyListener implements MyEventListener { @Override public void onEventTriggered(Event event) { System.out.println("Event triggered"); } } public static void main(String[] args) { Event event = new Event(); MyListener listener = new MyListener(); event.addListener(listener); // Trigger the event event.trigger(); // Remove the listener event.removeListener(listener); } ``` 在这个例子中,我们创建了一个 MyListener 类来实现 MyEventListener 接口。我们将 MyListener 实例添加到 Event 对象的监听器列表中,并触发事件。当事件发生时,onEventTriggered 方法将被调用,并输出 "Event triggered"。最后,我们从监听器列表中移除了 MyListener 实例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值