(原创声明:该文是作者的原创,面向对象是FPGA入门者,后续会有进阶的高级教程。宗旨是让每个想做FPGA的人轻松入门,作者不光让大家知其然,还要让大家知其所以然!每个工程作者都搭建了全自动化的仿真环境,只需要双击top_tb.bat文件就可以完成整个的仿真(前提是安装了modelsim),降低了初学者的门槛。如需整个工程请留言(WX:Blue23Light),不收任何费用,但是仅供参考,不建议大家获得资料后从事一些商业活动!)
SPI的英文全称为Serial Peripheral Interface,顾名思义为串行外设接口。SPI是一种同步串行通信接口规范,主要应用于嵌入式系统中的短距离通信。该接口由摩托罗拉在20世纪80年代中期开发,后发展成了行业规范。
SPI是一种高速的、全双工的、同步的通信总线,并且至多仅需使用4根线,节约了芯片的管脚,SPI主要应用于EEPROM、FLASH、ADC、DAC等芯片,还有数字信号处理器和数字信号解码器之间。
SPI设备之间采用全双工模式通信,是一个主机和一个或者多个从机的主从模式。主机负责初始化帧,这个数据传输帧可以用于读与写两种操作,片选线可以从多个从机选择一个来响应主机的请求。
SPI通信中有4种不同的操作模式,不同的从机设备可能在出厂时就被设置好了某种模式,并且无法更改。但是SPI通信必须处于同一种模式下才能进行。因此我们应该对自己手里的SPI主机设备进行模式的配置,也就是通过CPOL(时钟极性)和CPHA(时钟相位)来控制SPI主设备的通信模式,具体如下:
时钟极性(CPOL)定义了SCLK时钟线空闲状态时的电平:
CPOL=0,即SCLK=0,表示SCLK时钟信号线在空闲状态时的电平为低电平,因此有效状态为高电平。
CPOL=1,即SCLK=1,表示SCLK时钟信号线在空闲状态时的电平为高电平,因此有效状态为低电平。
时钟相位(CPHA)定义了数据位相对于时钟线的时序(即相位):
CPHA=0,即表示输出(out)端在上一个时钟周期的后沿改变数据,而输入(in)端在时钟周期的前沿(或不久之后)捕获数据。输出端保持数据有效直到当前时钟周期的尾部边缘。对于第一个时钟周期来说,第一位的数据必须在时钟前沿之前出现在MOSI线上。也就是一个CPHA=0的周期包括半个时钟空闲和半个时钟置位的周期。
CPHA=1,即表示输出(out)端在当前时钟周期的前沿改变数据,而输入(in)端在时钟周期的后沿(或不久之后)捕获数据。输出端保持数据有效直到下一个时钟周期的前沿。对于最后一个时钟周期来说,从机设备在片选信号消失之前保持MISO信号线有效。也就是一个CHPA=1的周期包括半个时钟置位和半个时钟空闲的周期。
时钟极性和相位的时序图。红线表示时钟的前沿,蓝线表示时钟的后沿。
SPI的FPGA实现,网上有很多的教程,很多是用状态机实现的,本节我们用一种简单的方式来实现SPI的主机通信,先来实现CPOL=0和CPHA=0;下节课再用通用方式来实现4种模式。
SPI的时钟一般都是一个范围,这个要看实际芯片的datasheet,所以我们可以采用主时钟的分频来实现,主要在规定的范围内即可。
如下所示,定义了参数DNUM是要发送数据的位宽,FRE_DIV是主时钟的分频数,取2的幂次,如果是4就是主时钟的4分频,假设主时钟是100MHz,那SPI的时钟就是25MHz。FRE_DIV_INDEX就是分频数的那个幂次,FRE_DIV取4,FRE_DIV_INDEX就是2,FRE_DIV取8,FRE_DIV_INDEX就是3......
用信号spi_conv来标识一次SPI通信,spi_state_cnt的计数最大值是数据位宽和分频数的乘积(总共消耗的系统时钟数),同时根据计数值值完成spi_over和spi_cs信号的控制。
根据计数器取分频时钟,即SPI的时钟spi_sck,因为CPOL=0,所以spi_sck在空闲状态时低电平。因为CPHA=0,就是在spi_sck的下升沿SPI的输出spi_mosi进行变化,通过信号shift_en和shift_en_d来取spi_sck的下降沿send_en_flag。最后将要发送的数据通过send_en_flag控制放到线spi_mosi上即可。
新建tb测试文件,在文件中设计了2个task,spi_data()用来产生16位的随机数据,spi_slave()模拟接收spi_master发送的串行信号,并与spi_master实际发送的并行多位信号进行比较,通过打印信息确定设计的功能是否正确。
双击sim目录下得top_tb.bat文件,完成系统得自动化仿真。
1次SPI发送的仿真波形图如下所示。
查看modelsim的Transcript窗口,spi_master发送的数据和spi_slave接收的数据一致,设计功能正常。