学生期间,做的设计比较小或者偏向 demo 类型,那么 ip 核是会占据设计的很大一部分。但使用 ip 核本身对学习者来说就很有意义。通过 ip 的使用,会了解一个工程如何组织,如何阅读手册,如何通过仿真结果优化修改自己的设计。本系列就会通过使用一系列的基础 ip,讨论如何组织工程,阅读手册,编写基础的粘合逻辑,testbench 以及功能仿真。
作者:李凡
来源:https://zhuanlan.zhihu.com/p/47847664
使用 ip 对于数字逻辑方面的工作来说,是非常正常的,基础的 ip 之于数字逻辑设计,与与非门相比大概只是设计层次上的差别。更何况对于 SoC 公司来说,购买一整个外设模块的 ip 也是很正常的,比如 USB 之类的模块。
本文作为本系列的第一篇,将会从我们最亲近的老朋友 FIFO 开始。使用 ip 核的教程在网络上有很多,大多数文章各有特色(当然转载的也没那么有特色),我也会努力写出自己的特色。本系列将偏基础向(啰嗦),希望能帮助逐渐入门的同学,看一遍文章,操作一遍,就能提高一点滋事水平。
什么是 FIFO
写到后面才想起来,我们既然是基础向的教程,首先应该先说说什么是 FIFO。
FIFO 就是 Fire In Front dOOr 的缩写,意思是城门失火,殃及池鱼。。。开个玩笑。
First In First Out,先入先出队列。FIFO 本质上只有两种操作,读&写,分别从队列的两端读取第一个数据,写入最后一个数据。
如图是一个深度为5的FIFO ,通过wtr/rd 指针的移动实现FIFO读写
FIFO 在数据结构课上最先和大家见面,广泛用于计算机程序和结构中,在 FPGA 中的 FIFO 的含义和软件中的 FIFO 完全相同,只不过更加贴近硬件的实现。在数据缓冲,跨时钟域处理中将会大量运用到 FIFO 结构。
在 Vivado 中创建一个 ip 核
Vivado 是 Xilinx 家的集成设计环境,笔者使用的版本是 2018.1,Vivado 在 2018 年的第一个版本(以前以为.1 就是一月份出的意思)。每年据说共有 4 个版本,但目前官网上的版本还是 2018.2 。如果初学者不需要维护现有项目,使用一个次新版本一般没有问题。Vivado 高版本可以打开低版本工程(需要 update),低版本仅能“打开”高版本工程,打开为只读模式,需要另存为当前版本才可以编辑。
vivado的界面我就暂时不介绍了
打开软件后,第一感觉还是很舒服的(不知道读者们怎么看)。暂时先不介绍整个界面,从左侧导航栏选择 ip Catlog 就可以新建一个 ip 核,面对很多很多的 ip,可以使用上方的搜索功能,键入 fifo,额,发现有很多 fifo,还都是什么 axi 什么的。这里我们选择 FIFO Generator。(如果你的屏幕不够大,你可能需要往下拉,才能看见她)关于 AXI 接口你可以从以下的链接了解,本文暂时不会涉及。
https://zhuanlan.zhihu.com/p/44766356
视你的电脑性能而定,n 秒后你打开了 ip 核的设置界面:
左侧是根据你目前的设定,暂时你的 fifo 模块长这样。右侧是 FIFO 的配置选项,共有几个方面的配置:Basic ,Native port 等。本文基本上会把各常用的配置项都讲解一下。不过笔者经历尚浅,可能有部分配置讲的不完全对,欢迎指出。
BASIC
BASIC 中玩家可以控制你 FIFO 的两个方面,一是 FIFO 的接口,二是 FIFO 的类型。
FIFO 的接口分为两类,一类是 Native 接口,这类接口使用比较简单,另一类是 AXI 协议接口,这类协议口线比较多,操作相对复杂,但是 AXI 是一种标准化的总线接口,有很多标准化的好处并且运用广泛。关于这两种接口的比较,可以参看这篇文章的第一部分
https://zhuanlan.zhihu.com/p/46672113
我们今天主要讨论在简单设计中,使用比较广泛的 Native 接口。
说到 FIFO 的类型,主要有两部分的不同:1.读写是否使用一个时钟 2.使用何种硬件资源。
硬件资源分为三种,使用 BRAM,即块 RAM 资源,这是 FPGA 内嵌的一种重要的专用 RAM 资源;使用分布式的 RAM (Distributed RAM),即将 FPGA 中的基础,多用途的 LUT 查找表资源用作 RAM;使用内嵌的专用 FIFO 资源,关于这个我不是很熟悉,之后会查一下,专业 FIFO 应该会提供很小的延迟。
使用不同的硬件资源构成的 FIFO 会有不同的特性,可以在图中的表格中看到,使用 BRAM 好处最多,可以在读写两端使用不同的数据宽度,可以使用 ECC (一种数据校验特性),支持 First-World Fall Through ,以及支持动态错误注入。错误注入一般用于仿真真实环境中,数据出现错误的情况。而使用分布式的 RAM 则仅支持 First-World Fall Through 功能,但 BRAM 是一种比较重要的资源,如果设计的 FIFO 对延时不敏感,可以使用分布式的 RAM 以节约 BRAM 资源。分布式 RAM 由 LUT 查找表组成,LUT 是 FPGA 的基本资源。
Native Ports
在 Native Ports 中比较重要的是设定 FIFO 的数据宽度以及 FIFO 的深度。宽度指的是数据线的位数,即同时能够写入读取多少比特的数据;如果使用 BRAM ,那么读取数据的宽度可以和写入数据的宽度不同。但也不可以完全不匹配,举个例子,写入数据宽度为8,那么读取数据宽度就可以为 1,2,4,8;写入数据宽度为3,那么读取数据宽度只能为 1 或 2。也就是说,读取宽度可以为写入宽度的幂级数分之一。
深度指的是 FIFO 的容量,这在 FIFO 设计中是一种主要的考量,更大但没有必要的深度会造成资源浪费。当然,如果深度不够则是一种更尴尬的情况。如果功能验证中,和 FIFO 有关的部分出现了所谓的“奇怪”现象,那么应该观察是否是 FIFO 溢出造成的。
FIFO 的复位使用的高电平复位,如果设计中系统复位信号是低电平有效的,那么不要忘记要将系统复位电平取反后再接入 FIFO 复位电平。一般我们在同步系统设计中使用异步复位。
上图中系统提示,FIFO 在读取时有一个时钟的 latency(延迟),潜台词是写入将不会有延迟。这个延迟我们将在后文的仿真结果中分析。
Status Flags
在第三个页面中,可以为我们的 FIFO 增加一些状态信号,包括 almost Full/Empty 信号,这两个信号,顾名思义,就是在 FIFO 几乎要满或者几乎要空的情况下置起,所谓的“几乎“就是指还差一个数据满或者空。
除了我们的标准“几乎”满/空信号之外,玩家还可以设定自己的“几乎”标准,称为 programmable Full/Empty 信号,即可以自己定义“可编程几乎”的标准为 n 个数据。
这个页面上还提供握手选项,但一般我们在初级设计中不会需要 FIFO 具有这种“交互”特性,实质上 AXI 协议接口也会提供握手特性。
第四个页面 Data Count,顾名思义就是提供一个信号来表示当前 FIFO 中的数据总数,对于我们演示还是相当有用,我们这里加上计数信号,但就不再截图了。(图已经够多了)
最后一个页面是我们 FIFO 的 summary,提供完整的配置信息以供检查我们的设计。并告知我们该 IP 所使用的硬件资源
Summary
根据报告我们了解到这个 FIFO 使用了一个 18K 的 BRAM,虽然这个 FIFO 很小,但还是占据了一个最小的 18K 块。在 8 位宽的情况下,18K 块至少能容纳 2048 的深度。
至此,选择 OK,我们就完成了一个 FIFO IP 的创建,挺轻松的,不是么。
FIFO 的接口
在完成配置之后,我们首先介绍一下 FIFO 的所有 Native 接口。所有接口分为三组 FIFO\_WRITE,FIFO\_READ 以及时钟复位接口。
FIFO 的时钟,复位信号,如果使用的是双时钟 FIFO,那么读写信号将会有自己独立的时钟。复位信号可以配置为同步或者异步复位。
FIFO 的读写数据 dout/din 信号,数据进出 FIFO 的通道;
FIFO的读写使能 wr\_en/rd\_en 信号 ,在读写使能高电平的情况下,时钟上升沿时刻会进行读写操作;
FIFO 的状态信号,包括 full,empty 等,在相应的状态下,这些信号会被置起;
在顶层文件中示例化 FIFO
在一个系统设计中,我们会给系统编写一个顶层文件。所谓顶层文件,即所有设计文件以功能模块的形式组合,一一被实例化到一个 v 文件中。那么这个 v 文件一方面位于所有文件的顶层,另一方面,所有的综合,布局布线都是以这个顶层文件为单位进行。
换句话说,本文所做的系统很小,小到只有一个 FIFO。但我们还是为这个小小的 FIFO 编写一个顶层文件。在 source 界面右键,添加源文件,选择添加 design 文件。
命名为 fifo\_top.v 并在顶层文件中实例化 fifo 模块。关于实例化,这里简单说下,实例化在 verilog 中类似软件编程中的实例化对象概念。即通过实例化,在当前源文件中,调用一个现有的模块,并制定实例化模块的名字以及端口的连接,举个栗子:
wire 连线a;
fifo_generator_0(模块本身的名字) my_fifo(我们实例化的模块的名字) (
.模块的端口 a (连线a), // input clk
);
即实例化一个 fifo\_generator\_0 模块,命名为 my\_fifo。并将一条连接线 连线a 连接到实例化的 my\_fifo 模块的端口 a。关于模块本身的名字和实例化模块的名字,不知道这么解释会不会比较容易理解:
int a = 1;
//int:(模块本身的名字)-> 变量类型
//a : (我们实例化的模块的名字) -> 变量名
// = 1 :( .模块的端口 a (连线a) )
//; : ;
将实例化模块比作变量,模块本身比作变量类型,定义端口连接比作定义变量值。
好了,回到我们的顶层模块,实例化 FIFO 模块,并定义顶层模块的输入输出端口,我们这里直接使用 FIFO 的端口作为顶层模块的端口。
那么,怎么实例化,要怎么知道 FIFO 的端口,嗯,自己对着 ip 核生成界面写一下吧。。开玩笑的。实际上你可以这样:
还是 sources 界面,在下方选择 IP Sources,点开 ip 核的箭头,可以找到一个 veo 文件,里面就是实例化的模板。vho 文件对应的是 VHDL 语言。veo 文件打开后长这样,复制即可。
这里插播一个广告,推荐使用 VSCode 编辑器,好用又好看的开源免费编辑器,微软良心出品,就是低配机器启动速度可能捉鸡。感兴趣的可以戳下方链接了解。
VSCode 布道指南 V1.0 (一)zhuanlan.zhihu.com
module fifo_top(
input clk,
input rst,
input [7 : 0] din,
input wr_en,
input rd_en,
output [7 : 0] dout,
output full,
output almost_full,
output empty,
output almost_empty,
output [3 : 0] data_count,
output prog_full,
output prog_empty
);
fifo_generator_0 my_fifo (
.clk(clk), // input clk
.rst(rst), // input rst
.din(din), // input [7 : 0] din
.wr_en(wr_en), // input wr_en
.rd_en(rd_en), // input rd_en
.dout(dout), // output [7 : 0] dout
.full(full), // output full
.almost_full(almost_full), // output almost_full
.empty(empty), // output empty
.almost_empty(almost_empty), // output almost_empty
.data_count(data_count), // output [3 : 0] data_count
.prog_full(prog_full), // output prog_full
.prog_empty(prog_empty) // output prog_empty
);
endmodule
未完待续
倒不是我想要把一篇文章硬要分成两篇文章凑字数,实在是写着写着发现写得实在太长了。。
本文的下篇已经写了不少,应该很快就会完成,在下篇中我们将了解到:
如何添加,编写 testbench 并展开仿真;
通过仿真结果,结合数据手册,分析 FIFO 的特性。
因为本文想要走基础向的风格,所以欢迎私信我或者在评论中留下你们的问题以及建议,这能帮助我写出更好的基础向的文章。另外关于 FIFO 本身有什么疑问也可以提出来,我们可以在下篇中一起研究研究。
声明
除截图外,本文配图来自网络。
推荐阅读
关注此系列,请关注专栏FPGA的逻辑