用 FPGA 作为 QSPI 通信从机时,涉及到的一些要点,简单记录一下。
一、QSPI 的通信机制
QSPI 通信的具体原理不再详述,可以参考博文:
仅贴出来两张 QSPI 4 线数据通信时的时序。


根据 QSPI 的读写时序可以看出,读写一帧 QSPI 数据,由命令+地址+空指令(读)+数据组成,这里可以结合 MCU 自定义各个部分的位宽,例如设计 6 位命令+10 位地址+8 位空指令(读)+32 位数据的形式。下图是 FPGA 处理 QSPI 的框图。

处理流程大致如下:FPGA 接收到 QSPI 数据帧后解析出命令、地址和数据,即转成并口通信,然后再根据自定义的命令和地址读写相应的控制寄存器和状态寄存器,以及数据缓存 FIFO。
二、仿真分析
从上面的 QSPI 读写波形可以看出,一帧完整的 QSPI 读时序由指令+地址+空指令周期+数据组成,而一帧完整的 QSPI 写时序由指令+地址+数据组成。
结合 QSPI 的读写时序和 FPGA 端处理流程,分别写出读写时序的状态机以及 Test Bench 文件。下面是 FPGA 作为从机实现的 QSPI 读写仿真波形。

仿真波形分别完成了一次主机写操作和读操作。主机 QSPI 写操作的详细仿真波形:

写操作是主机向地址 10’h130 写数据 32’haabbccdd,可以看出在写操作结束,状态机能够正确的发出写命令 master_wr 高电平、地址以及数据。
主机 QSPI 读操作的详细仿真波形:

读操作是主机读取地址 10’h141 的值,读操作相较于写操作而言,地址和数据之间多了 8 个周期的空指令,待 FPGA 准备数据。在读操作结束,主机能够正确的读取到 10’h141 地址的数据 32’hccddeeff。
三、设计要点
3.1 QSPI 空指令周期
在 QSPI 总线读操作时,如果 FPGA 给出的数据足够快,一般小于半个 QSPI 的时钟周期,那么不需要空指令周期;反之,如果不能在半个 QSPI 的时钟周期内准备好数据,则需要根据实际情况,增加 QSPI 总线的空指令周期。
3.2 读写使能周期
QSPI 总线转成并行总线时,读写使能信号应满足只有一个系统时钟周期的有效电平,否则会出现数据连续读写的情况。
四、调试记录
在调试过程中,曾遇到一些问题,尤其是经常会出现电平异常的情况,如图 7 所示。这样的情况均是 FPGA 处理 QSPI 的状态机出现异常导致。结合 QSPI 读写时序和仿真波形可以解决。

最后遇到一个蜜汁困惑的问题:先烧写 MCU 程序,再烧写 FPGA 程序,数据输出正确;反之,错误,但是如果将示波器表笔加在 qspi_clk 上,则输出又正确。正确的 QSPI 传输波形如图 8 图 9 所示,只测了 QSPI_CLK 和 QSPI_IO0。


对于出现问题的情况,先抓取第一个波形,指令段有部分异常波形,数据段成高阻态。如图 10 图 11 所示。


后续的波形,指令和地址阶段都正确,数据阶段不正确,有点像高阻态。猜测由于某种原因导致 FPGA 的 qspi 接口未转换成输出接口,而 MCU 的 qspi 接口转换成了输入接口。有一种办法可以验证,即 MCU 发送 qspi 读命令之前增加一个 FPGA 复位命令,看数据输出是否正常。通过实测,数据仍是错误。
FPGA 不下载程序,只下载 MCU 的程序,观察波形,发现输出正常;FPGA 下载程序的情况下,波形如上段文字分析,说明 FPGA 的时序影响到了 MCU 发出的波形。
进一步排查 FPGA 收发 qspi 的代码,屏蔽 FPGA 发送端的代码,FPGA 接收数据正常,波形正常。再进一步定位到 qspi_rd => 1;出现的问题,同时将该信号输出到管脚,发现下载 MCU 程序的时候出现了高电平。由于表笔夹在 qspi_clk 管脚会改善结果,所以只测了 qspi_rd 波形,如图 12 所示。

然后,在 SignalTap 中同时观察 qspi_clk 和 qspi_rd 波形,当 MCU 下载程序的时候,qspi_clk 会出现高低电平跳变,这样 FPGA 状态机就会乱掉,进一步导致 qspi_rd 为高。如图 13 所示。

根本原因:qspi_clk 引脚未加下拉电阻造成 MCU 下载程序时波形不稳定,这也解释了为什么 qspi_clk 放置示波器表笔,数据传输正常。
解决办法:1)qspi_clk 加下拉电阻,qspi_cs 加上拉电阻(虽然 SignalTap 采集到的数据是稳定的高电平;另外,主机采用的是 STM32,QSPI 部分参考的官方原理图);2)FPGA 收发 qspi 数据的状态机引入 qspi_cs 低电平判断来运行状态机,最初设计的状态机是直接通过 qspi_clk 的跳变沿实现接收数据和发送数据,如果 qspi_clk 有扰动,整个状态机就会乱掉,所以加上 qspi_cs 低电平判断 qspi 数据传输的开始。
20210315 更新
在 MCU 连续多次操作 QSPI 写命令时,发现 FPGA 收到的命令会有遗漏。通过 SignalTap 连续抓取 FPGA 波形,MCU 单步调试,发现确实会有命令遗漏的情况,如下图所示。

图中,遗漏了向地址 0x165 写数据的命令,这样就是 FPGA 接收 QSPI 命令的时序出现了问题。
由于设计的 QSPI 接口时钟频率大概几十 MB(MCU 为 QSPI 主机),考虑到后续处理数据可以采用更高的频率,如 100Mhz,所以 QSPI 命令转并口时,采用了系统时钟频率,只不过这里的处理有些草率,导致出现亚稳态的情况。原始的处理代码:
reg [2:0] state; always@(posedge sys_clk or negedge rst_n) if(!rst_n) begin state <= 3'd0; qspi_frame_cmd_in <= 16'h0; qspi_frame_cmd_in_valid_1 <= 0; qspi_frame_cmd_in_valid_2 <= 0; end else case(state) 3'd0: begin state <= state + 1; // qspi_frame_cmd_in <= 16'h0; qspi_frame_cmd_in_valid_1 <= 0; qspi_frame_cmd_in_valid_2 <= 0; end 3'd1: begin if(cnt == 3'd1) begin state <= state + 1; qspi_frame_cmd_in[15:8] <= data_in; qspi_frame_cmd_in_valid_1 <= 0; qspi_frame_cmd_in_valid_2 <= 0; end else state <= 3'd1; end 3'd2: begin if(cnt == 3'd2) begin state <= state + 1; qspi_frame_cmd_in[7:0] <= data_in; qspi_frame_cmd_in_valid_1 <= 1; qspi_frame_cmd_in_valid_2 <= 0; end else state <= 3'd2; end 3'd3: begin state <= state + 1; qspi_frame_cmd_in <= qspi_frame_cmd_in; qspi_frame_cmd_in_valid_1 <= 0; qspi_frame_cmd_in_valid_2 <= 0; end 3'd4: begin if(cnt == 3'd3) begin state <= 0; qspi_frame_cmd_in <= qspi_frame_cmd_in; qspi_frame_cmd_in_valid_1 <= 0; qspi_frame_cmd_in_valid_2 <= 1; end else state <= 3'd4; end default: state <= 0; endcase
在系统时钟下,通过判断 cnt 是否满足条件来使一帧 QSPI 数据中的命令和数据标志位置 1,而 cnt 是由采样 QSPI 数据的状态机来改变的,所以导致命令和数据标志置 1 会出现亚稳态。
修改为:将命令和数据标志置 1 放在采样 QSPI 数据的状态机下,再通过跨时钟域单比特信号处理,将低时钟频率的信号转换成高时钟频率的信号,也就是将 QSPI 时钟频率下的命令和数据转换成系统时钟频率下的命令和数据。仿真波形如下:

qspi_frame_cmd_in_valid_1_pos 和 qspi_frame_cmd_in_valid_1_pos 分别是跨时钟域处理后的信号,进一步转换并口的读写信号。

再通过 SignalTap 抓取波形,经多次测试,并未发现命令遗漏的情况了。
扫码关注尚为网微信公众号

原创文章,作者:sunev,如若转载,请注明出处:https://www.sunev.cn/embedded/700.html
评论列表(1条)
楼主能不能分享下通信代码,想学习学习