基于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,信号抓取结果:

相关推荐
CoderIsArt2 小时前
FPGA实现量子计算机仿真器重要论文
fpga开发·量子计算
上班最快乐4 小时前
基于FPGA的APS6404L-3SQR QSPI PSRAM驱动设计(4)
fpga开发
LCMICRO-133108477461 天前
长芯微LDC64115完全P2P替代AD4115,是一款低功耗、低噪声、24位、Σ-Δ(Σ-Δ)模数转换器(ADC)
stm32·单片机·嵌入式硬件·fpga开发·硬件工程·模数转换器
inquisiter1 天前
plic中断级联设计和使用
fpga开发·riscv
JSMSEMI111 天前
JSM3488E RS‑485/RS‑422 收发器芯片
fpga开发
学习永无止境@2 天前
Vivado FPGA程序压缩
fpga开发
daxi1502 天前
Verilog入门实战——第2讲:核心语法基础(数据类型+赋值语句)
fpga开发·fpga
嵌入式-老费2 天前
Linux camera驱动开发(vivado hls不能导出ip的问题)
图像处理·fpga开发
CoderIsArt2 天前
FPGA量子计算教学平台设计方案与实现步骤
fpga开发·量子计算