SPI接口的Verilog语言实现

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

电子与电气工程系
课程设计、专题(综合)实验报告
课题名称__串行接口IP核的设计与验证(spi)_
专业____ 电子信息工程________
班级_____ 08电子1班__________
学号__0806012103_ 0806012104_
姓名___ 高江柯____吴冠雄__ ______
成绩________________________
指导教师_______袁江南____________
2011年 6 月 15 日
串行接口IP核的设计与验证(SPI)
(FPGA作为主机)
一、实验目的:
通过本实验的学习,使学生掌握使用VHDL 设计一个实用数字系统的能力,以及单片机串行接口编程等知识,训练 VHDL以及单片机的编程与综合使用能力,培养工程设计的基本技能,为今后毕业设计以及实际工作奠定基础。

二、实验原理
SPI 接口是在CPU 和外围低速器件之间进行同步串行数据传输,在主器件的移位脉冲下,数据按位传输,高位在前,地位在后,为全双工通信,数据传输速度总体来说比I2C 总线要快,速度可达到几Mbps。

SPI 接口是以主从方式工作的,这种模式通常有一个主器件和一个或多个从器件,其接口包括以下四种信号:
(1)MOSI –主器件数据输出,从器件数据输入
(2)MISO –主器件数据输入,从器件数据输出
(3)SCLK –时钟信号,由主器件产生
(4)/CS –从器件使能信号,由主器件控制
在点对点的通信中,SPI 接口不需要进行寻址操作,且为全双工通信,显得简单高效。

SPI接口在内部硬件实际上是两个简单的移位寄存器,传输的数据为8位,在主器件产生的从器件使能信号和移位脉冲下,按位传输,高位在前,低位在后。

下图所示,在SCLK 的下降沿上数据改变,同时一位数据被存入移位寄存器。

三、实验步骤:
1、查找关于SPI的资料,认识理解SPI。

2、根据SPI传输数据的时序图构建出SPI的框图。

3、利用自顶向下的方法根据框图分模块进行程序的编写。

4、把各模块连接构成完整的SPI传输系统。

5、进行仿真验证。

6、在康芯开发板上验证此SPI系统收发及中断功能是否正常。

(由于时
间原因其步未能完成)
四、设计过程:
模块1:
输入说明:
data7[7..0] : FPGA写数据到RAM的输入口。

enable : 为输入使能。

reset : 复位。

clk : 输入时钟。

contr_addr[5..0]: FPGA写数据到RAM的地址的最大值,即写入RAM
中数据的个数。

feedback : SPI系统发送完数据的反馈,提示FPGA又可以重
新写数据到RAM,然后发送。

输出说明:
wr_en : 控制RAM写使能。

wr_addr : 写数据到RAM的地址控制,写一数据地址加一。

wr_clock : RAM写数据时钟,上升沿有效。

fullflag : RAM写满的标志。

data_out[7..0] : RAM中写入的数据。

模块说明:
FPGA通过SPI发送数据,数据必须要有个缓存阶段。

此设计中是利用RAM 作为缓存。

该模块就是数据写入RAM的控制器,并提示FPGA开始发送数据及发送多少个数据。

SPI发送完数据后提示FPGA重新写数据到RAM继续发送。

仿真时序图:
模块2:
输入说明:
data[7..0] :输入RAM的数据。

wraddress[5..0] :RAM写地下输入,写一数据地址加一。

wren :RAM写使能
rdaddress[5..0] :RAM读地址输入,读一数据地址加一。

rden :RAM读使能
wrclock :RAM写数据时钟,上升沿有效。

rdclock :RAM读数据时钟,上升沿有效。

输出说明:
q[7..0] :读RAM数据输出口。

模块说明:
这是双通道的RAM,此设计中需要定制2个容量为64BIT的,能进行数据的读写。

需注意的数据的读写不能同时操作。

模块3:
输入说明:
clk :系统时钟。

ramindata[7..0] :输入需要发送的数据。

Cpuwr :
Miso :主机输入从机输出。

Reset :复位。

RFirq :开始发送。

Fullflag :RAM存满,请求开始发送标志。

Addr_num[5..0] :需要发送数据的个数。

输出说明:
feedback :发送完数据后反馈给RAM控制器,请求重新写入RAM数据
发送。

outdata[7..0] :接收到从机发出的数据,用于测试。

mosi :主机发出的移位数据,从机输入。

Irq :发送完中断主求。

CSN :从机选择控制,低电平有效。

SCK :主机从机发送移位数据时钟。

SendAddr[5..0] :RAM读地址控制,读取RAM中的数据进行发送。

SendRDen :RAM读使能。

ReceiveData[7..0] :接收到的数据,用于写入RAM。

ReceiveAddr[5..0] :接收到的数据写入RAM的地址控制。

SendramClk :读取RAM中的数据用于发送的时钟,上升沿有效。

ReceiveClk :接收到的数据写入RAM中的时钟控制,上升沿有效。

ReceiveWen :接收到数据写入RAM使能。

CE:
Sendfinishtest :发送结束标志。

ram_rd :整个数据接收完后,读取RAM中数据使能。

ram_rd_clk :整个数据接收完后,读取RAM中数时钟,上升沿有效。

ram_rd_addr[5..0] :整个数据接收完后,读取RAM中数据的地址控制。

模块说明:
此模块是整个系统的核心部分,控制着SPI的收发。

其整个流程大致如下:
具体的程序写法:
其收发过程都是通过状态机实现的。

状态机容易构成性能良好的同步时序逻辑模块,而且能很大限度的消除毛刺现象。

读取第一个数据(4个状态):
开始发送接收(17个状态,采用顺序编码):
5'b00000:
CSN=1'b0; 片选从机,低电平有效
SCK=1'b0; 拉低SPI时钟线,主机发送数据最高位
mosi=senddata[7]; 读取数据最高位放到数据线MOSI发送
SendRDen=1'b1; 读取发送数据使能
SendramCLK=1'b0; 拉低读RAM时钟线
bitcounter=bitcounter+5'b1;
5'b00001:
SCK=1'b1; 拉高SPI时钟线,主机接收从机发出数据最高位
StatusReg0[7]=miso; 存取最高位数据
SendramCLK=1'b1; 拉高读RAM时钟线,产生上升沿读RAM
bitcounter=bitcounter+5'b1;
5'b00010:
SCK=1'b0; 拉低SPI时钟线,主机发送数据次高位数据
mosi=senddata[6]; 读取数据次高位放到数据线MOSI发送
bitcounter=bitcounter+5'b1;
5'b00011:
SCK=1'b1; 拉高SPI时钟线,主机接收从机发出数据次高位
StatusReg0[6]=miso; 存取次高位数据
ramsendtemp=ramindata; 读取RAM中下一个数据
bitcounter=bitcounter+5'b1;
5'b00100:
SCK=1'b0; 拉低SPI时钟线,主机发送数据的第五位
mosi=senddata[5]; 发送数据第五位
SendramCLK=1'b0; 拉低读RAM时钟线
SendRDen=1'b0; 关闭读RAM使能
SendAddr=SendAddr+6'b1; 读RAM地址加一
bitcounter=bitcounter+5'b1;
5'b00101:
SCK=1'b1;
StatusReg0[5]=miso; 接收从机发出的数据第五位
bitcounter=bitcounter+5'b1;
5'b00110:
SCK=1'b0;
mosi=senddata[4]; 主机发送数据第四位
bitcounter=bitcounter+5'b1;
5'b00111:
SCK=1'b1;
StatusReg0[4]=miso; 接收从机发出的数据第四位
bitcounter=bitcounter+5'b1;
5'b01000:
SCK=1'b0;
mosi=senddata[3]; 主机发送数据第三位
bitcounter=bitcounter+5'b1;
5'b01001:
SCK=1'b1;
StatusReg0[3]=miso; 主机接收从机发出的数据第三位
bitcounter=bitcounter+5'b1;
5'b01010:
SCK=1'b0;
mosi=senddata[2]; 主机发送数据第二位
bitcounter=bitcounter+5'b1;
5'b01011:
SCK=1'b1;
StatusReg0[2]=miso; 主机接收从机发送的数据的第二位
bitcounter=bitcounter+5'b1;
5'b01100:
SCK=1'b0;
mosi=senddata[1]; 主机发送数据的次低位
bitcounter=bitcounter+5'b1;
5'b01101:
SCK=1'b1;
StatusReg0[1]=miso; 主机接收数据的次低位
bitcounter=bitcounter+5'b1;
5'b01110:
SCK=1'b0;
mosi=senddata[0]; 主机发送数据的最低位
bitcounter=bitcounter+5'b1;
6'b01111:
SCK=1'b1;
StatusReg0[0]=miso; 主机接收数据的最低位
bitcounter=bitcounter+5'b1;
senddata=ramsendtemp;
6'b10000:
SCK=1'b0;
bitcounter=5'b0;
ramdReceivetemp=StatusReg0; 主机存储接收到的第一个数据,并准备
写入RAM
sendFinishByte=sendFinishByte+6'b1; 发送数据个数加一
receiveByte=receiveByte+6'b1; 接收数据个数加一
发送接收中间数据态(18个状态,采用顺序编码):
5'b00000:
SCK=1'b0;
mosi=senddata[7]; 主机发送中间数据最高位
SendramCLK=1'b0;
SendRDen=1'b1;
bitcounter=bitcounter+5'b1;
5'b00001:
SCK=1'b0;
SendramCLK=1'b1;
bitcounter=bitcounter+5'b1;
5'b00010:
SCK=1'b1;
StatusReg0[7]=miso; 主机接收从机发出的数据的最高位
bitcounter=bitcounter+5'b1;
5'b00011:
SCK=1'b0;
mosi=senddata[6]; 主机发送中间数据次高位
ramsendtemp=ramindata; 读取RAM,作为下一个发送数
bitcounter=bitcounter+5'b1;
5'b00100:
SCK=1'b1;
StatusReg0[6]=miso; 主机接收从机发出数据的次高位
SendAddr=SendAddr+6'b1; 读RAM地址加一
SendramCLK=1'b0;
SendRDen=1'b0;
bitcounter=bitcounter+5'b1;
5'b00101:
SCK=1'b0;
mosi=senddata[5]; 主机发送中间数据第五位
ReceiveCLK=1'b0; 拉低写数据到RAM时钟
ReceiveWen=1'b1; RAM写数据使能
bitcounter=bitcounter+5'b1;
5'b00110:
SCK=1'b1;
StatusReg0[5]=miso; 主机接收从机发出的数据第五位
bitcounter=bitcounter+5'b1;
5'b00111:
SCK=1'b0;
mosi=senddata[4]; 主机发送中间数据的第四位
ReceiveData=ramdReceivetemp; 准备好写入的数据(在时钟上升沿到来前,
将数据放到总线上)bitcounter=bitcounter+5'b1;
5'b01000:
SCK=1'b1;
StatusReg0[4]=miso; 主机接收从机发出数据的第四位
bitcounter=bitcounter+5'b1;
ReceiveCLK=1'b1; 将拉收到的上一个数据第一次写入RAM (为
了安全,每个数据写两次)
5'b01001:
SCK=1'b0;
mosi=senddata[3];
bitcounter=bitcounter+5'b1;
ReceiveCLK=1'b0;
5'b01010:
SCK=1'b1;
StatusReg0[3]=miso;
bitcounter=bitcounter+5'b1;
ReceiveCLK=1'b1; 第二次写入(为了安全,每个数据写两次)
5'b01011:
SCK=1'b0;
mosi=senddata[2];
bitcounter=bitcounter+5'b1;
ReceiveAddr=ReceiveAddr+6'b1;
ReceiveCLK=1'b0;
ReceiveWen=1'b0; //取消写ram信号
5'b01100:
SCK=1'b1;
StatusReg0[2]=miso;
bitcounter=bitcounter+5'b1;
5'b01101:
SCK=1'b0;
mosi=senddata[1];
bitcounter=bitcounter+5'b1;
5'b01110:
SCK=1'b1;
StatusReg0[1]=miso;
bitcounter=bitcounter+5'b1;
5'b01111:
SCK=1'b0;
mosi=senddata[0];
bitcounter=bitcounter+5'b1;
6'b10000:
SCK=1'b1;
StatusReg0[0]=miso;
bitcounter=bitcounter+5'b1;
6'b10001:
SCK=1'b0;
bitcounter=5'b0;
sendFinishByte=sendFinishByte+6'b1; 接收数据个数加一
ramdReceivetemp=StatusReg0; 主机缓存接收到的数据
senddata=ramsendtemp; 主机把读到的下一个数据拉到发生寄
存器,准备发送
receiveByte=receiveByte+6'b1;
发送最后一个数据(21个状态,采用顺序编码):
前面17个状态与接收发送第一个数据,中间数据基本相同。

故不作具体陈述。

6'b10010:
CSN=1'b1; 取消选择从机使能
bitcounter=bitcounter+5'b1;
ReceiveCLK=1'b0;
6'b10011:
ReceiveCLK=1'b1; 接收到数据第二次写入RAM(为了安
全,每个数据写两次)
bitcounter=bitcounter+5'b1;
6'b10100:
ready=1'b0; 发送接收完准备取消
feedback=1'b1; 反馈给RAM控制器,可以重新写数据
senden=1'b0; 取消发送使能
sendfilish=1; 发送完成标志
receiveByte=receiveByte+6'b1; 接收数据加一
ReceiveAddr=6'b0; 接收地址清0
ReceiveCLK=1'b0;
ReceiveWen=1'b0; 取消写ram信号
ReceiveReady=1'b1;
bitcounter=bitcounter+5'b1;
6'b10101:
bitcounter=5'b0;
feedback=1'b0; 反馈信号清0
顶成框图:
仿真通过设置contr_addr=4来写五个数据(11,12,7,15,16)到RAM,然后发送。

五、设计中遇到的问题及解决办法:
1、控制RAM写数据的时钟时,忽视了Verilog语言的语句执行特
导致RAM中总是写不进去数据。

wr_clock=1'b0;
wr_clock=1'b1; 第一次写入
wr_clock=1'b0;
wr_clock=1'b1; 第二次写入
这样执行后的结果为wr_clock=1'b1,一直为上高平,并不会有两个上升沿。

最好的解决方法就是使用状态机。

2’b00: wr_clock=1'b0;
2’b01: wr_clock=1'b1; 第一次写入
2’b10: wr_clock=1'b0;
2’b11: wr_clock=1'b1; 第二次写入
2、处理反馈信号feedback与存满信号fullflag的逻辑关系时,遇到
了些麻烦,最终巧妙的运用异或解决了问题。

a=(~feedback)&fullflag0;
b=feedback&(~fullflag0);
fullflag=a|b;
3、在定制FIFO时遇到很大的问题,如数据不能按照严格的时序进出
FIFO,而且有些数据还不能写进FIFO,经过多方面调试改写还是没
能解决。

最终只能放弃FIFO,改用RAM,外加个控制器就可以了。

六、设计心得与体会:
1、这次设计花了我四天多时间完成,做之前单纯的以为SPI不过就是写
几个简单的移位寄存器外加个时钟就可以了,却完全忽视了SPI具有严格的时序要求及数据发送接收存储问题。

SPI不是只发送接收一个八位数据就可以了的,它应该具有强大的信息交换传输功能。

2、仿真时序很不好把握,以后尽量少用Quartus II自带仿真器,要多多学
习MODOSIM。

3、Verilog HDL自学了一段时间,第一次用来编写一个完整的系统。

和VHDL
比起来还是各有各的好处吧。

它们描述各层的能力各有千秋。

Verilog HDL 善于描述更低层设计,比VHDL更灵活,而VHDL善于描述一些高层的设计(主要是一些算法及控制好实现)。

Verilog HDL在仿真方面比VHDL也
更方便些,如TESTBEN。

4、FPGA方面需学习的还有很多,为了设计SPI借书查了些资料,才发现
自己FPGA基本上还没有入门。

电子设计做信号题需加倍努力啊。

相关文档
最新文档