FPGA与MCU之间的通信想必是很多异构人极为头疼的难题。如果每次写一个工程都要大费周章重写通信逻辑、通信协议之类的东西,不仅耗费心神,而且浪费时间。本文基于安陆PH1A90SBG484,提出一个已经通过门级仿真验证的通用通信界面解决方案。详细代码见以下链接:
Github代码仓库
SPI及FPGA平台简介
FPGA开发板:米联客MLK_S201_AP106开发板(安路官方PH1A90验证板改版),主芯片PH1A90SBG484-2,等效LUT4资源约110K,带有一个FMC-LPC连接器,适合用于一般高速采集应用。
SPI协议:CPOL=0,CPHA=0,8bit通信,MSB First。建议使用带有外设DMA的、至少有一主一从两个SPI的MCU平台。
使用SPI的优势
首先,SPI作为已经提出了40年的串行通信协议,拥有技术成熟、应用范围广的特点。从51单片机到酷睿12代,都或多或少有SPI通信的接口。而单片机上,SPI通常会配备相应的收发DMA,这也为大规模数据高速传输提供了可能。
其次,SPI下限低,上限高。SPI典型时序包括一个随路时钟信号和两根串行数据线、一根使能数据线。这个物理结构使得SPI的数据对齐相对简单。经典的SPI协议最高速度可以达到36Mbit/s,优化后的SPI,其链路速度可以达到100Mbit/s甚至更高。当然,本实现受限于链路稳定性,不建议链路速度超过5Mbit/s。
最后,安路官方对于SPI的支持也比较完善,提供了一个SPI IP核,可配置为主机模式或者从机模式。我只需要编写SPI之后的通信逻辑就够了,这大大降低了这个通信界面的编写难度(手搓SPI总会遇到各种各样的意外情况)。
系统框图及概念明晰
这个FPGA-MCU通用通信界面(后简称“通信界面”)在FPGA侧实现,实测使用逻辑资源约1K(实现后可优化至不足1K),用作外部器件通过SPI与FPGA内部逻辑的通信。这个通信界面需要一主一从两条SPI通道,参数均为:8位数据,MSB First,CPOL=0(空闲时时钟低电平),CPHA=0(有效时第一个时钟沿数据有效)。
在下文中,“主通道”与“从通道”的概念都是站在MCU(外部主机,不一定是MCU,也可以是SOC甚至是X86主机,但是这里简称为MCU)角度进行阐述的。也就是说,MCU的“主通道”对接的是上图中的SPI从机IP核
,MCU的“从通道”对接的是上图中的SPI主机IP核
。
主通道作为MCU侧可以完全自主控制的通道,用作读写Config RAM
内的内容、发出SPI事务中断
以及设置Trans状态机
的相关参数。这些功能都由主状态机
实现。其中,读写Config RAM
的方式是段式读写。MCU发送的8位地址将会被左移8位,拓展为16位地址,作为读写开始地址;MCU发送的8位数据长度则指示将要读写的数据长度。这样设计可以最大程度节约时序开销,达到尽可能快的读写速度和尽可能高的读写效率。
从通道作为FPGA可以控制的通道,由MCU通过主通道控制,有四种发送模式:不发送、连续发送一个连续地址空间的数据、连续发送单个地址空间的数据以及由Trans发送中断
触发发送一个连续地址空间的数据。FPGA复位时为不发送状态。
MCU还可以通过发送相应的指令,由主状态机
对FPGA内部逻辑发出SPI事务中断
,提供一些模块的激活信号。一共有16个中断,分为高字节和低字节两组,
协议详解
读写数据模式
Config RAM
的位宽是8位,深度是64k,正好是一个16位地址的最大深度。因此,8位address
参数在写入FPGA之后,读写数据的基地址就将会是address
左移8位所组成的16位值。也就是说,如果address
写入的是0x10
,则将会从0x1000
开始进行读写(包含0x1000
)length
个字节。
如果进入的是写模式,则在length
之后跟length+1
个字节的数据即可:
如果进入的是读模式,则FPGA将会忽略length
之后从MCU发出的字节,并且在length
之后返回length+1
个字节:
在结束length+1
个字节的读写之后,FPGA将会返回到空闲状态,等待MCU下一次option的写入。
中断模式
FPGA内提供16个中断,由高字节(high byte)和低字节(low byte)组成,具体传输时序如下:
数据自动返回设置模式
第二个字节是mode,具体取值如下:
0x00 --- 不进行自动数据传输
0x01 --- 返回一片RAM空间的数据
0x02 --- 只返回一个RAM空间的数据
0x03 --- 由Trans发送中断触发一片RAM空间数据发送
自动数据返回是在MCU的SPI从机通道进行的。如果mode
为0x00,则后两个参数不影响FPGA内部的执行,通常设置为0即可,但是不能不传输;如果mode
为0x01,则进入一片地址空间的数据返回,从start sec
右移8位的地址开始(包含此地址),写256 * (sec_num + 1)
个字节后结束。
例如,传入start sec
为0x02,sec_num
为0x03,则会在MCU的从机通道不断返回0x0200
到0x05ff
共1024个字节,直到下一次设置mode
不为0x01时结束。如果在返回数据过程中,mode被设置为0,则本次数据传输不会终止,直到返回0x05ff
地址的数据后才会终止传输。这保证了数据传输的完整性,使得MCU不会在下一次开始数据传输时因为上一次传输的遗留数据而产生不良影响。
如果mode
为0x02,则会在MCU从通道不断返回(start addr << 8) | sec num
地址处的字节数据。例如,传入start addr
为0x02,sec num
为0x03,则会在MCU的从机通道不断返回0x0203
地址的字节数据,直到下一次设置mode
不为0x02时结束。
如果mode
为0x03,则具体数据参数同mode
为0x01,只不过每次发送都由内部逻辑触发,发送完毕后会向内部逻辑发送一次结束中断。
工程配置
通信界面的工程目录大概长这个样子:
其中,sim/testbench/bench.v
是用于仿真的demo,TCL脚本是用于ModelSim仿真的参考脚本,仅用于仿真参数的参考,一般来说不能直接跑。因为PH1器件的仿真库文件体积过大,因此工程内没有附带,需要仿真的话可以自行编译,需要一定的ModelSim基础。
后缀.al文件是安路TD开发软件的工程文件,可以直接用TD5.6.1打开,请勿使用低于5.6.1版本的TD软件打开,可能会因为版本引发一些不可预知的问题。为避免潜在的版权问题,这里不提供TD5.6.1的安装文件和License。如果需要TD5.6.1及其相关资料的下载权限,可以在安路官网注册账号之后向官方发送邮件申请。
核心代码目录如下:
其中,所有核心代码都在spi.v文件中,因此spi.v也稍显巨大,有约1500行,主状态机包含31个状态。在ip文件夹内的是一主一从两个SPI的IP核以及RAM空间的IP核。Config RAM和Trans RAM都由同一个模块例化而成,保证参数一致性。
在TD内的源码组织架构是这样的:
如果要在实际工程之中使用通信界面的话,可以把整个spi文件夹复制到新工程的源码目录之中,通过添加spi.v和IP源码文件的方式使用此通信界面,不建议使用综合后产生的spi.ip.v文件。因为在后面的时序约束和系统调试中可能需要改变系统时钟,但是安路的SPI主机IP核的系统时钟是要在IP图形设置中设置的(这个时钟与从通道的SCK时钟有关,在更改系统时钟的时候必须同步更改这个参数),具体设置界面如下:
这里直接就是200MHz拉满,但是实际工程中可能系统时钟频率也就100MHz上下(实测我的一个AD高速采集工程也就能跑到150MHz无时序违例)。而如果需要修改这个值的话,用综合后产生的门电路源码文件肯定是不行的。
技术实现详解
主状态机
下面先列举出主状态机的状态:
// spi.v
reg [7 : 0] state;
localparam STATE_RESET = 8'h00;
localparam STATE_READ_OPTION = 8'h01;
localparam STATE_READ_OPTION_READ = 8'h02;
localparam STATE_READ_ADDR = 8'h03;
localparam STATE_READ_ADDR_READ = 8'h04;
localparam STATE_READ_LENGTH = 8'h05;
localparam STATE_READ_LENGTH_READ = 8'h06;
localparam STATE_RX_DATA_0 = 8'h07;
localparam STATE_RX_DATA_1 = 8'h08;
localparam STATE_TX_DATA_0 = 8'h09;
localparam STATE_TX_DATA_1 = 8'h0a;
localparam STATE_TX_DATA_2 = 8'h0b;
localparam STATE_TX_DATA_3 = 8'h0c;
localparam STATE_TX_DATA_4 = 8'h0d;
localparam STATE_INT_READ_LOW_BYTE = 8'h0e;
localparam STATE_INT_READ_LOW_BYTE_READ = 8'h0f;
localparam STATE_INT_READ_HIGH_BYTE = 8'h10;
localparam STATE_INT_READ_HIGH_BYTE_READ = 8'h11;
localparam STATE_INT = 8'h12;
localparam STATE_TRANS_READ_MODE = 8'h13;
localparam STATE_TRANS_READ_MODE_READ = 8'h14;
localparam STATE_TRANS_READ_S_ADDR = 8'h15;
localparam STATE_TRANS_READ_S_ADDR_READ = 8'h16;
localparam STATE_TRANS_READ_E_ADDR = 8'h17;
localparam STATE_TRANS_READ_E_ADDR_READ = 8'h18;
localparam STATE_TRANS_INT = 8'h19;
状态转移图如下:
状态机在系统初始化之后会进行Config RAM
的初始化,具体方法就是用一个寄存器当计数器,每周期加一,应这个计数器作为写地址,向Config RAM
的这个地址写0,直到计数器到Config RAM
的最大深度为止。本来这个初始化过程是相对独立的,在Config RAM
清零完成后才真正进入状态机。但是后来为了逻辑统一性,还是放进了状态机中执行。
这里需要说明一下的是,后缀_READ
的状态用于读取SPI IP核的输出数据。读取数据时,读使能和地址在同一个周期,但是读出的数据是在下一个周期从IP核中送出的。因此每一次读数据都需要增加一个副状态用于数据读取。
读取数据时需要先从Config RAM
中取出数据,然后等待SPI上一次发送完毕后再送入SPI IP核中。这个过程需要好几个阶段完成,STATE_TX_DATA_0
到STATE_TX_DATA_5
就是为这个过程服务的。
Trans状态机
同样地,以下是Trans状态机的所有状态:
// spi.v
reg [7 : 0] trans_state;
localparam TRANS_STATE_RESET = 8'h00;
localparam TRANS_STATE_READ_MODE = 8'h01;
localparam TRANS_STATE_WRITE_DATA_0 = 8'h02;
localparam TRANS_STATE_WRITE_DATA_1 = 8'h03;
localparam TRANS_STATE_WRITE_DATA_2 = 8'h04;
这个状态机相对简易,此处就不放状态转移图了。同样地,在系统复位之后也会清零Trans RAM
,才会进入TRANS_STATE_READ_MODE
状态。
参考资料
安路官方的文档需要在安路官网注册账号才能查看。
安路官方SPI IP核应用手册
安路官方RAM应用手册文章来源:https://www.toymoban.com/news/detail-761286.html
补充说明
2023年8月9日
如果要改成EG4器件的工程,可以直接进行器件的修改,然后把所有IP核一个个打开,都重新配置一次就行了。文章来源地址https://www.toymoban.com/news/detail-761286.html
到了这里,关于基于SPI的FPGA-MCU通用通信界面设计与技术详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!