设为首页 加入收藏

TOP

第7讲 SPI和RAM IP核(一)
2017-10-10 12:23:55 】 浏览:5009
Tags:SPI RAM

学习目的:

(1) 熟悉SPI接口和它的读写时序;

(2) 复习Verilog仿真语句中的$readmemb命令和$display命令;

(3) 掌握SPI接口写时序操作的硬件语言描述流程(本例仅以写时序为例),为以后描述更复杂的时序逻辑电路奠定基础。

学习过程:

SPI的相关知识】

① SPI的速度比串口的快,采用源同步传输的方式,且为串行传输,应用场景不同则时序和接口名称会有不同;

② 串行flash的读写擦除命令可通过SPI接口进行通信,CPU芯片与FPGA可通过SPI接口进行通信,某些芯片的参数寄存器也可通过SPI的方式配置;

③ SPI接口说明

clip_image002

图1 SPI接口

SCLK:主机给从机的系统时钟信号;

SDI:主机输出给从机的数据信号;

SDO:从机输出给主机的数据信号;

CS:片选信号(此处为高电平有效);

SDIO(三线模式):主机与从机之间的双向数据总线。

DAC3283芯片与SPI有关内容】

DAC3283芯片的寄存器映射如图1所示:

image

图1 DAC3283芯片的寄存器映射图

从图1可知,此DAC芯片共有32个寄存器需要配置(CONFIG0~CONFIG31),且每个寄存器均为8bit。

【关于这些寄存器的配置方法】对于寄存器数量少的情况,可以直接定义几个reg型变量,然后用这些reg变量初始化那些待配置的寄存器;而对于像本例这样有如此多的寄存器需要配置,则需通过FPGA的ROM或RAM完成寄存器的初始化,即:先将配置参数写到FPGA的ROM或者RAM中,然后再通过FPGA把这些参数从ROM或者RAM中读出并写入到外部芯片的寄存器中去。在本例中,是通过SPI接口写入到DAC3283芯片的。

DAC3283芯片的SPI接口时序】

image

图2 SPI的写时序图

image

图3 SPI的读时序图

首先,对图2、图3中的几个信号名称作介绍。SCLK是由FPGA送给DAC芯片的时钟信号;SDENB是串行接口的使能信号(相当于片选信号),只有当它为低电平时,SPI的读写才有效;对于“三线模式”的SPI,SDIO是一个双向的数据线,负责数据的读写数据传输;对于“四线模式”的SPI,SDIO也是一个双向的数据线,但ALARM_SDO是一个只读数据线,仅负责DAC芯片输出数据的传输。数据在SCLK的上升沿时刻写入DAC芯片,在SCLK的下降沿时刻从DAC芯片中读出。

然后,介绍一下SPI每次传输的数据内容。本例中,SPI采用的是先传高位后传低位的串行传输方式,每次传输16bit,其中,前8bit构成SPI的指令周期,后8bit构成SPI的数据周期。在前8bit中,rwb(R/W)是读/写控制信号,即:若rwb为高电平时,此条指令为读指令;若rwb为低电平时,此条指令为写指令。N1、N0指明每帧传输的数据字节数(范围为1~4字节,不包含前面的那个指令字节),A3…A0是DAC芯片各寄存器的地址。

SPI接口的状态机】

 
   

image

图4 DAC芯片寄存器初始化操作状态机(概念模型)

IDLE是起始状态(默认态),work_en是从外部输入的启动使能信号(高电平有效);WAIT是等待状态,在本例中,等待8个时钟周期后(wait_cnt[3] == 1'b1)即进入到READ_MEM状态(读存储器状态),直接读取RAM中的数据;WRITE_REG是写寄存器状态,将32个16bit位宽的数据通过串行移位的方式送给DAC芯片的寄存器,移位完1个16bit位宽的数据后(shift_cnt == 4'd15 && pose_flag == 1'b1 && data_end != 1'b1)就回到WAIT状态,然后重新等待8个时钟周期,重新执行READ_MEM状态和WRITE_REG状态,直到32个数据全都读写完毕后才进入STOP状态(shift_cnt == 4'd15 && pose_flag == 1'b1 && data_end == 1'b1),拉高conf_end信号。

【设计步骤】

(1) 写spi_ctrl.v文件,首先写分频计数器,将50MHz的系统时钟分频为1MHz作为SPI的时钟spi_clk(一般是50~60MHz,但此处仅是演示实验,用1MHz即可),同时,产生clk_p和clk_n两个相位相反、频率为1MHz的reg信号,clk_p是正相时钟信号,用以触发脉冲标志信号pose_flag(标志SPI时钟信号上升沿的到来),clk_n是反相的时钟信号,用作spi_clk,送给SPI当作时钟使用,以上共需写4个always语句块。

(2) 建立一个16bit x 32的单口RAM IP核(仅读不写),并例化到spi_ctrl.v中,完成mif文件的编辑和拷贝。

(3) 声明状态机变量,写状态机(两段式),包括等待状态下的计数器、读RAM的地址产生模块(1个系统时钟周期读出1个数据)、串行移位寄存器(左移,移出最高位)、移位操作的计数器、data_end信号控制模块、数据输出模块、片选信号控制模块、conf_end信号控制模块以及状态跳转模块等部分。

(4) 写testbench文件和run.do文件,运行仿真并分析仿真结果(跑300us)。对testbench文件的要求如下:①能使用$readmemb命令读出dac_ini_16x32.mif文件中的数据;②能在rec_spi任务块中实现对SPI传输数据的回收,并与dac_ini_16x32.mif文件中的数据进行校对,然后用$display命令显示校对结果和数据信息。

【代码实现】

(1)设计一个计数分频模块,用50MHz的系统时钟产生1MHz的时钟信号给SPI读/写操作使用,同时产生clk_p和clk_n两个相位相反、频率均为1MHz的信号:

image

同时,产生一个标志clk_p上升沿的标志信号pose_flag,用它统一系统的全局时钟:

image

【注意事项】尽量不要使用分频产生的clk_p或clk_n当作always块的触发条件!原因:时钟信号在很大程度上决定了整个设计的性能和可靠性,应尽量避免使用FPGA内部逻辑产生的时钟,因为它很容易导致功能或时序出现问题,内部逻辑产生的时钟信号容易出现毛刺(布线用线的质量较差),影响信号质量,同时,组合逻辑电路固有的延时也容易导致时序问题。详细的解说请见特权的《深入玩转FPGA》一书的P59。

(2)RAM IP核的创建和例化:

clip_image015

图5 RAM IP核的参数配置

clip_image017

图6 去掉q端寄存器

clip_image019

图7 新建和添加mif文件

clip_image021

图8 勾选例化文件

例化到spi_ctrl.v文件中:

image

(3)声明状态变量和状态参数,写状态机:

image

状态跳转控制:

image

image

① 写状态机写到WAIT状态时,发现需要产生wait_end信号,而wait_end信号是由计数等待8个时钟周期后产生的,故先需要设计WAIT状态等待计数模块:

image

然后,用设计wait_end信号产生模块:

image

② 写状态机写到READ_MEM状态时,发现需要先将RAM中的数据读出来,故先需设计产生读RAM地址的模块:

image

③ 写状态机写到WRITE_REG状态时,发现先要将从RAM读出来的数据缓存起来(通过shift_buf缓存),然后逐比特地通过sdi线送

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Verilog笔记——YUV2RGB的模块测试 下一篇第6讲 PLL和ROM的IP核使用

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目