课程资源
视频:https://www.bilibili.com/video/BV14K4y1u7kH/
资料:https://www.aliyundrive.com/s/E9H7Mc5hqhu
第1章 FPGA简介
第1节 什么是FPGA
FPGA是什么东西?
-
FPGA全称Field-Programmable Gate Array,即现场可编程门阵列
-
简而言之FPGA是一个可以通过编程来改变内部结构的芯片。
-
如果要实现相应的功能,需要通过编程即使用硬件描述语言进行程序设计,经过EDA工具编译、综合、布局布线成后转换为可烧录的文件,最终加载到FPGA器件中去,改变FPGA内部的连线,最终完成所实现的功能。
FPGA可以做什么?
- FPGA一般应用于通信接口设计、数字信号处理等比较高端的场合。
- ASIC(application specific integrated circuit,专用集成电路)的原型验证。
- FPGA还用于一些非标(没有定义标准的)场合,FPGA在军工领域应用很广泛。
- FPGA也是比较适用于新科技新领域。
FPGA相较于其他集成电路的优势和劣势
- FPGA的优势是:
- 相较于ASIC,FPGA的开发难度,大大缩减了开发周期
- FPGA是可重复编程的,其研发成本与风险也要比ASIC小许多
- FPGA比单片机、CPU等集成电路芯片拥有更高的效率,更低的功耗
- FPGA也有劣势。
- FPGA开发难度比单片机、CPU难
- FPGA相较于ASIC成本较高、性能较差,资源利用率低的问题
单片机、ASIC和FPGA的开发难度和性能排序
- 开发难度:单片机<FPGA<ASIC
- 性能:单片机<FPGA<ASIC
第2节 FPGA的基本结构
FPGA只能采用一种可以重复配置的结构来实现,查找表(LUT)可以很好满足这一要求,目前主流的FPGA芯片是基于SRAM工艺的查找表。
查找表(LUT,Look-Up-Table),本质上是一个RAM。
以实现数字逻辑 Y = A & B & C Y=A\& B\& C Y=A&B&C的功能为例,对比ASIC和FPGA的实现结构
-
ASIC为了实现该逻辑,逻辑门都已经事先确定好,Y的输出值为两个逻辑与运算后的结果,其基本实现结构如下图所示:
-
FPGA如果要实现同样的逻辑功能,用户首先在 EDA工具中用硬件描述语言写出" Y = A & B & C Y=A\& B\& C Y=A&B&C"逻辑代码;EDA工具分析这一行代码,得出该逻辑代码的真值表;然后软件工具将真值表写到查找表上,从而实现该代码的功能。
FPGA芯片最重要的参考指标
- 可编程逻辑模块的数量
- 固定功能逻辑模块的数目,如乘法器的数目
- 存储器资源的大小,如嵌入式RAM的大小
在最底层的可编程逻辑模块上,存在着基本的两种部件:触发器和查找表。触发器和查找表的组合方式不同,是各个FPGA家族之间区别的重要依据,并且查找表本身的结构也可能各不相同(有4输入或6输入或其他)
第3节 更为复杂的FPGA架构
1985年Xilinx公司推出了第一块FPGA芯片-XC2064,最初的FPGA包含8×8=64的逻辑块阵列和85000个晶体管,其门电路不超过1000个,且每个逻辑块由一个4输入的查找表和其他简单功能模块构成。
22年后,FPGA行业两大巨头Xilinx和Altera公司纷纷推出了采用最近65nm工艺的FPGA产品,其门数量已经达到千万级,晶体管个数更是超过10亿个。
在这22年间,FPGA在紧跟半导体工艺进步的同时也推动了半导体的发展进程——2001年采用150nm工艺、2002年采用130nm工艺,2003年采用90nm工艺,2006年采用65nm工艺。随着技术的发展和工艺节点的进步,FPGA的容量和性能在不断提高的同时,其功耗却不断的优化减少。
在FPGA内部,有“软内核”和“硬内核”之分。
-
比如,如果要利用FPGA的可编程性在芯片内部构造实现一个计数器逻辑,那么在构造计数器逻辑过程中使用到的功能便可以称为“软功能”,又称为软内核。
-
而如果某个功能是直接利用芯片实现的,则利用了芯片内部的“硬功能”,又叫硬内核。比如时间单元等。可以理解为:硬内核是实现固定功能的芯片。
第4节 带嵌入式处理器的FPGA
利用FPGA的可编程构造实现的事情之一即为使用其中的一部分数字逻辑资源制作一个或多个软处理器内核。如果FPGA供应商希望提供一个占用较少硅片面积、消耗较低功率但性能更高的处理器,解决方案是将其实现为硬内核。如果需要高速、高性能的处理器,并且需要实现逻辑编程时,传统的方法是在电路板上放置处理器**(如ARM、DSP等)和FPGA**。
在2010年4月硅谷举行的嵌入式系统大会上,赛灵思发布了可扩展处理平台的架构详情,这款基于ARM处理器的SoC可满足复杂嵌入式系统的高性能、低功耗和多核处理能力要求。该处理器将通用基础双ARM Cortex-A9 MP Core处理器系统作为“主系统”,结合低功耗28nm工艺技术,以实现高度的灵活性、强大的配置功能和高性能。
带有嵌入式处理器的FPGA意味着ARM+FPGA;进一步意味着这种芯片既需要软件工程师编写软件,也需要FPGA工程师搭建周边电路(如ARM的接口、时钟配置和可编程逻辑部分的开发)。
第5节 数据存储以及配置方式
FPGA实现一个逻辑表达式时,会把逻辑表达式的真值表写到LUT上去,断电之后LUT中的内容就清除了。即FPGA中的程序的特点是:断电之后程序就清除了,如果需要运行程序,需要上电之后,重新加载。
通过不同的配置模式,FPGA便会有不同的编程方式。以下为常用的几种配置模式:
- 并行模式:通过并行PROM、Flash配置FPGA;
- 主从模式:使用一片PROM配置多片FPGA;
- 串行模式:串行PROM配置FPGA;
- 外设模式:将FPGA作为微处理器的外设,由微处理器对其编程。
目前,主流的FPGA都是基于SRAM工艺的,在大部分的FPGA开发板上,使用的都是串行配置模式。由于SRAM掉电就会丢失内部数据,因此往往都会外接一个能够掉电保存数据的片外存储器以保存程序。上电时FPGA便将外部存储器(PROM)中的数据读入片内RAM以完成配置,对FPGA编程完成后便进入工作状态;掉电后FPGA内部SRAM中存储的数据丢失,逻辑清零。这种方式配置FPGA不仅能反复使用,还无需重复的手动配置。完成一次主动配置之后每次上电便会自动的实现FPGA的内部编程。
第2章 FPGA开发流程
FPGA的设计流程就是利用EDA开发软件和编程工具对FPGA芯片进行开发的过程。
原理图和HDL(Hardware description language,硬件描述语言)是两种最常用的数字硬件电路描述方法。其中运用HDL设计方法具有更好的移植性、通用性以及利于模块划分的特点。
典型的FPGA的开发流程有9步,如下图所示
- 功能定义/器件选型
- 设计输入
- 功能仿真
- 综合优化
- 综合后仿真
- 时序仿真
- 布线后仿真
- 板级仿真与验证
- 芯片烧录与调试
FPGA需要做的工作是:功能定义/器件选型、设计输入、功能仿真、芯片烧制与调试、静态时序分析(选择做)。总结而言,主要工作就是:需求分析、写代码和调试代码。其他非工作都由工具辅助完成。功能仿真、调试是工作中最花时间的部分
第1节 功能定义/器件选型
FPGA设计项目开始之前,需要进行方案论证、系统设计和FPGA芯片的选型等准备性工作。
在确定并评估好方案后,需要进行系统功能的定义和模块的划分。
FPGA器件选型
- 根据方案中任务要求,如方案中所确定的系统功能以及复杂度,对硬件的工作速度和器件自身的资源、成本以及性能指标等各方面进行综合考虑,选择合适的器件类型。
第2节 设计输入
FPGA系统设计方法
-
设计方法一般采用自顶向下的设计方法。
-
首先将整个系统划分成若干基本模块;然后在将每个基本模块划分成下一层次的基本单元;依次划分后,确定好各个模块的功能以及各个模块需要设计的输入和输出信号;在通过EDA工具进行各个模块的设计。
-
最终要实现的目标:针对每个输入信号,利用EDA工具以及FPGA资源设计出需要的输出信号逻辑。
设计输入是指在EDA工具中代码设计出所希望的系统的过程。通常情况下是使用HDL的方式描绘设计出最终的数字电路。
设计师接触比较多的HDL语言是行为HDL,其主流语言是Verilog HDL和VHDL,这两种语言都是IEEE标准。虽然两者在语法结构以及设计标准上存在差别,但是都有一个共同的特点:语言和芯片工艺无关。在任何一款FPGA上,使用任何一种语言都可以设计出所希望的数字电路。
早期也会用原理图的方法进行输入设计。现在用的很少。
除了两种IEEE标准语言外还有厂商自己的语言。在实际的设计中也可用HDL为主,原理图为辅的混合设计方式,以发挥两者的各自特色与优势。
第3节 功能仿真
功能仿真也称为综合前仿真,用户设计好数字逻辑后需要检查自己的设计是否符合预期,在不需要综合之前通过仿真软件对电路进行逻辑验证。在功能仿真器件电路可以不用考虑延迟等因素,仅对初步的功能进行检验。通过建立测试平台即Testbench,利用波形编译器(仿真软件)和硬件描述语言建立好波形文件和激励信号,在仿真软件上会模拟实际电路的波形显示出输出波形信号,并生成报告文件。用户通过观察各个时间点信号的变化情况来验证自己所设计逻辑的正确性。综合前仿真在FPGA开发过程中不是一定要进行的步骤,但却是极为关键的一步。在实际的工作学习中,充分利用好仿真工具,能够提高设计的效率并及时发现设计缺陷,从而为后续的开发过程提供保障。常用的硬件描述语言的仿真工具有ModelTech公司的ModelSim、VCS、Ncsim以及NC-VHDL等软件。
第4节 综合优化
所谓综合即针对给定的电路实现功能和实现该电路的约束条件,如速度、功耗、成本及电路类型等,通过计算机进行优化处理获得一个能满足上述要求的电路设计方案。也就是说,被综合的文件是HDL文件(或其他相应文件),综合的依据是逻辑设计的描述和各种约束条件,综合的结果则是一个硬件电路的实现方案,该方案必须同时满足预期的功能和约束条件。对于综合来说,满足要求的方案可能有多个,综合器将产生一个最优的或接近最优的结果。因此,综合的过程也就是设计目标的优化过程,最后获得的结构与综合器的工作性能有关。常用的综合工具有Synplicity公司的Synplify/SynplifyPro软件以及各个FPGA厂家自己推出的综合开发工具。
获得的是电路模型,不是真正的电路。
第5节 综合后仿真
综合后仿真是用来检查综合结果是否和原设计一致。后仿真与前仿真的区别在于:前仿真是指综合前的仿真,如在Modelsim对撰写的代码直接进行仿真,而后仿真是综合后的仿真,也就是功能仿真。假设设计师在Modelsim中用HDL编写了一个计数器代码,其通过了行为级的仿真后被加载到quartus或者其他的综合工具中进行综合,完成综合后会生成功能网表,将行为语言转换成寄存器传送级语言,此时设计师再将其加载到Modelsim中进行的仿真被叫做后仿真。后仿真成功后还需要在quartus中进行映射和布局布线,并进行时序分析生成时序网表,描述器件里门或者布线的延时。最后将延时网表和功能网表一起加载到Modelsim中仿真,这一仿真为门级仿真,而在实际的设计过程中,一般来说不做综合后仿真也不会带来太大的影响。
第6节 布线后仿真
布局布线可理解为利用实现工具把逻辑映射到目标器件结构的资源中从而决定逻辑的最佳布局,选择逻辑与输入输出功能链接的布线通道进行连线,并产生相应文件(如配置文件与相关报告)。实现是将综合生成的逻辑网表配置到具体的FPGA芯片上,布局布线是其中最重要的过程。在完成综合之后,就是相当于有了各种元件,但如何建立元件之间的连接,就像在PCB上把元件放在哪里,元件之间的连接以及相连关系又是怎么样的,这个都是布局布线完成的工作,综合的结果可能每次都一样,但是布局布线的结构基本每次都不会一样。布局将逻辑网表中的硬件原语和底层单元合理地配置到芯片内部的固有硬件结构上,并且往往需要在速度最优和面积最优之间作出选择。布线根据布局的拓扑结构,利用芯片内部的各种连线资源,合理正确地连接各个元件。目前,FPGA的结构非常复杂,特别是在有时序约束条件时,需要利用时序驱动的引擎进行布局布线。布线结束后,软件工具会自动生成报告,提供有关设计中各部分资源的使用情况。由于只有FPGA芯片生产商对芯片结构最为了解,所以布局布线必须选择芯片开发商提供的工具。
第7节 时序仿真
时序仿真,也称为后仿真,是指将布局布线的延时信息反标注到设计网表中来检测有无时序违规(即不满足时序约束条件或器件固有的时序规则,如建立时间、保持时间等)现象。时序仿真使用布局布线后器件给出的模块和连线的延时信息,在最坏的情况下对电路的行为作出实际地估计。时序仿真使用的仿真器和功能仿真使用的仿真器是相同的,所需的流程和激励也是相同的,唯一的差别是:时序仿真加载到仿真器的设计包括基于实际布局布线设计的最坏情况的布局布线延时,并且在仿真结果波形图中时序仿真后的信号加载了时延,而功能仿真没有。由于不同芯片的内部延时不一样,不同的布局布线方案也给延时带来不同的影响。因此在布局布线后,通过对系统和各个模块进行时序仿真,分析其时序关系,估计系统性能,以及检查和消除竞争冒险是非常有必要的。在功能仿真中介绍的软件工具一般都支持综合后仿真。
第8节 板级仿真与验证
板级仿真主要应用于高速电路设计中,对高速系统的信号完整性、电磁干扰等特征进行分析,一般都以第三方工具进行仿真和验证,在实际的工作中一般接触较少。
第9节 芯片烧录与调试
设计的最后一步就是芯片的编程与调试。编程是指将FPGA开发工具最后产生使用的数据文件(位数据流文件,BitstreamGeneration)加载到FPGA芯片中。其中,芯片编程需要满足一定的条件,如编程电压、编程时序和编程算法等,而这些条件一般厂家都会事先完成设计,设计师直接按照规范操作即可。数据文件下载到FPGA芯片中以后还需要进行调试验证,逻辑分析仪(LogicAnalyzer,LA)便是FPGA设计的主要调试工具。使用LA需要引出大量的测试管脚,且LA价格昂贵,但是当工程较大、所需要调试观察的信号过多时,仍旧需要LA来对芯片内部的信号进行观察验证。目前,主流的FPGA芯片生产商都提供了内嵌的在线逻辑分析仪(如XilinxISE中的ChipScope、AlteraQuartusII中的SignalTapII以及SignalProb),它们只需要占用芯片少量的逻辑资源便可达到同样的效果,在实际的工程调试中发挥了极大的作用。
第3章 硬件描述语言Verilog
第1节 Verilog的历史
传统硬件电路设计方法:原理图设计法。不适用于大规模电路设计。
传统的设计方法无法满足高级设计的需求,最终出现了借助先进EDA工具的使用硬件描述语言(HDL)的设计方法。设计工程师可以使用HDL来表述自己的设计思想,通过利用EDA工具进行仿真,自动综合到门级电路,最终在ASIC或FPGA实现其功能。
HDL发展至今已有二十多年历史,当今业界标准(IEEE标准)中主要有VHDL和Verilog HDL这两种硬件描述语言。本书采用的是Verilog HDL。
Verilog HDL最初在1983年由Gateway Design Automation公司为其模拟器产品开发的硬件描述语言,当时这只是公司产品的专用语言。随着公司模拟、仿真器产品的广泛使用,Verilog HDL作为一种实用语言逐渐为众多设计者所接受。1990年一次致力于增加语言普及性的活动中,Verilog HDL被推向公众领域从而被更多人熟知。
Open Verilog International(OVI)是促进Verilog发展的国际性组织。1992年OVI决定致力于推广Verilog OVI标准为IEEE标准,这一推广最后获得成功,Verilog语言与1995年成为IEEE标准,称为IEEE Std1364—1995。
第2节 综合和仿真
2.1 综合
在Verilog描述出硬件功能后需要使用综合器对Verilog代码进行解释并将代码转化成实际的电路来表示,最终产生实际的电路,也被称为网表。这种将Verilog代码转成网表的工具就是综合器,Verilog代码转化成网表的过程被称为综合。
QUARTUS、ISE和VIVADO等FPGA开发工具都是综合器,而在集成电路设计领域常用的综合器是DC。
2.2 仿真
如果在编写好代码、综合成电路、烧写到FPGA后才发现问题,此时再去定位问题就会非常地困难。而在综合前,设计师可以在电脑里通过仿真软件对代码进行仿真测试,检测出BUG并将其解决,最后再将程序烧写进FPGA。
需要注意的是:在仿真过程中没有将代码转成电路,仿真器只是对代码进行仿真验证。至于该代码是否可转成电路,仿真器并不关心。
由此可见,Verilog的代码不仅可以描述电路,还可以用于测试。事实上,Verilog定义的语法非常之多,但绝大部分都是为了仿真测试来使用的,只有少部分才是用于电路设计。
Verilog中用于设计的语法是学习的重点,掌握好设计的语法并熟练应用于各种复杂的项目是技能的核心。而其他测试用的语法,在需要时查找和参考就已经足够了。
2.3 可综合设计
Verilog是描述硬件电路的,其建立在硬件电路的基础之上。而有些语法结构只是以仿真测试为目的,是不能与实际硬件电路对应起来的。也就是说在使用这些语法时,将一个语言描述的程序映射成实际硬件电路中的结构是不能实现的,也称为不可综合语法。
“综合”要做的事情有:编译rtl代码,从库里选择用到的门器件,把这些器件按照“逻辑”搭建成“门”电路。
不可综合,是指找不到对应的“门”器件来实现相应的代码。比如“#100”之类的延时功能,简单的门器件是无法实现延时100个单元的,还有打印语句等,也是门器件无法实现的。
下面是不可综合或不推荐使用的代码:
下面是推荐使用的设计代码:
第3节 模块结构
模块(module)是Verilog的基本描述单元,是用于描述某个设计的功能或结构及与其他模块通信的外部端口。
模块在概念上可等同一个器件。
基于模块,Verilog采用模块化的设计方法使得系统更有条理也便于仿真和测试。
对大型的数字电路进行设计时,可以将其分割成大小不一的小模块,每个小模块实现特定的功能,最后通过由顶层模块调用子模块的方式来实现整体功能,这就是自顶向下(Top-Down)的设计思想。
模块有5个主要部分:端口定义、参数定义(可选)、I/O说明、内部信号声明和功能定义。它的一般语法结构如下所示:
3.1模块名和端口定义
其中模块是以module开始,以endmodule结束。模块名是模块唯一的标识符,一般建议模块名尽量用能够描述其功能的名字来命名,并且模块名和文件名相同。
模块的端口表示的是模块的输入和输出口名,也是其与其他模块联系端口的标识。
模块名和端口定义语法格式
module module_name(
端口1,
端口2,
端口3,
);
endmodule
3.2 参数定义
参数定义是将常量用符号代替以增加代码可读性和可修改性。
参数定义的语法格式:
parameter DATA_W = x; // x是一个常数
3.3 接口定义
模块的端口可以是输入端口、输出端口或双向端口。
输入端口语法格式:
input [信号位宽-1: 0] 端口名;
输出端口语法格式:
output [信号位宽-1: 0] 端口名;
双向端口语法格式:
inout [信号位宽-1: 0] 端口名;
3.4 信号类型
信号类型有两种:reg(寄存器型)和wire(线网型)
语法格式如下
reg [信号位宽-1: 0] R变量1;
wire [信号位宽-1: 0] W变量1;
3.5 功能描述
模块中最重要的部分是逻辑功能定义部分,有三种方法可以在模块中产生逻辑
- 使用
assign
声明语句,如描述一个两输入的与门assign a = b & c
- 使用
always
块 - 模块例化,模块例化指:在此模块中调用其他模块。
- 模块例化可以采用位置关联和名字关联,建议按照名字来关联,可能有些关键没有用到,可以在映射中采用空白处理。
3.6 模块例化
信号端口可以通过位置关联,也可以通过名称关联,但是不能混合使用。建议使用名称关联。
举例说明模块的例化
module and (C, A, B);
input A, B;
output C;
// 功能逻辑
endmodule
// 模块and_2就是对模块and的例化
module and_2(XXX);
// 端口定义
// 方式1:例化时,采用位置关联
and A1(T3, A, B);
// 方式2:例化时,采用名称关联
and A2(
.C(T3),
.A(A),
.B(B)
);
endmodule
在实例化中,有些管脚没用到,可在映射中采用空白处理,如:
DFF d1(
.Q(QS),
.Qbar(), // 该管脚悬空
.Data(D),
.Preset(), // 该管脚悬空
.Clock(CK)
);
第4节 信号类型
4.1 信号位宽
定义信号类型的同时,必须定义好信号的位宽。默认信号的位宽是1位,如位宽为1的wire型信号a可直接用wire a
来表示。信号的位宽大于1位时就一定要表示出来,如用wire [7: 0]
来表示该wire信号的位宽为8位。
信号的位宽取决于该信号要表示的最大值。该信号能表示的无符号数最大值是:2n-1,其中n表示该信号的位宽。
4.2 线网类型wire
线网类型代表的就是物理连接线,因此其不存储逻辑值,必须由器件驱动。通常用assign进行赋值。
定义wire型变量如下所示:
wire [3: 0] Sat; // 4位线型信号
wire Cnt; // 1位线型信号
wire [31: 0] Kisp, pisp, Lisp; // 32位线型信号
4.3 寄存器类型reg
reg是最常用的寄存器类型,寄存器类型通常用于对存储单元的描述,如D型触发器、ROM等。
寄存器类型信号的特点是:在某种触发机制下分配了一个值,在下一个触发机制到来之前保留原值。
值得注意的是:reg类型的变量不一定是存储单元,如在always语句中进行描述的必须使用reg类型的变量。
定义reg类型变量如下所示:
reg [3: 0] Sat; // 4位寄存器型信号
reg Cnt; // 1位寄存器
reg [31: 0] Kisp; // 32位寄存器
4.4 wire和reg的区别
reg型信号并不一定生成寄存器。
针对何时使用reg何时使用wire,此处总结一套方法:在本模块中使用always设计的信号都定义为reg型,其他信号都定义为wire型。
第5节 功能描述-组合逻辑
5.1 assign语句
assign语句是连续赋值语句,一般是将一个变量的值不间断地赋值到另一个变量,两个变量之间就类似于被导线连在了一起,习惯上当做连线用。
assign语句的基本格式
assign a = b (逻辑运算符) c;
assign语句的功能属于组合逻辑的范畴,应用范围可以概括为以下几点:
- 持续赋值
- 连线
- 对wire型变量赋值
需要说明的是:多条assign语句之间相互独立、并行执行。
5.2 组合逻辑always语句
always语句是条件循环语句,执行机制是通过一个称为敏感变量表的事件驱动来实现的。always语句的基本格式是:
always @(敏感事件)begin
程序语句
end
always
是@
后面跟着事件。整个always
的意思是:当敏感事件的条件满足时,就执行一次“程序语句”。敏感事件每满足一次,就执行“程序语句”一次。
示例代码
always @(a or b or c or sel)begin
if(sel == 0)
c = a + b
else
c = a + d
end
示例代码的意思是当信号a或信号b或信号d或信号sel发生变化时,就执行2-5行。sel值为0时,c的结果为a+b;sel值不为0时,c的结果为a+d。
当敏感信号非常多时,很容易把敏感信号遗漏,为了避免这种情况可以用*
来代替。这个*
指的是“程序语句”中所有的条件信号,即a、b、d、sel(不包括c),推荐这种写法。具体代码如下所示:
always @(*)begin
if (sel==0)
c = a + b
else
c = a + d
end
这种条件信号变化则结果立即变化的always语句被称为“组合逻辑”。
注意点
- 组合逻辑always语句中敏感变量必须写全,或者用
*
代替 - 组合逻辑器件的赋值采用阻塞赋值“=”,时序逻辑器件的赋值语句采用非阻塞赋值
<=
。
5.3 数字进制
数字表示方式
在Verilog中数字表示方式最常用的格式是:<位宽>'<基数><数值>
,如4'b1011
。
位宽,通俗理解就是位宽是几就有几根线。可选项,如果没有这一项就可以通过常量的值进行推断。如b10010
可以推断出位宽为5。
基数,表示数值的进制。可以是二进制(b或B)、十进制(d或D)、八进制(o或O)、十六进制(h或H)。可选项,默认为十进制。
数值,是由基数所决定的表示常量真实值的一串ASCII码。如果基数定义为b或B,数值可以是0、1、x或X(不定态)、z或Z(高阻态)。其他进制不可以有x或z。位宽比数值的位数大,数值高位补零,位宽比数值的位数小,数值取低位。
二进制
二进制是FPGA中最重要的进制。
二进制表示数
二进制表示有符号数,加一根线表示符号。
二进制表示小数
不管表示小数还是整数,可以自定义二进制数值表(自己对数值进行编码)。
不定态
数字电路中只有高电平和低电平,分别用1和0表示。但是代码中常常能见到如1'bx
和1'bz
这样的代码。数字电路中没有实际的电平来对应x和z。x和z更多地是用来表示设计者的意图或用于仿真目的,旨在告诉综合器和仿真器如何解释这段代码。
x态,称为不定态,常用于判断条件,旨在告诉综合工具,无论电平是0或1都可以。
判断条件din==4'b10x0
等价于din==4'b1000||din==4'b1010
,其中||
是“或”符号。
注意点:
- 电路中没有不定态,只有高电平和低电平
- 不定态只能用于表示不关注电路是高电平还是低电平
- 建议所有信号写成明确的0或1,不要写成不定态
高阻态
z态,称为高阻态,表示设计者不驱动这个信号(既不给0又不给1),通常用于三态门接口当中。
硬件上使用三态门是为了减少管脚,而fpga内部没有必要减少连线,所以在fpga内部使用高阻态‘z’是没有意义的,建议不要使用高阻态z。
这样的电路是由下面两行代码实现的
assign data = (wr_en==1)?wr_data:1'bz;
assign rd_data = data;
总的来说高阻态“z”是表示“不驱动总线”这个行为。
5.4 算数运算符
尽量避免除法和求余,如果一定要用到除法,尽量让除数为2的n次方。
加减是最简单的运算,而乘法可以拆解为多个加法运算。加减乘对应的电路都比较小。
位宽问题
FPGA开发中,需要注意信号的位宽,运算的最终结果取决于“=”左边信号的位宽,保留低位,丢弃高位。以减法为例
补码问题
- 使用加法的时候,为了保证结果的正确性,必须要保留进位,也就是结果要扩展位宽。
- 正数的补码不变,负数的补码是数值位取反后加1。
- FPGA及其他计算机系统中,所有数据的保存方式都是以补码的方式保存。
5.5 逻辑运算符
逻辑与(符号:&&
)
-
1位逻辑与
电路图如图
说明:A和 B 都为 1 , C 为 1; 否则 C 为 0。
verilog代码示例
reg A, B; always@(*)begin C = A && B; end
-
多位逻辑与
电路图如图
说明:A或 B 都不 为 0 时,C 为 1, 否则 为 0。
verilog代码示例
reg[2: 0] A, B, C; always@(*)begin C = A && B; end
逻辑或(符号:||
)
-
1位逻辑或
电路图如图
说明:A和 B 其中 1 个 为 1, C 为 1;否 则 C 为 0。
verilog代码示例
reg A, B; always@(*)begin C = A || B; end
-
多位逻辑或
电路图如图
说明:A和 B 其中 1 个 非 0, C 为 1;否 则 C 为 0。
verilog代码示例
reg[2: 0] A, B, C; always@(*)begin C = A || B; end
逻辑非
verilog代码示例
if(!a)begin{
}
end
总结
- 进行逻辑运算的时候,如果操作数是多位的,则可以将操作数看做整体,若操作数中每一位都是0值则为逻辑0值;若操作数中至少存在一个1则为逻辑1值。非零为真
- 双目逻辑运算符
&&
和||
的优先级高于算术运算符,!
的优先级高于双目逻辑运算符。 - 为了提高程序的可读性,多用括号区分运算符的优先级
- 多用
逻辑与
和逻辑或
,少用逻辑非
5.6 按位逻辑运算符
Verilog语言中有4种按位运算符
-
~
:一元非,相当于非门运算 -
&
:二元与,相当于与门运算 -
|
:二元或,相当于或门运算 -
^
:二元异或,相当于异或门运算
这4个按位运算符有如下几种用法
-
单目按位与
表示对一个信号进行每位之间相与的操作,例子如下
-
单目按位或
表示对一个信号每位进行相或的操作,例子如下
-
单目按位非
表示对一个信号进行每位取反的操作
-
双目按位与
表示对两个信号进行对应位相与的操作
如果操作数长度不相等,长度较小的操作数在最左侧添0补位
-
双目按位或
表示对两个信号进行对应位相或的操作
如果操作数长度不相等,长度较小的操作数在最左侧添0补位
-
双目按位异或
表示对两个信号进行对应位相异或的操作
如果操作数长度不相等,长度较小的操作数在最左侧添0补位
5.7 关系运算符
关系运算符有:>(大于)、<(小于)、>=(不小于)、<=(不大于)、==(逻辑相等)和!=(逻辑不等)。
关系操作符的结果为真(1)或假(0)。如果操作数中有一位为x或z,那么结果为x。
如果操作数长度不同,长度较短的操作数在最重要的位方向(左方)添0补齐。
5.8 移位运算符
在VerilogHDL中有两种移位运算符,分别为“<<”(左移位运算符)和“>>”(右移位运算符)。下面分别介绍两者的用法
移位操作实际上是选哪根线相连
左移
-
左移操作属于逻辑移位,需要用0来填补移出的空位,即在低位补0。
-
左移操作需根据位宽储存结果,例子如下
-
左移操作的操作数可以是常数,也可以是信号。同样,左移操作的移位数可以是常数,也可以是信号。
右移
- 右移操作属于逻辑移位,需要用0来填补移出的空位,即在高位补0,补多少个0,取决于保存结果的信号的位宽。
- 与左移操作相似,右移操作是不消耗逻辑资源的,其只是线的连接。
- 移操作的移位数可以是常数,也可以是信号。
注意
- 通过左移实现乘法
- 通过右移实现除法
- 利用左移产生独热码(one-hot code)
5.9 条件运算符
条件语句的三种常见形式,如下所示:
Verilog中条件语句共有4种形式
- 三目运算符
- if语句
- case语句
- 选择语句
三目运算符
-
语法格式
条件表达式? 真表达式: 假表达式; condition_expr? true_expr: false_expr;
其含义为:当
条件表达式
为真时,执行真表达式
;当条件表达式
为假时,执行假表达式。 -
示例
always@(*) begin r=s? t: u; end
其对应的电路图如下:
-
条件表达式的作用实际上类似于多路选择器,其可以用if-else语句代替
-
条件运算符可用在数据流建模中的条件赋值,这种情况下条件表达式的作用相当于控制开关。
-
条件三目表达式可以嵌套使用
if语句
-
条件表达式需要使用括号括起来
-
如果存在if-if语句,可能会有二义性,需要使用语句
begin--end
-
简单示例
if(Sum < 60)begin Grade = C; Total_C = Total_C + 1; end else if(Sum < 75)begin Grade = B; Total_B = Total_B + 1; end else begin Grade = A; Total_A = Total_A + 1; end
case语句
-
语法
case (<expression>) case_item1 : <single statement> case_item2, case_item3 : <single statement> case_item4 : begin <multiple statements> end default : <statement> endcase
-
建议:case语句的缺省项必须写,防止产生锁存器。
选择语句
-
语法
vect[a +: b] 或 vect[a -: b] 1. vect为变量名 2. a为起始位置,可以是常数也可以是信号 3. 加号或者减号代表升序或者降序 4. b表示位宽,只能是常数
-
vect[a +: b]
等价于vect[a : a+b-1]
,示例如下:vect[7 +: 3]==vect[7 : 9]
vect[9 -: 4]==vect[9 : 6]
-
选择语句的实际用法如下
需求:当cnt==0时,将
din[7: 0]
赋值给data[15: 8]
;当cnt==1
时,将din[7: 0]
赋值给data[7: 0]
实现:
data[15-8*cnt -: 8] <= din[7: 0]
-
选择语句其本质上时一个选择器
总结
- 初学者喜欢使用if-else,但case语句电路时延更低
- if-else有优先级,综合会得到类似级联的结构;case无优先级,电路是并行的会得到多路选择器
- 虽然在RTL视图中,两种语句综合出的电路存在着较大的差别,但是在布局布线时会自动对电路进行优化,最终映射到FPGA上电路基本毫无差别。
5.10 拼接运算符
拼接运算符不消耗硬件资源,只是把线换一种组合方式,参照实例如下:
wire [7: 0] Dbus;
// 以反转的顺序将低端4位赋给高端4位
assign Dbus[7: 4] = {Dbus[0], Dbus[1], Dbus[2], Dbus[3]};
// 高4位与低4位交换
assign Dbus = {Dbus[3: 0], Dbus[7: 4]};
第6节 功能描述- 时序逻辑
6.1 always语句
时序逻辑的代码一般有两种
- 同步复位的时序逻辑
- 异步复位的时序逻辑
同步复位的时序逻辑中,复位不是立即有效,而是沿着时钟上升沿时复位才有效,代码结构如下
always @(posedge clk)begin
if (rst_n == 1'b0)
// 代码语句
else begin
// 代码语句
end
end
异步复位的时序逻辑中,复位立即有效,与时钟无关,其代码结构如下:
always @(posedge clk or negedge rst_n)begin
if (rst_n==1'b0)
// 代码语句
else begin
// 代码语句
end
end
为了教学方便,本课程采用异步时钟逻辑
6.2 D触发器
数字电路中介绍了多种触发器,如JK触发器、D触发器、RS触发器、T触发器等
在FPGA中使用的是最简单的D触发器
D触发器结构和功能
上图为D触发器的结构图,可以将其视为一个芯片,该芯片拥有4个管脚,其中3个是输入管脚,1个输出管脚
- 输入管脚
- 时钟:clk
- 复位:rst_n
- 信号:d
- 输出管脚
- 输出:q
D触发器的功能
- 当复位rst_n管脚为0时,输出q立刻为0
- 当复位rst_n管脚为1时
- 在时钟clk的上升沿时,将信号d的值传给q
- 时钟clk不是上升沿时,d变化,q不会发生变化
D触发器的波形图示例
D触发器的Verilog代码
always @(posedge clk or negedge rst_n)begin
if (rst_n==1'b0)begin
q<=0;
end
else begin
q<=d;
end
end
理想波形图和实际波形图
先有时钟上升沿,此为因,然后再将d的值赋给q,这才是结果。这个因果是有先后关系的,对于硬件来说这个“先后”无论是多么地迅速,也一定会占用一定时间,所以q的变化会稍后于clk的上升沿。例如下图就是硬件的实际变化情况。
上升沿时刻输出信号的值与上升沿前一瞬间输出信号的值相同
6.3 时钟
时钟信号就是每隔固定时间上下变化的信号
- 时钟周期:本次上升沿和上一次上升沿之间占用的时间
- 时钟频率:时钟周期的倒数
- 占空比:高电平占整个时钟周期的时间
FPGA时钟的占空比一般是50%。占空比对与FPGA而言没有太大的意义,因为FPGA使用的是时钟上升沿来触发,设计师们更加关心的是时钟频率。
普通FPGA所支持的时钟频率不超过150M,高端器件一般不超过700M。FPGA所对应的时钟周期一般在纳秒级。
时钟是FPGA中最重要的信号,其他所有信号在时钟的上升沿统一变化,这就像军队的令旗。所有士兵看到令旗到来的时刻,执行已经设定好的命令。
FPGA系统的时钟越少越好,最好只存在一个时钟。不要用自制的分频时钟;不要敏感列表检测信号的上升沿。
6.4 时序逻辑代码和硬件
always @(posedge clk or negedge rst_n)begin
if (rst_n==1'b0)begin
q<=0;
end
else begin
q<=a+d;
end
end
上面代码的电路图表示如下所示:
上面代码也可以如下表示,两份代码表示的电路图完全一样,两种写法视情况而定。下面的写法即为组合逻辑+D触发器。
assign c=a+d;
always @(posedge clk or negedge rst_n)begin
if (rst_n==1'b0)begin
q<=0;
end
else begin
q<=a+d;
end
end
6.5 阻塞赋值和非阻塞赋值
Verilog语言支持两种类型的赋值:
- 阻塞赋值:使用
=
语句。在一个begin...end
中,阻塞赋值是按照赋值顺序一行一行执行的。 - 非阻塞赋值:使用
<=
语句。在一个begin...end
中,在同一时间内同时赋值
例如下面的代码
begin
c=a;
d=c+a;
end
begin
c<=a;
d<=c+a;
end
语法层面分析上述代码如下所示:
规范要求:组合逻辑都使用阻塞赋值,时序逻辑都使用非阻塞赋值。制定这个规范的原因并不是考虑语法需要,而是为了正确的进行硬件描述。
第4章 FPGA开发平台介绍
第1节 开发环境
目前两大FPGA制造商分别是Xilinx(目前被AMD收购)和Altera(目前被Intel收购)。Xilinx芯片的开发工具包括vivado和ISE,Altera芯片的开发工具是Quartus。
本课程使用的开发板是XILINX-K7芯片
第2节 软件界面
Vivado
第3节 第一个上板例程
本节关键点
- 新建工程
- 使用Verilog代码录入设计
- 将综合的电路布局到Xilinx FPGA
- 分配电路的输入输出到FPGA上的指定引脚
- 编程配置教学板上的FPGA芯片
3.1 新建工程
-
填写工程名和工程位置
-
project type一般为
RTL Project
-
新的空白工程一般不用
Add Source
和Add Constraints
-
选择默认的Xilinx器件
3.2 代码设计
-
新建.v文件:File-> Text Editor->New File
-
在工程目录下新建src目录
-
在src目录下新建
light.v
文件
-
-
Add Sources:点击Add Sources按钮,选中第二个单选按钮
Add or create design sources
,将light.v
添加进去
3.3 编译设计电路
完成代码设计之后,需要经过Vivado软件中的几个工具的处理,分别是:
- 分析代码
- 综合电路
- 生成目标芯片的实现内容
这些应用工具被聚集在一起,统称为编译器。
选择Vivado左侧下方的PROGRAM AND DEBUG->Generate Bitstream
运行编译器。
编译一遍一般会报错,提示没有配置引脚
3.4 引脚分配
-
点击Vivado左侧下方的
IMPlEMENTATION->Open Implemented Design
-
切换到
I/O Planning
视图 -
配置好引脚
-
Ctrl+S
,保存约束 -
重新编译
3.5 下载和调试
-
点击
PROGRAM AND DEBUG->Open Hardware Manager->Open Target->Auto Connect
-
点击
PROGRAM AND DEBUG->Open Hardware Manager->Program Device
,把编译生成的Bit流文件下载到开发板上文章来源:https://www.toymoban.com/news/detail-481280.html -
在开发板上拨动开关进行调试文章来源地址https://www.toymoban.com/news/detail-481280.html
- 调试的时候发现与预期不符合,就需要定位问题,此时将使用到ILA在线调试工具,具体使用方法见下一章。
- 完成调试之后,工程最终确定之后,需要把工程固化到开发板里面,让开发板只要上电就可以运行工程,而不需要重新下载。具体方法如下:
Settings->Project Settings->Bitstream->勾选-bin_file
- 重新编译一次生成bin文件,生成的文件会放在
工程目录->工程名.runs->impl_1
目录下 - 在保证连接开发板的情况下,点击
PROGRAM AND DEBUG->Open Hardware Manager->Add Configuration Memory Device
- 选择Flash芯片的型号
- 选择生成的bin文件,将工程固化到fpga中
到了这里,关于《FPGA至简设计原理与应用》学习笔记1 —— FPGA基础的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!