1.摘要:
对于这款芯片来说,如果想要更高的频率就需要使用QPI模式,或者SPI QUAD模式。具体详情看前面两篇介绍(基于FPGA的APS6404L-3SQR QSPI PSRAM驱动设计(1))和基于FPGA的APS6404L-3SQR QSPI PSRAM驱动设计(2)。
2.FPGA逻辑设计
为了尽可能的验证芯片功能,QPI模式的状态机我是这样设计的:

具体代码逻辑如下:
parameterIDLE=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的四根数据线具体代码为:
endelse 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
)(
inputwirei_clk ,
inputwirei_rst ,
outputwireo_spi_clk ,
outputwireo_spi_cs ,
/*synthesis PAP_MARK_DEBUG="1"*/inputwirei_qspi_sio0,
/*synthesis PAP_MARK_DEBUG="1"*/input wirei_qspi_sio1,
/*synthesis PAP_MARK_DEBUG="1"*/inputwirei_qspi_sio2,
/*synthesis PAP_MARK_DEBUG="1"*/input wirei_qspi_sio3,
outputwireo_qspi_sio0,
outputwireo_qspi_sio1,
outputwireo_qspi_sio2,
outputwireo_qspi_sio3,
/*synthesis PAP_MARK_DEBUG="1"*/output wireo_qspi_cmd_flag,
/*synthesis PAP_MARK_DEBUG="1"*/output wireo_qspi_addr_flag,
/*synthesis PAP_MARK_DEBUG="1"*/output wireo_qspi_wrdata_flag,
/*synthesis PAP_MARK_DEBUG="1"*/output wireo_qspi_rddata_flag,
inputwirei_fifo_full,
inputwirei_qspi_wren,
inputwirei_qspi_rden,
inputwire[7:0]i_write_length,
inputwire[7:0]i_read_length,
inputwirei_exit_qpi_mode,
output wireo_wr_requset,
/*synthesis PAP_MARK_DEBUG="1"*/inputwire[23:0]i_user_wr_addr,
/*synthesis PAP_MARK_DEBUG="1"*/inputwire[7 :0] i_user_data ,
/*synthesis PAP_MARK_DEBUG="1"*/inputwirei_user_data_val,
/*synthesis PAP_MARK_DEBUG="1"*/inputwire[7 :0] i_user_cmd ,
/*synthesis PAP_MARK_DEBUG="1"*/inputwirei_user_cmd_valid ,
outputwireo_user_ready ,
outputwire[7:0] o_user_read_data ,
outputwire o_user_read_valid
);
循环读写debug,信号抓取结果:


