基于FPGA的APS6404L-3SQR QSPI PSRAM驱动设计(3)

1.摘要:

对于这款芯片来说,如果想要更高的频率就需要使用QPI模式,或者SPI QUAD模式。具体详情看前面两篇介绍(基于FPGA的APS6404L-3SQR QSPI PSRAM驱动设计(1))和基于FPGA的APS6404L-3SQR QSPI PSRAM驱动设计(2)

2.FPGA逻辑设计

为了尽可能的验证芯片功能,QPI模式的状态机我是这样设计的:

具体代码逻辑如下:

复制代码
parameter		IDLE		=	5'd0	,
				QPI_WRCMD	=	5'd1	,
				QPI_WRADDR	=	5'd2	,
				QPI_WRDATA	=	5'd3	,
				QPI_WREND	=	5'd4	,
				QPI_RDWAIT	=	5'd5	,
				QPI_RDDATA	=	5'd6	,
				QPI_RDEND	=	5'd7	;

always@(posedge i_clk,posedge i_rst)
begin
	if(i_rst)
		r_qspi_fsm_current	<=	'd0	;
	else 
		r_qspi_fsm_current	<=	r_qspi_fsm_next	;
end 

always@(*)
begin
	if(i_rst)
		r_qspi_fsm_next	<=	'd0	;
	else begin
		case (r_qspi_fsm_current)
			IDLE		:	
				if (w_user_active)
					r_qspi_fsm_next	<=	QPI_WRCMD	;
				else 
					r_qspi_fsm_next	<=	IDLE	;
			QPI_WRCMD		:
				if (r_cnt == 'd1 && r_qpi_cnt)
					if (i_exit_qpi_mode)	//退出QPI模式if (i_fsm_current == 'd10)
						r_qspi_fsm_next	<=	QPI_WREND	;
					else 
						r_qspi_fsm_next	<=	QPI_WRADDR	;
				else 
					r_qspi_fsm_next	<=	QPI_WRCMD		;
			QPI_WRADDR	:
				if (i_qspi_wren && r_cnt == 'd5 && r_qpi_cnt)
					r_qspi_fsm_next	<=	QPI_WRDATA	;
				else if (i_qspi_rden && r_cnt == 'd5 && r_qpi_cnt)
					r_qspi_fsm_next <= QPI_RDWAIT;
				else 
					r_qspi_fsm_next	<=	QPI_WRADDR	;
			QPI_WRDATA	:
				if (i_user_data_val && r_qpi_cnt && (r_wrbyte_cnt == i_write_length))
					r_qspi_fsm_next	<=	QPI_WREND	;
				else 
					r_qspi_fsm_next	<=	QPI_WRDATA	;
			QPI_WREND	:
				r_qspi_fsm_next	<=	IDLE	;
			QPI_RDWAIT	:
				if (r_cnt == 'd5 && r_qpi_cnt)
					r_qspi_fsm_next	<=	QPI_RDDATA	;
				else 
					r_qspi_fsm_next	<=	QPI_RDWAIT	;
			QPI_RDDATA	:
				if (ro_user_read_valid  & (r_rdbyte_cnt == i_read_length))
					r_qspi_fsm_next	<=	QPI_RDEND	;
				else 
					r_qspi_fsm_next	<=	QPI_RDDATA	;
			QPI_RDEND	:
				r_qspi_fsm_next	<=	IDLE	;
			default		:
				r_qspi_fsm_next	<=	IDLE	;
		endcase 
	end 
end 

2.1、IDLE状态

在初始状态的跳转和SPI一样,这要此模块准备就绪(o_user_ready为高)且有命令输入即跳出初始状态到达写命令状态。

2.2、QPI_WRCMD状态

同SPI模块类似,使用r_cnt 和 r_qpi_cnt作为标志状态。同时,既然已经使用了QPI模块那说明在SPI模块肯定已经配置了进入QPI模式,所以在QPI模块除了读写可能还需要一个退出QPI模式的指令,当输入此指令时不需要地址和数据。8bit命令输出完就跳到SPI模式了。如下图所示我这里是以退出QPI模式画的时序图,读写命令的话则跳到写地址状态(QPI_WRADDR):

对于写命令,在(cmd_flag & r_qpi_cnt)锁存下,将命令信号i_user_cmd复制给QPI的四根数据线具体代码为:

复制代码
	end else if (cmd_flag & r_qpi_cnt) begin
		rio_qspi_mosi0	<=	i_user_cmd[(2-r_cnt)*4 - 4];//8
		rio_qspi_mosi1	<=	i_user_cmd[(2-r_cnt)*4 - 3];//7
		rio_qspi_mosi2	<=	i_user_cmd[(2-r_cnt)*4 - 2];//6
		rio_qspi_mosi3	<=	i_user_cmd[(2-r_cnt)*4 - 1];//5

2.3、QPI_WRADDR状态

地址没什么好说的和写命令几乎是一模一样,命令是8bit,地址是24bit/4 = 6个clk,所以r_cnt技术到5即可。然后根据是写命令还是读命令分别跳转到QPI_WRDATA(写数据)或者QPI_RDWAIT(读等待)状态。具体时序图如下:

对于写地址,在(addr_flag & r_qpi_cnt)锁存下,将命令信号i_user_wr_addr复制给QPI的四根数据线具体代码为:

复制代码
	end	else if (addr_flag & r_qpi_cnt)	begin
		rio_qspi_mosi0	<=	i_user_wr_addr[(6-r_cnt)*4 - 4];//4
		rio_qspi_mosi1	<=	i_user_wr_addr[(6-r_cnt)*4 - 3];//5
		rio_qspi_mosi2	<=	i_user_wr_addr[(6-r_cnt)*4 - 2];//6
		rio_qspi_mosi3	<=	i_user_wr_addr[(6-r_cnt)*4 - 1];//7

2.4、QPI_WRDATA状态

写数据状态跳转也是换汤不换药,一个样子。无非就是多加一个写计数器(r_wrbyte_cnt),当写计数器到了写突发长度时候跳出写数据状态。

复制代码
always@(posedge i_clk,posedge i_rst)
begin
	if(i_rst)
		r_wrbyte_cnt	<=	'd0	;
	else if (i_user_data_val && r_qpi_cnt && (r_wrbyte_cnt == i_write_length))
		r_wrbyte_cnt	<=	'd0	;
	else if (i_user_data_val && r_qpi_cnt)
		r_wrbyte_cnt	<= r_wrbyte_cnt + 'd1	;
end 

同样对于数据,由于是一个clk传输4bit,且数据是一直传输,所以我们对r_qpi_cnt在进行二分频处理得出r_wrfifo_flag信号,在根据r_wrfifo_flag的上升沿和下降沿进行不同位的写入:

复制代码
always@(posedge i_clk,posedge i_rst)
begin
	if(i_rst)
		r_wrfifo_flag	<=	'd0	;
	else if (r_qpi_cnt)
		r_wrfifo_flag	<= ~r_wrfifo_flag	;
	else 
		r_wrfifo_flag	<=	r_wrfifo_flag	;
end 

	end else if (wr_data_flag & r_qpi_cnt)	begin
		if (!r_wrfifo_flag)	begin
			rio_qspi_mosi0	<=	i_user_data[P_DATA_WIDTH - 4];//4
			rio_qspi_mosi1	<=	i_user_data[P_DATA_WIDTH - 3];//5
			rio_qspi_mosi2	<=	i_user_data[P_DATA_WIDTH - 2];//6
			rio_qspi_mosi3	<=	i_user_data[P_DATA_WIDTH - 1];//7
		end else begin
			rio_qspi_mosi0	<=	i_user_data[P_DATA_WIDTH - 8];//4
			rio_qspi_mosi1	<=	i_user_data[P_DATA_WIDTH - 7];//5
			rio_qspi_mosi2	<=	i_user_data[P_DATA_WIDTH - 6];//6
			rio_qspi_mosi3	<=	i_user_data[P_DATA_WIDTH - 5];//7
		end 

具体如下图所示:黄色线是第一个clk写入0000,蓝色线是第二个clk写入0000,第一个数据即为0000_0000。同理第二个数据为0000_0001.。。。。。

2.5、QPI_RDDATA状态

读等待状态就不记录了,一个样子。到读数据状态时需要接受来自PSRAM的读数据,和写数据一样,反过来即可,加一个读计数器,读到突发长度跳出状态。

复制代码
always@(posedge i_clk,posedge i_rst) 
begin
	if(i_rst)
		r_rdbyte_cnt <= 'd0;
	else if (ro_user_read_valid & (r_rdbyte_cnt == i_read_length))
		r_rdbyte_cnt <= 'd0;
	else if (ro_user_read_valid)	
		r_rdbyte_cnt <= r_rdbyte_cnt + 'd1;
	else 
		r_rdbyte_cnt <= r_rdbyte_cnt;
end

always@(negedge o_spi_clk,posedge i_rst)
begin
	if(i_rst)
		ro_user_read_data <= 'd0;
	else if (r_rd_data_flag[1] & r_qpi_cnt)	begin
		if (r_wrfifo_flag)	begin
			ro_user_read_data[P_DATA_WIDTH - 1] <= i_qspi_sio3;
			ro_user_read_data[P_DATA_WIDTH - 2] <= i_qspi_sio2;
			ro_user_read_data[P_DATA_WIDTH - 3] <= i_qspi_sio1;
			ro_user_read_data[P_DATA_WIDTH - 4] <= i_qspi_sio0;
		end  else begin
			ro_user_read_data[P_DATA_WIDTH - 5] <= i_qspi_sio3;
			ro_user_read_data[P_DATA_WIDTH - 6] <= i_qspi_sio2;
			ro_user_read_data[P_DATA_WIDTH - 7] <= i_qspi_sio1;
			ro_user_read_data[P_DATA_WIDTH - 8] <= i_qspi_sio0;
		end 
	end else 
		ro_user_read_data <= ro_user_read_data;
end

至于读出数据标志,也一样和读数据的条件一样。

复制代码
always@(posedge i_clk,posedge i_rst) 
begin
	if(i_rst)
		ro_user_read_valid <= 'd0;
	else if (r_rd_data_flag[1] & r_qpi_cnt & !r_wrfifo_flag)	
		ro_user_read_valid <= 'd1;
	else 
		ro_user_read_valid <= 'd0;
end

具体结果如下图所示,读出数据为00,01,02.。。。。。。。。。。。。。。:

3、顶层端口

复制代码
qspi_drive#(
    parameter               	P_DATA_WIDTH        	= 8 ,
							P_ADDR_WIDTH		= 24,
							P_READ_DATA_WIDTH   	= 8 , 
							P_CPOL              	= 0 ,
							P_CPHL              	= 0 
)(                  
	input	wire					i_clk               ,
	input	wire					i_rst               ,

	output	wire					o_spi_clk           ,
	output	wire					o_spi_cs            ,
	
	/*synthesis PAP_MARK_DEBUG="1"*/input	wire					i_qspi_sio0		,
	/*synthesis PAP_MARK_DEBUG="1"*/input 	wire					i_qspi_sio1		,
	/*synthesis PAP_MARK_DEBUG="1"*/input	wire					i_qspi_sio2		,
	/*synthesis PAP_MARK_DEBUG="1"*/input 	wire					i_qspi_sio3		,
	output	wire					o_qspi_sio0		,
	output	wire					o_qspi_sio1		,
	output	wire					o_qspi_sio2		,
	output	wire					o_qspi_sio3		,
	/*synthesis PAP_MARK_DEBUG="1"*/output 	wire					o_qspi_cmd_flag	,
	/*synthesis PAP_MARK_DEBUG="1"*/output 	wire					o_qspi_addr_flag	,
	/*synthesis PAP_MARK_DEBUG="1"*/output 	wire					o_qspi_wrdata_flag	,
	/*synthesis PAP_MARK_DEBUG="1"*/output 	wire					o_qspi_rddata_flag	,

	input	wire					i_fifo_full	,
	input	wire					i_qspi_wren		,
	input	wire					i_qspi_rden		,
	input	wire	[7:0]			i_write_length	,
	input	wire	[7:0]			i_read_length	,
	input	wire					i_exit_qpi_mode	,
	output 	wire					o_wr_requset	,

	/*synthesis PAP_MARK_DEBUG="1"*/input	wire	[23:0]			i_user_wr_addr	,
	/*synthesis PAP_MARK_DEBUG="1"*/input	wire	[7 :0]   		i_user_data         ,
	/*synthesis PAP_MARK_DEBUG="1"*/input	wire					i_user_data_val	,
	/*synthesis PAP_MARK_DEBUG="1"*/input	wire	[7 :0]   		i_user_cmd        ,
	/*synthesis PAP_MARK_DEBUG="1"*/input	wire					i_user_cmd_valid       ,
	output	wire					o_user_ready        ,

	output	wire	[7:0]   			o_user_read_data    ,
	output	wire                        o_user_read_valid   
);

循环读写debug,信号抓取结果:

相关推荐
仲南音1 天前
Xilinx FPGA——时钟IP核MMCM、PLL
fpga开发
I'm a winner2 天前
基于Xilinx FPGA的LVDS高速串行通信系统(三)--数据训练及握手机制【文末源码】
fpga开发
upper20202 天前
coremark移植坑之-nostartfiles -nostdlib参数
学习·fpga开发·coremark移植·riscv设计
Eloudy2 天前
Vivado 纯命令行构建 FPGA 项目教程
fpga开发
Rambo.xia2 天前
AXI跨时钟域与数据一致性——CDC FIFO深度不够、写后读冒险、多Master仲裁、乱序返回,出了Bug根本查不出来
fpga开发·bug
upper20202 天前
FPGA部署卷积神经网络识别MINST图片
fpga开发·卷积神经网络·minst·minst识别·部署神经网络·神经网络识别图片
你是我的解忧王子3 天前
嵌入式 ADC采样 原始数据分析上层(平均值 波动率 正态分布 线性补偿 系统误差)
fpga开发·数据挖掘·数据分析
9527华安3 天前
FPGA纯verilog代码ISP图像处理培训教程,基于IMX214,提供工程源码+视频教程+FPGA开发板
图像处理·fpga开发·isp·imx214·mipi
千寻xun4 天前
二、实战篇-NVME SSD控制之ZYNQ实现(六)-读写NVME SSD硬盘数据
fpga开发·nvme·nvme ssd
千寻xun4 天前
二、实战篇-NVME SSD控制之ZYNQ实现(三)
fpga开发·nvme ssd·nvme协议