17 SPI FLASH读写

news2024/11/28 20:54:42

SPI 协议简介

SPI 即 Serial Periphera linterface 的缩写,顾名思义就是串行外围设备接口,主要用于与FLASH、实时时钟、AD 转换器等外设模块的通信,它是一种高速的全双工同步的通信总线。
SPI 设备分为主设备和从设备,SPI 通信必须由主设备发起,主设备通过片选引脚(CSn)来选择对应的从设备,通过时钟引脚(SCK)向从设备提供时钟,通过数据输出引脚(MOSI)引脚向从设备发送数据,通过数据输入引脚(MISO)引脚来读取从设备返回的数据,如下是 SPI 的总线拓扑图(分别是1个主设备对应1个从设备和1个主设备对应多个从设备):
在这里插入图片描述
在这里插入图片描述

SPI 时序

通过 SPI 的时钟极性(CPOL)和相位( CPHA)可以组合出4种工作模式,如下表所示是对应的4种工作模式:
在这里插入图片描述

  1. 模式0(CPOL = 0, CPHA = 0)
    CPOL = 0:空闲时是低电平,第一个跳变沿是上升沿,第二个跳变沿是下降沿
    CPHA = 0:数据在第一个跳变沿(上升沿)采样
    在这里插入图片描述
  2. 模式1(CPOL = 0, CPHA = 1)
    CPOL = 0:空闲时是低电平,第一个跳变沿是上升沿,第二个跳变沿是下降沿
    CPHA = 1:数据在第二个跳变沿(下降沿)采样
    在这里插入图片描述
  3. 模式2(CPOL = 1, CPHA = 0)
    CPOL = 1:空闲时是高电平,第一个跳变沿是下降沿,第二个跳变沿是上升沿
    CPHA = 0:数据在第一个跳变沿(下降沿)采样
    在这里插入图片描述
  4. 模式3(CPOL = 1, CPHA = 1)
    CPOL = 1:空闲时是高电平,第一个跳变沿是下降沿,第二个跳变沿是上升沿
    CPHA = 1:数据在第二个跳变沿(上升沿)采样
    在这里插入图片描述

FLASH 简介

实验使用的 FLASH 芯片型号为W25Q128,它是华邦公司推出的一款的 NOR FIash 芯片,其存储空间为128 Mbit,相当于16MB,支持 Standard SPI、Dual SPI 和 Quad SPI 三种 SPI 协议通信方式,最大传输数据速率可达104MHZ,如下是芯片的引脚示意图:
在这里插入图片描述
在这里插入图片描述

存储结构

整个存储阵列被分成被为 256 给我块,每个块又分为 16 个扇区,每个扇区由分为 16 个页,每页包含 256 个字节。
在这里插入图片描述

操作指令

在这里插入图片描述在这里插入图片描述
FLASH 操作指令有很多,其中常用的有读ID指令(0x9F)、写使能指令(0x06)、扇区擦除指令(每次擦除4KB,0x20)、全擦除指令(0xC7)、读指令(0x03)、写指令(又叫页编程,0x02)、读状态寄存器1指令(0x05)。

FLASH 操作时序

  1. 读ID指令
    在操作 FLASH 之前应读取其ID,校验 FLASH 型号,如下是读 ID 的时序:
    在这里插入图片描述
  2. 写使能指令
    在进行擦除、写入操作前需要先发送写使能指令,其时序如下:
    在这里插入图片描述
  3. 扇区擦除指令
    W25Q128在写数据时只能将1修改为0,所以在写入新数据之前必须要进行擦除操作,将FLASH中的存储单元全部设置为1,如下是扇区擦除时序:
    在这里插入图片描述
  4. 全擦除指令
    除了扇区擦除指令外,还有全擦除指令,其时序如下:
    在这里插入图片描述
  5. 读指令
    读取 FLASH 一次可以读取多个字节,其时序如下:
    在这里插入图片描述
  6. 写指令
    向 FLASH 写入数据时不能跨页(页大小256B),若需要写入多页或者写入数据跨页则需要分多次写入,如下是写入数据的时序:
    在这里插入图片描述
  7. 读状态寄存器1指令
    在进行擦除、写入操作后需要轮询状态寄存器0的bit0,以检查擦除或写入操作是否结束(全擦除指令耗时很长),如下是读状态寄存器的时序:
    在这里插入图片描述

硬件设计

FLASH 芯片的硬件原理图较为简单,芯片本身就 8 个引脚,其原理图如下:
在这里插入图片描述

代码编写

代码一共分为3个模块,分别是 SPI 驱动模块、FLASH 驱动模块、FLASH 读写测试模块,其功能如下:
SPI 驱动模块;提供 SPI 总线收发数据的功能。
FLASH 驱动模块;基于 SPI 驱动模块实现 FLASH 的一些基本操作,如读 ID、写使能、写、擦除、读、读状态寄存器等。
FLASH 读写测试模块;利用 FLASH 驱动模块提供的 FLASH 基本操作进行 FLASH 读ID、擦除、写、读等测试,测试过程中状态 LED 常灭,测试出错状态 LED闪烁,测试完成状态 LED 常亮。

SPI 驱动模块

module spi_driver #(
	parameter SPI_CS_MAX = 1,					//片选数量
	parameter SPI_BITS = 8,						//SPI位宽
	parameter SPI_CLK_PERIOD = 4,				//SPI时钟周期,以系统时钟为参考,最小为4
	parameter SPI_MODE = 0,						//SPI模式
	parameter ALMOST_DONE_ADVANCE = 0,			//即将传输完成信号提前输出时间,小于SPI_CLK_PERIOD-1
	parameter ALMOST_IDLE_ADVANCE = 0			//即将空闲信号提前输出时间,小于SPI_CLK_PERIOD-1
)
(
	input sys_rst_n,							//系统复位
	input sys_clk,								//系统时钟

	input [SPI_BITS-1:0] tx_data,				//需要发送的数据
	input tr_start,								//启动传输

	output reg [SPI_BITS-1:0] rx_data,			//接收到的数据
	output reg tr_done,							//传输完成
	output reg almost_tr_done,					//传输即将完成

	output tr_idle,								//SPI空闲
	output reg almost_tr_idle,					//SPI即将空闲

	input [SPI_CS_MAX-1:0] sel_cs,				//片选设置

	output spi_clk,								//SPI时钟
	output spi_mosi,							//SPI MOSI
	input spi_miso,								//SPI MISO
	output [SPI_CS_MAX-1:0] spi_cs				//SPI片选
);

//SPI时钟周期,只能是偶数分频
localparam CLK_PERIOD = (SPI_CLK_PERIOD / 2 * 2);

//spi传输忙标志
reg tr_busy;

//SPI时钟周期计数器,按SPI时钟周期进行计数
reg [15:0] clk_period_count;

//传输bit计数
reg [7:0] bit_cnt;

//发送移位寄存器
reg [SPI_BITS-1:0] tx_shift_reg;
//接收移位寄存器
reg [SPI_BITS-1:0] rx_shift_reg;


//空闲标志
assign tr_idle = ~tr_busy;

//启动SPI传输
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		tr_busy <= 1'b0;
	else if((tr_start == 1'b1) && (tr_busy == 1'b0))
		tr_busy <= 1'b1;
	else if((tr_busy == 1'b1) && (bit_cnt == (SPI_BITS - 1)) && (clk_period_count == (CLK_PERIOD - 1)))
		tr_busy <= 1'b0;
end

//SPI即将空闲
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		almost_tr_idle <= 1'b1;
	else if((tr_start == 1'b1) && (almost_tr_idle == 1'b1))
		almost_tr_idle <= 1'b0;
	else if((almost_tr_idle == 1'b0) && (bit_cnt == (SPI_BITS - 1)) && (clk_period_count == (CLK_PERIOD - 1 - ALMOST_IDLE_ADVANCE)))
		almost_tr_idle <= 1'b1;
end

//按SPI时钟周期进行计数
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		clk_period_count <= 0;
	else if(tr_busy == 1'b1)
		if(clk_period_count < (CLK_PERIOD - 1))
			clk_period_count <= clk_period_count + 1;
		else
			clk_period_count <= 0;
	else
		clk_period_count <= 0;
end

//进行传输计数
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		bit_cnt <= 0;
	else if(tr_busy == 1'b1) begin
		if(clk_period_count == (CLK_PERIOD - 1)) begin
			if(bit_cnt < (SPI_BITS - 1))
				bit_cnt <= bit_cnt + 1;
		end
	end
	else
		bit_cnt <= 0;
end

//输出接收到的数据,接收完最后1bit时输出
always @(posedge sys_clk) begin
	if(!sys_rst_n) begin
		rx_data <= 0;
		tr_done <= 0;
	end
	else if((tr_busy == 1'b1) && (bit_cnt == (SPI_BITS - 1)) && (clk_period_count == (CLK_PERIOD - 1))) begin
		rx_data <= rx_shift_reg;
		tr_done <= 1;
	end
	else
		tr_done <= 0;
end

//输出传输完成预告
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		almost_tr_done <= 0;
	else if((tr_busy == 1'b1) && (bit_cnt == (SPI_BITS - 1)) && (clk_period_count == (CLK_PERIOD - 1 - ALMOST_DONE_ADVANCE)))
		almost_tr_done <= 1;
	else
		almost_tr_done <= 0;
end

generate
	if(SPI_MODE == 0) begin
		//输出SPI时钟,模式0
		//时钟默认为低电平,工作时前半段为低电平,后半段为高电平
		assign spi_clk = ((tr_busy == 1'b1) && (clk_period_count > (CLK_PERIOD / 2 - 1))) ? 1'b1 : 1'b0;
	end
	else if(SPI_MODE == 1) begin
		//输出SPI时钟,模式1
		//时钟默认为低电平,工作时前半段为高电平,后半段为低电平
		assign spi_clk = ((tr_busy == 1'b1) && (clk_period_count <= (CLK_PERIOD / 2 - 1))) ? 1'b1 : 1'b0;
	end
	else if(SPI_MODE == 2) begin
		//输出SPI时钟,模式2
		//时钟默认为高电平,工作时前半段为高电平,后半段为低电平
		assign spi_clk = ((tr_busy == 1'b1) && (clk_period_count > (CLK_PERIOD / 2 - 1))) ? 1'b0 : 1'b1;
	end
	else begin
		//输出SPI时钟,模式3
		//时钟默认为高电平,工作时前半段为高电平,后半段为低电平
		assign spi_clk = ((tr_busy == 1'b1) && (clk_period_count <= (CLK_PERIOD / 2 - 1))) ? 1'b0 : 1'b1;
	end
endgenerate

//启动时锁定数据,随后进行发送数据移位操作
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		tx_shift_reg <= (1 << SPI_BITS) - 1;
	else if((tr_start == 1'b1) && (tr_busy == 1'b0))
		tx_shift_reg <= tx_data;
	else if((tr_busy == 1'b1) && (bit_cnt < (SPI_BITS - 1)) && (clk_period_count == (CLK_PERIOD - 1)))
		tx_shift_reg <= {tx_shift_reg[6:0], tx_shift_reg[7]};
end
//输出SPI MOSI
assign spi_mosi = tx_shift_reg[7];

//采样SPI MOSI
//在时钟周期的3/4处采样
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		rx_shift_reg <= 0;
	else if(tr_busy == 1'b1) begin
		if(clk_period_count == (CLK_PERIOD - CLK_PERIOD / 4 - 1))
			rx_shift_reg = {rx_shift_reg[6:0], spi_miso};
	end
	else
		rx_shift_reg <= 0;
end

//输出片选信号
assign spi_cs = sel_cs;

endmodule

FLASH 驱动模块

module flash_driver  #(
	parameter SPI_CLK_PERIOD = 4,
	parameter SPI_CS_DELAY = 500,
	parameter DATA_REQ_ADVANCE = 1
)
(
	input sys_rst_n,
	input sys_clk,

	input flash_start,
	input [7:0] flash_cmd,
	input [24:0] flash_addr,

	input [8:0] wr_data_len,
	input [7:0] wr_data,
	output wr_data_req,

	input [8:0] rd_data_len,
	output [7:0] rd_data,
	output rd_data_flag,

	output reg [23:0] flash_id,
	output reg flash_id_flag,

	output reg [7:0] flash_sr_reg,
	output reg flash_sr_reg_flag,

	output flash_idle,

	output spi_clk,
	output spi_mosi,
	input spi_miso,
	output spi_cs
);

//状态机的状态
localparam IDLE_STATE = 8'h01;			//空闲状态
localparam RDID_STATE = 8'h02;			//读FLASH ID状态
localparam WREN_STATE = 8'h04;			//写使能状态
localparam SSE_STATE = 8'h08;			//子扇区擦除状态
localparam BE_STATE = 8'h10;			//全擦除
localparam READ_STATE = 8'h20;			//读状态
localparam PP_STATE = 8'h40;			//写状态(页编程)
localparam RDSR_STATE = 8'h80;			//读状态寄存器状态

//指令集
localparam RDID_CMD = 8'h9f;			//读ID指令
localparam WREN_CMD = 8'h06;			//写使能指令
localparam SSE_CMD = 8'h20;				//子扇区擦除指令
localparam BE_CMD = 8'hc7;				//全擦除指令
localparam READ_CMD = 8'h0b;			//读指令
localparam PP_CMD = 8'h02;				//写指令(页编程)
localparam RDSR_CMD = 8'h05;			//读状态寄存器指令

//传输计数
reg [15:0] tx_count;
reg [15:0] rx_count;

//当前状态
reg [7:0] current_state;
//下一刻的状态
reg [7:0] next_state;
//当前状态结束标志,切换到下一个状态
reg state_done;

//片选前延时
reg [16:0] cs_front_delay_count;
//片选后延时
reg [16:0] cs_back_delay_count;
//指令操作延时,部分命令发送完成后需要延时
reg [16:0] cmd_back_ddelay_count;

//SPI需要发送的数据
reg [7:0] spi_tx_data;
//启动SPI发送
reg spi_tr_start;
//SPI片选控制
reg spi_ctrl_cs;

//SPI接收到的数据
wire [7:0] spi_rx_data;
//SPI传输完成
wire spi_tr_done;
//SPI即将传输完成
wire spi_almost_tr_done;

//SPI空闲
wire spi_tr_idle;
//SPI即将空闲
wire spi_almost_tr_idle;

//状态跳转
always @(posedge sys_clk)begin
	if(!sys_rst_n)
		current_state <= IDLE_STATE;
	else
		current_state <= next_state;
end

//根据当前状态确定下一刻状态
always @(*)begin
	case(current_state)
		IDLE_STATE: begin
			if((state_done == 1'b0) && (flash_start == 1'b1) && (flash_cmd == RDID_CMD))
				next_state = RDID_STATE;
			else if((state_done == 1'b0) && (flash_start == 1'b1) && (flash_cmd == WREN_CMD))
				next_state = WREN_STATE;
			else if((state_done == 1'b0) && (flash_start == 1'b1) && (flash_cmd == SSE_CMD))
				next_state = SSE_STATE;
			else if((state_done == 1'b0) && (flash_start == 1'b1) && (flash_cmd == BE_CMD))
				next_state = BE_STATE;
			else if((state_done == 1'b0) && (flash_start == 1'b1) && (flash_cmd == READ_CMD))
				next_state = READ_STATE;
			else if((state_done == 1'b0) && (flash_start == 1'b1) && (flash_cmd == PP_CMD))
				next_state = PP_STATE;
			else if((state_done == 1'b0) && (flash_start == 1'b1) && (flash_cmd == RDSR_CMD))
				next_state = RDSR_STATE;
			else
				next_state = IDLE_STATE;
		end
		RDID_STATE: begin
			if(state_done == 1'b1)
				next_state = IDLE_STATE;
			else
				next_state = RDID_STATE;
		end
		WREN_STATE: begin
			if(state_done == 1'b1)
				next_state = IDLE_STATE;
			else
				next_state = WREN_STATE;
		end
		SSE_STATE: begin
			if(state_done == 1'b1)
				next_state = IDLE_STATE;
			else
				next_state = SSE_STATE;
			end
		BE_STATE: begin
			if(state_done == 1'b1)
				next_state = IDLE_STATE;
			else
				next_state = BE_STATE;
		end
		READ_STATE: begin
			if(state_done == 1'b1)
				next_state = IDLE_STATE;
			else
				next_state = READ_STATE;
		end
		PP_STATE: begin
			if(state_done == 1'b1)
				next_state = IDLE_STATE;
			else
				next_state = PP_STATE;
		end
		RDSR_STATE: begin
			if(state_done == 1'b1)
				next_state = IDLE_STATE;
			else
				next_state = RDSR_STATE;
		end
		default:
			next_state = IDLE_STATE;
	endcase
end

//空闲标志输出
assign flash_idle = ((current_state == IDLE_STATE) && (state_done == 1'b0)) ? 1'b1 : 1'b0;

//进行计数器计数,输出延时信号
always @(posedge sys_clk) begin
	if(!sys_rst_n) begin
		cs_front_delay_count <= 0;
		cs_back_delay_count <= 0;
		cmd_back_ddelay_count <= 0;
		state_done <= 1'b0;
	end
	else begin
		case(current_state)
			IDLE_STATE: begin
				cs_front_delay_count <= 0;
				cs_back_delay_count <= 0;
				cmd_back_ddelay_count <= 0;
				state_done <= 1'b0;
			end
			RDID_STATE: begin
				//片选拉低后延时计数
				if(cs_front_delay_count < (SPI_CS_DELAY - 1))
					cs_front_delay_count <= cs_front_delay_count + 1;
				//片选拉高前延时计数
				if(rx_count >= 4) begin
					if(cs_back_delay_count < (SPI_CS_DELAY - 1))
						cs_back_delay_count <= cs_back_delay_count + 1;
				end
				//指令操作延时
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1)) begin
					if(cmd_back_ddelay_count < (500 - 1))
						cmd_back_ddelay_count <= cmd_back_ddelay_count + 1;
				end
				//状态结束判断
				if(cmd_back_ddelay_count >= (500 - 1))
					state_done <= 1'b1;
			end
			WREN_STATE: begin
				//片选拉低后延时计数
				if(cs_front_delay_count < (SPI_CS_DELAY - 1))
					cs_front_delay_count <= cs_front_delay_count + 1;
				//片选拉高前延时计数
				if(rx_count >= 1) begin
					if(cs_back_delay_count < (SPI_CS_DELAY - 1))
						cs_back_delay_count <= cs_back_delay_count + 1;
				end
				//指令操作延时
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1)) begin
					if(cmd_back_ddelay_count < (500 - 1))
						cmd_back_ddelay_count <= cmd_back_ddelay_count + 1;
				end
				//状态结束判断
				if(cmd_back_ddelay_count >= (500 - 1))
					state_done <= 1'b1;
			end
			SSE_STATE: begin
				//片选拉低后延时计数
				if(cs_front_delay_count < (SPI_CS_DELAY - 1))
					cs_front_delay_count <= cs_front_delay_count + 1;
				//片选拉高前延时计数
				if(rx_count >= 4) begin
					if(cs_back_delay_count < (SPI_CS_DELAY - 1))
						cs_back_delay_count <= cs_back_delay_count + 1;
				end
				//指令操作延时
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1)) begin
					if(cmd_back_ddelay_count < (500 - 1))
						cmd_back_ddelay_count <= cmd_back_ddelay_count + 1;
				end
				//状态结束判断
				if(cmd_back_ddelay_count >= (500 - 1))
					state_done <= 1'b1;
			end
			BE_STATE: begin
				//片选拉低后延时计数
				if(cs_front_delay_count < (SPI_CS_DELAY - 1))
					cs_front_delay_count <= cs_front_delay_count + 1;
				//片选拉高前延时计数
				if(rx_count >= 1) begin
					if(cs_back_delay_count < (SPI_CS_DELAY - 1))
						cs_back_delay_count <= cs_back_delay_count + 1;
				end
				//指令操作延时
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1)) begin
					if(cmd_back_ddelay_count < (500 - 1))
						cmd_back_ddelay_count <= cmd_back_ddelay_count + 1;
				end
				//状态结束判断
				if(cmd_back_ddelay_count >= (500 - 1))
					state_done <= 1'b1;
			end
			READ_STATE: begin
				//片选拉低后延时计数
				if(cs_front_delay_count < (SPI_CS_DELAY - 1))
					cs_front_delay_count <= cs_front_delay_count + 1;
				//片选拉高前延时计数
				if(rx_count >= (4 + rd_data_len)) begin
					if(cs_back_delay_count < (SPI_CS_DELAY - 1))
						cs_back_delay_count <= cs_back_delay_count + 1;
				end
				//指令操作延时
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1)) begin
					if(cmd_back_ddelay_count < (500 - 1))
						cmd_back_ddelay_count <= cmd_back_ddelay_count + 1;
				end
				//状态结束判断
				if(cmd_back_ddelay_count >= (500 - 1))
					state_done <= 1'b1;
			end
			PP_STATE: begin
				//片选拉低后延时计数
				if(cs_front_delay_count < (SPI_CS_DELAY - 1))
					cs_front_delay_count <= cs_front_delay_count + 1;
				//片选拉高前延时计数
				if(rx_count >= (4 + wr_data_len)) begin
					if(cs_back_delay_count < (SPI_CS_DELAY - 1))
						cs_back_delay_count <= cs_back_delay_count + 1;
				end
				//指令操作延时
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1)) begin
					if(cmd_back_ddelay_count < (500 - 1))
						cmd_back_ddelay_count <= cmd_back_ddelay_count + 1;
				end
				//状态结束判断
				if(cmd_back_ddelay_count >= (500 - 1))
					state_done <= 1'b1;
			end
			RDSR_STATE: begin
				//片选拉低后延时计数
				if(cs_front_delay_count < (SPI_CS_DELAY - 1))
					cs_front_delay_count <= cs_front_delay_count + 1;
				//片选拉高前延时计数
				if(rx_count >= 2) begin
					if(cs_back_delay_count < (SPI_CS_DELAY - 1))
						cs_back_delay_count <= cs_back_delay_count + 1;
				end
				//指令操作延时
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1)) begin
					if(cmd_back_ddelay_count < (500 - 1))
						cmd_back_ddelay_count <= cmd_back_ddelay_count + 1;
				end
				//状态结束判断
				if(cmd_back_ddelay_count >= (500 - 1))
					state_done <= 1'b1;
			end
			default: begin
				cs_front_delay_count <= 0;
				cs_back_delay_count <= 0;
				cmd_back_ddelay_count <= 0;
				state_done <= 1'b0;
			end
		endcase
	end
end

//控制SPI传输
always @(posedge sys_clk)begin
	if(!sys_rst_n) begin
		spi_ctrl_cs <= 1'b1;
		spi_tx_data <= 8'b0;
		spi_tr_start <= 1'b0;
		tx_count <= 16'b0;
	end
	else begin
		case(current_state)
			IDLE_STATE: begin
				spi_ctrl_cs <= 1'b1;
				spi_tx_data <= 8'b0;
				spi_tr_start <= 1'b0;
				tx_count <= 16'b0;
			end
			RDID_STATE: begin
				//控制片选
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1))
					spi_ctrl_cs <= 1'b1;
				else
					spi_ctrl_cs <= 1'b0;

				//控制传输
				if(cs_front_delay_count >= (SPI_CS_DELAY - 1)) begin
					if((spi_almost_tr_idle == 1'b1) && (spi_tr_start == 1'b0)) begin
						//启动spi传输
						if(tx_count == 0) begin
							spi_tx_data <= 8'h9f;
							spi_tr_start <= 1'b1;
						end
						else if(tx_count < 4) begin
							spi_tx_data <= 8'hFF;
							spi_tr_start <= 1'b1;
						end

						//发送计数
						if(tx_count < 4)
							tx_count <= tx_count + 16'b1;
					end
					else if(spi_almost_tr_idle == 1'b0)
						spi_tr_start <= 1'b0;
				end
				else
					spi_tr_start <= 1'b0;
			end
			WREN_STATE: begin
				//控制片选
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1))
					spi_ctrl_cs <= 1'b1;
				else
					spi_ctrl_cs <= 1'b0;

				//控制传输
				if(cs_front_delay_count >= (SPI_CS_DELAY - 1)) begin
					if((spi_almost_tr_idle == 1'b1) && (spi_tr_start == 1'b0)) begin
						//启动spi传输
						if(tx_count == 0) begin
							spi_tx_data <= 8'h06;
							spi_tr_start <= 1'b1;
						end

						//发送计数
						if(tx_count < 1)
							tx_count <= tx_count + 16'b1;
					end
					else if(spi_almost_tr_idle == 1'b0)
						spi_tr_start <= 1'b0;
				end
				else
					spi_tr_start <= 1'b0;
			end
			SSE_STATE: begin
				//控制片选
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1))
					spi_ctrl_cs <= 1'b1;
				else
					spi_ctrl_cs <= 1'b0;

				//控制传输
				if(cs_front_delay_count >= (SPI_CS_DELAY - 1)) begin
					if((spi_almost_tr_idle == 1'b1) && (spi_tr_start == 1'b0)) begin
						//启动spi传输
						if(tx_count == 0) begin
							spi_tx_data <= 8'h20;
							spi_tr_start <= 1'b1;
						end
						else if(tx_count == 1) begin
							spi_tx_data <= flash_addr[23:16];
							spi_tr_start <= 1'b1;
						end
						else if(tx_count == 2) begin
							spi_tx_data <= flash_addr[15:8];
							spi_tr_start <= 1'b1;
						end
						else if(tx_count == 3) begin
							spi_tx_data <= flash_addr[7:0];
							spi_tr_start <= 1'b1;
						end

						//发送计数
						if(tx_count < 4)
							tx_count <= tx_count + 16'b1;
					end
					else if(spi_almost_tr_idle == 1'b0)
						spi_tr_start <= 1'b0;
				end
				else
					spi_tr_start <= 1'b0;
			end
			BE_STATE: begin
				//控制片选
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1))
					spi_ctrl_cs <= 1'b1;
				else
					spi_ctrl_cs <= 1'b0;

				//控制传输
				if(cs_front_delay_count >= (SPI_CS_DELAY - 1)) begin
					if((spi_almost_tr_idle == 1'b1) && (spi_tr_start == 1'b0)) begin
						//启动spi传输
						if(tx_count == 0) begin
							spi_tx_data <= 8'hc7;
							spi_tr_start <= 1'b1;
						end

						//发送计数
						if(tx_count < 1)
							tx_count <= tx_count + 16'b1;
					end
					else if(spi_almost_tr_idle == 1'b0)
						spi_tr_start <= 1'b0;
				end
				else
					spi_tr_start <= 1'b0;
			end
			READ_STATE: begin
				//控制片选
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1))
					spi_ctrl_cs <= 1'b1;
				else
					spi_ctrl_cs <= 1'b0;

				//控制传输
				if(cs_front_delay_count >= (SPI_CS_DELAY - 1)) begin
					if((spi_almost_tr_idle == 1'b1) && (spi_tr_start == 1'b0)) begin
						//启动spi传输
						if(tx_count == 0) begin
							spi_tx_data <= 8'h03;
							spi_tr_start <= 1'b1;
						end
						else if(tx_count == 1) begin
							spi_tx_data <= flash_addr[23:16];
							spi_tr_start <= 1'b1;
						end
						else if(tx_count == 2) begin
							spi_tx_data <= flash_addr[15:8];
							spi_tr_start <= 1'b1;
						end
						else if(tx_count == 3) begin
							spi_tx_data <= flash_addr[7:0];
							spi_tr_start <= 1'b1;
						end
						else if(tx_count < (4 + rd_data_len)) begin
							spi_tx_data <= 8'hFF;
							spi_tr_start <= 1'b1;
						end

						//发送计数
						if(tx_count < (4 + rd_data_len))
							tx_count <= tx_count + 16'b1;
					end
					else if(spi_almost_tr_idle == 1'b0)
						spi_tr_start <= 1'b0;
				end
				else
					spi_tr_start <= 1'b0;
			end
			PP_STATE: begin
				//控制片选
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1))
					spi_ctrl_cs <= 1'b1;
				else
					spi_ctrl_cs <= 1'b0;

				//控制传输
				if(cs_front_delay_count >= (SPI_CS_DELAY - 1)) begin
					if((spi_almost_tr_idle == 1'b1) && (spi_tr_start == 1'b0)) begin
						//启动spi传输
						if(tx_count == 0) begin
							spi_tx_data <= 8'h02;
							spi_tr_start <= 1'b1;
						end
						else if(tx_count == 1) begin
							spi_tx_data <= flash_addr[23:16];
							spi_tr_start <= 1'b1;
						end
						else if(tx_count == 2) begin
							spi_tx_data <= flash_addr[15:8];
							spi_tr_start <= 1'b1;
						end
						else if(tx_count == 3) begin
							spi_tx_data <= flash_addr[7:0];
							spi_tr_start <= 1'b1;
						end
						else if(tx_count < (4 + wr_data_len)) begin
							spi_tx_data <= wr_data;
							spi_tr_start <= 1'b1;
						end

						//发送计数
						if(tx_count < (4 + wr_data_len))
							tx_count <= tx_count + 16'b1;
					end
					else if(spi_almost_tr_idle == 1'b0)
						spi_tr_start <= 1'b0;
				end
			end
			RDSR_STATE: begin
				//控制片选
				if(cs_back_delay_count >= (SPI_CS_DELAY - 1))
					spi_ctrl_cs <= 1'b1;
				else
					spi_ctrl_cs <= 1'b0;

				//控制传输
				if(cs_front_delay_count >= (SPI_CS_DELAY - 1)) begin
					if((spi_almost_tr_idle == 1'b1) && (spi_tr_start == 1'b0)) begin
						//启动spi传输
						if(tx_count == 0) begin
							spi_tx_data <= 8'h05;
							spi_tr_start <= 1'b1;
						end
						else if(tx_count < 2) begin
							spi_tx_data <= 8'hFF;
							spi_tr_start <= 1'b1;
						end

						//发送计数
						if(tx_count < 2)
							tx_count <= tx_count + 16'b1;
					end
					else if(spi_almost_tr_idle == 1'b0)
						spi_tr_start <= 1'b0;
				end
				else
					spi_tr_start <= 1'b0;
			end
			default: begin
				spi_ctrl_cs <= 1'b1;
				spi_tx_data <= 8'b0;
				spi_tr_start <= 1'b0;
			end
		endcase
	end
end

//处理读取ID和状态寄存器
always @(posedge sys_clk)begin
	if(!sys_rst_n) begin
		flash_id <= 24'b0;
		flash_id_flag <= 1'b0;
		flash_sr_reg <= 8'b0;
		flash_sr_reg_flag <= 1'b0;
	end
	else begin
		case(current_state)
			RDID_STATE: begin
				if(spi_tr_done == 1'b1) begin
					if(rx_count == 1)
						flash_id[23:16] <= spi_rx_data;
					else if(rx_count == 2)
						flash_id[15:8] <= spi_rx_data;
					else if(rx_count == 3)
						flash_id[7:0] <= spi_rx_data;

					if(rx_count == 3)
						flash_id_flag <= 1'b1;
					else
						flash_id_flag <= 1'b0;
				end
				else
					flash_id_flag <= 1'b0;
			end
			RDSR_STATE: begin
				if(spi_tr_done == 1'b1) begin
					if(rx_count == 1)
						flash_sr_reg <= spi_rx_data;

					if(rx_count == 1)
						flash_sr_reg_flag <= 1'b1;
					else
						flash_sr_reg_flag <= 1'b0;
				end
				else
					flash_sr_reg_flag <= 1'b0;
			end
			default: begin
				flash_id_flag <= 1'b0;
				flash_sr_reg_flag <= 1'b0;
			end
		endcase
	end
end

//SPI控制器接收计数
always @(posedge sys_clk)begin
	if(!sys_rst_n)
		rx_count <= 0;
	else if((current_state != IDLE_STATE) && (spi_tr_done == 1'b1))
		rx_count <= rx_count + 1;
	else if(current_state == IDLE_STATE)
		rx_count <= 0;
end

//flash数据输出
assign rd_data = ((current_state == READ_STATE) &&(rx_count >= 4)) ? spi_rx_data : 0'b0;
assign rd_data_flag = ((current_state == READ_STATE) &&(rx_count >= 4)) ? spi_tr_done : 0'b0;

//数据请求输出
assign wr_data_req = ((current_state == PP_STATE) &&(tx_count >= 4) && (tx_count < (4 + wr_data_len))) ? spi_almost_tr_done : 0'b0;

spi_driver #(
	.SPI_CS_MAX(1),
	.SPI_BITS(8),
	.SPI_CLK_PERIOD(4),
	.SPI_MODE(0),
	//因为将外部数据转发到spi_driver需要一个时钟,所以这里加1个时钟周期
	.ALMOST_DONE_ADVANCE(DATA_REQ_ADVANCE + 1),
	//此模块准备数据需要一个时钟周期,所以为1
	.ALMOST_IDLE_ADVANCE(1)
)
spi_driver_inst0(
	.sys_rst_n(sys_rst_n),
	.sys_clk(sys_clk),

	.tx_data(spi_tx_data),
	.tr_start(spi_tr_start),

	.rx_data(spi_rx_data),
	.tr_done(spi_tr_done),
	.almost_tr_done(spi_almost_tr_done),

	.tr_idle(spi_tr_idle),
	.almost_tr_idle(spi_almost_tr_idle),

	.sel_cs(spi_ctrl_cs),

	.spi_clk(spi_clk),
	.spi_mosi(spi_mosi),
	.spi_miso(spi_miso),
	.spi_cs(spi_cs)
);

endmodule

FLASH 读写测试模块

module spi_flash_rw_test #(
	parameter ALARM_LED_PERIOD = 25'd25_000_000
)
(
	input sys_rst_n,
	input sys_clk,

	output spi_mosi,
	input spi_miso,
	output spi_sclk,
	output spi_cs,

	output reg alarm_led
);

//flash指令集
localparam RDID_CMD = 8'h9f;			//读ID指令
localparam WREN_CMD = 8'h06;			//写使能指令
localparam SSE_CMD = 8'h20;				//子扇区擦除指令
localparam BE_CMD = 8'hc7;				//全擦除指令
localparam READ_CMD = 8'h0b;			//读指令
localparam PP_CMD = 8'h02;				//写指令(页编程)
localparam RDSR_CMD = 8'h05;			//读状态寄存器指令

//状态机的状态
localparam IDLE_STATE = 16'h0001;			//空闲状态

localparam RDID_STATE = 16'h0002;			//读FLASH ID状态

localparam BE_WREN_STATE = 16'h0004;		//全擦除前写使能状态
localparam BE_STATE = 16'h0008;				//全擦除状态
localparam BE_WAIT_STATE = 16'h0010;		//全擦除后等待flash空闲状态

localparam PP_WREN_STATE = 16'h0020;		//页编程前写使能状态
localparam PP_STATE = 16'h0040;				//写状态(页编程)
localparam PP_WAIT_STATE = 16'h0080;		//页编程后等待flash空闲状态

localparam READ1_STATE = 16'h0100;			//第一次读状态,验证写入数据是否成功

localparam SSE_WREN_STATE = 16'h0200;		//子扇区擦除前写使能状态
localparam SSE_STATE = 16'h0400;			//子扇区擦除状态
localparam SSE_WAIT_STATE = 16'h0800;		//子扇区擦除后等待flash空闲状态

localparam READ2_STATE = 16'h1000;			//第二次读状态,验证擦除是否成功

//错误指示
reg error_flag;
//警示LED闪烁计数器
reg [31:0] led_count;

//flash擦除或写入忙标志
reg flash_ew_busy;

//写入计数
reg [8:0] write_count;

//读取计数
reg [8:0] read_count;

//当前状态
reg [15:0] current_state;
//下一刻的状态
reg [15:0] next_state;
//对应状态结束标志,应切换到下一个状态,一个bit对应一个状态
reg [15:0] state_done;
//状态启动标志
reg [15:0] state_start;

//flash操作开始信号
reg flash_start;
//flash操作命令
reg [7:0] flash_cmd;
//flash操作地址
reg [24:0] flash_addr;

//flash写入长度
reg [8:0] wr_data_len;
//写入flash的数据,需要在收到wr_data_req后更新
reg [7:0] wr_data;
//flash写入数据请求
wire wr_data_req;

//flash读长度
reg [8:0]rd_data_len;
//flash中读取到的数据
wire [7:0] rd_data;
//flash读取数据有效标志
wire rd_data_flag;

//flash ID
wire [23:0] flash_id;
//flash ID有效标志
wire flash_id_flag;

//flash 状态寄存器0
wire [7:0] flash_sr_reg;
//flash 状态寄存器0有效标志
wire flash_sr_reg_flag;

//flash空闲标志
wire flash_driver_idle;

//根据错误标志控制led闪烁或常亮
//操作未完成熄灭
//操作完成常亮
//发生错误时闪烁
always @(posedge sys_clk) begin
	if(!sys_rst_n) begin
		alarm_led <= 1'b0;
		led_count <= 32'b0;
	end
	else if(error_flag == 1'b1) begin
		if(led_count >= (ALARM_LED_PERIOD - 1)) begin
			alarm_led <= ~alarm_led;
			led_count <= 32'b0;
		end
		else
			led_count <= led_count + 32'b1;
	end
	else if((current_state == IDLE_STATE) && (state_done != 16'h0)) begin
		alarm_led <= 1'b1;
		led_count <= 32'b0;
	end
	else begin 
		alarm_led <= 1'b0;
		led_count <= 32'b0;
	end
end

//状态跳转
always @(posedge sys_clk)begin
	if(!sys_rst_n)
		current_state <= IDLE_STATE;
	else
		current_state <= next_state;
end

//根据当前状态确定下一刻状态
always @(*)begin
	case(current_state)
		IDLE_STATE: begin
			if((state_done == 16'h0) && (error_flag == 1'b0))
				next_state = RDID_STATE;
			else
				next_state = IDLE_STATE;
		end
		RDID_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & RDID_STATE)
				next_state = BE_WREN_STATE;
			else
				next_state = RDID_STATE;
		end
		BE_WREN_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & BE_WREN_STATE)
				next_state = BE_STATE;
			else
				next_state = BE_WREN_STATE;
		end
		BE_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & BE_STATE)
				next_state = BE_WAIT_STATE;
			else
				next_state = BE_STATE;
		end
		BE_WAIT_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & BE_WAIT_STATE)
				next_state = PP_WREN_STATE;
			else
				next_state = BE_WAIT_STATE;
		end
		PP_WREN_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & PP_WREN_STATE)
				next_state = PP_STATE;
			else
				next_state = PP_WREN_STATE;
		end
		PP_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & PP_STATE)
				next_state = PP_WAIT_STATE;
			else
				next_state = PP_STATE;
		end
		PP_WAIT_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & PP_WAIT_STATE)
				next_state = READ1_STATE;
			else
				next_state = PP_WAIT_STATE;
		end
		READ1_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & READ1_STATE)
				next_state = SSE_WREN_STATE;
			else
				next_state = READ1_STATE;
		end
		SSE_WREN_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & SSE_WREN_STATE)
				next_state = SSE_STATE;
			else
				next_state = SSE_WREN_STATE;
		end
		SSE_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & SSE_STATE)
				next_state = SSE_WAIT_STATE;
			else
				next_state = SSE_STATE;
		end
		SSE_WAIT_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & SSE_WAIT_STATE)
				next_state = READ2_STATE;
			else
				next_state = SSE_WAIT_STATE;
		end
		READ2_STATE: begin
			if(error_flag == 1'b1)
				next_state = IDLE_STATE;
			else if(state_done & READ2_STATE)
				next_state = IDLE_STATE;
			else
				next_state = READ2_STATE;
		end
		default:
			next_state = IDLE_STATE;
	endcase
end

//控制flash擦除、读写等
always @(posedge sys_clk)begin
	if(!sys_rst_n) begin
		flash_start <= 1'b0;
		flash_cmd <= 8'h0;
		flash_addr <= 24'h0;
		wr_data_len <= 9'h0;
		rd_data_len <= 9'h0;
		state_done <= 16'h0;
		state_start <= 16'h0;
	end
	else begin
		case(current_state)
			RDID_STATE: begin
				//启动读ID操作
				if((flash_driver_idle == 1'b1) && (!(state_start & RDID_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= RDID_CMD;
					state_start <= state_start | RDID_STATE;
				end
				//读ID操作完成
				if((flash_driver_idle == 1'b1) && (state_start & RDID_STATE) && (flash_start == 1'b0)) begin
					if(!(state_done & RDID_STATE))
						state_done <= state_done | RDID_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			BE_WREN_STATE: begin
				//启动写使能操作
				if((flash_driver_idle == 1'b1) && (!(state_start & BE_WREN_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= WREN_CMD;
					state_start <= state_start | BE_WREN_STATE;
				end
				//写使能操作完成
				if((flash_driver_idle == 1'b1) && (state_start & BE_WREN_STATE) && (flash_start == 1'b0)) begin
					if(!(state_done & BE_WREN_STATE))
						state_done <= state_done | BE_WREN_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			BE_STATE: begin
				//启动全擦除操作
				if((flash_driver_idle == 1'b1) && (!(state_start & BE_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= BE_CMD;
					state_start <= state_start | BE_STATE;
				end
				//全擦除操作完成
				if((flash_driver_idle == 1'b1) && (state_start & BE_STATE) && (flash_start == 1'b0)) begin
					if(!(state_done & BE_STATE))
						state_done <= state_done | BE_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			BE_WAIT_STATE: begin
				//启动读状态寄存器操作
				if((flash_driver_idle == 1'b1) && (!(state_start & BE_WAIT_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= RDSR_CMD;
					state_start <= state_start | BE_WAIT_STATE;
				end
				//读状态寄存器操作完成
				if((flash_driver_idle == 1'b1) && (state_start & BE_WAIT_STATE) && (flash_start == 1'b0)) begin
					//检查擦除是否结束,若未结束则再次轮询状态寄存器
					if(flash_ew_busy == 1'b1)
						state_start <= state_start & ~BE_WAIT_STATE;
					else if(!(state_done & BE_WAIT_STATE))
						state_done <= state_done | BE_WAIT_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			PP_WREN_STATE: begin
				//启动写使能操作
				if((flash_driver_idle == 1'b1) && (!(state_start & PP_WREN_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= WREN_CMD;
					state_start <= state_start | PP_WREN_STATE;
				end
				//写使能操作完成
				if((flash_driver_idle == 1'b1) && (state_start & PP_WREN_STATE) && (flash_start == 1'b0)) begin
					if(!(state_done & PP_WREN_STATE))
						state_done <= state_done | PP_WREN_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			PP_STATE: begin
				//启动写flash操作
				if((flash_driver_idle == 1'b1) && (!(state_start & PP_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= PP_CMD;
					flash_addr <= 24'h1000;
					wr_data_len <= 9'd256;
					state_start <= state_start | PP_STATE;
				end
				//写操作完成
				if((flash_driver_idle == 1'b1) && (state_start & PP_STATE) && (flash_start == 1'b0)) begin
					if(!(state_done & PP_STATE))
						state_done <= state_done | PP_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			PP_WAIT_STATE: begin
				//启动读状态寄存器操作
				if((flash_driver_idle == 1'b1) && (!(state_start & PP_WAIT_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= RDSR_CMD;
					state_start <= state_start | PP_WAIT_STATE;
				end
				//读状态寄存器操作完成
				if((flash_driver_idle == 1'b1) && (state_start & PP_WAIT_STATE) && (flash_start == 1'b0)) begin
					//检查擦除是否结束,若未结束则再次轮询状态寄存器
					if(flash_ew_busy == 1'b1)
						state_start <= state_start & ~PP_WAIT_STATE;
					else if(!(state_done & PP_WAIT_STATE))
						state_done <= state_done | PP_WAIT_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			READ1_STATE: begin
				//启动读flash操作
				if((flash_driver_idle == 1'b1) && (!(state_start & READ1_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= READ_CMD;
					flash_addr <= 24'h1000;
					rd_data_len <= 9'd256;
					state_start <= state_start | READ1_STATE;
				end
				//读操作完成
				if((flash_driver_idle == 1'b1) && (state_start & READ1_STATE) && (flash_start == 1'b0)) begin
					if(!(state_done & READ1_STATE))
						state_done <= state_done | READ1_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			SSE_WREN_STATE: begin
				//启动写使能操作
				if((flash_driver_idle == 1'b1) && (!(state_start & SSE_WREN_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= WREN_CMD;
					state_start <= state_start | SSE_WREN_STATE;
				end
				//写使能操作完成
				if((flash_driver_idle == 1'b1) && (state_start & SSE_WREN_STATE) && (flash_start == 1'b0)) begin
					if(!(state_done & SSE_WREN_STATE))
						state_done <= state_done | SSE_WREN_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			SSE_STATE: begin
				//启动子扇区擦除操作
				if((flash_driver_idle == 1'b1) && (!(state_start & SSE_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= SSE_CMD;
					flash_addr <= 24'h1000;
					state_start <= state_start | SSE_STATE;
				end
				//子扇区擦除操作完成
				if((flash_driver_idle == 1'b1) && (state_start & SSE_STATE) && (flash_start == 1'b0)) begin
					if(!(state_done & SSE_STATE))
						state_done <= state_done | SSE_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			SSE_WAIT_STATE: begin
				//启动读状态寄存器操作
				if((flash_driver_idle == 1'b1) && (!(state_start & SSE_WAIT_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= RDSR_CMD;
					state_start <= state_start | SSE_WAIT_STATE;
				end
				//读状态寄存器操作完成
				if((flash_driver_idle == 1'b1) && (state_start & SSE_WAIT_STATE) && (flash_start == 1'b0)) begin
					//检查擦除是否结束,若未结束则再次轮询状态寄存器
					if(flash_ew_busy == 1'b1)
						state_start <= state_start & ~SSE_WAIT_STATE;
					else if(!(state_done & SSE_WAIT_STATE))
						state_done <= state_done | SSE_WAIT_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			READ2_STATE: begin
				//启动读flash操作
				if((flash_driver_idle == 1'b1) && (!(state_start & READ2_STATE)) && (flash_start == 1'b0)) begin
					flash_start <= 1'b1;
					flash_cmd <= READ_CMD;
					flash_addr <= 24'h1000;
					rd_data_len <= 9'd256;
					state_start <= state_start | READ2_STATE;
				end
				//读操作完成
				if((flash_driver_idle == 1'b1) && (state_start & READ2_STATE) && (flash_start == 1'b0)) begin
					if(!(state_done & READ2_STATE))
						state_done <= state_done | READ2_STATE;
				end
				//flash操作已经启动,复位启动标志
				if((flash_driver_idle == 1'b0) && (flash_start == 1'b1))
					flash_start <= 1'b0;
			end
			default: begin
				flash_start <= 1'b0;
				flash_cmd <= 8'h0;
				flash_addr <= 24'h0;
			end
		endcase
	end
end

//处理flash driver返回的数据,包括ID、读取数据、状态寄存器
always @(posedge sys_clk)begin
	if(!sys_rst_n) begin
		error_flag <= 1'b0;
		flash_ew_busy <= 1'h0;
		read_count <= 9'h0;
	end
	else begin
		case(current_state)
			RDID_STATE: begin
				//检查ID是否正确
				if(flash_id_flag == 1'b1) begin
					if(flash_id != 24'hef4018)
						error_flag <= 1'b1;
				end
			end
			BE_WAIT_STATE: begin
				//检查擦除是否完成
				if(flash_sr_reg_flag == 1'b1)
					flash_ew_busy <= flash_sr_reg[0];
			end
			PP_WAIT_STATE: begin
				//检查写是否完成
				if(flash_sr_reg_flag == 1'b1)
					flash_ew_busy <= flash_sr_reg[0];
			end
			READ1_STATE: begin
				if(rd_data_flag == 1'b1) begin
					//检查接收的数据
					if(rd_data != read_count[7:0])
						error_flag <= 1'b1;
					//接收计数
					read_count <= read_count + 9'h1;
				end
			end
			SSE_WAIT_STATE: begin
				//检查子扇区擦除是否完成
				if(flash_sr_reg_flag == 1'b1)
					flash_ew_busy <= flash_sr_reg[0];
			end
			READ2_STATE: begin
				if(rd_data_flag == 1'b1) begin
					//检查接收的数据
					if(rd_data != 8'hff)
						error_flag <= 1'b1;
				end
			end
		default: begin
			read_count <= 9'h0;
		end
		endcase
	end
end

//生成写入的数据
always @(posedge sys_clk)begin
	if(!sys_rst_n) begin
		wr_data <= 8'h0;
		write_count <= 9'h0;
	end
	else if(current_state == PP_STATE) begin
		if(wr_data_req == 1'b1) begin
			//生成写入数据
			wr_data <= write_count[7:0];
			//写入计数
			write_count <= write_count + 9'h1;
		end
	end
	else begin
		wr_data <= 0;
		write_count <= 0;
	end
end

flash_driver #(
	.SPI_CLK_PERIOD(4),
	.SPI_CS_DELAY(500),
	.DATA_REQ_ADVANCE(1)
)
flash_driver_inst0(
	.sys_rst_n(sys_rst_n),
	.sys_clk(sys_clk),

	.flash_start(flash_start),
	.flash_cmd(flash_cmd),
	.flash_addr(flash_addr),

	.wr_data_len(wr_data_len),
	.wr_data(wr_data),
	.wr_data_req(wr_data_req),

	.rd_data_len(rd_data_len),
	.rd_data(rd_data),
	.rd_data_flag(rd_data_flag),

	.flash_id(flash_id),
	.flash_id_flag(flash_id_flag),

	.flash_sr_reg(flash_sr_reg),
	.flash_sr_reg_flag(flash_sr_reg_flag),

	.flash_idle(flash_driver_idle),

	.spi_clk(spi_sclk),
	.spi_mosi(spi_mosi),
	.spi_miso(spi_miso),
	.spi_cs(spi_cs)
);

endmodule

仿真激励

`timescale 1ns / 1ps

module tb_spi_flash_rw_test( );

reg sys_rst_n;
reg sys_clk;

wire spi_mosi;
reg spi_miso;
wire spi_clk;
wire spi_cs;

wire alarm_led;

reg [7:0] miso_data;
reg signed [7:0] loop_cnt1;
reg [16:0] loop_cnt2;

initial begin
	sys_clk = 1'b0;
	sys_rst_n = 1'b0;
	#200
	sys_rst_n = 1'b1;
end

//产生MISO
initial begin
	spi_miso = 1'b1;

	//模拟读flash ID
	wait(spi_cs == 1'b0);
	miso_data = 8'h0;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	miso_data = 8'hef;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	miso_data = 8'h40;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	miso_data = 8'h18;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;

	//模拟写使能
	wait(spi_cs == 1'b0);
	miso_data = 8'h0;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;

	//模拟全擦除
	wait(spi_cs == 1'b0);
	miso_data = 8'h0;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;

	//模拟全擦除忙
	wait(spi_cs == 1'b0);
	miso_data = 8'h0;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	miso_data = 8'h01;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;
	//模拟全擦除空闲
	wait(spi_cs == 1'b0);
	miso_data = 8'h0;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	miso_data = 8'h00;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;

	//模拟写使能
	wait(spi_cs == 1'b0);
	miso_data = 8'h0;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;

	//模拟写
	wait(spi_cs == 1'b0);
	for(loop_cnt2 = 0; loop_cnt2 < 4; loop_cnt2 = loop_cnt2 + 1) begin
		miso_data = 8'h0;
		for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
			wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
			spi_miso = miso_data[loop_cnt1];
			wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
		end
	end
	for(loop_cnt2 = 0; loop_cnt2 < 256; loop_cnt2 = loop_cnt2 + 1) begin
		miso_data = 8'h0;
		for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
			wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
			spi_miso = miso_data[loop_cnt1];
			wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
		end
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;

	//模拟写忙
	wait(spi_cs == 1'b0);
	miso_data = 8'h0;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	miso_data = 8'h01;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;
	//模拟写空闲
	wait(spi_cs == 1'b0);
	miso_data = 8'h0;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	miso_data = 8'h00;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;

	//模拟读
	wait(spi_cs == 1'b0);
	for(loop_cnt2 = 0; loop_cnt2 < 5; loop_cnt2 = loop_cnt2 + 1) begin
		miso_data = 8'h0;
		for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
			wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
			spi_miso = miso_data[loop_cnt1];
			wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
		end
	end
	for(loop_cnt2 = 0; loop_cnt2 < 256; loop_cnt2 = loop_cnt2 + 1) begin
		miso_data = loop_cnt2[7:0];
		for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
			wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
			spi_miso = miso_data[loop_cnt1];
			wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
		end
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;

	//模拟写使能
	wait(spi_cs == 1'b0);
	miso_data = 8'h0;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;

	//模拟子扇区擦除
	wait(spi_cs == 1'b0);
	for(loop_cnt2 = 0; loop_cnt2 < 4; loop_cnt2 = loop_cnt2 + 1) begin
		miso_data = 8'h0;
		for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
			wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
			spi_miso = miso_data[loop_cnt1];
			wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
		end
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;

	//模拟子扇区擦除忙
	wait(spi_cs == 1'b0);
	miso_data = 8'h0;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	miso_data = 8'h01;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;
	//模拟子扇区擦除空闲
	wait(spi_cs == 1'b0);
	miso_data = 8'h0;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	miso_data = 8'h00;
	for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
		wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
		spi_miso = miso_data[loop_cnt1];
		wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;

	//模拟读
	wait(spi_cs == 1'b0);
	for(loop_cnt2 = 0; loop_cnt2 < 5; loop_cnt2 = loop_cnt2 + 1) begin
		miso_data = 8'h0;
		for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
			wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
			spi_miso = miso_data[loop_cnt1];
			wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
		end
	end
	for(loop_cnt2 = 0; loop_cnt2 < 256; loop_cnt2 = loop_cnt2 + 1) begin
		miso_data = 8'hff;
		for(loop_cnt1 = 7; loop_cnt1 >= 0; loop_cnt1 = loop_cnt1 - 1) begin
			wait((spi_clk == 1'b0) && (spi_cs == 1'b0));
			spi_miso = miso_data[loop_cnt1];
			wait((spi_clk == 1'b1) && (spi_cs == 1'b0));
		end
	end
	wait(spi_cs == 1'b1);
	spi_miso = 1'b1;
end

//产生时钟
always #10 sys_clk = ~sys_clk;

spi_flash_rw_test #(
	.ALARM_LED_PERIOD(25'd250)
)
tb_spi_flash_rw_test_inst0(
	.sys_rst_n(sys_rst_n),
	.sys_clk(sys_clk),

	.spi_mosi(spi_mosi),
	.spi_miso(spi_miso),
	.spi_sclk(spi_clk),
	.spi_cs(spi_cs),

	.alarm_led(alarm_led)
);

endmodule

引脚约束

create_clock -period 20.000 -name sys_clk -waveform {0.000 10.000} [get_ports sys_clk]

set_property IOSTANDARD LVCMOS15 [get_ports sys_clk]
set_property PACKAGE_PIN R4 [get_ports sys_clk]

set_property IOSTANDARD LVCMOS15 [get_ports sys_rst_n]
set_property PACKAGE_PIN U7 [get_ports sys_rst_n]

set_property IOSTANDARD LVCMOS15 [get_ports alarm_led]
set_property PACKAGE_PIN V9 [get_ports alarm_led]

set_property IOSTANDARD LVCMOS33 [get_ports spi_cs]
set_property PACKAGE_PIN N14 [get_ports spi_cs]

set_property IOSTANDARD LVCMOS33 [get_ports spi_miso]
set_property PACKAGE_PIN N13 [get_ports spi_miso]

set_property IOSTANDARD LVCMOS33 [get_ports spi_mosi]
set_property PACKAGE_PIN R14 [get_ports spi_mosi]

set_property IOSTANDARD LVCMOS33 [get_ports spi_sclk]
set_property PACKAGE_PIN P14 [get_ports spi_sclk]

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

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

相关文章

vs code中如何使用git

由于本地代码有了一些储备&#xff0c;所以想通过网址托管形式&#xff0c;之前一直使用了github&#xff0c;但是鉴于一直被墙&#xff0c;无法登录账号&#xff0c;所以选择了国内的gitee来作为托管网站。 gitee的网址&#xff1a;Gitee - 基于 Git 的代码托管和研发协作平台…

蓝桥杯-网络安全比赛(7)基础知识 HTTP、TTL、IP数据包、MSS、MTU、ARP、LLMNR、MDNS、NBNS。

1. IP中TTL值能够给我提供什么信息&#xff1f;2. IP头部中标志、13位偏移、32位源IP地址、目标IP、IP数据包格式&#xff0c;有多少字节3. IP头部中的16位标识是什么&#xff1f;4. MSS 和MTU分别有多大&#xff1f;5. 怎么获取路由IP信息&#xff1f;PING、NSLOOKUP、TRACERT…

记忆化搜索专题

前言 如果要记忆化搜索的话&#xff0c;如果数据是10的九次方&#xff0c;我们不可能开一个那么大的数组来存储&#xff0c;所以我们要学会用map来存储 leecode1553 class Solution {unordered_map<int, int> memo; public:int minDays(int n) {if (n < 1) {return n;…

渗透测试-信息收集

网络安全信息收集是网络安全领域中至关重要的一环&#xff0c;它涉及到对目标系统、网络或应用进行全面而细致的信息搜集和分析。这一过程不仅有助于理解目标网络的结构、配置和潜在的安全风险&#xff0c;还能为后续的渗透测试、风险评估和安全加固提供有力的支持。 在网络安…

linux_用户与组

用户与组 基于账号的访问控制 账号类型&#xff1a;用户账号(UID) 、组账号(GID) 用户账号简介 作用: 1.可以登陆操作系统 2.不同的用户具备不同的权限 唯一标识&#xff1a;UID&#xff08;编号从0开始的编号&#xff0c;默认最大60000&#xff09; 管理员root的UID&…

鸿蒙开发接口Ability框架:【(AbilityContext)】

AbilityContext AbilityContext是Ability的上下文环境&#xff0c;继承自Context。 AbilityContext模块提供允许访问特定于ability的资源的能力&#xff0c;包括对Ability的启动、停止的设置、获取caller通信接口、拉起弹窗请求用户授权等。 说明&#xff1a; 本模块首批接口…

算法设计与分析 例题 绘制Huffman树、循环赛、分治、最短路与动态规划

1.考虑用哈夫曼算法来找字符a,b,c,d,e,f 的最优编码。这些字符出现在文件中 的频数之比为 20:10:6:4:44:16。要求&#xff1a; &#xff08;1&#xff09;&#xff08;4 分&#xff09;简述使用哈夫曼算法构造最优编码的基本步骤&#xff1b; &#xff08;2&#xff09;&…

代码已经推送到远程,如何退回之前的提交记录

现状&#xff1a;代码已经推送到远程&#xff0c;如何退回之前的提交记录 Sourcetree工具实现 最后使用命令强制推送&#xff1a; git push origin dev --forceidea 工具实现 强行推送

网络编程学习笔记1

文章目录 一、socket1、创建socket2、网络通信流程3、accept()函数4、signal()函数5、recv()函数6、connect()函数 二、I/O多路复用1.select模型2.poll模型3.epoll模型 注 一、socket 1、创建socket int socket(int domain,int type,int protocol); //返回值&#xff1a;一个…

ssm120基于SSM框架的金鱼销售平台的开发和实现+jsp

金鱼销售平台 摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于金鱼销售平台当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了金鱼销售平台&#xff0c;它彻底改…

电商平台商品数据的价值在哪里?如何实现批量抓取?

一、电商平台商品数据的价值探秘 在数字经济的浪潮中&#xff0c;电商平台商品数据如同一座蕴藏着无尽宝藏的矿山&#xff0c;其价值远超过我们表面的认知。今天&#xff0c;就让我们一起揭开这座矿山的神秘面纱&#xff0c;探寻其中的奥秘。 首先&#xff0c;电商平台商品数…

FPGA第2篇,FPGA与CPU GPU APU DSP NPU TPU 之间的关系与区别

简介&#xff1a;首先&#xff0c;FPGA与CPU GPU APU NPU TPU DSP这些不同类型的处理器&#xff0c;可以被统称为"处理器"或者"加速器"。它们在计算机硬件系统中承担着核心的计算和处理任务&#xff0c;可以说是系统的"大脑"和"加速引擎&qu…

AI算法-高数5-线性代数1-基本概念、向量

线性代数&#xff1a;主要研究1、张量>CV计算机视觉 2、研究张量的线性关系。 深度学习的表现之所以能够超过传统的机器学习算法离不开神经网络&#xff0c;然而神经网络最基本的数据结构就是向量和矩阵&#xff0c;神经网络的输入是向量&#xff0c;然后通过每个矩阵对向量…

自动驾驶技术与传感器数据处理

目录 自动驾驶总体架构 感知系统 决策系统 定位系统 ​计算平台​ 仿真平台​ 自动驾驶公开数据集 激光点云 点云表征方式 1) 原始点云 2) 三维点云体素化 3)深度图 4)鸟瞰图 点云检测障碍物的步骤 PCL点云库 车载毫米波雷达 车载相机 设备标定 自动驾驶…

DMA原理、传输过程及传输方式

1.DMA DMA(Direct Memory Access&#xff0c;直接存储器访问)&#xff0c;是硬件实现存储器与存储器之间或存储器与I/O设备之间直接进行数据传输的内存技术&#xff0c;它允许不同速度的硬件设备(外设到内存、内存到外设、内存到内存、外设到外设)进行沟通&#xff0c;而不需要…

MySQL相关文件的介绍

其中的pid-file/var/run/mysqld/mysqld.pid是用来定义MySQL的进程ID的信息的&#xff0c; 这个ID是操作系统分配给MySQL服务进程的唯一标识&#xff0c;使得系统管理员可以轻松识别和管理该进程。 其中的log-error/var/log/mysqld.log是MySQL的错误日志文件&#xff0c;如果有…

C++string 类的常用方法

string (构造函数) (1) default 构造长度为零字符的空字符串。 (2) copy 构造 str 的副本。 (3) substring 复制从字符位置 pos 开始并跨越 len 字符的 str 部分&#xff08;如果任一 str 太短或 len 为 string&#xff1a;&#xff1a;npos&#xff0c;则复制 str 的末尾…

Ardupilot开源代码之Rover上路 - 后续1

Ardupilot开源代码之Rover上路 - 后续1 1. 源由2. 问题汇总2.1 问题1&#xff1a;飞控选择2.2 问题2&#xff1a;飞控安装位置和固定2.3 问题3&#xff1a;各种插头、插座配套2.4 问题4&#xff1a;分电板缺陷2.5 问题5&#xff1a;电机编码器接线及正反向问题2.6 问题6&#x…

docker-compose集成elk(基于logstash+filebeat)采集java和nginx日志

1.准备compose.yml编排式文件 services: #日志信息同步logstash:container_name: logstashimage: docker.elastic.co/logstash/logstash:7.17.14 #logstash:command: logstash -f /usr/share/logstash/pipeline/logstash.confdepends_on:- elasticsearchrestart: on-failurepo…

车机HMI:驾驶员小命握在UI设计师手,九大法则必须遵循。

本文给大家列举了九大法则&#xff0c;欢迎评论点赞交流。 在车机HMI设计中&#xff0c;为了降低驾驶员的反应时间并增加驾驶安全性&#xff0c;可以遵循以下UI设计法则&#xff1a; 易读性和可识别性 确保界面上的文本和图标清晰易读&#xff0c;避免使用过小、过于复杂或模…